123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- """
- 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
- @property
- 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)
|