[Bash-completion-commits] [SCM] bash-completion branch, master, updated. 1061876bbc70f2e3d9c6d09fcd06796df2d59123

Freddy Vulto fvulto at gmail.com
Fri Jan 29 22:25:14 UTC 2010


The following commit has been merged in the master branch:
commit d866854066fb3b6711cf6fc92ff648a0a12ee9d8
Author: Freddy Vulto <fvulto at gmail.com>
Date:   Fri Jan 29 23:23:30 2010 +0100

    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

diff --git a/CHANGES b/CHANGES
index dc626b1..bb9f57d 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 b01d495..57284a4 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 e8e4a5a..dddfc19 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 0000000..05ad230
--- /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 0000000..cc56149
--- /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 ade7873..6b56e49 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

-- 
bash-completion



More information about the Bash-completion-commits mailing list