Skip to content

Commit cb24709

Browse files
authored
Merge pull request #993 from akinomyoga/_comp_compgen-doc
doc(api-and-naming): add rules for xfuncs and generators
2 parents 0cd3bce + 39cc200 commit cb24709

File tree

5 files changed

+185
-30
lines changed

5 files changed

+185
-30
lines changed

bash_completion

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,11 @@ _comp_split()
371371
done
372372
shift "$((OPTIND - 1))"
373373
if (($# != 2)); then
374-
printf '%s\n' "bash_completion: $FUNCNAME: unexpected number of arguments." >&2
374+
printf '%s\n' "bash_completion: $FUNCNAME: unexpected number of arguments" >&2
375375
printf '%s\n' "usage: $FUNCNAME [-a] [-F SEP] ARRAY_NAME TEXT" >&2
376376
return 2
377377
elif [[ $1 == @(*[^_a-zA-Z0-9]*|[0-9]*|''|_*|IFS|OPTIND|OPTARG|OPTERR) ]]; then
378-
printf '%s\n' "bash_completion: $FUNCNAME: invalid array name '$1'." >&2
378+
printf '%s\n' "bash_completion: $FUNCNAME: invalid array name '$1'" >&2
379379
return 2
380380
fi
381381

@@ -422,26 +422,27 @@ _comp_compgen__error_fallback()
422422
# The array name should not start with an underscores "_", which is
423423
# internally used. The array name should not be either "IFS" or
424424
# "OPT{IND,ARG,ERR}".
425-
# -F sep Set a set of separator characters (used as IFS in evaluating
426-
# `compgen'). The default separator is $' \t\n'. Note that this is
427-
# not the set of separators to delimit output of `compgen', but the
428-
# separators in evaluating the expansions of `-W '...'`, etc. The
429-
# delimiter of the output of `compgen` is always a newline.
430-
# -l The same as -F $'\n'. Use lines as words in evaluating compgen.
431425
# -c cur Set a word used as a prefix to filter the completions. The default
432426
# is ${cur-}.
433427
# -R The same as -c ''. Use raw outputs without filtering.
434428
# -C dir Evaluate compgen/generator in the specified directory.
435429
# @var[in,opt] cur Used as the default value of a prefix to filter the
436430
# completions.
437431
#
438-
# Usage #1: _comp_compgen [-alR|-F sep|-v arr|-c cur] -- options...
432+
# Usage #1: _comp_compgen [-alR|-F sep|-v arr|-c cur|-C dir] -- options...
439433
# Call `compgen` with the specified arguments and store the results in the
440434
# specified array. This function essentially performs arr=($(compgen args...))
441435
# but properly handles shell options, IFS, etc. using _comp_split. This
442436
# function is equivalent to `_comp_split [-a] -l arr "$(IFS=sep; compgen
443437
# args... -- cur)"`, but this pattern is frequent in the codebase and is good
444438
# to separate out as a function for the possible future implementation change.
439+
# OPTIONS
440+
# -F sep Set a set of separator characters (used as IFS in evaluating
441+
# `compgen'). The default separator is $' \t\n'. Note that this is
442+
# not the set of separators to delimit output of `compgen', but the
443+
# separators in evaluating the expansions of `-W '...'`, etc. The
444+
# delimiter of the output of `compgen` is always a newline.
445+
# -l The same as -F $'\n'. Use lines as words in evaluating compgen.
445446
# @param $1... options Arguments that are passed to compgen (if $1 starts with
446447
# a hyphen `-`).
447448
#
@@ -453,12 +454,17 @@ _comp_compgen__error_fallback()
453454
#
454455
# Note: The array option `-V arr` in bash >= 5.3 should be instead specified
455456
# as `-v arr` as a part of the `_comp_compgen` options.
457+
# @return True (0) if at least one completion is generated, False (1) if no
458+
# completion is generated, or 2 with an incorrect usage.
456459
#
457-
# Usage #2: _comp_compgen [-alR|-v arr|-c cur] name args...
460+
# Usage #2: _comp_compgen [-aR|-v arr|-c cur|-C dir|-i cmd|-x cmd] name args...
458461
# Call the generator `_comp_compgen_NAME ARGS...` with the specified options.
459462
# This provides a common interface to call the functions `_comp_compgen_NAME`,
460463
# which produce completion candidates, with custom options [-alR|-v arr|-c
461464
# cur]. The option `-F sep` is not used with this usage.
465+
# OPTIONS
466+
# -x cmd Call exported generator `_comp_xfunc_CMD_compgen_NAME`
467+
# -i cmd Call internal generator `_comp_cmd_CMD__compgen_NAME`
462468
# @param $1... name args Calls the function _comp_compgen_NAME with the
463469
# specified ARGS (if $1 does not start with a hyphen `-`). The options
464470
# [-alR|-v arr|-c cur] are inherited by the child calls of `_comp_compgen`
@@ -470,6 +476,7 @@ _comp_compgen__error_fallback()
470476
# These variables are internally used to pass the effect of the options
471477
# [-alR|-v arr|-c cur] to the child calls of `_comp_compgen` in
472478
# `_comp_compgen_NAME`.
479+
# @return Exit status of the generator.
473480
#
474481
# @remarks When no options are supplied to _comp_compgen, `_comp_compgen NAME
475482
# args` is equivalent to the direct call `_comp_compgen_NAME args`. As the
@@ -502,35 +509,51 @@ _comp_compgen()
502509
local _append=${_comp_compgen__append-}
503510
local _var=${_comp_compgen__var-COMPREPLY}
504511
local _cur=${_comp_compgen__cur-${cur-}}
505-
local _ifs=$' \t\n' _dir=""
512+
local _dir=""
513+
local _ifs=$' \t\n' _has_ifs=""
514+
local _icmd="" _xcmd=""
506515

507516
local _old_nocasematch=""
508517
if shopt -q nocasematch; then
509518
_old_nocasematch=set
510519
shopt -u nocasematch
511520
fi
512521
local OPTIND=1 OPTARG="" OPTERR=0 _opt
513-
while getopts ':alF:v:Rc:C:' _opt "$@"; do
522+
while getopts ':av:Rc:C:lF:i:x:' _opt "$@"; do
514523
case $_opt in
515524
a) _append=set ;;
516525
v)
517526
if [[ $OPTARG == @(*[^_a-zA-Z0-9]*|[0-9]*|''|_*|IFS|OPTIND|OPTARG|OPTERR) ]]; then
518-
printf 'bash_completion: %s: -v: invalid array name `%s'\''.\n' "$FUNCNAME" "$OPTARG" >&2
527+
printf 'bash_completion: %s: -v: invalid array name `%s'\''\n' "$FUNCNAME" "$OPTARG" >&2
519528
return 2
520529
fi
521530
_var=$OPTARG
522531
;;
523-
l) _ifs=$'\n' ;;
524-
F) _ifs=$OPTARG ;;
525532
c) _cur=$OPTARG ;;
526533
R) _cur="" ;;
527534
C)
528535
if [[ ! $OPTARG ]]; then
529-
printf 'bash_completion: %s: -C: invalid directory name `%s'\''.\n' "$FUNCNAME" "$OPTARG" >&2
536+
printf 'bash_completion: %s: -C: invalid directory name `%s'\''\n' "$FUNCNAME" "$OPTARG" >&2
530537
return 2
531538
fi
532539
_dir=$OPTARG
533540
;;
541+
l) _has_ifs=set _ifs=$'\n' ;;
542+
F) _has_ifs=set _ifs=$OPTARG ;;
543+
[ix])
544+
if [[ ! $OPTARG ]]; then
545+
printf 'bash_completion: %s: -%s: invalid command name `%s'\''\n' "$FUNCNAME" "$_opt" "$OPTARG" >&2
546+
return 2
547+
elif [[ $_icmd ]]; then
548+
printf 'bash_completion: %s: -%s: `-i %s'\'' is already specified\n' "$FUNCNAME" "$_opt" "$_icmd" >&2
549+
return 2
550+
elif [[ $_xcmd ]]; then
551+
printf 'bash_completion: %s: -%s: `-x %s'\'' is already specified\n' "$FUNCNAME" "$_opt" "$_xcmd" >&2
552+
return 2
553+
fi
554+
;;&
555+
i) _icmd=$OPTARG ;;
556+
x) _xcmd=$OPTARG ;;
534557
*)
535558
printf 'bash_completion: %s: usage error\n' "$FUNCNAME" >&2
536559
return 2
@@ -540,15 +563,28 @@ _comp_compgen()
540563
[[ $_old_nocasematch ]] && shopt -s nocasematch
541564
shift "$((OPTIND - 1))"
542565
if (($# == 0)); then
543-
printf 'bash_completion: %s: unexpected number of arguments.\n' "$FUNCNAME" >&2
566+
printf 'bash_completion: %s: unexpected number of arguments\n' "$FUNCNAME" >&2
544567
printf 'usage: %s [-alR|-F SEP|-v ARR|-c CUR] -- ARGS...' "$FUNCNAME" >&2
545568
return 2
546569
fi
547570

548571
if [[ $1 != -* ]]; then
549572
# usage: _comp_compgen [options] NAME args
550-
if ! declare -F "_comp_compgen_$1" &>/dev/null; then
551-
printf 'bash_completion: %s: unrecognized category `%s'\'' (function _comp_compgen_%s not found).\n' "$FUNCNAME" "$1" "$1" >&2
573+
if [[ $_has_ifs ]]; then
574+
printf 'bash_completion: %s: `-l'\'' and `-F sep'\'' are not supported for generators\n' "$FUNCNAME" >&2
575+
return 2
576+
fi
577+
578+
local -a _generator
579+
if [[ $_icmd ]]; then
580+
_generator=("_comp_cmd_${_icmd//[^a-zA-Z0-9_]/_}__compgen_$1")
581+
elif [[ $_xcmd ]]; then
582+
_generator=(_comp_xfunc "$_xcmd" "compgen_$1")
583+
else
584+
_generator=("_comp_compgen_$1")
585+
fi
586+
if ! declare -F "${_generator[0]}" &>/dev/null; then
587+
printf 'bash_completion: %s: unrecognized generator `%s'\'' (function %s not found)\n' "$FUNCNAME" "$1" "${_generator[0]}" >&2
552588
return 2
553589
fi
554590

@@ -570,7 +606,7 @@ _comp_compgen()
570606
# Note: we use $1 as a part of a function name, and we use $2... as
571607
# arguments to the function if any.
572608
# shellcheck disable=SC2145
573-
_comp_compgen_"$@"
609+
"${_generator[@]}" "${@:2}"
574610
local _status=$?
575611

576612
# Go back to the original directory.
@@ -584,6 +620,10 @@ _comp_compgen()
584620
fi
585621

586622
# usage: _comp_compgen [options] -- [compgen_options]
623+
if [[ $_icmd || $_xcmd ]]; then
624+
printf 'bash_completion: %s: generator name is unspecified for `%s'\''\n' "$FUNCNAME" "${_icmd:+-i $_icmd}${_xcmd:+x $_xcmd}" >&2
625+
return 2
626+
fi
587627

588628
# Note: $* in the below checks would be affected by uncontrolled IFS in
589629
# bash >= 5.0, so we need to set IFS to the normal value. The behavior in
@@ -596,7 +636,7 @@ _comp_compgen()
596636
# "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9]
597637
# separately.
598638
if [[ $* == *\$[0-9]* || $* == *\$\{[0-9]* ]]; then
599-
printf 'bash_completion: %s: positional parameter $1, $2, ... do not work inside this function.\n' "$FUNCNAME" >&2
639+
printf 'bash_completion: %s: positional parameter $1, $2, ... do not work inside this function\n' "$FUNCNAME" >&2
600640
return 2
601641
fi
602642

doc/api-and-naming.md

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,17 @@ deprecated in.
4141
Due to its nature, bash-completion adds a number of functions and variables in
4242
the shell's environment.
4343

44-
| | `bash_completion` | `completions/*` |
45-
|:------------------------------------|:--------------------|:---------------------------------------------------------------------------|
46-
| public configuration variables | `BASH_COMPLETION_*` | `BASH_COMPLETION_CMD_${Command^^}_${Config^^}` |
47-
| private non-local variables | `_comp__*` | `_comp_cmd_${Command}__${Data}` |
48-
| private non-local mutable variables | `_comp__*_mut_*` | `_comp_cmd_${Command}__mut_${Data}` |
49-
| exporter function local variables | `_*` (not `_comp*`) | `_*` (not `_comp*`) |
50-
| public/exported functions | `_comp_*` | `_comp_cmd_${Command}` (functions for `complete -F`) |
51-
| | | `_comp_xfunc_${Command}_${Utility}` (functions for use with `_comp_xfunc`) |
52-
| private/internal functions | `_comp__*` | `_comp_cmd_${Command}__${Utility}` (utility functions) |
44+
| | `bash_completion` | `completions/*` |
45+
|:------------------------------------|:------------------------|:--------------------------------------------------------------------------------------|
46+
| public configuration variables | `BASH_COMPLETION_*` | `BASH_COMPLETION_CMD_${Command^^}_${Config^^}` |
47+
| private non-local variables | `_comp__*` | `_comp_cmd_${Command}__${Data}` |
48+
| private non-local mutable variables | `_comp__*_mut_*` | `_comp_cmd_${Command}__mut_${Data}` |
49+
| exporter function local variables | `_*` (not `_comp*`) | `_*` (not `_comp*`) |
50+
| public/exported functions | `_comp_*` | `_comp_cmd_${Command}` (functions for `complete -F`) |
51+
| | | `_comp_xfunc_${Command}_${Utility}` (functions for use with `_comp_xfunc`) |
52+
| | `_comp_compgen_${Name}` | `_comp_xfunc_${Command}_compgen_${Name}` (generators for use with `_comp_compgen -x`) |
53+
| private/internal functions | `_comp__*` | `_comp_cmd_${Command}__${Utility}` (utility functions) |
54+
| | | `_comp_cmd_${Command}__compgen_${Name}` (generators for use with `_comp_compgen -i`) |
5355

5456
`${Command}` refers to a command name (with characters not allowed in POSIX
5557
function or variable names replaced by an underscore), `${Config}` the name of
@@ -110,3 +112,68 @@ distinctive and clash free enough.
110112
It is known that a lot of functions and variables in the tree do not follow
111113
these naming rules yet. Things introduced after version 2.11 should, and we are
112114
evaluating our options for handling older ones.
115+
116+
## Exported functions (xfunc)
117+
118+
Exported functions (xfunc) are the functions defined in completion files for
119+
specific commands but exposed to other completion files. The xfuncs have the
120+
name `_comp_xfunc_CMD_NAME` where `CMD` is the name of the associated command,
121+
and `NAME` is the name of the utility. The other functions defined in specific
122+
completion files are considered private and should not be called outside the
123+
file.
124+
125+
The xfuncs can be called by `_comp_xfunc CMD NAME ARGS` from external files.
126+
The xfuncs are supposed to be directly called as `_comp_xfunc_CMD_NAME ARGS`
127+
from the same file where they are defined, or if they wrap a `_comp_cmd_NAME__*`
128+
function, that one should be called directly instead.
129+
130+
Note: The name `xfunc` was initially the name of a utility function, `_xfunc`,
131+
to call "eXternal FUNCtions" that are defined in other completion files. The
132+
concept is later extended to also mean "eXported".
133+
134+
## Generator functions
135+
136+
The generator functions, which have names of the form `_comp_compgen_NAME`, are
137+
used to generate completion candidates. A generator function is supposed to be
138+
called by `_comp_compgen [OPTS] NAME ARGS` where `OPTS = -aRl|-v var|-c cur|-C
139+
dir|-F sep` are the options to modify the behavior (see the code comment of
140+
`_comp_compgen` for details). When there are no `opts`, the generator function
141+
is supposed to be directly called as `_comp_compgen_NAME ARGS`. The result is
142+
stored in the target variable (which is `COMPREPLY` by default but can be
143+
specified by `-v var` in `OPTS`).
144+
145+
### Implementing a generator function
146+
147+
To implement a generator function, one should generate completion candidates by
148+
calling `_comp_compgen` or other generators. To avoid conflicts with the
149+
options specified to `_comp_compgen`, one should not directly modify or
150+
reference the target variable. When post-filtering is needed, store them in
151+
local arrays, filter them, and finally append them by `_comp_compgen -- -W
152+
"${arr[@]}"`.
153+
154+
A generator function should replace the existing content of the variable by
155+
default. When the appending behavior is favored, the caller should specify it
156+
through `_comp_compgen -a NAME`. The generator function do not need to process
157+
it because internal `_comp_compgen` calls automatically reflects the option
158+
`-a` specified to the outer calls of `_comp_compgen`.
159+
160+
The exit status is implementation-defined.
161+
162+
- The `_comp_compgen -- COMPGEN_ARGS` returns whether there is at least one
163+
completion. This is useful when one wants to reuse the array content with
164+
`"${tmp[@]}"` avoiding `nounset` error.
165+
- Some use other rules for the exit status. E.g., `help` and `usage` return
166+
whether there were options *before* filtering by cur. This is used for
167+
`_comp_compgen_help || _comp_compgen_usage`.
168+
169+
Whether to clear the target variable on runtime error (when `-a` is not
170+
specified in `OPTS`) is implementation-defined. On the other hand, the
171+
generator function should not leave any side effects in the target variable on
172+
usage error. Note that the target variable might be cleared by the internal
173+
calls of `_comp_compgen`. To explicitly clear the target variable,
174+
`_comp_compgen_set` can be called without arguments.
175+
176+
Exported generators are defined with the names `_comp_xfunc_CMD_compgen_NAME`
177+
and called by `_comp_compgen [opts] -x CMD NAME args`. Internal generators are
178+
defined with the names `_comp_cmd_CMD__compgen_NAME` and called by
179+
`_comp_compgen [opts] -i CMD NAME args`.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Dummy completion file for _comp_compgen tests -*- shell-script -*-
2+
3+
_comp_xfunc_compgen_cmd1_compgen_generator1() {
4+
_comp_compgen -- -W '5foo 6bar 7baz'
5+
}
6+
7+
_comp_cmd_compgen_cmd1__compgen_generator2() {
8+
_comp_compgen -- -W '5abc 6def 7ghi'
9+
}
10+
11+
_comp_cmd_compgen_cmd1() {
12+
local cur prev words cword comp_args
13+
_comp_initialize -- "$@" || return
14+
_comp_compgen -- -W '012 123 234'
15+
_comp_compgen -ai compgen-cmd1 generator2
16+
} &&
17+
complete -F _comp_cmd_compgen_cmd1 compgen-cmd1
18+
19+
# ex: filetype=sh
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Dummy completion file for _comp_compgen tests -*- shell-script -*-
2+
3+
_comp_cmd_compgen_cmd2() {
4+
local cur prev words cword comp_args
5+
_comp_initialize -- "$@" || return
6+
_comp_compgen -- -W '012 123 234'
7+
_comp_compgen -ax compgen-cmd1 generator1
8+
} &&
9+
complete -F _comp_cmd_compgen_cmd2 compgen-cmd2
10+
11+
# ex: filetype=sh

test/t/unit/test_unit_compgen.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,21 @@ def test_6_option_C_4(self, functions, completion):
128128
# Note: we are not in the original directory that "b" exists, so Bash
129129
# will not suffix a slash to the directory name.
130130
assert completion == "b"
131+
132+
def test_7_icmd(self, bash, functions):
133+
with bash_env_saved(bash) as bash_env:
134+
bash_env.write_variable(
135+
"BASH_COMPLETION_USER_DIR", "$PWD/_comp_compgen", quote=False
136+
)
137+
138+
completions = assert_complete(bash, "compgen-cmd1 '")
139+
assert completions == ["012", "123", "234", "5abc", "6def", "7ghi"]
140+
141+
def test_7_xcmd(self, bash, functions):
142+
with bash_env_saved(bash) as bash_env:
143+
bash_env.write_variable(
144+
"BASH_COMPLETION_USER_DIR", "$PWD/_comp_compgen", quote=False
145+
)
146+
147+
completions = assert_complete(bash, "compgen-cmd2 '")
148+
assert completions == ["012", "123", "234", "5foo", "6bar", "7baz"]

0 commit comments

Comments
 (0)