This file is indexed.

/usr/lib/python3/dist-packages/invoke/parser/context.py is in python3-invoke 0.11.1+dfsg1-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
import itertools

from ..vendor.lexicon import Lexicon

from .argument import Argument


def to_flag(name):
    name = name.replace('_', '-')
    if len(name) == 1:
        return '-' + name
    return '--' + name

def sort_candidate(arg):
    names = arg.names
    # TODO: is there no "split into two buckets on predicate" builtin?
    shorts = set(x for x in names if len(x.strip('-')) == 1)
    longs = set(x for x in names if x not in shorts)
    return sorted(shorts if shorts else longs)[0]

def flag_key(x):
    """
    Obtain useful key list-of-ints for sorting CLI flags.
    """
    # Setup
    ret = []
    x = sort_candidate(x)
    # Long-style flags win over short-style ones, so the first item of
    # comparison is simply whether the flag is a single character long (with
    # non-length-1 flags coming "first" [lower number])
    ret.append(1 if len(x) == 1 else 0)
    # Next item of comparison is simply the strings themselves,
    # case-insensitive. They will compare alphabetically if compared at this
    # stage.
    ret.append(x.lower())
    # Finally, if the case-insensitive test also matched, compare
    # case-sensitive, but inverse (with lowercase letters coming first)
    inversed = ''
    for char in x:
        inversed += char.lower() if char.isupper() else char.upper()
    ret.append(inversed)
    return ret


# Named slightly more verbose so Sphinx references can be unambiguous.
# Got real sick of fully qualified paths.
class ParserContext(object):
    """
    Parsing context with knowledge of flags & their format.

    Generally associated with the core program or a task.

    When run through a parser, will also hold runtime values filled in by the
    parser.
    """
    def __init__(self, name=None, aliases=(), args=()):
        """
        Create a new ``ParserContext`` named ``name``, with ``aliases``.

        ``name`` is optional, and should be a string if given. It's used to
        tell ParserContext objects apart, and for use in a Parser when
        determining what chunk of input might belong to a given ParserContext.

        ``aliases`` is also optional and should be an iterable containing
        strings. Parsing will honor any aliases when trying to "find" a given
        context in its input.

        May give one or more ``args``, which is a quick alternative to calling
        ``for arg in args: self.add_arg(arg)`` after initialization.
        """
        self.args = Lexicon()
        self.positional_args = []
        self.flags = Lexicon()
        self.inverse_flags = {} # No need for Lexicon here
        self.name = name
        self.aliases = aliases
        for arg in args:
            self.add_arg(arg)

    def __str__(self):
        aliases = ""
        if self.aliases:
            aliases = " ({0})".format(', '.join(self.aliases))
        name = (" {0!r}{1}".format(self.name, aliases)) if self.name else ""
        args = (": {0!r}".format(self.args)) if self.args else ""
        return "<parser/Context{0}{1}>".format(name, args)

    def __repr__(self):
        return str(self)

    def add_arg(self, *args, **kwargs):
        """
        Adds given ``Argument`` (or constructor args for one) to this context.

        The Argument in question is added to the following dict attributes:

        * ``args``: "normal" access, i.e. the given names are directly exposed
          as keys.
        * ``flags``: "flaglike" access, i.e. the given names are translated
          into CLI flags, e.g. ``"foo"`` is accessible via ``flags['--foo']``.
        * ``inverse_flags``: similar to ``flags`` but containing only the
          "inverse" versions of boolean flags which default to True. This
          allows the parser to track e.g. ``--no-myflag`` and turn it into a
          False value for the ``myflag`` Argument.
        """
        # Normalize
        if len(args) == 1 and isinstance(args[0], Argument):
            arg = args[0]
        else:
            arg = Argument(*args, **kwargs)
        # Uniqueness constraint: no name collisions
        for name in arg.names:
            if name in self.args:
                msg = "Tried to add an argument named {0!r} but one already exists!" # noqa
                raise ValueError(msg.format(name))
        # First name used as "main" name for purposes of aliasing
        main = arg.names[0] # NOT arg.name
        self.args[main] = arg
        # Note positionals in distinct, ordered list attribute
        if arg.positional:
            self.positional_args.append(arg)
        # Add names & nicknames to flags, args
        self.flags[to_flag(main)] = arg
        for name in arg.nicknames:
            self.args.alias(name, to=main)
            self.flags.alias(to_flag(name), to=to_flag(main))
        # Add attr_name to args, but not flags
        if arg.attr_name:
            self.args.alias(arg.attr_name, to=main)
        # Add to inverse_flags if required
        if arg.kind == bool and arg.default is True:
            # Invert the 'main' flag name here, which will be a dashed version
            # of the primary argument name if underscore-to-dash transformation
            # occurred.
            inverse_name = to_flag("no-{0}".format(main))
            self.inverse_flags[inverse_name] = to_flag(main)

    @property
    def needs_positional_arg(self):
        return any(x.value is None for x in self.positional_args)

    @property
    def as_kwargs(self):
        """
        This context's arguments' values keyed by their ``.name`` attribute.

        Results in a dict suitable for use in Python contexts, where e.g. an
        arg named ``foo-bar`` becomes accessible as ``foo_bar``.
        """
        ret = {}
        for arg in self.args.values():
            ret[arg.name] = arg.value
        return ret

    def names_for(self, flag):
        # TODO: should probably be a method on Lexicon/AliasDict
        return list(set([flag] + self.flags.aliases_of(flag)))

    def help_for(self, flag):
        """
        Return 2-tuple of ``(flag-spec, help-string)`` for given ``flag``.
        """
        # Obtain arg obj
        if flag not in self.flags:
            err = "{0!r} is not a valid flag for this context! Valid flags are: {1!r}" # noqa
            raise ValueError(err.format(flag, self.flags.keys()))
        arg = self.flags[flag]
        # Determine expected value type, if any
        value = {
            str: 'STRING',
        }.get(arg.kind)
        # Format & go
        full_names = []
        for name in self.names_for(flag):
            if value:
                # Short flags are -f VAL, long are --foo=VAL
                # When optional, also, -f [VAL] and --foo[=VAL]
                if len(name.strip('-')) == 1:
                    value_ = ("[{0}]".format(value)) if arg.optional else value
                    valuestr = " {0}".format(value_)
                else:
                    valuestr = "={0}".format(value)
                    if arg.optional:
                        valuestr = "[{0}]".format(valuestr)
            else:
                # no value => boolean
                # check for inverse
                if name in self.inverse_flags.values():
                    name = "--[no-]{0}".format(name[2:])

                valuestr = ""
            # Tack together
            full_names.append(name + valuestr)
        namestr = ", ".join(sorted(full_names, key=len))
        helpstr = arg.help or ""
        return namestr, helpstr

    def help_tuples(self):
        """
        Return sorted iterable of help tuples for all member Arguments.

        Sorts like so:

        * General sort is alphanumerically
        * Short flags win over long flags
        * Arguments with *only* long flags and *no* short flags will come
          first.
        * When an Argument has multiple long or short flags, it will sort using
          the most favorable (lowest alphabetically) candidate.

        This will result in a help list like so::

            --alpha, --zeta # 'alpha' wins
            --beta
            -a, --query # short flag wins
            -b, --argh
            -c
        """
        # TODO: argument/flag API must change :(
        # having to call to_flag on 1st name of an Argument is just dumb.
        # To pass in an Argument object to help_for may require moderate
        # changes?
        # Cast to list to ensure non-generator on Python 3.
        return list(map(
            lambda x: self.help_for(to_flag(x.name)),
            sorted(self.flags.values(), key=flag_key)
        ))

    def flag_names(self):
        """
        Similar to `help_tuples` but returns flag names only, no helpstrs.

        Specifically, all flag names, flattened, in rough order.
        """
        # Regular flag names
        flags = sorted(self.flags.values(), key=flag_key)
        names = [self.names_for(to_flag(x.name)) for x in flags]
        # Inverse flag names sold separately
        names.append(self.inverse_flags.keys())
        return tuple(itertools.chain.from_iterable(names))