#!/usr/bin/env bash # The pressure valve. # # panama-hook theme-set dark orchid # # Runs ~/.config/panama/hooks/ and everything executable in # ~/.config/panama/hooks/.d/, in sorted order, with the hook's arguments. # # This exists so "can Panama also do X when the theme changes" is a five-line # file somebody drops in a directory rather than a fork, a feature request, or # a patch that has to be rebased forever. docs/UPSTREAM-INSPIRATION.md defers a # plugin host as premature and still should: this is the thirty-line version # that covers most of what people actually want from one, and it has no API to # keep stable beyond "we will run your script and tell you what happened". # # A failing hook is reported and stepped over. Somebody's broken script must # never break a theme change, an upgrade, or a login -- which is exactly what # would happen if this used `set -e` and the caller did too. # # Hooks run synchronously, so a slow one delays whatever called it. That is # deliberate: the alternative is a hook whose output arrives after the thing it # was reacting to has already finished, which is harder to reason about than a # pause. set -uo pipefail HOOK_DIR="${PANAMA_HOOK_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/panama/hooks}" name="${1:-}" if [[ -z "$name" ]]; then echo 'usage: panama-hook [args...]' >&2 exit 2 fi shift # A hook name reaches the filesystem, so it cannot be allowed to leave the # directory. Callers are all in-repo today, which is exactly when this is # cheap to add and easy to forget. if [[ ! "$name" =~ ^[a-z][a-z0-9-]*$ ]]; then echo "panama-hook: refusing hook name: $name" >&2 exit 2 fi run_one() { local script="$1" # Shifted off before the arguments are forwarded, or every hook receives # its own path as $1 and the real arguments arrive one place late. shift [[ -f "$script" && -x "$script" ]] || return 0 if ! "$script" "$@"; then printf 'panama-hook: %s failed (%s); continuing\n' \ "$(basename "$script")" "$name" >&2 fi } # The single file first, then the .d directory in sorted order. Both are # optional and having neither is the normal case. run_one "$HOOK_DIR/$name" "$@" if [[ -d "$HOOK_DIR/$name.d" ]]; then while IFS= read -r script; do [[ -n "$script" ]] || continue run_one "$script" "$@" done < <(find "$HOOK_DIR/$name.d" -maxdepth 1 -type f | sort) fi exit 0