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

249 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 .exceptions import BadArgumentUsage 

34from .exceptions import BadOptionUsage 

35from .exceptions import NoSuchOption 

36from .exceptions import UsageError 

37 

38if t.TYPE_CHECKING: 

39 from .core import Argument as CoreArgument 

40 from .core import Context 

41 from .core import Option as CoreOption 

42 from .core import Parameter as CoreParameter 

43 

44V = t.TypeVar("V") 

45 

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

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

48# prompt or use the flag_value. 

49_flag_needs_value = object() 

50 

51 

52def _unpack_args( 

53 args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int] 

54) -> tuple[cabc.Sequence[str | cabc.Sequence[str | None] | None], list[str]]: 

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

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

57 and all remaining arguments as the second. 

58 

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

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

61 

62 Missing items are filled with `None`. 

63 """ 

64 args = deque(args) 

65 nargs_spec = deque(nargs_spec) 

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

67 spos: int | None = None 

68 

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

70 try: 

71 if spos is None: 

72 return c.popleft() 

73 else: 

74 return c.pop() 

75 except IndexError: 

76 return None 

77 

78 while nargs_spec: 

79 nargs = _fetch(nargs_spec) 

80 

81 if nargs is None: 

82 continue 

83 

84 if nargs == 1: 

85 rv.append(_fetch(args)) 

86 elif nargs > 1: 

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

88 

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

90 # so we need to turn them around. 

91 if spos is not None: 

92 x.reverse() 

93 

94 rv.append(tuple(x)) 

95 elif nargs < 0: 

96 if spos is not None: 

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

98 

99 spos = len(rv) 

100 rv.append(None) 

101 

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

103 # we fill it with the remainder. 

104 if spos is not None: 

105 rv[spos] = tuple(args) 

106 args = [] 

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

108 

109 return tuple(rv), list(args) 

110 

111 

112def _split_opt(opt: str) -> tuple[str, str]: 

113 first = opt[:1] 

114 if first.isalnum(): 

115 return "", opt 

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

117 return opt[:2], opt[2:] 

118 return first, opt[1:] 

119 

120 

121def _normalize_opt(opt: str, ctx: Context | None) -> str: 

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

123 return opt 

124 prefix, opt = _split_opt(opt) 

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

126 

127 

128class _Option: 

129 def __init__( 

130 self, 

131 obj: CoreOption, 

132 opts: cabc.Sequence[str], 

133 dest: str | None, 

134 action: str | None = None, 

135 nargs: int = 1, 

136 const: t.Any | None = None, 

137 ): 

138 self._short_opts = [] 

139 self._long_opts = [] 

140 self.prefixes: set[str] = set() 

141 

142 for opt in opts: 

143 prefix, value = _split_opt(opt) 

144 if not prefix: 

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

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

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

148 self._short_opts.append(opt) 

149 else: 

150 self._long_opts.append(opt) 

151 self.prefixes.add(prefix) 

152 

153 if action is None: 

154 action = "store" 

155 

156 self.dest = dest 

157 self.action = action 

158 self.nargs = nargs 

159 self.const = const 

160 self.obj = obj 

161 

162 @property 

163 def takes_value(self) -> bool: 

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

165 

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

167 if self.action == "store": 

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

169 elif self.action == "store_const": 

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

171 elif self.action == "append": 

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

173 elif self.action == "append_const": 

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

175 elif self.action == "count": 

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

177 else: 

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

179 state.order.append(self.obj) 

180 

181 

182class _Argument: 

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

184 self.dest = dest 

185 self.nargs = nargs 

186 self.obj = obj 

187 

188 def process( 

189 self, 

190 value: str | cabc.Sequence[str | None] | None, 

191 state: _ParsingState, 

192 ) -> None: 

193 if self.nargs > 1: 

194 assert value is not None 

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

196 if holes == len(value): 

197 value = None 

198 elif holes != 0: 

199 raise BadArgumentUsage( 

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

201 name=self.dest, nargs=self.nargs 

202 ) 

203 ) 

204 

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

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

207 # environment may be tried. 

208 value = None 

209 

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

211 state.order.append(self.obj) 

212 

213 

214class _ParsingState: 

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

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

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

218 self.rargs = rargs 

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

220 

221 

222class _OptionParser: 

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

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

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

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

227 

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

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

230 types or defaults). 

231 

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

233 should go with. 

234 

235 .. deprecated:: 8.2 

236 Will be removed in Click 9.0. 

237 """ 

238 

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

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

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

242 self.ctx = ctx 

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

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

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

246 #: safely. 

247 self.allow_interspersed_args: bool = True 

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

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

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

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

252 self.ignore_unknown_options: bool = False 

253 

254 if ctx is not None: 

255 self.allow_interspersed_args = ctx.allow_interspersed_args 

256 self.ignore_unknown_options = ctx.ignore_unknown_options 

257 

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

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

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

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

262 

263 def add_option( 

264 self, 

265 obj: CoreOption, 

266 opts: cabc.Sequence[str], 

267 dest: str | None, 

268 action: str | None = None, 

269 nargs: int = 1, 

270 const: t.Any | None = None, 

271 ) -> None: 

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

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

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

275 ``append``, ``append_const`` or ``count``. 

276 

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

278 that is returned from the parser. 

279 """ 

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

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

282 self._opt_prefixes.update(option.prefixes) 

283 for opt in option._short_opts: 

284 self._short_opt[opt] = option 

285 for opt in option._long_opts: 

286 self._long_opt[opt] = option 

287 

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

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

290 

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

292 that is returned from the parser. 

293 """ 

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

295 

296 def parse_args( 

297 self, args: list[str] 

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

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

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

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

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

303 will be memorized multiple times as well. 

304 """ 

305 state = _ParsingState(args) 

306 try: 

307 self._process_args_for_options(state) 

308 self._process_args_for_args(state) 

309 except UsageError: 

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

311 raise 

312 return state.opts, state.largs, state.order 

313 

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

315 pargs, args = _unpack_args( 

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

317 ) 

318 

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

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

321 

322 state.largs = args 

323 state.rargs = [] 

324 

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

326 while state.rargs: 

327 arg = state.rargs.pop(0) 

328 arglen = len(arg) 

329 # Double dashes always handled explicitly regardless of what 

330 # prefixes are valid. 

331 if arg == "--": 

332 return 

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

334 self._process_opts(arg, state) 

335 elif self.allow_interspersed_args: 

336 state.largs.append(arg) 

337 else: 

338 state.rargs.insert(0, arg) 

339 return 

340 

341 # Say this is the original argument list: 

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

343 # ^ 

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

345 # 

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

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

348 # been removed from largs). 

349 # 

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

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

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

353 # 

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

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

356 # 

357 # If allow_interspersed_args is false, largs will always be 

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

359 # not a very interesting subset! 

360 

361 def _match_long_opt( 

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

363 ) -> None: 

364 if opt not in self._long_opt: 

365 from difflib import get_close_matches 

366 

367 possibilities = get_close_matches(opt, self._long_opt) 

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

369 

370 option = self._long_opt[opt] 

371 if option.takes_value: 

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

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

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

375 # consumed. 

376 if explicit_value is not None: 

377 state.rargs.insert(0, explicit_value) 

378 

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

380 

381 elif explicit_value is not None: 

382 raise BadOptionUsage( 

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

384 ) 

385 

386 else: 

387 value = None 

388 

389 option.process(value, state) 

390 

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

392 stop = False 

393 i = 1 

394 prefix = arg[0] 

395 unknown_options = [] 

396 

397 for ch in arg[1:]: 

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

399 option = self._short_opt.get(opt) 

400 i += 1 

401 

402 if not option: 

403 if self.ignore_unknown_options: 

404 unknown_options.append(ch) 

405 continue 

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

407 if option.takes_value: 

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

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

410 if i < len(arg): 

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

412 stop = True 

413 

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

415 

416 else: 

417 value = None 

418 

419 option.process(value, state) 

420 

421 if stop: 

422 break 

423 

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

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

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

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

428 if self.ignore_unknown_options and unknown_options: 

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

430 

431 def _get_value_from_state( 

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

433 ) -> t.Any: 

434 nargs = option.nargs 

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)