Coverage for src/click/shell_completion.py: 23%

190 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-05-21 11:44 +0200

1import os 

2import re 

3import typing as t 

4from gettext import gettext as _ 

5 

6from .core import Argument 

7from .core import BaseCommand 

8from .core import Context 

9from .core import MultiCommand 

10from .core import Option 

11from .core import Parameter 

12from .core import ParameterSource 

13from .parser import split_arg_string 

14from .utils import echo 

15 

16 

17def shell_complete( 

18 cli: BaseCommand, 

19 ctx_args: t.Dict[str, t.Any], 

20 prog_name: str, 

21 complete_var: str, 

22 instruction: str, 

23) -> int: 

24 """Perform shell completion for the given CLI program. 

25 

26 :param cli: Command being called. 

27 :param ctx_args: Extra arguments to pass to 

28 ``cli.make_context``. 

29 :param prog_name: Name of the executable in the shell. 

30 :param complete_var: Name of the environment variable that holds 

31 the completion instruction. 

32 :param instruction: Value of ``complete_var`` with the completion 

33 instruction and shell, in the form ``instruction_shell``. 

34 :return: Status code to exit with. 

35 """ 

36 shell, _, instruction = instruction.partition("_") 

37 comp_cls = get_completion_class(shell) 

38 

39 if comp_cls is None: 

40 return 1 

41 

42 comp = comp_cls(cli, ctx_args, prog_name, complete_var) 

43 

44 if instruction == "source": 

45 echo(comp.source()) 

46 return 0 

47 

48 if instruction == "complete": 

49 echo(comp.complete()) 

50 return 0 

51 

52 return 1 

53 

54 

55class CompletionItem: 

56 """Represents a completion value and metadata about the value. The 

57 default metadata is ``type`` to indicate special shell handling, 

58 and ``help`` if a shell supports showing a help string next to the 

59 value. 

60 

61 Arbitrary parameters can be passed when creating the object, and 

62 accessed using ``item.attr``. If an attribute wasn't passed, 

63 accessing it returns ``None``. 

64 

65 :param value: The completion suggestion. 

66 :param type: Tells the shell script to provide special completion 

67 support for the type. Click uses ``"dir"`` and ``"file"``. 

68 :param help: String shown next to the value if supported. 

69 :param kwargs: Arbitrary metadata. The built-in implementations 

70 don't use this, but custom type completions paired with custom 

71 shell support could use it. 

72 """ 

73 

74 __slots__ = ("value", "type", "help", "_info") 

75 

76 def __init__( 

77 self, 

78 value: t.Any, 

79 type: str = "plain", 

80 help: t.Optional[str] = None, 

81 **kwargs: t.Any, 

82 ) -> None: 

83 self.value = value 

84 self.type = type 

85 self.help = help 

86 self._info = kwargs 

87 

88 def __getattr__(self, name: str) -> t.Any: 

89 return self._info.get(name) 

90 

91 

92# Only Bash >= 4.4 has the nosort option. 

93_SOURCE_BASH = """\ 

94%(complete_func)s() { 

95 local IFS=$'\\n' 

96 local response 

97 

98 response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ 

99%(complete_var)s=bash_complete $1) 

100 

101 for completion in $response; do 

102 IFS=',' read type value <<< "$completion" 

103 

104 if [[ $type == 'dir' ]]; then 

105 COMPREPLY=() 

106 compopt -o dirnames 

107 elif [[ $type == 'file' ]]; then 

108 COMPREPLY=() 

109 compopt -o default 

110 elif [[ $type == 'plain' ]]; then 

111 COMPREPLY+=($value) 

112 fi 

113 done 

114 

115 return 0 

116} 

117 

118%(complete_func)s_setup() { 

119 complete -o nosort -F %(complete_func)s %(prog_name)s 

120} 

121 

122%(complete_func)s_setup; 

123""" 

124 

125_SOURCE_ZSH = """\ 

126#compdef %(prog_name)s 

127 

128%(complete_func)s() { 

129 local -a completions 

130 local -a completions_with_descriptions 

131 local -a response 

132 (( ! $+commands[%(prog_name)s] )) && return 1 

133 

134 response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ 

135%(complete_var)s=zsh_complete %(prog_name)s)}") 

136 

137 for type key descr in ${response}; do 

138 if [[ "$type" == "plain" ]]; then 

139 if [[ "$descr" == "_" ]]; then 

140 completions+=("$key") 

141 else 

142 completions_with_descriptions+=("$key":"$descr") 

143 fi 

144 elif [[ "$type" == "dir" ]]; then 

145 _path_files -/ 

146 elif [[ "$type" == "file" ]]; then 

147 _path_files -f 

148 fi 

149 done 

150 

151 if [ -n "$completions_with_descriptions" ]; then 

152 _describe -V unsorted completions_with_descriptions -U 

153 fi 

154 

155 if [ -n "$completions" ]; then 

156 compadd -U -V unsorted -a completions 

157 fi 

158} 

159 

160compdef %(complete_func)s %(prog_name)s; 

161""" 

162 

163_SOURCE_FISH = """\ 

164function %(complete_func)s; 

165 set -l response; 

166 

167 for value in (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ 

168COMP_CWORD=(commandline -t) %(prog_name)s); 

169 set response $response $value; 

170 end; 

171 

172 for completion in $response; 

173 set -l metadata (string split "," $completion); 

174 

175 if test $metadata[1] = "dir"; 

176 __fish_complete_directories $metadata[2]; 

177 else if test $metadata[1] = "file"; 

178 __fish_complete_path $metadata[2]; 

179 else if test $metadata[1] = "plain"; 

180 echo $metadata[2]; 

181 end; 

182 end; 

183end; 

184 

185complete --no-files --command %(prog_name)s --arguments \ 

186"(%(complete_func)s)"; 

187""" 

188 

189 

190class ShellComplete: 

191 """Base class for providing shell completion support. A subclass for 

192 a given shell will override attributes and methods to implement the 

193 completion instructions (``source`` and ``complete``). 

194 

195 :param cli: Command being called. 

196 :param prog_name: Name of the executable in the shell. 

197 :param complete_var: Name of the environment variable that holds 

198 the completion instruction. 

199 

200 .. versionadded:: 8.0 

201 """ 

202 

203 name: t.ClassVar[str] 

204 """Name to register the shell as with :func:`add_completion_class`. 

205 This is used in completion instructions (``{name}_source`` and 

206 ``{name}_complete``). 

207 """ 

208 

209 source_template: t.ClassVar[str] 

210 """Completion script template formatted by :meth:`source`. This must 

211 be provided by subclasses. 

212 """ 

213 

214 def __init__( 

215 self, 

216 cli: BaseCommand, 

217 ctx_args: t.Dict[str, t.Any], 

218 prog_name: str, 

219 complete_var: str, 

220 ) -> None: 

221 self.cli = cli 

222 self.ctx_args = ctx_args 

223 self.prog_name = prog_name 

224 self.complete_var = complete_var 

225 

226 @property 

227 def func_name(self) -> str: 

228 """The name of the shell function defined by the completion 

229 script. 

230 """ 

231 safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), re.ASCII) 

232 return f"_{safe_name}_completion" 

233 

234 def source_vars(self) -> t.Dict[str, t.Any]: 

235 """Vars for formatting :attr:`source_template`. 

236 

237 By default this provides ``complete_func``, ``complete_var``, 

238 and ``prog_name``. 

239 """ 

240 return { 

241 "complete_func": self.func_name, 

242 "complete_var": self.complete_var, 

243 "prog_name": self.prog_name, 

244 } 

245 

246 def source(self) -> str: 

247 """Produce the shell script that defines the completion 

248 function. By default this ``%``-style formats 

249 :attr:`source_template` with the dict returned by 

250 :meth:`source_vars`. 

251 """ 

252 return self.source_template % self.source_vars() 

253 

254 def get_completion_args(self) -> t.Tuple[t.List[str], str]: 

255 """Use the env vars defined by the shell script to return a 

256 tuple of ``args, incomplete``. This must be implemented by 

257 subclasses. 

258 """ 

259 raise NotImplementedError 

260 

261 def get_completions( 

262 self, args: t.List[str], incomplete: str 

263 ) -> t.List[CompletionItem]: 

264 """Determine the context and last complete command or parameter 

265 from the complete args. Call that object's ``shell_complete`` 

266 method to get the completions for the incomplete value. 

267 

268 :param args: List of complete args before the incomplete value. 

269 :param incomplete: Value being completed. May be empty. 

270 """ 

271 ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) 

272 obj, incomplete = _resolve_incomplete(ctx, args, incomplete) 

273 return obj.shell_complete(ctx, incomplete) 

274 

275 def format_completion(self, item: CompletionItem) -> str: 

276 """Format a completion item into the form recognized by the 

277 shell script. This must be implemented by subclasses. 

278 

279 :param item: Completion item to format. 

280 """ 

281 raise NotImplementedError 

282 

283 def complete(self) -> str: 

284 """Produce the completion data to send back to the shell. 

285 

286 By default this calls :meth:`get_completion_args`, gets the 

287 completions, then calls :meth:`format_completion` for each 

288 completion. 

289 """ 

290 args, incomplete = self.get_completion_args() 

291 completions = self.get_completions(args, incomplete) 

292 out = [self.format_completion(item) for item in completions] 

293 return "\n".join(out) 

294 

295 

296class BashComplete(ShellComplete): 

297 """Shell completion for Bash.""" 

298 

299 name = "bash" 

300 source_template = _SOURCE_BASH 

301 

302 def _check_version(self) -> None: 

303 import subprocess 

304 

305 output = subprocess.run( 

306 ["bash", "-c", "echo ${BASH_VERSION}"], stdout=subprocess.PIPE 

307 ) 

308 match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) 

309 

310 if match is not None: 

311 major, minor = match.groups() 

312 

313 if major < "4" or major == "4" and minor < "4": 

314 raise RuntimeError( 

315 _( 

316 "Shell completion is not supported for Bash" 

317 " versions older than 4.4." 

318 ) 

319 ) 

320 else: 

321 raise RuntimeError( 

322 _("Couldn't detect Bash version, shell completion is not supported.") 

323 ) 

324 

325 def source(self) -> str: 

326 self._check_version() 

327 return super().source() 

328 

329 def get_completion_args(self) -> t.Tuple[t.List[str], str]: 

330 cwords = split_arg_string(os.environ["COMP_WORDS"]) 

331 cword = int(os.environ["COMP_CWORD"]) 

332 args = cwords[1:cword] 

333 

334 try: 

335 incomplete = cwords[cword] 

336 except IndexError: 

337 incomplete = "" 

338 

339 return args, incomplete 

340 

341 def format_completion(self, item: CompletionItem) -> str: 

342 return f"{item.type},{item.value}" 

343 

344 

345class ZshComplete(ShellComplete): 

346 """Shell completion for Zsh.""" 

347 

348 name = "zsh" 

349 source_template = _SOURCE_ZSH 

350 

351 def get_completion_args(self) -> t.Tuple[t.List[str], str]: 

352 cwords = split_arg_string(os.environ["COMP_WORDS"]) 

353 cword = int(os.environ["COMP_CWORD"]) 

354 args = cwords[1:cword] 

355 

356 try: 

357 incomplete = cwords[cword] 

358 except IndexError: 

359 incomplete = "" 

360 

361 return args, incomplete 

362 

363 def format_completion(self, item: CompletionItem) -> str: 

364 return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" 

365 

366 

367class FishComplete(ShellComplete): 

368 """Shell completion for Fish.""" 

369 

370 name = "fish" 

371 source_template = _SOURCE_FISH 

372 

373 def get_completion_args(self) -> t.Tuple[t.List[str], str]: 

374 cwords = split_arg_string(os.environ["COMP_WORDS"]) 

375 incomplete = os.environ["COMP_CWORD"] 

376 args = cwords[1:] 

377 

378 # Fish stores the partial word in both COMP_WORDS and 

379 # COMP_CWORD, remove it from complete args. 

380 if incomplete and args and args[-1] == incomplete: 

381 args.pop() 

382 

383 return args, incomplete 

384 

385 def format_completion(self, item: CompletionItem) -> str: 

386 if item.help: 

387 return f"{item.type},{item.value}\t{item.help}" 

388 

389 return f"{item.type},{item.value}" 

390 

391 

392_available_shells: t.Dict[str, t.Type[ShellComplete]] = { 

393 "bash": BashComplete, 

394 "fish": FishComplete, 

395 "zsh": ZshComplete, 

396} 

397 

398 

399def add_completion_class( 

400 cls: t.Type[ShellComplete], name: t.Optional[str] = None 

401) -> None: 

402 """Register a :class:`ShellComplete` subclass under the given name. 

403 The name will be provided by the completion instruction environment 

404 variable during completion. 

405 

406 :param cls: The completion class that will handle completion for the 

407 shell. 

408 :param name: Name to register the class under. Defaults to the 

409 class's ``name`` attribute. 

410 """ 

411 if name is None: 

412 name = cls.name 

413 

414 _available_shells[name] = cls 

415 

416 

417def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]: 

418 """Look up a registered :class:`ShellComplete` subclass by the name 

419 provided by the completion instruction environment variable. If the 

420 name isn't registered, returns ``None``. 

421 

422 :param shell: Name the class is registered under. 

423 """ 

424 return _available_shells.get(shell) 

425 

426 

427def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: 

428 """Determine if the given parameter is an argument that can still 

429 accept values. 

430 

431 :param ctx: Invocation context for the command represented by the 

432 parsed complete args. 

433 :param param: Argument object being checked. 

434 """ 

435 if not isinstance(param, Argument): 

436 return False 

437 

438 assert param.name is not None 

439 value = ctx.params[param.name] 

440 return ( 

441 param.nargs == -1 

442 or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE 

443 or ( 

444 param.nargs > 1 

445 and isinstance(value, (tuple, list)) 

446 and len(value) < param.nargs 

447 ) 

448 ) 

449 

450 

451def _start_of_option(ctx: Context, value: str) -> bool: 

452 """Check if the value looks like the start of an option.""" 

453 if not value: 

454 return False 

455 

456 c = value[0] 

457 return c in ctx._opt_prefixes 

458 

459 

460def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool: 

461 """Determine if the given parameter is an option that needs a value. 

462 

463 :param args: List of complete args before the incomplete value. 

464 :param param: Option object being checked. 

465 """ 

466 if not isinstance(param, Option): 

467 return False 

468 

469 if param.is_flag or param.count: 

470 return False 

471 

472 last_option = None 

473 

474 for index, arg in enumerate(reversed(args)): 

475 if index + 1 > param.nargs: 

476 break 

477 

478 if _start_of_option(ctx, arg): 

479 last_option = arg 

480 

481 return last_option is not None and last_option in param.opts 

482 

483 

484def _resolve_context( 

485 cli: BaseCommand, ctx_args: t.Dict[str, t.Any], prog_name: str, args: t.List[str] 

486) -> Context: 

487 """Produce the context hierarchy starting with the command and 

488 traversing the complete arguments. This only follows the commands, 

489 it doesn't trigger input prompts or callbacks. 

490 

491 :param cli: Command being called. 

492 :param prog_name: Name of the executable in the shell. 

493 :param args: List of complete args before the incomplete value. 

494 """ 

495 ctx_args["resilient_parsing"] = True 

496 ctx = cli.make_context(prog_name, args.copy(), **ctx_args) 

497 args = ctx.protected_args + ctx.args 

498 

499 while args: 

500 command = ctx.command 

501 

502 if isinstance(command, MultiCommand): 

503 if not command.chain: 

504 name, cmd, args = command.resolve_command(ctx, args) 

505 

506 if cmd is None: 

507 return ctx 

508 

509 ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True) 

510 args = ctx.protected_args + ctx.args 

511 else: 

512 while args: 

513 name, cmd, args = command.resolve_command(ctx, args) 

514 

515 if cmd is None: 

516 return ctx 

517 

518 sub_ctx = cmd.make_context( 

519 name, 

520 args, 

521 parent=ctx, 

522 allow_extra_args=True, 

523 allow_interspersed_args=False, 

524 resilient_parsing=True, 

525 ) 

526 args = sub_ctx.args 

527 

528 ctx = sub_ctx 

529 args = [*sub_ctx.protected_args, *sub_ctx.args] 

530 else: 

531 break 

532 

533 return ctx 

534 

535 

536def _resolve_incomplete( 

537 ctx: Context, args: t.List[str], incomplete: str 

538) -> t.Tuple[t.Union[BaseCommand, Parameter], str]: 

539 """Find the Click object that will handle the completion of the 

540 incomplete value. Return the object and the incomplete value. 

541 

542 :param ctx: Invocation context for the command represented by 

543 the parsed complete args. 

544 :param args: List of complete args before the incomplete value. 

545 :param incomplete: Value being completed. May be empty. 

546 """ 

547 # Different shells treat an "=" between a long option name and 

548 # value differently. Might keep the value joined, return the "=" 

549 # as a separate item, or return the split name and value. Always 

550 # split and discard the "=" to make completion easier. 

551 if incomplete == "=": 

552 incomplete = "" 

553 elif "=" in incomplete and _start_of_option(ctx, incomplete): 

554 name, _, incomplete = incomplete.partition("=") 

555 args.append(name) 

556 

557 # The "--" marker tells Click to stop treating values as options 

558 # even if they start with the option character. If it hasn't been 

559 # given and the incomplete arg looks like an option, the current 

560 # command will provide option name completions. 

561 if "--" not in args and _start_of_option(ctx, incomplete): 

562 return ctx.command, incomplete 

563 

564 params = ctx.command.get_params(ctx) 

565 

566 # If the last complete arg is an option name with an incomplete 

567 # value, the option will provide value completions. 

568 for param in params: 

569 if _is_incomplete_option(ctx, args, param): 

570 return param, incomplete 

571 

572 # It's not an option name or value. The first argument without a 

573 # parsed value will provide value completions. 

574 for param in params: 

575 if _is_incomplete_argument(ctx, param): 

576 return param, incomplete 

577 

578 # There were no unparsed arguments, the command may be a group that 

579 # will provide command name completions. 

580 return ctx.command, incomplete