Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/click/parser.py: 12%

250 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

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 

24import typing as t 

25from collections import deque 

26from gettext import gettext as _ 

27from gettext import ngettext 

28 

29from .exceptions import BadArgumentUsage 

30from .exceptions import BadOptionUsage 

31from .exceptions import NoSuchOption 

32from .exceptions import UsageError 

33 

34if t.TYPE_CHECKING: 34 ↛ 35line 34 didn't jump to line 35, because the condition on line 34 was never true

35 import typing_extensions as te 

36 from .core import Argument as CoreArgument 

37 from .core import Context 

38 from .core import Option as CoreOption 

39 from .core import Parameter as CoreParameter 

40 

41V = t.TypeVar("V") 

42 

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

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

45# prompt or use the flag_value. 

46_flag_needs_value = object() 

47 

48 

49def _unpack_args( 

50 args: t.Sequence[str], nargs_spec: t.Sequence[int] 

51) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]: 

52 """Given an iterable of arguments and an iterable of nargs specifications, 

53 it returns a tuple with all the unpacked arguments at the first index 

54 and all remaining arguments as the second. 

55 

56 The nargs specification is the number of arguments that should be consumed 

57 or `-1` to indicate that this position should eat up all the remainders. 

58 

59 Missing items are filled with `None`. 

60 """ 

61 args = deque(args) 

62 nargs_spec = deque(nargs_spec) 

63 rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = [] 

64 spos: t.Optional[int] = None 

65 

66 def _fetch(c: "te.Deque[V]") -> t.Optional[V]: 

67 try: 

68 if spos is None: 

69 return c.popleft() 

70 else: 

71 return c.pop() 

72 except IndexError: 

73 return None 

74 

75 while nargs_spec: 

76 nargs = _fetch(nargs_spec) 

77 

78 if nargs is None: 

79 continue 

80 

81 if nargs == 1: 

82 rv.append(_fetch(args)) 

83 elif nargs > 1: 

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

85 

86 # If we're reversed, we're pulling in the arguments in reverse, 

87 # so we need to turn them around. 

88 if spos is not None: 

89 x.reverse() 

90 

91 rv.append(tuple(x)) 

92 elif nargs < 0: 

93 if spos is not None: 

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

95 

96 spos = len(rv) 

97 rv.append(None) 

98 

99 # spos is the position of the wildcard (star). If it's not `None`, 

100 # we fill it with the remainder. 

101 if spos is not None: 

102 rv[spos] = tuple(args) 

103 args = [] 

104 rv[spos + 1 :] = reversed(rv[spos + 1 :]) 

105 

106 return tuple(rv), list(args) 

107 

108 

109def split_opt(opt: str) -> t.Tuple[str, str]: 

110 first = opt[:1] 

111 if first.isalnum(): 

112 return "", opt 

113 if opt[1:2] == first: 

114 return opt[:2], opt[2:] 

115 return first, opt[1:] 

116 

117 

118def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str: 

119 if ctx is None or ctx.token_normalize_func is None: 

120 return opt 

121 prefix, opt = split_opt(opt) 

122 return f"{prefix}{ctx.token_normalize_func(opt)}" 

123 

124 

125def split_arg_string(string: str) -> t.List[str]: 

126 """Split an argument string as with :func:`shlex.split`, but don't 

127 fail if the string is incomplete. Ignores a missing closing quote or 

128 incomplete escape sequence and uses the partial token as-is. 

129 

130 .. code-block:: python 

131 

132 split_arg_string("example 'my file") 

133 ["example", "my file"] 

134 

135 split_arg_string("example my\\") 

136 ["example", "my"] 

137 

138 :param string: String to split. 

139 """ 

140 import shlex 

141 

142 lex = shlex.shlex(string, posix=True) 

143 lex.whitespace_split = True 

144 lex.commenters = "" 

145 out = [] 

146 

147 try: 

148 for token in lex: 

149 out.append(token) 

150 except ValueError: 

151 # Raised when end-of-string is reached in an invalid state. Use 

152 # the partial token as-is. The quote or escape character is in 

153 # lex.state, not lex.token. 

154 out.append(lex.token) 

155 

156 return out 

157 

158 

159class Option: 

160 def __init__( 

161 self, 

162 obj: "CoreOption", 

163 opts: t.Sequence[str], 

164 dest: t.Optional[str], 

165 action: t.Optional[str] = None, 

166 nargs: int = 1, 

167 const: t.Optional[t.Any] = None, 

168 ): 

169 self._short_opts = [] 

170 self._long_opts = [] 

171 self.prefixes = set() 

172 

173 for opt in opts: 

174 prefix, value = split_opt(opt) 

175 if not prefix: 

176 raise ValueError(f"Invalid start character for option ({opt})") 

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

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

179 self._short_opts.append(opt) 

180 else: 

181 self._long_opts.append(opt) 

182 self.prefixes.add(prefix) 

183 

184 if action is None: 

185 action = "store" 

186 

187 self.dest = dest 

188 self.action = action 

189 self.nargs = nargs 

190 self.const = const 

191 self.obj = obj 

192 

193 @property 

194 def takes_value(self) -> bool: 

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

196 

197 def process(self, value: str, state: "ParsingState") -> None: 

198 if self.action == "store": 

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

200 elif self.action == "store_const": 

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

202 elif self.action == "append": 

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

204 elif self.action == "append_const": 

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

206 elif self.action == "count": 

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

208 else: 

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

210 state.order.append(self.obj) 

211 

212 

213class Argument: 

214 def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1): 

215 self.dest = dest 

216 self.nargs = nargs 

217 self.obj = obj 

218 

219 def process( 

220 self, 

221 value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]], 

222 state: "ParsingState", 

223 ) -> None: 

224 if self.nargs > 1: 

225 assert value is not None 

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

227 if holes == len(value): 

228 value = None 

229 elif holes != 0: 

230 raise BadArgumentUsage( 

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

232 name=self.dest, nargs=self.nargs 

233 ) 

234 ) 

235 

236 if self.nargs == -1 and self.obj.envvar is not None and value == (): 

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

238 # environment may be tried. 

239 value = None 

240 

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

242 state.order.append(self.obj) 

243 

244 

245class ParsingState: 

246 def __init__(self, rargs: t.List[str]) -> None: 

247 self.opts: t.Dict[str, t.Any] = {} 

248 self.largs: t.List[str] = [] 

249 self.rargs = rargs 

250 self.order: t.List["CoreParameter"] = [] 

251 

252 

253class OptionParser: 

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

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

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

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

258 

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

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

261 types or defaults). 

262 

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

264 should go with. 

265 """ 

266 

267 def __init__(self, ctx: t.Optional["Context"] = None) -> None: 

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

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

270 self.ctx = ctx 

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

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

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

274 #: safely. 

275 self.allow_interspersed_args = True 

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

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

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

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

280 self.ignore_unknown_options = False 

281 

282 if ctx is not None: 

283 self.allow_interspersed_args = ctx.allow_interspersed_args 

284 self.ignore_unknown_options = ctx.ignore_unknown_options 

285 

286 self._short_opt: t.Dict[str, Option] = {} 

287 self._long_opt: t.Dict[str, Option] = {} 

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

289 self._args: t.List[Argument] = [] 

290 

291 def add_option( 

292 self, 

293 obj: "CoreOption", 

294 opts: t.Sequence[str], 

295 dest: t.Optional[str], 

296 action: t.Optional[str] = None, 

297 nargs: int = 1, 

298 const: t.Optional[t.Any] = None, 

299 ) -> None: 

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

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

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

303 ``append``, ``append_const`` or ``count``. 

304 

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

306 that is returned from the parser. 

307 """ 

308 opts = [normalize_opt(opt, self.ctx) for opt in opts] 

309 option = Option(obj, opts, dest, action=action, nargs=nargs, const=const) 

310 self._opt_prefixes.update(option.prefixes) 

311 for opt in option._short_opts: 

312 self._short_opt[opt] = option 

313 for opt in option._long_opts: 

314 self._long_opt[opt] = option 

315 

316 def add_argument( 

317 self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1 

318 ) -> None: 

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

320 

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

322 that is returned from the parser. 

323 """ 

324 self._args.append(Argument(obj, dest=dest, nargs=nargs)) 

325 

326 def parse_args( 

327 self, args: t.List[str] 

328 ) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]: 

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

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

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

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

333 will be memorized multiple times as well. 

334 """ 

335 state = ParsingState(args) 

336 try: 

337 self._process_args_for_options(state) 

338 self._process_args_for_args(state) 

339 except UsageError: 

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

341 raise 

342 return state.opts, state.largs, state.order 

343 

344 def _process_args_for_args(self, state: ParsingState) -> None: 

345 pargs, args = _unpack_args( 

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

347 ) 

348 

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

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

351 

352 state.largs = args 

353 state.rargs = [] 

354 

355 def _process_args_for_options(self, state: ParsingState) -> None: 

356 while state.rargs: 

357 arg = state.rargs.pop(0) 

358 arglen = len(arg) 

359 # Double dashes always handled explicitly regardless of what 

360 # prefixes are valid. 

361 if arg == "--": 

362 return 

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

364 self._process_opts(arg, state) 

365 elif self.allow_interspersed_args: 

366 state.largs.append(arg) 

367 else: 

368 state.rargs.insert(0, arg) 

369 return 

370 

371 # Say this is the original argument list: 

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

373 # ^ 

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

375 # 

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

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

378 # been removed from largs). 

379 # 

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

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

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

383 # 

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

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

386 # 

387 # If allow_interspersed_args is false, largs will always be 

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

389 # not a very interesting subset! 

390 

391 def _match_long_opt( 

392 self, opt: str, explicit_value: t.Optional[str], state: ParsingState 

393 ) -> None: 

394 if opt not in self._long_opt: 

395 from difflib import get_close_matches 

396 

397 possibilities = get_close_matches(opt, self._long_opt) 

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

399 

400 option = self._long_opt[opt] 

401 if option.takes_value: 

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

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

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

405 # consumed. 

406 if explicit_value is not None: 

407 state.rargs.insert(0, explicit_value) 

408 

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

410 

411 elif explicit_value is not None: 

412 raise BadOptionUsage( 

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

414 ) 

415 

416 else: 

417 value = None 

418 

419 option.process(value, state) 

420 

421 def _match_short_opt(self, arg: str, state: ParsingState) -> None: 

422 stop = False 

423 i = 1 

424 prefix = arg[0] 

425 unknown_options = [] 

426 

427 for ch in arg[1:]: 

428 opt = normalize_opt(f"{prefix}{ch}", self.ctx) 

429 option = self._short_opt.get(opt) 

430 i += 1 

431 

432 if not option: 

433 if self.ignore_unknown_options: 

434 unknown_options.append(ch) 

435 continue 

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

437 if option.takes_value: 

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

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

440 if i < len(arg): 

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

442 stop = True 

443 

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

445 

446 else: 

447 value = None 

448 

449 option.process(value, state) 

450 

451 if stop: 

452 break 

453 

454 # If we got any unknown options we re-combinate the string of the 

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

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

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

458 if self.ignore_unknown_options and unknown_options: 

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

460 

461 def _get_value_from_state( 

462 self, option_name: str, option: Option, state: ParsingState 

463 ) -> t.Any: 

464 nargs = option.nargs 

465 

466 if len(state.rargs) < nargs: 

467 if option.obj._flag_needs_value: 

468 # Option allows omitting the value. 

469 value = _flag_needs_value 

470 else: 

471 raise BadOptionUsage( 

472 option_name, 

473 ngettext( 

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

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

476 nargs, 

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

478 ) 

479 elif nargs == 1: 

480 next_rarg = state.rargs[0] 

481 

482 if ( 

483 option.obj._flag_needs_value 

484 and isinstance(next_rarg, str) 

485 and next_rarg[:1] in self._opt_prefixes 

486 and len(next_rarg) > 1 

487 ): 

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

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

490 value = _flag_needs_value 

491 else: 

492 value = state.rargs.pop(0) 

493 else: 

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

495 del state.rargs[:nargs] 

496 

497 return value 

498 

499 def _process_opts(self, arg: str, state: ParsingState) -> None: 

500 explicit_value = None 

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

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

503 # to long match the option first. 

504 if "=" in arg: 

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

506 else: 

507 long_opt = arg 

508 norm_long_opt = normalize_opt(long_opt, self.ctx) 

509 

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

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

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

513 try: 

514 self._match_long_opt(norm_long_opt, explicit_value, state) 

515 except NoSuchOption: 

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

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

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

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

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

521 # error. 

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

523 self._match_short_opt(arg, state) 

524 return 

525 

526 if not self.ignore_unknown_options: 

527 raise 

528 

529 state.largs.append(arg)