Coverage for typer / _click / parser.py: 100%
213 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +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"""
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 collections import deque 1abcdefg
26from collections.abc import Sequence 1abcdefg
27from typing import ( 1abcdefg
28 TYPE_CHECKING,
29 Any,
30 TypeVar,
31 Union,
32)
34from .exceptions import BadArgumentUsage, BadOptionUsage, NoSuchOption, UsageError 1abcdefg
36if TYPE_CHECKING: 1abcdefg
37 from typer.core import TyperArgument as CoreArgument
38 from typer.core import TyperOption as CoreOption
40 from .core import Context
41 from .core import Parameter as CoreParameter
43V = TypeVar("V") 1abcdefg
46def _unpack_args( 1abcdefg
47 args: Sequence[str], nargs_spec: Sequence[int]
48) -> tuple[Sequence[str | Sequence[str | None] | None], list[str]]:
49 """Given an iterable of arguments and an iterable of nargs specifications,
50 it returns a tuple with all the unpacked arguments at the first index
51 and all remaining arguments as the second.
53 The nargs specification is the number of arguments that should be consumed
54 or `-1` to indicate that this position should eat up all the remainders.
55 """
56 args = deque(args) 1abcdefg
57 nargs_spec = deque(nargs_spec) 1abcdefg
58 rv: list[str | tuple[str | None, ...] | None] = [] 1abcdefg
59 spos: int | None = None 1abcdefg
61 def _fetch(c: deque[V]) -> V | None: 1abcdefg
62 try: 1abcdefg
63 if spos is None: 1abcdefg
64 return c.popleft() 1abcdefg
65 else:
66 return c.pop() 1abcdefg
67 except IndexError: 1abcdefg
68 return None 1abcdefg
70 while nargs_spec: 1abcdefg
71 nargs = _fetch(nargs_spec) 1abcdefg
72 assert nargs is not None 1abcdefg
74 if nargs == 1: 1abcdefg
75 rv.append(_fetch(args)) 1abcdefg
76 elif nargs > 1: 1abcdefg
77 x = [_fetch(args) for _ in range(nargs)] 1abcdefg
79 # If we're reversed, we're pulling in the arguments in reverse,
80 # so we need to turn them around.
81 if spos is not None: 1abcdefg
82 x.reverse() 1abcdefg
84 rv.append(tuple(x)) 1abcdefg
85 elif nargs < 0: 1abcdefg
86 if spos is not None: # pragma: no cover 1abcdefg
87 raise TypeError("Cannot have two nargs < 0")
89 spos = len(rv) 1abcdefg
90 rv.append(None) 1abcdefg
92 # spos is the position of the wildcard (star). If it's not `None`,
93 # we fill it with the remainder.
94 if spos is not None: 1abcdefg
95 rv[spos] = tuple(args) 1abcdefg
96 args = [] 1abcdefg
97 rv[spos + 1 :] = reversed(rv[spos + 1 :]) 1abcdefg
99 return tuple(rv), list(args) 1abcdefg
102def _split_opt(opt: str) -> tuple[str, str]: 1abcdefg
103 first = opt[:1] 1abcdefg
104 if first.isalnum(): 1abcdefg
105 return "", opt 1abcdefg
106 if opt[1:2] == first: 1abcdefg
107 return opt[:2], opt[2:] 1abcdefg
108 return first, opt[1:] 1abcdefg
111def _normalize_opt(opt: str, ctx: Union["Context", None]) -> str: 1abcdefg
112 if ctx is None or ctx.token_normalize_func is None: 1abcdefg
113 return opt 1abcdefg
114 prefix, opt = _split_opt(opt) 1abcdefg
115 return f"{prefix}{ctx.token_normalize_func(opt)}" 1abcdefg
118class _Option: 1abcdefg
119 def __init__( 1abcdefg
120 self,
121 obj: "CoreOption",
122 opts: Sequence[str],
123 dest: str | None,
124 action: str = "store",
125 nargs: int = 1,
126 const: Any | None = None,
127 ):
128 self._short_opts = [] 1abcdefg
129 self._long_opts = [] 1abcdefg
130 self.prefixes: set[str] = set() 1abcdefg
132 for opt in opts: 1abcdefg
133 prefix, value = _split_opt(opt) 1abcdefg
134 if not prefix: # pragma: no cover 1abcdefg
135 raise ValueError(f"Invalid start character for option ({opt})") 1abcdefg
136 self.prefixes.add(prefix[0]) 1abcdefg
137 if len(prefix) == 1 and len(value) == 1: 1abcdefg
138 self._short_opts.append(opt) 1abcdefg
139 else:
140 self._long_opts.append(opt) 1abcdefg
141 self.prefixes.add(prefix) 1abcdefg
143 self.dest = dest 1abcdefg
144 self.action = action 1abcdefg
145 self.nargs = nargs 1abcdefg
146 self.const = const 1abcdefg
147 self.obj = obj 1abcdefg
149 @property 1abcdefg
150 def takes_value(self) -> bool: 1abcdefg
151 return self.action in ("store", "append") 1abcdefg
153 def process(self, value: Any, state: "_ParsingState") -> None: 1abcdefg
154 if self.action == "store": 1abcdefg
155 state.opts[self.dest] = value # type: ignore 1abcdefg
156 elif self.action == "store_const": 1abcdefg
157 state.opts[self.dest] = self.const # type: ignore 1abcdefg
158 elif self.action == "append": 1abcdefg
159 state.opts.setdefault(self.dest, []).append(value) # type: ignore 1abcdefg
160 elif self.action == "append_const": 1abcdefg
161 state.opts.setdefault(self.dest, []).append(self.const) # type: ignore 1abcdefg
162 elif self.action == "count": 1abcdefg
163 state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore 1abcdefg
164 else: # pragma: no cover
165 raise ValueError(f"unknown action '{self.action}'")
166 state.order.append(self.obj) 1abcdefg
169class _Argument: 1abcdefg
170 def __init__(self, obj: "CoreArgument", dest: str | None, nargs: int = 1): 1abcdefg
171 self.dest = dest 1abcdefg
172 self.nargs = nargs 1abcdefg
173 self.obj = obj 1abcdefg
175 def process( 1abcdefg
176 self,
177 value: str | Sequence[str | None] | None,
178 state: "_ParsingState",
179 ) -> None:
180 if self.nargs > 1: 1abcdefg
181 assert value is not None 1abcdefg
182 holes = sum(1 for x in value if x is None) 1abcdefg
183 if holes == len(value): 1abcdefg
184 value = None 1abcdefg
185 elif holes != 0: 1abcdefg
186 raise BadArgumentUsage( 1abcdefg
187 f"Argument {self.dest!r} takes {self.nargs} values."
188 )
190 if self.nargs == -1 and value == (): 1abcdefg
191 # Replace empty tuple with None so regular default resolution
192 # (env var, default map, and parameter default) can be tried.
193 value = None 1abcdefg
195 state.opts[self.dest] = value # type: ignore 1abcdefg
196 state.order.append(self.obj) 1abcdefg
199class _ParsingState: 1abcdefg
200 def __init__(self, rargs: list[str]) -> None: 1abcdefg
201 self.opts: dict[str, Any] = {} 1abcdefg
202 self.largs: list[str] = [] 1abcdefg
203 self.rargs = rargs 1abcdefg
204 self.order: list[CoreParameter] = [] 1abcdefg
207class _OptionParser: 1abcdefg
208 """The option parser is an internal class that is ultimately used to
209 parse options and arguments. It's modelled after optparse and brings
210 a similar but vastly simplified API. It should generally not be used
211 directly as the high level Click classes wrap it for you.
213 It's not nearly as extensible as optparse or argparse as it does not
214 implement features that are implemented on a higher level (such as
215 types or defaults).
216 """
218 def __init__(self, ctx: Union["Context", None] = None) -> None: 1abcdefg
219 self.ctx = ctx 1abcdefg
220 # This controls how the parser deals with interspersed arguments.
221 # If this is set to `False`, the parser will stop on the first
222 # non-option. Click uses this to implement nested subcommands
223 # safely.
224 self.allow_interspersed_args: bool = True 1abcdefg
225 # This tells the parser how to deal with unknown options. By
226 # default it will error out (which is sensible), but there is a
227 # second mode where it will ignore it and continue processing
228 # after shifting all the unknown options into the resulting args.
229 self.ignore_unknown_options: bool = False 1abcdefg
231 if ctx is not None: 1abcdefg
232 self.allow_interspersed_args = ctx.allow_interspersed_args 1abcdefg
233 self.ignore_unknown_options = ctx.ignore_unknown_options 1abcdefg
235 self._short_opt: dict[str, _Option] = {} 1abcdefg
236 self._long_opt: dict[str, _Option] = {} 1abcdefg
237 self._opt_prefixes = {"-", "--"} 1abcdefg
238 self._args: list[_Argument] = [] 1abcdefg
240 def add_option( 1abcdefg
241 self,
242 obj: "CoreOption",
243 opts: Sequence[str],
244 dest: str | None,
245 action: str = "store",
246 nargs: int = 1,
247 const: Any | None = None,
248 ) -> None:
249 """Adds a new option named `dest` to the parser. The destination
250 is not inferred (unlike with optparse) and needs to be explicitly
251 provided. Action can be any of ``store``, ``store_const``,
252 ``append``, ``append_const`` or ``count``.
254 The `obj` can be used to identify the option in the order list
255 that is returned from the parser.
256 """
257 opts = [_normalize_opt(opt, self.ctx) for opt in opts] 1abcdefg
258 option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const) 1abcdefg
259 self._opt_prefixes.update(option.prefixes) 1abcdefg
260 for opt in option._short_opts: 1abcdefg
261 self._short_opt[opt] = option 1abcdefg
262 for opt in option._long_opts: 1abcdefg
263 self._long_opt[opt] = option 1abcdefg
265 def add_argument( 1abcdefg
266 self, obj: "CoreArgument", dest: str | None, nargs: int = 1
267 ) -> None:
268 """Adds a positional argument named `dest` to the parser.
270 The `obj` can be used to identify the option in the order list
271 that is returned from the parser.
272 """
273 self._args.append(_Argument(obj, dest=dest, nargs=nargs)) 1abcdefg
275 def parse_args( 1abcdefg
276 self, args: list[str]
277 ) -> tuple[dict[str, Any], list[str], list["CoreParameter"]]:
278 """Parses positional arguments and returns ``(values, args, order)``
279 for the parsed options and arguments as well as the leftover
280 arguments if there are any. The order is a list of objects as they
281 appear on the command line. If arguments appear multiple times they
282 will be memorized multiple times as well.
283 """
284 state = _ParsingState(args) 1abcdefg
285 try: 1abcdefg
286 self._process_args_for_options(state) 1abcdefg
287 self._process_args_for_args(state) 1abcdefg
288 except UsageError: 1abcdefg
289 if self.ctx is None or not self.ctx.resilient_parsing: 1abcdefg
290 raise 1abcdefg
291 return state.opts, state.largs, state.order 1abcdefg
293 def _process_args_for_args(self, state: _ParsingState) -> None: 1abcdefg
294 pargs, args = _unpack_args( 1abcdefg
295 state.largs + state.rargs, [x.nargs for x in self._args]
296 )
298 for idx, arg in enumerate(self._args): 1abcdefg
299 arg.process(pargs[idx], state) 1abcdefg
301 state.largs = args 1abcdefg
302 state.rargs = [] 1abcdefg
304 def _process_args_for_options(self, state: _ParsingState) -> None: 1abcdefg
305 while state.rargs: 1abcdefg
306 arg = state.rargs.pop(0) 1abcdefg
307 arglen = len(arg) 1abcdefg
308 # Double dashes always handled explicitly regardless of what
309 # prefixes are valid.
310 if arg == "--": 1abcdefg
311 return 1abcdefg
312 elif arg[:1] in self._opt_prefixes and arglen > 1: 1abcdefg
313 self._process_opts(arg, state) 1abcdefg
314 elif self.allow_interspersed_args: 1abcdefg
315 state.largs.append(arg) 1abcdefg
316 else:
317 state.rargs.insert(0, arg) 1abcdefg
318 return 1abcdefg
320 # Say this is the original argument list:
321 # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
322 # ^
323 # (we are about to process arg(i)).
324 #
325 # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
326 # [arg0, ..., arg(i-1)] (any options and their arguments will have
327 # been removed from largs).
328 #
329 # The while loop will usually consume 1 or more arguments per pass.
330 # If it consumes 1 (eg. arg is an option that takes no arguments),
331 # then after _process_arg() is done the situation is:
332 #
333 # largs = subset of [arg0, ..., arg(i)]
334 # rargs = [arg(i+1), ..., arg(N-1)]
335 #
336 # If allow_interspersed_args is false, largs will always be
337 # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
338 # not a very interesting subset!
340 def _match_long_opt( 1abcdefg
341 self, opt: str, explicit_value: str | None, state: _ParsingState
342 ) -> None:
343 if opt not in self._long_opt: 1abcdefg
344 from difflib import get_close_matches 1abcdefg
346 possibilities = get_close_matches(opt, self._long_opt) 1abcdefg
347 raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) 1abcdefg
349 option = self._long_opt[opt] 1abcdefg
350 if option.takes_value: 1abcdefg
351 # At this point it's safe to modify rargs by injecting the
352 # explicit value, because no exception is raised in this
353 # branch. This means that the inserted value will be fully
354 # consumed.
355 if explicit_value is not None: 1abcdefg
356 state.rargs.insert(0, explicit_value) 1abcdefg
358 value = self._get_value_from_state(opt, option, state) 1abcdefg
360 elif explicit_value is not None: # pragma: no cover 1abcdefg
361 raise BadOptionUsage(opt, f"Option {opt!r} does not take a value.")
363 else:
364 value = None 1abcdefg
366 option.process(value, state) 1abcdefg
368 def _match_short_opt(self, arg: str, state: _ParsingState) -> None: 1abcdefg
369 stop = False 1abcdefg
370 i = 1 1abcdefg
371 prefix = arg[0] 1abcdefg
372 unknown_options = [] 1abcdefg
374 for ch in arg[1:]: 1abcdefg
375 opt = _normalize_opt(f"{prefix}{ch}", self.ctx) 1abcdefg
376 option = self._short_opt.get(opt) 1abcdefg
377 i += 1 1abcdefg
379 if not option: 1abcdefg
380 if self.ignore_unknown_options: 1abcdefg
381 unknown_options.append(ch) 1abcdefg
382 continue 1abcdefg
383 raise NoSuchOption(opt, ctx=self.ctx) 1abcdefg
384 if option.takes_value: 1abcdefg
385 # Any characters left in arg? Pretend they're the
386 # next arg, and stop consuming characters of arg.
387 if i < len(arg): 1abcdefg
388 state.rargs.insert(0, arg[i:]) 1abcdefg
389 stop = True 1abcdefg
391 value = self._get_value_from_state(opt, option, state) 1abcdefg
393 else:
394 value = None 1abcdefg
396 option.process(value, state) 1abcdefg
398 if stop: 1abcdefg
399 break 1abcdefg
401 # If we got any unknown options we recombine the string of the
402 # remaining options and re-attach the prefix, then report that
403 # to the state as new 'largs'. This way there is basic combinatorics
404 # that can be achieved while still ignoring unknown arguments.
405 if self.ignore_unknown_options and unknown_options: 1abcdefg
406 state.largs.append(f"{prefix}{''.join(unknown_options)}") 1abcdefg
408 def _get_value_from_state( 1abcdefg
409 self, option_name: str, option: _Option, state: _ParsingState
410 ) -> str | Sequence[str]:
411 nargs = option.nargs 1abcdefg
413 value: str | Sequence[str]
415 if len(state.rargs) < nargs: 1abcdefg
416 msg = "an argument." if nargs == 1 else f"{nargs} arguments." 1abcdefg
417 raise BadOptionUsage( 1abcdefg
418 option_name,
419 f"Option {option_name!r} requires {msg}",
420 )
421 elif nargs == 1: 1abcdefg
422 value = state.rargs.pop(0) 1abcdefg
423 else:
424 value = tuple(state.rargs[:nargs]) 1abcdefg
425 del state.rargs[:nargs] 1abcdefg
427 return value 1abcdefg
429 def _process_opts(self, arg: str, state: _ParsingState) -> None: 1abcdefg
430 explicit_value = None 1abcdefg
431 # Long option handling happens in two parts. The first part is
432 # supporting explicitly attached values. In any case, we will try
433 # to long match the option first.
434 if "=" in arg: 1abcdefg
435 long_opt, explicit_value = arg.split("=", 1) 1abcdefg
436 else:
437 long_opt = arg 1abcdefg
438 norm_long_opt = _normalize_opt(long_opt, self.ctx) 1abcdefg
440 # At this point we will match the (assumed) long option through
441 # the long option matching code. Note that this allows options
442 # like "-foo" to be matched as long options.
443 try: 1abcdefg
444 self._match_long_opt(norm_long_opt, explicit_value, state) 1abcdefg
445 except NoSuchOption: 1abcdefg
446 # At this point the long option matching failed, and we need
447 # to try with short options. However there is a special rule
448 # which says, that if we have a two character options prefix
449 # (applies to "--foo" for instance), we do not dispatch to the
450 # short option code and will instead raise the no option
451 # error.
452 if arg[:2] not in self._opt_prefixes: 1abcdefg
453 self._match_short_opt(arg, state) 1abcdefg
454 return 1abcdefg
456 if not self.ignore_unknown_options: 1abcdefg
457 raise 1abcdefg
459 state.largs.append(arg) 1abcdefg