Skip to content

perf(tar): cache the parse result of GNU tar help #1362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 8, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 85 additions & 21 deletions completions/tar
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
_comp_deprecate_var 2.12 \
COMP_TAR_INTERNAL_PATHS BASH_COMPLETION_CMD_TAR_INTERNAL_PATHS

_comp_cmd_gtar__parse_help_opt()
_comp_cmd_gtar__init_parse_help_opt()
{
local opttype arg opt separator optvar
opttype=long
Expand Down Expand Up @@ -98,7 +98,7 @@ _comp_cmd_gtar__parse_help_opt()
eval "$optvar=\"\$$optvar$separator\"\"$opt\""
}

_comp_cmd_gtar__parse_help_line()
_comp_cmd_gtar__init_parse_help_line()
{
local i
local -a tmp
Expand All @@ -107,7 +107,7 @@ _comp_cmd_gtar__parse_help_line()
case "$i" in
# regular options
--* | -*)
_comp_cmd_gtar__parse_help_opt "$i" "$2"
_comp_cmd_gtar__init_parse_help_opt "$i" "$2"
;;

# end once there is single non-option word
Expand All @@ -119,8 +119,21 @@ _comp_cmd_gtar__parse_help_line()
done <<<"$1"
}

_comp_cmd_gtar__parse_help()
# Parse the output of "tar --help" with GNU tar and store the parse results
# in a global variable.
#
# @var[out] _comp_cmd_gtar__saved_opts
# The parse results are stored in this global variable in a form that can
# be evaluated by the builtin "eval".
_comp_cmd_gtar__init_parse_help()
{
local long_arg_none=""
local long_arg_opt=""
local long_arg_req=""
local short_arg_none=""
local short_arg_opt=""
local short_arg_req=""

local str line arg
while IFS= read -r line; do
# Ok, this requires some comment probably. The GNU help output prints
Expand All @@ -141,14 +154,49 @@ _comp_cmd_gtar__parse_help()
[[ ${BASH_REMATCH[1]} ]] && arg=opt || arg=req
fi

_comp_cmd_gtar__parse_help_line "$str" "$arg"
_comp_cmd_gtar__init_parse_help_line "$str" "$arg"
fi
done <<<"$(tar --help)"

long_opts="\
$long_arg_none $long_arg_opt $long_arg_req"
local long_opts="$long_arg_none $long_arg_opt $long_arg_req"

local short_opts="$short_arg_none$short_arg_opt$short_arg_req"

printf -v _comp_cmd_gtar__saved_opts '%s=%q ' \
long_opts "$long_opts" \
short_opts "$short_opts" \
long_arg_none "$long_arg_none" \
long_arg_opt "$long_arg_opt" \
long_arg_req "$long_arg_req" \
short_arg_none "$short_arg_none" \
short_arg_opt "$short_arg_opt" \
short_arg_req "$short_arg_req"

short_opts="$short_arg_none$short_arg_opt$short_arg_req"
# Clean up functions only used in initialization
unset -f _comp_cmd_gtar__init_parse_help
unset -f _comp_cmd_gtar__init_parse_help_line
unset -f _comp_cmd_gtar__init_parse_help_opt
}
_comp_cmd_gtar__init_parse_help

# Load the parse results of "tar --help" for GNU tar into variables.
#
# @var[out] long_opts
# @var[out] short_opts
# @var[out] long_arg_none
# @var[out] long_arg_opt
# @var[out] long_arg_req
# @var[out] short_arg_none
# @var[out] short_arg_opt
# @var[out] short_arg_req
# The parse results are returned in these variables.
#
# @var[in] _comp_cmd_gtar__saved_opts
# The parse results are obtained by evaluating this variable with the
# "eval" builtin.
_comp_cmd_gtar__load_opts()
{
eval -- "$_comp_cmd_gtar__saved_opts"
}

# Hack: parse --warning keywords from tar's error output
Expand All @@ -165,8 +213,15 @@ _comp_cmd_gtar__parse_warnings()
_comp_compgen -- -W '"${warnings[@]}"'
}

# @var[out] old_opt_progress
# @var[out] old_opt_used
# @var[out,array] old_opt_parsed
_comp_cmd_tar__parse_old_opt()
{
old_opt_progress=""
old_opt_used=""
old_opt_parsed=()

local first_word char

# current word is the first word
Expand All @@ -193,6 +248,10 @@ _comp_cmd_tar__parse_old_opt()
}

# Make the analysis of whole command line.
#
# @var[out] old_opt_progress
# @var[out] old_opt_used
# @var[out,array] old_opt_parsed
_comp_cmd_tar__preparse_cmdline()
{
local first_arg=set i modes="ctxurdA"
Expand Down Expand Up @@ -507,11 +566,7 @@ _comp_cmd_tar__detect_ext()

_comp_cmd_tar__gnu()
{
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="" \
old_opt_used="" old_opt_parsed=()
local basic_tar=""

# Main mode, e.g. "x" or "c" or the long forms "extract" or "create"
local tar_mode=none
Expand All @@ -528,12 +583,15 @@ _comp_cmd_tar__gnu()
fi

local cur prev words cword was_split comp_args

_comp_initialize -s -- "$@" || return

# Fill the {long,short}_{opts,arg*}
_comp_cmd_gtar__parse_help
local long_opts short_opts \
long_arg_none long_arg_opt long_arg_req \
short_arg_none short_arg_opt short_arg_req
_comp_cmd_gtar__load_opts

local old_opt_progress old_opt_used old_opt_parsed
_comp_cmd_tar__preparse_cmdline "${words[@]}"

local ext
Expand Down Expand Up @@ -694,11 +752,7 @@ _comp_cmd_tar__posix_prev_handle()

_comp_cmd_tar__posix()
{
local long_opts short_opts basic_tar=set \
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="" \
old_opt_used=set old_opt_parsed=()
local basic_tar=set

# Main mode, e.g. -x or -c (extract/creation)
local tar_mode=none
Expand All @@ -710,15 +764,25 @@ _comp_cmd_tar__posix()
local tar_compression_mode=none

local cur prev words cword was_split comp_args

_comp_initialize -s -- "$@" || return

# Initialize option variables:
# relatively compatible modes are {c,t,x}
# relatively compatible options {b,f,m,v,w}
# Note: long_{opts,arg_{none,opt}} and short_arg_opt are currently unused
local long_opts short_opts \
long_arg_none long_arg_opt long_arg_req \
short_arg_none short_arg_opt short_arg_req
long_arg_none=""
long_arg_opt=""
long_arg_req=""
long_opts=""
short_arg_req="fb"
short_arg_opt=""
short_arg_none="wmv"
short_opts="$short_arg_req$short_arg_none"

local old_opt_progress old_opt_used old_opt_parsed
_comp_cmd_tar__preparse_cmdline "${words[@]}"

local ext
Expand Down