Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/click/parser.py: 64%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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"""
21# This code uses parts of optparse written by Gregory P. Ward and
22# maintained by the Python Software Foundation.
23# Copyright 2001-2006 Gregory P. Ward
24# Copyright 2002-2006 Python Software Foundation
25from __future__ import annotations
27import collections.abc as cabc
28import typing as t
29from collections import deque
30from gettext import gettext as _
31from gettext import ngettext
33from .exceptions import BadArgumentUsage
34from .exceptions import BadOptionUsage
35from .exceptions import NoSuchOption
36from .exceptions import UsageError
38if t.TYPE_CHECKING:
39 from .core import Argument as CoreArgument
40 from .core import Context
41 from .core import Option as CoreOption
42 from .core import Parameter as CoreParameter
44V = t.TypeVar("V")
46# Sentinel value that indicates an option was passed as a flag without a
47# value but is not a flag option. Option.consume_value uses this to
48# prompt or use the flag_value.
49_flag_needs_value = object()
52def _unpack_args(
53 args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int]
54) -> tuple[cabc.Sequence[str | cabc.Sequence[str | None] | None], list[str]]:
55 """Given an iterable of arguments and an iterable of nargs specifications,
56 it returns a tuple with all the unpacked arguments at the first index
57 and all remaining arguments as the second.
59 The nargs specification is the number of arguments that should be consumed
60 or `-1` to indicate that this position should eat up all the remainders.
62 Missing items are filled with `None`.
63 """
64 args = deque(args)
65 nargs_spec = deque(nargs_spec)
66 rv: list[str | tuple[str | None, ...] | None] = []
67 spos: int | None = None
69 def _fetch(c: deque[V]) -> V | None:
70 try:
71 if spos is None:
72 return c.popleft()
73 else:
74 return c.pop()
75 except IndexError:
76 return None
78 while nargs_spec:
79 nargs = _fetch(nargs_spec)
81 if nargs is None:
82 continue
84 if nargs == 1:
85 rv.append(_fetch(args))
86 elif nargs > 1:
87 x = [_fetch(args) for _ in range(nargs)]
89 # If we're reversed, we're pulling in the arguments in reverse,
90 # so we need to turn them around.
91 if spos is not None:
92 x.reverse()
94 rv.append(tuple(x))
95 elif nargs < 0:
96 if spos is not None:
97 raise TypeError("Cannot have two nargs < 0")
99 spos = len(rv)
100 rv.append(None)
102 # spos is the position of the wildcard (star). If it's not `None`,
103 # we fill it with the remainder.
104 if spos is not None:
105 rv[spos] = tuple(args)
106 args = []
107 rv[spos + 1 :] = reversed(rv[spos + 1 :])
109 return tuple(rv), list(args)
112def _split_opt(opt: str) -> tuple[str, str]:
113 first = opt[:1]
114 if first.isalnum():
115 return "", opt
116 if opt[1:2] == first:
117 return opt[:2], opt[2:]
118 return first, opt[1:]
121def _normalize_opt(opt: str, ctx: Context | None) -> str:
122 if ctx is None or ctx.token_normalize_func is None:
123 return opt
124 prefix, opt = _split_opt(opt)
125 return f"{prefix}{ctx.token_normalize_func(opt)}"
128class _Option:
129 def __init__(
130 self,
131 obj: CoreOption,
132 opts: cabc.Sequence[str],
133 dest: str | None,
134 action: str | None = None,
135 nargs: int = 1,
136 const: t.Any | None = None,
137 ):
138 self._short_opts = []
139 self._long_opts = []
140 self.prefixes: set[str] = set()
142 for opt in opts:
143 prefix, value = _split_opt(opt)
144 if not prefix:
145 raise ValueError(f"Invalid start character for option ({opt})")
146 self.prefixes.add(prefix[0])
147 if len(prefix) == 1 and len(value) == 1:
148 self._short_opts.append(opt)
149 else:
150 self._long_opts.append(opt)
151 self.prefixes.add(prefix)
153 if action is None:
154 action = "store"
156 self.dest = dest
157 self.action = action
158 self.nargs = nargs
159 self.const = const
160 self.obj = obj
162 @property
163 def takes_value(self) -> bool:
164 return self.action in ("store", "append")
166 def process(self, value: t.Any, state: _ParsingState) -> None:
167 if self.action == "store":
168 state.opts[self.dest] = value # type: ignore
169 elif self.action == "store_const":
170 state.opts[self.dest] = self.const # type: ignore
171 elif self.action == "append":
172 state.opts.setdefault(self.dest, []).append(value) # type: ignore
173 elif self.action == "append_const":
174 state.opts.setdefault(self.dest, []).append(self.const) # type: ignore
175 elif self.action == "count":
176 state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore
177 else:
178 raise ValueError(f"unknown action '{self.action}'")
179 state.order.append(self.obj)
182class _Argument:
183 def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1):
184 self.dest = dest
185 self.nargs = nargs
186 self.obj = obj
188 def process(
189 self,
190 value: str | cabc.Sequence[str | None] | None,
191 state: _ParsingState,
192 ) -> None:
193 if self.nargs > 1:
194 assert value is not None
195 holes = sum(1 for x in value if x is None)
196 if holes == len(value):
197 value = None
198 elif holes != 0:
199 raise BadArgumentUsage(
200 _("Argument {name!r} takes {nargs} values.").format(
201 name=self.dest, nargs=self.nargs
202 )
203 )
205 if self.nargs == -1 and self.obj.envvar is not None and value == ():
206 # Replace empty tuple with None so that a value from the
207 # environment may be tried.
208 value = None
210 state.opts[self.dest] = value # type: ignore
211 state.order.append(self.obj)
214class _ParsingState:
215 def __init__(self, rargs: list[str]) -> None:
216 self.opts: dict[str, t.Any] = {}
217 self.largs: list[str] = []
218 self.rargs = rargs
219 self.order: list[CoreParameter] = []
222class _OptionParser:
223 """The option parser is an internal class that is ultimately used to
224 parse options and arguments. It's modelled after optparse and brings
225 a similar but vastly simplified API. It should generally not be used
226 directly as the high level Click classes wrap it for you.
228 It's not nearly as extensible as optparse or argparse as it does not
229 implement features that are implemented on a higher level (such as
230 types or defaults).
232 :param ctx: optionally the :class:`~click.Context` where this parser
233 should go with.
235 .. deprecated:: 8.2
236 Will be removed in Click 9.0.
237 """
239 def __init__(self, ctx: Context | None = None) -> None:
240 #: The :class:`~click.Context` for this parser. This might be
241 #: `None` for some advanced use cases.
242 self.ctx = ctx
243 #: This controls how the parser deals with interspersed arguments.
244 #: If this is set to `False`, the parser will stop on the first
245 #: non-option. Click uses this to implement nested subcommands
246 #: safely.
247 self.allow_interspersed_args: bool = True
248 #: This tells the parser how to deal with unknown options. By
249 #: default it will error out (which is sensible), but there is a
250 #: second mode where it will ignore it and continue processing
251 #: after shifting all the unknown options into the resulting args.
252 self.ignore_unknown_options: bool = False
254 if ctx is not None:
255 self.allow_interspersed_args = ctx.allow_interspersed_args
256 self.ignore_unknown_options = ctx.ignore_unknown_options
258 self._short_opt: dict[str, _Option] = {}
259 self._long_opt: dict[str, _Option] = {}
260 self._opt_prefixes = {"-", "--"}
261 self._args: list[_Argument] = []
263 def add_option(
264 self,
265 obj: CoreOption,
266 opts: cabc.Sequence[str],
267 dest: str | None,
268 action: str | None = None,
269 nargs: int = 1,
270 const: t.Any | None = None,
271 ) -> None:
272 """Adds a new option named `dest` to the parser. The destination
273 is not inferred (unlike with optparse) and needs to be explicitly
274 provided. Action can be any of ``store``, ``store_const``,
275 ``append``, ``append_const`` or ``count``.
277 The `obj` can be used to identify the option in the order list
278 that is returned from the parser.
279 """
280 opts = [_normalize_opt(opt, self.ctx) for opt in opts]
281 option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const)
282 self._opt_prefixes.update(option.prefixes)
283 for opt in option._short_opts:
284 self._short_opt[opt] = option
285 for opt in option._long_opts:
286 self._long_opt[opt] = option
288 def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None:
289 """Adds a positional argument named `dest` to the parser.
291 The `obj` can be used to identify the option in the order list
292 that is returned from the parser.
293 """
294 self._args.append(_Argument(obj, dest=dest, nargs=nargs))
296 def parse_args(
297 self, args: list[str]
298 ) -> tuple[dict[str, t.Any], list[str], list[CoreParameter]]:
299 """Parses positional arguments and returns ``(values, args, order)``
300 for the parsed options and arguments as well as the leftover
301 arguments if there are any. The order is a list of objects as they
302 appear on the command line. If arguments appear multiple times they
303 will be memorized multiple times as well.
304 """
305 state = _ParsingState(args)
306 try:
307 self._process_args_for_options(state)
308 self._process_args_for_args(state)
309 except UsageError:
310 if self.ctx is None or not self.ctx.resilient_parsing:
311 raise
312 return state.opts, state.largs, state.order
314 def _process_args_for_args(self, state: _ParsingState) -> None:
315 pargs, args = _unpack_args(
316 state.largs + state.rargs, [x.nargs for x in self._args]
317 )
319 for idx, arg in enumerate(self._args):
320 arg.process(pargs[idx], state)
322 state.largs = args
323 state.rargs = []
325 def _process_args_for_options(self, state: _ParsingState) -> None:
326 while state.rargs:
327 arg = state.rargs.pop(0)
328 arglen = len(arg)
329 # Double dashes always handled explicitly regardless of what
330 # prefixes are valid.
331 if arg == "--":
332 return
333 elif arg[:1] in self._opt_prefixes and arglen > 1:
334 self._process_opts(arg, state)
335 elif self.allow_interspersed_args:
336 state.largs.append(arg)
337 else:
338 state.rargs.insert(0, arg)
339 return
341 # Say this is the original argument list:
342 # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
343 # ^
344 # (we are about to process arg(i)).
345 #
346 # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
347 # [arg0, ..., arg(i-1)] (any options and their arguments will have
348 # been removed from largs).
349 #
350 # The while loop will usually consume 1 or more arguments per pass.
351 # If it consumes 1 (eg. arg is an option that takes no arguments),
352 # then after _process_arg() is done the situation is:
353 #
354 # largs = subset of [arg0, ..., arg(i)]
355 # rargs = [arg(i+1), ..., arg(N-1)]
356 #
357 # If allow_interspersed_args is false, largs will always be
358 # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
359 # not a very interesting subset!
361 def _match_long_opt(
362 self, opt: str, explicit_value: str | None, state: _ParsingState
363 ) -> None:
364 if opt not in self._long_opt:
365 from difflib import get_close_matches
367 possibilities = get_close_matches(opt, self._long_opt)
368 raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
370 option = self._long_opt[opt]
371 if option.takes_value:
372 # At this point it's safe to modify rargs by injecting the
373 # explicit value, because no exception is raised in this
374 # branch. This means that the inserted value will be fully
375 # consumed.
376 if explicit_value is not None:
377 state.rargs.insert(0, explicit_value)
379 value = self._get_value_from_state(opt, option, state)
381 elif explicit_value is not None:
382 raise BadOptionUsage(
383 opt, _("Option {name!r} does not take a value.").format(name=opt)
384 )
386 else:
387 value = None
389 option.process(value, state)
391 def _match_short_opt(self, arg: str, state: _ParsingState) -> None:
392 stop = False
393 i = 1
394 prefix = arg[0]
395 unknown_options = []
397 for ch in arg[1:]:
398 opt = _normalize_opt(f"{prefix}{ch}", self.ctx)
399 option = self._short_opt.get(opt)
400 i += 1
402 if not option:
403 if self.ignore_unknown_options:
404 unknown_options.append(ch)
405 continue
406 raise NoSuchOption(opt, ctx=self.ctx)
407 if option.takes_value:
408 # Any characters left in arg? Pretend they're the
409 # next arg, and stop consuming characters of arg.
410 if i < len(arg):
411 state.rargs.insert(0, arg[i:])
412 stop = True
414 value = self._get_value_from_state(opt, option, state)
416 else:
417 value = None
419 option.process(value, state)
421 if stop:
422 break
424 # If we got any unknown options we recombine the string of the
425 # remaining options and re-attach the prefix, then report that
426 # to the state as new larg. This way there is basic combinatorics
427 # that can be achieved while still ignoring unknown arguments.
428 if self.ignore_unknown_options and unknown_options:
429 state.largs.append(f"{prefix}{''.join(unknown_options)}")
431 def _get_value_from_state(
432 self, option_name: str, option: _Option, state: _ParsingState
433 ) -> t.Any:
434 nargs = option.nargs
436 if len(state.rargs) < nargs:
437 if option.obj._flag_needs_value:
438 # Option allows omitting the value.
439 value = _flag_needs_value
440 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]
452 if (
453 option.obj._flag_needs_value
454 and isinstance(next_rarg, str)
455 and next_rarg[:1] in self._opt_prefixes
456 and len(next_rarg) > 1
457 ):
458 # The next arg looks like the start of an option, don't
459 # use it as the value if omitting the value is allowed.
460 value = _flag_needs_value
461 else:
462 value = state.rargs.pop(0)
463 else:
464 value = tuple(state.rargs[:nargs])
465 del state.rargs[:nargs]
467 return value
469 def _process_opts(self, arg: str, state: _ParsingState) -> None:
470 explicit_value = None
471 # Long option handling happens in two parts. The first part is
472 # supporting explicitly attached values. In any case, we will try
473 # to long match the option first.
474 if "=" in arg:
475 long_opt, explicit_value = arg.split("=", 1)
476 else:
477 long_opt = arg
478 norm_long_opt = _normalize_opt(long_opt, self.ctx)
480 # At this point we will match the (assumed) long option through
481 # the long option matching code. Note that this allows options
482 # 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 need
487 # to try with short options. However there is a special rule
488 # which says, that if we have a two character options prefix
489 # (applies to "--foo" for instance), we do not dispatch to the
490 # short option code and will instead raise the no option
491 # error.
492 if arg[:2] not in self._opt_prefixes:
493 self._match_short_opt(arg, state)
494 return
496 if not self.ignore_unknown_options:
497 raise
499 state.largs.append(arg)
502def __getattr__(name: str) -> object:
503 import warnings
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}"]
521 if name == "split_arg_string":
522 from .shell_completion import split_arg_string
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_string
532 raise AttributeError(name)