Coverage for C:\Repos\ekr-pylint\pylint\config\utils.py: 22%
98 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
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
5"""Utils for arguments/options parsing and handling."""
7from __future__ import annotations
9import re
10import warnings
11from collections.abc import Callable, Sequence
12from pathlib import Path
13from typing import TYPE_CHECKING, Any
15from pylint import extensions, utils
16from pylint.config.argument import (
17 _CallableArgument,
18 _ExtendArgument,
19 _StoreArgument,
20 _StoreNewNamesArgument,
21 _StoreOldNamesArgument,
22 _StoreTrueArgument,
23)
24from pylint.config.callback_actions import _CallbackAction
25from pylint.config.exceptions import ArgumentPreprocessingError
27if TYPE_CHECKING:
28 from pylint.lint.run import Run
31def _convert_option_to_argument(
32 opt: str, optdict: dict[str, Any]
33) -> (
34 _StoreArgument
35 | _StoreTrueArgument
36 | _CallableArgument
37 | _StoreOldNamesArgument
38 | _StoreNewNamesArgument
39 | _ExtendArgument
40):
41 """Convert an optdict to an Argument class instance."""
42 if "level" in optdict and "hide" not in optdict:
43 warnings.warn(
44 "The 'level' key in optdicts has been deprecated. "
45 "Use 'hide' with a boolean to hide an option from the help message.",
46 DeprecationWarning,
47 )
49 # Get the long and short flags
50 flags = [f"--{opt}"]
51 if "short" in optdict:
52 flags += [f"-{optdict['short']}"]
54 # Get the action type
55 action = optdict.get("action", "store")
57 if action == "store_true":
58 return _StoreTrueArgument(
59 flags=flags,
60 action=action,
61 default=optdict.get("default", True),
62 arg_help=optdict.get("help", ""),
63 hide_help=optdict.get("hide", False),
64 section=optdict.get("group", None),
65 )
66 if not isinstance(action, str) and issubclass(action, _CallbackAction):
67 return _CallableArgument(
68 flags=flags,
69 action=action,
70 arg_help=optdict.get("help", ""),
71 kwargs=optdict.get("kwargs", {}),
72 hide_help=optdict.get("hide", False),
73 section=optdict.get("group", None),
74 metavar=optdict.get("metavar", None),
75 )
76 try:
77 default = optdict["default"]
78 except KeyError:
79 warnings.warn(
80 "An option dictionary should have a 'default' key to specify "
81 "the option's default value. This key will be required in pylint "
82 "3.0. It is not required for 'store_true' and callable actions.",
83 DeprecationWarning,
84 )
85 default = None
86 if action == "extend":
87 return _ExtendArgument(
88 flags=flags,
89 action=action,
90 default=[] if default is None else default,
91 arg_type=optdict["type"],
92 choices=optdict.get("choices", None),
93 arg_help=optdict.get("help", ""),
94 metavar=optdict.get("metavar", ""),
95 hide_help=optdict.get("hide", False),
96 section=optdict.get("group", None),
97 dest=optdict.get("dest", None),
98 )
99 if "kwargs" in optdict:
100 if "old_names" in optdict["kwargs"]:
101 return _StoreOldNamesArgument(
102 flags=flags,
103 default=default,
104 arg_type=optdict["type"],
105 choices=optdict.get("choices", None),
106 arg_help=optdict.get("help", ""),
107 metavar=optdict.get("metavar", ""),
108 hide_help=optdict.get("hide", False),
109 kwargs=optdict.get("kwargs", {}),
110 section=optdict.get("group", None),
111 )
112 if "new_names" in optdict["kwargs"]:
113 return _StoreNewNamesArgument(
114 flags=flags,
115 default=default,
116 arg_type=optdict["type"],
117 choices=optdict.get("choices", None),
118 arg_help=optdict.get("help", ""),
119 metavar=optdict.get("metavar", ""),
120 hide_help=optdict.get("hide", False),
121 kwargs=optdict.get("kwargs", {}),
122 section=optdict.get("group", None),
123 )
124 if "dest" in optdict:
125 return _StoreOldNamesArgument(
126 flags=flags,
127 default=default,
128 arg_type=optdict["type"],
129 choices=optdict.get("choices", None),
130 arg_help=optdict.get("help", ""),
131 metavar=optdict.get("metavar", ""),
132 hide_help=optdict.get("hide", False),
133 kwargs={"old_names": [optdict["dest"]]},
134 section=optdict.get("group", None),
135 )
136 return _StoreArgument(
137 flags=flags,
138 action=action,
139 default=default,
140 arg_type=optdict["type"],
141 choices=optdict.get("choices", None),
142 arg_help=optdict.get("help", ""),
143 metavar=optdict.get("metavar", ""),
144 hide_help=optdict.get("hide", False),
145 section=optdict.get("group", None),
146 )
149def _parse_rich_type_value(value: Any) -> str:
150 """Parse rich (toml) types into strings."""
151 if isinstance(value, (list, tuple)):
152 return ",".join(_parse_rich_type_value(i) for i in value)
153 if isinstance(value, re.Pattern):
154 return value.pattern
155 if isinstance(value, dict):
156 return ",".join(f"{k}:{v}" for k, v in value.items())
157 return str(value)
160# pylint: disable-next=unused-argument
161def _init_hook(run: Run, value: str | None) -> None:
162 """Execute arbitrary code from the init_hook.
164 This can be used to set the 'sys.path' for example.
165 """
166 assert value is not None
167 exec(value) # pylint: disable=exec-used
170def _set_rcfile(run: Run, value: str | None) -> None:
171 """Set the rcfile."""
172 assert value is not None
173 run._rcfile = value
176def _set_output(run: Run, value: str | None) -> None:
177 """Set the output."""
178 assert value is not None
179 run._output = value
182def _add_plugins(run: Run, value: str | None) -> None:
183 """Add plugins to the list of loadable plugins."""
184 assert value is not None
185 run._plugins.extend(utils._splitstrip(value))
188def _set_verbose_mode(run: Run, value: str | None) -> None:
189 assert value is None
190 run.verbose = True
193def _enable_all_extensions(run: Run, value: str | None) -> None:
194 """Enable all extensions."""
195 assert value is None
196 for filename in Path(extensions.__file__).parent.iterdir():
197 if filename.suffix == ".py" and not filename.stem.startswith("_"):
198 extension_name = f"pylint.extensions.{filename.stem}"
199 if extension_name not in run._plugins:
200 run._plugins.append(extension_name)
203PREPROCESSABLE_OPTIONS: dict[
204 str, tuple[bool, Callable[[Run, str | None], None]]
205] = { # pylint: disable=consider-using-namedtuple-or-dataclass
206 "--init-hook": (True, _init_hook),
207 "--rcfile": (True, _set_rcfile),
208 "--output": (True, _set_output),
209 "--load-plugins": (True, _add_plugins),
210 "--verbose": (False, _set_verbose_mode),
211 "-v": (False, _set_verbose_mode),
212 "--enable-all-extensions": (False, _enable_all_extensions),
213}
216def _preprocess_options(run: Run, args: Sequence[str]) -> list[str]:
217 """Pre-process options before full config parsing has started."""
218 processed_args: list[str] = []
220 i = 0
221 while i < len(args):
222 argument = args[i]
223 if not argument.startswith("-"):
224 processed_args.append(argument)
225 i += 1
226 continue
228 try:
229 option, value = argument.split("=", 1)
230 except ValueError:
231 option, value = argument, None
233 if option not in PREPROCESSABLE_OPTIONS:
234 processed_args.append(argument)
235 i += 1
236 continue
238 takearg, cb = PREPROCESSABLE_OPTIONS[option]
240 if takearg and value is None:
241 i += 1
242 if i >= len(args) or args[i].startswith("-"):
243 raise ArgumentPreprocessingError(f"Option {option} expects a value")
244 value = args[i]
245 elif not takearg and value is not None:
246 raise ArgumentPreprocessingError(f"Option {option} doesn't expects a value")
248 cb(run, value)
249 i += 1
251 return processed_args