]>
Commit | Line | Data |
---|---|---|
1 | import os | |
2 | import re | |
3 | import typing as t | |
4 | from gettext import gettext as _ | |
5 | ||
6 | from .core import Argument | |
7 | from .core import BaseCommand | |
8 | from .core import Context | |
9 | from .core import MultiCommand | |
10 | from .core import Option | |
11 | from .core import Parameter | |
12 | from .core import ParameterSource | |
13 | from .parser import split_arg_string | |
14 | from .utils import echo | |
15 | ||
16 | ||
17 | def shell_complete( | |
18 | cli: BaseCommand, | |
19 | ctx_args: t.MutableMapping[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 | ||
55 | class 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: t.Any = value | |
84 | self.type: str = type | |
85 | self.help: t.Optional[str] = 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 | ||
160 | if [[ $zsh_eval_context[-1] == loadautofunc ]]; then | |
161 | # autoload from fpath, call function directly | |
162 | %(complete_func)s "$@" | |
163 | else | |
164 | # eval/source/. command, register function for later | |
165 | compdef %(complete_func)s %(prog_name)s | |
166 | fi | |
167 | """ | |
168 | ||
169 | _SOURCE_FISH = """\ | |
170 | function %(complete_func)s; | |
171 | set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ | |
172 | COMP_CWORD=(commandline -t) %(prog_name)s); | |
173 | ||
174 | for completion in $response; | |
175 | set -l metadata (string split "," $completion); | |
176 | ||
177 | if test $metadata[1] = "dir"; | |
178 | __fish_complete_directories $metadata[2]; | |
179 | else if test $metadata[1] = "file"; | |
180 | __fish_complete_path $metadata[2]; | |
181 | else if test $metadata[1] = "plain"; | |
182 | echo $metadata[2]; | |
183 | end; | |
184 | end; | |
185 | end; | |
186 | ||
187 | complete --no-files --command %(prog_name)s --arguments \ | |
188 | "(%(complete_func)s)"; | |
189 | """ | |
190 | ||
191 | ||
192 | class ShellComplete: | |
193 | """Base class for providing shell completion support. A subclass for | |
194 | a given shell will override attributes and methods to implement the | |
195 | completion instructions (``source`` and ``complete``). | |
196 | ||
197 | :param cli: Command being called. | |
198 | :param prog_name: Name of the executable in the shell. | |
199 | :param complete_var: Name of the environment variable that holds | |
200 | the completion instruction. | |
201 | ||
202 | .. versionadded:: 8.0 | |
203 | """ | |
204 | ||
205 | name: t.ClassVar[str] | |
206 | """Name to register the shell as with :func:`add_completion_class`. | |
207 | This is used in completion instructions (``{name}_source`` and | |
208 | ``{name}_complete``). | |
209 | """ | |
210 | ||
211 | source_template: t.ClassVar[str] | |
212 | """Completion script template formatted by :meth:`source`. This must | |
213 | be provided by subclasses. | |
214 | """ | |
215 | ||
216 | def __init__( | |
217 | self, | |
218 | cli: BaseCommand, | |
219 | ctx_args: t.MutableMapping[str, t.Any], | |
220 | prog_name: str, | |
221 | complete_var: str, | |
222 | ) -> None: | |
223 | self.cli = cli | |
224 | self.ctx_args = ctx_args | |
225 | self.prog_name = prog_name | |
226 | self.complete_var = complete_var | |
227 | ||
228 | @property | |
229 | def func_name(self) -> str: | |
230 | """The name of the shell function defined by the completion | |
231 | script. | |
232 | """ | |
233 | safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII) | |
234 | return f"_{safe_name}_completion" | |
235 | ||
236 | def source_vars(self) -> t.Dict[str, t.Any]: | |
237 | """Vars for formatting :attr:`source_template`. | |
238 | ||
239 | By default this provides ``complete_func``, ``complete_var``, | |
240 | and ``prog_name``. | |
241 | """ | |
242 | return { | |
243 | "complete_func": self.func_name, | |
244 | "complete_var": self.complete_var, | |
245 | "prog_name": self.prog_name, | |
246 | } | |
247 | ||
248 | def source(self) -> str: | |
249 | """Produce the shell script that defines the completion | |
250 | function. By default this ``%``-style formats | |
251 | :attr:`source_template` with the dict returned by | |
252 | :meth:`source_vars`. | |
253 | """ | |
254 | return self.source_template % self.source_vars() | |
255 | ||
256 | def get_completion_args(self) -> t.Tuple[t.List[str], str]: | |
257 | """Use the env vars defined by the shell script to return a | |
258 | tuple of ``args, incomplete``. This must be implemented by | |
259 | subclasses. | |
260 | """ | |
261 | raise NotImplementedError | |
262 | ||
263 | def get_completions( | |
264 | self, args: t.List[str], incomplete: str | |
265 | ) -> t.List[CompletionItem]: | |
266 | """Determine the context and last complete command or parameter | |
267 | from the complete args. Call that object's ``shell_complete`` | |
268 | method to get the completions for the incomplete value. | |
269 | ||
270 | :param args: List of complete args before the incomplete value. | |
271 | :param incomplete: Value being completed. May be empty. | |
272 | """ | |
273 | ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) | |
274 | obj, incomplete = _resolve_incomplete(ctx, args, incomplete) | |
275 | return obj.shell_complete(ctx, incomplete) | |
276 | ||
277 | def format_completion(self, item: CompletionItem) -> str: | |
278 | """Format a completion item into the form recognized by the | |
279 | shell script. This must be implemented by subclasses. | |
280 | ||
281 | :param item: Completion item to format. | |
282 | """ | |
283 | raise NotImplementedError | |
284 | ||
285 | def complete(self) -> str: | |
286 | """Produce the completion data to send back to the shell. | |
287 | ||
288 | By default this calls :meth:`get_completion_args`, gets the | |
289 | completions, then calls :meth:`format_completion` for each | |
290 | completion. | |
291 | """ | |
292 | args, incomplete = self.get_completion_args() | |
293 | completions = self.get_completions(args, incomplete) | |
294 | out = [self.format_completion(item) for item in completions] | |
295 | return "\n".join(out) | |
296 | ||
297 | ||
298 | class BashComplete(ShellComplete): | |
299 | """Shell completion for Bash.""" | |
300 | ||
301 | name = "bash" | |
302 | source_template = _SOURCE_BASH | |
303 | ||
304 | @staticmethod | |
305 | def _check_version() -> None: | |
306 | import subprocess | |
307 | ||
308 | output = subprocess.run( | |
309 | ["bash", "-c", 'echo "${BASH_VERSION}"'], stdout=subprocess.PIPE | |
310 | ) | |
311 | match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) | |
312 | ||
313 | if match is not None: | |
314 | major, minor = match.groups() | |
315 | ||
316 | if major < "4" or major == "4" and minor < "4": | |
317 | echo( | |
318 | _( | |
319 | "Shell completion is not supported for Bash" | |
320 | " versions older than 4.4." | |
321 | ), | |
322 | err=True, | |
323 | ) | |
324 | else: | |
325 | echo( | |
326 | _("Couldn't detect Bash version, shell completion is not supported."), | |
327 | err=True, | |
328 | ) | |
329 | ||
330 | def source(self) -> str: | |
331 | self._check_version() | |
332 | return super().source() | |
333 | ||
334 | def get_completion_args(self) -> t.Tuple[t.List[str], str]: | |
335 | cwords = split_arg_string(os.environ["COMP_WORDS"]) | |
336 | cword = int(os.environ["COMP_CWORD"]) | |
337 | args = cwords[1:cword] | |
338 | ||
339 | try: | |
340 | incomplete = cwords[cword] | |
341 | except IndexError: | |
342 | incomplete = "" | |
343 | ||
344 | return args, incomplete | |
345 | ||
346 | def format_completion(self, item: CompletionItem) -> str: | |
347 | return f"{item.type},{item.value}" | |
348 | ||
349 | ||
350 | class ZshComplete(ShellComplete): | |
351 | """Shell completion for Zsh.""" | |
352 | ||
353 | name = "zsh" | |
354 | source_template = _SOURCE_ZSH | |
355 | ||
356 | def get_completion_args(self) -> t.Tuple[t.List[str], str]: | |
357 | cwords = split_arg_string(os.environ["COMP_WORDS"]) | |
358 | cword = int(os.environ["COMP_CWORD"]) | |
359 | args = cwords[1:cword] | |
360 | ||
361 | try: | |
362 | incomplete = cwords[cword] | |
363 | except IndexError: | |
364 | incomplete = "" | |
365 | ||
366 | return args, incomplete | |
367 | ||
368 | def format_completion(self, item: CompletionItem) -> str: | |
369 | return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" | |
370 | ||
371 | ||
372 | class FishComplete(ShellComplete): | |
373 | """Shell completion for Fish.""" | |
374 | ||
375 | name = "fish" | |
376 | source_template = _SOURCE_FISH | |
377 | ||
378 | def get_completion_args(self) -> t.Tuple[t.List[str], str]: | |
379 | cwords = split_arg_string(os.environ["COMP_WORDS"]) | |
380 | incomplete = os.environ["COMP_CWORD"] | |
381 | args = cwords[1:] | |
382 | ||
383 | # Fish stores the partial word in both COMP_WORDS and | |
384 | # COMP_CWORD, remove it from complete args. | |
385 | if incomplete and args and args[-1] == incomplete: | |
386 | args.pop() | |
387 | ||
388 | return args, incomplete | |
389 | ||
390 | def format_completion(self, item: CompletionItem) -> str: | |
391 | if item.help: | |
392 | return f"{item.type},{item.value}\t{item.help}" | |
393 | ||
394 | return f"{item.type},{item.value}" | |
395 | ||
396 | ||
397 | ShellCompleteType = t.TypeVar("ShellCompleteType", bound=t.Type[ShellComplete]) | |
398 | ||
399 | ||
400 | _available_shells: t.Dict[str, t.Type[ShellComplete]] = { | |
401 | "bash": BashComplete, | |
402 | "fish": FishComplete, | |
403 | "zsh": ZshComplete, | |
404 | } | |
405 | ||
406 | ||
407 | def add_completion_class( | |
408 | cls: ShellCompleteType, name: t.Optional[str] = None | |
409 | ) -> ShellCompleteType: | |
410 | """Register a :class:`ShellComplete` subclass under the given name. | |
411 | The name will be provided by the completion instruction environment | |
412 | variable during completion. | |
413 | ||
414 | :param cls: The completion class that will handle completion for the | |
415 | shell. | |
416 | :param name: Name to register the class under. Defaults to the | |
417 | class's ``name`` attribute. | |
418 | """ | |
419 | if name is None: | |
420 | name = cls.name | |
421 | ||
422 | _available_shells[name] = cls | |
423 | ||
424 | return cls | |
425 | ||
426 | ||
427 | def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]: | |
428 | """Look up a registered :class:`ShellComplete` subclass by the name | |
429 | provided by the completion instruction environment variable. If the | |
430 | name isn't registered, returns ``None``. | |
431 | ||
432 | :param shell: Name the class is registered under. | |
433 | """ | |
434 | return _available_shells.get(shell) | |
435 | ||
436 | ||
437 | def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: | |
438 | """Determine if the given parameter is an argument that can still | |
439 | accept values. | |
440 | ||
441 | :param ctx: Invocation context for the command represented by the | |
442 | parsed complete args. | |
443 | :param param: Argument object being checked. | |
444 | """ | |
445 | if not isinstance(param, Argument): | |
446 | return False | |
447 | ||
448 | assert param.name is not None | |
449 | # Will be None if expose_value is False. | |
450 | value = ctx.params.get(param.name) | |
451 | return ( | |
452 | param.nargs == -1 | |
453 | or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE | |
454 | or ( | |
455 | param.nargs > 1 | |
456 | and isinstance(value, (tuple, list)) | |
457 | and len(value) < param.nargs | |
458 | ) | |
459 | ) | |
460 | ||
461 | ||
462 | def _start_of_option(ctx: Context, value: str) -> bool: | |
463 | """Check if the value looks like the start of an option.""" | |
464 | if not value: | |
465 | return False | |
466 | ||
467 | c = value[0] | |
468 | return c in ctx._opt_prefixes | |
469 | ||
470 | ||
471 | def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool: | |
472 | """Determine if the given parameter is an option that needs a value. | |
473 | ||
474 | :param args: List of complete args before the incomplete value. | |
475 | :param param: Option object being checked. | |
476 | """ | |
477 | if not isinstance(param, Option): | |
478 | return False | |
479 | ||
480 | if param.is_flag or param.count: | |
481 | return False | |
482 | ||
483 | last_option = None | |
484 | ||
485 | for index, arg in enumerate(reversed(args)): | |
486 | if index + 1 > param.nargs: | |
487 | break | |
488 | ||
489 | if _start_of_option(ctx, arg): | |
490 | last_option = arg | |
491 | ||
492 | return last_option is not None and last_option in param.opts | |
493 | ||
494 | ||
495 | def _resolve_context( | |
496 | cli: BaseCommand, | |
497 | ctx_args: t.MutableMapping[str, t.Any], | |
498 | prog_name: str, | |
499 | args: t.List[str], | |
500 | ) -> Context: | |
501 | """Produce the context hierarchy starting with the command and | |
502 | traversing the complete arguments. This only follows the commands, | |
503 | it doesn't trigger input prompts or callbacks. | |
504 | ||
505 | :param cli: Command being called. | |
506 | :param prog_name: Name of the executable in the shell. | |
507 | :param args: List of complete args before the incomplete value. | |
508 | """ | |
509 | ctx_args["resilient_parsing"] = True | |
510 | ctx = cli.make_context(prog_name, args.copy(), **ctx_args) | |
511 | args = ctx.protected_args + ctx.args | |
512 | ||
513 | while args: | |
514 | command = ctx.command | |
515 | ||
516 | if isinstance(command, MultiCommand): | |
517 | if not command.chain: | |
518 | name, cmd, args = command.resolve_command(ctx, args) | |
519 | ||
520 | if cmd is None: | |
521 | return ctx | |
522 | ||
523 | ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True) | |
524 | args = ctx.protected_args + ctx.args | |
525 | else: | |
526 | sub_ctx = ctx | |
527 | ||
528 | while args: | |
529 | name, cmd, args = command.resolve_command(ctx, args) | |
530 | ||
531 | if cmd is None: | |
532 | return ctx | |
533 | ||
534 | sub_ctx = cmd.make_context( | |
535 | name, | |
536 | args, | |
537 | parent=ctx, | |
538 | allow_extra_args=True, | |
539 | allow_interspersed_args=False, | |
540 | resilient_parsing=True, | |
541 | ) | |
542 | args = sub_ctx.args | |
543 | ||
544 | ctx = sub_ctx | |
545 | args = [*sub_ctx.protected_args, *sub_ctx.args] | |
546 | else: | |
547 | break | |
548 | ||
549 | return ctx | |
550 | ||
551 | ||
552 | def _resolve_incomplete( | |
553 | ctx: Context, args: t.List[str], incomplete: str | |
554 | ) -> t.Tuple[t.Union[BaseCommand, Parameter], str]: | |
555 | """Find the Click object that will handle the completion of the | |
556 | incomplete value. Return the object and the incomplete value. | |
557 | ||
558 | :param ctx: Invocation context for the command represented by | |
559 | the parsed complete args. | |
560 | :param args: List of complete args before the incomplete value. | |
561 | :param incomplete: Value being completed. May be empty. | |
562 | """ | |
563 | # Different shells treat an "=" between a long option name and | |
564 | # value differently. Might keep the value joined, return the "=" | |
565 | # as a separate item, or return the split name and value. Always | |
566 | # split and discard the "=" to make completion easier. | |
567 | if incomplete == "=": | |
568 | incomplete = "" | |
569 | elif "=" in incomplete and _start_of_option(ctx, incomplete): | |
570 | name, _, incomplete = incomplete.partition("=") | |
571 | args.append(name) | |
572 | ||
573 | # The "--" marker tells Click to stop treating values as options | |
574 | # even if they start with the option character. If it hasn't been | |
575 | # given and the incomplete arg looks like an option, the current | |
576 | # command will provide option name completions. | |
577 | if "--" not in args and _start_of_option(ctx, incomplete): | |
578 | return ctx.command, incomplete | |
579 | ||
580 | params = ctx.command.get_params(ctx) | |
581 | ||
582 | # If the last complete arg is an option name with an incomplete | |
583 | # value, the option will provide value completions. | |
584 | for param in params: | |
585 | if _is_incomplete_option(ctx, args, param): | |
586 | return param, incomplete | |
587 | ||
588 | # It's not an option name or value. The first argument without a | |
589 | # parsed value will provide value completions. | |
590 | for param in params: | |
591 | if _is_incomplete_argument(ctx, param): | |
592 | return param, incomplete | |
593 | ||
594 | # There were no unparsed arguments, the command may be a group that | |
595 | # will provide command name completions. | |
596 | return ctx.command, incomplete |