Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/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

253 statements  

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 __future__ import annotations 

26 

27import collections.abc as cabc 

28import typing as t 

29from collections import deque 

30from gettext import gettext as _ 

31from gettext import ngettext 

32 

33from ._utils import FLAG_NEEDS_VALUE 

34from ._utils import UNSET 

35from .exceptions import BadArgumentUsage 

36from .exceptions import BadOptionUsage 

37from .exceptions import NoSuchOption 

38from .exceptions import UsageError 

39 

40if t.TYPE_CHECKING: 

41 from ._utils import T_FLAG_NEEDS_VALUE 

42 from ._utils import T_UNSET 

43 from .core import Argument as CoreArgument 

44 from .core import Context 

45 from .core import Option as CoreOption 

46 from .core import Parameter as CoreParameter 

47 

48V = t.TypeVar("V") 

49 

50 

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. 

57 

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. 

60 

61 Missing items are filled with ``UNSET``. 

62 """ 

63 args = deque(args) 

64 nargs_spec = deque(nargs_spec) 

65 rv: list[str | tuple[str | T_UNSET, ...] | T_UNSET] = [] 

66 spos: int | None = None 

67 

68 def _fetch(c: deque[V]) -> V | T_UNSET: 

69 try: 

70 if spos is None: 

71 return c.popleft() 

72 else: 

73 return c.pop() 

74 except IndexError: 

75 return UNSET 

76 

77 while nargs_spec: 

78 nargs = _fetch(nargs_spec) 

79 

80 if nargs is None: 

81 continue 

82 

83 if nargs == 1: 

84 rv.append(_fetch(args)) # type: ignore[arg-type] 

85 elif nargs > 1: 

86 x = [_fetch(args) for _ in range(nargs)] 

87 

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

92 

93 rv.append(tuple(x)) 

94 elif nargs < 0: 

95 if spos is not None: 

96 raise TypeError("Cannot have two nargs < 0") 

97 

98 spos = len(rv) 

99 rv.append(UNSET) 

100 

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

107 

108 return tuple(rv), list(args) 

109 

110 

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:] 

118 

119 

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

125 

126 

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

140 

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) 

151 

152 if action is None: 

153 action = "store" 

154 

155 self.dest = dest 

156 self.action = action 

157 self.nargs = nargs 

158 self.const = const 

159 self.obj = obj 

160 

161 @property 

162 def takes_value(self) -> bool: 

163 return self.action in ("store", "append") 

164 

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) 

179 

180 

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 

186 

187 def process( 

188 self, 

189 value: str | cabc.Sequence[str | None] | None | T_UNSET, 

190 state: _ParsingState, 

191 ) -> None: 

192 if self.nargs > 1: 

193 assert isinstance(value, cabc.Sequence) 

194 holes = sum(1 for x in value if x is UNSET) 

195 if holes == len(value): 

196 value = UNSET 

197 elif holes != 0: 

198 raise BadArgumentUsage( 

199 _("Argument {name!r} takes {nargs} values.").format( 

200 name=self.dest, nargs=self.nargs 

201 ) 

202 ) 

203 

204 # We failed to collect any argument value so we consider the argument as unset. 

205 if value == (): 

206 value = UNSET 

207 

208 state.opts[self.dest] = value # type: ignore 

209 state.order.append(self.obj) 

210 

211 

212class _ParsingState: 

213 def __init__(self, rargs: list[str]) -> None: 

214 self.opts: dict[str, t.Any] = {} 

215 self.largs: list[str] = [] 

216 self.rargs = rargs 

217 self.order: list[CoreParameter] = [] 

218 

219 

220class _OptionParser: 

221 """The option parser is an internal class that is ultimately used to 

222 parse options and arguments. It's modelled after optparse and brings 

223 a similar but vastly simplified API. It should generally not be used 

224 directly as the high level Click classes wrap it for you. 

225 

226 It's not nearly as extensible as optparse or argparse as it does not 

227 implement features that are implemented on a higher level (such as 

228 types or defaults). 

229 

230 :param ctx: optionally the :class:`~click.Context` where this parser 

231 should go with. 

232 

233 .. deprecated:: 8.2 

234 Will be removed in Click 9.0. 

235 """ 

236 

237 def __init__(self, ctx: Context | None = None) -> None: 

238 #: The :class:`~click.Context` for this parser. This might be 

239 #: `None` for some advanced use cases. 

240 self.ctx = ctx 

241 #: This controls how the parser deals with interspersed arguments. 

242 #: If this is set to `False`, the parser will stop on the first 

243 #: non-option. Click uses this to implement nested subcommands 

244 #: safely. 

245 self.allow_interspersed_args: bool = True 

246 #: This tells the parser how to deal with unknown options. By 

247 #: default it will error out (which is sensible), but there is a 

248 #: second mode where it will ignore it and continue processing 

249 #: after shifting all the unknown options into the resulting args. 

250 self.ignore_unknown_options: bool = False 

251 

252 if ctx is not None: 

253 self.allow_interspersed_args = ctx.allow_interspersed_args 

254 self.ignore_unknown_options = ctx.ignore_unknown_options 

255 

256 self._short_opt: dict[str, _Option] = {} 

257 self._long_opt: dict[str, _Option] = {} 

258 self._opt_prefixes = {"-", "--"} 

259 self._args: list[_Argument] = [] 

260 

261 def add_option( 

262 self, 

263 obj: CoreOption, 

264 opts: cabc.Sequence[str], 

265 dest: str | None, 

266 action: str | None = None, 

267 nargs: int = 1, 

268 const: t.Any | None = None, 

269 ) -> None: 

270 """Adds a new option named `dest` to the parser. The destination 

271 is not inferred (unlike with optparse) and needs to be explicitly 

272 provided. Action can be any of ``store``, ``store_const``, 

273 ``append``, ``append_const`` or ``count``. 

274 

275 The `obj` can be used to identify the option in the order list 

276 that is returned from the parser. 

277 """ 

278 opts = [_normalize_opt(opt, self.ctx) for opt in opts] 

279 option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const) 

280 self._opt_prefixes.update(option.prefixes) 

281 for opt in option._short_opts: 

282 self._short_opt[opt] = option 

283 for opt in option._long_opts: 

284 self._long_opt[opt] = option 

285 

286 def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None: 

287 """Adds a positional argument named `dest` to the parser. 

288 

289 The `obj` can be used to identify the option in the order list 

290 that is returned from the parser. 

291 """ 

292 self._args.append(_Argument(obj, dest=dest, nargs=nargs)) 

293 

294 def parse_args( 

295 self, args: list[str] 

296 ) -> tuple[dict[str, t.Any], list[str], list[CoreParameter]]: 

297 """Parses positional arguments and returns ``(values, args, order)`` 

298 for the parsed options and arguments as well as the leftover 

299 arguments if there are any. The order is a list of objects as they 

300 appear on the command line. If arguments appear multiple times they 

301 will be memorized multiple times as well. 

302 """ 

303 state = _ParsingState(args) 

304 try: 

305 self._process_args_for_options(state) 

306 self._process_args_for_args(state) 

307 except UsageError: 

308 if self.ctx is None or not self.ctx.resilient_parsing: 

309 raise 

310 return state.opts, state.largs, state.order 

311 

312 def _process_args_for_args(self, state: _ParsingState) -> None: 

313 pargs, args = _unpack_args( 

314 state.largs + state.rargs, [x.nargs for x in self._args] 

315 ) 

316 

317 for idx, arg in enumerate(self._args): 

318 arg.process(pargs[idx], state) 

319 

320 state.largs = args 

321 state.rargs = [] 

322 

323 def _process_args_for_options(self, state: _ParsingState) -> None: 

324 while state.rargs: 

325 arg = state.rargs.pop(0) 

326 arglen = len(arg) 

327 # Double dashes always handled explicitly regardless of what 

328 # prefixes are valid. 

329 if arg == "--": 

330 return 

331 elif arg[:1] in self._opt_prefixes and arglen > 1: 

332 self._process_opts(arg, state) 

333 elif self.allow_interspersed_args: 

334 state.largs.append(arg) 

335 else: 

336 state.rargs.insert(0, arg) 

337 return 

338 

339 # Say this is the original argument list: 

340 # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] 

341 # ^ 

342 # (we are about to process arg(i)). 

343 # 

344 # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of 

345 # [arg0, ..., arg(i-1)] (any options and their arguments will have 

346 # been removed from largs). 

347 # 

348 # The while loop will usually consume 1 or more arguments per pass. 

349 # If it consumes 1 (eg. arg is an option that takes no arguments), 

350 # then after _process_arg() is done the situation is: 

351 # 

352 # largs = subset of [arg0, ..., arg(i)] 

353 # rargs = [arg(i+1), ..., arg(N-1)] 

354 # 

355 # If allow_interspersed_args is false, largs will always be 

356 # *empty* -- still a subset of [arg0, ..., arg(i-1)], but 

357 # not a very interesting subset! 

358 

359 def _match_long_opt( 

360 self, opt: str, explicit_value: str | None, state: _ParsingState 

361 ) -> None: 

362 if opt not in self._long_opt: 

363 from difflib import get_close_matches 

364 

365 possibilities = get_close_matches(opt, self._long_opt) 

366 raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) 

367 

368 option = self._long_opt[opt] 

369 if option.takes_value: 

370 # At this point it's safe to modify rargs by injecting the 

371 # explicit value, because no exception is raised in this 

372 # branch. This means that the inserted value will be fully 

373 # consumed. 

374 if explicit_value is not None: 

375 state.rargs.insert(0, explicit_value) 

376 

377 value = self._get_value_from_state(opt, option, state) 

378 

379 elif explicit_value is not None: 

380 raise BadOptionUsage( 

381 opt, _("Option {name!r} does not take a value.").format(name=opt) 

382 ) 

383 

384 else: 

385 value = UNSET 

386 

387 option.process(value, state) 

388 

389 def _match_short_opt(self, arg: str, state: _ParsingState) -> None: 

390 stop = False 

391 i = 1 

392 prefix = arg[0] 

393 unknown_options = [] 

394 

395 for ch in arg[1:]: 

396 opt = _normalize_opt(f"{prefix}{ch}", self.ctx) 

397 option = self._short_opt.get(opt) 

398 i += 1 

399 

400 if not option: 

401 if self.ignore_unknown_options: 

402 unknown_options.append(ch) 

403 continue 

404 raise NoSuchOption(opt, ctx=self.ctx) 

405 if option.takes_value: 

406 # Any characters left in arg? Pretend they're the 

407 # next arg, and stop consuming characters of arg. 

408 if i < len(arg): 

409 state.rargs.insert(0, arg[i:]) 

410 stop = True 

411 

412 value = self._get_value_from_state(opt, option, state) 

413 

414 else: 

415 value = UNSET 

416 

417 option.process(value, state) 

418 

419 if stop: 

420 break 

421 

422 # If we got any unknown options we recombine the string of the 

423 # remaining options and re-attach the prefix, then report that 

424 # to the state as new larg. This way there is basic combinatorics 

425 # that can be achieved while still ignoring unknown arguments. 

426 if self.ignore_unknown_options and unknown_options: 

427 state.largs.append(f"{prefix}{''.join(unknown_options)}") 

428 

429 def _get_value_from_state( 

430 self, option_name: str, option: _Option, state: _ParsingState 

431 ) -> str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE: 

432 nargs = option.nargs 

433 

434 value: str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE 

435 

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] 

451 

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] 

466 

467 return value 

468 

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) 

479 

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 

495 

496 if not self.ignore_unknown_options: 

497 raise 

498 

499 state.largs.append(arg) 

500 

501 

502def __getattr__(name: str) -> object: 

503 import warnings 

504 

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

520 

521 if name == "split_arg_string": 

522 from .shell_completion import split_arg_string 

523 

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 

531 

532 raise AttributeError(name)