Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/click/parser.py: 63%

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

251 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 | T_UNSET] | T_UNSET], 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[str]) -> str | 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 if spos is None: 

79 nargs = nargs_spec.popleft() 

80 else: 

81 nargs = nargs_spec.pop() 

82 

83 if nargs == 1: 

84 rv.append(_fetch(args)) 

85 elif nargs > 1: 

86 x: list[str | T_UNSET] = [_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( 

145 _("Invalid start character for option ({option})").format( 

146 option=opt 

147 ) 

148 ) 

149 self.prefixes.add(prefix[0]) 

150 if len(prefix) == 1 and len(value) == 1: 

151 self._short_opts.append(opt) 

152 else: 

153 self._long_opts.append(opt) 

154 self.prefixes.add(prefix) 

155 

156 if action is None: 

157 action = "store" 

158 

159 self.dest = dest 

160 self.action = action 

161 self.nargs = nargs 

162 self.const = const 

163 self.obj = obj 

164 

165 @property 

166 def takes_value(self) -> bool: 

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

168 

169 def process(self, value: t.Any, state: _ParsingState) -> None: 

170 if self.action == "store": 

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

172 elif self.action == "store_const": 

173 state.opts[self.dest] = self.const # type: ignore 

174 elif self.action == "append": 

175 state.opts.setdefault(self.dest, []).append(value) # type: ignore 

176 elif self.action == "append_const": 

177 state.opts.setdefault(self.dest, []).append(self.const) # type: ignore 

178 elif self.action == "count": 

179 state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore 

180 else: 

181 raise ValueError(f"unknown action '{self.action}'") 

182 state.order.append(self.obj) 

183 

184 

185class _Argument: 

186 def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1): 

187 self.dest = dest 

188 self.nargs = nargs 

189 self.obj = obj 

190 

191 def process( 

192 self, 

193 value: str | cabc.Sequence[str | T_UNSET] | T_UNSET, 

194 state: _ParsingState, 

195 ) -> None: 

196 if self.nargs > 1: 

197 assert isinstance(value, cabc.Sequence) 

198 holes = sum(x is UNSET for x in value) 

199 if holes == len(value): 

200 value = UNSET 

201 elif holes != 0: 

202 raise BadArgumentUsage( 

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

204 name=self.dest, nargs=self.nargs 

205 ) 

206 ) 

207 

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

209 if value == (): 

210 value = UNSET 

211 

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

213 state.order.append(self.obj) 

214 

215 

216class _ParsingState: 

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

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

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

220 self.rargs = rargs 

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

222 

223 

224class _OptionParser: 

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

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

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

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

229 

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

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

232 types or defaults). 

233 

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

235 should go with. 

236 

237 .. deprecated:: 8.2 

238 Will be removed in Click 9.0. 

239 """ 

240 

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

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

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

244 self.ctx = ctx 

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

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

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

248 #: safely. 

249 self.allow_interspersed_args: bool = True 

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

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

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

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

254 self.ignore_unknown_options: bool = False 

255 

256 if ctx is not None: 

257 self.allow_interspersed_args = ctx.allow_interspersed_args 

258 self.ignore_unknown_options = ctx.ignore_unknown_options 

259 

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

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

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

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

264 

265 def add_option( 

266 self, 

267 obj: CoreOption, 

268 opts: cabc.Sequence[str], 

269 dest: str | None, 

270 action: str | None = None, 

271 nargs: int = 1, 

272 const: t.Any | None = None, 

273 ) -> None: 

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

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

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

277 ``append``, ``append_const`` or ``count``. 

278 

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

280 that is returned from the parser. 

281 """ 

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

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

284 self._opt_prefixes.update(option.prefixes) 

285 for opt in option._short_opts: 

286 self._short_opt[opt] = option 

287 for opt in option._long_opts: 

288 self._long_opt[opt] = option 

289 

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

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

292 

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

294 that is returned from the parser. 

295 """ 

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

297 

298 def parse_args( 

299 self, args: list[str] 

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

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

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

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

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

305 will be memorized multiple times as well. 

306 """ 

307 state = _ParsingState(args) 

308 try: 

309 self._process_args_for_options(state) 

310 self._process_args_for_args(state) 

311 except UsageError: 

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

313 raise 

314 return state.opts, state.largs, state.order 

315 

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

317 pargs, args = _unpack_args( 

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

319 ) 

320 

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

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

323 

324 state.largs = args 

325 state.rargs = [] 

326 

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

328 while state.rargs: 

329 arg = state.rargs.pop(0) 

330 arglen = len(arg) 

331 # Double dashes always handled explicitly regardless of what 

332 # prefixes are valid. 

333 if arg == "--": 

334 return 

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

336 self._process_opts(arg, state) 

337 elif self.allow_interspersed_args: 

338 state.largs.append(arg) 

339 else: 

340 state.rargs.insert(0, arg) 

341 return 

342 

343 # Say this is the original argument list: 

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

345 # ^ 

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

347 # 

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

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

350 # been removed from largs). 

351 # 

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

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

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

355 # 

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

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

358 # 

359 # If allow_interspersed_args is false, largs will always be 

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

361 # not a very interesting subset! 

362 

363 def _match_long_opt( 

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

365 ) -> None: 

366 if opt not in self._long_opt: 

367 raise NoSuchOption(opt, possibilities=self._long_opt, ctx=self.ctx) 

368 

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) 

377 

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

379 

380 elif explicit_value is not None: 

381 raise BadOptionUsage( 

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

383 ) 

384 

385 else: 

386 value = UNSET 

387 

388 option.process(value, state) 

389 

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

391 stop = False 

392 i = 1 

393 prefix = arg[0] 

394 unknown_options = [] 

395 

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 

400 

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 

412 

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

414 

415 else: 

416 value = UNSET 

417 

418 option.process(value, state) 

419 

420 if stop: 

421 break 

422 

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

429 

430 def _get_value_from_state( 

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

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

433 nargs = option.nargs 

434 

435 value: str | cabc.Sequence[str] | T_UNSET | T_FLAG_NEEDS_VALUE 

436 

437 if len(state.rargs) < nargs: 

438 if option.obj._flag_needs_value: 

439 # Option allows omitting the value. 

440 value = FLAG_NEEDS_VALUE 

441 else: 

442 raise BadOptionUsage( 

443 option_name, 

444 ngettext( 

445 "Option {name!r} requires an argument.", 

446 "Option {name!r} requires {nargs} arguments.", 

447 nargs, 

448 ).format(name=option_name, nargs=nargs), 

449 ) 

450 elif nargs == 1: 

451 next_rarg = state.rargs[0] 

452 

453 if ( 

454 option.obj._flag_needs_value 

455 and isinstance(next_rarg, str) 

456 and next_rarg[:1] in self._opt_prefixes 

457 and len(next_rarg) > 1 

458 ): 

459 # The next arg looks like the start of an option, don't 

460 # use it as the value if omitting the value is allowed. 

461 value = FLAG_NEEDS_VALUE 

462 else: 

463 value = state.rargs.pop(0) 

464 else: 

465 value = tuple(state.rargs[:nargs]) 

466 del state.rargs[:nargs] 

467 

468 return value 

469 

470 def _process_opts(self, arg: str, state: _ParsingState) -> None: 

471 explicit_value = None 

472 # Long option handling happens in two parts. The first part is 

473 # supporting explicitly attached values. In any case, we will try 

474 # to long match the option first. 

475 if "=" in arg: 

476 long_opt, explicit_value = arg.split("=", 1) 

477 else: 

478 long_opt = arg 

479 norm_long_opt = _normalize_opt(long_opt, self.ctx) 

480 

481 # At this point we will match the (assumed) long option through 

482 # the long option matching code. Note that this allows options 

483 # like "-foo" to be matched as long options. 

484 try: 

485 self._match_long_opt(norm_long_opt, explicit_value, state) 

486 except NoSuchOption: 

487 # At this point the long option matching failed, and we need 

488 # to try with short options. However there is a special rule 

489 # which says, that if we have a two character options prefix 

490 # (applies to "--foo" for instance), we do not dispatch to the 

491 # short option code and will instead raise the no option 

492 # error. 

493 if arg[:2] not in self._opt_prefixes: 

494 self._match_short_opt(arg, state) 

495 return 

496 

497 if not self.ignore_unknown_options: 

498 raise 

499 

500 state.largs.append(arg) 

501 

502 

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

504 import warnings 

505 

506 if name in { 

507 "OptionParser", 

508 "Argument", 

509 "Option", 

510 "split_opt", 

511 "normalize_opt", 

512 "ParsingState", 

513 }: 

514 warnings.warn( 

515 f"'parser.{name}' is deprecated and will be removed in Click 9.0." 

516 " The old parser is available in 'optparse'.", 

517 DeprecationWarning, 

518 stacklevel=2, 

519 ) 

520 return globals()[f"_{name}"] 

521 

522 if name == "split_arg_string": 

523 from .shell_completion import split_arg_string 

524 

525 warnings.warn( 

526 "Importing 'parser.split_arg_string' is deprecated, it will only be" 

527 " available in 'shell_completion' in Click 9.0.", 

528 DeprecationWarning, 

529 stacklevel=2, 

530 ) 

531 return split_arg_string 

532 

533 raise AttributeError(name)