Skip to content

Commit

Permalink
feat: add _comp_compgen_split
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 28, 2023
1 parent 99fdcbe commit 542bf73
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 0 deletions.
40 changes: 40 additions & 0 deletions bash_completion
Expand Up @@ -633,6 +633,46 @@ _comp_compgen_set()
eval -- "$_var${_append:++}=(\"\$@\")"
}

# Simply split the text and generate completions. This function should be used
# instead of `_comp_compgen -- -W "$(command)"`, which is vulnerable because
# option -W evaluates the shell expansions included in the option argument.
# Options:
# -F sep Specify the separators. The default is $' \t\n'
# -l The same as -F $'\n'
# -X arg The same as the compgen option -X.
# -S arg The same as the compgen option -S.
# -P arg The same as the compgen option -P.
# -o arg The same as the compgen option -o.
# @param $1 String to split
# @since 2.12
_comp_compgen_split()
{
local _ifs=$' \t\n'
local -a _compgen_options=()

local OPTIND=1 OPTARG="" OPTERR=0 _opt
while getopts ':lF:X:S:P:o:' _opt "$@"; do
case $_opt in
l) _ifs=$'\n' ;;
F) _ifs=$OPTARG ;;
[XSPo]) _compgen_options+=("-$_opt" "$OPTARG") ;;
*)
printf 'bash_completion: usage: %s [-l|-F sep] [--] str\n' "$FUNCNAME" >&2
return 2
;;
esac
done
shift "$((OPTIND - 1))"
if (($# != 1)); then
printf 'bash_completion: %s: unexpected number of arguments.\n' "$FUNCNAME" >&2
printf 'usage: %s [-l|-F sep] [--] str' "$FUNCNAME" >&2
return 2
fi

local _split_input=$1
_comp_compgen -F "$_ifs" -- "${_compgen_options[@]}" -W '$_split_input'
}

# Check if the argument looks like a path.
# @param $1 thing to check
# @return True (0) if it does, False (> 0) otherwise
Expand Down
102 changes: 102 additions & 0 deletions test/t/unit/test_unit_compgen_split.py
@@ -0,0 +1,102 @@
import pytest

from conftest import assert_bash_exec


@pytest.mark.bashcomp(cmd=None)
class TestUtilCompgenSplit:
@pytest.fixture
def functions(self, bash):
assert_bash_exec(
bash,
"_comp__test_dump() { ((${#arr[@]})) && printf '<%s>' \"${arr[@]}\"; echo; }",
)
assert_bash_exec(
bash,
'_comp__test_compgen() { local -a arr=(00); _comp_compgen -v arr "$@"; _comp__test_dump; }',
)

assert_bash_exec(
bash,
"_comp__test_cmd1() { echo foo bar; echo baz; }",
)
assert_bash_exec(
bash,
'_comp__test_attack() { echo "\\$(echo should_not_run >&2)"; }',
)

def test_1_basic(self, bash, functions):
output = assert_bash_exec(
bash,
'_comp__test_compgen split -- "$(_comp__test_cmd1)"',
want_output=True,
)
assert output.strip() == "<foo><bar><baz>"

def test_2_attack(self, bash, functions):
output = assert_bash_exec(
bash,
'_comp__test_compgen split -- "$(_comp__test_attack)"',
want_output=True,
)
assert output.strip() == "<$(echo><should_not_run><>&2)>"

def test_3_sep1(self, bash, functions):
output = assert_bash_exec(
bash,
'_comp__test_compgen split -l -- "$(_comp__test_cmd1)"',
want_output=True,
)
assert output.strip() == "<foo bar><baz>"

def test_3_sep2(self, bash, functions):
output = assert_bash_exec(
bash,
"_comp__test_compgen split -F $'b\\n' -- \"$(_comp__test_cmd1)\"",
want_output=True,
)
assert output.strip() == "<foo ><ar><az>"

def test_4_optionX(self, bash, functions):
output = assert_bash_exec(
bash,
'_comp__test_compgen split -X bar -- "$(_comp__test_cmd1)"',
want_output=True,
)
assert output.strip() == "<foo><baz>"

def test_4_optionS(self, bash, functions):
output = assert_bash_exec(
bash,
'_comp__test_compgen split -S .txt -- "$(_comp__test_cmd1)"',
want_output=True,
)
assert output.strip() == "<foo.txt><bar.txt><baz.txt>"

def test_4_optionP(self, bash, functions):
output = assert_bash_exec(
bash,
'_comp__test_compgen split -P /tmp/ -- "$(_comp__test_cmd1)"',
want_output=True,
)
assert output.strip() == "</tmp/foo></tmp/bar></tmp/baz>"

def test_4_optionPS(self, bash, functions):
output = assert_bash_exec(
bash,
'_comp__test_compgen split -P [ -S ] -- "$(_comp__test_cmd1)"',
want_output=True,
)
assert output.strip() == "<[foo]><[bar]><[baz]>"

def test_5_empty(self, bash, functions):
output = assert_bash_exec(
bash, '_comp__test_compgen split -- ""', want_output=True
)
assert output.strip() == ""

def test_5_empty2(self, bash, functions):
output = assert_bash_exec(
bash, '_comp__test_compgen split -- " "', want_output=True
)
assert output.strip() == ""

0 comments on commit 542bf73

Please sign in to comment.