Skip to content

refactor: _parse_{help,usage} => _comp_compgen_help #954

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 8 commits into from
May 8, 2023
195 changes: 109 additions & 86 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -1190,10 +1190,52 @@ _comp_initialize()
return 0
}

# Helper function for _parse_help and _parse_usage.
# Helper function for _comp_compgen_help and _comp_compgen_usage.
# Obtain the help output based on the arguments.
# @param $@ args Arguments specified to the caller.
# @var[out] _lines
# @return 2 if the usage is wrong, 1 if no output is obtained, or otherwise 0.
_comp_compgen_help__get_help_lines()
{
local -a help_cmd
case ${1-} in
-)
if (($# > 1)); then
printf 'bash_completion: %s -: extra arguments for -\n' "${FUNCNAME[1]}" >&2
printf 'usage: %s -\n' "${FUNCNAME[1]}" >&2
printf 'usage: %s -c cmd args...\n' "${FUNCNAME[1]}" >&2
printf 'usage: %s [-- args...]\n' "${FUNCNAME[1]}" >&2
return 2
fi
help_cmd=(exec cat)
;;
-c)
if (($# < 2)); then
printf 'bash_completion: %s -c: no command is specified\n' "${FUNCNAME[1]}" >&2
printf 'usage: %s -\n' "${FUNCNAME[1]}" >&2
printf 'usage: %s -c cmd args...\n' "${FUNCNAME[1]}" >&2
printf 'usage: %s [-- args...]\n' "${FUNCNAME[1]}" >&2
return 2
fi
help_cmd=("${@:2}")
;;
--) shift 1 ;&
*)
local ret
_comp_dequote "${comp_args[0]-}" || ret=${comp_args[0]-}
help_cmd=("${ret:-false}" "$@")
;;
esac

local ret
_comp_split -l ret "$(LC_ALL=C "${help_cmd[@]}" 2>&1)" &&
_lines=("${ret[@]}")
}

# Helper function for _comp_compgen_help and _comp_compgen_usage.
# @var[in,out] _options Add options
# @return True (0) if an option was found, False (> 0) otherwise
# TODO: rename per API conventions, rework to use vars rather than outputting
__parse_options()
_comp_compgen_help__parse()
{
local option option2 i

Expand All @@ -1219,108 +1261,89 @@ __parse_options()
if [[ $option =~ (\[((no|dont)-?)\]). ]]; then
option2=${option/"${BASH_REMATCH[1]}"/}
option2=${option2%%[<{().[]*}
printf '%s\n' "${option2/=*/=}"
_options+=("${option2/=*/=}")
option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"}
fi

option=${option%%[<{().[]*}
option=${option/=*/=}
[[ $option ]] || return 1

printf '%s\n' "$option"
_options+=("$option")
}

# Parse GNU style help output of the given command.
# @param $1 command; if "-", read from stdin and ignore rest of args
# @param $2 command options (default: --help)
# Parse GNU style help output of the given command and generate and store
# completions in an array. The help output is produced in the way depending on
# the usage:
# usage: _comp_compgen_help - # read from stdin
# usage: _comp_compgen_help -c cmd args... # run "cmd args..."
# usage: _comp_compgen_help [[--] args...] # run "${comp_args[0]} args..."
# When no arguments are specified, `--help` is assumed.
#
# TODO: rename per API conventions, rework to use vars rather than outputting
_parse_help()
# @var[in] comp_args[0]
_comp_compgen_help()
{
local IFS=$' \t\n'
local reset_monitor=$(shopt -po monitor) reset_lastpipe=$(shopt -p lastpipe) reset_noglob=$(shopt -po noglob)
set +o monitor
shopt -s lastpipe
set -o noglob
(($#)) || set -- -- --help

local cmd=$1
local line rc=1
(
case $cmd in
-) exec cat ;;
*)
# shellcheck disable=SC2086
_comp_dequote "$cmd" && LC_ALL=C "$ret" ${2:---help} 2>&1
;;
esac
) |
while read -r line; do

[[ $line == *([[:blank:]])-* ]] || continue
# transform "-f FOO, --foo=FOO" to "-f , --foo=FOO" etc
while [[ $line =~ ((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+([,_-]+[A-Z0-9]+)?(\.\.+)?\]? ]]; do
line=${line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"}
done
__parse_options "${line// or /, }" && rc=0
local -a _lines
_comp_compgen_help__get_help_lines "$@" || return "$?"

local -a _options=()
local _line
for _line in "${_lines[@]}"; do
[[ $_line == *([[:blank:]])-* ]] || continue
# transform "-f FOO, --foo=FOO" to "-f , --foo=FOO" etc
while [[ $_line =~ ((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+([,_-]+[A-Z0-9]+)?(\.\.+)?\]? ]]; do
_line=${_line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"}
done
_comp_compgen_help__parse "${_line// or /, }"
done
((${#_options[@]})) || return 1

$reset_monitor
$reset_lastpipe
$reset_noglob
return $rc
_comp_compgen -- -W '"${_options[@]}"'
return 0
}

# Parse BSD style usage output (options in brackets) of the given command.
# @param $1 command; if "-", read from stdin and ignore rest of args
# @param $2 command options (default: --usage)
#
# TODO: rename per API conventions, rework to use vars rather than outputting
_parse_usage()
{
local IFS=$' \t\n'
local reset_monitor=$(shopt -po monitor) reset_lastpipe=$(shopt -p lastpipe) reset_noglob=$(shopt -po noglob)
set +o monitor
shopt -s lastpipe
set -o noglob

local cmd=$1
local line match option i char rc=1
(
case $cmd in
-) exec cat ;;
*)
# shellcheck disable=SC2086
_comp_dequote "$cmd" && LC_ALL=C "$ret" ${2:---usage} 2>&1
;;
esac
) |
while read -r line; do

while [[ $line =~ \[[[:space:]]*(-[^]]+)[[:space:]]*\] ]]; do
match=${BASH_REMATCH[0]}
option=${BASH_REMATCH[1]}
case $option in
-?(\[)+([a-zA-Z0-9?]))
# Treat as bundled short options
for ((i = 1; i < ${#option}; i++)); do
char=${option:i:1}
[[ $char != '[' ]] && printf '%s\n' -"$char" && rc=0
done
;;
*)
__parse_options "$option" && rc=0
;;
esac
line=${line#*"$match"}
done

# Parse BSD style usage output (options in brackets) of the given command. The
# help output is produced in the way depending on the usage:
# usage: _comp_compgen_usage - # read from stdin
# usage: _comp_compgen_usage -c cmd args... # run "cmd args..."
# usage: _comp_compgen_usage [[--] args...] # run "${comp_args[0]} args..."
# When no arguments are specified, `--usage` is assumed.
#
# @var[in] comp_args[0]
_comp_compgen_usage()
{
(($#)) || set -- -- --usage

local -a _lines
_comp_compgen_help__get_help_lines "$@" || return "$?"

local -a _options=()
local _line _match _option _i _char
for _line in "${_lines[@]}"; do
while [[ $_line =~ \[[[:space:]]*(-[^]]+)[[:space:]]*\] ]]; do
_match=${BASH_REMATCH[0]}
_option=${BASH_REMATCH[1]}
case $_option in
-?(\[)+([a-zA-Z0-9?]))
# Treat as bundled short options
for ((_i = 1; _i < ${#_option}; _i++)); do
_char=${_option:_i:1}
[[ $_char != '[' ]] && _options+=("-$_char")
done
;;
*)
_comp_compgen_help__parse "$_option"
;;
esac
_line=${_line#*"$_match"}
done
done
((${#_options[@]})) || return 1

$reset_monitor
$reset_lastpipe
$reset_noglob
return $rc
_comp_compgen -- -W '"${_options[@]}"'
return 0
}

# This function completes on signal names (minus the SIG prefix)
Expand Down
58 changes: 58 additions & 0 deletions bash_completion.d/000_bash_completion_compat.bash
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,62 @@ _tilde()
! _comp_compgen -c "$1" tilde
}

# Helper function for _parse_help and _parse_usage.
# @return True (0) if an option was found, False (> 0) otherwise
# @deprecated Use _comp_compgen_help__parse
__parse_options()
{
local -a _options=()
_comp_compgen_help__parse "$1"
printf '%s\n' "${_options[@]}"
}

# Parse GNU style help output of the given command.
# @param $1 command; if "-", read from stdin and ignore rest of args
# @param $2 command options (default: --help)
# @deprecated Use `_comp_compgen_help`. `COMPREPLY=($(compgen -W
# '$(_parse_help "$1" ...)' -- "$cur"))` can be replaced with
# `_comp_compgen_help [-- ...]`. Also, `var=($(_parse_help "$1" ...))` can
# be replaced with `_comp_compgen -Rv var help [-- ...]`.
_parse_help()
{
local -a args
if [[ $1 == - ]]; then
args=(-)
else
local ret opt IFS=$' \t\n'
_comp_dequote "$1"
_comp_split opt "${2:---help}"
args=(-c "$ret" ${opt[@]+"${opt[@]}"})
fi
local -a ret=()
_comp_compgen -Rv ret help "${args[@]}" || return 1
((${#ret[@]})) && printf '%s\n' "${ret[@]}"
return 0
}

# Parse BSD style usage output (options in brackets) of the given command.
# @param $1 command; if "-", read from stdin and ignore rest of args
# @param $2 command options (default: --usage)
# @deprecated Use `_comp_compgen_usage`. `COMPREPLY=($(compgen -W
# '$(_parse_usage "$1" ...)' -- "$cur"))` can be replaced with
# `_comp_compgen_usage [-- ...]`. `var=($(_parse_usage "$1" ...))` can be
# replaced with `_comp_compgen -Rv var usage [-- ...]`.
_parse_usage()
{
local -a args
if [[ $1 == - ]]; then
args=(-)
else
local ret opt IFS=$' \t\n'
_comp_dequote "$1"
_comp_split opt "${2:---usage}"
args=(-c "$ret" ${opt[@]+"${opt[@]}"})
fi
local -a ret=()
_comp_compgen -Rv ret usage "${args[@]}" || return 1
((${#ret[@]})) && printf '%s\n' "${ret[@]}"
return 0
}

# ex: filetype=sh
2 changes: 1 addition & 1 deletion completions/2to3
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ _comp_cmd_2to3()
[[ $was_split ]] && return

if [[ $cur == -* ]]; then
COMPREPLY=($(compgen -W '$(_parse_help "$1")' -- "$cur"))
_comp_compgen_help
[[ ${COMPREPLY-} == *= ]] && compopt -o nospace
return
fi
Expand Down
2 changes: 1 addition & 1 deletion completions/_adb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ _comp_cmd_adb()
if [[ ! $has_cmd ]]; then
local tmp=()
if [[ ! $cur || $cur == -* ]]; then
tmp+=($(compgen -W '$(_parse_help "$1" help)' -- "$cur"))
_comp_compgen -av tmp help -- help
fi
if [[ ! $cur || $cur != -* ]]; then
tmp+=($("$1" help 2>&1 | awk '$1 == "adb" { print $2 }'))
Expand Down
4 changes: 1 addition & 3 deletions completions/_cal
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ _comp_cmd_cal()
esac

if [[ $cur == -* ]]; then
COMPREPLY=($(
compgen -W '$(_parse_help "$1" || _parse_usage "$1")' -- "$cur"
))
_comp_compgen_help || _comp_compgen_usage
return
fi

Expand Down
4 changes: 1 addition & 3 deletions completions/_chsh
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ _comp_cmd_chsh()
esac

if [[ $cur == -* ]]; then
COMPREPLY=($(
compgen -W '$(_parse_help "$1" || _parse_usage "$1")' -- "$cur"
))
_comp_compgen_help || _comp_compgen_usage
else
_allowed_users
fi
Expand Down
4 changes: 1 addition & 3 deletions completions/_dmesg
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ _comp_cmd_dmesg()
;;
esac

COMPREPLY=($(
compgen -W '$(_parse_help "$1" || _parse_usage "$1")' -- "$cur"
))
_comp_compgen_help || _comp_compgen_usage
} &&
complete -F _comp_cmd_dmesg dmesg

Expand Down
2 changes: 1 addition & 1 deletion completions/_eject
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ _comp_cmd_eject()
esac

if [[ $cur == -* ]]; then
COMPREPLY=($(compgen -W '$(_parse_help "$1")' -- "$cur"))
_comp_compgen_help
return
elif [[ $prev == @(-d|--default) ]]; then
return
Expand Down
4 changes: 1 addition & 3 deletions completions/_hexdump
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ _comp_cmd_hexdump()
esac

if [[ $cur == -* ]]; then
COMPREPLY=($(
compgen -W '$(_parse_help "$1" || _parse_usage "$1")' -- "$cur"
))
_comp_compgen_help || _comp_compgen_usage
return
fi

Expand Down
2 changes: 1 addition & 1 deletion completions/_ionice
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ _comp_cmd_ionice()
esac

if [[ $cur == -* ]]; then
COMPREPLY=($(compgen -W '$(_parse_help "$1" -h)' -- "$cur"))
_comp_compgen_help -- -h
return
fi
} &&
Expand Down
2 changes: 1 addition & 1 deletion completions/_mock
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ _comp_cmd_mock()
[[ $was_split ]] && return

if [[ $cur == -* ]]; then
COMPREPLY=($(compgen -W '$(_parse_help "$1")' -- "$cur"))
_comp_compgen_help
[[ ${COMPREPLY-} == *= ]] && compopt -o nospace
else
_comp_compgen_filedir '@(?(no)src.r|s)pm'
Expand Down
2 changes: 1 addition & 1 deletion completions/_repomanage
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ _comp_cmd_repomanage()
[[ $was_split ]] && return

if [[ $cur == -* ]]; then
COMPREPLY=($(compgen -W '$(_parse_help "$1")' -- "$cur"))
_comp_compgen_help
[[ ${COMPREPLY-} == *= ]] && compopt -o nospace
else
_comp_compgen_filedir -d
Expand Down
2 changes: 1 addition & 1 deletion completions/_reptyr
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ _comp_cmd_reptyr()
esac

if [[ $cur == -* ]]; then
COMPREPLY=($(compgen -W '$(_parse_help "$1")' -- "$cur"))
_comp_compgen_help
return
fi

Expand Down
Loading