Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
parser.py530 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# This code uses parts of optparse written by Gregory P. Ward and21# maintained by the Python Software Foundation.22# Copyright 2001-2006 Gregory P. Ward23# Copyright 2002-2006 Python Software Foundation24import typing as t25from collections import deque26from gettext import gettext as _27from gettext import ngettext28 29from .exceptions import BadArgumentUsage30from .exceptions import BadOptionUsage31from .exceptions import NoSuchOption32from .exceptions import UsageError33 34if t.TYPE_CHECKING:35    import typing_extensions as te36    from .core import Argument as CoreArgument37    from .core import Context38    from .core import Option as CoreOption39    from .core import Parameter as CoreParameter40 41V = t.TypeVar("V")42 43# Sentinel value that indicates an option was passed as a flag without a44# value but is not a flag option. Option.consume_value uses this to45# prompt or use the flag_value.46_flag_needs_value = object()47 48 49def _unpack_args(50    args: t.Sequence[str], nargs_spec: t.Sequence[int]51) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]:52    """Given an iterable of arguments and an iterable of nargs specifications,53    it returns a tuple with all the unpacked arguments at the first index54    and all remaining arguments as the second.55 56    The nargs specification is the number of arguments that should be consumed57    or `-1` to indicate that this position should eat up all the remainders.58 59    Missing items are filled with `None`.60    """61    args = deque(args)62    nargs_spec = deque(nargs_spec)63    rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = []64    spos: t.Optional[int] = None65 66    def _fetch(c: "te.Deque[V]") -> t.Optional[V]:67        try:68            if spos is None:69                return c.popleft()70            else:71                return c.pop()72        except IndexError:73            return None74 75    while nargs_spec:76        nargs = _fetch(nargs_spec)77 78        if nargs is None:79            continue80 81        if nargs == 1:82            rv.append(_fetch(args))83        elif nargs > 1:84            x = [_fetch(args) for _ in range(nargs)]85 86            # If we're reversed, we're pulling in the arguments in reverse,87            # so we need to turn them around.88            if spos is not None:89                x.reverse()90 91            rv.append(tuple(x))92        elif nargs < 0:93            if spos is not None:94                raise TypeError("Cannot have two nargs < 0")95 96            spos = len(rv)97            rv.append(None)98 99    # spos is the position of the wildcard (star).  If it's not `None`,100    # we fill it with the remainder.101    if spos is not None:102        rv[spos] = tuple(args)103        args = []104        rv[spos + 1 :] = reversed(rv[spos + 1 :])105 106    return tuple(rv), list(args)107 108 109def split_opt(opt: str) -> t.Tuple[str, str]:110    first = opt[:1]111    if first.isalnum():112        return "", opt113    if opt[1:2] == first:114        return opt[:2], opt[2:]115    return first, opt[1:]116 117 118def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str:119    if ctx is None or ctx.token_normalize_func is None:120        return opt121    prefix, opt = split_opt(opt)122    return f"{prefix}{ctx.token_normalize_func(opt)}"123 124 125def split_arg_string(string: str) -> t.List[str]:126    """Split an argument string as with :func:`shlex.split`, but don't127    fail if the string is incomplete. Ignores a missing closing quote or128    incomplete escape sequence and uses the partial token as-is.129 130    .. code-block:: python131 132        split_arg_string("example 'my file")133        ["example", "my file"]134 135        split_arg_string("example my\\")136        ["example", "my"]137 138    :param string: String to split.139    """140    import shlex141 142    lex = shlex.shlex(string, posix=True)143    lex.whitespace_split = True144    lex.commenters = ""145    out = []146 147    try:148        for token in lex:149            out.append(token)150    except ValueError:151        # Raised when end-of-string is reached in an invalid state. Use152        # the partial token as-is. The quote or escape character is in153        # lex.state, not lex.token.154        out.append(lex.token)155 156    return out157 158 159class Option:160    def __init__(161        self,162        obj: "CoreOption",163        opts: t.Sequence[str],164        dest: t.Optional[str],165        action: t.Optional[str] = None,166        nargs: int = 1,167        const: t.Optional[t.Any] = None,168    ):169        self._short_opts = []170        self._long_opts = []171        self.prefixes: t.Set[str] = set()172 173        for opt in opts:174            prefix, value = split_opt(opt)175            if not prefix:176                raise ValueError(f"Invalid start character for option ({opt})")177            self.prefixes.add(prefix[0])178            if len(prefix) == 1 and len(value) == 1:179                self._short_opts.append(opt)180            else:181                self._long_opts.append(opt)182                self.prefixes.add(prefix)183 184        if action is None:185            action = "store"186 187        self.dest = dest188        self.action = action189        self.nargs = nargs190        self.const = const191        self.obj = obj192 193    @property194    def takes_value(self) -> bool:195        return self.action in ("store", "append")196 197    def process(self, value: t.Any, state: "ParsingState") -> None:198        if self.action == "store":199            state.opts[self.dest] = value  # type: ignore200        elif self.action == "store_const":201            state.opts[self.dest] = self.const  # type: ignore202        elif self.action == "append":203            state.opts.setdefault(self.dest, []).append(value)  # type: ignore204        elif self.action == "append_const":205            state.opts.setdefault(self.dest, []).append(self.const)  # type: ignore206        elif self.action == "count":207            state.opts[self.dest] = state.opts.get(self.dest, 0) + 1  # type: ignore208        else:209            raise ValueError(f"unknown action '{self.action}'")210        state.order.append(self.obj)211 212 213class Argument:214    def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1):215        self.dest = dest216        self.nargs = nargs217        self.obj = obj218 219    def process(220        self,221        value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]],222        state: "ParsingState",223    ) -> None:224        if self.nargs > 1:225            assert value is not None226            holes = sum(1 for x in value if x is None)227            if holes == len(value):228                value = None229            elif holes != 0:230                raise BadArgumentUsage(231                    _("Argument {name!r} takes {nargs} values.").format(232                        name=self.dest, nargs=self.nargs233                    )234                )235 236        if self.nargs == -1 and self.obj.envvar is not None and value == ():237            # Replace empty tuple with None so that a value from the238            # environment may be tried.239            value = None240 241        state.opts[self.dest] = value  # type: ignore242        state.order.append(self.obj)243 244 245class ParsingState:246    def __init__(self, rargs: t.List[str]) -> None:247        self.opts: t.Dict[str, t.Any] = {}248        self.largs: t.List[str] = []249        self.rargs = rargs250        self.order: t.List["CoreParameter"] = []251 252 253class OptionParser:254    """The option parser is an internal class that is ultimately used to255    parse options and arguments.  It's modelled after optparse and brings256    a similar but vastly simplified API.  It should generally not be used257    directly as the high level Click classes wrap it for you.258 259    It's not nearly as extensible as optparse or argparse as it does not260    implement features that are implemented on a higher level (such as261    types or defaults).262 263    :param ctx: optionally the :class:`~click.Context` where this parser264                should go with.265    """266 267    def __init__(self, ctx: t.Optional["Context"] = None) -> None:268        #: The :class:`~click.Context` for this parser.  This might be269        #: `None` for some advanced use cases.270        self.ctx = ctx271        #: This controls how the parser deals with interspersed arguments.272        #: If this is set to `False`, the parser will stop on the first273        #: non-option.  Click uses this to implement nested subcommands274        #: safely.275        self.allow_interspersed_args: bool = True276        #: This tells the parser how to deal with unknown options.  By277        #: default it will error out (which is sensible), but there is a278        #: second mode where it will ignore it and continue processing279        #: after shifting all the unknown options into the resulting args.280        self.ignore_unknown_options: bool = False281 282        if ctx is not None:283            self.allow_interspersed_args = ctx.allow_interspersed_args284            self.ignore_unknown_options = ctx.ignore_unknown_options285 286        self._short_opt: t.Dict[str, Option] = {}287        self._long_opt: t.Dict[str, Option] = {}288        self._opt_prefixes = {"-", "--"}289        self._args: t.List[Argument] = []290 291    def add_option(292        self,293        obj: "CoreOption",294        opts: t.Sequence[str],295        dest: t.Optional[str],296        action: t.Optional[str] = None,297        nargs: int = 1,298        const: t.Optional[t.Any] = None,299    ) -> None:300        """Adds a new option named `dest` to the parser.  The destination301        is not inferred (unlike with optparse) and needs to be explicitly302        provided.  Action can be any of ``store``, ``store_const``,303        ``append``, ``append_const`` or ``count``.304 305        The `obj` can be used to identify the option in the order list306        that is returned from the parser.307        """308        opts = [normalize_opt(opt, self.ctx) for opt in opts]309        option = Option(obj, opts, dest, action=action, nargs=nargs, const=const)310        self._opt_prefixes.update(option.prefixes)311        for opt in option._short_opts:312            self._short_opt[opt] = option313        for opt in option._long_opts:314            self._long_opt[opt] = option315 316    def add_argument(317        self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1318    ) -> None:319        """Adds a positional argument named `dest` to the parser.320 321        The `obj` can be used to identify the option in the order list322        that is returned from the parser.323        """324        self._args.append(Argument(obj, dest=dest, nargs=nargs))325 326    def parse_args(327        self, args: t.List[str]328    ) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]:329        """Parses positional arguments and returns ``(values, args, order)``330        for the parsed options and arguments as well as the leftover331        arguments if there are any.  The order is a list of objects as they332        appear on the command line.  If arguments appear multiple times they333        will be memorized multiple times as well.334        """335        state = ParsingState(args)336        try:337            self._process_args_for_options(state)338            self._process_args_for_args(state)339        except UsageError:340            if self.ctx is None or not self.ctx.resilient_parsing:341                raise342        return state.opts, state.largs, state.order343 344    def _process_args_for_args(self, state: ParsingState) -> None:345        pargs, args = _unpack_args(346            state.largs + state.rargs, [x.nargs for x in self._args]347        )348 349        for idx, arg in enumerate(self._args):350            arg.process(pargs[idx], state)351 352        state.largs = args353        state.rargs = []354 355    def _process_args_for_options(self, state: ParsingState) -> None:356        while state.rargs:357            arg = state.rargs.pop(0)358            arglen = len(arg)359            # Double dashes always handled explicitly regardless of what360            # prefixes are valid.361            if arg == "--":362                return363            elif arg[:1] in self._opt_prefixes and arglen > 1:364                self._process_opts(arg, state)365            elif self.allow_interspersed_args:366                state.largs.append(arg)367            else:368                state.rargs.insert(0, arg)369                return370 371        # Say this is the original argument list:372        # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]373        #                            ^374        # (we are about to process arg(i)).375        #376        # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of377        # [arg0, ..., arg(i-1)] (any options and their arguments will have378        # been removed from largs).379        #380        # The while loop will usually consume 1 or more arguments per pass.381        # If it consumes 1 (eg. arg is an option that takes no arguments),382        # then after _process_arg() is done the situation is:383        #384        #   largs = subset of [arg0, ..., arg(i)]385        #   rargs = [arg(i+1), ..., arg(N-1)]386        #387        # If allow_interspersed_args is false, largs will always be388        # *empty* -- still a subset of [arg0, ..., arg(i-1)], but389        # not a very interesting subset!390 391    def _match_long_opt(392        self, opt: str, explicit_value: t.Optional[str], state: ParsingState393    ) -> None:394        if opt not in self._long_opt:395            from difflib import get_close_matches396 397            possibilities = get_close_matches(opt, self._long_opt)398            raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)399 400        option = self._long_opt[opt]401        if option.takes_value:402            # At this point it's safe to modify rargs by injecting the403            # explicit value, because no exception is raised in this404            # branch.  This means that the inserted value will be fully405            # consumed.406            if explicit_value is not None:407                state.rargs.insert(0, explicit_value)408 409            value = self._get_value_from_state(opt, option, state)410 411        elif explicit_value is not None:412            raise BadOptionUsage(413                opt, _("Option {name!r} does not take a value.").format(name=opt)414            )415 416        else:417            value = None418 419        option.process(value, state)420 421    def _match_short_opt(self, arg: str, state: ParsingState) -> None:422        stop = False423        i = 1424        prefix = arg[0]425        unknown_options = []426 427        for ch in arg[1:]:428            opt = normalize_opt(f"{prefix}{ch}", self.ctx)429            option = self._short_opt.get(opt)430            i += 1431 432            if not option:433                if self.ignore_unknown_options:434                    unknown_options.append(ch)435                    continue436                raise NoSuchOption(opt, ctx=self.ctx)437            if option.takes_value:438                # Any characters left in arg?  Pretend they're the439                # next arg, and stop consuming characters of arg.440                if i < len(arg):441                    state.rargs.insert(0, arg[i:])442                    stop = True443 444                value = self._get_value_from_state(opt, option, state)445 446            else:447                value = None448 449            option.process(value, state)450 451            if stop:452                break453 454        # If we got any unknown options we recombine the string of the455        # remaining options and re-attach the prefix, then report that456        # to the state as new larg.  This way there is basic combinatorics457        # that can be achieved while still ignoring unknown arguments.458        if self.ignore_unknown_options and unknown_options:459            state.largs.append(f"{prefix}{''.join(unknown_options)}")460 461    def _get_value_from_state(462        self, option_name: str, option: Option, state: ParsingState463    ) -> t.Any:464        nargs = option.nargs465 466        if len(state.rargs) < nargs:467            if option.obj._flag_needs_value:468                # Option allows omitting the value.469                value = _flag_needs_value470            else:471                raise BadOptionUsage(472                    option_name,473                    ngettext(474                        "Option {name!r} requires an argument.",475                        "Option {name!r} requires {nargs} arguments.",476                        nargs,477                    ).format(name=option_name, nargs=nargs),478                )479        elif nargs == 1:480            next_rarg = state.rargs[0]481 482            if (483                option.obj._flag_needs_value484                and isinstance(next_rarg, str)485                and next_rarg[:1] in self._opt_prefixes486                and len(next_rarg) > 1487            ):488                # The next arg looks like the start of an option, don't489                # use it as the value if omitting the value is allowed.490                value = _flag_needs_value491            else:492                value = state.rargs.pop(0)493        else:494            value = tuple(state.rargs[:nargs])495            del state.rargs[:nargs]496 497        return value498 499    def _process_opts(self, arg: str, state: ParsingState) -> None:500        explicit_value = None501        # Long option handling happens in two parts.  The first part is502        # supporting explicitly attached values.  In any case, we will try503        # to long match the option first.504        if "=" in arg:505            long_opt, explicit_value = arg.split("=", 1)506        else:507            long_opt = arg508        norm_long_opt = normalize_opt(long_opt, self.ctx)509 510        # At this point we will match the (assumed) long option through511        # the long option matching code.  Note that this allows options512        # like "-foo" to be matched as long options.513        try:514            self._match_long_opt(norm_long_opt, explicit_value, state)515        except NoSuchOption:516            # At this point the long option matching failed, and we need517            # to try with short options.  However there is a special rule518            # which says, that if we have a two character options prefix519            # (applies to "--foo" for instance), we do not dispatch to the520            # short option code and will instead raise the no option521            # error.522            if arg[:2] not in self._opt_prefixes:523                self._match_short_opt(arg, state)524                return525 526            if not self.ignore_unknown_options:527                raise528 529            state.largs.append(arg)530 
codekingpro/portable-devtools · Team Ai