Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/click/parser.py: 64%
249 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 06:03 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 06:03 +0000
1"""
2This module started out as largely a copy paste from the stdlib's
3optparse module with the features removed that we do not need from
4optparse because we implement them in Click on a higher level (for
5instance type handling, help formatting and a lot more).
7The plan is to remove more and more from here over time.
9The reason this is a different module and not optparse from the stdlib
10is that there are differences in 2.x and 3.x about the error messages
11generated and optparse in the stdlib uses gettext for no good reason
12and might cause us issues.
14Click uses parts of optparse written by Gregory P. Ward and maintained
15by the Python Software Foundation. This is limited to code in parser.py.
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 and
21# maintained by the Python Software Foundation.
22# Copyright 2001-2006 Gregory P. Ward
23# Copyright 2002-2006 Python Software Foundation
24from __future__ import annotations
26import collections.abc as cabc
27import typing as t
28from collections import deque
29from gettext import gettext as _
30from gettext import ngettext
32from .exceptions import BadArgumentUsage
33from .exceptions import BadOptionUsage
34from .exceptions import NoSuchOption
35from .exceptions import UsageError
37if t.TYPE_CHECKING:
38 from .core import Argument as CoreArgument
39 from .core import Context
40 from .core import Option as CoreOption
41 from .core import Parameter as CoreParameter
43V = t.TypeVar("V")
45# Sentinel value that indicates an option was passed as a flag without a
46# value but is not a flag option. Option.consume_value uses this to
47# prompt or use the flag_value.
48_flag_needs_value = object()
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 index
56 and all remaining arguments as the second.
58 The nargs specification is the number of arguments that should be consumed
59 or `-1` to indicate that this position should eat up all the remainders.
61 Missing items are filled with `None`.
62 """
63 args = deque(args)
64 nargs_spec = deque(nargs_spec)
65 rv: list[str | tuple[str | None, ...] | None] = []
66 spos: int | None = None
68 def _fetch(c: deque[V]) -> V | None:
69 try:
70 if spos is None:
71 return c.popleft()
72 else:
73 return c.pop()
74 except IndexError:
75 return None
77 while nargs_spec:
78 nargs = _fetch(nargs_spec)
80 if nargs is None:
81 continue
83 if nargs == 1:
84 rv.append(_fetch(args))
85 elif nargs > 1:
86 x = [_fetch(args) for _ in range(nargs)]
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()
93 rv.append(tuple(x))
94 elif nargs < 0:
95 if spos is not None:
96 raise TypeError("Cannot have two nargs < 0")
98 spos = len(rv)
99 rv.append(None)
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 :])
108 return tuple(rv), list(args)
111def _split_opt(opt: str) -> tuple[str, str]:
112 first = opt[:1]
113 if first.isalnum():
114 return "", opt
115 if opt[1:2] == first:
116 return opt[:2], opt[2:]
117 return first, opt[1:]
120def _normalize_opt(opt: str, ctx: Context | None) -> str:
121 if ctx is None or ctx.token_normalize_func is None:
122 return opt
123 prefix, opt = _split_opt(opt)
124 return f"{prefix}{ctx.token_normalize_func(opt)}"
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()
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)
152 if action is None:
153 action = "store"
155 self.dest = dest
156 self.action = action
157 self.nargs = nargs
158 self.const = const
159 self.obj = obj
161 @property
162 def takes_value(self) -> bool:
163 return self.action in ("store", "append")
165 def process(self, value: t.Any, state: _ParsingState) -> None:
166 if self.action == "store":
167 state.opts[self.dest] = value # type: ignore
168 elif self.action == "store_const":
169 state.opts[self.dest] = self.const # type: ignore
170 elif self.action == "append":
171 state.opts.setdefault(self.dest, []).append(value) # type: ignore
172 elif self.action == "append_const":
173 state.opts.setdefault(self.dest, []).append(self.const) # type: ignore
174 elif self.action == "count":
175 state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore
176 else:
177 raise ValueError(f"unknown action '{self.action}'")
178 state.order.append(self.obj)
181class _Argument:
182 def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1):
183 self.dest = dest
184 self.nargs = nargs
185 self.obj = obj
187 def process(
188 self,
189 value: str | cabc.Sequence[str | None] | None,
190 state: _ParsingState,
191 ) -> None:
192 if self.nargs > 1:
193 assert value is not None
194 holes = sum(1 for x in value if x is None)
195 if holes == len(value):
196 value = None
197 elif holes != 0:
198 raise BadArgumentUsage(
199 _("Argument {name!r} takes {nargs} values.").format(
200 name=self.dest, nargs=self.nargs
201 )
202 )
204 if self.nargs == -1 and self.obj.envvar is not None and value == ():
205 # Replace empty tuple with None so that a value from the
206 # environment may be tried.
207 value = None
209 state.opts[self.dest] = value # type: ignore
210 state.order.append(self.obj)
213class _ParsingState:
214 def __init__(self, rargs: list[str]) -> None:
215 self.opts: dict[str, t.Any] = {}
216 self.largs: list[str] = []
217 self.rargs = rargs
218 self.order: list[CoreParameter] = []
221class _OptionParser:
222 """The option parser is an internal class that is ultimately used to
223 parse options and arguments. It's modelled after optparse and brings
224 a similar but vastly simplified API. It should generally not be used
225 directly as the high level Click classes wrap it for you.
227 It's not nearly as extensible as optparse or argparse as it does not
228 implement features that are implemented on a higher level (such as
229 types or defaults).
231 :param ctx: optionally the :class:`~click.Context` where this parser
232 should go with.
234 .. deprecated:: 8.2
235 Will be removed in Click 9.0.
236 """
238 def __init__(self, ctx: Context | None = None) -> None:
239 #: The :class:`~click.Context` for this parser. This might be
240 #: `None` for some advanced use cases.
241 self.ctx = ctx
242 #: This controls how the parser deals with interspersed arguments.
243 #: If this is set to `False`, the parser will stop on the first
244 #: non-option. Click uses this to implement nested subcommands
245 #: safely.
246 self.allow_interspersed_args: bool = True
247 #: This tells the parser how to deal with unknown options. By
248 #: default it will error out (which is sensible), but there is a
249 #: second mode where it will ignore it and continue processing
250 #: after shifting all the unknown options into the resulting args.
251 self.ignore_unknown_options: bool = False
253 if ctx is not None:
254 self.allow_interspersed_args = ctx.allow_interspersed_args
255 self.ignore_unknown_options = ctx.ignore_unknown_options
257 self._short_opt: dict[str, _Option] = {}
258 self._long_opt: dict[str, _Option] = {}
259 self._opt_prefixes = {"-", "--"}
260 self._args: list[_Argument] = []
262 def add_option(
263 self,
264 obj: CoreOption,
265 opts: cabc.Sequence[str],
266 dest: str | None,
267 action: str | None = None,
268 nargs: int = 1,
269 const: t.Any | None = None,
270 ) -> None:
271 """Adds a new option named `dest` to the parser. The destination
272 is not inferred (unlike with optparse) and needs to be explicitly
273 provided. Action can be any of ``store``, ``store_const``,
274 ``append``, ``append_const`` or ``count``.
276 The `obj` can be used to identify the option in the order list
277 that is returned from the parser.
278 """
279 opts = [_normalize_opt(opt, self.ctx) for opt in opts]
280 option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const)
281 self._opt_prefixes.update(option.prefixes)
282 for opt in option._short_opts:
283 self._short_opt[opt] = option
284 for opt in option._long_opts:
285 self._long_opt[opt] = option
287 def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None:
288 """Adds a positional argument named `dest` to the parser.
290 The `obj` can be used to identify the option in the order list
291 that is returned from the parser.
292 """
293 self._args.append(_Argument(obj, dest=dest, nargs=nargs))
295 def parse_args(
296 self, args: list[str]
297 ) -> tuple[dict[str, t.Any], list[str], list[CoreParameter]]:
298 """Parses positional arguments and returns ``(values, args, order)``
299 for the parsed options and arguments as well as the leftover
300 arguments if there are any. The order is a list of objects as they
301 appear on the command line. If arguments appear multiple times they
302 will be memorized multiple times as well.
303 """
304 state = _ParsingState(args)
305 try:
306 self._process_args_for_options(state)
307 self._process_args_for_args(state)
308 except UsageError:
309 if self.ctx is None or not self.ctx.resilient_parsing:
310 raise
311 return state.opts, state.largs, state.order
313 def _process_args_for_args(self, state: _ParsingState) -> None:
314 pargs, args = _unpack_args(
315 state.largs + state.rargs, [x.nargs for x in self._args]
316 )
318 for idx, arg in enumerate(self._args):
319 arg.process(pargs[idx], state)
321 state.largs = args
322 state.rargs = []
324 def _process_args_for_options(self, state: _ParsingState) -> None:
325 while state.rargs:
326 arg = state.rargs.pop(0)
327 arglen = len(arg)
328 # Double dashes always handled explicitly regardless of what
329 # prefixes are valid.
330 if arg == "--":
331 return
332 elif arg[:1] in self._opt_prefixes and arglen > 1:
333 self._process_opts(arg, state)
334 elif self.allow_interspersed_args:
335 state.largs.append(arg)
336 else:
337 state.rargs.insert(0, arg)
338 return
340 # Say this is the original argument list:
341 # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
342 # ^
343 # (we are about to process arg(i)).
344 #
345 # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
346 # [arg0, ..., arg(i-1)] (any options and their arguments will have
347 # been removed from largs).
348 #
349 # The while loop will usually consume 1 or more arguments per pass.
350 # If it consumes 1 (eg. arg is an option that takes no arguments),
351 # then after _process_arg() is done the situation is:
352 #
353 # largs = subset of [arg0, ..., arg(i)]
354 # rargs = [arg(i+1), ..., arg(N-1)]
355 #
356 # If allow_interspersed_args is false, largs will always be
357 # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
358 # not a very interesting subset!
360 def _match_long_opt(
361 self, opt: str, explicit_value: str | None, state: _ParsingState
362 ) -> None:
363 if opt not in self._long_opt:
364 from difflib import get_close_matches
366 possibilities = get_close_matches(opt, self._long_opt)
367 raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
369 option = self._long_opt[opt]
370 if option.takes_value:
371 # At this point it's safe to modify rargs by injecting the
372 # explicit value, because no exception is raised in this
373 # branch. This means that the inserted value will be fully
374 # consumed.
375 if explicit_value is not None:
376 state.rargs.insert(0, explicit_value)
378 value = self._get_value_from_state(opt, option, state)
380 elif explicit_value is not None:
381 raise BadOptionUsage(
382 opt, _("Option {name!r} does not take a value.").format(name=opt)
383 )
385 else:
386 value = None
388 option.process(value, state)
390 def _match_short_opt(self, arg: str, state: _ParsingState) -> None:
391 stop = False
392 i = 1
393 prefix = arg[0]
394 unknown_options = []
396 for ch in arg[1:]:
397 opt = _normalize_opt(f"{prefix}{ch}", self.ctx)
398 option = self._short_opt.get(opt)
399 i += 1
401 if not option:
402 if self.ignore_unknown_options:
403 unknown_options.append(ch)
404 continue
405 raise NoSuchOption(opt, ctx=self.ctx)
406 if option.takes_value:
407 # Any characters left in arg? Pretend they're the
408 # next arg, and stop consuming characters of arg.
409 if i < len(arg):
410 state.rargs.insert(0, arg[i:])
411 stop = True
413 value = self._get_value_from_state(opt, option, state)
415 else:
416 value = None
418 option.process(value, state)
420 if stop:
421 break
423 # If we got any unknown options we recombine the string of the
424 # remaining options and re-attach the prefix, then report that
425 # to the state as new larg. This way there is basic combinatorics
426 # that can be achieved while still ignoring unknown arguments.
427 if self.ignore_unknown_options and unknown_options:
428 state.largs.append(f"{prefix}{''.join(unknown_options)}")
430 def _get_value_from_state(
431 self, option_name: str, option: _Option, state: _ParsingState
432 ) -> t.Any:
433 nargs = option.nargs
435 if len(state.rargs) < nargs:
436 if option.obj._flag_needs_value:
437 # Option allows omitting the value.
438 value = _flag_needs_value
439 else:
440 raise BadOptionUsage(
441 option_name,
442 ngettext(
443 "Option {name!r} requires an argument.",
444 "Option {name!r} requires {nargs} arguments.",
445 nargs,
446 ).format(name=option_name, nargs=nargs),
447 )
448 elif nargs == 1:
449 next_rarg = state.rargs[0]
451 if (
452 option.obj._flag_needs_value
453 and isinstance(next_rarg, str)
454 and next_rarg[:1] in self._opt_prefixes
455 and len(next_rarg) > 1
456 ):
457 # The next arg looks like the start of an option, don't
458 # use it as the value if omitting the value is allowed.
459 value = _flag_needs_value
460 else:
461 value = state.rargs.pop(0)
462 else:
463 value = tuple(state.rargs[:nargs])
464 del state.rargs[:nargs]
466 return value
468 def _process_opts(self, arg: str, state: _ParsingState) -> None:
469 explicit_value = None
470 # Long option handling happens in two parts. The first part is
471 # supporting explicitly attached values. In any case, we will try
472 # to long match the option first.
473 if "=" in arg:
474 long_opt, explicit_value = arg.split("=", 1)
475 else:
476 long_opt = arg
477 norm_long_opt = _normalize_opt(long_opt, self.ctx)
479 # At this point we will match the (assumed) long option through
480 # the long option matching code. Note that this allows options
481 # like "-foo" to be matched as long options.
482 try:
483 self._match_long_opt(norm_long_opt, explicit_value, state)
484 except NoSuchOption:
485 # At this point the long option matching failed, and we need
486 # to try with short options. However there is a special rule
487 # which says, that if we have a two character options prefix
488 # (applies to "--foo" for instance), we do not dispatch to the
489 # short option code and will instead raise the no option
490 # error.
491 if arg[:2] not in self._opt_prefixes:
492 self._match_short_opt(arg, state)
493 return
495 if not self.ignore_unknown_options:
496 raise
498 state.largs.append(arg)
501def __getattr__(name: str) -> object:
502 import warnings
504 if name in {
505 "OptionParser",
506 "Argument",
507 "Option",
508 "split_opt",
509 "normalize_opt",
510 "ParsingState",
511 }:
512 warnings.warn(
513 f"'parser.{name}' is deprecated and will be removed in Click 9.0."
514 " The old parser is available in 'optparse'."
515 )
516 return globals()[f"_{name}"]
518 if name == "split_arg_string":
519 from .shell_completion import split_arg_string
521 warnings.warn(
522 "Importing 'parser.split_arg_string' is deprecated, it will only be"
523 " available in 'shell_completion' in Click 9.0.",
524 DeprecationWarning,
525 stacklevel=2,
526 )
527 return split_arg_string
529 raise AttributeError(name)