1
0

first commit

This commit is contained in:
2026-04-06 18:54:09 +02:00
commit 2809630826
2 changed files with 279 additions and 0 deletions

44
completion.bash Normal file
View File

@@ -0,0 +1,44 @@
# goto completion -*- shell-script -*-
_comp_cmd_goto() {
local cur prev words cword comp_args
_comp_initialize -- "$@" || return
# echo "[$cur] [$prev] [${words[@]}] [$cword] [$comp_args]"
local -A opts=([-e]="" [-l]="" [-t]="")
local i
for i in "${!words[@]}"; do
[[ ${words[i]} && $i -ne $cword ]] && unset -v "opts[${words[i]}]"
case "${words[i]}" in
-e)
unset -v 'opts[-l]' 'opts[-t]'
;;
-l)
unset -v 'opts[-e]' 'opts[-t]'
;;
-t)
unset -v 'opts[-e]' 'opts[-l]'
;;
esac
done
if [[ $cur == -* ]]; then
_comp_compgen -- -W '"${!opts[@]}"'
return
fi
case $prev in
-e)
[[ -v 'opts[-e]' ]] && __goto_comp_keys
return
;;
esac
# do goto keys only if we did not have -[elt]
[[ ${words[*]} == *\ -* ]] || __goto_comp_keys
} &&
complete -F _comp_cmd_goto goto
# ex: filetype=sh

235
goto.bash Normal file
View File

@@ -0,0 +1,235 @@
#!/bin/bash
# the goto command allows changing directory using directory aliases
# directory where goto files live
readonly __GOTO_ROOT_DIR=~/.goto
# goto configuration file that contains the directory mappings
readonly __GOTO_FILE=${__GOTO_ROOT_DIR}/goto.yaml
# path to yq executable
readonly __GOTO_YQ=/usr/local/bin/yq
__goto_load() {
# load the directory mappings from the configuration file into the variable reference passed as parameter
# $1: name of the result associative array to create
local -n data_ref=$1
local entries entry key value
mapfile entries < <(${__GOTO_YQ} 'explode(.) | to_entries[] | "\(.key) \(.value)"' ${__GOTO_FILE})
data_ref=()
for entry in "${entries[@]}"; do
if [[ -z "${entry}" ]]; then
continue
fi
IFS=' ' read key value <<< "${entry}"
if [[ -z "${key}" ]] || [[ -z "${value}" ]]; then
continue
fi
data_ref["${key}"]="${value}"
done
}
__goto_comp_keys() {
# loads and initializes directory aliases completions for the goto command
local -A data
__goto_load data
_comp_compgen -- -W '"${!data[@]}"'
}
__goto_resolve_path() {
# resolves a path to its canonical form
# $1: path to resolve
readlink -m "${1/#\~/${HOME}}"
}
__goto_prompt() {
# prompt string for the terminal
local -A data
__goto_load data
local entry_key entry_value best_match="" best_key=""
for entry_key in "${!data[@]}"; do
entry_value=$(__goto_resolve_path "${data[${entry_key}]}")
# select the closest ancestor of the current working directory
if [[ "${PWD}" == "${entry_value}"* ]] && [[ ${#entry_value} -gt ${#best_match} ]]; then
best_match="${entry_value}"
best_key="${entry_key}"
fi
done
if [[ -n "${best_key}" ]]; then
local remaining="${PWD#${best_match}}"
printf '%s' "#${best_key}${remaining}"
else
local w='\w'
printf '%s' "${w@P}"
fi
}
__goto_test() {
# validates the goto configuration file
# command: goto -t
local -A data
__goto_load data
local entry_key entry_value
local -i ret_code=0
for entry_key in "${!data[@]}"; do
entry_value=$(__goto_resolve_path "${data[${entry_key}]}")
if ! [[ -e "${entry_value}" ]]; then
echo "Not found: ${entry_key}${entry_value}"
ret_code=1
elif ! [[ -d "${entry_value}" ]]; then
echo "Not a directory: ${entry_key}${entry_value}"
ret_code=1
fi
done
if (( ret_code == 0 )); then
echo 'Configuration ok'
fi
return ${ret_code}
}
__goto_edit() {
# edits the goto configuration file
# command: goto -e
nano ${__GOTO_FILE}
}
__goto_list() {
# displays the goto configuration file
# command: goto -l
local -A data
__goto_load data
local -a sorted_keys
mapfile -d '' sorted_keys < <(printf '%s\0' "${!data[@]}" | sort -z)
local entry_key entry_value
for entry_key in "${sorted_keys[@]}"; do
entry_value=$(__goto_resolve_path "${data[${entry_key}]}")
echo "${entry_key};${entry_value}"
done | column -t -s ';'
}
__goto_add() {
# adds or replaces an entry to the goto configuration file
# command: goto -a <directory alias>[:<directory path>]
# $1: directory alias and optional path
local dir_alias dir_path
IFS=':' read dir_alias dir_path <<< "$1"
if [[ -n "${dir_path}" ]]; then
dir_path=$(__goto_resolve_path "${dir_path}")
else
dir_path="${PWD}"
fi
dir_alias="${dir_alias}" dir_path="${dir_path}" ${__GOTO_YQ} -i '. | (.[env(dir_alias)] = env(dir_path))' ${__GOTO_FILE}
}
__goto_remove() {
# removes an entry from the goto configuration file
# command: goto -r <directory alias>
# $1: directory alias
dir_alias="$1" ${__GOTO_YQ} -i 'del(.[env(dir_alias)])' ${__GOTO_FILE}
}
__goto_get() {
# returns the path denoted by a directory alias
# command: goto -g <directory alias>
# $1: directory alias
local dir_path="$(dir_alias="$1" ${__GOTO_YQ} '.[env(dir_alias)] // ""' ${__GOTO_FILE})"
if [[ -n "${dir_path}" ]]; then
__goto_resolve_path "${dir_path}"
else
return 10;
fi
}
goto() {
# the goto command
local OPTIND OPT
while getopts 'a:r:g:elt' OPT; do
case "${OPT}" in
a)
__goto_add "${OPTARG}"
return
;;
r)
__goto_remove "${OPTARG}"
return
;;
g)
__goto_get "${OPTARG}"
return
;;
e)
__goto_edit
return
;;
l)
__goto_list
return
;;
t)
__goto_test
return
;;
*)
echo "goto: invalid option ${OPTARG}" >&2
return 1
;;
esac
done
shift $((OPTIND-1))
if (( $# > 1 )); then
echo 'goto: too many arguments' >&2
return 1
fi
if (( $# == 0 )); then
cd
return
fi
local dir_path="$(dir_alias="$1" ${__GOTO_YQ} '.[env(dir_alias)] // ""' ${__GOTO_FILE})"
if [[ -z "${dir_path}" ]]; then
echo "goto: $1: no such entry" >&2
return 2
fi
cd $(__goto_resolve_path "${dir_path}")
}
# complete -F __goto_completions goto
source ${__GOTO_ROOT_DIR}/completion.bash