parser.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. from __future__ import annotations
  2. import os
  3. from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
  4. from collections import OrderedDict
  5. from virtualenv.config.convert import get_type
  6. from virtualenv.config.env_var import get_env_var
  7. from virtualenv.config.ini import IniConfig
  8. class VirtualEnvOptions(Namespace):
  9. def __init__(self, **kwargs) -> None:
  10. super().__init__(**kwargs)
  11. self._src = None
  12. self._sources = {}
  13. def set_src(self, key, value, src):
  14. setattr(self, key, value)
  15. if src.startswith("env var"):
  16. src = "env var"
  17. self._sources[key] = src
  18. def __setattr__(self, key, value) -> None:
  19. if getattr(self, "_src", None) is not None:
  20. self._sources[key] = self._src
  21. super().__setattr__(key, value)
  22. def get_source(self, key):
  23. return self._sources.get(key)
  24. @property
  25. def verbosity(self):
  26. if not hasattr(self, "verbose") and not hasattr(self, "quiet"):
  27. return None
  28. return max(self.verbose - self.quiet, 0)
  29. def __repr__(self) -> str:
  30. return f"{type(self).__name__}({', '.join(f'{k}={v}' for k, v in vars(self).items() if not k.startswith('_'))})"
  31. class VirtualEnvConfigParser(ArgumentParser):
  32. """Custom option parser which updates its defaults by checking the configuration files and environmental vars."""
  33. def __init__(self, options=None, env=None, *args, **kwargs) -> None:
  34. env = os.environ if env is None else env
  35. self.file_config = IniConfig(env)
  36. self.epilog_list = []
  37. self.env = env
  38. kwargs["epilog"] = self.file_config.epilog
  39. kwargs["add_help"] = False
  40. kwargs["formatter_class"] = HelpFormatter
  41. kwargs["prog"] = "virtualenv"
  42. super().__init__(*args, **kwargs)
  43. self._fixed = set()
  44. if options is not None and not isinstance(options, VirtualEnvOptions):
  45. msg = "options must be of type VirtualEnvOptions"
  46. raise TypeError(msg)
  47. self.options = VirtualEnvOptions() if options is None else options
  48. self._interpreter = None
  49. self._app_data = None
  50. def _fix_defaults(self):
  51. for action in self._actions:
  52. action_id = id(action)
  53. if action_id not in self._fixed:
  54. self._fix_default(action)
  55. self._fixed.add(action_id)
  56. def _fix_default(self, action):
  57. if hasattr(action, "default") and hasattr(action, "dest") and action.default != SUPPRESS:
  58. as_type = get_type(action)
  59. names = OrderedDict((i.lstrip("-").replace("-", "_"), None) for i in action.option_strings)
  60. outcome = None
  61. for name in names:
  62. outcome = get_env_var(name, as_type, self.env)
  63. if outcome is not None:
  64. break
  65. if outcome is None and self.file_config:
  66. for name in names:
  67. outcome = self.file_config.get(name, as_type)
  68. if outcome is not None:
  69. break
  70. if outcome is not None:
  71. action.default, action.default_source = outcome
  72. else:
  73. outcome = action.default, "default"
  74. self.options.set_src(action.dest, *outcome)
  75. def enable_help(self):
  76. self._fix_defaults()
  77. self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit")
  78. def parse_known_args(self, args=None, namespace=None):
  79. if namespace is None:
  80. namespace = self.options
  81. elif namespace is not self.options:
  82. msg = "can only pass in parser.options"
  83. raise ValueError(msg)
  84. self._fix_defaults()
  85. self.options._src = "cli" # noqa: SLF001
  86. try:
  87. namespace.env = self.env
  88. return super().parse_known_args(args, namespace=namespace)
  89. finally:
  90. self.options._src = None # noqa: SLF001
  91. class HelpFormatter(ArgumentDefaultsHelpFormatter):
  92. def __init__(self, prog, **kwargs) -> None:
  93. super().__init__(prog, max_help_position=32, width=240, **kwargs)
  94. def _get_help_string(self, action):
  95. text = super()._get_help_string(action)
  96. if hasattr(action, "default_source"):
  97. default = " (default: %(default)s)"
  98. if text.endswith(default):
  99. text = f"{text[: -len(default)]} (default: %(default)s -> from %(default_source)s)"
  100. return text
  101. __all__ = [
  102. "HelpFormatter",
  103. "VirtualEnvConfigParser",
  104. "VirtualEnvOptions",
  105. ]