summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFreddy Vulto <fvulto@gmail.com>2010-01-29 23:24:58 +0100
committerFreddy Vulto <fvulto@gmail.com>2010-01-29 23:24:58 +0100
commit1061876bbc70f2e3d9c6d09fcd06796df2d59123 (patch)
tree3c5b00fba53625323859d1b22d41ab70c9715466
parente7d7ae81efc694e45d6667f6decc1afde407831d (diff)
parentd866854066fb3b6711cf6fc92ff648a0a12ee9d8 (diff)
downloadbash-completion-1061876bbc70f2e3d9c6d09fcd06796df2d59123.tar.gz
Merge branch 'fvu'
-rw-r--r--CHANGES1
-rw-r--r--bash_completion60
-rw-r--r--contrib/cpio6
-rw-r--r--test/completion/chown.exp3
-rw-r--r--test/lib/completions/chown.exp70
-rw-r--r--test/lib/library.exp177
6 files changed, 242 insertions, 75 deletions
diff --git a/CHANGES b/CHANGES
index 69f4015c..106f997b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -67,6 +67,7 @@ bash-completion (2.x)
[ Leonard Crestez ]
* Improve ssh -o suboption completion (Alioth: #312122).
* Fix NFS mounts completion (Alioth: #312285).
+ * Fix completion of usernames (Alioth: #311396, Debian: #511788).
[ Raphaƫl Droz ]
* Add xsltproc completion (Alioth: #311843).
diff --git a/bash_completion b/bash_completion
index b6709bd1..d59ef6f2 100644
--- a/bash_completion
+++ b/bash_completion
@@ -771,20 +771,38 @@ _installed_modules()
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()
{
local IFS=$'\n'
- cur=${cur//\\\\ / }
- if [[ $cur = *@(\\:|.)* ]]; then
- user=${cur%%*([^:.])}
- COMPREPLY=( $(compgen -P ${user/\\\\} -g -- ${cur##*[.:]}) )
+ if [[ $cur = *\\\\* || $cur = *:*:* ]]; then
+ # Give up early on if something seems horribly wrong.
+ return
+ 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
- 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
- type compopt &>/dev/null && compopt -o nospace
- COMPREPLY=( $( compgen -S : -u -- "$cur" ) )
+ # Completing a partial 'usernam<TAB>'.
+ #
+ # 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
}
@@ -908,8 +926,10 @@ complete -F _service service
_chown()
{
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
@@ -926,22 +946,22 @@ _chown()
$split && return 0
- # options completion
if [[ "$cur" == -* ]]; then
+ # Complete -options
COMPREPLY=( $( compgen -W '-c -h -f -R -v --changes --dereference \
--no-dereference --from --silent --quiet --reference --recursive \
--verbose --help --version' -- "$cur" ) )
else
- _count_args
+ local args
- case $args in
- 1)
- _usergroup
- ;;
- *)
- _filedir
- ;;
- esac
+ # The first argument is an usergroup; the rest are filedir.
+ _count_args :
+
+ if [[ $args == 1 ]]; then
+ _usergroup
+ else
+ _filedir
+ fi
fi
} # _chown()
complete -F _chown -o filenames chown
diff --git a/contrib/cpio b/contrib/cpio
index e8e4a5a5..dddfc190 100644
--- a/contrib/cpio
+++ b/contrib/cpio
@@ -11,8 +11,8 @@ _cpio()
local cur prev split=false
COMPREPLY=()
- cur=`_get_cword`
- prev=${COMP_WORDS[COMP_CWORD-1]}
+ cur=`_get_cword :`
+ prev=`_get_pword :`
_split_longopt && split=true
@@ -91,7 +91,7 @@ _cpio()
esac
fi
}
-complete -F _cpio cpio
+complete -F _cpio -o filenames cpio
}
# Local variables:
diff --git a/test/completion/chown.exp b/test/completion/chown.exp
new file mode 100644
index 00000000..05ad2300
--- /dev/null
+++ b/test/completion/chown.exp
@@ -0,0 +1,3 @@
+if {[assert_bash_type chown]} {
+ source "lib/completions/chown.exp"
+}; # if
diff --git a/test/lib/completions/chown.exp b/test/lib/completions/chown.exp
new file mode 100644
index 00000000..cc56149b
--- /dev/null
+++ b/test/lib/completions/chown.exp
@@ -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
diff --git a/test/lib/library.exp b/test/lib/library.exp
index f9895d88..fab33bff 100644
--- a/test/lib/library.exp
+++ b/test/lib/library.exp
@@ -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
# command.
-# @param list $expected
+# @param list $expected Expected completions.
# @param string $cmd Command given to generate items
# @param string $test (optional) Test title. Default is "$cmd<TAB> should show completions"
# @param string $prompt (optional) Bash prompt. Default is "/@"
@@ -113,66 +113,72 @@ proc assert_bash_list_dir {expected cmd dir {test ""} {prompt /@} {size 20}} {
# argument-to-complete and to be replaced with the longest common prefix
# of $expected. If empty string (default), `assert_complete' autodetects
# if the last argument is an argument-to-complete by checking if $cmd
-# doesn't end with whitespace. Specifying `cword' is only necessary if
-# this autodetection fails, e.g. when the last whitespace is escaped or
+# doesn't end with whitespace. Specifying `cword' should only be necessary
+# if this autodetection fails, e.g. when the last whitespace is escaped or
# quoted, e.g. "finger foo\ " or "finger 'foo "
# @param list $filters (optional) List of filters to apply to this function to tweak
# the expected completions and argument-to-complete. Possible values:
# - "ltrim_colon_completions"
# @result boolean True if successful, False if not
proc assert_complete {expected cmd {test ""} {prompt /@} {size 20} {cword ""} {filters ""}} {
- if {$test == ""} {set test "$cmd should show completions"}
- send "$cmd\t"
- if {[llength $expected] == 1} {
- expect -ex "$cmd"
- if {[lsearch -exact $filters "ltrim_colon_completions"] == -1} {
- set cmds [split $cmd]
- set cur ""; # Default to empty word to complete on
- if {[llength $cmds] > 1} {
- # Assume last word of `$cmd' is word to complete on.
- set cur [lindex $cmds [expr [llength $cmds] - 1]]
- }; # if
- # Remove second word from beginning of single item $expected
- if {[string first $cur $expected] == 0} {
- set expected [string range $expected [string length $cur] end]
- }; # if
- }; # if
+ if {[llength $expected] == 0} {
+ assert_no_complete $cmd $test
} else {
- expect -ex "$cmd\r\n"
- # Make sure expected items are unique
- set expected [lsort -unique $expected]
- }; # if
-
- if {[lsearch -exact $filters "ltrim_colon_completions"] != -1} {
- # If partial contains colon (:), remove partial from begin of items
- # See also: bash_completion.__ltrim_colon_completions()
- _ltrim_colon_completions cword expected
- }; # if
-
- if {[match_items $expected $test $prompt $size]} {
+ if {$test == ""} {set test "$cmd should show completions"}
+ send "$cmd\t"
if {[llength $expected] == 1} {
- pass "$test"
+ expect -ex "$cmd"
+
+ if {[lsearch -exact $filters "ltrim_colon_completions"] == -1} {
+ set cur ""; # Default to empty word to complete on
+ set words [split_words_bash $cmd]
+ if {[llength $words] > 1} {
+ # Assume last word of `$cmd' is word to complete on.
+ set index [expr [llength $words] - 1]
+ set cur [lindex $words $index]
+ }; # if
+ # Remove second word from beginning of single item $expected
+ if {[string first $cur $expected] == 0} {
+ set expected [list [string range $expected [string length $cur] end]]
+ }; # if
+ }; # if
} else {
- # Remove optional (partial) last argument-to-complete from `cmd',
- # E.g. "finger test@" becomes "finger"
+ expect -ex "$cmd\r\n"
+ # Make sure expected items are unique
+ set expected [lsort -unique $expected]
+ }; # if
+
+ if {[lsearch -exact $filters "ltrim_colon_completions"] != -1} {
+ # If partial contains colon (:), remove partial from begin of items
+ # See also: bash_completion.__ltrim_colon_completions()
+ _ltrim_colon_completions cword expected
+ }; # if
- if {[lsearch -exact $filters "ltrim_colon_completions"] != -1} {
- set cmd2 $cmd
+ if {[match_items $expected $test $prompt $size]} {
+ if {[llength $expected] == 1} {
+ pass "$test"
} else {
- set cmd2 [_remove_cword_from_cmd $cmd $cword]
+ # Remove optional (partial) last argument-to-complete from `cmd',
+ # E.g. "finger test@" becomes "finger"
+
+ if {[lsearch -exact $filters "ltrim_colon_completions"] != -1} {
+ set cmd2 $cmd
+ } else {
+ set cmd2 [_remove_cword_from_cmd $cmd $cword]
+ }; # if
+
+ # Determine common prefix of completions
+ set common [::textutil::string::longestCommonPrefixList $expected]
+ #if {[string length $common] > 0} {set common " $common"}
+ expect {
+ -ex "$prompt$cmd2$common" { pass "$test" }
+ -re $prompt { unresolved "$test at prompt" }
+ -re eof { unresolved "eof" }
+ }; # expect
}; # if
-
- # Determine common prefix of completions
- set common [::textutil::string::longestCommonPrefixList $expected]
- #if {[string length $common] > 0} {set common " $common"}
- expect {
- -ex "$prompt$cmd2$common" { pass "$test" }
- -re $prompt { unresolved "$test at prompt" }
- -re eof { unresolved "eof" }
- }; # expect
+ } else {
+ fail "$test"
}; # if
- } else {
- fail "$test"
}; # if
}; # assert_complete()
@@ -213,13 +219,18 @@ proc _remove_cword_from_cmd {cmd {cword ""}} {
}; # _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
proc assert_complete_any {cmd {test ""} {prompt /@}} {
if {$test == ""} {set test "$cmd should show completions"}
send "$cmd\t"
expect -ex "$cmd"
- # Escape special regexp characters
- regsub -all {([\^$+*?.|(){}[\]\\])} $cmd {\\\1} cmd
+ _escape_regexp_chars cmd
expect {
-timeout 1
# Match completions, multiple words
@@ -415,6 +426,29 @@ proc assert_exec {cmd {stdout ''} {test ''}} {
}; # 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.
# `exec sort' is used instead of `lsort' to achieve exactly the
# 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 "" }
for {set j 0} {$j < $size && $i + $j < [llength $items]} {incr j} {
set item "[lindex $items [expr {$i + $j}]]"
- # Escape special regexp characters
- regsub -all {([\^$+*?.|(){}[\]\\])} $item {\\\1} item
+ _escape_regexp_chars item
append expected $item
if {[llength $items] > 1} {append expected {\s+}};
}; # for
@@ -609,6 +642,46 @@ proc 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.
proc start_bash {} {
global TESTDIR TOOL_EXECUTABLE spawn_id