[Bash-completion-commits] [bash-completion] 01/05: tar: rework the completion completely

Ville Skyttä scop-guest at moszumanska.debian.org
Mon Jun 1 16:26:17 UTC 2015


This is an automated email from the git hooks/post-receive script.

scop-guest pushed a commit to branch master
in repository bash-completion.

commit 8b23c84cc2935b15b8cb2ac1a90d061b914229a6
Author: Pavel Raiskup <praiskup at redhat.com>
Date:   Tue Mar 11 19:05:57 2014 +0100

    tar: rework the completion completely
    
    Use the parsed 'tar --help' output for completion, at least for
    GNU tar.  For non-GNU tars use the _posix_tar completion.  Adjust
    the testsuite to cover some improvements.
---
 completions/bsdtar               |   1 +
 completions/star                 |   1 +
 completions/tar                  | 768 ++++++++++++++++++++++++++++++++++-----
 test/fixtures/tar/archive.tar.xz | Bin 0 -> 10240 bytes
 test/fixtures/tar/dir/fileA      |   0
 test/fixtures/tar/dir/fileB      |   0
 test/fixtures/tar/dir/fileC      |   0
 test/fixtures/tar/dir/hello      |   0
 test/fixtures/tar/escape.tar     | Bin 0 -> 10240 bytes
 test/lib/completions/tar.exp     | 111 +++++-
 10 files changed, 790 insertions(+), 91 deletions(-)

diff --git a/completions/bsdtar b/completions/bsdtar
new file mode 120000
index 0000000..e1d18b0
--- /dev/null
+++ b/completions/bsdtar
@@ -0,0 +1 @@
+tar
\ No newline at end of file
diff --git a/completions/star b/completions/star
new file mode 120000
index 0000000..e1d18b0
--- /dev/null
+++ b/completions/star
@@ -0,0 +1 @@
+tar
\ No newline at end of file
diff --git a/completions/tar b/completions/tar
index 8d867d0..dc053a1 100644
--- a/completions/tar
+++ b/completions/tar
@@ -1,37 +1,455 @@
 # bash completion for GNU tar                              -*- shell-script -*-
+#
+# General info
+# ============
+#
+# The "old" style arguments
+# -------------------------
+#
+# We don't "advice" the old tar option format by default for GNU tar, example:
+#
+#   'tar czfT /tmp/archive.tar patterns.txt'
+#
+# We rather advice the 'tar -czf /tmp/archive.tar -T patterns.txt' format of
+# arguments.  Though, if user starts the 'first' tar argument without leading
+# dash, we treat the command line apropriately.
+#
+#
+# long/short options origin
+# -------------------------
+#
+# For GNU tar, everything is parsed from `tar --help` output so not so much
+# per-distribution work should be needed.  The _parse_help does not seem to be
+# good enough so parsed here directly.
+#
+#
+# FIXME: --starting-file (-K) (should be matched for extraction only)
+# FIXME: handle already used (at least short) options
+# FIXME: Test-cases for make check.
+#        - check for no global variable pollution
+# FIXME: why PS4='$BASH_SOURCE:$LINENO: ' shows sometimes negative lines?
+# FIXME: timeout on tarball listing
+# FIXME: cache 'tar --help' parsing results into global variables
+# FIXME: at least 'tar -<tab>' should show some helping text (apart from just
+#        pure option advices)
+# FIXME: short option completion should be more intuitive
+#        - verbose mode option should be adviced multiple times
+#        - mode option should be adviced only once
+#        - format option should be adviced only once
+#        ...
 
-_tar()
+__gtar_parse_help_opt()
 {
-    local cur prev words cword split
-    _init_completion -s || return
+    local opttype arg opt separator optvar
+    opttype=long
+    arg="$2"
+    opt="$1"
+    separator=" "
 
-    local ext regex tar untar
+    case "$opt" in
+        --*)
+            ;;
+        -\?)
+            return ;;
+        -*)
+            opttype=short
+            opt=${opt##-}
+            separator=
+            ;;
+        *)
+            echo >&2 "not an option $opt"
+            return 1
+            ;;
+    esac
+
+    # Remove arguments.
+    opt=${opt//\[*/}
+    opt=${opt//=*/=}
+
+    # Basic sanity.
+    opt=${opt//\"*/}
+    opt=${opt//\'*/}
+    opt=${opt//\;*/}
+
+    optvar=$opttype'_arg_'$arg
+
+    eval "$optvar=\"\$$optvar$separator\"\"$opt\""
+}
 
-    if [[ $cword -eq 1 ]]; then
-        COMPREPLY=( $( compgen -W 'c t x u r d A' -- "$cur" ) )
+
+__gtar_parse_help_line()
+{
+    local i
+
+    for i in $1; do
+        case "$i" in
+            # regular options
+            --*|-*)
+                __gtar_parse_help_opt "$i" "$2"
+                ;;
+
+            # end once there is single non-option word
+            *)
+                break;
+        esac
+    done
+}
+
+
+__gnu_tar_parse_help()
+{
+    local str line arg
+    while IFS= read line; do
+        # Ok, this requires some comment probably.  The GNU help output prints
+        # options on lines beginning with spaces.  After that, there is one
+        # or more options separated by ', ' separator string.  We are matching
+        # like this then:  ^<spaces>(<separator>?<option>)+<whatever>$
+        if [[ "$line" =~ \
+            ^[[:blank:]]{1,10}(((,[[:blank:]])?(--?([\]\[a-zA-Z0-9?-=]+))(,[[:space:]])?)+).*$ ]]; then
+
+            line=${BASH_REMATCH[1]}
+            str="${line//,/ }"
+
+            # Detect that all options on this line accept arguments (and whether
+            # the arguments are required or not).  Note that only long option
+            # description in GNU help output mentions arguments.  So the $line
+            # variable may contain e.g. '-X, --XXX[=NAME], -XXX2[=NAME]'.
+            arg=none
+            if [[ "$line" =~ --[A-Za-z0-9-]+(\[?)= ]]; then
+                test -n "${BASH_REMATCH[1]}" && arg=opt || arg=req
+            fi
+
+            __gtar_parse_help_line "$str" "$arg"
+        fi
+    done <<<"$(tar --help)"
+
+    long_opts="\
+        $long_arg_none\
+        $long_arg_opt\
+        $long_arg_req"
+
+    short_opts="$short_arg_none$short_arg_opt$short_arg_req"
+}
+
+
+# Hack: parse --warning keywords from tar's error output
+__gtar_parse_warnings()
+{
+    while IFS= read line; do
+        if [[ $line =~ ^[[:blank:]]*-[[:blank:]]*[\`\']([a-zA-Z0-9-]+)\'$ ]]; then
+            echo "${BASH_REMATCH[1]} no-${BASH_REMATCH[1]}"
+        fi
+    done <<<"$(LC_ALL=C tar --warning= 2>&1)"
+}
+
+
+# Helper to obtain last character of string.
+__tar_last_char()
+{
+    echo "${1: $(( ${#1} - 1))}"
+}
+
+
+__tar_parse_old_opt()
+{
+    local first_word char
+
+    # current word is the first word
+    test "$cword" -eq 1 -a -n "$cur" -a "${cur:0:1}" != '-' \
+        && old_opt_progress=1
+
+    # check that first argument does not begin with "-"
+    first_word=${words[1]}
+    test -n "$first_word" -a "${first_word:0:1}" != "-" \
+        && old_opt_used=1
+
+    # parse the old option (if present) contents to allow later code expect
+    # corresponding arguments
+    if test $old_opt_used -eq 1; then
+        char=${first_word:0:1}
+        while test -n "$char"; do
+            if __tar_is_argreq "$char"; then
+                old_opt_parsed+=("$char")
+            fi
+            first_word=${first_word##$char}
+            char=${first_word:0:1}
+        done
+    fi
+}
+
+
+# Make the analysis of whole command line.
+__tar_preparse_cmdline()
+{
+    local first_arg my_args tmparg i modes="ctxurdA"
+
+    shift # progname
+
+    __tar_parse_old_opt
+
+    first_arg=1
+    for i in "$@"; do
+        case "$i" in
+            --delete|--test-label)
+                tar_mode=${i:2:100}
+                tar_mode_arg=$i
+                break
+                ;;
+            --*)
+                # skip
+                ;;
+            -*[$modes]*)
+                tar_mode=${i//[^$modes]/}
+                tar_mode=${tar_mode:0:1}
+                tar_mode_arg=$i
+                break
+                ;;
+            *[$modes]*)
+                # Only the first arg may be "MODE" without leading dash
+                if test $first_arg -eq 1; then
+                    tar_mode=${i//[^$modes]/}
+                    tar_mode=${tar_mode:0:1}
+                    tar_mode_arg=$i
+                fi
+                ;;
+        esac
+        first_arg=0
+    done
+}
+
+
+# Generate completions for -f/--file.
+__tar_file_option()
+{
+    local ext="$1"
+
+    case "$tar_mode" in
+        c)
+            # no need to advise user to re-write existing tarball
+            _filedir -d
+            ;;
+        *)
+            _filedir "$ext"
+            ;;
+    esac
+}
+
+
+# Returns truth if option requires argument.  No equal sign must be pasted.
+# Accepts option in format: 'c', '-c', '--create'
+__tar_is_argreq()
+{
+    local opt
+    opt=$1
+    case "$opt" in
+        -[A-Za-z0-9?])
+            [[ "$short_arg_req" =~ ${opt##-} ]] && return 0
+            ;;
+        [A-Za-z0-9?])
+            [[ "$short_arg_req" =~ ${opt} ]] && return 0
+            ;;
+        --*)
+            [[ "$long_arg_req" =~ [[:blank:]]$opt=[[:blank:]] ]] && return 0
+            ;;
+    esac
+
+    return 1
+}
+
+
+# Called only for short parameter
+__tar_complete_mode()
+{
+    local short_modes has_mode rawopt generated \
+          allshort_raw_unused allshort_raw \
+          filler i
+
+    short_modes="ctx"
+    test x"$basic_tar" = x && short_modes="ctxurdA"
+
+    # Remove prefix when needed
+    rawopt=${cur#-}
+
+    # -c -z -x ... => czx
+    allshort_raw=${short_opts//[- ]/}
+
+    # init the 'mode' option if no option is in ${cur}
+    if test "$tar_mode" = none; then
+
+        # when user passed something like 'tar cf' do not put the '-' before
+        filler=
+        if test -z "$cur" && test x"$basic_tar" = x; then
+            filler=-
+        fi
+
+        generated=""
+        for (( i=0 ; 1; i++ )); do
+            local c="${short_modes:$i:1}"
+            test -z "$c" && break
+            generated+=" $filler$cur$c"
+        done
+
+        COMPREPLY=( $(compgen -W "$generated" ) )
         return 0
     fi
 
+    # The last short option requires argument, like '-cf<TAB>'.  Cut the
+    # completion here to enforce argument processing.
+    if test "$old_opt_progress" -eq 0 \
+        && __tar_is_argreq "$(__tar_last_char "$cur")"; then
+        COMPREPLY=( "$cur" ) && return 0
+    fi
+
+    allshort_raw_unused=${allshort_raw//[$rawopt]/}
+    if test "$tar_mode" != none; then
+        allshort_raw_unused=${allshort_raw_unused//[$short_modes]}
+    fi
+
+    generated=
+    for (( i=0 ; 1; i++ )); do
+        local c="${allshort_raw_unused:$i:1}"
+        test -z "$c" && break
+        generated+=" $cur$c"
+    done
+
+    COMPREPLY=( $( compgen -W "$generated" ) )
+
+    return 0
+}
+
+
+__gtar_complete_lopts()
+{
+    local rv
+    COMPREPLY=( $( compgen -W "$long_opts" -- "$cur" ) )
+    rv=$?
+    [[ $COMPREPLY == *= ]] && compopt -o nospace
+    return $rv
+}
+
+
+__gtar_complete_sopts()
+{
+    local generated short_mode_opts i c
+    short_mode_opts="ctxurdA"
+    generated=${short_opts//[$short_mode_opts]/}
+
+    for (( i=0 ; 1; i++ )); do
+        c="${allshort_raw_unused:$i:1}"
+        test -z "$c" && break
+        generated+=" $cur$c"
+    done
+
+    COMPREPLY=( $( compgen -W "$generated" -- "$cur" ) )
+}
+
+
+__tar_try_mode()
+{
+    case "$cur" in
+        --*)
+            # posix tar does not support long opts
+            test -n "$basic_tar" && return 0
+            __gtar_complete_lopts
+            return $?
+            ;;
+
+        -*)
+            # posix tar does not support short optios
+            test -n "$basic_tar" && return 0
+
+            __tar_complete_mode && return 0
+            ;;
+
+        *)
+            if test "$cword" -eq 1 || test "$tar_mode" = none; then
+                __tar_complete_mode && return 0
+            fi
+            ;;
+    esac
+    return 1
+}
+
+
+__tar_adjust_PREV_from_old_option()
+{
+    # deal with old style arguments here
+    # $ tar cfTC # expects this sequence of arguments:
+    # $ tar cfTC ARCHIVE_FILE PATTERNS_FILE CHANGE_DIR
+    if test "$old_opt_used" -eq 1 \
+         -a "$cword" -gt 1 \
+         -a "$cword" -lt $(( ${#old_opt_parsed[@]} + 2 ));
+    then
+        # make e.g. 'C' option from 'cffCT'
+        prev="-${old_opt_parsed[ $cword - 2 ]}"
+    fi
+}
+
+
+__tar_extract_like_mode()
+{
+    local i
+    for i in x d t delete; do
+        test "$tar_mode" = "$i" && return 0
+    done
+    return 1
+}
+
+
+__tar_try_list_archive()
+{
+    local tarball tarbin untar
+
+    __tar_extract_like_mode || return 1
+
+    # This all is just to approach directory completion from "virtual"
+    # directory structure in tarbal (for which the _filedir is unusable)
+
+    set -- "${words[@]}"
+    tarbin=$1
+    untar="tf"
+    shift
+
+    read tarball <<<"$(printf -- '%s\n' "$@" \
+        | sed -n "/^.\{1,\}$regex\$/p" | tee /tmp/jetel)"
+    if test -n "$tarball"; then
+        local IFS=$'\n'
+        COMPREPLY=($(compgen -o filenames -W "$(
+            while read line; do
+                printf "%q\n" "$(printf %q"\n" "$line")"
+            done <<<"$($tarbin $untar "$tarball" 2>/dev/null)"
+        )" -- "$(printf "%q\n" "$cur")"))
+        return 0
+    fi
+}
+
+__tar_cleanup_prev()
+{
+    if [[ "$prev" =~ ^-[a-zA-Z0-9?]*$ ]]; then
+        # transformate '-caf' ~> '-f'
+        prev="-$(__tar_last_char "$prev")"
+    fi
+}
+
+__tar_detect_ext()
+{
     local tars='@(@(tar|gem|spkg)?(.@(Z|[bgx]z|bz2|lz?(ma)))|t@([abglx]z|b?(z)2))'
+    ext="$tars"
+    regex='\(\(tar\|gem\|spkg\)\(\.\(Z\|[bgx]z\|bz2\|lz\(ma\)\?\)\)\?\|t\([abglx]z\|bz\?2\)\)'
 
-    case ${words[1]} in
+    case "$tar_mode_arg" in
         --*)
+            # Should never happen?
             ;;
         ?(-)*[cr]*f)
-            if [[ $cword -eq 2 ]]; then
-                ext='@(tar|gem|spkg)'
-                case ${words[1]} in
-                    *a*)    ext="$tars"               ;;
-                    *z*)    ext='t?(ar.)gz'           ;;
-                    *Z*)    ext='ta@(r.Z|z)'          ;;
-                    *[jy]*) ext='t@(?(ar.)bz?(2)|b2)' ;;
-                    *J*)    ext='t?(ar.)xz'           ;;
-                esac
-                _filedir "$ext"
-            else
-                _filedir
-            fi
-            return 0
+            ext='@(tar|gem|spkg)'
+            case ${words[1]} in
+                *a*)    ext="$tars"               ;;
+                *z*)    ext='t?(ar.)gz'           ;;
+                *Z*)    ext='ta@(r.Z|z)'          ;;
+                *[jy]*) ext='t@(?(ar.)bz?(2)|b2)' ;;
+                *J*)    ext='t?(ar.)xz'           ;;
+            esac
             ;;
         +([^ZzJjy])f)
             ext="$tars"
@@ -49,84 +467,258 @@ _tar()
             ext='@(@(tar|gem|spkg).@(lzma|xz)|t[lx]z)'
             regex='\(\(tar\|gem\|spkg\)\.\(lzma\|xz\)\|t[lx]z\)'
             ;;
-        *)
-            _filedir
-            return 0
-            ;;
     esac
+}
 
-    case $prev in
-        *${ext:-$tars})
-            # complete on files in tar file
-            #
-            # get name of tar file from command line
-            tar=$( sed -e 's/^.* \([^ ]*'$regex'\) .*$/\1/' <<<"${words[@]}" )
-            # devise how to untar and list it
-            untar=t${words[1]//[^Jzjyf]/}
-
-            local IFS=$'\n'
-            COMPREPLY=( $( compgen -W "$( printf '%s\n' $( tar $untar $tar \
-                2>/dev/null ) )" -- "$cur" ) )
-            return 0
-            ;;
-        -C|--directory)
-            _filedir -d
-            return 0
-            ;;
-        --atime-preserve)
-            COMPREPLY=( $( compgen -W 'replace system' -- "$cur" ) )
-            return 0
-            ;;
-        --group)
-            COMPREPLY=( $( compgen -g -- "$cur" ) )
-            return 0
-            ;;
-        --owner)
-            COMPREPLY=( $( compgen -u -- "$cur" ) )
-            return 0
-            ;;
-        -F|--info-script|--new-volume-script|--rmt-command|--rsh-command|\
-        -I|--use-compress-program)
-            compopt -o filenames
-            COMPREPLY=( $( compgen -c -- "$cur" ) )
-            return 0
-            ;;
-        --volno-file|--add-file|-T|--files-from|-X|--exclude-from|--index-file)
+
+_gtar()
+{
+    local long_opts short_opts                          \
+          long_arg_none  long_arg_opt  long_arg_req     \
+          short_arg_none short_arg_opt short_arg_req    \
+          tar_mode tar_mode_arg old_opt_progress=0      \
+          old_opt_used=0 old_opt_parsed=()
+
+    # Main mode, e.g. -x or -c (extract/creation)
+    local tar_mode=none
+
+    # The mode argument, e.g. -cpf or -c
+    # FIXME: handle long options
+    local tar_mode_arg=
+
+    if test "$_TAR_OPT_DEBUG" = 1; then
+        set -x
+        PS4="\$BASH_SOURCE:\$LINENO: "
+    fi
+
+    local cur prev words cword split
+
+    _init_completion -s || return
+
+    # Fill the {long,short}_{opts,arg*}
+    __gnu_tar_parse_help
+
+    __tar_preparse_cmdline "${words[@]}"
+
+    local ext regex tar untar
+
+    __tar_detect_ext
+
+    while true; do # just-for-easy-break while, not looping
+        __tar_adjust_PREV_from_old_option
+        __tar_posix_prev_handle && break
+        __tar_cleanup_prev
+
+        # Handle all options *REQUIRING* argument.  Optional arguments are up to
+        # user (TODO: is there any sane way to deal with this?).  This case
+        # statement successes only if there already is PREV.
+        case $prev in
+            -C|--directory)
+                _filedir -d
+                break
+                ;;
+            --atime-preserve)
+                COMPREPLY=( $( compgen -W 'replace system' -- "$cur" ) )
+                break
+                ;;
+            --group)
+                COMPREPLY=( $( compgen -g -- "$cur" ) )
+                break
+                ;;
+            --owner)
+                COMPREPLY=( $( compgen -u -- "$cur" ) )
+                break
+                ;;
+            -F|--info-script|--new-volume-script|--rmt-command|--rsh-command|\
+            -I|--use-compress-program)
+                compopt -o filenames
+                COMPREPLY=( $( compgen -c -- "$cur" ) )
+                break
+                ;;
+            --volno-file|--add-file|-T|--files-from|-X|--exclude-from|\
+            --index-file|--listed-incremental|-g)
+                _filedir
+                break
+                ;;
+            -H|--format)
+                COMPREPLY=( $( compgen -W 'gnu oldgnu pax posix ustar v7' \
+                    -- "$cur" ) )
+                break
+                ;;
+            --quoting-style)
+                COMPREPLY=( $( compgen -W 'literal shell shell-always c c-maybe
+                    escape locale clocale' -- "$cur" ) )
+                break
+                ;;
+            --totals)
+                COMPREPLY=( $( compgen -W 'SIGHUP SIGQUIT SIGINT SIGUSR1 SIGUSR2' \
+                    -- "$cur" ) )
+                break
+                ;;
+            --warning)
+                COMPREPLY=( $( compgen -W "$(__gtar_parse_warnings)" -- "$cur" ) )
+                break
+                ;;
+            --file|-f|-!(-*)f)
+                __tar_file_option "$ext"
+                break
+                ;;
+            --*)
+                # parameter with required argument but no completion yet
+                [[ " $long_arg_req " =~ \ $prev=\  ]] && break
+
+                # parameter with optional argument passed with =, something like
+                # --ocurrence=*<TAB> which is not handled above
+                [[ " $long_arg_opt " =~ \ $prev\  ]] && break
+
+                # if there is some unknown option with '=', for example
+                # (literally) user does --nonexistent=<TAB>, we do not want
+                # continue also
+                $split && break
+
+                # Most probably, when code goes here, the PREV varibale contains
+                # some string from "$long_arg_none" and we want continue.
+                ;;
+            -[a-zA-Z0-9?])
+                # argument required but no completion yet
+                [[ "$short_arg_req" =~ ${prev##-} ]] && break
+                ;;
+        esac
+
+        # safety belts
+        case "$cur" in
+            -[a-zA-Z0-9]=*)
+                # e.g. 'tar -c -f=sth' does not what user could expect
+                break
+                ;;
+        esac
+
+        # Handle the main operational mode of tar.  We should do it as soon as
+        # possible.
+        __tar_try_mode && break
+
+        # handle others
+        case "$cur" in
+            --*)
+                __gtar_complete_lopts
+                break
+                ;;
+            -*)
+                # called only if it is *not* first parameter
+                __gtar_complete_sopts
+                break
+                ;;
+        esac
+
+        # the first argument must be "mode" argument or --param, if any of those
+        # was truth - the 'break' statement would have been already called
+        test "$cword" -eq 1 && break
+
+        __tar_try_list_archive && break
+
+        # file completion on relevant files
+        if test $tar_mode != none; then
             _filedir
+        fi
+
+        break
+    done # just-for-easy-break while
+
+    if test "$_TAR_OPT_DEBUG" = 1; then
+        set +x
+        unset PS4
+    fi
+
+    return 0
+}
+
+
+__tar_posix_prev_handle()
+{
+    case "$prev" in
+        -f)
+            __tar_file_option "$ext"
             return 0
             ;;
-        -H|--format)
-            COMPREPLY=( $( compgen -W 'gnu oldgnu pax posix ustar v7' \
-                -- "$cur" ) )
-            return 0
-            ;;
-        --quoting-style)
-            COMPREPLY=( $( compgen -W 'literal shell shell-always c c-maybe
-                escape locale clocale' -- "$cur" ) )
-            return 0
-            ;;
-        --totals)
-            COMPREPLY=( $( compgen -W 'SIGHUP SIGQUIT SIGINT SIGUSR1 SIGUSR2' \
-                -- "$cur" ) )
-            return 0
-            ;;
-        --occurrence|--sparse-version|--to-command|--mode|--mtime|\
-        --tape-length|-b|--blocking-factor|--record-size|-V|--text|--backup|\
-        --exclude|--exclude-tag*|-K|--starting-file|-N|--newer|--after-date|\
-        --suffix|--strip-components|--transform|--xform|--checkpoint|\
-        --checkpoint-action|--no-quote-chars|--quote-chars|--warnings)
+        -b)
             return 0
-            ;;
     esac
 
-    $split && return 0
+    return 1
+}
+
+
+_posix_tar()
+{
+    local long_opts short_opts basic_tar                \
+          long_arg_none  long_arg_opt  long_arg_req     \
+          short_arg_none short_arg_opt short_arg_req    \
+          tar_mode tar_mode_arg old_opt_progress=0      \
+          old_opt_used=1 old_opt_parsed=()
+
+    # Main mode, e.g. -x or -c (extract/creation)
+    local tar_mode=none
+
+    # The mode argument, e.g. -cpf or -c
+    local tar_mode_arg=
+
+    local cur prev words cword split
+
+    _init_completion -s || return
+
+    basic_tar=yes
+    tar_mode=none
+
+    # relatively compatible modes are {c,t,x}
+    # relatively compatible options {b,f,m,v,w}
+    short_arg_req="fb"
+    short_arg_none="wmv"
+    short_opts="$short_arg_req$short_arg_none"
+
+    __tar_preparse_cmdline "${words[@]}"
+
+    local ext regex tar untar
+
+    __tar_detect_ext
+
+    __tar_adjust_PREV_from_old_option
+
+    __tar_posix_prev_handle && return 0
+
+    __tar_try_mode && return
+
+    __tar_try_list_archive && return
 
     # file completion on relevant files
-    _filedir "$ext"
+    _filedir
+}
 
-    return 0
+
+_tar()
+{
+    local cmd=${COMP_WORDS[0]} output line
+    read line <<<"$($cmd --version)"
+    case "$line" in
+    *GNU*)
+        _gtar "$@"
+        ;;
+    *)
+        _posix_tar "$@"
+        ;;
+    esac
 }
-[ -n "${COMP_TAR_INTERNAL_PATHS:-}" ] && complete -F _tar -o dirnames tar ||
-    complete -F _tar tar
+
+
+if [ -n "${COMP_TAR_INTERNAL_PATHS:-}" ]; then
+    complete -F _tar        -o dirnames tar
+    complete -F _gtar       -o dirnames gtar
+    complete -F _posix_tar  -o dirnames bsdtar
+    complete -F _posix_tar  -o dirnames star
+else
+    complete -F _tar        tar
+    complete -F _gtar       gtar
+    complete -F _posix_tar  bsdtar
+    complete -F _posix_tar  star
+fi
 
 # ex: ts=4 sw=4 et filetype=sh
diff --git a/test/fixtures/tar/archive.tar.xz b/test/fixtures/tar/archive.tar.xz
new file mode 100644
index 0000000..c8d2725
Binary files /dev/null and b/test/fixtures/tar/archive.tar.xz differ
diff --git a/test/fixtures/tar/dir/fileA b/test/fixtures/tar/dir/fileA
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/tar/dir/fileB b/test/fixtures/tar/dir/fileB
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/tar/dir/fileC b/test/fixtures/tar/dir/fileC
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/tar/dir/hello b/test/fixtures/tar/dir/hello
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/tar/escape.tar b/test/fixtures/tar/escape.tar
new file mode 100644
index 0000000..7af7e9f
Binary files /dev/null and b/test/fixtures/tar/escape.tar differ
diff --git a/test/lib/completions/tar.exp b/test/lib/completions/tar.exp
index 37f1ae4..70e38d0 100644
--- a/test/lib/completions/tar.exp
+++ b/test/lib/completions/tar.exp
@@ -2,19 +2,124 @@ proc setup {} {
     save_env
 }
 
-
 proc teardown {} {
-    assert_env_unmodified
+    assert_env_unmodified {/OLDPWD=/d}
 }
 
-
 setup
 
+# Detect whether system's tar is GNU tar
+set cmd "tar --version"
+send "$cmd\r"
+expect "^$cmd\r\n"
+expect {
+    -re "GNU\[^\n\]*\n" {
+        set tar_version gnu
+    }
+    -re ".*\n" {
+        set tar_version unknown
+    }
+}
+sync_after_int
+
+
+set test "old option: list escaped chars"
+assert_complete_dir "a/b\\'c/" "tar tf escape.tar a/b\\\'" $::srcdir/fixtures/tar $test
+sync_after_int
+
+# TODO: "tar tf escape.tar a/b"
 
+set test "check that any completion done"
 assert_complete_any "tar "
+sync_after_int
+
+# Use bsdtar as the it completes to only 'zc zt zx' ('tar' can be GNU tar and it
+# can would have more options)
+set test "old option: mode is not on first place"
+assert_complete {zc zt zx} "bsdtar z" $test
+sync_after_int
+
+set test "old option: test 'f' when mode is not as a first option"
+assert_complete_dir "dir/ dir2/" "tar zfc " $::srcdir/fixtures/tar
+sync_after_int
+
+set test "old option: creating archive and 'f' option"
+assert_complete_dir "dir/ dir2/" "tar cf " $::srcdir/fixtures/tar
+sync_after_int
+
+set test "old option: archive listing"
+assert_complete_dir "dir/fileA dir/fileB dir/fileC" "tar tf archive.tar.xz dir/file" $::srcdir/fixtures/tar
+sync_after_int
 
+set test "old option: check _second_ option in \"old\" argument"
+assert_complete_dir "dir/ dir2/" "bsdtar cbfvv NOT_EXISTS " $::srcdir/fixtures/tar
+sync_after_int
 
+set test "old option: create and members"
+assert_complete_dir "dir/ dir2/ archive.tar.xz escape.tar" "tar cTfvv NOT_EXISTS DONT_CREATE.tar " $::srcdir/fixtures/tar
 sync_after_int
 
+if { "$tar_version" == "gnu" } {
+    set test "check short options"
+    assert_complete_any "tar -c"
+    sync_after_int
+
+    set test "mode not as a first option"
+    assert_complete_dir "dir/ dir2/" "tar -zcf " $::srcdir/fixtures/tar
+    sync_after_int
+
+    # Only directories should be completed.
+    set test "check that we do not suggest re-writing existing archive"
+    assert_complete_dir "dir/ dir2/" "tar -cf " $::srcdir/fixtures/tar
+    sync_after_int
+
+    set test "check --file option"
+    assert_complete_dir "dir/ dir2/" "tar -c --file " $::srcdir/fixtures/tar
+    sync_after_int
+
+    set test "check --file option #2"
+    assert_complete_dir "dir/ dir2/" "tar -cvv --file " $::srcdir/fixtures/tar
+    sync_after_int
+
+    set test "archive listing"
+    assert_complete_dir "dir/fileA dir/fileB dir/fileC" "tar -tf archive.tar.xz dir/file" $::srcdir/fixtures/tar
+    sync_after_int
+
+    set test "archive listing with --file"
+    assert_complete_dir "dir/fileA dir/fileB dir/fileC" "tar -t --file archive.tar.xz dir/file" $::srcdir/fixtures/tar
+    sync_after_int
+
+    # Some random options should work:
+    set test "test random tar's long option #1"
+    assert_complete "--blocking-factor= --block-number" "tar --block" $test
+    sync_after_int
+
+    set test "test random tar's long option #2"
+    assert_complete "--owner=" "tar --own" $test -nospace
+    sync_after_int
+
+    set test "test random tar's long option #3"
+    assert_complete "--posix" "tar -cf /dev/null --posi" $test
+    sync_after_int
+
+    # --owner
+    set users [exec bash -c "compgen -A user"]
+    set test "test --owner option"
+    assert_complete $users "tar --owner=" $test
+    sync_after_int
+
+    # --group
+    set groups [exec bash -c "compgen -A group"]
+    set test "test --group option"
+    assert_complete $groups "tar --group=" $test
+    sync_after_int
+
+    # use -b for this as -b is still not handled by tar's completion
+    set test "short opt -XXXb <TAB> (arg required)"
+    assert_no_complete "tar -cvvfb " $test
+    sync_after_int
+
+    # TODO: how to test that 'tar -cf<TAB>' completes to 'tar -cf '
+}
 
 teardown

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/bash-completion/bash-completion.git



More information about the Bash-completion-commits mailing list