#!/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