third_party.pylibs.pylint.src/pylint/config/argument.py
Daniël van Noord 3047faf28b
Handle `group in optdicts and option_groups` (#6207)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
2022-04-06 18:29:08 +02:00

434 lines
12 KiB
Python

# 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."""