18
18
from functools import total_ordering
19
19
from itertools import starmap
20
20
from string import Template
21
- from typing import Any , Dict , List , Sequence
21
+ from typing import Any , Dict , List , Mapping
22
22
from typing import Optional as Opt
23
- from typing import Union
23
+ from typing import Sequence , Tuple , Union
24
24
25
25
# version detector. Precedence: installed dist, git, 'UNKNOWN'
26
26
try :
32
32
__version__ = get_version (root = ".." , relative_to = __file__ )
33
33
except (ImportError , LookupError ):
34
34
__version__ = "UNKNOWN"
35
- __all__ = ["complete" , "add_argument_to" , "SUPPORTED_SHELLS" , "FILE" , "DIRECTORY" , "DIR" ]
35
+ __all__ = ["complete" , "add_argument_to" , "SUPPORTED_SHELLS" , "FILE" , "DIRECTORY" , "DIR" , "fglob" ]
36
36
log = logging .getLogger (__name__ )
37
37
38
38
SUPPORTED_SHELLS : List [str ] = []
51
51
)
52
52
53
53
54
+ def fglob (fglob : str ):
55
+ '''Glob files'''
56
+ return {
57
+ '__glob__' : fglob ,
58
+ 'bash' : '_shtab_compgen_files' , # Uses `__glob__` internally
59
+ 'zsh' : f"_files -g '{ fglob } '" ,
60
+ 'tcsh' : f'f:{ fglob } ' ,}
61
+
62
+
54
63
class _ShtabPrintCompletionAction (Action ):
55
64
pass
56
65
@@ -124,8 +133,26 @@ class Required:
124
133
125
134
126
135
def complete2pattern (opt_complete , shell : str , choice_type2fn ) -> str :
127
- return (opt_complete .get (shell , "" )
128
- if isinstance (opt_complete , dict ) else choice_type2fn [opt_complete ])
136
+ if isinstance (opt_complete , dict ):
137
+ return opt_complete .get (shell , "" )
138
+ else :
139
+ return choice_type2fn [opt_complete ]
140
+
141
+
142
+ def bash_complete2compgen (
143
+ opt_complete : Mapping [str , str ],
144
+ shell : str ,
145
+ choice_type2fn : Mapping [str , str ],
146
+ ) -> Tuple [str , Tuple [str ]]:
147
+ # Same inputs as `complete2pattern`
148
+ options = []
149
+ if isinstance (opt_complete , dict ):
150
+ if '__glob__' in opt_complete :
151
+ option_glob = opt_complete ['__glob__' ]
152
+ options .extend (['-X' , f'!{ option_glob } ' ])
153
+ return opt_complete .get (shell ), tuple (options )
154
+ else :
155
+ return choice_type2fn [opt_complete ], tuple (options )
129
156
130
157
131
158
def bash_listify (lst : Sequence [str ]) -> str :
@@ -194,8 +221,11 @@ def recurse(parser, prefix):
194
221
195
222
if hasattr (positional , "complete" ):
196
223
# shtab `.complete = ...` functions
197
- comp_pattern = complete2pattern (positional .complete , "bash" , choice_type2fn )
198
- compgens .append (f"{ prefix } _pos_{ i } _COMPGEN={ comp_pattern } " )
224
+ comp_gen , comp_genopts = bash_complete2compgen (positional .complete , "bash" ,
225
+ choice_type2fn )
226
+ compgens .extend ([
227
+ f"{ prefix } _pos_{ i } _COMPGEN={ comp_gen } " ,
228
+ f"{ prefix } _pos_{ i } _COMPGEN_options={ bash_listify (comp_genopts )} " ,])
199
229
200
230
if positional .choices :
201
231
# choices (including subparsers & shtab `.complete` functions)
@@ -207,7 +237,9 @@ def recurse(parser, prefix):
207
237
# append special completion type to `compgens`
208
238
# NOTE: overrides `.complete` attribute
209
239
log .debug (f"Choice.{ choice .type } :{ prefix } :{ positional .dest } " )
210
- compgens .append (f"{ prefix } _pos_{ i } _COMPGEN={ choice_type2fn [choice .type ]} " )
240
+ compgens .extend ([
241
+ f"{ prefix } _pos_{ i } _COMPGEN={ choice_type2fn [choice .type ]} " ,
242
+ f"{ prefix } _pos_{ i } _COMPGEN_options=()" ,])
211
243
elif isinstance (positional .choices , dict ):
212
244
# subparser, so append to list of subparsers & recurse
213
245
log .debug ("subcommand:%s" , choice )
@@ -237,7 +269,8 @@ def recurse(parser, prefix):
237
269
this_positional_choices .append (str (choice ))
238
270
239
271
if this_positional_choices :
240
- choices .append (f"{ prefix } _pos_{ i } _choices={ bash_listify (this_positional_choices )} " )
272
+ choices .append (
273
+ f"{ prefix } _pos_{ i } _choices={ bash_listify (this_positional_choices )} " )
241
274
242
275
# skip default `nargs` values
243
276
if positional .nargs not in (None , "1" , "?" ):
@@ -258,9 +291,12 @@ def recurse(parser, prefix):
258
291
for option_string in optional .option_strings :
259
292
if hasattr (optional , "complete" ):
260
293
# shtab `.complete = ...` functions
261
- comp_pattern_str = complete2pattern (optional .complete , "bash" , choice_type2fn )
262
- compgens .append (
263
- f"{ prefix } _{ wordify (option_string )} _COMPGEN={ comp_pattern_str } " )
294
+ comp_gen , comp_genopts = bash_complete2compgen (optional .complete , "bash" ,
295
+ choice_type2fn )
296
+ compgens .extend ([
297
+ f"{ prefix } _{ wordify (option_string )} _COMPGEN={ comp_gen } " ,
298
+ f"{ prefix } _{ wordify (option_string )} _COMPGEN_options={ bash_listify (comp_genopts )} " ,
299
+ ])
264
300
265
301
if optional .choices :
266
302
# choices (including shtab `.complete` functions)
@@ -270,15 +306,17 @@ def recurse(parser, prefix):
270
306
# NOTE: overrides `.complete` attribute
271
307
if isinstance (choice , Choice ):
272
308
log .debug (f"Choice.{ choice .type } :{ prefix } :{ optional .dest } " )
273
- func_str = choice_type2fn [ choice . type ]
274
- compgens . append (
275
- f"{ prefix } _{ wordify (option_string )} _COMPGEN= { func_str } " )
309
+ compgens . extend ([
310
+ f" { prefix } _ { wordify ( option_string ) } _COMPGEN= { choice_type2fn [ choice . type ] } " ,
311
+ f"{ prefix } _{ wordify (option_string )} _COMPGEN_options=()" ,] )
276
312
else :
277
313
# simple choice
278
314
this_optional_choices .append (str (choice ))
279
315
280
316
if this_optional_choices :
281
- choices .append (f"{ prefix } _{ wordify (option_string )} _choices={ bash_listify (this_optional_choices )} " )
317
+ choices .append (
318
+ f"{ prefix } _{ wordify (option_string )} _choices={ bash_listify (this_optional_choices )} "
319
+ )
282
320
283
321
# Check for nargs.
284
322
if optional .nargs is not None and optional .nargs != 1 :
@@ -328,7 +366,9 @@ def complete_bash(parser, root_prefix=None, preamble="", choice_functions=None):
328
366
${preamble}
329
367
# $1=COMP_WORDS[1]
330
368
_shtab_compgen_files() {
331
- compgen -f -- $1 # files
369
+ local cur="$1"
370
+ shift
371
+ compgen -f "$@" -- "$cur" # files
332
372
}
333
373
334
374
# $1=COMP_WORDS[1]
@@ -363,6 +403,13 @@ def complete_bash(parser, root_prefix=None, preamble="", choice_functions=None):
363
403
local current_action_compgen_var=${current_action}_COMPGEN
364
404
current_action_compgen="${!current_action_compgen_var-}"
365
405
406
+ if [ -z "$current_action_compgen" ]; then
407
+ current_action_compgen_options=()
408
+ else
409
+ local current_action_compgen_options_var="${current_action}_COMPGEN_options[@]"
410
+ current_action_compgen_options=("${!current_action_compgen_options_var}")
411
+ fi
412
+
366
413
local current_action_choices_var="${current_action}_choices[@]"
367
414
current_action_choices="${!current_action_choices_var-}"
368
415
@@ -393,6 +440,7 @@ def complete_bash(parser, root_prefix=None, preamble="", choice_functions=None):
393
440
local current_action_args_start_index
394
441
local current_action_choices
395
442
local current_action_compgen
443
+ local -a current_action_compgen_options
396
444
local current_action_is_positional
397
445
local current_action_nargs
398
446
local current_option_strings
@@ -450,11 +498,14 @@ def complete_bash(parser, root_prefix=None, preamble="", choice_functions=None):
450
498
# handle redirection operators
451
499
COMPREPLY=( $(compgen -f -- "${completing_word}") )
452
500
else
453
- # use choices & compgen
454
- local IFS=$'\\ n' # items may contain spaces, so delimit using newline
455
- COMPREPLY=( $([ -n "${current_action_compgen}" ] \\
456
- && "${current_action_compgen}" "${completing_word}") )
457
- unset IFS
501
+ COMPREPLY=()
502
+ # use compgen
503
+ if [ -n "${current_action_compgen}" ]; then
504
+ local IFS=$'\\ n' # items may contain spaces, so delimit using newline
505
+ COMPREPLY+=( $("${current_action_compgen}" "${current_action_compgen_options[@]}" "${completing_word}") )
506
+ unset IFS
507
+ fi
508
+ # use choices
458
509
COMPREPLY+=( $(compgen -W "${current_action_choices[*]}" -- "${completing_word}") )
459
510
fi
460
511
0 commit comments