-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2044 from gaelicWizard/completion/alias
completion/aliases: eliminate use of `eval`
- Loading branch information
Showing
8 changed files
with
113 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# shellcheck shell=bash | ||
about-plugin 'Automatic completion of aliases' | ||
# Load after all aliases and completions to understand what needs to be completed | ||
# BASH_IT_LOAD_PRIORITY: 800 | ||
|
||
# References: | ||
# http://superuser.com/a/437508/119764 | ||
# http://stackoverflow.com/a/1793178/1228454 | ||
|
||
# Automatically add completion for all aliases to commands having completion functions | ||
function _bash-it-component-completion-callback-on-init-aliases() { | ||
local namespace="alias_completion" | ||
local tmp_file completion_loader alias_name line completions | ||
local alias_arg_words new_completion compl_func compl_wrapper alias_defn | ||
|
||
# create array of function completion triggers, keeping multi-word triggers together | ||
IFS=$'\n' read -d '' -ra completions < <(complete -p) | ||
((${#completions[@]} == 0)) && return 0 | ||
|
||
completions=("${completions[@]##complete -* * -}") # strip all but last option plus trigger(s) | ||
completions=("${completions[@]#complete -}") # strip anything missed | ||
completions=("${completions[@]#? * }") # strip last option and arg, leaving only trigger(s) | ||
|
||
# create temporary file for wrapper functions and completions | ||
tmp_file="$(mktemp -t "${namespace}-${RANDOM}XXXXXX")" || return 1 | ||
|
||
completion_loader="$(complete -p -D 2> /dev/null | sed -Ene 's/.* -F ([^ ]*).*/\1/p')" | ||
|
||
# read in "<alias> '<aliased command>' '<command args>'" lines from defined aliases | ||
# some aliases do have backslashes that needs to be interpreted | ||
# shellcheck disable=SC2162 | ||
while read line; do | ||
line="${line#alias }" | ||
alias_name="${line%%=*}" | ||
alias_defn="${line#*=}" # alias definition | ||
alias_cmd="${alias_defn%%[[:space:]]*}" # first word of alias | ||
alias_cmd="${alias_cmd:1}" # lose opening quotation mark | ||
alias_args="${alias_defn#*[[:space:]]}" # everything after first word | ||
alias_args="${alias_args%\'}" # lose ending quotation mark | ||
|
||
# skip aliases to pipes, boolean control structures and other command lists | ||
[[ "${alias_args}" =~ [\|\&\;\)\(\n] ]] && continue | ||
# avoid expanding wildcards | ||
read -a alias_arg_words <<< "$alias_args" | ||
|
||
# skip alias if there is no completion function triggered by the aliased command | ||
if ! _bash-it-array-contains-element "$alias_cmd" "${completions[@]}"; then | ||
if [[ -n "$completion_loader" ]]; then | ||
# force loading of completions for the aliased command | ||
"${completion_loader:?}" "${alias_cmd}" | ||
# 124 means completion loader was successful | ||
[[ $? -eq 124 ]] || continue | ||
completions+=("$alias_cmd") | ||
else | ||
continue | ||
fi | ||
fi | ||
new_completion="$(complete -p "$alias_cmd" 2> /dev/null)" | ||
|
||
# create a wrapper inserting the alias arguments if any | ||
if [[ -n $alias_args ]]; then | ||
compl_func="${new_completion/#* -F /}" | ||
compl_func="${compl_func%% *}" | ||
# avoid recursive call loops by ignoring our own functions | ||
if [[ "${compl_func#_"$namespace"::}" == "$compl_func" ]]; then | ||
compl_wrapper="_${namespace}::${alias_name}" | ||
echo "function $compl_wrapper { | ||
local compl_word=\$2 | ||
local prec_word=\$3 | ||
# check if prec_word is the alias itself. if so, replace it | ||
# with the last word in the unaliased form, i.e., | ||
# alias_cmd + ' ' + alias_args. | ||
if [[ \$COMP_LINE == \"\$prec_word \$compl_word\" ]]; then | ||
prec_word='$alias_cmd $alias_args' | ||
prec_word=\${prec_word#* } | ||
fi | ||
(( COMP_CWORD += ${#alias_arg_words[@]} )) | ||
COMP_WORDS=($alias_cmd $alias_args \${COMP_WORDS[@]:1}) | ||
(( COMP_POINT -= \${#COMP_LINE} )) | ||
COMP_LINE=\${COMP_LINE/$alias_name/$alias_cmd $alias_args} | ||
(( COMP_POINT += \${#COMP_LINE} )) | ||
$compl_func \"$alias_cmd\" \"\$compl_word\" \"\$prec_word\" | ||
}" >> "$tmp_file" | ||
new_completion="${new_completion/ -F $compl_func / -F $compl_wrapper }" | ||
fi | ||
fi | ||
|
||
# replace completion trigger by alias | ||
if [[ -n $new_completion ]]; then | ||
new_completion="${new_completion% *} $alias_name" | ||
echo "$new_completion" >> "$tmp_file" | ||
fi | ||
done < <(alias -p) | ||
# shellcheck source=/dev/null | ||
source "$tmp_file" && command rm -f "$tmp_file" | ||
} | ||
|
||
_bash-it-component-completion-callback-on-init-aliases |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,105 +1,5 @@ | ||
# shellcheck shell=bash | ||
# Load after the other completions to understand what needs to be completed | ||
# BASH_IT_LOAD_PRIORITY: 365 | ||
# stub for renamed file | ||
|
||
cite about-plugin | ||
about-plugin 'Automatic completion of aliases' | ||
|
||
# References: | ||
# http://superuser.com/a/437508/119764 | ||
# http://stackoverflow.com/a/1793178/1228454 | ||
|
||
# This needs to be a plugin so it gets executed after the completions and the aliases have been defined. | ||
# Bash-it loads its components in the order | ||
# 1) Aliases | ||
# 2) Completions | ||
# 3) Plugins | ||
# 4) Custom scripts | ||
|
||
# Automatically add completion for all aliases to commands having completion functions | ||
function alias_completion { | ||
local namespace="alias_completion" | ||
local tmp_file completion_loader alias_name alias_tokens line completions | ||
local alias_arg_words new_completion compl_func compl_wrapper | ||
|
||
# parse function based completion definitions, where capture group 2 => function and 3 => trigger | ||
local compl_regex='complete( +[^ ]+)* -F ([^ ]+) ("[^"]+"|[^ ]+)' | ||
# parse alias definitions, where capture group 1 => trigger, 2 => command, 3 => command arguments | ||
local alias_regex="alias( -- | )([^=]+)='(\"[^\"]+\"|[^ ]+)(( +[^ ]+)*)'" | ||
|
||
# create array of function completion triggers, keeping multi-word triggers together | ||
eval "completions=($(complete -p | sed -Ene "/$compl_regex/s//'\3'/p"))" | ||
((${#completions[@]} == 0)) && return 0 | ||
|
||
# create temporary file for wrapper functions and completions | ||
tmp_file="$(mktemp -t "${namespace}-${RANDOM}XXXXXX")" || return 1 | ||
|
||
completion_loader="$(complete -p -D 2> /dev/null | sed -Ene 's/.* -F ([^ ]*).*/\1/p')" | ||
|
||
# read in "<alias> '<aliased command>' '<command args>'" lines from defined aliases | ||
# some aliases do have backslashes that needs to be interpreted | ||
# shellcheck disable=SC2162 | ||
while read line; do | ||
eval "alias_tokens=($line)" 2> /dev/null || continue # some alias arg patterns cause an eval parse error | ||
# shellcheck disable=SC2154 # see `eval` above | ||
alias_name="${alias_tokens[0]}" alias_cmd="${alias_tokens[1]}" alias_args="${alias_tokens[2]# }" | ||
|
||
# skip aliases to pipes, boolean control structures and other command lists | ||
# (leveraging that eval errs out if $alias_args contains unquoted shell metacharacters) | ||
eval "alias_arg_words=($alias_args)" 2> /dev/null || continue | ||
# avoid expanding wildcards | ||
read -a alias_arg_words <<< "$alias_args" | ||
|
||
# skip alias if there is no completion function triggered by the aliased command | ||
if ! _bash-it-array-contains-element "$alias_cmd" "${completions[@]}"; then | ||
if [[ -n "$completion_loader" ]]; then | ||
# force loading of completions for the aliased command | ||
eval "$completion_loader $alias_cmd" | ||
# 124 means completion loader was successful | ||
[[ $? -eq 124 ]] || continue | ||
completions+=("$alias_cmd") | ||
else | ||
continue | ||
fi | ||
fi | ||
new_completion="$(complete -p "$alias_cmd" 2> /dev/null)" | ||
|
||
# create a wrapper inserting the alias arguments if any | ||
if [[ -n $alias_args ]]; then | ||
compl_func="${new_completion/#* -F /}" | ||
compl_func="${compl_func%% *}" | ||
# avoid recursive call loops by ignoring our own functions | ||
if [[ "${compl_func#_"$namespace"::}" == "$compl_func" ]]; then | ||
compl_wrapper="_${namespace}::${alias_name}" | ||
echo "function $compl_wrapper { | ||
local compl_word=\$2 | ||
local prec_word=\$3 | ||
# check if prec_word is the alias itself. if so, replace it | ||
# with the last word in the unaliased form, i.e., | ||
# alias_cmd + ' ' + alias_args. | ||
if [[ \$COMP_LINE == \"\$prec_word \$compl_word\" ]]; then | ||
prec_word='$alias_cmd $alias_args' | ||
prec_word=\${prec_word#* } | ||
fi | ||
(( COMP_CWORD += ${#alias_arg_words[@]} )) | ||
COMP_WORDS=($alias_cmd $alias_args \${COMP_WORDS[@]:1}) | ||
(( COMP_POINT -= \${#COMP_LINE} )) | ||
COMP_LINE=\${COMP_LINE/$alias_name/$alias_cmd $alias_args} | ||
(( COMP_POINT += \${#COMP_LINE} )) | ||
$compl_func \"$alias_cmd\" \"\$compl_word\" \"\$prec_word\" | ||
}" >> "$tmp_file" | ||
new_completion="${new_completion/ -F $compl_func / -F $compl_wrapper }" | ||
fi | ||
fi | ||
|
||
# replace completion trigger by alias | ||
if [[ -n $new_completion ]]; then | ||
new_completion="${new_completion% *} $alias_name" | ||
echo "$new_completion" >> "$tmp_file" | ||
fi | ||
done < <(alias -p | sed -Ene "s/$alias_regex/\2 '\3' '\4'/p") | ||
# shellcheck source=/dev/null | ||
source "$tmp_file" && command rm -f "$tmp_file" | ||
} | ||
|
||
alias_completion | ||
_enable-completion aliases && _disable-plugin alias-completion | ||
source "${BASH_IT?}/completion/aliases.completion.bash" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters