dotfiles/bin/desk

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