Skip to content

Commit 77c9fd6

Browse files
authored
Merge pull request #952 from akinomyoga/_filedir
refactor: `{ => _comp_compgen}_filedir` and `_comp_compgen [opts] NAME`
2 parents a26a2a9 + 4a4851c commit 77c9fd6

File tree

334 files changed

+992
-910
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

334 files changed

+992
-910
lines changed

bash_completion

Lines changed: 155 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -381,51 +381,96 @@ _comp_split()
381381
((_new_size > _old_size))
382382
}
383383

384-
# Call `compgen` with the specified arguments and store the results in the
385-
# specified array.
386-
# Usage: _comp_compgen [-alR|-F sep|-v arr|-c cur] -- args...
387-
# This function essentially performs arr=($(compgen args...)) but properly
388-
# handles shell options, IFS, etc. using _comp_split. This function is
389-
# equivalent to `_comp_split [-a] -l arr "$(IFS=sep; compgen args... -- cur)"`,
390-
# but this pattern is frequent in the codebase and is good to separate out as a
391-
# function for the possible future implementation change.
384+
# Provide a common interface to generate completion candidates in COMPREPLY or
385+
# in a specified array.
392386
# OPTIONS
393387
# -a Append to the array
394388
# -v arr Store the results to the array ARR. The default is `COMPREPLY`.
389+
# The array name should not start with an underscores "_", which is
390+
# internally used. The array name should not be either "IFS" or
391+
# "OPT{IND,ARG,ERR}".
395392
# -F sep Set a set of separator characters (used as IFS in evaluating
396393
# `compgen'). The default separator is $' \t\n'. Note that this is
397394
# not the set of separators to delimit output of `compgen', but the
398395
# separators in evaluating the expansions of `-W '...'`, etc. The
399396
# delimiter of the output of `compgen` is always a newline.
400-
# -l The same as -F $'\n'.
397+
# -l The same as -F $'\n'. Use lines as words in evaluating compgen.
401398
# -c cur Set a word used as a prefix to filter the completions. The default
402399
# is ${cur-}.
403-
# -R The same as -c ''.
404-
# @param $1 array_name The array name
405-
# The array name should not start with an underscores "_", which is
406-
# internally used. The array name should not be either "IFS" or
407-
# "OPT{IND,ARG,ERR}".
408-
# @param $2... args The arguments that are passed to compgen
400+
# -R The same as -c ''. Use raw outputs without filtering.
401+
# @var[in,opt] cur Used as the default value of a prefix to filter the
402+
# completions.
403+
#
404+
# Usage #1: _comp_compgen [-alR|-F sep|-v arr|-c cur] -- options...
405+
# Call `compgen` with the specified arguments and store the results in the
406+
# specified array. This function essentially performs arr=($(compgen args...))
407+
# but properly handles shell options, IFS, etc. using _comp_split. This
408+
# function is equivalent to `_comp_split [-a] -l arr "$(IFS=sep; compgen
409+
# args... -- cur)"`, but this pattern is frequent in the codebase and is good
410+
# to separate out as a function for the possible future implementation change.
411+
# @param $1... options Arguments that are passed to compgen (if $1 starts with
412+
# a hyphen `-`).
413+
#
409414
# Note: References to positional parameters $1, $2, ... (such as -W '$1')
410415
# will not work as expected because these reference the arguments of
411416
# `_comp_compgen' instead of those of the caller function. When there are
412417
# needs to reference them, save the arguments to an array and reference the
413-
# array instead. The array option `-V arr` in bash >= 5.3 should be instead
414-
# specified as `-v arr` as a part of `_comp_compgen` options.
415-
# @var[in] cur Used as the default value of a prefix to filter the
416-
# completions.
418+
# array instead.
419+
#
420+
# Note: The array option `-V arr` in bash >= 5.3 should be instead specified
421+
# as `-v arr` as a part of the `_comp_compgen` options.
422+
#
423+
# Usage #2: _comp_compgen [-alR|-v arr|-c cur] name args...
424+
# Call `_comp_compgen_NAME ARGS...` with the specified options. This provides
425+
# a common interface to call the functions `_comp_compgen_NAME`, which produce
426+
# completion candidates, with custom options [-alR|-v arr|-cur]. The option
427+
# `-F sep` is not used with this usage.
428+
# @param $1... name args Calls the function _comp_compgen_NAME with the
429+
# specified ARGS (if $1 does not start with a hyphen `-`). The options
430+
# [-alR|-v arr|-c cur] are inherited by the child calls of `_comp_compgen`
431+
# inside `_comp_compgen_NAME` unless the child call `_comp_compgen` receives
432+
# overriding options.
433+
# @var[in,opt,internal] _comp_compgen__append
434+
# @var[in,opt,internal] _comp_compgen__var
435+
# @var[in,opt,internal] _comp_compgen__cur
436+
# These variables are internally used to pass the effect of the options
437+
# [-alR|-v arr|-c cur] to the child calls of `_comp_compgen` in
438+
# `_comp_compgen_NAME`.
439+
#
440+
# @remarks Design `_comp_compgen_NAME`: a function that produce completions can
441+
# be defined with the name _comp_compgen_NAME. The function is supposed to
442+
# generate completions by calling `_comp_compgen`. To reflect the options
443+
# specified to the outer calls of `_comp_compgen`, the function should not
444+
# directly modify `COMPREPLY`. To add words, one can call
445+
#
446+
# _comp_compgen -- -W '"${words[@]}"'
447+
#
448+
# To directly add words without filtering by `cur`, one can call
449+
#
450+
# _comp_compgen -R -- -W '"${words[@]}"'
451+
#
452+
# or use the utility `_comp_compgen_set`:
453+
#
454+
# _comp_compgen_set "${words[@]}"
455+
#
456+
# Other nested calls of _comp_compgen can also be used. The function is
457+
# supposed to replace the existing content of the array by default to allow the
458+
# caller control whether to replace or append by the option `-a`.
459+
#
417460
_comp_compgen()
418461
{
419-
local _append="" _var=COMPREPLY _cur=${cur-} _ifs=$' \t\n'
420-
local -a _split_options=(-l)
462+
local _append=${_comp_compgen__append-}
463+
local _var=${_comp_compgen__var-COMPREPLY}
464+
local _cur=${_comp_compgen__cur-${cur-}}
465+
local _ifs=$' \t\n'
421466

422467
local OPTIND=1 OPTARG="" OPTERR=0 _opt
423468
while getopts ':alF:v:Rc:' _opt "$@"; do
424469
case $_opt in
425-
a) _append=set _split_options+=(-a) ;;
470+
a) _append=set ;;
426471
v)
427472
if [[ $OPTARG == @(*[^_a-zA-Z0-9]*|[0-9]*|''|_*|IFS|OPTIND|OPTARG|OPTERR) ]]; then
428-
printf 'bash_completion: %s: invalid array name `%s'\''.\n' "$FUNCNAME" "-v $OPTARG" >&2
473+
printf 'bash_completion: %s: -v: invalid array name `%s'\''.\n' "$FUNCNAME" "$OPTARG" >&2
429474
return 2
430475
fi
431476
_var=$OPTARG
@@ -445,17 +490,38 @@ _comp_compgen()
445490
printf 'bash_completion: %s: unexpected number of arguments.\n' "$FUNCNAME" >&2
446491
printf 'usage: %s [-alR|-F SEP|-v ARR|-c CUR] -- ARGS...' "$FUNCNAME" >&2
447492
return 2
448-
elif
449-
# Note: $* in the below checks would be affected by uncontrolled IFS in
450-
# bash >= 5.0, so we need to set IFS to the normal value. The behavior
451-
# in bash < 5.0, where unquoted $* in conditional command did not honor
452-
# IFS, was a bug.
453-
local IFS=$' \t\n'
454-
[[ $* == *\$[0-9]* || $* == *\$\{[0-9]* ]]
455-
then
456-
# Note: extglob *\$?(\{)[0-9]* can be extremely slow when the string
457-
# "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9]
458-
# separately.
493+
fi
494+
495+
if [[ $1 != -* ]]; then
496+
# usage: _comp_compgen [options] NAME args
497+
if ! declare -F "_comp_compgen_$1" &>/dev/null; then
498+
printf 'bash_completion: %s: unrecognized category `%s'\'' (function _comp_compgen_%s not found).\n' "$FUNCNAME" "$1" "$1" >&2
499+
return 2
500+
fi
501+
502+
local _comp_compgen__append=$_append
503+
local _comp_compgen__var=$_var
504+
local _comp_compgen__cur=$_cur cur=$_cur
505+
# Note: we use $1 as a part of a function name, and we use $2... as
506+
# arguments to the function if any.
507+
# shellcheck disable=SC2145
508+
_comp_compgen_"$@"
509+
return
510+
fi
511+
512+
# usage: _comp_compgen [options] -- [compgen_options]
513+
514+
# Note: $* in the below checks would be affected by uncontrolled IFS in
515+
# bash >= 5.0, so we need to set IFS to the normal value. The behavior in
516+
# bash < 5.0, where unquoted $* in conditional command did not honor IFS,
517+
# was a bug.
518+
# Note: Also, ${_cur:+-- "$_cur"} and ${_append:+-a} would be affected by
519+
# uncontrolled IFS.
520+
local IFS=$' \t\n'
521+
# Note: extglob *\$?(\{)[0-9]* can be extremely slow when the string
522+
# "${*:2:_nopt}" becomes longer, so we test \$[0-9] and \$\{[0-9]
523+
# separately.
524+
if [[ $* == *\$[0-9]* || $* == *\$\{[0-9]* ]]; then
459525
printf 'bash_completion: %s: positional parameter $1, $2, ... do not work inside this function.\n' "$FUNCNAME" >&2
460526
return 2
461527
fi
@@ -475,7 +541,23 @@ _comp_compgen()
475541
return "$_status"
476542
}
477543

478-
_comp_split "${_split_options[@]}" "$_var" "$_result"
544+
_comp_split -l ${_append:+-a} "$_var" "$_result"
545+
}
546+
547+
# usage: _comp_compgen_set [words...]
548+
# Reset COMPREPLY with the specified WORDS. If no arguments are specified, the
549+
# array is cleared.
550+
#
551+
# When an array name is specified by `-v VAR` in a caller _comp_compgen, the
552+
# array is reset instead of COMPREPLY. When the `-a` flag is specified in a
553+
# caller _comp_compgen, the words are appended to the existing elements of the
554+
# array instead of replacing the existing elements. This function ignores
555+
# ${cur-} or the prefix specified by `-v CUR`.
556+
_comp_compgen_set()
557+
{
558+
local _append=${_comp_compgen__append-}
559+
local _var=${_comp_compgen__var-COMPREPLY}
560+
eval -- "$_var${_append:++}=(\"\$@\")"
479561
}
480562

481563
# Check if the argument looks like a path.
@@ -766,10 +848,9 @@ _comp_quote_compgen()
766848
# completions with `.$1' and the uppercase version of it as file
767849
# extension.
768850
#
769-
# TODO: rename per API conventions
770-
_filedir()
851+
_comp_compgen_filedir()
771852
{
772-
_tilde "${cur-}" || return
853+
_comp_compgen_tilde && return
773854

774855
local -a toks
775856
local arg=${1-}
@@ -805,11 +886,15 @@ _filedir()
805886
fi
806887

807888
if ((${#toks[@]} != 0)); then
808-
# 2>/dev/null for direct invocation, e.g. in the _filedir unit test
889+
# 2>/dev/null for direct invocation, e.g. in the _comp_compgen_filedir unit test
809890
compopt -o filenames 2>/dev/null
810-
COMPREPLY+=("${toks[@]}")
811891
fi
812-
} # _filedir()
892+
893+
# Note: bash < 4.4 has a bug that all the elements are connected with
894+
# ${v-"${a[@]}"} when IFS does not contain whitespace.
895+
local IFS=$' \t\n'
896+
_comp_compgen_set ${toks[@]+"${toks[@]}"}
897+
} # _comp_compgen_filedir()
813898

814899
# This function splits $cur=--foo=bar into $prev=--foo, $cur=bar, making it
815900
# easier to support both "--foo bar" and "--foo=bar" style completions.
@@ -955,7 +1040,7 @@ _comp_variable_assignments()
9551040
case $prev in
9561041
TZ)
9571042
cur=/usr/share/zoneinfo/$cur
958-
_filedir
1043+
_comp_compgen -a filedir
9591044
if ((${#COMPREPLY[@]})); then
9601045
for i in "${!COMPREPLY[@]}"; do
9611046
if [[ ${COMPREPLY[i]} == *.tab ]]; then
@@ -980,7 +1065,7 @@ _comp_variable_assignments()
9801065
;;
9811066
*)
9821067
_variables && return 0
983-
_filedir
1068+
_comp_compgen -a filedir
9841069
;;
9851070
esac
9861071

@@ -995,9 +1080,12 @@ _comp_variable_assignments()
9951080
#
9961081
# Options:
9971082
# -n EXCLUDE Passed to _comp_get_words -n with redirection chars
998-
# -e XSPEC Passed to _filedir as first arg for stderr redirections
999-
# -o XSPEC Passed to _filedir as first arg for other output redirections
1000-
# -i XSPEC Passed to _filedir as first arg for stdin redirections
1083+
# -e XSPEC Passed to _comp_compgen_filedir as first arg for stderr
1084+
# redirections
1085+
# -o XSPEC Passed to _comp_compgen_filedir as first arg for other output
1086+
# redirections
1087+
# -i XSPEC Passed to _comp_compgen_filedir as first arg for stdin
1088+
# redirections
10011089
# -s Split long options with _comp__split_longopt, implies -n =
10021090
# @param $1...$3 args Original arguments specified to the completion function.
10031091
# The first argument $1 is command name. The second
@@ -1008,9 +1096,9 @@ _comp_variable_assignments()
10081096
# @var[out] prev Reconstructed previous word
10091097
# @var[out] words Reconstructed words
10101098
# @var[out] cword Current word index in `words`
1011-
# @var[out] comp_args Original arguments specified to the completion function
1012-
# are saved in this array, if the arguments $1...$3 is
1013-
# specified.
1099+
# @var[out] comp_args Original arguments specified to the completion
1100+
# function are saved in this array, if the arguments
1101+
# $1...$3 is specified.
10141102
# @var[out,opt] was_split When "-s" is specified, `"set"/""` is set depending
10151103
# on whether the split happened.
10161104
# @return True (0) if completion needs further processing,
@@ -1066,7 +1154,7 @@ _comp_initialize()
10661154
;;
10671155
esac
10681156
cur=${cur##"$redir"}
1069-
_filedir "$xspec"
1157+
_comp_compgen filedir "$xspec"
10701158
return 1
10711159
fi
10721160

@@ -1370,21 +1458,21 @@ _ncpus()
13701458
}
13711459

13721460
# Perform tilde (~) completion
1373-
# @return True (0) if completion needs further processing,
1374-
# False (1) if tilde is followed by a valid username, completions are
1461+
# @return False (1) if completion needs further processing,
1462+
# True (0) if tilde is followed by a valid username, completions are
13751463
# put in COMPREPLY and no further processing is necessary.
1376-
# TODO: rename per API conventions
1377-
_tilde()
1464+
_comp_compgen_tilde()
13781465
{
1379-
if [[ ${1-} == \~* && $1 != */* ]]; then
1466+
if [[ ${cur-} == \~* && $cur != */* ]]; then
13801467
# Try generate ~username completions
1381-
if _comp_compgen -c "${1#\~}" -- -P '~' -u; then
1382-
# 2>/dev/null for direct invocation, e.g. in the _tilde unit test
1468+
if _comp_compgen -c "${cur#\~}" -- -P '~' -u; then
1469+
# 2>/dev/null for direct invocation, e.g. in the
1470+
# _comp_compgen_tilde unit test
13831471
compopt -o filenames 2>/dev/null
1384-
return 1
1472+
return 0
13851473
fi
13861474
fi
1387-
return 0
1475+
return 1
13881476
}
13891477

13901478
# Expand variable starting with tilde (~)
@@ -1431,7 +1519,7 @@ _expand()
14311519
__expand_tilde_by_ref cur
14321520
;;
14331521
~*)
1434-
_tilde "$cur" ||
1522+
_comp_compgen -v COMPREPLY tilde &&
14351523
eval "COMPREPLY[0]=$(printf ~%q "${COMPREPLY[0]#\~}")"
14361524
return ${#COMPREPLY[@]}
14371525
;;
@@ -2412,23 +2500,23 @@ _comp_longopt()
24122500
return
24132501
;;
24142502
--!(no-*)dir*)
2415-
_filedir -d
2503+
_comp_compgen -a filedir -d
24162504
return
24172505
;;
24182506
--!(no-*)@(file|path)*)
2419-
_filedir
2507+
_comp_compgen -a filedir
24202508
return
24212509
;;
24222510
--+([-a-z0-9_]))
24232511
local argtype=$(LC_ALL=C $1 --help 2>&1 | command sed -ne \
24242512
"s|.*$prev\[\{0,1\}=[<[]\{0,1\}\([-A-Za-z0-9_]\{1,\}\).*|\1|p")
24252513
case ${argtype,,} in
24262514
*dir*)
2427-
_filedir -d
2515+
_comp_compgen -a filedir -d
24282516
return
24292517
;;
24302518
*file* | *path*)
2431-
_filedir
2519+
_comp_compgen -a filedir
24322520
return
24332521
;;
24342522
esac
@@ -2445,10 +2533,10 @@ _comp_longopt()
24452533
done)"
24462534
[[ ${COMPREPLY-} == *= ]] && compopt -o nospace
24472535
elif [[ $1 == *@(rmdir|chroot) ]]; then
2448-
_filedir -d
2536+
_comp_compgen -a filedir -d
24492537
else
24502538
[[ $1 == *mkdir ]] && compopt -o nospace
2451-
_filedir
2539+
_comp_compgen -a filedir
24522540
fi
24532541
}
24542542
# makeinfo and texi2dvi are defined elsewhere.
@@ -2468,7 +2556,7 @@ _filedir_xspec()
24682556
local cur prev words cword comp_args
24692557
_comp_initialize -- "$@" || return
24702558
2471-
_tilde "$cur" || return
2559+
_comp_compgen_tilde && return
24722560
24732561
local ret
24742562
_comp_quote_compgen "$cur"

bash_completion.d/000_bash_completion_compat.bash

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,22 @@ _command_offset()
258258
_comp_command_offset "$@"
259259
}
260260

261+
# @deprecated Use `_comp_compgen -a filedir`
262+
_filedir()
263+
{
264+
_comp_compgen -a filedir "$@"
265+
}
266+
267+
# Perform tilde (~) completion
268+
# @return True (0) if completion needs further processing,
269+
# False (1) if tilde is followed by a valid username, completions are
270+
# put in COMPREPLY and no further processing is necessary.
271+
# @deprecated Use `_comp_compgen -c CUR tilde [-d]`. Note that the exit status
272+
# of `_comp_compgen_tilde` is flipped. It returns 0 when the tilde completions
273+
# are attempted, or otherwise 1.
274+
_tilde()
275+
{
276+
! _comp_compgen -c "$1" tilde
277+
}
278+
261279
# ex: filetype=sh

0 commit comments

Comments
 (0)