Skip to content

Commit d27c622

Browse files
committed
Add generate subparser to pylint-config
1 parent c303a45 commit d27c622

File tree

6 files changed

+180
-0
lines changed

6 files changed

+180
-0
lines changed

pylint/config/_pylint_config/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
Everything in this module is private.
88
"""
99

10+
from pylint.config._pylint_config.main import _handle_pylint_config_commands # noqa
1011
from pylint.config._pylint_config.setup import _register_generate_config_options # noqa
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
"""Everything related to the 'pylint-config generate' command."""
6+
7+
8+
from __future__ import annotations
9+
10+
import warnings
11+
from typing import TYPE_CHECKING
12+
13+
from pylint.config._pylint_config import utils
14+
from pylint.config._pylint_config.help_message import get_subparser_help
15+
16+
if TYPE_CHECKING:
17+
from pylint.lint.pylinter import PyLinter
18+
19+
20+
def generate_interactive_config(linter: PyLinter) -> None:
21+
print("Starting interactive pylint configuration generation")
22+
23+
format_type = utils.get_and_validate_format()
24+
25+
if format_type == "toml":
26+
linter._generate_config_file()
27+
else:
28+
with warnings.catch_warnings():
29+
warnings.filterwarnings("ignore", category=DeprecationWarning)
30+
linter.generate_config(skipsections=("Commands",))
31+
32+
33+
def handle_generate_command(linter: PyLinter) -> int:
34+
"""Handle 'pylint-config generate'."""
35+
# Interactively generate a pylint configuration
36+
if linter.config.interactive:
37+
generate_interactive_config(linter)
38+
return 0
39+
print(get_subparser_help(linter, "generate"))
40+
return 32

pylint/config/_pylint_config/main.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
"""Everything related to the 'pylint-config' command."""
6+
7+
8+
from __future__ import annotations
9+
10+
from typing import TYPE_CHECKING
11+
12+
from pylint.config._pylint_config.generate_command import handle_generate_command
13+
from pylint.config._pylint_config.help_message import get_help
14+
15+
if TYPE_CHECKING:
16+
from pylint.lint.pylinter import PyLinter
17+
18+
19+
def _handle_pylint_config_commands(linter: PyLinter) -> int:
20+
"""Handle whichever command is passed to 'pylint-config'."""
21+
if linter.config.config_subcommand == "generate":
22+
return handle_generate_command(linter)
23+
24+
print(get_help(linter._arg_parser))
25+
return 32

pylint/config/_pylint_config/utils.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
"""Utils for the 'pylint-config' command."""
6+
7+
8+
import sys
9+
10+
if sys.version_info >= (3, 8):
11+
from typing import Literal
12+
else:
13+
from typing_extensions import Literal
14+
15+
16+
SUPPORTED_FORMATS = {"t", "toml", "i", "ini"}
17+
18+
19+
def get_and_validate_format() -> Literal["toml", "ini"]:
20+
"""Make sure that the output format is either .toml or .ini."""
21+
# pylint: disable-next=bad-builtin
22+
format_type = input(
23+
"Please choose the format of configuration, (T)oml or (I)ni (.cfg): "
24+
).lower()
25+
26+
if format_type not in SUPPORTED_FORMATS:
27+
raise ValueError(
28+
f"Format should be one of {', '.join(i.capitalize() for i in sorted(SUPPORTED_FORMATS))}"
29+
)
30+
31+
if format_type.startswith("t"):
32+
return "toml"
33+
return "ini"

pylint/lint/run.py

+12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from pylint import config
1515
from pylint.config._pylint_config import (
16+
_handle_pylint_config_commands,
1617
_register_generate_config_options,
1718
)
1819
from pylint.config.config_initialization import _config_initialization
@@ -149,6 +150,17 @@ def __init__(
149150
linter, args, reporter, config_file=self._rcfile, verbose_mode=self.verbose
150151
)
151152

153+
# Handle the 'pylint-config' command
154+
if self._is_pylint_config:
155+
warnings.warn(
156+
"NOTE: The 'pylint-config' command is experimental and usage can change",
157+
UserWarning,
158+
)
159+
code = _handle_pylint_config_commands(linter)
160+
if exit:
161+
sys.exit(code)
162+
return
163+
152164
# Display help messages if there are no files to lint
153165
if not args:
154166
print(linter.help())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
"""Test for the 'pylint-config generate' command."""
6+
7+
8+
import warnings
9+
10+
import pytest
11+
from pytest import CaptureFixture, MonkeyPatch
12+
13+
from pylint.lint.run import _PylintConfigRun as Run
14+
15+
16+
def test_generate_interactive_exitcode(monkeypatch: MonkeyPatch) -> None:
17+
"""Check that we exit correctly based on different parameters."""
18+
# Monkeypatch everything we don't want to check in this test
19+
monkeypatch.setattr(
20+
"pylint.config._pylint_config.utils.get_and_validate_format", lambda: "toml"
21+
)
22+
23+
with warnings.catch_warnings():
24+
warnings.filterwarnings("ignore", message="NOTE:.*", category=UserWarning)
25+
with pytest.raises(SystemExit) as ex:
26+
Run(["generate", "--interactive"])
27+
assert ex.value.code == 0
28+
29+
Run(["generate", "--interactive"], exit=False)
30+
31+
32+
def test_format_of_output(
33+
monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]
34+
) -> None:
35+
"""Check that we output the correct format."""
36+
# Set the answers needed for the input() calls
37+
answers = iter(["T", "toml", "TOML", "I", "INI", "TOMLINI"])
38+
monkeypatch.setattr("builtins.input", lambda x: next(answers))
39+
40+
with warnings.catch_warnings():
41+
warnings.filterwarnings("ignore", message="NOTE:.*", category=UserWarning)
42+
# Check 'T'
43+
Run(["generate", "--interactive"], exit=False)
44+
captured = capsys.readouterr()
45+
assert "[tool.pylint.main]" in captured.out
46+
47+
# Check 'toml'
48+
Run(["generate", "--interactive"], exit=False)
49+
captured = capsys.readouterr()
50+
assert "[tool.pylint.main]" in captured.out
51+
52+
# Check 'TOML'
53+
Run(["generate", "--interactive"], exit=False)
54+
captured = capsys.readouterr()
55+
assert "[tool.pylint.main]" in captured.out
56+
57+
# Check 'I'
58+
Run(["generate", "--interactive"], exit=False)
59+
captured = capsys.readouterr()
60+
assert "[MAIN]" in captured.out
61+
62+
# Check 'INI'
63+
Run(["generate", "--interactive"], exit=False)
64+
captured = capsys.readouterr()
65+
assert "[MAIN]" in captured.out
66+
67+
# Check 'TOMLINI'
68+
with pytest.raises(ValueError, match="Format should be one.*"):
69+
Run(["generate", "--interactive"], exit=False)

0 commit comments

Comments
 (0)