mirror of
https://github.com/iridakos/goto.git
synced 2025-05-15 23:10:35 -07:00
I also made the documentation and name of file more generic as it works for both zsh and bash. I removed the shebang since this file is simply sourced by the calling shell and it doesn't have executable permissions anyway. The various functions were updated to return non-zero on errors in case anyone uses goto in other scripts. The logic for parsing ~/.goto to determine which aliases can be cleaned was simplified into an awk script that removed the array manipulation logic that was not zsh compatible.
343 lines
8.5 KiB
Bash
343 lines
8.5 KiB
Bash
# MIT License
|
|
#
|
|
# Copyright (c) 2018 Lazarus Lazaridis
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
# Changes to the given alias directory
|
|
# or executes a command based on the arguments.
|
|
function goto()
|
|
{
|
|
local target
|
|
|
|
if [ -z "$1" ]; then
|
|
# display usage and exit when no args
|
|
_goto_usage
|
|
return
|
|
fi
|
|
|
|
subcommand="$1"
|
|
shift
|
|
case "$subcommand" in
|
|
-c|--cleanup)
|
|
_goto_cleanup "$@"
|
|
;;
|
|
-r|--register) # Register an alias
|
|
_goto_register_alias "$@"
|
|
;;
|
|
-u|--unregister) # Unregister an alias
|
|
_goto_unregister_alias "$@"
|
|
;;
|
|
-l|--list)
|
|
_goto_list_aliases
|
|
;;
|
|
-h|--help)
|
|
_goto_usage
|
|
;;
|
|
-v|--version)
|
|
_goto_version
|
|
;;
|
|
*)
|
|
_goto_directory "$subcommand"
|
|
;;
|
|
esac
|
|
return $?
|
|
}
|
|
|
|
function _goto_usage()
|
|
{
|
|
cat <<\USAGE
|
|
usage: goto [<option>] <alias> [<directory>]
|
|
|
|
default usage:
|
|
goto <alias> - changes to the directory registered for the given alias
|
|
|
|
OPTIONS:
|
|
-r, --register: registers an alias
|
|
goto -r|--register <alias> <directory>
|
|
-u, --unregister: unregisters an alias
|
|
goto -u|--unregister <alias>
|
|
-l, --list: lists aliases
|
|
goto -l|--list
|
|
-c, --cleanup: cleans up non existent directory aliases
|
|
goto -c|--cleanup
|
|
-h, --help: prints this help
|
|
goto -h|--help
|
|
-v, --version: displays the version of the goto script
|
|
goto -v|--version
|
|
USAGE
|
|
}
|
|
|
|
# Displays version
|
|
function _goto_version()
|
|
{
|
|
echo "goto version 1.0.0"
|
|
}
|
|
|
|
# Expands directory.
|
|
# Helpful for ~, ., .. paths
|
|
function _goto_expand_directory()
|
|
{
|
|
cd "$1" 2>/dev/null && pwd
|
|
}
|
|
|
|
# Lists registered aliases.
|
|
function _goto_list_aliases()
|
|
{
|
|
local IFS=$'\n'
|
|
if [ -f ~/.goto ]; then
|
|
sed '/^\s*$/d' ~/.goto 2>/dev/null
|
|
else
|
|
echo "You haven't configured any directory aliases yet."
|
|
fi
|
|
}
|
|
|
|
# Registers and alias.
|
|
function _goto_register_alias()
|
|
{
|
|
if [ "$#" -ne "2" ]; then
|
|
_goto_error "usage: goto -r|--register <alias> <directory>"
|
|
return 1
|
|
fi
|
|
|
|
if ! [[ $1 =~ ^[[:alnum:]]+[a-zA-Z0-9_-]*$ ]]; then
|
|
_goto_error "invalid alias - can start with letters or digits followed by letters, digits, hyphens or underscores"
|
|
return 1
|
|
fi
|
|
|
|
local resolved
|
|
resolved=$(_goto_find_alias_directory "$1")
|
|
|
|
if [ -n "$resolved" ]; then
|
|
_goto_error "alias '$1' exists"
|
|
return 1
|
|
fi
|
|
|
|
local directory
|
|
directory=$(_goto_expand_directory "$2")
|
|
if [ -z "$directory" ]; then
|
|
_goto_error "failed to register '$1' to '$2' - can't cd to directory"
|
|
return 1
|
|
fi
|
|
|
|
# Append entry to file.
|
|
echo "$1 $directory" >> ~/.goto
|
|
echo "Alias '$1' registered successfully."
|
|
}
|
|
|
|
# Unregisters the given alias.
|
|
function _goto_unregister_alias
|
|
{
|
|
if [ "$#" -ne "1" ]; then
|
|
_goto_error "usage: goto -u|--unregister <alias>"
|
|
return 1
|
|
fi
|
|
|
|
local resolved
|
|
resolved=$(_goto_find_alias_directory "$1")
|
|
if [ -z "$resolved" ]; then
|
|
_goto_error "alias '$1' does not exist"
|
|
return 1
|
|
fi
|
|
|
|
# Delete entry from file.
|
|
sed "/^$1 /d" ~/.goto > ~/.goto_ && mv ~/.goto_ ~/.goto
|
|
echo "Alias '$1' unregistered successfully."
|
|
}
|
|
|
|
# Unregisters aliases whose directories no longer exist.
|
|
function _goto_cleanup()
|
|
{
|
|
if [ ! -f ~/.goto ]; then
|
|
return
|
|
fi
|
|
|
|
while IFS= read -r i && [ -n "$i" ]; do
|
|
echo "Cleaning up: $i"
|
|
_goto_unregister_alias "$i"
|
|
done <<< "$(awk '{al=$1; $1=""; dir=substr($0,2);
|
|
system("[ ! -d \"" dir "\" ] && echo " al)}' ~/.goto)"
|
|
}
|
|
|
|
# Changes to the given alias' directory
|
|
function _goto_directory()
|
|
{
|
|
local target
|
|
|
|
target=$(_goto_resolve_alias "$1") || return 1
|
|
|
|
cd "$target" 2> /dev/null || \
|
|
{ _goto_error "Failed to goto '$target'" && return 1; }
|
|
}
|
|
|
|
# Fetches the alias directory.
|
|
function _goto_find_alias_directory()
|
|
{
|
|
local resolved
|
|
|
|
resolved=$(sed -n "s/^$1 \\(.*\\)/\\1/p" ~/.goto 2>/dev/null)
|
|
echo "$resolved"
|
|
}
|
|
|
|
# Displays the given error.
|
|
# Used for common error output.
|
|
function _goto_error()
|
|
{
|
|
(>&2 echo "goto error: $1")
|
|
}
|
|
|
|
# Fetches alias directory, errors if it doesn't exist.
|
|
function _goto_resolve_alias()
|
|
{
|
|
local resolved
|
|
|
|
resolved=$(_goto_find_alias_directory "$1")
|
|
|
|
if [ -z "$resolved" ]; then
|
|
_goto_error "unregistered alias $1"
|
|
return 1
|
|
else
|
|
echo "${resolved}"
|
|
fi
|
|
}
|
|
|
|
# Completes the goto function with the available commands
|
|
function _complete_goto_commands()
|
|
{
|
|
local IFS=$' \t\n'
|
|
|
|
# shellcheck disable=SC2207
|
|
COMPREPLY=($(compgen -W "-r --register -u --unregister -l --list -c --cleanup -v --version" -- "$1"))
|
|
}
|
|
|
|
# Completes the goto function with the available aliases
|
|
function _complete_goto_aliases()
|
|
{
|
|
local IFS=$'\n' matches
|
|
|
|
# shellcheck disable=SC2207
|
|
matches=($(sed -n "/^$1/p" ~/.goto 2>/dev/null))
|
|
|
|
if [ "${#matches[@]}" -eq "1" ]; then
|
|
# remove the filenames attribute from the completion method
|
|
compopt +o filenames 2>/dev/null
|
|
|
|
# if you find only one alias don't append the directory
|
|
COMPREPLY=("${matches[0]// *}")
|
|
else
|
|
for i in "${!matches[@]}"; do
|
|
# remove the filenames attribute from the completion method
|
|
compopt +o filenames 2>/dev/null
|
|
|
|
if ! [[ $(uname -s) =~ Darwin* ]]; then
|
|
matches[$i]=$(printf '%*s' "-$COLUMNS" "${matches[$i]}")
|
|
|
|
COMPREPLY+=("$(compgen -W "${matches[$i]}")")
|
|
else
|
|
COMPREPLY+=("${matches[$i]// */}")
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
# Bash programmable completion for the goto function
|
|
function _complete_goto_bash()
|
|
{
|
|
local cur="${COMP_WORDS[$COMP_CWORD]}" prev
|
|
|
|
if [ "$COMP_CWORD" -eq "1" ]; then
|
|
# if we are on the first argument
|
|
if [[ $cur == -* ]]; then
|
|
# and starts like a command, prompt commands
|
|
_complete_goto_commands "$cur"
|
|
else
|
|
# and doesn't start as a command, prompt aliases
|
|
_complete_goto_aliases "$cur"
|
|
fi
|
|
elif [ "$COMP_CWORD" -eq "2" ]; then
|
|
# if we are on the second argument
|
|
prev="${COMP_WORDS[1]}"
|
|
|
|
if [[ $prev = "-u" ]] || [[ $prev = "--unregister" ]]; then
|
|
# prompt with aliases only if user tries to unregister one
|
|
_complete_goto_aliases "$cur"
|
|
fi
|
|
elif [ "$COMP_CWORD" -eq "3" ]; then
|
|
# if we are on the third argument
|
|
prev="${COMP_WORDS[1]}"
|
|
|
|
if [[ $prev = "-r" ]] || [[ $prev = "--register" ]]; then
|
|
# prompt with directories only if user tries to register an alias
|
|
local IFS=$' \t\n'
|
|
|
|
# shellcheck disable=SC2207
|
|
COMPREPLY=($(compgen -d -- "$cur"))
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Zsh programmable completion for the goto function
|
|
function _complete_goto_zsh()
|
|
{
|
|
local all_aliases=()
|
|
while IFS= read -r line; do
|
|
all_aliases+=("$line")
|
|
done <<< "$(sed -e 's/ /:/g' ~/.goto 2>/dev/null)"
|
|
|
|
local state
|
|
local -a options=(
|
|
'(1)'{-r,--register}'[registers an alias]:register:->register'
|
|
'(- 1 2)'{-u,--unregister}'[unregisters an alias]:unregister:->unregister'
|
|
'(: -)'{-l,--list}'[lists aliases]'
|
|
'(*)'{-c,--cleanup}'[cleans up non existent directory aliases]'
|
|
'(: -)'{-h,--help}'[prints this help]'
|
|
'(* -)'{-v,--version}'[displays the version of the goto script]'
|
|
)
|
|
|
|
_arguments -C \
|
|
"${options[@]}" \
|
|
'1:alias:->aliases' \
|
|
'2:dir:_files' \
|
|
&& ret=0
|
|
|
|
case ${state} in
|
|
(aliases)
|
|
_describe -t aliases 'goto aliases:' all_aliases && ret=0
|
|
;;
|
|
(unregister)
|
|
_describe -t aliases 'unregister alias:' all_aliases && ret=0
|
|
;;
|
|
esac
|
|
return $ret
|
|
}
|
|
|
|
# Register the goto completions.
|
|
if [ -n "${BASH_VERSION}" ]; then
|
|
if ! [[ $(uname -s) =~ Darwin* ]]; then
|
|
complete -o filenames -F _complete_goto_bash goto
|
|
else
|
|
complete -F _complete_goto_bash goto
|
|
fi
|
|
elif [ -n "${ZSH_VERSION}" ]; then
|
|
compdef _complete_goto_zsh goto
|
|
else
|
|
echo "Unsupported shell."
|
|
exit 1
|
|
fi
|