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

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). 

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 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. 

13 

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. 

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 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) 

33 

34from .exceptions import BadArgumentUsage, BadOptionUsage, NoSuchOption, UsageError 1abcdefg

35 

36if TYPE_CHECKING: 1abcdefg

37 from typer.core import TyperArgument as CoreArgument 

38 from typer.core import TyperOption as CoreOption 

39 

40 from .core import Context 

41 from .core import Parameter as CoreParameter 

42 

43V = TypeVar("V") 1abcdefg

44 

45 

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. 

52 

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

60 

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

69 

70 while nargs_spec: 1abcdefg

71 nargs = _fetch(nargs_spec) 1abcdefg

72 assert nargs is not None 1abcdefg

73 

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

78 

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

83 

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") 

88 

89 spos = len(rv) 1abcdefg

90 rv.append(None) 1abcdefg

91 

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

98 

99 return tuple(rv), list(args) 1abcdefg

100 

101 

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

109 

110 

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

116 

117 

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

131 

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

142 

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

148 

149 @property 1abcdefg

150 def takes_value(self) -> bool: 1abcdefg

151 return self.action in ("store", "append") 1abcdefg

152 

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

167 

168 

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

174 

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 ) 

189 

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

194 

195 state.opts[self.dest] = value # type: ignore 1abcdefg

196 state.order.append(self.obj) 1abcdefg

197 

198 

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

205 

206 

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. 

212 

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 """ 

217 

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

230 

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

234 

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

239 

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``. 

253 

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

264 

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. 

269 

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

274 

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

292 

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 ) 

297 

298 for idx, arg in enumerate(self._args): 1abcdefg

299 arg.process(pargs[idx], state) 1abcdefg

300 

301 state.largs = args 1abcdefg

302 state.rargs = [] 1abcdefg

303 

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

319 

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! 

339 

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

345 

346 possibilities = get_close_matches(opt, self._long_opt) 1abcdefg

347 raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) 1abcdefg

348 

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

357 

358 value = self._get_value_from_state(opt, option, state) 1abcdefg

359 

360 elif explicit_value is not None: # pragma: no cover 1abcdefg

361 raise BadOptionUsage(opt, f"Option {opt!r} does not take a value.") 

362 

363 else: 

364 value = None 1abcdefg

365 

366 option.process(value, state) 1abcdefg

367 

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

373 

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

378 

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

390 

391 value = self._get_value_from_state(opt, option, state) 1abcdefg

392 

393 else: 

394 value = None 1abcdefg

395 

396 option.process(value, state) 1abcdefg

397 

398 if stop: 1abcdefg

399 break 1abcdefg

400 

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

407 

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

412 

413 value: str | Sequence[str] 

414 

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

426 

427 return value 1abcdefg

428 

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

439 

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

455 

456 if not self.ignore_unknown_options: 1abcdefg

457 raise 1abcdefg

458 

459 state.largs.append(arg) 1abcdefg