zsh:functions: Rewrite resolve()

It was a mess and had to be rewritten. The new version is a bit simpler
by dropping unstable features that I never used anyway.
It still supports a verbose flag that prints every step of the process.
This commit is contained in:
2020-11-23 01:23:23 +01:00
parent 12c7fbcfc4
commit 6fed362d4c

View File

@@ -154,95 +154,78 @@ _get_config_dir() {
## Function that resolves a command to the end ## Function that resolves a command to the end
resolve() { resolve() {
# TODO: comment!! local verbose cmd old_cmd args last_line
# In script mode only the result and its arguments are printed local -a full_cmd
# The result can then be used directly by other scripts without further
# manipulation
typeset SCRIPT_MODE VERBOSE_MODE 1>&2
while getopts "sv" opt 2>/dev/null; do
case $opt in
s) SCRIPT_MODE=1;;
v) VERBOSE_MODE=1;;
*) echo "Unknown flag!" >&2
return 1;;
esac
done
shift $(( $OPTIND - 1 ))
if (( $SCRIPT_MODE )) && (( $VERBOSE_MODE )); then # Since there are multiple occasions were the same line would be printed
echo "Script and verbose mode do no work together." >&2 # multiple times, this function makes the output in combination with the
return 1 # verbose flag cleaner.
fi local uniq_print() {
if [[ "$*" != "$last_line" ]]; then
typeset THIS THIS_COMMAND THIS_ARGUMENTS 1>&2 printf "%s\n" "$*"
# When receiving a command with arguments, do not differ between last_line="$*"
# one and multiple arguments.
THIS="$*"
THIS_COMMAND="${THIS%% *}"
# ${THIS%%* } would result in THIS_COMMAND when no arguements are specified.
# We want an empty string in this case.
THIS_ARGUMENTS="${THIS#${THIS_COMMAND}}"
# Resolve all aliases
while [[ "$(which $THIS_COMMAND | head -n1)" =~ "^${THIS_COMMAND}: aliased to " ]]; do
if (( $VERBOSE_MODE )); then
echo $THIS_COMMAND$THIS_ARGUMENTS
fi fi
THIS="$(which "$THIS_COMMAND" | cut -d' ' -f4-)" }
THIS_COMMAND="${THIS%% *}"
THIS_ARGUMENTS="${THIS#${THIS_COMMAND}}$THIS_ARGUMENTS"
while getopts v flag 2>/dev/null; do
[[ "$flag" != "v" ]] || verbose=1
done done
shift $((OPTIND - 1))
command_type="$(type $THIS_COMMAND)" full_cmd=("$@")
if [[ "$command_type" =~ "^$THIS_COMMAND is a shell function from " ]]; then cmd="${full_cmd[1]}"
if (( $SCRIPT_MODE )); then
echo -n "$THIS_COMMAND$THIS_ARGUMENTS" (( ! verbose )) || uniq_print "${full_cmd[@]}"
return 0
elif (( $VERBOSE_MODE )); then # Resolve aliases
echo "$THIS_COMMAND$THIS_ARGUMENTS" while [[ "$cmd" != "$old_cmd" ]]; do
out="$(command -v "$cmd")"
# NOTE: cannot be combined for the case that $cmd is not an alias
out="${out#alias $cmd=}" # Extract the alias or command
out="${${out#\'}%\'}"
# Beware of potential empty element leading to print trailing whitespace
if (( $#full_cmd > 1)); then
full_cmd=($out "${full_cmd[2,-1]}")
else else
echo "$* is resolved to:\n$THIS_COMMAND$THIS_ARGUMENTS" full_cmd=($out)
fi fi
echo -n "${command_type}:" (( ! verbose )) || uniq_print "${full_cmd[@]}"
from_file="$(echo $command_type | cut -d' ' -f7-)" old_cmd="$cmd"
# from_file=${command_type##* } cmd="${full_cmd[1]}"
grep -En -m1 "(function[ \t]+${THIS_COMMAND}[ \t]*(\(\)|)[ \t]*{|${THIS_COMMAND}[ \t]*\(\)[ \t]*{)" "$from_file" \ done
| cut -d: -f1
else
if (( $VERBOSE_MODE )); then
echo "$THIS_COMMAND$THIS_ARGUMENTS"
fi
THIS_COMMAND="$(which $THIS_COMMAND)"
if [[ $? -ne 0 ]]; then
echo "${THIS_COMMAND%% *} not found." >&2
return 1
fi
if (( $VERBOSE_MODE )); then
echo -n "$THIS_COMMAND"
NEXT_STEP="$(file -bh $THIS_COMMAND | cut -d' ' -f4-)"
if [[ "${NEXT_STEP:0:1}" != '/' ]]; then
NEXT_STEP="${THIS_COMMAND%/*}/$NEXT_STEP"
fi
while [[ "$(file -bh $THIS_COMMAND)" =~ "^symbolic link to" && "$NEXT_STEP" != "$THIS_COMMAND" ]]; do
THIS_COMMAND=$NEXT_STEP
NEXT_STEP="$(file -bh $THIS_COMMAND | cut -d' ' -f4-)"
if [[ "${NEXT_STEP:0:1}" != '/' ]]; then
NEXT_STEP="${THIS_COMMAND%/*}/$NEXT_STEP"
fi
echo -n "\n$THIS_COMMAND" # Resolve symlinks
if [[ -e "$cmd" ]]; then
# When we are not verbose a call to realpath is sufficient and way
# faster.
if (( ! verbose )); then
cmd="$(realpath "$cmd")"
full_cmd=("$cmd" "${full_cmd[2,-1]}")
else
old_cmd=
while [[ "$cmd" != "$old_cmd" ]]; do
# Get filename with potential symlink target
out="$(stat -c "%N" "$cmd")"
# NOTE: cannot be combined for the case that $cmd is not symlink
out="${out#*-> }"
out="${${out#\'}%\'}"
# Beware of symlinks pointing to a relative path
[[ "${out[1]}" = '/' ]] || out="$(dirname "$cmd")/$out"
# Beware of potential empty element leading to print trailing
# whitespace
if (( $#full_cmd > 1)); then
full_cmd=("$out" "${full_cmd[2,-1]}")
else
full_cmd=("$out")
fi
uniq_print "${full_cmd[@]}" # verbose is set
old_cmd="$cmd"
cmd="${full_cmd[1]}"
done done
echo $THIS_ARGUMENTS
return 0
fi fi
THIS_COMMAND="$(realpath $THIS_COMMAND)"
if (( $SCRIPT_MODE )); then
echo -n "$THIS_COMMAND$THIS_ARGUMENTS"
return 0
fi
echo "$* is resolved to:\n$THIS_COMMAND$THIS_ARGUMENTS"
fi fi
uniq_print "${full_cmd[@]}"
} }
## Grep a keyword at the beginning of a line (ignoring whitespace) in a man page ## Grep a keyword at the beginning of a line (ignoring whitespace) in a man page