Coverage for C:\Repos\ekr-pylint\pylint\config\argument.py: 36%
133 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"""Definition of an Argument class and transformers for various argument types.
7An Argument instance represents a pylint option to be handled by an argparse.ArgumentParser
8"""
10from __future__ import annotations
12import argparse
13import os
14import pathlib
15import re
16import sys
17from collections.abc import Callable
18from typing import Any, Pattern, Sequence, Tuple, Union
20from pylint import interfaces
21from pylint import utils as pylint_utils
22from pylint.config.callback_actions import _CallbackAction, _ExtendAction
23from pylint.config.deprecation_actions import _NewNamesAction, _OldNamesAction
24from pylint.constants import PY38_PLUS
26if sys.version_info >= (3, 8):
27 from typing import Literal
28else:
29 from typing_extensions import Literal
32_ArgumentTypes = Union[
33 str,
34 int,
35 float,
36 bool,
37 Pattern[str],
38 Sequence[str],
39 Sequence[Pattern[str]],
40 Tuple[int, ...],
41]
42"""List of possible argument types."""
45def _confidence_transformer(value: str) -> Sequence[str]:
46 """Transforms a comma separated string of confidence values."""
47 if not value:
48 return interfaces.CONFIDENCE_LEVEL_NAMES
49 values = pylint_utils._check_csv(value)
50 for confidence in values:
51 if confidence not in interfaces.CONFIDENCE_LEVEL_NAMES:
52 raise argparse.ArgumentTypeError(
53 f"{value} should be in {*interfaces.CONFIDENCE_LEVEL_NAMES,}"
54 )
55 return values
58def _csv_transformer(value: str) -> Sequence[str]:
59 """Transforms a comma separated string."""
60 return pylint_utils._check_csv(value)
63YES_VALUES = {"y", "yes", "true"}
64NO_VALUES = {"n", "no", "false"}
67def _yn_transformer(value: str) -> bool:
68 """Transforms a yes/no or stringified bool into a bool."""
69 value = value.lower()
70 if value in YES_VALUES:
71 return True
72 if value in NO_VALUES:
73 return False
74 raise argparse.ArgumentTypeError(
75 None, f"Invalid yn value '{value}', should be in {*YES_VALUES, *NO_VALUES}"
76 )
79def _non_empty_string_transformer(value: str) -> str:
80 """Check that a string is not empty and remove quotes."""
81 if not value:
82 raise argparse.ArgumentTypeError("Option cannot be an empty string.")
83 return pylint_utils._unquote(value)
86def _path_transformer(value: str) -> str:
87 """Expand user and variables in a path."""
88 return os.path.expandvars(os.path.expanduser(value))
91def _py_version_transformer(value: str) -> tuple[int, ...]:
92 """Transforms a version string into a version tuple."""
93 try:
94 version = tuple(int(val) for val in value.replace(",", ".").split("."))
95 except ValueError:
96 raise argparse.ArgumentTypeError(
97 f"{value} has an invalid format, should be a version string. E.g., '3.8'"
98 ) from None
99 return version
102def _regexp_csv_transfomer(value: str) -> Sequence[Pattern[str]]:
103 """Transforms a comma separated list of regular expressions."""
104 patterns: list[Pattern[str]] = []
105 for pattern in _csv_transformer(value):
106 patterns.append(re.compile(pattern))
107 return patterns
110def _regexp_paths_csv_transfomer(value: str) -> Sequence[Pattern[str]]:
111 """Transforms a comma separated list of regular expressions paths."""
112 patterns: list[Pattern[str]] = []
113 for pattern in _csv_transformer(value):
114 patterns.append(
115 re.compile(
116 str(pathlib.PureWindowsPath(pattern)).replace("\\", "\\\\")
117 + "|"
118 + pathlib.PureWindowsPath(pattern).as_posix()
119 )
120 )
121 return patterns
124_TYPE_TRANSFORMERS: dict[str, Callable[[str], _ArgumentTypes]] = {
125 "choice": str,
126 "csv": _csv_transformer,
127 "float": float,
128 "int": int,
129 "confidence": _confidence_transformer,
130 "non_empty_string": _non_empty_string_transformer,
131 "path": _path_transformer,
132 "py_version": _py_version_transformer,
133 "regexp": re.compile,
134 "regexp_csv": _regexp_csv_transfomer,
135 "regexp_paths_csv": _regexp_paths_csv_transfomer,
136 "string": pylint_utils._unquote,
137 "yn": _yn_transformer,
138}
139"""Type transformers for all argument types.
141A transformer should accept a string and return one of the supported
142Argument types. It will only be called when parsing 1) command-line,
1432) configuration files and 3) a string default value.
144Non-string default values are assumed to be of the correct type.
145"""
148class _Argument:
149 """Class representing an argument to be parsed by an argparse.ArgumentsParser.
151 This is based on the parameters passed to argparse.ArgumentsParser.add_message.
152 See:
153 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
154 """
156 def __init__(
157 self,
158 *,
159 flags: list[str],
160 arg_help: str,
161 hide_help: bool,
162 section: str | None,
163 ) -> None:
164 self.flags = flags
165 """The name of the argument."""
167 self.hide_help = hide_help
168 """Whether to hide this argument in the help message."""
170 # argparse uses % formatting on help strings, so a % needs to be escaped
171 self.help = arg_help.replace("%", "%%")
172 """The description of the argument."""
174 if hide_help:
175 self.help = argparse.SUPPRESS
177 self.section = section
178 """The section to add this argument to."""
181class _BaseStoreArgument(_Argument):
182 """Base class for store arguments to be parsed by an argparse.ArgumentsParser.
184 This is based on the parameters passed to argparse.ArgumentsParser.add_message.
185 See:
186 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
187 """
189 def __init__(
190 self,
191 *,
192 flags: list[str],
193 action: str,
194 default: _ArgumentTypes,
195 arg_help: str,
196 hide_help: bool,
197 section: str | None,
198 ) -> None:
199 super().__init__(
200 flags=flags, arg_help=arg_help, hide_help=hide_help, section=section
201 )
203 self.action = action
204 """The action to perform with the argument."""
206 self.default = default
207 """The default value of the argument."""
210class _StoreArgument(_BaseStoreArgument):
211 """Class representing a store argument to be parsed by an argparse.ArgumentsParser.
213 This is based on the parameters passed to argparse.ArgumentsParser.add_message.
214 See:
215 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
216 """
218 def __init__(
219 self,
220 *,
221 flags: list[str],
222 action: str,
223 default: _ArgumentTypes,
224 arg_type: str,
225 choices: list[str] | None,
226 arg_help: str,
227 metavar: str,
228 hide_help: bool,
229 section: str | None,
230 ) -> None:
231 super().__init__(
232 flags=flags,
233 action=action,
234 default=default,
235 arg_help=arg_help,
236 hide_help=hide_help,
237 section=section,
238 )
240 self.type = _TYPE_TRANSFORMERS[arg_type]
241 """A transformer function that returns a transformed type of the argument."""
243 self.choices = choices
244 """A list of possible choices for the argument.
246 None if there are no restrictions.
247 """
249 self.metavar = metavar
250 """The metavar of the argument.
252 See:
253 https://docs.python.org/3/library/argparse.html#metavar
254 """
257class _StoreTrueArgument(_BaseStoreArgument):
258 """Class representing a 'store_true' argument to be parsed by an argparse.ArgumentsParser.
260 This is based on the parameters passed to argparse.ArgumentsParser.add_message.
261 See:
262 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
263 """
265 # pylint: disable-next=useless-super-delegation # We narrow down the type of action
266 def __init__(
267 self,
268 *,
269 flags: list[str],
270 action: Literal["store_true"],
271 default: _ArgumentTypes,
272 arg_help: str,
273 hide_help: bool,
274 section: str | None,
275 ) -> None:
276 super().__init__(
277 flags=flags,
278 action=action,
279 default=default,
280 arg_help=arg_help,
281 hide_help=hide_help,
282 section=section,
283 )
286class _DeprecationArgument(_Argument):
287 """Store arguments while also handling deprecation warnings for old and new names.
289 This is based on the parameters passed to argparse.ArgumentsParser.add_message.
290 See:
291 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
292 """
294 def __init__(
295 self,
296 *,
297 flags: list[str],
298 action: type[argparse.Action],
299 default: _ArgumentTypes,
300 arg_type: str,
301 choices: list[str] | None,
302 arg_help: str,
303 metavar: str,
304 hide_help: bool,
305 section: str | None,
306 ) -> None:
307 super().__init__(
308 flags=flags, arg_help=arg_help, hide_help=hide_help, section=section
309 )
311 self.action = action
312 """The action to perform with the argument."""
314 self.default = default
315 """The default value of the argument."""
317 self.type = _TYPE_TRANSFORMERS[arg_type]
318 """A transformer function that returns a transformed type of the argument."""
320 self.choices = choices
321 """A list of possible choices for the argument.
323 None if there are no restrictions.
324 """
326 self.metavar = metavar
327 """The metavar of the argument.
329 See:
330 https://docs.python.org/3/library/argparse.html#metavar
331 """
334class _ExtendArgument(_DeprecationArgument):
335 """Class for extend arguments to be parsed by an argparse.ArgumentsParser.
337 This is based on the parameters passed to argparse.ArgumentsParser.add_message.
338 See:
339 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
340 """
342 def __init__(
343 self,
344 *,
345 flags: list[str],
346 action: Literal["extend"],
347 default: _ArgumentTypes,
348 arg_type: str,
349 metavar: str,
350 arg_help: str,
351 hide_help: bool,
352 section: str | None,
353 choices: list[str] | None,
354 dest: str | None,
355 ) -> None:
356 # The extend action is included in the stdlib from 3.8+
357 if PY38_PLUS:
358 action_class = argparse._ExtendAction # type: ignore[attr-defined]
359 else:
360 action_class = _ExtendAction
362 self.dest = dest
363 """The destination of the argument."""
365 super().__init__(
366 flags=flags,
367 action=action_class,
368 default=default,
369 arg_type=arg_type,
370 choices=choices,
371 arg_help=arg_help,
372 metavar=metavar,
373 hide_help=hide_help,
374 section=section,
375 )
378class _StoreOldNamesArgument(_DeprecationArgument):
379 """Store arguments while also handling old names.
381 This is based on the parameters passed to argparse.ArgumentsParser.add_message.
382 See:
383 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
384 """
386 def __init__(
387 self,
388 *,
389 flags: list[str],
390 default: _ArgumentTypes,
391 arg_type: str,
392 choices: list[str] | None,
393 arg_help: str,
394 metavar: str,
395 hide_help: bool,
396 kwargs: dict[str, Any],
397 section: str | None,
398 ) -> None:
399 super().__init__(
400 flags=flags,
401 action=_OldNamesAction,
402 default=default,
403 arg_type=arg_type,
404 choices=choices,
405 arg_help=arg_help,
406 metavar=metavar,
407 hide_help=hide_help,
408 section=section,
409 )
411 self.kwargs = kwargs
412 """Any additional arguments passed to the action."""
415class _StoreNewNamesArgument(_DeprecationArgument):
416 """Store arguments while also emitting deprecation warnings.
418 This is based on the parameters passed to argparse.ArgumentsParser.add_message.
419 See:
420 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
421 """
423 def __init__(
424 self,
425 *,
426 flags: list[str],
427 default: _ArgumentTypes,
428 arg_type: str,
429 choices: list[str] | None,
430 arg_help: str,
431 metavar: str,
432 hide_help: bool,
433 kwargs: dict[str, Any],
434 section: str | None,
435 ) -> None:
436 super().__init__(
437 flags=flags,
438 action=_NewNamesAction,
439 default=default,
440 arg_type=arg_type,
441 choices=choices,
442 arg_help=arg_help,
443 metavar=metavar,
444 hide_help=hide_help,
445 section=section,
446 )
448 self.kwargs = kwargs
449 """Any additional arguments passed to the action."""
452class _CallableArgument(_Argument):
453 """Class representing an callable argument to be parsed by an argparse.ArgumentsParser.
455 This is based on the parameters passed to argparse.ArgumentsParser.add_message.
456 See:
457 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
458 """
460 def __init__(
461 self,
462 *,
463 flags: list[str],
464 action: type[_CallbackAction],
465 arg_help: str,
466 kwargs: dict[str, Any],
467 hide_help: bool,
468 section: str | None,
469 metavar: str,
470 ) -> None:
471 super().__init__(
472 flags=flags, arg_help=arg_help, hide_help=hide_help, section=section
473 )
475 self.action = action
476 """The action to perform with the argument."""
478 self.kwargs = kwargs
479 """Any additional arguments passed to the action."""
481 self.metavar = metavar
482 """The metavar of the argument.
484 See:
485 https://docs.python.org/3/library/argparse.html#metavar
486 """