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.
This commit is contained in:
parent
adff509ec5
commit
8b23c84cc2
1
completions/bsdtar
Symbolic link
1
completions/bsdtar
Symbolic link
@ -0,0 +1 @@
|
||||
tar
|
1
completions/star
Symbolic link
1
completions/star
Symbolic link
@ -0,0 +1 @@
|
||||
tar
|
776
completions/tar
776
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
|
||||
|
||||
if [[ $cword -eq 1 ]]; then
|
||||
COMPREPLY=( $( compgen -W 'c t x u r d A' -- "$cur" ) )
|
||||
# Remove arguments.
|
||||
opt=${opt//\[*/}
|
||||
opt=${opt//=*/=}
|
||||
|
||||
# Basic sanity.
|
||||
opt=${opt//\"*/}
|
||||
opt=${opt//\'*/}
|
||||
opt=${opt//\;*/}
|
||||
|
||||
optvar=$opttype'_arg_'$arg
|
||||
|
||||
eval "$optvar=\"\$$optvar$separator\"\"$opt\""
|
||||
}
|
||||
|
||||
|
||||
__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
|
||||
|
||||
local tars='@(@(tar|gem|spkg)?(.@(Z|[bgx]z|bz2|lz?(ma)))|t@([abglx]z|b?(z)2))'
|
||||
# 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
|
||||
|
||||
case ${words[1]} in
|
||||
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 "$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
|
||||
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)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
$split && return 0
|
||||
break
|
||||
done # just-for-easy-break while
|
||||
|
||||
# file completion on relevant files
|
||||
_filedir "$ext"
|
||||
if test "$_TAR_OPT_DEBUG" = 1; then
|
||||
set +x
|
||||
unset PS4
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
[ -n "${COMP_TAR_INTERNAL_PATHS:-}" ] && complete -F _tar -o dirnames tar ||
|
||||
complete -F _tar tar
|
||||
|
||||
|
||||
__tar_posix_prev_handle()
|
||||
{
|
||||
case "$prev" in
|
||||
-f)
|
||||
__tar_file_option "$ext"
|
||||
return 0
|
||||
;;
|
||||
-b)
|
||||
return 0
|
||||
esac
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
_tar()
|
||||
{
|
||||
local cmd=${COMP_WORDS[0]} output line
|
||||
read line <<<"$($cmd --version)"
|
||||
case "$line" in
|
||||
*GNU*)
|
||||
_gtar "$@"
|
||||
;;
|
||||
*)
|
||||
_posix_tar "$@"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
BIN
test/fixtures/tar/archive.tar.xz
vendored
Normal file
BIN
test/fixtures/tar/archive.tar.xz
vendored
Normal file
Binary file not shown.
0
test/fixtures/tar/dir/fileA
vendored
Normal file
0
test/fixtures/tar/dir/fileA
vendored
Normal file
0
test/fixtures/tar/dir/fileB
vendored
Normal file
0
test/fixtures/tar/dir/fileB
vendored
Normal file
0
test/fixtures/tar/dir/fileC
vendored
Normal file
0
test/fixtures/tar/dir/fileC
vendored
Normal file
0
test/fixtures/tar/dir/hello
vendored
Normal file
0
test/fixtures/tar/dir/hello
vendored
Normal file
BIN
test/fixtures/tar/escape.tar
vendored
Normal file
BIN
test/fixtures/tar/escape.tar
vendored
Normal file
Binary file not shown.
@ -2,19 +2,124 @@ proc setup {} {
|
||||
save_env
|
||||
}
|
||||
|
||||
|
||||
proc teardown {} {
|
||||
assert_env_unmodified
|
||||
assert_env_unmodified {/OLDPWD=/d}
|
||||
}
|
||||
|
||||
|
||||
setup
|
||||
|
||||
|
||||
assert_complete_any "tar "
|
||||
|
||||
|
||||
# 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user