Skip to content

Commit

Permalink
feat(_comp_compgen): support option -C
Browse files Browse the repository at this point in the history
Co-authored-by: Ville Skyttä <ville.skytta@iki.fi>
  • Loading branch information
akinomyoga and scop committed May 12, 2023
1 parent 1e11613 commit 6b3dfa5
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 61 deletions.
81 changes: 66 additions & 15 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,23 @@ _comp_split()
((_new_size > _old_size))
}

# Helper function for _comp_compgen
# @var[in] $?
# @var[in] _var
# @var[in] _append
# @return original $?
_comp_compgen__error_fallback()
{
local _status=$?
if [[ $_append ]]; then
# make sure existence of variable
eval -- "$_var+=()"
else
eval -- "$_var=()"
fi
return "$_status"
}

# Provide a common interface to generate completion candidates in COMPREPLY or
# in a specified array.
# OPTIONS
Expand All @@ -414,6 +431,7 @@ _comp_split()
# -c cur Set a word used as a prefix to filter the completions. The default
# is ${cur-}.
# -R The same as -c ''. Use raw outputs without filtering.
# -C dir Evaluate compgen/generator in the specified directory.
# @var[in,opt] cur Used as the default value of a prefix to filter the
# completions.
#
Expand All @@ -437,10 +455,10 @@ _comp_split()
# as `-v arr` as a part of the `_comp_compgen` options.
#
# Usage #2: _comp_compgen [-alR|-v arr|-c cur] name args...
# Call `_comp_compgen_NAME ARGS...` with the specified options. This provides
# a common interface to call the functions `_comp_compgen_NAME`, which produce
# completion candidates, with custom options [-alR|-v arr|-c cur]. The option
# `-F sep` is not used with this usage.
# Call the generator `_comp_compgen_NAME ARGS...` with the specified options.
# This provides a common interface to call the functions `_comp_compgen_NAME`,
# which produce completion candidates, with custom options [-alR|-v arr|-c
# cur]. The option `-F sep` is not used with this usage.
# @param $1... name args Calls the function _comp_compgen_NAME with the
# specified ARGS (if $1 does not start with a hyphen `-`). The options
# [-alR|-v arr|-c cur] are inherited by the child calls of `_comp_compgen`
Expand Down Expand Up @@ -484,10 +502,15 @@ _comp_compgen()
local _append=${_comp_compgen__append-}
local _var=${_comp_compgen__var-COMPREPLY}
local _cur=${_comp_compgen__cur-${cur-}}
local _ifs=$' \t\n'
local _ifs=$' \t\n' _dir=""

local _old_nocasematch=""
if shopt -q nocasematch; then
_old_nocasematch=set
shopt -u nocasematch
fi
local OPTIND=1 OPTARG="" OPTERR=0 _opt
while getopts ':alF:v:Rc:' _opt "$@"; do
while getopts ':alF:v:Rc:C:' _opt "$@"; do
case $_opt in
a) _append=set ;;
v)
Expand All @@ -501,12 +524,20 @@ _comp_compgen()
F) _ifs=$OPTARG ;;
c) _cur=$OPTARG ;;
R) _cur="" ;;
C)
if [[ ! $OPTARG ]]; then
printf 'bash_completion: %s: -C: invalid directory name `%s'\''.\n' "$FUNCNAME" "$OPTARG" >&2
return 2
fi
_dir=$OPTARG
;;
*)
printf 'bash_completion: %s: usage error\n' "$FUNCNAME" >&2
return 2
;;
esac
done
[[ $_old_nocasematch ]] && shopt -s nocasematch
shift "$((OPTIND - 1))"
if (($# == 0)); then
printf 'bash_completion: %s: unexpected number of arguments.\n' "$FUNCNAME" >&2
Expand All @@ -521,14 +552,35 @@ _comp_compgen()
return 2
fi

if [[ $_dir ]]; then
local _original_pwd=$PWD
local PWD=${PWD-} OLDPWD=${OLDPWD-}
# Note: We also redirect stdout because `cd` may output the target
# directory to stdout when CDPATH is set.
command cd -- "$_dir" &>/dev/null ||
{
_comp_compgen__error_fallback
return
}
fi

local _comp_compgen__append=$_append
local _comp_compgen__var=$_var
local _comp_compgen__cur=$_cur cur=$_cur
# Note: we use $1 as a part of a function name, and we use $2... as
# arguments to the function if any.
# shellcheck disable=SC2145
_comp_compgen_"$@"
return
local _status=$?

# Go back to the original directory.
# Note: Failure of this line results in the change of the current
# directory visible to the user. We intentionally do not redirect
# stderr so that the error message appear in the terminal.
# shellcheck disable=SC2164
[[ $_dir ]] && command cd -- "$_original_pwd"

return "$_status"
fi

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

local _result
_result=$(
if [[ $_dir ]]; then
# Note: We also redirect stdout because `cd` may output the target
# directory to stdout when CDPATH is set.
command cd -- "$_dir" &>/dev/null || return
fi
IFS=$_ifs compgen "$@" ${_cur:+-- "$_cur"}
) || {
local _status=$?
if [[ $_append ]]; then
# make sure existence of variable
eval -- "$_var+=()"
else
eval -- "$_var=()"
fi
return "$_status"
_comp_compgen__error_fallback
return
}

_comp_split -l ${_append:+-a} "$_var" "$_result"
Expand Down
10 changes: 2 additions & 8 deletions completions/_mount.linux
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,11 @@ _comp_cmd_mount()
return
;;
-L)
COMPREPLY=($(
command cd "/dev/disk/by-label/" 2>/dev/null || return
compgen -f -- "$cur"
))
_comp_compgen -C "/dev/disk/by-label/" -- -f
return
;;
-U)
COMPREPLY=($(
command cd "/dev/disk/by-uuid/" 2>/dev/null || return
compgen -f -- "$cur"
))
_comp_compgen -C "/dev/disk/by-uuid/" -- -f
return
;;
-O | --test-opts)
Expand Down
12 changes: 3 additions & 9 deletions completions/_slackpkg
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,16 @@ _comp_cmd_slackpkg()
;;
install-template | remove-template)
if [[ -e $confdir/templates ]]; then
COMPREPLY=($(
command cd -- "$confdir/templates"
compgen -f -X "!*.template" -- "$cur"
))
COMPREPLY=(${COMPREPLY[@]%.template})
_comp_compgen -C "$confdir/templates" -- -f -X \
"!?*.template" && COMPREPLY=("${COMPREPLY[@]%.template}")
fi
return
;;
remove)
_comp_compgen_filedir
_comp_compgen -a -- -W 'a ap d e f k kde kdei l n t tcl x xap xfce
y'
COMPREPLY+=($(
command cd /var/log/packages
compgen -f -- "$cur"
))
_comp_compgen -aC /var/log/packages -- -f
return
;;
install | reinstall | upgrade | blacklist | download)
Expand Down
5 changes: 2 additions & 3 deletions completions/_umount.linux
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,8 @@ _comp_cmd_umount__linux_fstab()
local i
for i in ${!COMPREPLY[*]}; do
[[ ${COMPREPLY[i]} == "$realcur"* ]] &&
COMPREPLY+=($(command cd -- "$dircur" 2>/dev/null &&
compgen -f -d -P "$dircur" \
-X "!${COMPREPLY[i]##"$dirrealcur"}" -- "$basecur"))
_comp_compgen -aC "$dircur" -c "$basecur" -- \
-f -d -P "$dircur" -X "!${COMPREPLY[i]##"$dirrealcur"}"
done
fi
fi
Expand Down
10 changes: 3 additions & 7 deletions completions/feh
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,12 @@ _comp_cmd_feh()
fi
local font_path
# font_path="$(imlib2-config --prefix 2>/dev/null)/share/imlib2/data/fonts"
# COMPREPLY=( $(command cd -- "$font_path" 2>/dev/null; compgen -f \
# -X "!*.@([tT][tT][fF])" -S / -- "$cur") )
# _comp_compgen -C "$font_path" -- -f -X "!*.@([tT][tT][fF])" -S /
for ((i = ${#words[@]} - 1; i > 0; i--)); do
if [[ ${words[i]} == -@(C|-fontpath) ]]; then
font_path="${words[i + 1]}"
COMPREPLY+=($(
command cd -- "$font_path" 2>/dev/null
compgen -f \
-X "!*.@([tT][tT][fF])" -S / -- "$cur"
))
_comp_compgen -aC "$font_path" -- \
-f -X "!*.@([tT][tT][fF])" -S /
fi
done
compopt -o nospace
Expand Down
5 changes: 1 addition & 4 deletions completions/removepkg
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ _comp_cmd_removepkg()
fi

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

Expand Down
5 changes: 1 addition & 4 deletions completions/sbopkg
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,8 @@ _comp_cmd_sbopkg()
COMPREPLY=($(
command sed -ne "/^SLACKBUILD NAME: $cur/{s/^SLACKBUILD NAME: //;p}" \
"$REPO_ROOT/$REPO_NAME/$REPO_BRANCH/SLACKBUILDS.TXT"
)
$(
command cd -- "$QUEUEDIR"
compgen -f -X "!*.sqf" -- "$cur"
))
_comp_compgen -aC "$QUEUEDIR" -- -f -X "!*.sqf"
} &&
complete -F _comp_cmd_sbopkg sbopkg

Expand Down
5 changes: 1 addition & 4 deletions completions/slapt-get
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,7 @@ _comp_cmd_slapt_get()
return
;;
ins) # --remove|--filelist
COMPREPLY=($(
command cd /var/log/packages
compgen -f -- "$cur"
))
_comp_compgen -C /var/log/packages -- -f
return
;;
set) # --install-set
Expand Down
5 changes: 1 addition & 4 deletions completions/xdg-mime
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ _comp_cmd_xdg_mime__mimetype()
local d i
local -a arr
for d in /usr/share/mime /usr/local/share/mime; do
arr=($(
command cd "$d" 2>/dev/null || exit 1
compgen -f -o plusdirs -X "!*.xml" -- "$cur"
)) || continue
_comp_compgen -v arr -C "$d" -- -f -o plusdirs -X "!*.xml" || continue
for i in "${!arr[@]}"; do
case ${arr[i]} in
packages*) unset -v "arr[i]" ;; # not a MIME type dir
Expand Down
51 changes: 48 additions & 3 deletions test/t/unit/test_unit_compgen.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
import re

from conftest import assert_bash_exec, bash_env_saved
from conftest import assert_bash_exec, bash_env_saved, assert_complete


@pytest.mark.bashcomp(cmd=None)
Expand All @@ -13,11 +14,27 @@ def functions(self, bash):
)
assert_bash_exec(
bash,
'_comp__test_words() { local -a arr=(00) input; input=("${@:1:$#-1}"); _comp_compgen -v arr -c "${@:$#}" -- -W \'${input[@]+"${input[@]}"}\'; _comp__test_dump; }',
'_comp__test_compgen() { local -a arr=(00); _comp_compgen -v arr "$@"; _comp__test_dump; }',
)
assert_bash_exec(
bash,
'_comp__test_words_ifs() { local -a arr=(00); local input=$2; _comp_compgen -F "$1" -v arr -c "${@:$#}" -- -W \'$input\'; _comp__test_dump; }',
'_comp__test_words() { local -a input=("${@:1:$#-1}"); _comp__test_compgen -c "${@:$#}" -- -W \'${input[@]+"${input[@]}"}\'; }',
)
assert_bash_exec(
bash,
'_comp__test_words_ifs() { local input=$2; _comp__test_compgen -F "$1" -c "${@:$#}" -- -W \'$input\'; }',
)

assert_bash_exec(
bash,
'_comp_cmd_fc() { _comp_compgen -c "$(_get_cword)" -C _filedir filedir; }; '
"complete -F _comp_cmd_fc fc; "
"complete -F _comp_cmd_fc -o filenames fc2",
)
assert_bash_exec(
bash,
'_comp_cmd_fcd() { _comp_compgen -c "$(_get_cword)" -C _filedir filedir -d; }; '
"complete -F _comp_cmd_fcd fcd",
)

def test_1_basic(self, bash, functions):
Expand Down Expand Up @@ -83,3 +100,31 @@ def test_5_option_F(self, bash, functions):
want_output=True,
)
assert output.strip() == "< 1><3 4><6 >< >"

def test_6_option_C_1(self, bash, functions):
output = assert_bash_exec(
bash,
"_comp__test_compgen -c a -C _filedir filedir",
want_output=True,
)
set1 = set(re.findall(r"<[^<>]*>", output.strip()))
assert set1 == {"<a b>", "<a$b>", "<a&b>", "<a'b>", "<ab>", "<aé>"}

def test_6_option_C_2(self, bash, functions):
output = assert_bash_exec(
bash,
"_comp__test_compgen -c b -C _filedir -- -d",
want_output=True,
)
assert output.strip() == "<brackets>"

@pytest.mark.parametrize("funcname", "fc fc2".split())
def test_6_option_C_3(self, bash, functions, funcname):
completion = assert_complete(bash, "%s _filedir ab/" % funcname)
assert completion == "e"

@pytest.mark.complete(r"fcd a\ ")
def test_6_option_C_4(self, functions, completion):
# Note: we are not in the original directory that "b" exists, so Bash
# will not suffix a slash to the directory name.
assert completion == "b"

0 comments on commit 6b3dfa5

Please sign in to comment.