summaryrefslogtreecommitdiff
path: root/src/virtualenv/config/cli/parser.py
blob: 5ab4fbde18f0052279aec4af9f22d8f1ed153bd1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import os
from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
from collections import OrderedDict

from virtualenv.config.convert import get_type

from ..env_var import get_env_var
from ..ini import IniConfig


class VirtualEnvOptions(Namespace):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._src = None
        self._sources = {}

    def set_src(self, key, value, src):
        setattr(self, key, value)
        if src.startswith("env var"):
            src = "env var"
        self._sources[key] = src

    def __setattr__(self, key, value):
        if getattr(self, "_src", None) is not None:
            self._sources[key] = self._src
        super().__setattr__(key, value)

    def get_source(self, key):
        return self._sources.get(key)

    @property
    def verbosity(self):
        if not hasattr(self, "verbose") and not hasattr(self, "quiet"):
            return None
        return max(self.verbose - self.quiet, 0)

    def __repr__(self):
        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 variables
    """

    def __init__(self, options=None, env=None, *args, **kwargs):
        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):
            raise TypeError("options must be of type VirtualEnvOptions")
        self.options = VirtualEnvOptions() if options is None else options
        self._interpreter = None
        self._app_data = None

    def _fix_defaults(self):
        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):
        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):
        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=None, namespace=None):
        if namespace is None:
            namespace = self.options
        elif namespace is not self.options:
            raise ValueError("can only pass in parser.options")
        self._fix_defaults()
        self.options._src = "cli"
        try:
            namespace.env = self.env
            return super().parse_known_args(args, namespace=namespace)
        finally:
            self.options._src = None


class HelpFormatter(ArgumentDefaultsHelpFormatter):
    def __init__(self, prog):
        super().__init__(prog, max_help_position=32, width=240)

    def _get_help_string(self, action):

        text = super()._get_help_string(action)
        if 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",
]