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
« 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 _
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
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.
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)
39 if comp_cls is None:
40 return 1
42 comp = comp_cls(cli, ctx_args, prog_name, complete_var)
44 if instruction == "source":
45 echo(comp.source())
46 return 0
48 if instruction == "complete":
49 echo(comp.complete())
50 return 0
52 return 1
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.
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``.
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 """
74 __slots__ = ("value", "type", "help", "_info")
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
88 def __getattr__(self, name: str) -> t.Any:
89 return self._info.get(name)
92# Only Bash >= 4.4 has the nosort option.
93_SOURCE_BASH = """\
94%(complete_func)s() {
95 local IFS=$'\\n'
96 local response
98 response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \
99%(complete_var)s=bash_complete $1)
101 for completion in $response; do
102 IFS=',' read type value <<< "$completion"
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
115 return 0
116}
118%(complete_func)s_setup() {
119 complete -o nosort -F %(complete_func)s %(prog_name)s
120}
122%(complete_func)s_setup;
123"""
125_SOURCE_ZSH = """\
126#compdef %(prog_name)s
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
134 response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \
135%(complete_var)s=zsh_complete %(prog_name)s)}")
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
151 if [ -n "$completions_with_descriptions" ]; then
152 _describe -V unsorted completions_with_descriptions -U
153 fi
155 if [ -n "$completions" ]; then
156 compadd -U -V unsorted -a completions
157 fi
158}
160compdef %(complete_func)s %(prog_name)s;
161"""
163_SOURCE_FISH = """\
164function %(complete_func)s;
165 set -l response;
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;
172 for completion in $response;
173 set -l metadata (string split "," $completion);
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;
185complete --no-files --command %(prog_name)s --arguments \
186"(%(complete_func)s)";
187"""
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``).
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.
200 .. versionadded:: 8.0
201 """
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 """
209 source_template: t.ClassVar[str]
210 """Completion script template formatted by :meth:`source`. This must
211 be provided by subclasses.
212 """
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
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"
234 def source_vars(self) -> t.Dict[str, t.Any]:
235 """Vars for formatting :attr:`source_template`.
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 }
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()
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
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.
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)
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.
279 :param item: Completion item to format.
280 """
281 raise NotImplementedError
283 def complete(self) -> str:
284 """Produce the completion data to send back to the shell.
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)
296class BashComplete(ShellComplete):
297 """Shell completion for Bash."""
299 name = "bash"
300 source_template = _SOURCE_BASH
302 def _check_version(self) -> None:
303 import subprocess
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())
310 if match is not None:
311 major, minor = match.groups()
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 )
325 def source(self) -> str:
326 self._check_version()
327 return super().source()
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]
334 try:
335 incomplete = cwords[cword]
336 except IndexError:
337 incomplete = ""
339 return args, incomplete
341 def format_completion(self, item: CompletionItem) -> str:
342 return f"{item.type},{item.value}"
345class ZshComplete(ShellComplete):
346 """Shell completion for Zsh."""
348 name = "zsh"
349 source_template = _SOURCE_ZSH
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]
356 try:
357 incomplete = cwords[cword]
358 except IndexError:
359 incomplete = ""
361 return args, incomplete
363 def format_completion(self, item: CompletionItem) -> str:
364 return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
367class FishComplete(ShellComplete):
368 """Shell completion for Fish."""
370 name = "fish"
371 source_template = _SOURCE_FISH
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:]
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()
383 return args, incomplete
385 def format_completion(self, item: CompletionItem) -> str:
386 if item.help:
387 return f"{item.type},{item.value}\t{item.help}"
389 return f"{item.type},{item.value}"
392_available_shells: t.Dict[str, t.Type[ShellComplete]] = {
393 "bash": BashComplete,
394 "fish": FishComplete,
395 "zsh": ZshComplete,
396}
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.
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
414 _available_shells[name] = cls
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``.
422 :param shell: Name the class is registered under.
423 """
424 return _available_shells.get(shell)
427def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool:
428 """Determine if the given parameter is an argument that can still
429 accept values.
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
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 )
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
456 c = value[0]
457 return c in ctx._opt_prefixes
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.
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
469 if param.is_flag or param.count:
470 return False
472 last_option = None
474 for index, arg in enumerate(reversed(args)):
475 if index + 1 > param.nargs:
476 break
478 if _start_of_option(ctx, arg):
479 last_option = arg
481 return last_option is not None and last_option in param.opts
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.
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
499 while args:
500 command = ctx.command
502 if isinstance(command, MultiCommand):
503 if not command.chain:
504 name, cmd, args = command.resolve_command(ctx, args)
506 if cmd is None:
507 return ctx
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)
515 if cmd is None:
516 return ctx
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
528 ctx = sub_ctx
529 args = [*sub_ctx.protected_args, *sub_ctx.args]
530 else:
531 break
533 return ctx
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.
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)
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
564 params = ctx.command.get_params(ctx)
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
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
578 # There were no unparsed arguments, the command may be a group that
579 # will provide command name completions.
580 return ctx.command, incomplete