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

1import re 1aebfcdg

2from abc import ABC, abstractmethod 1aebfcdg

3from collections.abc import MutableMapping 1aebfcdg

4from typing import Any, ClassVar, TypeVar 1aebfcdg

5 

6from .core import Command, Context, Parameter, ParameterSource 1aebfcdg

7 

8 

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. 

14 

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 """ 

19 

20 __slots__ = ("value", "type", "help", "_info") 1aebfcdg

21 

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

33 

34 def __getattr__(self, name: str) -> Any: 1aebfcdg

35 return self._info.get(name) 1aebfcdg

36 

37 

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 """ 

43 

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 """ 

49 

50 source_template: ClassVar[str] 1abcd

51 """Completion script template formatted by `source`. This must 1aebfcdg

52 be provided by subclasses. 

53 """ 

54 

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

66 

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

74 

75 @abstractmethod 1aebfcdg

76 def source_vars(self) -> dict[str, Any]: 1aebfcdg

77 """Vars for formatting `source_template`.""" 

78 pass # pragma: no cover 

79 

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

86 

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 

94 

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

103 

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 

110 

111 def complete(self) -> str: 1aebfcdg

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

113 

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

122 

123 

124ShellCompleteType = TypeVar("ShellCompleteType", bound="type[ShellComplete]") 1aebfcdg

125 

126 

127_available_shells: dict[str, type[ShellComplete]] = {} 1aebfcdg

128 

129 

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

136 

137 return cls 1aebfcdg

138 

139 

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

146 

147 

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

154 

155 lex = shlex.shlex(string, posix=True) 1aebfcdg

156 lex.whitespace_split = True 1aebfcdg

157 lex.commenters = "" 1aebfcdg

158 out = [] 1aebfcdg

159 

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) 

168 

169 return out 1aebfcdg

170 

171 

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

178 

179 if not isinstance(param, TyperArgument): 1aebfcdg

180 return False 1aebfcdg

181 

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 ) 

194 

195 

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

200 

201 c = value[0] 1aebfcdg

202 return c in ctx._opt_prefixes 1aebfcdg

203 

204 

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

209 

210 if not isinstance(param, TyperOption): 1aebfcdg

211 return False 1aebfcdg

212 

213 if param.is_flag or param.count: 1aebfcdg

214 return False 1aebfcdg

215 

216 last_option = None 1aebfcdg

217 

218 for index, arg in enumerate(reversed(args)): 1aebfcdg

219 if index + 1 > param.nargs: 1aebfcdg

220 break 1aebfcdg

221 

222 if _start_of_option(ctx, arg): 1aebfcdg

223 last_option = arg 1aebfcdg

224 break 1aebfcdg

225 

226 return last_option is not None and last_option in param.opts 1aebfcdg

227 

228 

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

241 

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

245 

246 while args: 1aebfcdg

247 command = ctx.command 1aebfcdg

248 

249 if isinstance(command, TyperGroup): 1aebfcdg

250 # if not command.chain: 

251 name, cmd, args = command.resolve_command(ctx, args) 1aebfcdg

252 

253 if cmd is None: 1aebfcdg

254 return ctx 1aebfcdg

255 

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 

263 

264 return ctx 1aebfcdg

265 

266 

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

282 

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

289 

290 params = ctx.command.get_params(ctx) 1aebfcdg

291 

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

297 

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

303 

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