| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- from __future__ import annotations
- import os
- from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
- from collections import OrderedDict
- from typing import TYPE_CHECKING, Any
- if TYPE_CHECKING:
- from argparse import Action
- from collections.abc import Mapping, Sequence
- from virtualenv.config.convert import get_type
- from virtualenv.config.env_var import get_env_var
- from virtualenv.config.ini import IniConfig
- class VirtualEnvOptions(Namespace):
- def __init__(self, **kwargs: Any) -> None: # noqa: ANN401
- super().__init__(**kwargs)
- self._src: str | None = None
- self._sources: dict[str, str] = {}
- def set_src(self, key: str, value: Any, src: str) -> None: # noqa: ANN401
- """Set an option value and record where it came from.
- :param key: the option name
- :param value: the option value
- :param src: the source of the value (e.g. ``"cli"``, ``"env var"``, ``"default"``)
- """
- setattr(self, key, value)
- if src.startswith("env var"):
- src = "env var"
- self._sources[key] = src
- def __setattr__(self, key: str, value: Any) -> None: # noqa: ANN401
- if (src := getattr(self, "_src", None)) is not None:
- self._sources[key] = src
- super().__setattr__(key, value)
- def get_source(self, key: str) -> str | None:
- """Return the source that provided a given option value.
- :param key: the option name
- :returns: the source string (e.g. ``"cli"``, ``"env var"``, ``"default"``), or ``None`` if not tracked
- """
- return self._sources.get(key)
- @property
- def verbosity(self) -> int | None:
- """The verbosity level, computed as ``verbose - quiet``, clamped to zero.
- :returns: the verbosity level, or ``None`` if neither ``--verbose`` nor ``--quiet`` has been parsed yet
- """
- if not hasattr(self, "verbose") and not hasattr(self, "quiet"):
- return None
- return max(self.verbose - self.quiet, 0)
- def __repr__(self) -> str:
- return f"{type(self).__name__}({', '.join(f'{k}={v}' for k, v in vars(self).items() if not k.startswith('_'))})"
- class VirtualEnvConfigParser(ArgumentParser):
- """Custom option parser which updates its defaults by checking the configuration files and environmental vars."""
- def __init__(
- self,
- options: VirtualEnvOptions | None = None,
- env: Mapping[str, str] | None = None,
- *args: Any, # noqa: ANN401
- **kwargs: Any, # noqa: ANN401
- ) -> None:
- env = os.environ if env is None else env
- self.file_config = IniConfig(env)
- self.epilog_list = []
- self.env = env
- kwargs["epilog"] = self.file_config.epilog
- kwargs["add_help"] = False
- kwargs["formatter_class"] = HelpFormatter
- kwargs["prog"] = "virtualenv"
- super().__init__(*args, **kwargs)
- self._fixed = set()
- if options is not None and not isinstance(options, VirtualEnvOptions):
- msg = "options must be of type VirtualEnvOptions"
- raise TypeError(msg)
- self.options = VirtualEnvOptions() if options is None else options
- self._interpreter = None
- self._app_data = None
- def _fix_defaults(self) -> None:
- for action in self._actions:
- action_id = id(action)
- if action_id not in self._fixed:
- self._fix_default(action)
- self._fixed.add(action_id)
- def _fix_default(self, action: Action) -> None:
- if hasattr(action, "default") and hasattr(action, "dest") and action.default != SUPPRESS:
- as_type = get_type(action)
- names = OrderedDict((i.lstrip("-").replace("-", "_"), None) for i in action.option_strings)
- outcome = None
- for name in names:
- outcome = get_env_var(name, as_type, self.env)
- if outcome is not None:
- break
- if outcome is None and self.file_config:
- for name in names:
- outcome = self.file_config.get(name, as_type)
- if outcome is not None:
- break
- if outcome is not None:
- action.default, action.default_source = outcome
- else:
- outcome = action.default, "default"
- self.options.set_src(action.dest, *outcome)
- def enable_help(self) -> None:
- self._fix_defaults()
- self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit")
- def parse_known_args(
- self, args: Sequence[str] | None = None, namespace: VirtualEnvOptions | None = None
- ) -> tuple[VirtualEnvOptions, list[str]]:
- if namespace is None:
- namespace = self.options
- elif namespace is not self.options:
- msg = "can only pass in parser.options"
- raise ValueError(msg)
- self._fix_defaults()
- self.options._src = "cli" # noqa: SLF001
- try:
- namespace.env = self.env
- return super().parse_known_args(args, namespace=namespace)
- finally:
- self.options._src = None # noqa: SLF001
- class HelpFormatter(ArgumentDefaultsHelpFormatter):
- def __init__(self, prog: str, **kwargs: Any) -> None: # noqa: ANN401
- super().__init__(prog, max_help_position=32, width=240, **kwargs)
- def _get_help_string(self, action: Action) -> str | None:
- text = super()._get_help_string(action)
- if text is not None and hasattr(action, "default_source"):
- default = " (default: %(default)s)"
- if text.endswith(default):
- text = f"{text[: -len(default)]} (default: %(default)s -> from %(default_source)s)"
- return text
- __all__ = [
- "HelpFormatter",
- "VirtualEnvConfigParser",
- "VirtualEnvOptions",
- ]
|