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

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

361 statements  

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 

6from __future__ import annotations 

7 

8import argparse 

9import os 

10import sys 

11from collections.abc import Callable, Container, Mapping 

12from typing import Literal, TextIO 

13 

14from . import io as _io 

15from .completers import BaseCompleter, ChoicesCompleter, FilesCompleter, SuppressCompleter 

16from .io import debug, mute_stderr 

17from .lexers import split_line 

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

19 

20safe_actions = { 

21 argparse._StoreAction, 

22 argparse._StoreConstAction, 

23 argparse._StoreTrueAction, 

24 argparse._StoreFalseAction, 

25 argparse._AppendAction, 

26 argparse._AppendConstAction, 

27 argparse._CountAction, 

28} 

29 

30 

31def default_validator(completion: str, prefix: str) -> bool: 

32 return completion.startswith(prefix) 

33 

34 

35class CompletionFinder: 

36 """ 

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

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

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

40 """ 

41 

42 _parser: argparse.ArgumentParser | None 

43 _formatter: argparse.HelpFormatter | None 

44 always_complete_options: bool | Literal["long", "short"] 

45 exclude: Container[str] | None 

46 validator: Callable[[str, str], bool] 

47 print_suppressed: bool 

48 completing: bool 

49 _display_completions: dict[str, str] 

50 default_completer: BaseCompleter 

51 append_space: bool 

52 

53 active_parsers: list[argparse.ArgumentParser] 

54 visited_positionals: list[argparse.Action] 

55 

56 def __init__( 

57 self, 

58 argument_parser: argparse.ArgumentParser | None = None, 

59 always_complete_options: bool | Literal["long", "short"] = True, 

60 exclude: Container[str] | None = None, 

61 validator: Callable[[str, str], bool] | None = None, 

62 print_suppressed: bool = False, 

63 default_completer: BaseCompleter = FilesCompleter(), 

64 append_space: bool | None = None, 

65 ) -> None: 

66 self._parser = argument_parser # type: ignore[assignment] 

67 self._formatter = None 

68 self.always_complete_options = always_complete_options 

69 self.exclude = exclude 

70 if validator is None: 

71 validator = default_validator 

72 self.validator = validator 

73 self.print_suppressed = print_suppressed 

74 self.completing = False 

75 self._display_completions = {} 

76 self.default_completer = default_completer 

77 if append_space is None: 

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

79 self.append_space = append_space 

80 

81 def __call__( 

82 self, 

83 argument_parser: argparse.ArgumentParser, 

84 always_complete_options: bool | str = True, 

85 exit_method: Callable = os._exit, 

86 output_stream: TextIO | None = None, 

87 exclude: Container[str] | None = None, 

88 validator: Callable[[str, str], bool] | None = None, 

89 print_suppressed: bool = False, 

90 append_space: bool | None = None, 

91 default_completer: BaseCompleter = FilesCompleter(), 

92 ) -> None: 

93 """ 

94 :param argument_parser: The argument parser to autocomplete on 

95 :param always_complete_options: 

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

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

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

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

100 will be suggested. 

101 :param exit_method: 

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

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

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

105 :param validator: 

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

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

108 :param print_suppressed: 

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

110 :param append_space: 

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

112 

113 .. note:: 

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

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

116 

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

118 

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

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

121 their execution is otherwise desirable. 

122 """ 

123 self.__init__( # type: ignore 

124 argument_parser, 

125 always_complete_options=always_complete_options, 

126 exclude=exclude, 

127 validator=validator, 

128 print_suppressed=print_suppressed, 

129 append_space=append_space, 

130 default_completer=default_completer, 

131 ) 

132 

133 if "_ARGCOMPLETE" not in os.environ: 

134 # not an argument completion invocation 

135 return 

136 

137 self._init_debug_stream() 

138 

139 if output_stream is None: 

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

141 if filename is not None: 

142 debug(f"Using output file {filename}") 

143 encoding = "utf-8" if os.environ.get("_ARGCOMPLETE_SHELL") == "powershell" else None 

144 output_stream = open(filename, "w", encoding=encoding) 

145 

146 if output_stream is None: 

147 try: 

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

149 except Exception: 

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

151 exit_method(1) 

152 

153 assert output_stream is not None 

154 

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

156 if len(ifs) != 1: 

157 debug(f"Invalid value for IFS, quitting [{ifs}]") 

158 exit_method(1) 

159 

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

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

162 debug(f"Invalid value for DFS, quitting [{dfs}]") 

163 exit_method(1) 

164 

165 comp_line = os.environ["COMP_LINE"] 

166 try: 

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

168 except ValueError: 

169 debug("Invalid value for COMP_POINT, quitting [{v}]".format(v=os.environ["COMP_POINT"])) 

170 exit_method(1) 

171 return 

172 

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

174 

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

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

177 # 1: <script> [args] 

178 # 2: python <script> [args] 

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

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

181 comp_words = comp_words[start:] 

182 

183 assert self._parser is not None 

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

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

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

187 

188 debug( 

189 f"\nLINE: {comp_line!r}", 

190 f"\nPOINT: {comp_point!r}", 

191 f"\nPREQUOTE: {cword_prequote!r}", 

192 f"\nPREFIX: {cword_prefix!r}", 

193 f"\nSUFFIX: {cword_suffix!r}", 

194 "\nWORDS:", 

195 comp_words, 

196 ) 

197 

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

199 

200 if dfs: 

201 display_completions = { 

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

203 } 

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

205 

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

207 completions = [f"{c}:{(self._display_completions.get(c) or '').replace(ifs, ' ')}" for c in completions] 

208 

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

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

211 output_stream.flush() 

212 _io.debug_stream.flush() 

213 exit_method(0) 

214 

215 def _init_debug_stream(self): 

216 """Initialize debug output stream 

217 

218 By default, writes to file descriptor 9, or stderr if that fails. 

219 This can be overridden by derived classes, for example to avoid 

220 clashes with file descriptors being used elsewhere (such as in pytest). 

221 """ 

222 try: 

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

224 except Exception: 

225 _io.debug_stream = sys.stderr 

226 debug() 

227 

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

229 active_parsers = self._patch_argument_parser() 

230 

231 parsed_args = argparse.Namespace() 

232 self.completing = True 

233 

234 try: 

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

236 with mute_stderr(): 

237 assert self._parser is not None 

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

239 debug("parsed args:", a) 

240 except BaseException as e: 

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

242 

243 self.completing = False 

244 

245 if "--" in comp_words: 

246 self.always_complete_options = False 

247 

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

249 completions = self.filter_completions(completions) 

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

251 return completions 

252 

253 def _patch_argument_parser(self): 

254 """ 

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

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

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

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

259 """ 

260 self.active_parsers = [] 

261 self.visited_positionals = [] 

262 

263 completer = self 

264 

265 def patch(parser): 

266 completer.visited_positionals.append(parser) 

267 completer.active_parsers.append(parser) 

268 

269 if isinstance(parser, IntrospectiveArgumentParser): 

270 return 

271 

272 classname = "MonkeyPatchedIntrospectiveArgumentParser" 

273 

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

275 

276 for action in parser._actions: 

277 if hasattr(action, "_orig_class"): 

278 continue 

279 

280 # TODO: accomplish this with super 

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

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

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

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

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

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

287 

288 if not completer.completing: 

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

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

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

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

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

294 elif self._orig_class in safe_actions: 

295 if not self.option_strings: 

296 completer.visited_positionals.append(self) 

297 

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

299 

300 action._orig_class = action.__class__ 

301 action._orig_callable = action.__call__ 

302 action.__class__ = IntrospectAction 

303 

304 patch(self._parser) 

305 

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

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

308 

309 return self.active_parsers 

310 

311 def _get_action_help(self, action): 

312 if action.help is None: 

313 return "" 

314 if "%" not in action.help: 

315 return action.help 

316 formatters = self._formatter 

317 if formatters is None: 

318 assert self._parser is not None 

319 self._formatter = formatters = self._parser.formatter_class(prog=self._parser.prog) 

320 return formatters._expand_help(action) 

321 

322 def _get_subparser_completions(self, parser, cword_prefix): 

323 aliases_by_parser: dict[argparse.ArgumentParser, list[str]] = {} 

324 for key in parser.choices: 

325 p = parser.choices[key] 

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

327 

328 for action in parser._get_subactions(): 

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

330 if alias.startswith(cword_prefix): 

331 self._display_completions[alias] = self._get_action_help(action) 

332 

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

334 return completions 

335 

336 def _include_options(self, action, cword_prefix): 

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

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

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

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

341 if self.always_complete_options == "long": 

342 return long_opts if long_opts else short_opts 

343 elif self.always_complete_options == "short": 

344 return short_opts if short_opts else long_opts 

345 return [] 

346 

347 def _get_option_completions(self, parser, cword_prefix): 

348 for action in parser._actions: 

349 if action.option_strings: 

350 for option_string in action.option_strings: 

351 if option_string.startswith(cword_prefix): 

352 self._display_completions[option_string] = self._get_action_help(action) 

353 

354 option_completions = [] 

355 for action in parser._actions: 

356 if not self.print_suppressed: 

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

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

359 continue 

360 if action.help == argparse.SUPPRESS: 

361 continue 

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

363 continue 

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

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

366 return option_completions 

367 

368 @staticmethod 

369 def _action_allowed(action, parser): 

370 # Logic adapted from take_action in ArgumentParser._parse_known_args 

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

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

373 if conflict_action in parser._seen_non_default_actions: 

374 return False 

375 return True 

376 

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

378 debug(f"Active actions (L={len(parser.active_actions)}): {parser.active_actions}") 

379 

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

381 optional_prefix = "" 

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

383 if greedy_actions: 

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

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

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

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

388 self._display_completions = {} 

389 completions = [] 

390 elif isoptional: 

391 if "=" in cword_prefix: 

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

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

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

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

396 else: 

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

398 return completions 

399 

400 complete_remaining_positionals = False 

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

402 for active_action in greedy_actions or parser.active_actions: 

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

404 if action_is_open(active_action): 

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

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

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

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

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

410 complete_remaining_positionals = True 

411 if not complete_remaining_positionals: 

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

413 debug("Skipping", active_action) 

414 continue 

415 

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

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

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

419 

420 if completer is None: 

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

422 completer = ChoicesCompleter(active_action.choices) 

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

424 completer = self.default_completer 

425 

426 if completer: 

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

428 continue 

429 

430 if callable(completer): 

431 completer_output = completer( 

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

433 ) 

434 if isinstance(completer_output, Mapping): 

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

436 if self.validator(completion, cword_prefix): 

437 completions.append(completion) 

438 self._display_completions[completion] = description 

439 else: 

440 for completion in completer_output: 

441 if self.validator(completion, cword_prefix): 

442 completions.append(completion) 

443 if isinstance(completer, ChoicesCompleter): 

444 self._display_completions[completion] = self._get_action_help(active_action) 

445 else: 

446 self._display_completions[completion] = "" 

447 else: 

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

449 for i in range(9999): 

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

451 if next_completion is None: 

452 break 

453 if self.validator(next_completion, cword_prefix): 

454 self._display_completions[next_completion] = "" 

455 completions.append(next_completion) 

456 if optional_prefix: 

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

458 debug("Completions:", completions) 

459 return completions 

460 

461 def collect_completions( 

462 self, active_parsers: list[argparse.ArgumentParser], parsed_args: argparse.Namespace, cword_prefix: str 

463 ) -> list[str]: 

464 """ 

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

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

467 

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

469 """ 

470 completions: list[str] = [] 

471 

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

473 active_parser = active_parsers[-1] 

474 debug("active_parser:", active_parser) 

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

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

477 debug("optional options:", completions) 

478 

479 next_positional = self._get_next_positional() 

480 debug("next_positional:", next_positional) 

481 

482 if isinstance(next_positional, argparse._SubParsersAction): 

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

484 

485 completions = self._complete_active_option( 

486 active_parser, next_positional, cword_prefix, parsed_args, completions 

487 ) 

488 debug("active options:", completions) 

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

490 

491 return completions 

492 

493 def _get_next_positional(self): 

494 """ 

495 Get the next positional action if it exists. 

496 """ 

497 active_parser = self.active_parsers[-1] 

498 last_positional = self.visited_positionals[-1] 

499 

500 all_positionals = active_parser._get_positional_actions() 

501 if not all_positionals: 

502 return None 

503 

504 if active_parser == last_positional: 

505 return all_positionals[0] 

506 

507 i = 0 

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

509 if all_positionals[i] == last_positional: 

510 break 

511 

512 if i + 1 < len(all_positionals): 

513 return all_positionals[i + 1] 

514 

515 return None 

516 

517 def filter_completions(self, completions: list[str]) -> list[str]: 

518 """ 

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

520 Returns the filtered completions as a list. 

521 

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

523 """ 

524 filtered_completions = [] 

525 for completion in completions: 

526 if self.exclude is not None and completion in self.exclude: 

527 continue 

528 if completion not in filtered_completions: 

529 filtered_completions.append(completion) 

530 return filtered_completions 

531 

532 def quote_completions( 

533 self, completions: list[str], cword_prequote: str, last_wordbreak_pos: int | None 

534 ) -> list[str]: 

535 """ 

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

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

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

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

540 

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

542 adds a space after the completion. 

543 

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

545 """ 

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

547 # PowerShell replaces the whole token, including any opening quote. 

548 # Its shell integration quotes these raw values with PowerShell's own escaping API. 

549 return completions 

550 

551 special_chars = "\\" 

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

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

554 if cword_prequote == "": 

555 # Bash mangles completions which contain characters in COMP_WORDBREAKS. 

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

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

558 if last_wordbreak_pos is not None: 

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

560 special_chars += "();<>|&!`$*?[]{} \t\n\"'" 

561 elif cword_prequote == '"': 

562 special_chars += '"`$!' 

563 

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

565 # tcsh and fish escapes special characters itself. 

566 special_chars = "" 

567 elif cword_prequote == "'": 

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

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

570 special_chars = "" 

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

572 

573 escape_char = "\\" 

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

575 # zsh uses colon as a separator between a completion and its description. 

576 special_chars += ":" 

577 

578 escaped_completions = [] 

579 for completion in completions: 

580 escaped_completion = completion 

581 for char in special_chars: 

582 escaped_completion = escaped_completion.replace(char, escape_char + char) 

583 escaped_completions.append(escaped_completion) 

584 if completion in self._display_completions: 

585 self._display_completions[escaped_completion] = self._display_completions[completion] 

586 

587 if self.append_space: 

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

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

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

591 continuation_chars = "=/:" 

592 if ( 

593 len(escaped_completions) == 1 

594 and escaped_completions[0] 

595 and escaped_completions[0][-1] not in continuation_chars 

596 ): 

597 if cword_prequote == "": 

598 escaped_completions[0] += " " 

599 

600 return escaped_completions 

601 

602 def rl_complete(self, text: str, state: int) -> str | None: 

603 """ 

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

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

606 Usage: 

607 

608 .. code-block:: python 

609 

610 import argcomplete, argparse, readline 

611 parser = argparse.ArgumentParser() 

612 ... 

613 completer = argcomplete.CompletionFinder(parser) 

614 readline.set_completer_delims("") 

615 readline.set_completer(completer.rl_complete) 

616 readline.parse_and_bind("tab: complete") 

617 result = input("prompt> ") 

618 """ 

619 if state == 0: 

620 cword_prequote, cword_prefix, _cword_suffix, comp_words, first_colon_pos = split_line(text) 

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

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

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

624 

625 if state < len(self._rl_matches): 

626 return self._rl_matches[state] 

627 else: 

628 return None 

629 

630 def get_display_completions(self) -> dict[str, str]: 

631 """ 

632 This function returns a mapping of completions to their help strings for displaying to the user. 

633 """ 

634 return self._display_completions 

635 

636 

637class ExclusiveCompletionFinder(CompletionFinder): 

638 @staticmethod 

639 def _action_allowed(action, parser): 

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

641 return False 

642 

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

644 if action._orig_class in append_classes: 

645 return True 

646 

647 return action not in parser._seen_non_default_actions