comparison env/lib/python3.9/site-packages/click/_bashcomplete.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 import copy
2 import os
3 import re
4
5 from .core import Argument
6 from .core import MultiCommand
7 from .core import Option
8 from .parser import split_arg_string
9 from .types import Choice
10 from .utils import echo
11
12 try:
13 from collections import abc
14 except ImportError:
15 import collections as abc
16
17 WORDBREAK = "="
18
19 # Note, only BASH version 4.4 and later have the nosort option.
20 COMPLETION_SCRIPT_BASH = """
21 %(complete_func)s() {
22 local IFS=$'\n'
23 COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
24 COMP_CWORD=$COMP_CWORD \\
25 %(autocomplete_var)s=complete $1 ) )
26 return 0
27 }
28
29 %(complete_func)setup() {
30 local COMPLETION_OPTIONS=""
31 local BASH_VERSION_ARR=(${BASH_VERSION//./ })
32 # Only BASH version 4.4 and later have the nosort option.
33 if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] \
34 && [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then
35 COMPLETION_OPTIONS="-o nosort"
36 fi
37
38 complete $COMPLETION_OPTIONS -F %(complete_func)s %(script_names)s
39 }
40
41 %(complete_func)setup
42 """
43
44 COMPLETION_SCRIPT_ZSH = """
45 #compdef %(script_names)s
46
47 %(complete_func)s() {
48 local -a completions
49 local -a completions_with_descriptions
50 local -a response
51 (( ! $+commands[%(script_names)s] )) && return 1
52
53 response=("${(@f)$( env COMP_WORDS=\"${words[*]}\" \\
54 COMP_CWORD=$((CURRENT-1)) \\
55 %(autocomplete_var)s=\"complete_zsh\" \\
56 %(script_names)s )}")
57
58 for key descr in ${(kv)response}; do
59 if [[ "$descr" == "_" ]]; then
60 completions+=("$key")
61 else
62 completions_with_descriptions+=("$key":"$descr")
63 fi
64 done
65
66 if [ -n "$completions_with_descriptions" ]; then
67 _describe -V unsorted completions_with_descriptions -U
68 fi
69
70 if [ -n "$completions" ]; then
71 compadd -U -V unsorted -a completions
72 fi
73 compstate[insert]="automenu"
74 }
75
76 compdef %(complete_func)s %(script_names)s
77 """
78
79 COMPLETION_SCRIPT_FISH = (
80 "complete --no-files --command %(script_names)s --arguments"
81 ' "(env %(autocomplete_var)s=complete_fish'
82 " COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t)"
83 ' %(script_names)s)"'
84 )
85
86 _completion_scripts = {
87 "bash": COMPLETION_SCRIPT_BASH,
88 "zsh": COMPLETION_SCRIPT_ZSH,
89 "fish": COMPLETION_SCRIPT_FISH,
90 }
91
92 _invalid_ident_char_re = re.compile(r"[^a-zA-Z0-9_]")
93
94
95 def get_completion_script(prog_name, complete_var, shell):
96 cf_name = _invalid_ident_char_re.sub("", prog_name.replace("-", "_"))
97 script = _completion_scripts.get(shell, COMPLETION_SCRIPT_BASH)
98 return (
99 script
100 % {
101 "complete_func": "_{}_completion".format(cf_name),
102 "script_names": prog_name,
103 "autocomplete_var": complete_var,
104 }
105 ).strip() + ";"
106
107
108 def resolve_ctx(cli, prog_name, args):
109 """Parse into a hierarchy of contexts. Contexts are connected
110 through the parent variable.
111
112 :param cli: command definition
113 :param prog_name: the program that is running
114 :param args: full list of args
115 :return: the final context/command parsed
116 """
117 ctx = cli.make_context(prog_name, args, resilient_parsing=True)
118 args = ctx.protected_args + ctx.args
119 while args:
120 if isinstance(ctx.command, MultiCommand):
121 if not ctx.command.chain:
122 cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
123 if cmd is None:
124 return ctx
125 ctx = cmd.make_context(
126 cmd_name, args, parent=ctx, resilient_parsing=True
127 )
128 args = ctx.protected_args + ctx.args
129 else:
130 # Walk chained subcommand contexts saving the last one.
131 while args:
132 cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
133 if cmd is None:
134 return ctx
135 sub_ctx = cmd.make_context(
136 cmd_name,
137 args,
138 parent=ctx,
139 allow_extra_args=True,
140 allow_interspersed_args=False,
141 resilient_parsing=True,
142 )
143 args = sub_ctx.args
144 ctx = sub_ctx
145 args = sub_ctx.protected_args + sub_ctx.args
146 else:
147 break
148 return ctx
149
150
151 def start_of_option(param_str):
152 """
153 :param param_str: param_str to check
154 :return: whether or not this is the start of an option declaration
155 (i.e. starts "-" or "--")
156 """
157 return param_str and param_str[:1] == "-"
158
159
160 def is_incomplete_option(all_args, cmd_param):
161 """
162 :param all_args: the full original list of args supplied
163 :param cmd_param: the current command paramter
164 :return: whether or not the last option declaration (i.e. starts
165 "-" or "--") is incomplete and corresponds to this cmd_param. In
166 other words whether this cmd_param option can still accept
167 values
168 """
169 if not isinstance(cmd_param, Option):
170 return False
171 if cmd_param.is_flag:
172 return False
173 last_option = None
174 for index, arg_str in enumerate(
175 reversed([arg for arg in all_args if arg != WORDBREAK])
176 ):
177 if index + 1 > cmd_param.nargs:
178 break
179 if start_of_option(arg_str):
180 last_option = arg_str
181
182 return True if last_option and last_option in cmd_param.opts else False
183
184
185 def is_incomplete_argument(current_params, cmd_param):
186 """
187 :param current_params: the current params and values for this
188 argument as already entered
189 :param cmd_param: the current command parameter
190 :return: whether or not the last argument is incomplete and
191 corresponds to this cmd_param. In other words whether or not the
192 this cmd_param argument can still accept values
193 """
194 if not isinstance(cmd_param, Argument):
195 return False
196 current_param_values = current_params[cmd_param.name]
197 if current_param_values is None:
198 return True
199 if cmd_param.nargs == -1:
200 return True
201 if (
202 isinstance(current_param_values, abc.Iterable)
203 and cmd_param.nargs > 1
204 and len(current_param_values) < cmd_param.nargs
205 ):
206 return True
207 return False
208
209
210 def get_user_autocompletions(ctx, args, incomplete, cmd_param):
211 """
212 :param ctx: context associated with the parsed command
213 :param args: full list of args
214 :param incomplete: the incomplete text to autocomplete
215 :param cmd_param: command definition
216 :return: all the possible user-specified completions for the param
217 """
218 results = []
219 if isinstance(cmd_param.type, Choice):
220 # Choices don't support descriptions.
221 results = [
222 (c, None) for c in cmd_param.type.choices if str(c).startswith(incomplete)
223 ]
224 elif cmd_param.autocompletion is not None:
225 dynamic_completions = cmd_param.autocompletion(
226 ctx=ctx, args=args, incomplete=incomplete
227 )
228 results = [
229 c if isinstance(c, tuple) else (c, None) for c in dynamic_completions
230 ]
231 return results
232
233
234 def get_visible_commands_starting_with(ctx, starts_with):
235 """
236 :param ctx: context associated with the parsed command
237 :starts_with: string that visible commands must start with.
238 :return: all visible (not hidden) commands that start with starts_with.
239 """
240 for c in ctx.command.list_commands(ctx):
241 if c.startswith(starts_with):
242 command = ctx.command.get_command(ctx, c)
243 if not command.hidden:
244 yield command
245
246
247 def add_subcommand_completions(ctx, incomplete, completions_out):
248 # Add subcommand completions.
249 if isinstance(ctx.command, MultiCommand):
250 completions_out.extend(
251 [
252 (c.name, c.get_short_help_str())
253 for c in get_visible_commands_starting_with(ctx, incomplete)
254 ]
255 )
256
257 # Walk up the context list and add any other completion
258 # possibilities from chained commands
259 while ctx.parent is not None:
260 ctx = ctx.parent
261 if isinstance(ctx.command, MultiCommand) and ctx.command.chain:
262 remaining_commands = [
263 c
264 for c in get_visible_commands_starting_with(ctx, incomplete)
265 if c.name not in ctx.protected_args
266 ]
267 completions_out.extend(
268 [(c.name, c.get_short_help_str()) for c in remaining_commands]
269 )
270
271
272 def get_choices(cli, prog_name, args, incomplete):
273 """
274 :param cli: command definition
275 :param prog_name: the program that is running
276 :param args: full list of args
277 :param incomplete: the incomplete text to autocomplete
278 :return: all the possible completions for the incomplete
279 """
280 all_args = copy.deepcopy(args)
281
282 ctx = resolve_ctx(cli, prog_name, args)
283 if ctx is None:
284 return []
285
286 has_double_dash = "--" in all_args
287
288 # In newer versions of bash long opts with '='s are partitioned, but
289 # it's easier to parse without the '='
290 if start_of_option(incomplete) and WORDBREAK in incomplete:
291 partition_incomplete = incomplete.partition(WORDBREAK)
292 all_args.append(partition_incomplete[0])
293 incomplete = partition_incomplete[2]
294 elif incomplete == WORDBREAK:
295 incomplete = ""
296
297 completions = []
298 if not has_double_dash and start_of_option(incomplete):
299 # completions for partial options
300 for param in ctx.command.params:
301 if isinstance(param, Option) and not param.hidden:
302 param_opts = [
303 param_opt
304 for param_opt in param.opts + param.secondary_opts
305 if param_opt not in all_args or param.multiple
306 ]
307 completions.extend(
308 [(o, param.help) for o in param_opts if o.startswith(incomplete)]
309 )
310 return completions
311 # completion for option values from user supplied values
312 for param in ctx.command.params:
313 if is_incomplete_option(all_args, param):
314 return get_user_autocompletions(ctx, all_args, incomplete, param)
315 # completion for argument values from user supplied values
316 for param in ctx.command.params:
317 if is_incomplete_argument(ctx.params, param):
318 return get_user_autocompletions(ctx, all_args, incomplete, param)
319
320 add_subcommand_completions(ctx, incomplete, completions)
321 # Sort before returning so that proper ordering can be enforced in custom types.
322 return sorted(completions)
323
324
325 def do_complete(cli, prog_name, include_descriptions):
326 cwords = split_arg_string(os.environ["COMP_WORDS"])
327 cword = int(os.environ["COMP_CWORD"])
328 args = cwords[1:cword]
329 try:
330 incomplete = cwords[cword]
331 except IndexError:
332 incomplete = ""
333
334 for item in get_choices(cli, prog_name, args, incomplete):
335 echo(item[0])
336 if include_descriptions:
337 # ZSH has trouble dealing with empty array parameters when
338 # returned from commands, use '_' to indicate no description
339 # is present.
340 echo(item[1] if item[1] else "_")
341
342 return True
343
344
345 def do_complete_fish(cli, prog_name):
346 cwords = split_arg_string(os.environ["COMP_WORDS"])
347 incomplete = os.environ["COMP_CWORD"]
348 args = cwords[1:]
349
350 for item in get_choices(cli, prog_name, args, incomplete):
351 if item[1]:
352 echo("{arg}\t{desc}".format(arg=item[0], desc=item[1]))
353 else:
354 echo(item[0])
355
356 return True
357
358
359 def bashcomplete(cli, prog_name, complete_var, complete_instr):
360 if "_" in complete_instr:
361 command, shell = complete_instr.split("_", 1)
362 else:
363 command = complete_instr
364 shell = "bash"
365
366 if command == "source":
367 echo(get_completion_script(prog_name, complete_var, shell))
368 return True
369 elif command == "complete":
370 if shell == "fish":
371 return do_complete_fish(cli, prog_name)
372 elif shell in {"bash", "zsh"}:
373 return do_complete(cli, prog_name, shell == "zsh")
374
375 return False