mirror of
https://github.com/apple/swift-argument-parser.git
synced 2026-06-06 20:18:23 +00:00
* Restrict access to symbols in BashCompletionsGenerator.swift. * Use key path instead of closure in BashCompletionsGenerator.swift. * Remove extraneous bash spacing. * Do not indent bash cases. Standardize bash indents. * Do not prefix bash cases with an open parenthesis. * Brace & quote bash variable uses. * Remove extraneous bash blank line. * Improve bash escaping. * Improve bash $cur, $prev, & $COMPREPLY. Use positional arguments passed by bash to the main completion function instead of reading from COMP_WORDS, as that can return the wrong info if completing an empty word before a non-empty word. Make $cur & $prev local & readonly. Remove unnecessary COMPREPLY=(). * Improve bash $SAP_SHELL & $SAP_SHELL_VERSION. Make them local & readonly. * Overhaul BashCompletionsGenerator.swift as [ParsableCommand.Type] extension. Inline some single-use functions. * Move bashValueCompletion(…) in BashCompletionsGenerator.swift. Move from ArgumentDefinition extension to [ParsableCommand.Type] extension. * Overhaul bash completion script generation: Attempt to emulate the much more complete & correct zsh completion script. Offer candidates for only the current positional / option value, not for all. Generate completions for positionals the same as for option values. Offer flags & options only if no prior option-terminator marker (a standalone --). Offer flags & options only if current word starts with a -, or if there are no remaining positional parameters. Parse options prior to subcommands later in the command line. Offer flags & options only once. Do not offer flags or options if the current word is an option value. * Add default help to bash completions iff no existing help subcommand. * Improve bash file & directory completions. Do not split paths with spaces into separate completions. Escape spaces in paths. Append / to directory paths. Do not use _filedir from bash-completions; use builtin bash constructs instead. Fix broken existing escaping of single quotes in file extension filters. More succinct & performant. * Disable history ! in bash completion scripts. * Do not include uppercased extensions in bash file(extensions:) completions. That behavior was a bug, not a feature. Released Swift Argument Parser documentation says: "Complete file names with the specified extensions." It does not mention including uppercase versions of the extensions. None of the other shells include uppercase versions of extensions. Why should uppercase versions be special? Why not case-insensitive matching? Why not lowercase versions? Etc. Any config depending on this behavior won't match uppercase extensions without manually being reconfigured, but there are a ton of other bug fixes that can also break compatibility. * Use single quoted string for bash list completions. bash scripts now escape single quotes in list values. Any existing list values with escapes that worked in double quotes will not work in the single quotes. But now: - list values needn't be escaped - unescaped double quotes in list values won't break the script - $ & other characters won't interact with the shell, so, e.g., command substitutions cannot cause problems * Allow bash list completions to contain spaces. * Allow bash custom completions to contain spaces. Properly refuses to complete when there are no completion candidates. Still cannot complete to an empty string. * bash custom completion of empty word followed by other words. * Prevent bash shellCommand completion scripting from breaking scripts. Eval the given command from a single-quoted string instead of running it directly in the shell. * Allow bash shellCommand completions to contain spaces. If existing shellCommand completions depend on spaces as completion delimiters, they will not work anymore. Newlines are now the only supported delimiters. Resolve #734 * Use zip(…) to generate bash positional argument numbers. * Rework bash positional case generation. * Reverse the polarity of the neutron flow of the completions help subcommand detector. * Rename flags & options vars to flagCompletions & optionCompletions, respectively, in BashCompletionsGenerator.swift. * Remove TODO comments from generated bash completion scripts. --------- Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
263 lines
8.0 KiB
Bash
263 lines
8.0 KiB
Bash
#!/bin/bash
|
|
|
|
# positional arguments:
|
|
#
|
|
# - 1: the current (sub)command's count of positional arguments
|
|
#
|
|
# required variables:
|
|
#
|
|
# - flags: the flags that the current (sub)command can accept
|
|
# - options: the options that the current (sub)command can accept
|
|
# - positional_number: value ignored
|
|
# - unparsed_words: unparsed words from the current command line
|
|
#
|
|
# modified variables:
|
|
#
|
|
# - flags: remove flags for this (sub)command that are already on the command line
|
|
# - options: remove options for this (sub)command that are already on the command line
|
|
# - positional_number: set to the current positional number
|
|
# - unparsed_words: remove all flags, options, and option values for this (sub)command
|
|
__math_offer_flags_options() {
|
|
local -ir positional_count="${1}"
|
|
positional_number=0
|
|
|
|
local was_flag_option_terminator_seen=false
|
|
local is_parsing_option_value=false
|
|
|
|
local -ar unparsed_word_indices=("${!unparsed_words[@]}")
|
|
local -i word_index
|
|
for word_index in "${unparsed_word_indices[@]}"; do
|
|
if "${is_parsing_option_value}"; then
|
|
# This word is an option value:
|
|
# Reset marker for next word iff not currently the last word
|
|
[[ "${word_index}" -ne "${unparsed_word_indices[${#unparsed_word_indices[@]} - 1]}" ]] && is_parsing_option_value=false
|
|
unset "unparsed_words[${word_index}]"
|
|
# Do not process this word as a flag or an option
|
|
continue
|
|
fi
|
|
|
|
local word="${unparsed_words["${word_index}"]}"
|
|
if ! "${was_flag_option_terminator_seen}"; then
|
|
case "${word}" in
|
|
--)
|
|
unset "unparsed_words[${word_index}]"
|
|
# by itself -- is a flag/option terminator, but if it is the last word, it is the start of a completion
|
|
if [[ "${word_index}" -ne "${unparsed_word_indices[${#unparsed_word_indices[@]} - 1]}" ]]; then
|
|
was_flag_option_terminator_seen=true
|
|
fi
|
|
continue
|
|
;;
|
|
-*)
|
|
# ${word} is a flag or an option
|
|
# If ${word} is an option, mark that the next word to be parsed is an option value
|
|
local option
|
|
for option in "${options[@]}"; do
|
|
[[ "${word}" = "${option}" ]] && is_parsing_option_value=true && break
|
|
done
|
|
|
|
# Remove ${word} from ${flags} or ${options} so it isn't offered again
|
|
local not_found=true
|
|
local -i index
|
|
for index in "${!flags[@]}"; do
|
|
if [[ "${flags[${index}]}" = "${word}" ]]; then
|
|
unset "flags[${index}]"
|
|
flags=("${flags[@]}")
|
|
not_found=false
|
|
break
|
|
fi
|
|
done
|
|
if "${not_found}"; then
|
|
for index in "${!options[@]}"; do
|
|
if [[ "${options[${index}]}" = "${word}" ]]; then
|
|
unset "options[${index}]"
|
|
options=("${options[@]}")
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
unset "unparsed_words[${word_index}]"
|
|
continue
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# ${word} is neither a flag, nor an option, nor an option value
|
|
if [[ "${positional_number}" -lt "${positional_count}" ]]; then
|
|
# ${word} is a positional
|
|
((positional_number++))
|
|
unset "unparsed_words[${word_index}]"
|
|
else
|
|
if [[ -z "${word}" ]]; then
|
|
# Could be completing a flag, option, or subcommand
|
|
positional_number=-1
|
|
else
|
|
# ${word} is a subcommand or invalid, so stop processing this (sub)command
|
|
positional_number=-2
|
|
fi
|
|
break
|
|
fi
|
|
done
|
|
|
|
unparsed_words=("${unparsed_words[@]}")
|
|
|
|
if\
|
|
! "${was_flag_option_terminator_seen}"\
|
|
&& ! "${is_parsing_option_value}"\
|
|
&& [[ ("${cur}" = -* && "${positional_number}" -ge 0) || "${positional_number}" -eq -1 ]]
|
|
then
|
|
COMPREPLY+=($(compgen -W "${flags[*]} ${options[*]}" -- "${cur}"))
|
|
fi
|
|
}
|
|
|
|
__math_add_completions() {
|
|
local completion
|
|
while IFS='' read -r completion; do
|
|
COMPREPLY+=("${completion}")
|
|
done < <(IFS=$'\n' compgen "${@}" -- "${cur}")
|
|
}
|
|
|
|
__math_custom_complete() {
|
|
if [[ -n "${cur}" || -z ${COMP_WORDS[${COMP_CWORD}]} || "${COMP_LINE:${COMP_POINT}:1}" != ' ' ]]; then
|
|
local -ar words=("${COMP_WORDS[@]}")
|
|
else
|
|
local -ar words=("${COMP_WORDS[@]::${COMP_CWORD}}" '' "${COMP_WORDS[@]:${COMP_CWORD}}")
|
|
fi
|
|
|
|
"${COMP_WORDS[0]}" "${@}" "${words[@]}"
|
|
}
|
|
|
|
_math() {
|
|
trap "$(shopt -p);$(shopt -po)" RETURN
|
|
shopt -s extglob
|
|
set +o history +o posix
|
|
|
|
local -xr SAP_SHELL=bash
|
|
local -x SAP_SHELL_VERSION
|
|
SAP_SHELL_VERSION="$(IFS='.';printf %s "${BASH_VERSINFO[*]}")"
|
|
local -r SAP_SHELL_VERSION
|
|
|
|
local -r cur="${2}"
|
|
local -r prev="${3}"
|
|
|
|
local -i positional_number
|
|
local -a unparsed_words=("${COMP_WORDS[@]:1:${COMP_CWORD}}")
|
|
|
|
local -a flags=(--version -h --help)
|
|
local -a options=()
|
|
__math_offer_flags_options 0
|
|
|
|
# Offer subcommand / subcommand argument completions
|
|
local -r subcommand="${unparsed_words[0]}"
|
|
unset 'unparsed_words[0]'
|
|
unparsed_words=("${unparsed_words[@]}")
|
|
case "${subcommand}" in
|
|
add|multiply|stats|help)
|
|
# Offer subcommand argument completions
|
|
"_math_${subcommand}"
|
|
;;
|
|
*)
|
|
# Offer subcommand completions
|
|
COMPREPLY+=($(compgen -W 'add multiply stats help' -- "${cur}"))
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_math_add() {
|
|
flags=(--hex-output -x --version -h --help)
|
|
options=()
|
|
__math_offer_flags_options 1
|
|
}
|
|
|
|
_math_multiply() {
|
|
flags=(--hex-output -x --version -h --help)
|
|
options=()
|
|
__math_offer_flags_options 1
|
|
}
|
|
|
|
_math_stats() {
|
|
flags=(--version -h --help)
|
|
options=()
|
|
__math_offer_flags_options 0
|
|
|
|
# Offer subcommand / subcommand argument completions
|
|
local -r subcommand="${unparsed_words[0]}"
|
|
unset 'unparsed_words[0]'
|
|
unparsed_words=("${unparsed_words[@]}")
|
|
case "${subcommand}" in
|
|
average|stdev|quantiles)
|
|
# Offer subcommand argument completions
|
|
"_math_stats_${subcommand}"
|
|
;;
|
|
*)
|
|
# Offer subcommand completions
|
|
COMPREPLY+=($(compgen -W 'average stdev quantiles' -- "${cur}"))
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_math_stats_average() {
|
|
flags=(--version -h --help)
|
|
options=(--kind)
|
|
__math_offer_flags_options 1
|
|
|
|
# Offer option value completions
|
|
case "${prev}" in
|
|
--kind)
|
|
__math_add_completions -W 'mean'$'\n''median'$'\n''mode'
|
|
return
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_math_stats_stdev() {
|
|
flags=(--version -h --help)
|
|
options=()
|
|
__math_offer_flags_options 1
|
|
}
|
|
|
|
_math_stats_quantiles() {
|
|
flags=(--version -h --help)
|
|
options=(--file --directory --shell --custom)
|
|
__math_offer_flags_options 3
|
|
|
|
# Offer option value completions
|
|
case "${prev}" in
|
|
--file)
|
|
__math_add_completions -o plusdirs -fX '!*.@(txt|md)'
|
|
return
|
|
;;
|
|
--directory)
|
|
__math_add_completions -d
|
|
return
|
|
;;
|
|
--shell)
|
|
__math_add_completions -W "$(eval 'head -100 /usr/share/dict/words | tail -50')"
|
|
return
|
|
;;
|
|
--custom)
|
|
__math_add_completions -W "$(__math_custom_complete ---completion stats quantiles -- --custom)"
|
|
return
|
|
;;
|
|
esac
|
|
|
|
# Offer positional completions
|
|
case "${positional_number}" in
|
|
1)
|
|
__math_add_completions -W 'alphabet'$'\n''alligator'$'\n''branch'$'\n''braggart'
|
|
return
|
|
;;
|
|
2)
|
|
__math_add_completions -W "$(__math_custom_complete ---completion stats quantiles -- customArg)"
|
|
return
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_math_help() {
|
|
flags=(--version)
|
|
options=()
|
|
__math_offer_flags_options 1
|
|
}
|
|
|
|
complete -o filenames -F _math math
|