Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
parser.py533 linesDownload Raw Back to click
1"""2This module started out as largely a copy paste from the stdlib's3optparse module with the features removed that we do not need from4optparse because we implement them in Click on a higher level (for5instance type handling, help formatting and a lot more).6 7The plan is to remove more and more from here over time.8 9The reason this is a different module and not optparse from the stdlib10is that there are differences in 2.x and 3.x about the error messages11generated and optparse in the stdlib uses gettext for no good reason12and might cause us issues.13 14Click uses parts of optparse written by Gregory P. Ward and maintained15by the Python Software Foundation. This is limited to code in parser.py.16 17Copyright 2001-2006 Gregory P. Ward. All rights reserved.18Copyright 2002-2006 Python Software Foundation. All rights reserved.19"""20 21# This code uses parts of optparse written by Gregory P. Ward and22# maintained by the Python Software Foundation.23# Copyright 2001-2006 Gregory P. Ward24# Copyright 2002-2006 Python Software Foundation25from __future__ import annotations26 27import collections.abc as cabc28import typing as t29from collections import deque30from gettext import gettext as _31from gettext import ngettext32 33from ._utils import FLAG_NEEDS_VALUE34from ._utils import UNSET35from .exceptions import BadArgumentUsage36from .exceptions import BadOptionUsage37from .exceptions import NoSuchOption38from .exceptions import UsageError39 40if t.TYPE_CHECKING:41    from ._utils import T_FLAG_NEEDS_VALUE42    from ._utils import T_UNSET43    from .core import Argument as CoreArgument44    from .core import Context45    from .core import Option as CoreOption46    from .core import Parameter as CoreParameter47 48V = t.TypeVar("V")49 50 51def _unpack_args(52    args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int]53) -> tuple[cabc.Sequence[str | cabc.Sequence[str | None] | None], list[str]]:54    """Given an iterable of arguments and an iterable of nargs specifications,55    it returns a tuple with all the unpacked arguments at the first index56    and all remaining arguments as the second.57 58    The nargs specification is the number of arguments that should be consumed59    or `-1` to indicate that this position should eat up all the remainders.60 61    Missing items are filled with ``UNSET``.62    """63    args = deque(args)64    nargs_spec = deque(nargs_spec)65    rv: list[str | tuple[str | T_UNSET, ...] | T_UNSET] = []66    spos: int | None = None67 68    def _fetch(c: deque[V]) -> V | T_UNSET:69        try:70            if spos is None:71                return c.popleft()72            else:73                return c.pop()74        except IndexError:75            return UNSET76 77    while nargs_spec:78        nargs = _fetch(nargs_spec)79 80        if nargs is None:81            continue82 83        if nargs == 1:84            rv.append(_fetch(args))  # type: ignore[arg-type]85        elif nargs > 1:86            x = [_fetch(args) for _ in range(nargs)]87 88            # If we're reversed, we're pulling in the arguments in reverse,89            # so we need to turn them around.90            if spos is not None:91                x.reverse()92 93            rv.append(tuple(x))94        elif nargs < 0:95            if spos is not None:96                raise TypeError("Cannot have two nargs < 0")97 98            spos = len(rv)99            rv.append(UNSET)100 101    # spos is the position of the wildcard (star).  If it's not `None`,102    # we fill it with the remainder.103    if spos is not None:104        rv[spos] = tuple(args)105        args = []106        rv[spos + 1 :] = reversed(rv[spos + 1 :])107 108    return tuple(rv), list(args)109 110 111def _split_opt(opt: str) -> tuple[str, str]:112    first = opt[:1]113    if first.isalnum():114        return "", opt115    if opt[1:2] == first:116        return opt[:2], opt[2:]117    return first, opt[1:]118 119 120def _normalize_opt(opt: str, ctx: Context | None) -> str:121    if ctx is None or ctx.token_normalize_func is None:122        return opt123    prefix, opt = _split_opt(opt)124    return f"{prefix}{ctx.token_normalize_func(opt)}"125 126 127class _Option:128    def __init__(129        self,130        obj: CoreOption,131        opts: cabc.Sequence[str],132        dest: str | None,133        action: str | None = None,134        nargs: int = 1,135        const: t.Any | None = None,136    ):137        self._short_opts = []138        self._long_opts = []139        self.prefixes: set[str] = set()140 141        for opt in opts:142            prefix, value = _split_opt(opt)143            if not prefix:144                raise ValueError(f"Invalid start character for option ({opt})")145            self.prefixes.add(prefix[0])146            if len(prefix) == 1 and len(value) == 1:147                self._short_opts.append(opt)148            else:149                self._long_opts.append(opt)150                self.prefixes.add(prefix)151 152        if action is None:153            action = "store"154 155        self.dest = dest156        self.action = action157        self.nargs = nargs158        self.const = const159        self.obj = obj160 161    @property162    def takes_value(self) -> bool:163        return self.action in ("store", "append")164 165    def process(self, value: t.Any, state: _ParsingState) -> None:166        if self.action == "store":167            state.opts[self.dest] = value  # type: ignore168        elif self.action == "store_const":169            state.opts[self.dest] = self.const  # type: ignore170        elif self.action == "append":171            state.opts.setdefault(self.dest, []).append(value)  # type: ignore172        elif self.action == "append_const":173            state.opts.setdefault(self.dest, []).append(self.const)  # type: ignore174        elif self.action == "count":175            state.opts[self.dest] = state.opts.get(self.dest, 0) + 1  # type: ignore176        else:177            raise ValueError(f"unknown action '{self.action}'")178        state.order.append(self.obj)179 180 181class _Argument:182    def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1):183        self.dest = dest184        self.nargs = nargs185        self.obj = obj186 187    def process(188        self,189        value: str | cabc.Sequence[str | None] | None | T_UNSET,190        state: _ParsingState,191    ) -> None:192        if self.nargs > 1:193            assert isinstance(value, cabc.Sequence)194            holes = sum(1 for x in value if x is UNSET)195            if holes == len(value):196                value = UNSET197            elif holes != 0:198                raise BadArgumentUsage(199                    _("Argument {name!r} takes {nargs} values.").format(200                        name=self.dest, nargs=self.nargs201                    )202                )203 204        # We failed to collect any argument value so we consider the argument as unset.205        if value == ():206            value = UNSET207 208        state.opts[self.dest] = value  # type: ignore209        state.order.append(self.obj)210 211 212class _ParsingState:213    def __init__(self, rargs: list[str]) -> None:214        self.opts: dict[str, t.Any] = {}215        self.largs: list[str] = []216        self.rargs = rargs217        self.order: list[CoreParameter] = []218 219 220class _OptionParser:221    """The option parser is an internal class that is ultimately used to222    parse options and arguments.  It's modelled after optparse and brings223    a similar but vastly simplified API.  It should generally not be used224    directly as the high level Click classes wrap it for you.225 226    It's not nearly as extensible as optparse or argparse as it does not227    implement features that are implemented on a higher level (such as228    types or defaults).229 230    :param ctx: optionally the :class:`~click.Context` where this parser231                should go with.232 233    .. deprecated:: 8.2234        Will be removed in Click 9.0.235    """236 237    def __init__(self, ctx: Context | None = None) -> None:238        #: The :class:`~click.Context` for this parser.  This might be239        #: `None` for some advanced use cases.240        self.ctx = ctx241        #: This controls how the parser deals with interspersed arguments.242        #: If this is set to `False`, the parser will stop on the first243        #: non-option.  Click uses this to implement nested subcommands244        #: safely.245        self.allow_interspersed_args: bool = True246        #: This tells the parser how to deal with unknown options.  By247        #: default it will error out (which is sensible), but there is a248        #: second mode where it will ignore it and continue processing249        #: after shifting all the unknown options into the resulting args.250        self.ignore_unknown_options: bool = False251 252        if ctx is not None:253            self.allow_interspersed_args = ctx.allow_interspersed_args254            self.ignore_unknown_options = ctx.ignore_unknown_options255 256        self._short_opt: dict[str, _Option] = {}257        self._long_opt: dict[str, _Option] = {}258        self._opt_prefixes = {"-", "--"}259        self._args: list[_Argument] = []260 261    def add_option(262        self,263        obj: CoreOption,264        opts: cabc.Sequence[str],265        dest: str | None,266        action: str | None = None,267        nargs: int = 1,268        const: t.Any | None = None,269    ) -> None:270        """Adds a new option named `dest` to the parser.  The destination271        is not inferred (unlike with optparse) and needs to be explicitly272        provided.  Action can be any of ``store``, ``store_const``,273        ``append``, ``append_const`` or ``count``.274 275        The `obj` can be used to identify the option in the order list276        that is returned from the parser.277        """278        opts = [_normalize_opt(opt, self.ctx) for opt in opts]279        option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const)280        self._opt_prefixes.update(option.prefixes)281        for opt in option._short_opts:282            self._short_opt[opt] = option283        for opt in option._long_opts:284            self._long_opt[opt] = option285 286    def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None:287        """Adds a positional argument named `dest` to the parser.288 289        The `obj` can be used to identify the option in the order list290        that is returned from the parser.291        """292        self._args.append(_Argument(obj, dest=dest, nargs=nargs))293 294    def parse_args(295        self, args: list[str]296    ) -> tuple[dict[str, t.Any], list[str], list[CoreParameter]]:297        """Parses positional arguments and returns ``(values, args, order)``298        for the parsed options and arguments as well as the leftover299        arguments if there are any.  The order is a list of objects as they300        appear on the command line.  If arguments appear multiple times they301        will be memorized multiple times as well.302        """303        state = _ParsingState(args)304        try:305            self._process_args_for_options(state)306            self._process_args_for_args(state)307        except UsageError:308            if self.ctx is None or not self.ctx.resilient_parsing:309                raise310        return state.opts, state.largs, state.order311 312    def _process_args_for_args(self, state: _ParsingState) -> None:313        pargs, args = _unpack_args(314            state.largs + state.rargs, [x.nargs for x in self._args]315        )316 317        for idx, arg in enumerate(self._args):318            arg.process(pargs[idx], state)319 320        state.largs = args321        state.rargs = []322 323    def _process_args_for_options(self, state: _ParsingState) -> None:324        while state.rargs:325            arg = state.rargs.pop(0)326            arglen = len(arg)327            # Double dashes always handled explicitly regardless of what328            # prefixes are valid.329            if arg == "--":330                return331            elif arg[:1] in self._opt_prefixes and arglen > 1:332                self._process_opts(arg, state)333            elif self.allow_interspersed_args:334                state.largs.append(arg)335            else:336                state.rargs.insert(0, arg)337                return338 339        # Say this is the original argument list:340        # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]341        #                            ^342        # (we are about to process arg(i)).343        #344        # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of345        # [arg0, ..., arg(i-1)] (any options and their arguments will have346        # been removed from largs).347        #348        # The while loop will usually consume 1 or more arguments per pass.349        # If it consumes 1 (eg. arg is an option that takes no arguments),350        # then after _process_arg() is done the situation is:351        #352        #   largs = subset of [arg0, ..., arg(i)]353        #   rargs = [arg(i+1), ..., arg(N-1)]354        #355        # If allow_interspersed_args is false, largs will always be356        # *empty* -- still a subset of [arg0, ..., arg(i-1)], but357        # not a very interesting subset!358 359    def _match_long_opt(360        self, opt: str, explicit_value: str | None, state: _ParsingState361    ) -> None:362        if opt not in self._long_opt:363            from difflib import get_close_matches364 365            possibilities = get_close_matches(opt, self._long_opt)366            raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)367 368        option = self._long_opt[opt]369        if option.takes_value:370            # At this point it's safe to modify rargs by injecting the371            # explicit value, because no exception is raised in this372            # branch.  This means that the inserted value will be fully373            # consumed.374            if explicit_value is not None:375                state.rargs.insert(0, explicit_value)376 377            value = self._get_value_from_state(opt, option, state)378 379        elif explicit_value is not None:380            raise BadOptionUsage(381                opt, _("Option {name!r} does not take a value.").format(name=opt)382            )383 384        else:385            value = UNSET386 387        option.process(value, state)388 389    def _match_short_opt(self, arg: str, state: _ParsingState) -> None:390        stop = False391        i = 1392        prefix = arg[0]393        unknown_options = []394 395        for ch in arg[1:]:396            opt = _normalize_opt(f"{prefix}{ch}", self.ctx)397            option = self._short_opt.get(opt)398            i += 1399 400            if not option:401                if self.ignore_unknown_options:402                    unknown_options.append(ch)403                    continue404                raise NoSuchOption(opt, ctx=self.ctx)405            if option.takes_value:406                # Any characters left in arg?  Pretend they're the407                # next arg, and stop consuming characters of arg.408                if i < len(arg):409                    state.rargs.insert(0, arg[i:])410                    stop = True411 412                value = self._get_value_from_state(opt, option, state)413 414            else:415                value = UNSET416 417            option.process(value, state)418 419            if stop:420                break421 422        # If we got any unknown options we recombine the string of the423        # remaining options and re-attach the prefix, then report that424        # to the state as new larg.  This way there is basic combinatorics425        # that can be achieved while still ignoring unknown arguments.426        if self.ignore_unknown_options and unknown_options:427            state.largs.append(f"{prefix}{''.join(unknown_options)}")428 429    def _get_value_from_state(430        self, option_name: str, option: _Option, state: _ParsingState431    ) -> str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE:432        nargs = option.nargs433 434        value: str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE435 436        if len(state.rargs) < nargs:437            if option.obj._flag_needs_value:438                # Option allows omitting the value.439                value = FLAG_NEEDS_VALUE440            else:441                raise BadOptionUsage(442                    option_name,443                    ngettext(444                        "Option {name!r} requires an argument.",445                        "Option {name!r} requires {nargs} arguments.",446                        nargs,447                    ).format(name=option_name, nargs=nargs),448                )449        elif nargs == 1:450            next_rarg = state.rargs[0]451 452            if (453                option.obj._flag_needs_value454                and isinstance(next_rarg, str)455                and next_rarg[:1] in self._opt_prefixes456                and len(next_rarg) > 1457            ):458                # The next arg looks like the start of an option, don't459                # use it as the value if omitting the value is allowed.460                value = FLAG_NEEDS_VALUE461            else:462                value = state.rargs.pop(0)463        else:464            value = tuple(state.rargs[:nargs])465            del state.rargs[:nargs]466 467        return value468 469    def _process_opts(self, arg: str, state: _ParsingState) -> None:470        explicit_value = None471        # Long option handling happens in two parts.  The first part is472        # supporting explicitly attached values.  In any case, we will try473        # to long match the option first.474        if "=" in arg:475            long_opt, explicit_value = arg.split("=", 1)476        else:477            long_opt = arg478        norm_long_opt = _normalize_opt(long_opt, self.ctx)479 480        # At this point we will match the (assumed) long option through481        # the long option matching code.  Note that this allows options482        # like "-foo" to be matched as long options.483        try:484            self._match_long_opt(norm_long_opt, explicit_value, state)485        except NoSuchOption:486            # At this point the long option matching failed, and we need487            # to try with short options.  However there is a special rule488            # which says, that if we have a two character options prefix489            # (applies to "--foo" for instance), we do not dispatch to the490            # short option code and will instead raise the no option491            # error.492            if arg[:2] not in self._opt_prefixes:493                self._match_short_opt(arg, state)494                return495 496            if not self.ignore_unknown_options:497                raise498 499            state.largs.append(arg)500 501 502def __getattr__(name: str) -> object:503    import warnings504 505    if name in {506        "OptionParser",507        "Argument",508        "Option",509        "split_opt",510        "normalize_opt",511        "ParsingState",512    }:513        warnings.warn(514            f"'parser.{name}' is deprecated and will be removed in Click 9.0."515            " The old parser is available in 'optparse'.",516            DeprecationWarning,517            stacklevel=2,518        )519        return globals()[f"_{name}"]520 521    if name == "split_arg_string":522        from .shell_completion import split_arg_string523 524        warnings.warn(525            "Importing 'parser.split_arg_string' is deprecated, it will only be"526            " available in 'shell_completion' in Click 9.0.",527            DeprecationWarning,528            stacklevel=2,529        )530        return split_arg_string531 532    raise AttributeError(name)533 
codekingpro/portable-devtools · Team Ai