Fix _usergroup, cpio and chown completions
Improve test suite. Thanks to Leonard Crestez (Alioth: #311396, Debian: #511788). `assert_complete' is improved. It proved difficult to tell tcl to ignore backslash escapes, e.g. the `\b' is no BACKSPACE but a literal `b'. The added function `split_words_bash' should to the trick now. Added function `assert_no_complete' which can also be reached by calling `assert_complete' with an empty `expected' argument: assert_complete "" qwerty
This commit is contained in:
parent
dc8af65161
commit
d866854066
1
CHANGES
1
CHANGES
@ -67,6 +67,7 @@ bash-completion (2.x)
|
|||||||
[ Leonard Crestez ]
|
[ Leonard Crestez ]
|
||||||
* Improve ssh -o suboption completion (Alioth: #312122).
|
* Improve ssh -o suboption completion (Alioth: #312122).
|
||||||
* Fix NFS mounts completion (Alioth: #312285).
|
* Fix NFS mounts completion (Alioth: #312285).
|
||||||
|
* Fix completion of usernames (Alioth: #311396, Debian: #511788).
|
||||||
|
|
||||||
[ Raphaël Droz ]
|
[ Raphaël Droz ]
|
||||||
* Add xsltproc completion (Alioth: #311843).
|
* Add xsltproc completion (Alioth: #311843).
|
||||||
|
@ -771,20 +771,38 @@ _installed_modules()
|
|||||||
awk '{if (NR != 1) print $1}' )" -- "$1" ) )
|
awk '{if (NR != 1) print $1}' )" -- "$1" ) )
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function completes on user:group format
|
# This function completes on user or user:group format; as for chown and cpio.
|
||||||
#
|
#
|
||||||
|
# The : must be added manually; it will only complete usernames initially.
|
||||||
|
# The legacy user.group format is not supported.
|
||||||
|
#
|
||||||
|
# It assumes compopt -o filenames; but doesn't touch it.
|
||||||
_usergroup()
|
_usergroup()
|
||||||
{
|
{
|
||||||
local IFS=$'\n'
|
local IFS=$'\n'
|
||||||
cur=${cur//\\\\ / }
|
if [[ $cur = *\\\\* || $cur = *:*:* ]]; then
|
||||||
if [[ $cur = *@(\\:|.)* ]]; then
|
# Give up early on if something seems horribly wrong.
|
||||||
user=${cur%%*([^:.])}
|
return
|
||||||
COMPREPLY=( $(compgen -P ${user/\\\\} -g -- ${cur##*[.:]}) )
|
elif [[ $cur = *\\:* ]]; then
|
||||||
|
# Completing group after 'user\:gr<TAB>'.
|
||||||
|
# Reply with a list of groups prefixed with 'user:', readline will
|
||||||
|
# escape to the colon.
|
||||||
|
local prefix
|
||||||
|
prefix=${cur%%*([^:])}
|
||||||
|
prefix=${prefix//\\}
|
||||||
|
COMPREPLY=( $( compgen -P "$prefix" -g -- "${cur#*[:]}" ) )
|
||||||
elif [[ $cur = *:* ]]; then
|
elif [[ $cur = *:* ]]; then
|
||||||
COMPREPLY=( $( compgen -g -- ${cur##*[.:]} ) )
|
# Completing group after 'user:gr<TAB>'.
|
||||||
|
# Reply with a list of unprefixed groups since readline with split on :
|
||||||
|
# and only replace the 'gr' part
|
||||||
|
COMPREPLY=( $( compgen -g -- "${cur#*:}" ) )
|
||||||
else
|
else
|
||||||
type compopt &>/dev/null && compopt -o nospace
|
# Completing a partial 'usernam<TAB>'.
|
||||||
COMPREPLY=( $( compgen -S : -u -- "$cur" ) )
|
#
|
||||||
|
# Don't suffix with a : because readline will escape it and add a
|
||||||
|
# slash. It's better to complete into 'chown username ' than 'chown
|
||||||
|
# username\:'.
|
||||||
|
COMPREPLY=( $( compgen -u -- "$cur" ) )
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -908,8 +926,10 @@ complete -F _service service
|
|||||||
_chown()
|
_chown()
|
||||||
{
|
{
|
||||||
local cur prev split=false
|
local cur prev split=false
|
||||||
cur=`_get_cword`
|
|
||||||
prev=${COMP_WORDS[COMP_CWORD-1]}
|
# Get cur and prev words; but don't treat user:group as separate words.
|
||||||
|
cur=`_get_cword :`
|
||||||
|
prev=`_get_pword :`
|
||||||
|
|
||||||
_split_longopt && split=true
|
_split_longopt && split=true
|
||||||
|
|
||||||
@ -926,22 +946,22 @@ _chown()
|
|||||||
|
|
||||||
$split && return 0
|
$split && return 0
|
||||||
|
|
||||||
# options completion
|
|
||||||
if [[ "$cur" == -* ]]; then
|
if [[ "$cur" == -* ]]; then
|
||||||
|
# Complete -options
|
||||||
COMPREPLY=( $( compgen -W '-c -h -f -R -v --changes --dereference \
|
COMPREPLY=( $( compgen -W '-c -h -f -R -v --changes --dereference \
|
||||||
--no-dereference --from --silent --quiet --reference --recursive \
|
--no-dereference --from --silent --quiet --reference --recursive \
|
||||||
--verbose --help --version' -- "$cur" ) )
|
--verbose --help --version' -- "$cur" ) )
|
||||||
else
|
else
|
||||||
_count_args
|
local args
|
||||||
|
|
||||||
case $args in
|
# The first argument is an usergroup; the rest are filedir.
|
||||||
1)
|
_count_args :
|
||||||
|
|
||||||
|
if [[ $args == 1 ]]; then
|
||||||
_usergroup
|
_usergroup
|
||||||
;;
|
else
|
||||||
*)
|
|
||||||
_filedir
|
_filedir
|
||||||
;;
|
fi
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
} # _chown()
|
} # _chown()
|
||||||
complete -F _chown -o filenames chown
|
complete -F _chown -o filenames chown
|
||||||
|
@ -11,8 +11,8 @@ _cpio()
|
|||||||
local cur prev split=false
|
local cur prev split=false
|
||||||
|
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
cur=`_get_cword`
|
cur=`_get_cword :`
|
||||||
prev=${COMP_WORDS[COMP_CWORD-1]}
|
prev=`_get_pword :`
|
||||||
|
|
||||||
_split_longopt && split=true
|
_split_longopt && split=true
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ _cpio()
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
complete -F _cpio cpio
|
complete -F _cpio -o filenames cpio
|
||||||
}
|
}
|
||||||
|
|
||||||
# Local variables:
|
# Local variables:
|
||||||
|
3
test/completion/chown.exp
Normal file
3
test/completion/chown.exp
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if {[assert_bash_type chown]} {
|
||||||
|
source "lib/completions/chown.exp"
|
||||||
|
}; # if
|
70
test/lib/completions/chown.exp
Normal file
70
test/lib/completions/chown.exp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
proc setup {} {
|
||||||
|
save_env
|
||||||
|
}; # setup()
|
||||||
|
|
||||||
|
proc teardown {} {
|
||||||
|
assert_env_unmodified
|
||||||
|
}; # teardown()
|
||||||
|
|
||||||
|
|
||||||
|
setup
|
||||||
|
|
||||||
|
|
||||||
|
assert_complete_any "chown "
|
||||||
|
sync_after_int
|
||||||
|
|
||||||
|
|
||||||
|
# All the tests use the root:root user and group. They're assumed to exist.
|
||||||
|
set fulluser "root"
|
||||||
|
set fullgroup "root"
|
||||||
|
|
||||||
|
# Partial username is assumed to be unambiguous.
|
||||||
|
set partuser "roo"
|
||||||
|
set partgroup "roo"
|
||||||
|
|
||||||
|
# Skip tests if root:root not available or if roo:roo matches multiple
|
||||||
|
# users/groups
|
||||||
|
if {[exec bash -c "compgen -A user $partuser" | wc -l] > 1 ||
|
||||||
|
[exec bash -c "compgen -A user $fulluser" | wc -l] != 1 ||
|
||||||
|
[exec bash -c "compgen -A group $partgroup" | wc -l] > 1 ||
|
||||||
|
[exec bash -c "compgen -A group $fullgroup" | wc -l] != 1} {
|
||||||
|
untested "Not running complex chown tests."
|
||||||
|
} else {
|
||||||
|
assert_complete $fulluser "chown $partuser"
|
||||||
|
sync_after_int
|
||||||
|
|
||||||
|
assert_complete $fulluser:$fullgroup "chown $fulluser:$partgroup"
|
||||||
|
sync_after_int
|
||||||
|
|
||||||
|
# One slash should work correctly (doubled here for tcl).
|
||||||
|
assert_complete $fulluser\\:$fullgroup "chown $fulluser\\:$partgroup"
|
||||||
|
sync_after_int
|
||||||
|
|
||||||
|
foreach prefix {
|
||||||
|
"funky\\ user:" "funky\\ user\\:" "funky.user:" "funky\\.user:" "fu\\ nky.user\\:"
|
||||||
|
"f\\ o\\ o\\.\\bar:" "foo\\_b\\ a\\.r\\ :"
|
||||||
|
} {
|
||||||
|
set test "Check preserve special chars in $prefix$partgroup<TAB>"
|
||||||
|
#assert_complete_into "chown $prefix$partgroup" "chown $prefix$fullgroup " $test
|
||||||
|
assert_complete $prefix$fullgroup "chown $prefix$partgroup" $test
|
||||||
|
sync_after_int
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that we give up in degenerate cases instead of spewing various junk.
|
||||||
|
|
||||||
|
assert_no_complete "chown $fulluser\\\\:$partgroup"
|
||||||
|
sync_after_int
|
||||||
|
|
||||||
|
assert_no_complete "chown $fulluser\\\\\\:$partgroup"
|
||||||
|
sync_after_int
|
||||||
|
|
||||||
|
assert_no_complete "chown $fulluser\\\\\\\\:$partgroup"
|
||||||
|
sync_after_int
|
||||||
|
|
||||||
|
# Colons in user/groupnames are not usually allowed.
|
||||||
|
assert_no_complete "chown foo:bar:$partgroup"
|
||||||
|
sync_after_int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
teardown
|
@ -104,7 +104,7 @@ proc assert_bash_list_dir {expected cmd dir {test ""} {prompt /@} {size 20}} {
|
|||||||
|
|
||||||
# Make sure the expected items are returned by TAB-completing the specified
|
# Make sure the expected items are returned by TAB-completing the specified
|
||||||
# command.
|
# command.
|
||||||
# @param list $expected
|
# @param list $expected Expected completions.
|
||||||
# @param string $cmd Command given to generate items
|
# @param string $cmd Command given to generate items
|
||||||
# @param string $test (optional) Test title. Default is "$cmd<TAB> should show completions"
|
# @param string $test (optional) Test title. Default is "$cmd<TAB> should show completions"
|
||||||
# @param string $prompt (optional) Bash prompt. Default is "/@"
|
# @param string $prompt (optional) Bash prompt. Default is "/@"
|
||||||
@ -113,28 +113,33 @@ proc assert_bash_list_dir {expected cmd dir {test ""} {prompt /@} {size 20}} {
|
|||||||
# argument-to-complete and to be replaced with the longest common prefix
|
# argument-to-complete and to be replaced with the longest common prefix
|
||||||
# of $expected. If empty string (default), `assert_complete' autodetects
|
# of $expected. If empty string (default), `assert_complete' autodetects
|
||||||
# if the last argument is an argument-to-complete by checking if $cmd
|
# if the last argument is an argument-to-complete by checking if $cmd
|
||||||
# doesn't end with whitespace. Specifying `cword' is only necessary if
|
# doesn't end with whitespace. Specifying `cword' should only be necessary
|
||||||
# this autodetection fails, e.g. when the last whitespace is escaped or
|
# if this autodetection fails, e.g. when the last whitespace is escaped or
|
||||||
# quoted, e.g. "finger foo\ " or "finger 'foo "
|
# quoted, e.g. "finger foo\ " or "finger 'foo "
|
||||||
# @param list $filters (optional) List of filters to apply to this function to tweak
|
# @param list $filters (optional) List of filters to apply to this function to tweak
|
||||||
# the expected completions and argument-to-complete. Possible values:
|
# the expected completions and argument-to-complete. Possible values:
|
||||||
# - "ltrim_colon_completions"
|
# - "ltrim_colon_completions"
|
||||||
# @result boolean True if successful, False if not
|
# @result boolean True if successful, False if not
|
||||||
proc assert_complete {expected cmd {test ""} {prompt /@} {size 20} {cword ""} {filters ""}} {
|
proc assert_complete {expected cmd {test ""} {prompt /@} {size 20} {cword ""} {filters ""}} {
|
||||||
|
if {[llength $expected] == 0} {
|
||||||
|
assert_no_complete $cmd $test
|
||||||
|
} else {
|
||||||
if {$test == ""} {set test "$cmd should show completions"}
|
if {$test == ""} {set test "$cmd should show completions"}
|
||||||
send "$cmd\t"
|
send "$cmd\t"
|
||||||
if {[llength $expected] == 1} {
|
if {[llength $expected] == 1} {
|
||||||
expect -ex "$cmd"
|
expect -ex "$cmd"
|
||||||
|
|
||||||
if {[lsearch -exact $filters "ltrim_colon_completions"] == -1} {
|
if {[lsearch -exact $filters "ltrim_colon_completions"] == -1} {
|
||||||
set cmds [split $cmd]
|
|
||||||
set cur ""; # Default to empty word to complete on
|
set cur ""; # Default to empty word to complete on
|
||||||
if {[llength $cmds] > 1} {
|
set words [split_words_bash $cmd]
|
||||||
|
if {[llength $words] > 1} {
|
||||||
# Assume last word of `$cmd' is word to complete on.
|
# Assume last word of `$cmd' is word to complete on.
|
||||||
set cur [lindex $cmds [expr [llength $cmds] - 1]]
|
set index [expr [llength $words] - 1]
|
||||||
|
set cur [lindex $words $index]
|
||||||
}; # if
|
}; # if
|
||||||
# Remove second word from beginning of single item $expected
|
# Remove second word from beginning of single item $expected
|
||||||
if {[string first $cur $expected] == 0} {
|
if {[string first $cur $expected] == 0} {
|
||||||
set expected [string range $expected [string length $cur] end]
|
set expected [list [string range $expected [string length $cur] end]]
|
||||||
}; # if
|
}; # if
|
||||||
}; # if
|
}; # if
|
||||||
} else {
|
} else {
|
||||||
@ -174,6 +179,7 @@ proc assert_complete {expected cmd {test ""} {prompt /@} {size 20} {cword ""} {f
|
|||||||
} else {
|
} else {
|
||||||
fail "$test"
|
fail "$test"
|
||||||
}; # if
|
}; # if
|
||||||
|
}; # if
|
||||||
}; # assert_complete()
|
}; # assert_complete()
|
||||||
|
|
||||||
|
|
||||||
@ -213,13 +219,18 @@ proc _remove_cword_from_cmd {cmd {cword ""}} {
|
|||||||
}; # _remove_cword_from_cmd()
|
}; # _remove_cword_from_cmd()
|
||||||
|
|
||||||
|
|
||||||
|
# Escape regexp special characters
|
||||||
|
proc _escape_regexp_chars {var} {
|
||||||
|
upvar $var str
|
||||||
|
regsub -all {([\^$+*?.|(){}[\]\\])} $str {\\\1} str
|
||||||
|
}
|
||||||
|
|
||||||
# Make sure any completions are returned
|
# Make sure any completions are returned
|
||||||
proc assert_complete_any {cmd {test ""} {prompt /@}} {
|
proc assert_complete_any {cmd {test ""} {prompt /@}} {
|
||||||
if {$test == ""} {set test "$cmd should show completions"}
|
if {$test == ""} {set test "$cmd should show completions"}
|
||||||
send "$cmd\t"
|
send "$cmd\t"
|
||||||
expect -ex "$cmd"
|
expect -ex "$cmd"
|
||||||
# Escape special regexp characters
|
_escape_regexp_chars cmd
|
||||||
regsub -all {([\^$+*?.|(){}[\]\\])} $cmd {\\\1} cmd
|
|
||||||
expect {
|
expect {
|
||||||
-timeout 1
|
-timeout 1
|
||||||
# Match completions, multiple words
|
# Match completions, multiple words
|
||||||
@ -415,6 +426,29 @@ proc assert_exec {cmd {stdout ''} {test ''}} {
|
|||||||
}; # assert_exec()
|
}; # assert_exec()
|
||||||
|
|
||||||
|
|
||||||
|
# Check that no completion is attempted on a certain command.
|
||||||
|
# Params:
|
||||||
|
# @cmd The command to attempt to complete.
|
||||||
|
# @test Optional parameter with test name.
|
||||||
|
proc assert_no_complete {{cmd} {test ""}} {
|
||||||
|
if {[string length $test] == 0} {
|
||||||
|
set test "$cmd shouldn't complete"
|
||||||
|
}; # if
|
||||||
|
|
||||||
|
send "$cmd\t"
|
||||||
|
expect -ex "$cmd"
|
||||||
|
|
||||||
|
# We can't anchor on $, simulate typing a magical string instead.
|
||||||
|
set endguard "Magic End Guard"
|
||||||
|
send "$endguard"
|
||||||
|
expect {
|
||||||
|
-re "^$endguard$" { pass "$test" }
|
||||||
|
default { fail "$test" }
|
||||||
|
timeout { fail "$test" }
|
||||||
|
}; # expect
|
||||||
|
}; # assert_no_complete()
|
||||||
|
|
||||||
|
|
||||||
# Sort list.
|
# Sort list.
|
||||||
# `exec sort' is used instead of `lsort' to achieve exactly the
|
# `exec sort' is used instead of `lsort' to achieve exactly the
|
||||||
# same sort order as in bash.
|
# same sort order as in bash.
|
||||||
@ -515,8 +549,7 @@ proc match_items {items test {prompt /@} {size 20}} {
|
|||||||
if {$i > $size} { set expected "\\s*" } else { set expected "" }
|
if {$i > $size} { set expected "\\s*" } else { set expected "" }
|
||||||
for {set j 0} {$j < $size && $i + $j < [llength $items]} {incr j} {
|
for {set j 0} {$j < $size && $i + $j < [llength $items]} {incr j} {
|
||||||
set item "[lindex $items [expr {$i + $j}]]"
|
set item "[lindex $items [expr {$i + $j}]]"
|
||||||
# Escape special regexp characters
|
_escape_regexp_chars item
|
||||||
regsub -all {([\^$+*?.|(){}[\]\\])} $item {\\\1} item
|
|
||||||
append expected $item
|
append expected $item
|
||||||
if {[llength $items] > 1} {append expected {\s+}};
|
if {[llength $items] > 1} {append expected {\s+}};
|
||||||
}; # for
|
}; # for
|
||||||
@ -609,6 +642,46 @@ proc source_bash_completion {} {
|
|||||||
}; # source_bash_completion()
|
}; # source_bash_completion()
|
||||||
|
|
||||||
|
|
||||||
|
# Split line into words, disregarding backslash escapes (e.g. \b (backspace),
|
||||||
|
# \g (bell)), but taking backslashed spaces into account.
|
||||||
|
# Aimed for simulating bash word splitting.
|
||||||
|
# Example usage:
|
||||||
|
#
|
||||||
|
# % set a {f cd\ \be}
|
||||||
|
# % split_words $a
|
||||||
|
# f {cd\ \be}
|
||||||
|
#
|
||||||
|
# @param string Line to split
|
||||||
|
# @return list Words
|
||||||
|
proc split_words_bash {line} {
|
||||||
|
set words {}
|
||||||
|
set glue false
|
||||||
|
foreach part [split $line] {
|
||||||
|
set glue_next false
|
||||||
|
# Does `part' end with a backslash (\)?
|
||||||
|
if {[string last "\\" $part] == [string length $part] - [string length "\\"]} {
|
||||||
|
# Remove end backslash
|
||||||
|
set part [string range $part 0 [expr [string length $part] - [string length "\\"] - 1]]
|
||||||
|
# Indicate glue on next run
|
||||||
|
set glue_next true
|
||||||
|
}; # if
|
||||||
|
# Must `part' be appended to latest word (= glue)?
|
||||||
|
if {[llength $words] > 0 && [string is true $glue]} {
|
||||||
|
# Yes, join `part' to latest word;
|
||||||
|
set zz [lindex $words [expr [llength $words] - 1]]
|
||||||
|
# Separate glue with backslash-space (\ );
|
||||||
|
lset words [expr [llength $words] - 1] "$zz\\ $part"
|
||||||
|
} else {
|
||||||
|
# No, don't append word to latest word;
|
||||||
|
# Append `part' as separate word
|
||||||
|
lappend words $part
|
||||||
|
}; # if
|
||||||
|
set glue $glue_next
|
||||||
|
}; # foreach
|
||||||
|
return $words
|
||||||
|
}; # split_words_bash()
|
||||||
|
|
||||||
|
|
||||||
# Start bash running as test environment.
|
# Start bash running as test environment.
|
||||||
proc start_bash {} {
|
proc start_bash {} {
|
||||||
global TESTDIR TOOL_EXECUTABLE spawn_id
|
global TESTDIR TOOL_EXECUTABLE spawn_id
|
||||||
|
Loading…
x
Reference in New Issue
Block a user