# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE # Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt """Definition of an Argument class and transformers for various argument types. An Argument instance represents a pylint option to be handled by an argparse.ArgumentParser """ import argparse import pathlib import re import sys from typing import ( Any, Callable, Dict, List, Optional, Pattern, Sequence, Tuple, Type, Union, ) from pylint import interfaces from pylint import utils as pylint_utils from pylint.config.callback_actions import _CallbackAction from pylint.config.deprecation_actions import _NewNamesAction, _OldNamesAction if sys.version_info >= (3, 8): from typing import Literal else: from typing_extensions import Literal _ArgumentTypes = Union[ str, int, float, bool, Pattern[str], Sequence[str], Sequence[Pattern[str]], Tuple[int, ...], ] """List of possible argument types.""" def _confidence_transformer(value: str) -> Sequence[str]: """Transforms a comma separated string of confidence values.""" values = pylint_utils._check_csv(value) for confidence in values: if confidence not in interfaces.CONFIDENCE_LEVEL_NAMES: raise argparse.ArgumentTypeError( f"{value} should be in {*interfaces.CONFIDENCE_LEVEL_NAMES,}" ) return values def _csv_transformer(value: str) -> Sequence[str]: """Transforms a comma separated string.""" return pylint_utils._check_csv(value) YES_VALUES = {"y", "yes", "true"} NO_VALUES = {"n", "no", "false"} def _yn_transformer(value: str) -> bool: """Transforms a yes/no or stringified bool into a bool.""" value = value.lower() if value in YES_VALUES: return True if value in NO_VALUES: return False raise argparse.ArgumentTypeError( None, f"Invalid yn value '{value}', should be in {*YES_VALUES, *NO_VALUES}" ) def _non_empty_string_transformer(value: str) -> str: """Check that a string is not empty and remove quotes.""" if not value: raise argparse.ArgumentTypeError("Option cannot be an empty string.") return pylint_utils._unquote(value) def _py_version_transformer(value: str) -> Tuple[int, ...]: """Transforms a version string into a version tuple.""" try: version = tuple(int(val) for val in value.replace(",", ".").split(".")) except ValueError: raise argparse.ArgumentTypeError( f"{value} has an invalid format, should be a version string. E.g., '3.8'" ) from None return version def _regexp_csv_transfomer(value: str) -> Sequence[Pattern[str]]: """Transforms a comma separated list of regular expressions.""" patterns: List[Pattern[str]] = [] for pattern in _csv_transformer(value): patterns.append(re.compile(pattern)) return patterns def _regexp_paths_csv_transfomer(value: str) -> Sequence[Pattern[str]]: """Transforms a comma separated list of regular expressions paths.""" patterns: List[Pattern[str]] = [] for pattern in _csv_transformer(value): patterns.append( re.compile( str(pathlib.PureWindowsPath(pattern)).replace("\\", "\\\\") + "|" + pathlib.PureWindowsPath(pattern).as_posix() ) ) return patterns _TYPE_TRANSFORMERS: Dict[str, Callable[[str], _ArgumentTypes]] = { "choice": str, "csv": _csv_transformer, "float": float, "int": int, "confidence": _confidence_transformer, "non_empty_string": _non_empty_string_transformer, "py_version": _py_version_transformer, "regexp": re.compile, "regexp_csv": _regexp_csv_transfomer, "regexp_paths_csv": _regexp_paths_csv_transfomer, "string": pylint_utils._unquote, "yn": _yn_transformer, } """Type transformers for all argument types. A transformer should accept a string and return one of the supported Argument types. It will only be called when parsing 1) command-line, 2) configuration files and 3) a string default value. Non-string default values are assumed to be of the correct type. """ class _Argument: """Class representing an argument to be parsed by an argparse.ArgumentsParser. This is based on the parameters passed to argparse.ArgumentsParser.add_message. See: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument """ def __init__( self, *, flags: List[str], arg_help: str, hide_help: bool, section: Optional[str], ) -> None: self.flags = flags """The name of the argument.""" self.hide_help = hide_help """Whether to hide this argument in the help message.""" # argparse uses % formatting on help strings, so a % needs to be escaped self.help = arg_help.replace("%", "%%") """The description of the argument.""" if hide_help: self.help = argparse.SUPPRESS self.section = section """The section to add this argument to.""" class _BaseStoreArgument(_Argument): """Base class for store arguments to be parsed by an argparse.ArgumentsParser. This is based on the parameters passed to argparse.ArgumentsParser.add_message. See: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument """ def __init__( self, *, flags: List[str], action: str, default: _ArgumentTypes, arg_help: str, hide_help: bool, section: Optional[str], ) -> None: super().__init__( flags=flags, arg_help=arg_help, hide_help=hide_help, section=section ) self.action = action """The action to perform with the argument.""" self.default = default """The default value of the argument.""" class _StoreArgument(_BaseStoreArgument): """Class representing a store argument to be parsed by an argparse.ArgumentsParser. This is based on the parameters passed to argparse.ArgumentsParser.add_message. See: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument """ def __init__( self, *, flags: List[str], action: str, default: _ArgumentTypes, arg_type: str, choices: Optional[List[str]], arg_help: str, metavar: str, hide_help: bool, section: Optional[str], ) -> None: super().__init__( flags=flags, action=action, default=default, arg_help=arg_help, hide_help=hide_help, section=section, ) self.type = _TYPE_TRANSFORMERS[arg_type] """A transformer function that returns a transformed type of the argument.""" self.choices = choices """A list of possible choices for the argument. None if there are no restrictions. """ self.metavar = metavar """The metavar of the argument. See: https://docs.python.org/3/library/argparse.html#metavar """ class _StoreTrueArgument(_BaseStoreArgument): """Class representing a 'store_true' argument to be parsed by an argparse.ArgumentsParser. This is based on the parameters passed to argparse.ArgumentsParser.add_message. See: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument """ # pylint: disable-next=useless-super-delegation # We narrow down the type of action def __init__( self, *, flags: List[str], action: Literal["store_true"], default: _ArgumentTypes, arg_help: str, hide_help: bool, section: Optional[str], ) -> None: super().__init__( flags=flags, action=action, default=default, arg_help=arg_help, hide_help=hide_help, section=section, ) class _DeprecationArgument(_Argument): """Store arguments while also handling deprecation warnings for old and new names. This is based on the parameters passed to argparse.ArgumentsParser.add_message. See: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument """ def __init__( self, *, flags: List[str], action: Type[argparse._StoreAction], default: _ArgumentTypes, arg_type: str, choices: Optional[List[str]], arg_help: str, metavar: str, hide_help: bool, section: Optional[str], ) -> None: super().__init__( flags=flags, arg_help=arg_help, hide_help=hide_help, section=section ) self.action = action """The action to perform with the argument.""" self.default = default """The default value of the argument.""" self.type = _TYPE_TRANSFORMERS[arg_type] """A transformer function that returns a transformed type of the argument.""" self.choices = choices """A list of possible choices for the argument. None if there are no restrictions. """ self.metavar = metavar """The metavar of the argument. See: https://docs.python.org/3/library/argparse.html#metavar """ class _StoreOldNamesArgument(_DeprecationArgument): """Store arguments while also handling old names. This is based on the parameters passed to argparse.ArgumentsParser.add_message. See: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument """ def __init__( self, *, flags: List[str], default: _ArgumentTypes, arg_type: str, choices: Optional[List[str]], arg_help: str, metavar: str, hide_help: bool, kwargs: Dict[str, Any], section: Optional[str], ) -> None: super().__init__( flags=flags, action=_OldNamesAction, default=default, arg_type=arg_type, choices=choices, arg_help=arg_help, metavar=metavar, hide_help=hide_help, section=section, ) self.kwargs = kwargs """Any additional arguments passed to the action.""" class _StoreNewNamesArgument(_DeprecationArgument): """Store arguments while also emitting deprecation warnings. This is based on the parameters passed to argparse.ArgumentsParser.add_message. See: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument """ def __init__( self, *, flags: List[str], default: _ArgumentTypes, arg_type: str, choices: Optional[List[str]], arg_help: str, metavar: str, hide_help: bool, kwargs: Dict[str, Any], section: Optional[str], ) -> None: super().__init__( flags=flags, action=_NewNamesAction, default=default, arg_type=arg_type, choices=choices, arg_help=arg_help, metavar=metavar, hide_help=hide_help, section=section, ) self.kwargs = kwargs """Any additional arguments passed to the action.""" class _CallableArgument(_Argument): """Class representing an callable argument to be parsed by an argparse.ArgumentsParser. This is based on the parameters passed to argparse.ArgumentsParser.add_message. See: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument """ def __init__( self, *, flags: List[str], action: Type[_CallbackAction], arg_help: str, kwargs: Dict[str, Any], hide_help: bool, section: Optional[str], ) -> None: super().__init__( flags=flags, arg_help=arg_help, hide_help=hide_help, section=section ) self.action = action """The action to perform with the argument.""" self.kwargs = kwargs """Any additional arguments passed to the action."""