Coverage for typer / _click / shell_completion.py: 100%
119 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
1import re 1aebfcdg
2from abc import ABC, abstractmethod 1aebfcdg
3from collections.abc import MutableMapping 1aebfcdg
4from typing import Any, ClassVar, TypeVar 1aebfcdg
6from .core import Command, Context, Parameter, ParameterSource 1aebfcdg
9class CompletionItem: 1aebfcdg
10 """Represents a completion value and metadata about the value. The
11 default metadata is ``type`` to indicate special shell handling,
12 and ``help`` if a shell supports showing a help string next to the
13 value.
15 Arbitrary parameters can be passed when creating the object, and
16 accessed using ``item.attr``. If an attribute wasn't passed,
17 accessing it returns ``None``.
18 """
20 __slots__ = ("value", "type", "help", "_info") 1aebfcdg
22 def __init__( 1aebfcdg
23 self,
24 value: Any,
25 type: str = "plain",
26 help: str | None = None,
27 **kwargs: Any,
28 ) -> None:
29 self.value: Any = value 1aebfcdg
30 self.type: str = type 1aebfcdg
31 self.help: str | None = help 1aebfcdg
32 self._info = kwargs 1aebfcdg
34 def __getattr__(self, name: str) -> Any: 1aebfcdg
35 return self._info.get(name) 1aebfcdg
38class ShellComplete(ABC): 1aebfcdg
39 """Base class for providing shell completion support. A subclass for
40 a given shell will override attributes and methods to implement the
41 completion instructions (``source`` and ``complete``).
42 """
44 name: ClassVar[str] 1abcd
45 """Name to register the shell as with `add_completion_class`. 1aebfcdg
46 This is used in completion instructions (``{name}_source`` and
47 ``{name}_complete``).
48 """
50 source_template: ClassVar[str] 1abcd
51 """Completion script template formatted by `source`. This must 1aebfcdg
52 be provided by subclasses.
53 """
55 def __init__( 1aebfcdg
56 self,
57 cli: Command,
58 ctx_args: MutableMapping[str, Any],
59 prog_name: str,
60 complete_var: str,
61 ) -> None:
62 self.cli = cli 1aebfcdg
63 self.ctx_args = ctx_args 1aebfcdg
64 self.prog_name = prog_name 1aebfcdg
65 self.complete_var = complete_var 1aebfcdg
67 @property 1aebfcdg
68 def func_name(self) -> str: 1aebfcdg
69 """The name of the shell function defined by the completion
70 script.
71 """
72 safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII) 1aebfcdg
73 return f"_{safe_name}_completion" 1aebfcdg
75 @abstractmethod 1aebfcdg
76 def source_vars(self) -> dict[str, Any]: 1aebfcdg
77 """Vars for formatting `source_template`."""
78 pass # pragma: no cover
80 def source(self) -> str: 1aebfcdg
81 """Produce the shell script that defines the completion
82 function. By default this ``%``-style formats
83 `source_template` with the dict returned by `source_vars`.
84 """
85 return self.source_template % self.source_vars() 1aebfcdg
87 @abstractmethod 1aebfcdg
88 def get_completion_args(self) -> tuple[list[str], str]: 1aebfcdg
89 """Use the env vars defined by the shell script to return a
90 tuple of ``args, incomplete``. This must be implemented by
91 subclasses.
92 """
93 pass # pragma: no cover
95 def get_completions(self, args: list[str], incomplete: str) -> list[CompletionItem]: 1aebfcdg
96 """Determine the context and last complete command or parameter
97 from the complete args. Call that object's ``shell_complete``
98 method to get the completions for the incomplete value.
99 """
100 ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) 1aebfcdg
101 obj, incomplete = _resolve_incomplete(ctx, args, incomplete) 1aebfcdg
102 return obj.shell_complete(ctx, incomplete) 1aebfcdg
104 @abstractmethod 1aebfcdg
105 def format_completion(self, item: CompletionItem) -> str: 1aebfcdg
106 """Format a completion item into the form recognized by the
107 shell script. This must be implemented by subclasses.
108 """
109 pass # pragma: no cover
111 def complete(self) -> str: 1aebfcdg
112 """Produce the completion data to send back to the shell.
114 By default this calls `get_completion_args`, gets the
115 completions, then calls `format_completion` for each
116 completion.
117 """
118 args, incomplete = self.get_completion_args() 1aebfcdg
119 completions = self.get_completions(args, incomplete) 1aebfcdg
120 out = [self.format_completion(item) for item in completions] 1aebfcdg
121 return "\n".join(out) 1aebfcdg
124ShellCompleteType = TypeVar("ShellCompleteType", bound="type[ShellComplete]") 1aebfcdg
127_available_shells: dict[str, type[ShellComplete]] = {} 1aebfcdg
130def add_completion_class(cls: ShellCompleteType, name: str) -> ShellCompleteType: 1aebfcdg
131 """Register a `ShellComplete` subclass under the given name.
132 The name will be provided by the completion instruction environment
133 variable during completion.
134 """
135 _available_shells[name] = cls 1aebfcdg
137 return cls 1aebfcdg
140def get_completion_class(shell: str) -> type[ShellComplete] | None: 1aebfcdg
141 """Look up a registered `ShellComplete` subclass by the name
142 provided by the completion instruction environment variable. If the
143 name isn't registered, returns ``None``.
144 """
145 return _available_shells.get(shell) 1aebfcdg
148def split_arg_string(string: str) -> list[str]: 1aebfcdg
149 """Split an argument string as with `shlex.split`, but don't
150 fail if the string is incomplete. Ignores a missing closing quote or
151 incomplete escape sequence and uses the partial token as-is.
152 """
153 import shlex 1aebfcdg
155 lex = shlex.shlex(string, posix=True) 1aebfcdg
156 lex.whitespace_split = True 1aebfcdg
157 lex.commenters = "" 1aebfcdg
158 out = [] 1aebfcdg
160 try: 1aebfcdg
161 for token in lex: 1aebfcdg
162 out.append(token) 1aebfcdg
163 except ValueError: # pragma: no cover
164 # Raised when end-of-string is reached in an invalid state. Use
165 # the partial token as-is. The quote or escape character is in
166 # lex.state, not lex.token.
167 out.append(lex.token)
169 return out 1aebfcdg
172def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: 1aebfcdg
173 """Determine if the given parameter is an argument that can still
174 accept values.
175 """
176 # avoid circular imports
177 from ..core import TyperArgument 1aebfcdg
179 if not isinstance(param, TyperArgument): 1aebfcdg
180 return False 1aebfcdg
182 assert param.name is not None 1aebfcdg
183 # Will be None if expose_value is False.
184 value = ctx.params.get(param.name) 1aebfcdg
185 return ( 1aebfcdg
186 param.nargs == -1
187 or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE
188 or (
189 param.nargs > 1
190 and isinstance(value, (tuple, list))
191 and len(value) < param.nargs
192 )
193 )
196def _start_of_option(ctx: Context, value: str) -> bool: 1aebfcdg
197 """Check if the value looks like the start of an option."""
198 if not value: 1aebfcdg
199 return False 1aebfcdg
201 c = value[0] 1aebfcdg
202 return c in ctx._opt_prefixes 1aebfcdg
205def _is_incomplete_option(ctx: Context, args: list[str], param: Parameter) -> bool: 1aebfcdg
206 """Determine if the given parameter is an option that needs a value."""
207 # avoid circular imports
208 from ..core import TyperOption 1aebfcdg
210 if not isinstance(param, TyperOption): 1aebfcdg
211 return False 1aebfcdg
213 if param.is_flag or param.count: 1aebfcdg
214 return False 1aebfcdg
216 last_option = None 1aebfcdg
218 for index, arg in enumerate(reversed(args)): 1aebfcdg
219 if index + 1 > param.nargs: 1aebfcdg
220 break 1aebfcdg
222 if _start_of_option(ctx, arg): 1aebfcdg
223 last_option = arg 1aebfcdg
224 break 1aebfcdg
226 return last_option is not None and last_option in param.opts 1aebfcdg
229def _resolve_context( 1aebfcdg
230 cli: Command,
231 ctx_args: MutableMapping[str, Any],
232 prog_name: str,
233 args: list[str],
234) -> Context:
235 """Produce the context hierarchy starting with the command and
236 traversing the complete arguments. This only follows the commands,
237 it doesn't trigger input prompts or callbacks.
238 """
239 # avoid circular imports
240 from ..core import TyperGroup 1aebfcdg
242 ctx_args["resilient_parsing"] = True 1aebfcdg
243 with cli.make_context(prog_name, args.copy(), **ctx_args) as ctx: 1aebfcdg
244 args = ctx._protected_args + ctx.args 1aebfcdg
246 while args: 1aebfcdg
247 command = ctx.command 1aebfcdg
249 if isinstance(command, TyperGroup): 1aebfcdg
250 # if not command.chain:
251 name, cmd, args = command.resolve_command(ctx, args) 1aebfcdg
253 if cmd is None: 1aebfcdg
254 return ctx 1aebfcdg
256 with cmd.make_context( 1aebfcdg
257 name, args, parent=ctx, resilient_parsing=True
258 ) as sub_ctx:
259 ctx = sub_ctx 1aebfcdg
260 args = ctx._protected_args + ctx.args 1aebfcdg
261 else: # pragma: no cover
262 break
264 return ctx 1aebfcdg
267def _resolve_incomplete( 1aebfcdg
268 ctx: Context, args: list[str], incomplete: str
269) -> tuple[Command | Parameter, str]:
270 """Find the Click object that will handle the completion of the
271 incomplete value. Return the object and the incomplete value.
272 """
273 # Different shells treat an "=" between a long option name and
274 # value differently. Might keep the value joined, return the "="
275 # as a separate item, or return the split name and value. Always
276 # split and discard the "=" to make completion easier.
277 if incomplete == "=": 1aebfcdg
278 incomplete = "" 1aebfcdg
279 elif "=" in incomplete and _start_of_option(ctx, incomplete): 1aebfcdg
280 name, _, incomplete = incomplete.partition("=") 1aebfcdg
281 args.append(name) 1aebfcdg
283 # The "--" marker tells Click to stop treating values as options
284 # even if they start with the option character. If it hasn't been
285 # given and the incomplete arg looks like an option, the current
286 # command will provide option name completions.
287 if "--" not in args and _start_of_option(ctx, incomplete): 1aebfcdg
288 return ctx.command, incomplete 1aebfcdg
290 params = ctx.command.get_params(ctx) 1aebfcdg
292 # If the last complete arg is an option name with an incomplete
293 # value, the option will provide value completions.
294 for param in params: 1aebfcdg
295 if _is_incomplete_option(ctx, args, param): 1aebfcdg
296 return param, incomplete 1aebfcdg
298 # It's not an option name or value. The first argument without a
299 # parsed value will provide value completions.
300 for param in params: 1aebfcdg
301 if _is_incomplete_argument(ctx, param): 1aebfcdg
302 return param, incomplete 1aebfcdg
304 # There were no unparsed arguments, the command may be a group that
305 # will provide command name completions.
306 return ctx.command, incomplete 1aebfcdg