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

249 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-09 06:03 +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# This code uses parts of optparse written by Gregory P. Ward and 

21# maintained by the Python Software Foundation. 

22# Copyright 2001-2006 Gregory P. Ward 

23# Copyright 2002-2006 Python Software Foundation 

24from __future__ import annotations 

25 

26import collections.abc as cabc 

27import typing as t 

28from collections import deque 

29from gettext import gettext as _ 

30from gettext import ngettext 

31 

32from .exceptions import BadArgumentUsage 

33from .exceptions import BadOptionUsage 

34from .exceptions import NoSuchOption 

35from .exceptions import UsageError 

36 

37if t.TYPE_CHECKING: 

38 from .core import Argument as CoreArgument 

39 from .core import Context 

40 from .core import Option as CoreOption 

41 from .core import Parameter as CoreParameter 

42 

43V = t.TypeVar("V") 

44 

45# Sentinel value that indicates an option was passed as a flag without a 

46# value but is not a flag option. Option.consume_value uses this to 

47# prompt or use the flag_value. 

48_flag_needs_value = object() 

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

62 """ 

63 args = deque(args) 

64 nargs_spec = deque(nargs_spec) 

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

66 spos: int | None = None 

67 

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

69 try: 

70 if spos is None: 

71 return c.popleft() 

72 else: 

73 return c.pop() 

74 except IndexError: 

75 return None 

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

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

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, 

190 state: _ParsingState, 

191 ) -> None: 

192 if self.nargs > 1: 

193 assert value is not None 

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

195 if holes == len(value): 

196 value = None 

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 if self.nargs == -1 and self.obj.envvar is not None and value == (): 

205 # Replace empty tuple with None so that a value from the 

206 # environment may be tried. 

207 value = None 

208 

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

210 state.order.append(self.obj) 

211 

212 

213class _ParsingState: 

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

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

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

217 self.rargs = rargs 

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

219 

220 

221class _OptionParser: 

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

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

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

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

226 

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

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

229 types or defaults). 

230 

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

232 should go with. 

233 

234 .. deprecated:: 8.2 

235 Will be removed in Click 9.0. 

236 """ 

237 

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

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

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

241 self.ctx = ctx 

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

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

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

245 #: safely. 

246 self.allow_interspersed_args: bool = True 

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

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

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

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

251 self.ignore_unknown_options: bool = False 

252 

253 if ctx is not None: 

254 self.allow_interspersed_args = ctx.allow_interspersed_args 

255 self.ignore_unknown_options = ctx.ignore_unknown_options 

256 

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

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

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

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

261 

262 def add_option( 

263 self, 

264 obj: CoreOption, 

265 opts: cabc.Sequence[str], 

266 dest: str | None, 

267 action: str | None = None, 

268 nargs: int = 1, 

269 const: t.Any | None = None, 

270 ) -> None: 

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

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

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

274 ``append``, ``append_const`` or ``count``. 

275 

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

277 that is returned from the parser. 

278 """ 

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

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

281 self._opt_prefixes.update(option.prefixes) 

282 for opt in option._short_opts: 

283 self._short_opt[opt] = option 

284 for opt in option._long_opts: 

285 self._long_opt[opt] = option 

286 

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

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

289 

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

291 that is returned from the parser. 

292 """ 

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

294 

295 def parse_args( 

296 self, args: list[str] 

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

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

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

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

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

302 will be memorized multiple times as well. 

303 """ 

304 state = _ParsingState(args) 

305 try: 

306 self._process_args_for_options(state) 

307 self._process_args_for_args(state) 

308 except UsageError: 

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

310 raise 

311 return state.opts, state.largs, state.order 

312 

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

314 pargs, args = _unpack_args( 

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

316 ) 

317 

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

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

320 

321 state.largs = args 

322 state.rargs = [] 

323 

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

325 while state.rargs: 

326 arg = state.rargs.pop(0) 

327 arglen = len(arg) 

328 # Double dashes always handled explicitly regardless of what 

329 # prefixes are valid. 

330 if arg == "--": 

331 return 

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

333 self._process_opts(arg, state) 

334 elif self.allow_interspersed_args: 

335 state.largs.append(arg) 

336 else: 

337 state.rargs.insert(0, arg) 

338 return 

339 

340 # Say this is the original argument list: 

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

342 # ^ 

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

344 # 

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

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

347 # been removed from largs). 

348 # 

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

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

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

352 # 

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

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

355 # 

356 # If allow_interspersed_args is false, largs will always be 

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

358 # not a very interesting subset! 

359 

360 def _match_long_opt( 

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

362 ) -> None: 

363 if opt not in self._long_opt: 

364 from difflib import get_close_matches 

365 

366 possibilities = get_close_matches(opt, self._long_opt) 

367 raise NoSuchOption(opt, possibilities=possibilities, 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 = None 

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 = None 

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 larg. 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 ) -> t.Any: 

433 nargs = option.nargs 

434 

435 if len(state.rargs) < nargs: 

436 if option.obj._flag_needs_value: 

437 # Option allows omitting the value. 

438 value = _flag_needs_value 

439 else: 

440 raise BadOptionUsage( 

441 option_name, 

442 ngettext( 

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

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

445 nargs, 

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

447 ) 

448 elif nargs == 1: 

449 next_rarg = state.rargs[0] 

450 

451 if ( 

452 option.obj._flag_needs_value 

453 and isinstance(next_rarg, str) 

454 and next_rarg[:1] in self._opt_prefixes 

455 and len(next_rarg) > 1 

456 ): 

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

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

459 value = _flag_needs_value 

460 else: 

461 value = state.rargs.pop(0) 

462 else: 

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

464 del state.rargs[:nargs] 

465 

466 return value 

467 

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

469 explicit_value = None 

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

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

472 # to long match the option first. 

473 if "=" in arg: 

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

475 else: 

476 long_opt = arg 

477 norm_long_opt = _normalize_opt(long_opt, self.ctx) 

478 

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

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

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

482 try: 

483 self._match_long_opt(norm_long_opt, explicit_value, state) 

484 except NoSuchOption: 

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

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

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

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

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

490 # error. 

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

492 self._match_short_opt(arg, state) 

493 return 

494 

495 if not self.ignore_unknown_options: 

496 raise 

497 

498 state.largs.append(arg) 

499 

500 

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

502 import warnings 

503 

504 if name in { 

505 "OptionParser", 

506 "Argument", 

507 "Option", 

508 "split_opt", 

509 "normalize_opt", 

510 "ParsingState", 

511 }: 

512 warnings.warn( 

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

514 " The old parser is available in 'optparse'." 

515 ) 

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

517 

518 if name == "split_arg_string": 

519 from .shell_completion import split_arg_string 

520 

521 warnings.warn( 

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

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

524 DeprecationWarning, 

525 stacklevel=2, 

526 ) 

527 return split_arg_string 

528 

529 raise AttributeError(name)