Skip to content

Commit 6b3dfa5

Browse files
akinomyogascop
andcommitted
feat(_comp_compgen): support option -C
Co-authored-by: Ville Skyttä <ville.skytta@iki.fi>
1 parent 1e11613 commit 6b3dfa5

File tree

10 files changed

+128
-61
lines changed

10 files changed

+128
-61
lines changed

bash_completion

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,23 @@ _comp_split()
397397
((_new_size > _old_size))
398398
}
399399

400+
# Helper function for _comp_compgen
401+
# @var[in] $?
402+
# @var[in] _var
403+
# @var[in] _append
404+
# @return original $?
405+
_comp_compgen__error_fallback()
406+
{
407+
local _status=$?
408+
if [[ $_append ]]; then
409+
# make sure existence of variable
410+
eval -- "$_var+=()"
411+
else
412+
eval -- "$_var=()"
413+
fi
414+
return "$_status"
415+
}
416+
400417
# Provide a common interface to generate completion candidates in COMPREPLY or
401418
# in a specified array.
402419
# OPTIONS
@@ -414,6 +431,7 @@ _comp_split()
414431
# -c cur Set a word used as a prefix to filter the completions. The default
415432
# is ${cur-}.
416433
# -R The same as -c ''. Use raw outputs without filtering.
434+
# -C dir Evaluate compgen/generator in the specified directory.
417435
# @var[in,opt] cur Used as the default value of a prefix to filter the
418436
# completions.
419437
#
@@ -437,10 +455,10 @@ _comp_split()
437455
# as `-v arr` as a part of the `_comp_compgen` options.
438456
#
439457
# Usage #2: _comp_compgen [-alR|-v arr|-c cur] name args...
440-
# Call `_comp_compgen_NAME ARGS...` with the specified options. This provides
441-
# a common interface to call the functions `_comp_compgen_NAME`, which produce
442-
# completion candidates, with custom options [-alR|-v arr|-c cur]. The option
443-
# `-F sep` is not used with this usage.
458+
# Call the generator `_comp_compgen_NAME ARGS...` with the specified options.
459+
# This provides a common interface to call the functions `_comp_compgen_NAME`,
460+
# which produce completion candidates, with custom options [-alR|-v arr|-c
461+
# cur]. The option `-F sep` is not used with this usage.
444462
# @param $1... name args Calls the function _comp_compgen_NAME with the
445463
# specified ARGS (if $1 does not start with a hyphen `-`). The options
446464
# [-alR|-v arr|-c cur] are inherited by the child calls of `_comp_compgen`
@@ -484,10 +502,15 @@ _comp_compgen()
484502
local _append=${_comp_compgen__append-}
485503
local _var=${_comp_compgen__var-COMPREPLY}
486504
local _cur=${_comp_compgen__cur-${cur-}}
487-
local _ifs=$' \t\n'
505+
local _ifs=$' \t\n' _dir=""
488506

507+
local _old_nocasematch=""
508+
if shopt -q nocasematch; then
509+
_old_nocasematch=set
510+
shopt -u nocasematch
511+
fi
489512
local OPTIND=1 OPTARG="" OPTERR=0 _opt
490-
while getopts ':alF:v:Rc:' _opt "$@"; do
513+
while getopts ':alF:v:Rc:C:' _opt "$@"; do
491514
case $_opt in
492515
a) _append=set ;;
493516
v)
@@ -501,12 +524,20 @@ _comp_compgen()
501524
F) _ifs=$OPTARG ;;
502525
c) _cur=$OPTARG ;;
503526
R) _cur="" ;;
527+
C)
528+
if [[ ! $OPTARG ]]; then
529+
printf 'bash_completion: %s: -C: invalid directory name `%s'\''.\n' "$FUNCNAME" "$OPTARG" >&2
530+
return 2
531+
fi
532+
_dir=$OPTARG
533+
;;
504534
*)
505535
printf 'bash_completion: %s: usage error\n' "$FUNCNAME" >&2
506536
return 2
507537
;;
508538
esac
509539
done
540+
[[ $_old_nocasematch ]] && shopt -s nocasematch
510541
shift "$((OPTIND - 1))"
511542
if (($# == 0)); then
512543
printf 'bash_completion: %s: unexpected number of arguments.\n' "$FUNCNAME" >&2
@@ -521,14 +552,35 @@ _comp_compgen()
521552
return 2
522553
fi
523554

555+
if [[ $_dir ]]; then
556+
local _original_pwd=$PWD
557+
local PWD=${PWD-} OLDPWD=${OLDPWD-}
558+
# Note: We also redirect stdout because `cd` may output the target
559+
# directory to stdout when CDPATH is set.
560+
command cd -- "$_dir" &>/dev/null ||
561+
{
562+
_comp_compgen__error_fallback
563+
return
564+
}
565+
fi
566+
524567
local _comp_compgen__append=$_append
525568
local _comp_compgen__var=$_var
526569
local _comp_compgen__cur=$_cur cur=$_cur
527570
# Note: we use $1 as a part of a function name, and we use $2... as
528571
# arguments to the function if any.
529572
# shellcheck disable=SC2145
530573
_comp_compgen_"$@"
531-
return
574+
local _status=$?
575+
576+
# Go back to the original directory.
577+
# Note: Failure of this line results in the change of the current
578+
# directory visible to the user. We intentionally do not redirect
579+
# stderr so that the error message appear in the terminal.
580+
# shellcheck disable=SC2164
581+
[[ $_dir ]] && command cd -- "$_original_pwd"
582+
583+
return "$_status"
532584
fi
533585

534586
# usage: _comp_compgen [options] -- [compgen_options]
@@ -550,16 +602,15 @@ _comp_compgen()
550602

551603
local _result
552604
_result=$(
605+
if [[ $_dir ]]; then
606+
# Note: We also redirect stdout because `cd` may output the target
607+
# directory to stdout when CDPATH is set.
608+
command cd -- "$_dir" &>/dev/null || return
609+
fi
553610
IFS=$_ifs compgen "$@" ${_cur:+-- "$_cur"}
554611
) || {
555-
local _status=$?
556-
if [[ $_append ]]; then
557-
# make sure existence of variable
558-
eval -- "$_var+=()"
559-
else
560-
eval -- "$_var=()"
561-
fi
562-
return "$_status"
612+
_comp_compgen__error_fallback
613+
return
563614
}
564615

565616
_comp_split -l ${_append:+-a} "$_var" "$_result"

completions/_mount.linux

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,11 @@ _comp_cmd_mount()
3737
return
3838
;;
3939
-L)
40-
COMPREPLY=($(
41-
command cd "/dev/disk/by-label/" 2>/dev/null || return
42-
compgen -f -- "$cur"
43-
))
40+
_comp_compgen -C "/dev/disk/by-label/" -- -f
4441
return
4542
;;
4643
-U)
47-
COMPREPLY=($(
48-
command cd "/dev/disk/by-uuid/" 2>/dev/null || return
49-
compgen -f -- "$cur"
50-
))
44+
_comp_compgen -C "/dev/disk/by-uuid/" -- -f
5145
return
5246
;;
5347
-O | --test-opts)

completions/_slackpkg

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,16 @@ _comp_cmd_slackpkg()
6565
;;
6666
install-template | remove-template)
6767
if [[ -e $confdir/templates ]]; then
68-
COMPREPLY=($(
69-
command cd -- "$confdir/templates"
70-
compgen -f -X "!*.template" -- "$cur"
71-
))
72-
COMPREPLY=(${COMPREPLY[@]%.template})
68+
_comp_compgen -C "$confdir/templates" -- -f -X \
69+
"!?*.template" && COMPREPLY=("${COMPREPLY[@]%.template}")
7370
fi
7471
return
7572
;;
7673
remove)
7774
_comp_compgen_filedir
7875
_comp_compgen -a -- -W 'a ap d e f k kde kdei l n t tcl x xap xfce
7976
y'
80-
COMPREPLY+=($(
81-
command cd /var/log/packages
82-
compgen -f -- "$cur"
83-
))
77+
_comp_compgen -aC /var/log/packages -- -f
8478
return
8579
;;
8680
install | reinstall | upgrade | blacklist | download)

completions/_umount.linux

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,8 @@ _comp_cmd_umount__linux_fstab()
8181
local i
8282
for i in ${!COMPREPLY[*]}; do
8383
[[ ${COMPREPLY[i]} == "$realcur"* ]] &&
84-
COMPREPLY+=($(command cd -- "$dircur" 2>/dev/null &&
85-
compgen -f -d -P "$dircur" \
86-
-X "!${COMPREPLY[i]##"$dirrealcur"}" -- "$basecur"))
84+
_comp_compgen -aC "$dircur" -c "$basecur" -- \
85+
-f -d -P "$dircur" -X "!${COMPREPLY[i]##"$dirrealcur"}"
8786
done
8887
fi
8988
fi

completions/feh

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,12 @@ _comp_cmd_feh()
2929
fi
3030
local font_path
3131
# font_path="$(imlib2-config --prefix 2>/dev/null)/share/imlib2/data/fonts"
32-
# COMPREPLY=( $(command cd -- "$font_path" 2>/dev/null; compgen -f \
33-
# -X "!*.@([tT][tT][fF])" -S / -- "$cur") )
32+
# _comp_compgen -C "$font_path" -- -f -X "!*.@([tT][tT][fF])" -S /
3433
for ((i = ${#words[@]} - 1; i > 0; i--)); do
3534
if [[ ${words[i]} == -@(C|-fontpath) ]]; then
3635
font_path="${words[i + 1]}"
37-
COMPREPLY+=($(
38-
command cd -- "$font_path" 2>/dev/null
39-
compgen -f \
40-
-X "!*.@([tT][tT][fF])" -S / -- "$cur"
41-
))
36+
_comp_compgen -aC "$font_path" -- \
37+
-f -X "!*.@([tT][tT][fF])" -S /
4238
fi
4339
done
4440
compopt -o nospace

completions/removepkg

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ _comp_cmd_removepkg()
1515
fi
1616

1717
local root=${ROOT:-/}
18-
COMPREPLY=($(
19-
command cd -- "$root/var/log/packages" 2>/dev/null || return 1
20-
compgen -f -- "$cur"
21-
))
18+
_comp_compgen -C "$root/var/log/packages" -- -f
2219
} &&
2320
complete -F _comp_cmd_removepkg removepkg
2421

completions/sbopkg

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,8 @@ _comp_cmd_sbopkg()
6464
COMPREPLY=($(
6565
command sed -ne "/^SLACKBUILD NAME: $cur/{s/^SLACKBUILD NAME: //;p}" \
6666
"$REPO_ROOT/$REPO_NAME/$REPO_BRANCH/SLACKBUILDS.TXT"
67-
)
68-
$(
69-
command cd -- "$QUEUEDIR"
70-
compgen -f -X "!*.sqf" -- "$cur"
7167
))
68+
_comp_compgen -aC "$QUEUEDIR" -- -f -X "!*.sqf"
7269
} &&
7370
complete -F _comp_cmd_sbopkg sbopkg
7471

completions/slapt-get

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,7 @@ _comp_cmd_slapt_get()
6969
return
7070
;;
7171
ins) # --remove|--filelist
72-
COMPREPLY=($(
73-
command cd /var/log/packages
74-
compgen -f -- "$cur"
75-
))
72+
_comp_compgen -C /var/log/packages -- -f
7673
return
7774
;;
7875
set) # --install-set

completions/xdg-mime

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ _comp_cmd_xdg_mime__mimetype()
55
local d i
66
local -a arr
77
for d in /usr/share/mime /usr/local/share/mime; do
8-
arr=($(
9-
command cd "$d" 2>/dev/null || exit 1
10-
compgen -f -o plusdirs -X "!*.xml" -- "$cur"
11-
)) || continue
8+
_comp_compgen -v arr -C "$d" -- -f -o plusdirs -X "!*.xml" || continue
129
for i in "${!arr[@]}"; do
1310
case ${arr[i]} in
1411
packages*) unset -v "arr[i]" ;; # not a MIME type dir

test/t/unit/test_unit_compgen.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
2+
import re
23

3-
from conftest import assert_bash_exec, bash_env_saved
4+
from conftest import assert_bash_exec, bash_env_saved, assert_complete
45

56

67
@pytest.mark.bashcomp(cmd=None)
@@ -13,11 +14,27 @@ def functions(self, bash):
1314
)
1415
assert_bash_exec(
1516
bash,
16-
'_comp__test_words() { local -a arr=(00) input; input=("${@:1:$#-1}"); _comp_compgen -v arr -c "${@:$#}" -- -W \'${input[@]+"${input[@]}"}\'; _comp__test_dump; }',
17+
'_comp__test_compgen() { local -a arr=(00); _comp_compgen -v arr "$@"; _comp__test_dump; }',
1718
)
1819
assert_bash_exec(
1920
bash,
20-
'_comp__test_words_ifs() { local -a arr=(00); local input=$2; _comp_compgen -F "$1" -v arr -c "${@:$#}" -- -W \'$input\'; _comp__test_dump; }',
21+
'_comp__test_words() { local -a input=("${@:1:$#-1}"); _comp__test_compgen -c "${@:$#}" -- -W \'${input[@]+"${input[@]}"}\'; }',
22+
)
23+
assert_bash_exec(
24+
bash,
25+
'_comp__test_words_ifs() { local input=$2; _comp__test_compgen -F "$1" -c "${@:$#}" -- -W \'$input\'; }',
26+
)
27+
28+
assert_bash_exec(
29+
bash,
30+
'_comp_cmd_fc() { _comp_compgen -c "$(_get_cword)" -C _filedir filedir; }; '
31+
"complete -F _comp_cmd_fc fc; "
32+
"complete -F _comp_cmd_fc -o filenames fc2",
33+
)
34+
assert_bash_exec(
35+
bash,
36+
'_comp_cmd_fcd() { _comp_compgen -c "$(_get_cword)" -C _filedir filedir -d; }; '
37+
"complete -F _comp_cmd_fcd fcd",
2138
)
2239

2340
def test_1_basic(self, bash, functions):
@@ -83,3 +100,31 @@ def test_5_option_F(self, bash, functions):
83100
want_output=True,
84101
)
85102
assert output.strip() == "< 1><3 4><6 >< >"
103+
104+
def test_6_option_C_1(self, bash, functions):
105+
output = assert_bash_exec(
106+
bash,
107+
"_comp__test_compgen -c a -C _filedir filedir",
108+
want_output=True,
109+
)
110+
set1 = set(re.findall(r"<[^<>]*>", output.strip()))
111+
assert set1 == {"<a b>", "<a$b>", "<a&b>", "<a'b>", "<ab>", "<aé>"}
112+
113+
def test_6_option_C_2(self, bash, functions):
114+
output = assert_bash_exec(
115+
bash,
116+
"_comp__test_compgen -c b -C _filedir -- -d",
117+
want_output=True,
118+
)
119+
assert output.strip() == "<brackets>"
120+
121+
@pytest.mark.parametrize("funcname", "fc fc2".split())
122+
def test_6_option_C_3(self, bash, functions, funcname):
123+
completion = assert_complete(bash, "%s _filedir ab/" % funcname)
124+
assert completion == "e"
125+
126+
@pytest.mark.complete(r"fcd a\ ")
127+
def test_6_option_C_4(self, functions, completion):
128+
# Note: we are not in the original directory that "b" exists, so Bash
129+
# will not suffix a slash to the directory name.
130+
assert completion == "b"

0 commit comments

Comments
 (0)