334 lines
9.7 KiB
Bash
Executable file
334 lines
9.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# vim: set filetype=sh:
|
|
|
|
PREFIX="${DESK_DIR:-$HOME/.desk}"
|
|
DESKS="${DESK_DESKS_DIR:-$PREFIX/desks}"
|
|
DESKFILE_NAME=Deskfile
|
|
|
|
|
|
## Commands
|
|
|
|
cmd_version() {
|
|
echo "◲ desk 0.6.0"
|
|
}
|
|
|
|
|
|
cmd_usage() {
|
|
cmd_version
|
|
echo
|
|
cat <<_EOF
|
|
Usage:
|
|
|
|
$PROGRAM
|
|
List the current desk and any associated aliases. If no desk
|
|
is being used, display available desks.
|
|
$PROGRAM init
|
|
Initialize desk configuration.
|
|
$PROGRAM (list|ls)
|
|
List all desks along with a description.
|
|
$PROGRAM (.|go) [<desk-name-or-path> [shell-args...]]
|
|
Activate a desk. Extra arguments are passed onto shell. If called with
|
|
no arguments, look for a Deskfile in the current directory. If not a
|
|
recognized desk, try as a path to directory containing a Deskfile.
|
|
$PROGRAM run <desk-name> <cmd>
|
|
Run a command within a desk's environment then exit. Think '\$SHELL -c'.
|
|
$PROGRAM edit [desk-name]
|
|
Edit (or create) a deskfile with the name specified, otherwise
|
|
edit the active deskfile.
|
|
$PROGRAM help
|
|
Show this text.
|
|
$PROGRAM version
|
|
Show version information.
|
|
|
|
Since desk spawns a shell, to deactivate and "pop" out a desk, you
|
|
simply need to exit or otherwise end the current shell process.
|
|
_EOF
|
|
}
|
|
|
|
cmd_init() {
|
|
if [ -d "$PREFIX" ]; then
|
|
echo "Desk dir already exists at ${PREFIX}"
|
|
exit 1
|
|
fi
|
|
read -p "Where do you want to store your deskfiles? (default: ${PREFIX}): " \
|
|
NEW_PREFIX
|
|
[ -z "${NEW_PREFIX}" ] && NEW_PREFIX="$PREFIX"
|
|
|
|
if [ ! -d "${NEW_PREFIX}" ]; then
|
|
echo "${NEW_PREFIX} doesn't exist, attempting to create."
|
|
mkdir -p "$NEW_PREFIX/desks"
|
|
fi
|
|
|
|
local SHELLTYPE=$(get_running_shell)
|
|
|
|
case "${SHELLTYPE}" in
|
|
bash) local SHELLRC="${HOME}/.bashrc" ;;
|
|
fish) local SHELLRC="${HOME}/.config/fish/config.fish" ;;
|
|
zsh) local SHELLRC="${HOME}/.zshrc" ;;
|
|
esac
|
|
|
|
read -p "Where's your shell rc file? (default: ${SHELLRC}): " \
|
|
USER_SHELLRC
|
|
[ -z "${USER_SHELLRC}" ] && USER_SHELLRC="$SHELLRC"
|
|
if [ ! -f "$USER_SHELLRC" ]; then
|
|
echo "${USER_SHELLRC} doesn't exist"
|
|
exit 1
|
|
fi
|
|
|
|
printf "\n# Hook for desk activation\n" >> "$USER_SHELLRC"
|
|
|
|
# Since the hook is appended to the rc file, its exit status becomes
|
|
# the exit status of `source $USER_SHELLRC` which typically
|
|
# indicates if something went wrong. If $DESK_ENV is void, `test`
|
|
# sets exit status to 1. That, however, is part of desk's normal
|
|
# operation, so we clear exit status after that.
|
|
if [ "$SHELLTYPE" == "fish" ]; then
|
|
echo "test -n \"\$DESK_ENV\"; and . \"\$DESK_ENV\"; or true" >> "$USER_SHELLRC"
|
|
else
|
|
echo "[ -n \"\$DESK_ENV\" ] && source \"\$DESK_ENV\" || true" >> "$USER_SHELLRC"
|
|
fi
|
|
|
|
echo "Done. Start adding desks to ${NEW_PREFIX}/desks!"
|
|
}
|
|
|
|
|
|
cmd_go() {
|
|
# TODESK ($1) may either be
|
|
#
|
|
# - the name of a desk in $DESKS/
|
|
# - a path to a Deskfile
|
|
# - a directory containing a Deskfile
|
|
# - empty -> `./Deskfile`
|
|
#
|
|
local TODESK="$1"
|
|
local DESKEXT=$(get_deskfile_extension)
|
|
local DESKPATH="$(find "${DESKS}/" -name "${TODESK}${DESKEXT}" 2>/dev/null)"
|
|
|
|
local POSSIBLE_DESKFILE_DIR="${TODESK%$DESKFILE_NAME}"
|
|
if [ -z "$POSSIBLE_DESKFILE_DIR" ]; then
|
|
POSSIBLE_DESKFILE_DIR="."
|
|
fi
|
|
|
|
# If nothing could be found in $DESKS/, check to see if this is a path to
|
|
# a Deskfile.
|
|
if [[ -z "$DESKPATH" && -d "$POSSIBLE_DESKFILE_DIR" ]]; then
|
|
if [ ! -f "${POSSIBLE_DESKFILE_DIR}/${DESKFILE_NAME}" ]; then
|
|
echo "No Deskfile found in '${POSSIBLE_DESKFILE_DIR}'"
|
|
exit 1
|
|
fi
|
|
|
|
local REALPATH=$( cd $POSSIBLE_DESKFILE_DIR && pwd )
|
|
DESKPATH="${REALPATH}/${DESKFILE_NAME}"
|
|
TODESK=$(basename "$REALPATH")
|
|
fi
|
|
|
|
# Shift desk name so we can forward on all arguments to the shell.
|
|
shift;
|
|
|
|
if [ -z "$DESKPATH" ]; then
|
|
echo "Desk $TODESK (${TODESK}${DESKEXT}) not found in $DESKS"
|
|
exit 1
|
|
else
|
|
local SHELL_EXEC="$(get_running_shell)"
|
|
DESK_NAME="${TODESK}" DESK_ENV="${DESKPATH}" exec "${SHELL_EXEC}" "$@"
|
|
fi
|
|
}
|
|
|
|
|
|
cmd_run() {
|
|
local TODESK="$1"
|
|
shift;
|
|
cmd_go "$TODESK" -ic "$@"
|
|
}
|
|
|
|
|
|
# Usage: desk list [options]
|
|
# Description: List all desks along with a description
|
|
# --only-names: List only the names of the desks
|
|
# --no-format: Use ' - ' to separate names from descriptions
|
|
cmd_list() {
|
|
if [ ! -d "${DESKS}/" ]; then
|
|
echo "No desk dir! Run 'desk init'."
|
|
exit 1
|
|
fi
|
|
local SHOW_DESCRIPTIONS DESKEXT AUTO_ALIGN name desc len longest out
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--only-names) SHOW_DESCRIPTIONS=false && AUTO_ALIGN=false ;;
|
|
--no-format) AUTO_ALIGN=false ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
DESKEXT=$(get_deskfile_extension)
|
|
out=""
|
|
longest=0
|
|
|
|
while read -d '' -r f; do
|
|
name=$(basename "${f/${DESKEXT}//}")
|
|
if [[ "$SHOW_DESCRIPTIONS" = false ]]; then
|
|
out+="$name"$'\n'
|
|
else
|
|
desc=$(echo_description "$f")
|
|
out+="$name - $desc"$'\n'
|
|
len=${#name}
|
|
(( len > longest )) && longest=$len
|
|
fi
|
|
done < <(find "${DESKS}/" -name "*${DESKEXT}" -print0)
|
|
if [[ "$AUTO_ALIGN" != false ]]; then
|
|
print_aligned "$out" "$longest"
|
|
else
|
|
printf "%s" "$out"
|
|
fi
|
|
}
|
|
|
|
# Usage: desk [options]
|
|
# Description: List the current desk and any associated aliases. If no desk is being used, display available desks
|
|
# --no-format: Use ' - ' to separate alias/export/function names from their descriptions
|
|
cmd_current() {
|
|
if [ -z "$DESK_ENV" ]; then
|
|
printf "No desk activated.\n\n%s" "$(cmd_list)"
|
|
exit 1
|
|
fi
|
|
|
|
local DESKPATH=$DESK_ENV
|
|
local CALLABLES=$(get_callables "$DESKPATH")
|
|
local AUTO_ALIGN len longest out
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--no-format) AUTO_ALIGN=false ;;
|
|
esac
|
|
shift
|
|
done
|
|
printf "%s - %s\n" "$DESK_NAME" "$(echo_description "$DESKPATH")"
|
|
|
|
if [[ -n "$CALLABLES" ]]; then
|
|
|
|
longest=0
|
|
out=$'\n'
|
|
for NAME in $CALLABLES; do
|
|
# Last clause in the grep regexp accounts for fish functions.
|
|
len=$((${#NAME} + 2))
|
|
(( len > longest )) && longest=$len
|
|
local DOCLINE=$(
|
|
grep -B 1 -E \
|
|
"^(alias ${NAME}=|export ${NAME}=|(function )?${NAME}( )?\()|function $NAME" "$DESKPATH" \
|
|
| grep "#")
|
|
|
|
if [ -z "$DOCLINE" ]; then
|
|
out+=" ${NAME}"$'\n'
|
|
else
|
|
out+=" ${NAME} - ${DOCLINE##\# }"$'\n'
|
|
fi
|
|
done
|
|
|
|
if [[ "$AUTO_ALIGN" != false ]]; then
|
|
print_aligned "$out" "$longest"
|
|
else
|
|
printf "%s" "$out"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
cmd_edit() {
|
|
if [ $# -eq 0 ]; then
|
|
if [ "$DESK_NAME" == "" ]; then
|
|
echo "No desk activated."
|
|
exit 3
|
|
fi
|
|
local DESKNAME_TO_EDIT="$DESK_NAME"
|
|
elif [ $# -eq 1 ]; then
|
|
local DESKNAME_TO_EDIT="$1"
|
|
else
|
|
echo "Usage: ${PROGRAM} edit [desk-name]"
|
|
exit 1
|
|
fi
|
|
|
|
local DESKEXT=$(get_deskfile_extension)
|
|
local EDIT_PATH="${DESKS}/${DESKNAME_TO_EDIT}${DESKEXT}"
|
|
|
|
${EDITOR:-vi} "$EDIT_PATH"
|
|
}
|
|
|
|
## Utilities
|
|
|
|
FNAME_CHARS='[a-zA-Z0-9_-]'
|
|
|
|
# Echo the description of a desk. $1 is the deskfile.
|
|
echo_description() {
|
|
local descline=$(grep -E "#\s+Description" "$1")
|
|
echo "${descline##*Description: }"
|
|
}
|
|
|
|
# Echo a list of aliases, exports, and functions for a given desk
|
|
get_callables() {
|
|
local DESKPATH=$1
|
|
grep -E "^(alias |export |(function )?${FNAME_CHARS}+ ?\()|function $NAME" "$DESKPATH" \
|
|
| sed 's/alias \([^= ]*\)=.*/\1/' \
|
|
| sed 's/export \([^= ]*\)=.*/\1/' \
|
|
| sed -E "s/(function )?(${FNAME_CHARS}+) ?\(\).*/\2/" \
|
|
| sed -E "s/function (${FNAME_CHARS}+).*/\1/"
|
|
}
|
|
|
|
get_running_shell() {
|
|
# Echo the name of the parent shell via procfs, if we have it available.
|
|
# Otherwise, try to use ps with bash's parent pid.
|
|
if [ -e /proc ]; then
|
|
# Get cmdline procfile of the process running this script.
|
|
local CMDLINE_FILE="/proc/$(grep PPid /proc/$$/status | cut -f2)/cmdline"
|
|
|
|
if [ -f "$CMDLINE_FILE" ]; then
|
|
# Strip out any verion that may be attached to the shell executable.
|
|
# Strip leading dash for login shells.
|
|
local CMDLINE_SHELL=$(sed -r -e 's/\x0.*//' -e 's/^-//' "$CMDLINE_FILE")
|
|
fi
|
|
else
|
|
# Strip leading dash for login shells.
|
|
# If the parent process is a login shell, guess bash.
|
|
local CMDLINE_SHELL=$(ps -o args -p $PPID | tail -1 | sed -e 's/login/bash/' -e 's/^-//')
|
|
fi
|
|
|
|
if [ ! -z "$CMDLINE_SHELL" ]; then
|
|
basename "$CMDLINE_SHELL"
|
|
exit
|
|
fi
|
|
|
|
# Fall back to $SHELL otherwise.
|
|
basename "$SHELL"
|
|
exit
|
|
}
|
|
|
|
# Echo the extension of recognized deskfiles (.fish for fish)
|
|
get_deskfile_extension() {
|
|
if [ "$(get_running_shell)" == "fish" ]; then
|
|
echo '.fish'
|
|
else
|
|
echo '.sh'
|
|
fi
|
|
}
|
|
|
|
# Echo first argument as key/value pairs aligned into two columns; second argument is the longest key
|
|
print_aligned() {
|
|
local out=$1 longest=$2
|
|
printf "%s" "$out" | awk -v padding="$longest" -F' - ' '{
|
|
printf "%-*s %s\n", padding, $1, substr($0, index($0, " - ")+2, length($0))
|
|
}'
|
|
}
|
|
|
|
|
|
PROGRAM="${0##*/}"
|
|
|
|
case "$1" in
|
|
init) shift; cmd_init "$@" ;;
|
|
help|--help) shift; cmd_usage "$@" ;;
|
|
version|--version) shift; cmd_version "$@" ;;
|
|
ls|list) shift; cmd_list "$@" ;;
|
|
go|.) shift; cmd_go "$@" ;;
|
|
run) shift; cmd_run "$@" ;;
|
|
edit) shift; cmd_edit "$@" ;;
|
|
*) cmd_current "$@" ;;
|
|
esac
|
|
exit 0
|
|
|