option.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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. import copy
  4. import optparse # pylint: disable=deprecated-module
  5. import pathlib
  6. import re
  7. from typing import List, Pattern, Union
  8. from pylint import utils
  9. # pylint: disable=unused-argument
  10. def _csv_validator(_, name, value):
  11. return utils._check_csv(value)
  12. # pylint: disable=unused-argument
  13. def _regexp_validator(_, name, value):
  14. if hasattr(value, "pattern"):
  15. return value
  16. return re.compile(value)
  17. # pylint: disable=unused-argument
  18. def _regexp_csv_validator(_, name, value):
  19. return [_regexp_validator(_, name, val) for val in _csv_validator(_, name, value)]
  20. def _regexp_paths_csv_validator(
  21. _, name: str, value: Union[str, List[Pattern[str]]]
  22. ) -> List[Pattern[str]]:
  23. if isinstance(value, list):
  24. return value
  25. patterns = []
  26. for val in _csv_validator(_, name, value):
  27. patterns.append(
  28. re.compile(
  29. str(pathlib.PureWindowsPath(val)).replace("\\", "\\\\")
  30. + "|"
  31. + pathlib.PureWindowsPath(val).as_posix()
  32. )
  33. )
  34. return patterns
  35. def _choice_validator(choices, name, value):
  36. if value not in choices:
  37. msg = "option %s: invalid value: %r, should be in %s"
  38. raise optparse.OptionValueError(msg % (name, value, choices))
  39. return value
  40. def _yn_validator(opt, _, value):
  41. if isinstance(value, int):
  42. return bool(value)
  43. if isinstance(value, str):
  44. value = value.lower()
  45. if value in {"y", "yes", "true"}:
  46. return True
  47. if value in {"n", "no", "false"}:
  48. return False
  49. msg = "option %s: invalid yn value %r, should be in (y, yes, true, n, no, false)"
  50. raise optparse.OptionValueError(msg % (opt, value))
  51. def _multiple_choice_validator(choices, name, value):
  52. values = utils._check_csv(value)
  53. for csv_value in values:
  54. if csv_value not in choices:
  55. msg = "option %s: invalid value: %r, should be in %s"
  56. raise optparse.OptionValueError(msg % (name, csv_value, choices))
  57. return values
  58. def _non_empty_string_validator(opt, _, value):
  59. if not value:
  60. msg = "indent string can't be empty."
  61. raise optparse.OptionValueError(msg)
  62. return utils._unquote(value)
  63. def _multiple_choices_validating_option(opt, name, value):
  64. return _multiple_choice_validator(opt.choices, name, value)
  65. def _py_version_validator(_, name, value):
  66. if not isinstance(value, tuple):
  67. try:
  68. value = tuple(int(val) for val in value.split("."))
  69. except (ValueError, AttributeError):
  70. raise optparse.OptionValueError(
  71. f"Invalid format for {name}, should be version string. E.g., '3.8'"
  72. ) from None
  73. return value
  74. VALIDATORS = {
  75. "string": utils._unquote,
  76. "int": int,
  77. "float": float,
  78. "regexp": lambda pattern: re.compile(pattern or ""),
  79. "regexp_csv": _regexp_csv_validator,
  80. "regexp_paths_csv": _regexp_paths_csv_validator,
  81. "csv": _csv_validator,
  82. "yn": _yn_validator,
  83. "choice": lambda opt, name, value: _choice_validator(opt["choices"], name, value),
  84. "multiple_choice": lambda opt, name, value: _multiple_choice_validator(
  85. opt["choices"], name, value
  86. ),
  87. "non_empty_string": _non_empty_string_validator,
  88. "py_version": _py_version_validator,
  89. }
  90. def _call_validator(opttype, optdict, option, value):
  91. if opttype not in VALIDATORS:
  92. raise Exception(f'Unsupported type "{opttype}"')
  93. try:
  94. return VALIDATORS[opttype](optdict, option, value)
  95. except TypeError:
  96. try:
  97. return VALIDATORS[opttype](value)
  98. except Exception as e:
  99. raise optparse.OptionValueError(
  100. f"{option} value ({value!r}) should be of type {opttype}"
  101. ) from e
  102. def _validate(value, optdict, name=""):
  103. """return a validated value for an option according to its type
  104. optional argument name is only used for error message formatting
  105. """
  106. try:
  107. _type = optdict["type"]
  108. except KeyError:
  109. return value
  110. return _call_validator(_type, optdict, name, value)
  111. # pylint: disable=no-member
  112. class Option(optparse.Option):
  113. TYPES = optparse.Option.TYPES + (
  114. "regexp",
  115. "regexp_csv",
  116. "regexp_paths_csv",
  117. "csv",
  118. "yn",
  119. "multiple_choice",
  120. "non_empty_string",
  121. "py_version",
  122. )
  123. ATTRS = optparse.Option.ATTRS + ["hide", "level"]
  124. TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER)
  125. TYPE_CHECKER["regexp"] = _regexp_validator
  126. TYPE_CHECKER["regexp_csv"] = _regexp_csv_validator
  127. TYPE_CHECKER["regexp_paths_csv"] = _regexp_paths_csv_validator
  128. TYPE_CHECKER["csv"] = _csv_validator
  129. TYPE_CHECKER["yn"] = _yn_validator
  130. TYPE_CHECKER["multiple_choice"] = _multiple_choices_validating_option
  131. TYPE_CHECKER["non_empty_string"] = _non_empty_string_validator
  132. TYPE_CHECKER["py_version"] = _py_version_validator
  133. def __init__(self, *opts, **attrs):
  134. super().__init__(*opts, **attrs)
  135. if hasattr(self, "hide") and self.hide:
  136. self.help = optparse.SUPPRESS_HELP
  137. def _check_choice(self):
  138. if self.type in {"choice", "multiple_choice"}:
  139. if self.choices is None:
  140. raise optparse.OptionError(
  141. "must supply a list of choices for type 'choice'", self
  142. )
  143. if not isinstance(self.choices, (tuple, list)):
  144. raise optparse.OptionError(
  145. # pylint: disable-next=consider-using-f-string
  146. "choices must be a list of strings ('%s' supplied)"
  147. % str(type(self.choices)).split("'")[1],
  148. self,
  149. )
  150. elif self.choices is not None:
  151. raise optparse.OptionError(
  152. f"must not supply choices for type {self.type!r}", self
  153. )
  154. optparse.Option.CHECK_METHODS[2] = _check_choice # type: ignore[index]
  155. def process(self, opt, value, values, parser):
  156. # First, convert the value(s) to the right type. Howl if any
  157. # value(s) are bogus.
  158. value = self.convert_value(opt, value)
  159. if self.type == "named":
  160. existent = getattr(values, self.dest)
  161. if existent:
  162. existent.update(value)
  163. value = existent
  164. # And then take whatever action is expected of us.
  165. # This is a separate method to make life easier for
  166. # subclasses to add new actions.
  167. return self.take_action(self.action, self.dest, opt, value, values, parser)