Skip to content

Commit 23b8144

Browse files
authored
Merge pull request #1118 from scop/feat/env
feat(env): complete commands and variable assignments
2 parents 0e3a17d + 0cd2883 commit 23b8144

File tree

6 files changed

+108
-3
lines changed

6 files changed

+108
-3
lines changed

bash_completion

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,7 +2201,7 @@ _comp_realcommand()
22012201
{
22022202
REPLY=""
22032203
local file
2204-
file=$(type -P "$1") || return $?
2204+
file=$(type -P -- "$1") || return $?
22052205
if type -p realpath >/dev/null; then
22062206
REPLY=$(realpath "$file")
22072207
elif type -p greadlink >/dev/null; then
@@ -2991,7 +2991,7 @@ _comp_complete_longopt()
29912991
# makeinfo and texi2dvi are defined elsewhere.
29922992
complete -F _comp_complete_longopt \
29932993
a2ps awk base64 bash bc bison cat chroot colordiff cp \
2994-
csplit cut date df diff dir du enscript env expand fmt fold gperf \
2994+
csplit cut date df diff dir du enscript expand fmt fold gperf \
29952995
grep grub head irb ld ldd less ln ls m4 mkdir mkfifo mknod \
29962996
mv netstat nl nm objcopy objdump od paste pr ptx readelf rm rmdir \
29972997
sed seq shar sort split strip sum tac tail tee \
@@ -3153,7 +3153,7 @@ _comp_load()
31533153
31543154
# Resolve absolute path to $cmd
31553155
local REPLY pathcmd origcmd=$cmd
3156-
if pathcmd=$(type -P "$cmd"); then
3156+
if pathcmd=$(type -P -- "$cmd"); then
31573157
_comp_abspath "$pathcmd"
31583158
cmd=$REPLY
31593159
fi

completions/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ bashcomp_DATA = 2to3 \
100100
ebtables \
101101
ecryptfs-migrate-home \
102102
_eject \
103+
env \
103104
eog \
104105
ether-wake \
105106
evince \

completions/env

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# bash completion for env(1) -*- shell-script -*-
2+
3+
_comp_cmd_env()
4+
{
5+
local cur prev words cword was_split comp_args
6+
_comp_initialize -s -- "$@" || return
7+
8+
local i noargopts='!(-*|*[uCS]*)'
9+
for ((i = 1; i <= cword; i++)); do
10+
if [[ ${words[i]} != -* || ${words[i]} == -?(-) && i -lt cword ]]; then
11+
[[ ${words[i]} == -?(-) ]] && ((i++))
12+
for (( ; i <= cword; i++)); do
13+
if [[ ${words[i]} != *=* ]]; then
14+
_comp_command_offset "$i"
15+
return
16+
fi
17+
done
18+
break
19+
fi
20+
21+
# shellcheck disable=SC2254
22+
[[ ${words[i]} == @(--@(unset|chdir|split-string)|-${noargopts}[uCS]) ]] &&
23+
((i++))
24+
done
25+
26+
# shellcheck disable=SC2254
27+
case "$prev" in
28+
--unset | -${noargopts}u)
29+
_comp_compgen -- -A variable
30+
return
31+
;;
32+
--chdir | -${noargopts}C)
33+
_comp_compgen_filedir -d
34+
return
35+
;;
36+
--split-string | -${noargopts}S)
37+
return
38+
;;
39+
--block-signal | --default-signal | --ignore-signal)
40+
# TODO signals, but only if completing with a =SIG
41+
;;
42+
esac
43+
44+
[[ $was_split ]] && return
45+
46+
_comp_variable_assignments "$cur" && return
47+
48+
if [[ $cur == -* ]]; then
49+
_comp_compgen_help || _comp_compgen_usage
50+
[[ ${COMPREPLY-} == *= ]] && compopt -o nospace
51+
return
52+
fi
53+
} &&
54+
complete -F _comp_cmd_env env
55+
56+
# ex: filetype=sh

test/t/test_env.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,44 @@
11
import pytest
22

3+
from conftest import assert_complete
4+
35

46
class TestEnv:
57
@pytest.mark.complete("env --", require_longopt=True)
68
def test_1(self, completion):
79
assert completion
10+
11+
@pytest.mark.complete("env __unknown_variable__=")
12+
def test_unknown_variable_falls_back_to_filedir(self, completion):
13+
assert "shared/" in completion
14+
15+
@pytest.mark.complete("env LANG=", xfail="! locale -a &>/dev/null")
16+
def test_lang_envvar(self, completion):
17+
assert any(x == "C" or x.startswith("C.") for x in completion)
18+
19+
@pytest.mark.parametrize(
20+
"opts",
21+
[
22+
"",
23+
"foo=bar",
24+
"--debug",
25+
"--debug foo=bar",
26+
"-",
27+
"- foo=bar",
28+
],
29+
)
30+
def test_command(self, bash, opts):
31+
completion = assert_complete(bash, "env %s s" % opts)
32+
assert completion == "h" or "sh" in completion
33+
34+
@pytest.mark.parametrize(
35+
"opts",
36+
[
37+
"foo=bar --non-existent",
38+
"- --non-existent",
39+
"-- --non-existent",
40+
],
41+
)
42+
def test_option_like_command(self, bash, opts):
43+
completion = assert_complete(bash, "env %s s" % opts)
44+
assert not (completion == "h" or "sh" in completion)

test/t/unit/test_unit_load.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,6 @@ def test_cmd_intree_precedence(self, bash, fixture_dir):
128128
# The in-tree `sh` completion should be loaded here,
129129
# and cause no output, unlike our `$PWD/prefix1/bin/sh` canary.
130130
assert_bash_exec(bash, "_comp_load sh", want_output=False)
131+
132+
def test_option_like_cmd_name(self, bash):
133+
assert_bash_exec(bash, "! _comp_load -- --non-existent")

test/t/unit/test_unit_realcommand.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,11 @@ def test_absolute_nonexistent(self, bash, functions):
8888
want_output=False,
8989
)
9090
assert output.strip() == ""
91+
92+
def test_option_like_cmd_name(self, bash, functions):
93+
output = assert_bash_exec(
94+
bash,
95+
"! __tester --non-existent",
96+
want_output=False,
97+
)
98+
assert output.strip() == ""

0 commit comments

Comments
 (0)