/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))
|