Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/argcomplete/finders.py: 14%

321 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:31 +0000

1# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the 

2# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE 

3# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License. 

4# See https://github.com/kislyuk/argcomplete for more info. 

5 

6import argparse 

7import os 

8import sys 

9from collections.abc import Mapping 

10from typing import Callable, Dict, List, Optional, Sequence, Union 

11 

12from . import io as _io 

13from .completers import ChoicesCompleter, FilesCompleter, SuppressCompleter 

14from .io import debug, mute_stderr 

15from .lexers import split_line 

16from .packages._argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied 

17 

18safe_actions = { 

19 argparse._StoreAction, 

20 argparse._StoreConstAction, 

21 argparse._StoreTrueAction, 

22 argparse._StoreFalseAction, 

23 argparse._AppendAction, 

24 argparse._AppendConstAction, 

25 argparse._CountAction, 

26} 

27 

28 

29def default_validator(completion, prefix): 

30 return completion.startswith(prefix) 

31 

32 

33class CompletionFinder(object): 

34 """ 

35 Inherit from this class if you wish to override any of the stages below. Otherwise, use 

36 ``argcomplete.autocomplete()`` directly (it's a convenience instance of this class). It has the same signature as 

37 :meth:`CompletionFinder.__call__()`. 

38 """ 

39 

40 def __init__( 

41 self, 

42 argument_parser=None, 

43 always_complete_options=True, 

44 exclude=None, 

45 validator=None, 

46 print_suppressed=False, 

47 default_completer=FilesCompleter(), 

48 append_space=None, 

49 ): 

50 self._parser = argument_parser 

51 self.always_complete_options = always_complete_options 

52 self.exclude = exclude 

53 if validator is None: 

54 validator = default_validator 

55 self.validator = validator 

56 self.print_suppressed = print_suppressed 

57 self.completing = False 

58 self._display_completions: Dict[str, str] = {} 

59 self.default_completer = default_completer 

60 if append_space is None: 

61 append_space = os.environ.get("_ARGCOMPLETE_SUPPRESS_SPACE") != "1" 

62 self.append_space = append_space 

63 

64 def __call__( 

65 self, 

66 argument_parser: argparse.ArgumentParser, 

67 always_complete_options: Union[bool, str] = True, 

68 exit_method: Callable = os._exit, 

69 output_stream=None, 

70 exclude: Optional[Sequence[str]] = None, 

71 validator: Optional[Callable] = None, 

72 print_suppressed: bool = False, 

73 append_space: Optional[bool] = None, 

74 default_completer=FilesCompleter(), 

75 ): 

76 """ 

77 :param argument_parser: The argument parser to autocomplete on 

78 :param always_complete_options: 

79 Controls the autocompletion of option strings if an option string opening character (normally ``-``) has not 

80 been entered. If ``True`` (default), both short (``-x``) and long (``--x``) option strings will be 

81 suggested. If ``False``, no option strings will be suggested. If ``long``, long options and short options 

82 with no long variant will be suggested. If ``short``, short options and long options with no short variant 

83 will be suggested. 

84 :param exit_method: 

85 Method used to stop the program after printing completions. Defaults to :meth:`os._exit`. If you want to 

86 perform a normal exit that calls exit handlers, use :meth:`sys.exit`. 

87 :param exclude: List of strings representing options to be omitted from autocompletion 

88 :param validator: 

89 Function to filter all completions through before returning (called with two string arguments, completion 

90 and prefix; return value is evaluated as a boolean) 

91 :param print_suppressed: 

92 Whether or not to autocomplete options that have the ``help=argparse.SUPPRESS`` keyword argument set. 

93 :param append_space: 

94 Whether to append a space to unique matches. The default is ``True``. 

95 

96 .. note:: 

97 If you are not subclassing CompletionFinder to override its behaviors, 

98 use :meth:`argcomplete.autocomplete()` directly. It has the same signature as this method. 

99 

100 Produces tab completions for ``argument_parser``. See module docs for more info. 

101 

102 Argcomplete only executes actions if their class is known not to have side effects. Custom action classes can be 

103 added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or 

104 their execution is otherwise desirable. 

105 """ 

106 self.__init__( # type: ignore 

107 argument_parser, 

108 always_complete_options=always_complete_options, 

109 exclude=exclude, 

110 validator=validator, 

111 print_suppressed=print_suppressed, 

112 append_space=append_space, 

113 default_completer=default_completer, 

114 ) 

115 

116 if "_ARGCOMPLETE" not in os.environ: 

117 # not an argument completion invocation 

118 return 

119 

120 try: 

121 _io.debug_stream = os.fdopen(9, "w") 

122 except Exception: 

123 _io.debug_stream = sys.stderr 

124 debug() 

125 

126 if output_stream is None: 

127 filename = os.environ.get("_ARGCOMPLETE_STDOUT_FILENAME") 

128 if filename is not None: 

129 debug("Using output file {}".format(filename)) 

130 output_stream = open(filename, "w") 

131 

132 if output_stream is None: 

133 try: 

134 output_stream = os.fdopen(8, "w") 

135 except Exception: 

136 debug("Unable to open fd 8 for writing, quitting") 

137 exit_method(1) 

138 

139 ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013") 

140 if len(ifs) != 1: 

141 debug("Invalid value for IFS, quitting [{v}]".format(v=ifs)) 

142 exit_method(1) 

143 

144 dfs = os.environ.get("_ARGCOMPLETE_DFS") 

145 if dfs and len(dfs) != 1: 

146 debug("Invalid value for DFS, quitting [{v}]".format(v=dfs)) 

147 exit_method(1) 

148 

149 comp_line = os.environ["COMP_LINE"] 

150 comp_point = int(os.environ["COMP_POINT"]) 

151 

152 cword_prequote, cword_prefix, cword_suffix, comp_words, last_wordbreak_pos = split_line(comp_line, comp_point) 

153 

154 # _ARGCOMPLETE is set by the shell script to tell us where comp_words 

155 # should start, based on what we're completing. 

156 # 1: <script> [args] 

157 # 2: python <script> [args] 

158 # 3: python -m <module> [args] 

159 start = int(os.environ["_ARGCOMPLETE"]) - 1 

160 comp_words = comp_words[start:] 

161 

162 if cword_prefix and cword_prefix[0] in self._parser.prefix_chars and "=" in cword_prefix: 

163 # Special case for when the current word is "--optional=PARTIAL_VALUE". Give the optional to the parser. 

164 comp_words.append(cword_prefix.split("=", 1)[0]) 

165 

166 debug( 

167 "\nLINE: {!r}".format(comp_line), 

168 "\nPOINT: {!r}".format(comp_point), 

169 "\nPREQUOTE: {!r}".format(cword_prequote), 

170 "\nPREFIX: {!r}".format(cword_prefix), 

171 "\nSUFFIX: {!r}".format(cword_suffix), 

172 "\nWORDS:", 

173 comp_words, 

174 ) 

175 

176 completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos) 

177 

178 if dfs: 

179 display_completions = { 

180 key: value.replace(ifs, " ") if value else "" for key, value in self._display_completions.items() 

181 } 

182 completions = [dfs.join((key, display_completions.get(key) or "")) for key in completions] 

183 

184 if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh": 

185 completions = [f"{c}:{self._display_completions.get(c)}" for c in completions] 

186 

187 debug("\nReturning completions:", completions) 

188 output_stream.write(ifs.join(completions)) 

189 output_stream.flush() 

190 _io.debug_stream.flush() 

191 exit_method(0) 

192 

193 def _get_completions(self, comp_words, cword_prefix, cword_prequote, last_wordbreak_pos): 

194 active_parsers = self._patch_argument_parser() 

195 

196 parsed_args = argparse.Namespace() 

197 self.completing = True 

198 

199 try: 

200 debug("invoking parser with", comp_words[1:]) 

201 with mute_stderr(): 

202 a = self._parser.parse_known_args(comp_words[1:], namespace=parsed_args) 

203 debug("parsed args:", a) 

204 except BaseException as e: 

205 debug("\nexception", type(e), str(e), "while parsing args") 

206 

207 self.completing = False 

208 

209 if "--" in comp_words: 

210 self.always_complete_options = False 

211 

212 completions = self.collect_completions(active_parsers, parsed_args, cword_prefix) 

213 completions = self.filter_completions(completions) 

214 completions = self.quote_completions(completions, cword_prequote, last_wordbreak_pos) 

215 return completions 

216 

217 def _patch_argument_parser(self): 

218 """ 

219 Since argparse doesn't support much introspection, we monkey-patch it to replace the parse_known_args method and 

220 all actions with hooks that tell us which action was last taken or about to be taken, and let us have the parser 

221 figure out which subparsers need to be activated (then recursively monkey-patch those). 

222 We save all active ArgumentParsers to extract all their possible option names later. 

223 """ 

224 self.active_parsers: List[argparse.ArgumentParser] = [] 

225 self.visited_positionals: List[argparse.Action] = [] 

226 

227 completer = self 

228 

229 def patch(parser): 

230 completer.visited_positionals.append(parser) 

231 completer.active_parsers.append(parser) 

232 

233 if isinstance(parser, IntrospectiveArgumentParser): 

234 return 

235 

236 classname = "MonkeyPatchedIntrospectiveArgumentParser" 

237 

238 parser.__class__ = type(classname, (IntrospectiveArgumentParser, parser.__class__), {}) 

239 

240 for action in parser._actions: 

241 if hasattr(action, "_orig_class"): 

242 continue 

243 

244 # TODO: accomplish this with super 

245 class IntrospectAction(action.__class__): # type: ignore 

246 def __call__(self, parser, namespace, values, option_string=None): 

247 debug("Action stub called on", self) 

248 debug("\targs:", parser, namespace, values, option_string) 

249 debug("\torig class:", self._orig_class) 

250 debug("\torig callable:", self._orig_callable) 

251 

252 if not completer.completing: 

253 self._orig_callable(parser, namespace, values, option_string=option_string) 

254 elif issubclass(self._orig_class, argparse._SubParsersAction): 

255 debug("orig class is a subparsers action: patching and running it") 

256 patch(self._name_parser_map[values[0]]) 

257 self._orig_callable(parser, namespace, values, option_string=option_string) 

258 elif self._orig_class in safe_actions: 

259 if not self.option_strings: 

260 completer.visited_positionals.append(self) 

261 

262 self._orig_callable(parser, namespace, values, option_string=option_string) 

263 

264 action._orig_class = action.__class__ 

265 action._orig_callable = action.__call__ 

266 action.__class__ = IntrospectAction 

267 

268 patch(self._parser) 

269 

270 debug("Active parsers:", self.active_parsers) 

271 debug("Visited positionals:", self.visited_positionals) 

272 

273 return self.active_parsers 

274 

275 def _get_subparser_completions(self, parser, cword_prefix): 

276 aliases_by_parser: Dict[argparse.ArgumentParser, List[str]] = {} 

277 for key in parser.choices.keys(): 

278 p = parser.choices[key] 

279 aliases_by_parser.setdefault(p, []).append(key) 

280 

281 for action in parser._get_subactions(): 

282 for alias in aliases_by_parser[parser.choices[action.dest]]: 

283 if alias.startswith(cword_prefix): 

284 self._display_completions[alias] = action.help or "" 

285 

286 completions = [subcmd for subcmd in parser.choices.keys() if subcmd.startswith(cword_prefix)] 

287 return completions 

288 

289 def _include_options(self, action, cword_prefix): 

290 if len(cword_prefix) > 0 or self.always_complete_options is True: 

291 return [opt for opt in action.option_strings if opt.startswith(cword_prefix)] 

292 long_opts = [opt for opt in action.option_strings if len(opt) > 2] 

293 short_opts = [opt for opt in action.option_strings if len(opt) <= 2] 

294 if self.always_complete_options == "long": 

295 return long_opts if long_opts else short_opts 

296 elif self.always_complete_options == "short": 

297 return short_opts if short_opts else long_opts 

298 return [] 

299 

300 def _get_option_completions(self, parser, cword_prefix): 

301 for action in parser._actions: 

302 if action.option_strings: 

303 for option_string in action.option_strings: 

304 if option_string.startswith(cword_prefix): 

305 self._display_completions[option_string] = action.help or "" 

306 

307 option_completions = [] 

308 for action in parser._actions: 

309 if not self.print_suppressed: 

310 completer = getattr(action, "completer", None) 

311 if isinstance(completer, SuppressCompleter) and completer.suppress(): 

312 continue 

313 if action.help == argparse.SUPPRESS: 

314 continue 

315 if not self._action_allowed(action, parser): 

316 continue 

317 if not isinstance(action, argparse._SubParsersAction): 

318 option_completions += self._include_options(action, cword_prefix) 

319 return option_completions 

320 

321 @staticmethod 

322 def _action_allowed(action, parser): 

323 # Logic adapted from take_action in ArgumentParser._parse_known_args 

324 # (members are saved by vendor._argparse.IntrospectiveArgumentParser) 

325 for conflict_action in parser._action_conflicts.get(action, []): 

326 if conflict_action in parser._seen_non_default_actions: 

327 return False 

328 return True 

329 

330 def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_args, completions): 

331 debug("Active actions (L={l}): {a}".format(l=len(parser.active_actions), a=parser.active_actions)) 

332 

333 isoptional = cword_prefix and cword_prefix[0] in parser.prefix_chars 

334 optional_prefix = "" 

335 greedy_actions = [x for x in parser.active_actions if action_is_greedy(x, isoptional)] 

336 if greedy_actions: 

337 assert len(greedy_actions) == 1, "expect at most 1 greedy action" 

338 # This means the action will fail to parse if the word under the cursor is not given 

339 # to it, so give it exclusive control over completions (flush previous completions) 

340 debug("Resetting completions because", greedy_actions[0], "must consume the next argument") 

341 self._display_completions = {} 

342 completions = [] 

343 elif isoptional: 

344 if "=" in cword_prefix: 

345 # Special case for when the current word is "--optional=PARTIAL_VALUE". 

346 # The completer runs on PARTIAL_VALUE. The prefix is added back to the completions 

347 # (and chopped back off later in quote_completions() by the COMP_WORDBREAKS logic). 

348 optional_prefix, _, cword_prefix = cword_prefix.partition("=") 

349 else: 

350 # Only run completers if current word does not start with - (is not an optional) 

351 return completions 

352 

353 complete_remaining_positionals = False 

354 # Use the single greedy action (if there is one) or all active actions. 

355 for active_action in greedy_actions or parser.active_actions: 

356 if not active_action.option_strings: # action is a positional 

357 if action_is_open(active_action): 

358 # Any positional arguments after this may slide down into this action 

359 # if more arguments are added (since the user may not be done yet), 

360 # so it is extremely difficult to tell which completers to run. 

361 # Running all remaining completers will probably show more than the user wants 

362 # but it also guarantees we won't miss anything. 

363 complete_remaining_positionals = True 

364 if not complete_remaining_positionals: 

365 if action_is_satisfied(active_action) and not action_is_open(active_action): 

366 debug("Skipping", active_action) 

367 continue 

368 

369 debug("Activating completion for", active_action, active_action._orig_class) 

370 # completer = getattr(active_action, "completer", DefaultCompleter()) 

371 completer = getattr(active_action, "completer", None) 

372 

373 if completer is None: 

374 if active_action.choices is not None and not isinstance(active_action, argparse._SubParsersAction): 

375 completer = ChoicesCompleter(active_action.choices) 

376 elif not isinstance(active_action, argparse._SubParsersAction): 

377 completer = self.default_completer 

378 

379 if completer: 

380 if isinstance(completer, SuppressCompleter) and completer.suppress(): 

381 continue 

382 

383 if callable(completer): 

384 completer_output = completer( 

385 prefix=cword_prefix, action=active_action, parser=parser, parsed_args=parsed_args 

386 ) 

387 if isinstance(completer_output, Mapping): 

388 for completion, description in completer_output.items(): 

389 if self.validator(completion, cword_prefix): 

390 completions.append(completion) 

391 self._display_completions[completion] = description 

392 else: 

393 for completion in completer_output: 

394 if self.validator(completion, cword_prefix): 

395 completions.append(completion) 

396 if isinstance(completer, ChoicesCompleter): 

397 self._display_completions[completion] = active_action.help or "" 

398 else: 

399 self._display_completions[completion] = "" 

400 else: 

401 debug("Completer is not callable, trying the readline completer protocol instead") 

402 for i in range(9999): 

403 next_completion = completer.complete(cword_prefix, i) # type: ignore 

404 if next_completion is None: 

405 break 

406 if self.validator(next_completion, cword_prefix): 

407 self._display_completions[next_completion] = "" 

408 completions.append(next_completion) 

409 if optional_prefix: 

410 completions = [optional_prefix + "=" + completion for completion in completions] 

411 debug("Completions:", completions) 

412 return completions 

413 

414 def collect_completions( 

415 self, active_parsers: List[argparse.ArgumentParser], parsed_args: argparse.Namespace, cword_prefix: str 

416 ) -> List[str]: 

417 """ 

418 Visits the active parsers and their actions, executes their completers or introspects them to collect their 

419 option strings. Returns the resulting completions as a list of strings. 

420 

421 This method is exposed for overriding in subclasses; there is no need to use it directly. 

422 """ 

423 completions: List[str] = [] 

424 

425 debug("all active parsers:", active_parsers) 

426 active_parser = active_parsers[-1] 

427 debug("active_parser:", active_parser) 

428 if self.always_complete_options or (len(cword_prefix) > 0 and cword_prefix[0] in active_parser.prefix_chars): 

429 completions += self._get_option_completions(active_parser, cword_prefix) 

430 debug("optional options:", completions) 

431 

432 next_positional = self._get_next_positional() 

433 debug("next_positional:", next_positional) 

434 

435 if isinstance(next_positional, argparse._SubParsersAction): 

436 completions += self._get_subparser_completions(next_positional, cword_prefix) 

437 

438 completions = self._complete_active_option( 

439 active_parser, next_positional, cword_prefix, parsed_args, completions 

440 ) 

441 debug("active options:", completions) 

442 debug("display completions:", self._display_completions) 

443 

444 return completions 

445 

446 def _get_next_positional(self): 

447 """ 

448 Get the next positional action if it exists. 

449 """ 

450 active_parser = self.active_parsers[-1] 

451 last_positional = self.visited_positionals[-1] 

452 

453 all_positionals = active_parser._get_positional_actions() 

454 if not all_positionals: 

455 return None 

456 

457 if active_parser == last_positional: 

458 return all_positionals[0] 

459 

460 i = 0 

461 for i in range(len(all_positionals)): 

462 if all_positionals[i] == last_positional: 

463 break 

464 

465 if i + 1 < len(all_positionals): 

466 return all_positionals[i + 1] 

467 

468 return None 

469 

470 def filter_completions(self, completions: List[str]) -> List[str]: 

471 """ 

472 De-duplicates completions and excludes those specified by ``exclude``. 

473 Returns the filtered completions as a list. 

474 

475 This method is exposed for overriding in subclasses; there is no need to use it directly. 

476 """ 

477 filtered_completions = [] 

478 for completion in completions: 

479 if self.exclude is not None: 

480 if completion in self.exclude: 

481 continue 

482 if completion not in filtered_completions: 

483 filtered_completions.append(completion) 

484 return filtered_completions 

485 

486 def quote_completions( 

487 self, completions: List[str], cword_prequote: str, last_wordbreak_pos: Optional[int] 

488 ) -> List[str]: 

489 """ 

490 If the word under the cursor started with a quote (as indicated by a nonempty ``cword_prequote``), escapes 

491 occurrences of that quote character in the completions, and adds the quote to the beginning of each completion. 

492 Otherwise, escapes all characters that bash splits words on (``COMP_WORDBREAKS``), and removes portions of 

493 completions before the first colon if (``COMP_WORDBREAKS``) contains a colon. 

494 

495 If there is only one completion, and it doesn't end with a **continuation character** (``/``, ``:``, or ``=``), 

496 adds a space after the completion. 

497 

498 This method is exposed for overriding in subclasses; there is no need to use it directly. 

499 """ 

500 special_chars = "\\" 

501 # If the word under the cursor was quoted, escape the quote char. 

502 # Otherwise, escape all special characters and specially handle all COMP_WORDBREAKS chars. 

503 if cword_prequote == "": 

504 # Bash mangles completions which contain characters in COMP_WORDBREAKS. 

505 # This workaround has the same effect as __ltrim_colon_completions in bash_completion 

506 # (extended to characters other than the colon). 

507 if last_wordbreak_pos: 

508 completions = [c[last_wordbreak_pos + 1 :] for c in completions] 

509 special_chars += "();<>|&!`$* \t\n\"'" 

510 elif cword_prequote == '"': 

511 special_chars += '"`$!' 

512 

513 if os.environ.get("_ARGCOMPLETE_SHELL") in ("tcsh", "fish"): 

514 # tcsh and fish escapes special characters itself. 

515 special_chars = "" 

516 elif cword_prequote == "'": 

517 # Nothing can be escaped in single quotes, so we need to close 

518 # the string, escape the single quote, then open a new string. 

519 special_chars = "" 

520 completions = [c.replace("'", r"'\''") for c in completions] 

521 

522 # PowerShell uses ` as escape character. 

523 if os.environ.get("_ARGCOMPLETE_SHELL") == "powershell": 

524 escape_char = '`' 

525 special_chars = special_chars.replace('`', '') 

526 else: 

527 escape_char = "\\" 

528 for char in special_chars: 

529 completions = [c.replace(char, escape_char + char) for c in completions] 

530 

531 if self.append_space: 

532 # Similar functionality in bash was previously turned off by supplying the "-o nospace" option to complete. 

533 # Now it is conditionally disabled using "compopt -o nospace" if the match ends in a continuation character. 

534 # This code is retained for environments where this isn't done natively. 

535 continuation_chars = "=/:" 

536 if len(completions) == 1 and completions[0][-1] not in continuation_chars: 

537 if cword_prequote == "": 

538 completions[0] += " " 

539 

540 return completions 

541 

542 def rl_complete(self, text, state): 

543 """ 

544 Alternate entry point for using the argcomplete completer in a readline-based REPL. See also 

545 `rlcompleter <https://docs.python.org/3/library/rlcompleter.html#completer-objects>`_. 

546 Usage: 

547 

548 .. code-block:: python 

549 

550 import argcomplete, argparse, readline 

551 parser = argparse.ArgumentParser() 

552 ... 

553 completer = argcomplete.CompletionFinder(parser) 

554 readline.set_completer_delims("") 

555 readline.set_completer(completer.rl_complete) 

556 readline.parse_and_bind("tab: complete") 

557 result = input("prompt> ") 

558 """ 

559 if state == 0: 

560 cword_prequote, cword_prefix, cword_suffix, comp_words, first_colon_pos = split_line(text) 

561 comp_words.insert(0, sys.argv[0]) 

562 matches = self._get_completions(comp_words, cword_prefix, cword_prequote, first_colon_pos) 

563 self._rl_matches = [text + match[len(cword_prefix) :] for match in matches] 

564 

565 if state < len(self._rl_matches): 

566 return self._rl_matches[state] 

567 else: 

568 return None 

569 

570 def get_display_completions(self): 

571 """ 

572 This function returns a mapping of option names to their help strings for displaying to the user. 

573 """ 

574 return self._display_completions 

575 

576 

577class ExclusiveCompletionFinder(CompletionFinder): 

578 @staticmethod 

579 def _action_allowed(action, parser): 

580 if not CompletionFinder._action_allowed(action, parser): 

581 return False 

582 

583 append_classes = (argparse._AppendAction, argparse._AppendConstAction) 

584 if action._orig_class in append_classes: 

585 return True 

586 

587 if action not in parser._seen_non_default_actions: 

588 return True 

589 

590 return False