Spaces:
Running
Running
""" | |
This module started out as largely a copy paste from the stdlib's | |
optparse module with the features removed that we do not need from | |
optparse because we implement them in Click on a higher level (for | |
instance type handling, help formatting and a lot more). | |
The plan is to remove more and more from here over time. | |
The reason this is a different module and not optparse from the stdlib | |
is that there are differences in 2.x and 3.x about the error messages | |
generated and optparse in the stdlib uses gettext for no good reason | |
and might cause us issues. | |
Click uses parts of optparse written by Gregory P. Ward and maintained | |
by the Python Software Foundation. This is limited to code in parser.py. | |
Copyright 2001-2006 Gregory P. Ward. All rights reserved. | |
Copyright 2002-2006 Python Software Foundation. All rights reserved. | |
""" | |
# This code uses parts of optparse written by Gregory P. Ward and | |
# maintained by the Python Software Foundation. | |
# Copyright 2001-2006 Gregory P. Ward | |
# Copyright 2002-2006 Python Software Foundation | |
import typing as t | |
from collections import deque | |
from gettext import gettext as _ | |
from gettext import ngettext | |
from .exceptions import BadArgumentUsage | |
from .exceptions import BadOptionUsage | |
from .exceptions import NoSuchOption | |
from .exceptions import UsageError | |
if t.TYPE_CHECKING: | |
import typing_extensions as te | |
from .core import Argument as CoreArgument | |
from .core import Context | |
from .core import Option as CoreOption | |
from .core import Parameter as CoreParameter | |
V = t.TypeVar("V") | |
# Sentinel value that indicates an option was passed as a flag without a | |
# value but is not a flag option. Option.consume_value uses this to | |
# prompt or use the flag_value. | |
_flag_needs_value = object() | |
def _unpack_args( | |
args: t.Sequence[str], nargs_spec: t.Sequence[int] | |
) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]: | |
"""Given an iterable of arguments and an iterable of nargs specifications, | |
it returns a tuple with all the unpacked arguments at the first index | |
and all remaining arguments as the second. | |
The nargs specification is the number of arguments that should be consumed | |
or `-1` to indicate that this position should eat up all the remainders. | |
Missing items are filled with `None`. | |
""" | |
args = deque(args) | |
nargs_spec = deque(nargs_spec) | |
rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = [] | |
spos: t.Optional[int] = None | |
def _fetch(c: "te.Deque[V]") -> t.Optional[V]: | |
try: | |
if spos is None: | |
return c.popleft() | |
else: | |
return c.pop() | |
except IndexError: | |
return None | |
while nargs_spec: | |
nargs = _fetch(nargs_spec) | |
if nargs is None: | |
continue | |
if nargs == 1: | |
rv.append(_fetch(args)) | |
elif nargs > 1: | |
x = [_fetch(args) for _ in range(nargs)] | |
# If we're reversed, we're pulling in the arguments in reverse, | |
# so we need to turn them around. | |
if spos is not None: | |
x.reverse() | |
rv.append(tuple(x)) | |
elif nargs < 0: | |
if spos is not None: | |
raise TypeError("Cannot have two nargs < 0") | |
spos = len(rv) | |
rv.append(None) | |
# spos is the position of the wildcard (star). If it's not `None`, | |
# we fill it with the remainder. | |
if spos is not None: | |
rv[spos] = tuple(args) | |
args = [] | |
rv[spos + 1 :] = reversed(rv[spos + 1 :]) | |
return tuple(rv), list(args) | |
def split_opt(opt: str) -> t.Tuple[str, str]: | |
first = opt[:1] | |
if first.isalnum(): | |
return "", opt | |
if opt[1:2] == first: | |
return opt[:2], opt[2:] | |
return first, opt[1:] | |
def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str: | |
if ctx is None or ctx.token_normalize_func is None: | |
return opt | |
prefix, opt = split_opt(opt) | |
return f"{prefix}{ctx.token_normalize_func(opt)}" | |
def split_arg_string(string: str) -> t.List[str]: | |
"""Split an argument string as with :func:`shlex.split`, but don't | |
fail if the string is incomplete. Ignores a missing closing quote or | |
incomplete escape sequence and uses the partial token as-is. | |
.. code-block:: python | |
split_arg_string("example 'my file") | |
["example", "my file"] | |
split_arg_string("example my\\") | |
["example", "my"] | |
:param string: String to split. | |
""" | |
import shlex | |
lex = shlex.shlex(string, posix=True) | |
lex.whitespace_split = True | |
lex.commenters = "" | |
out = [] | |
try: | |
for token in lex: | |
out.append(token) | |
except ValueError: | |
# Raised when end-of-string is reached in an invalid state. Use | |
# the partial token as-is. The quote or escape character is in | |
# lex.state, not lex.token. | |
out.append(lex.token) | |
return out | |
class Option: | |
def __init__( | |
self, | |
obj: "CoreOption", | |
opts: t.Sequence[str], | |
dest: t.Optional[str], | |
action: t.Optional[str] = None, | |
nargs: int = 1, | |
const: t.Optional[t.Any] = None, | |
): | |
self._short_opts = [] | |
self._long_opts = [] | |
self.prefixes: t.Set[str] = set() | |
for opt in opts: | |
prefix, value = split_opt(opt) | |
if not prefix: | |
raise ValueError(f"Invalid start character for option ({opt})") | |
self.prefixes.add(prefix[0]) | |
if len(prefix) == 1 and len(value) == 1: | |
self._short_opts.append(opt) | |
else: | |
self._long_opts.append(opt) | |
self.prefixes.add(prefix) | |
if action is None: | |
action = "store" | |
self.dest = dest | |
self.action = action | |
self.nargs = nargs | |
self.const = const | |
self.obj = obj | |
def takes_value(self) -> bool: | |
return self.action in ("store", "append") | |
def process(self, value: t.Any, state: "ParsingState") -> None: | |
if self.action == "store": | |
state.opts[self.dest] = value # type: ignore | |
elif self.action == "store_const": | |
state.opts[self.dest] = self.const # type: ignore | |
elif self.action == "append": | |
state.opts.setdefault(self.dest, []).append(value) # type: ignore | |
elif self.action == "append_const": | |
state.opts.setdefault(self.dest, []).append(self.const) # type: ignore | |
elif self.action == "count": | |
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore | |
else: | |
raise ValueError(f"unknown action '{self.action}'") | |
state.order.append(self.obj) | |
class Argument: | |
def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1): | |
self.dest = dest | |
self.nargs = nargs | |
self.obj = obj | |
def process( | |
self, | |
value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]], | |
state: "ParsingState", | |
) -> None: | |
if self.nargs > 1: | |
assert value is not None | |
holes = sum(1 for x in value if x is None) | |
if holes == len(value): | |
value = None | |
elif holes != 0: | |
raise BadArgumentUsage( | |
_("Argument {name!r} takes {nargs} values.").format( | |
name=self.dest, nargs=self.nargs | |
) | |
) | |
if self.nargs == -1 and self.obj.envvar is not None and value == (): | |
# Replace empty tuple with None so that a value from the | |
# environment may be tried. | |
value = None | |
state.opts[self.dest] = value # type: ignore | |
state.order.append(self.obj) | |
class ParsingState: | |
def __init__(self, rargs: t.List[str]) -> None: | |
self.opts: t.Dict[str, t.Any] = {} | |
self.largs: t.List[str] = [] | |
self.rargs = rargs | |
self.order: t.List["CoreParameter"] = [] | |
class OptionParser: | |
"""The option parser is an internal class that is ultimately used to | |
parse options and arguments. It's modelled after optparse and brings | |
a similar but vastly simplified API. It should generally not be used | |
directly as the high level Click classes wrap it for you. | |
It's not nearly as extensible as optparse or argparse as it does not | |
implement features that are implemented on a higher level (such as | |
types or defaults). | |
:param ctx: optionally the :class:`~click.Context` where this parser | |
should go with. | |
""" | |
def __init__(self, ctx: t.Optional["Context"] = None) -> None: | |
#: The :class:`~click.Context` for this parser. This might be | |
#: `None` for some advanced use cases. | |
self.ctx = ctx | |
#: This controls how the parser deals with interspersed arguments. | |
#: If this is set to `False`, the parser will stop on the first | |
#: non-option. Click uses this to implement nested subcommands | |
#: safely. | |
self.allow_interspersed_args: bool = True | |
#: This tells the parser how to deal with unknown options. By | |
#: default it will error out (which is sensible), but there is a | |
#: second mode where it will ignore it and continue processing | |
#: after shifting all the unknown options into the resulting args. | |
self.ignore_unknown_options: bool = False | |
if ctx is not None: | |
self.allow_interspersed_args = ctx.allow_interspersed_args | |
self.ignore_unknown_options = ctx.ignore_unknown_options | |
self._short_opt: t.Dict[str, Option] = {} | |
self._long_opt: t.Dict[str, Option] = {} | |
self._opt_prefixes = {"-", "--"} | |
self._args: t.List[Argument] = [] | |
def add_option( | |
self, | |
obj: "CoreOption", | |
opts: t.Sequence[str], | |
dest: t.Optional[str], | |
action: t.Optional[str] = None, | |
nargs: int = 1, | |
const: t.Optional[t.Any] = None, | |
) -> None: | |
"""Adds a new option named `dest` to the parser. The destination | |
is not inferred (unlike with optparse) and needs to be explicitly | |
provided. Action can be any of ``store``, ``store_const``, | |
``append``, ``append_const`` or ``count``. | |
The `obj` can be used to identify the option in the order list | |
that is returned from the parser. | |
""" | |
opts = [normalize_opt(opt, self.ctx) for opt in opts] | |
option = Option(obj, opts, dest, action=action, nargs=nargs, const=const) | |
self._opt_prefixes.update(option.prefixes) | |
for opt in option._short_opts: | |
self._short_opt[opt] = option | |
for opt in option._long_opts: | |
self._long_opt[opt] = option | |
def add_argument( | |
self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1 | |
) -> None: | |
"""Adds a positional argument named `dest` to the parser. | |
The `obj` can be used to identify the option in the order list | |
that is returned from the parser. | |
""" | |
self._args.append(Argument(obj, dest=dest, nargs=nargs)) | |
def parse_args( | |
self, args: t.List[str] | |
) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]: | |
"""Parses positional arguments and returns ``(values, args, order)`` | |
for the parsed options and arguments as well as the leftover | |
arguments if there are any. The order is a list of objects as they | |
appear on the command line. If arguments appear multiple times they | |
will be memorized multiple times as well. | |
""" | |
state = ParsingState(args) | |
try: | |
self._process_args_for_options(state) | |
self._process_args_for_args(state) | |
except UsageError: | |
if self.ctx is None or not self.ctx.resilient_parsing: | |
raise | |
return state.opts, state.largs, state.order | |
def _process_args_for_args(self, state: ParsingState) -> None: | |
pargs, args = _unpack_args( | |
state.largs + state.rargs, [x.nargs for x in self._args] | |
) | |
for idx, arg in enumerate(self._args): | |
arg.process(pargs[idx], state) | |
state.largs = args | |
state.rargs = [] | |
def _process_args_for_options(self, state: ParsingState) -> None: | |
while state.rargs: | |
arg = state.rargs.pop(0) | |
arglen = len(arg) | |
# Double dashes always handled explicitly regardless of what | |
# prefixes are valid. | |
if arg == "--": | |
return | |
elif arg[:1] in self._opt_prefixes and arglen > 1: | |
self._process_opts(arg, state) | |
elif self.allow_interspersed_args: | |
state.largs.append(arg) | |
else: | |
state.rargs.insert(0, arg) | |
return | |
# Say this is the original argument list: | |
# [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] | |
# ^ | |
# (we are about to process arg(i)). | |
# | |
# Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of | |
# [arg0, ..., arg(i-1)] (any options and their arguments will have | |
# been removed from largs). | |
# | |
# The while loop will usually consume 1 or more arguments per pass. | |
# If it consumes 1 (eg. arg is an option that takes no arguments), | |
# then after _process_arg() is done the situation is: | |
# | |
# largs = subset of [arg0, ..., arg(i)] | |
# rargs = [arg(i+1), ..., arg(N-1)] | |
# | |
# If allow_interspersed_args is false, largs will always be | |
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but | |
# not a very interesting subset! | |
def _match_long_opt( | |
self, opt: str, explicit_value: t.Optional[str], state: ParsingState | |
) -> None: | |
if opt not in self._long_opt: | |
from difflib import get_close_matches | |
possibilities = get_close_matches(opt, self._long_opt) | |
raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) | |
option = self._long_opt[opt] | |
if option.takes_value: | |
# At this point it's safe to modify rargs by injecting the | |
# explicit value, because no exception is raised in this | |
# branch. This means that the inserted value will be fully | |
# consumed. | |
if explicit_value is not None: | |
state.rargs.insert(0, explicit_value) | |
value = self._get_value_from_state(opt, option, state) | |
elif explicit_value is not None: | |
raise BadOptionUsage( | |
opt, _("Option {name!r} does not take a value.").format(name=opt) | |
) | |
else: | |
value = None | |
option.process(value, state) | |
def _match_short_opt(self, arg: str, state: ParsingState) -> None: | |
stop = False | |
i = 1 | |
prefix = arg[0] | |
unknown_options = [] | |
for ch in arg[1:]: | |
opt = normalize_opt(f"{prefix}{ch}", self.ctx) | |
option = self._short_opt.get(opt) | |
i += 1 | |
if not option: | |
if self.ignore_unknown_options: | |
unknown_options.append(ch) | |
continue | |
raise NoSuchOption(opt, ctx=self.ctx) | |
if option.takes_value: | |
# Any characters left in arg? Pretend they're the | |
# next arg, and stop consuming characters of arg. | |
if i < len(arg): | |
state.rargs.insert(0, arg[i:]) | |
stop = True | |
value = self._get_value_from_state(opt, option, state) | |
else: | |
value = None | |
option.process(value, state) | |
if stop: | |
break | |
# If we got any unknown options we recombine the string of the | |
# remaining options and re-attach the prefix, then report that | |
# to the state as new larg. This way there is basic combinatorics | |
# that can be achieved while still ignoring unknown arguments. | |
if self.ignore_unknown_options and unknown_options: | |
state.largs.append(f"{prefix}{''.join(unknown_options)}") | |
def _get_value_from_state( | |
self, option_name: str, option: Option, state: ParsingState | |
) -> t.Any: | |
nargs = option.nargs | |
if len(state.rargs) < nargs: | |
if option.obj._flag_needs_value: | |
# Option allows omitting the value. | |
value = _flag_needs_value | |
else: | |
raise BadOptionUsage( | |
option_name, | |
ngettext( | |
"Option {name!r} requires an argument.", | |
"Option {name!r} requires {nargs} arguments.", | |
nargs, | |
).format(name=option_name, nargs=nargs), | |
) | |
elif nargs == 1: | |
next_rarg = state.rargs[0] | |
if ( | |
option.obj._flag_needs_value | |
and isinstance(next_rarg, str) | |
and next_rarg[:1] in self._opt_prefixes | |
and len(next_rarg) > 1 | |
): | |
# The next arg looks like the start of an option, don't | |
# use it as the value if omitting the value is allowed. | |
value = _flag_needs_value | |
else: | |
value = state.rargs.pop(0) | |
else: | |
value = tuple(state.rargs[:nargs]) | |
del state.rargs[:nargs] | |
return value | |
def _process_opts(self, arg: str, state: ParsingState) -> None: | |
explicit_value = None | |
# Long option handling happens in two parts. The first part is | |
# supporting explicitly attached values. In any case, we will try | |
# to long match the option first. | |
if "=" in arg: | |
long_opt, explicit_value = arg.split("=", 1) | |
else: | |
long_opt = arg | |
norm_long_opt = normalize_opt(long_opt, self.ctx) | |
# At this point we will match the (assumed) long option through | |
# the long option matching code. Note that this allows options | |
# like "-foo" to be matched as long options. | |
try: | |
self._match_long_opt(norm_long_opt, explicit_value, state) | |
except NoSuchOption: | |
# At this point the long option matching failed, and we need | |
# to try with short options. However there is a special rule | |
# which says, that if we have a two character options prefix | |
# (applies to "--foo" for instance), we do not dispatch to the | |
# short option code and will instead raise the no option | |
# error. | |
if arg[:2] not in self._opt_prefixes: | |
self._match_short_opt(arg, state) | |
return | |
if not self.ignore_unknown_options: | |
raise | |
state.largs.append(arg) | |