direnv-cache/direnv-cache

131 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -eo pipefail
# Dumps all environment variables sorted alphabetically. Uses zero byte as a
# delimiter since bash variables can not contain '\0'.
dumpEnv() {
env -0 | sort -z
}
# Retrieves the value of variable name passed as $1 in dumped environment
# passed as $2 (see dumpEnv)
getByName() {
local name="$1"
local envFile="$2"
grep -z "^${name}=" $envFile | cut -z -f2- -d '=' | tr -d '\0'
}
# Produces a diff of two given environment dumps, suitable for consumption by
# bash. Given environment dumps A and B (both generated by dumpEnv),
#
# source A; source <(diffEnvs A B)
#
# shall be equivalent to
#
# source B
#
diffEnvs() {
local name
local old="$1"
local new="$2"
while IFS= read -r -d '' line; do
name=$(tr -d '\0' <<<"$line" | head -n1 | cut -f1 -d'=')
if ! [[ "$name" = DIRENV_* ]]; then
if ! getByName "$name" "$new" >/dev/null; then
echo "export -n '${name}'"
fi
fi
done <"$old"
while IFS= read -rd $'\0' line; do
name=$(tr -d '\0' <<<"$line" | head -n1 | cut -f1 -d'=')
if ! [[ "$name" = DIRENV_* ]]; then
if getByName "$name" "$old" >/dev/null; then
# found in old env, check if values match
if ! grep -z "^${line}\$" "$old" >/dev/null; then
# variable has changed
echo "export ${line@Q}"
fi
else
# variable is new
echo "export ${line@Q}"
fi
fi
done <"$new"
}
usage() {
echo "${BASH_SOURCE[0]} COMMAND"
echo
echo "Commands:"
echo -e "\tstatus,s: Show cache status"
echo -e "\treload,r: Recreate cache from .envrc"
echo -e "\tclear,c: Remove cache file for current directory"
}
getCacheFilePath() {
local cacheDir="${XDG_CACHE_HOME:-$HOME/.cache}/direnv-cache"
echo "${cacheDir}/$(pwd | sha1sum | tr -d ' -')"
}
switchToDirenvDir() {
# Find directory that contains the current .envrc. Allows direnv-cache to
# be called from subdirectories.
wd="$(pwd)"
if [[ -n $DIRENV_DIR ]]; then
wd="${DIRENV_DIR#-}"
fi
cd "$wd"
}
cmd="$1"
switchToDirenvDir
if ! [[ -e ".envrc" ]]; then
echo "Error, no .envrc found in current directory"
exit 1
fi
cacheFile=$(getCacheFilePath)
case $cmd in
reload|r)
if [[ -n $DIRENV_DIR ]]; then
# run self in clean environment (i.e. outside of the current direnv
# environment)
echo "Switching to clean environment"
exec direnv exec /proc "${BASH_SOURCE[0]}" "$@"
fi
echo "Re-creating cache"
mkdir -p "$(dirname ${cacheFile})"
dumpEnv > "${cacheFile}.pre"
source <(direnv stdlib)
echo "Re-creating recipe"
direnv exec . true > /dev/null
source "${cacheFile}.recipe"
dumpEnv > "${cacheFile}.post"
diffEnvs "${cacheFile}.pre" "${cacheFile}.post" > "$cacheFile"
rm "${cacheFile}.pre" "${cacheFile}.post"
echo "Environment cached in $cacheFile, telling direnv to reload"
direnv reload
;;
status|s)
shift
ls "$cacheFile" "$@"
;;
clear|c)
if [[ -e "$cacheFile" ]]; then
rm "$cacheFile"
fi
direnv reload
;;
*)
usage
exit 1
esac