From 0ecfe53b2d271133fac36de11ecfc0f7e47840f0 Mon Sep 17 00:00:00 2001 From: "Douglas B. Rumbaugh" Date: Sat, 6 Jun 2026 12:27:03 -0400 Subject: Initial version complete I dusted this off after years and had Claude finish it for me. caveat emptor: this is largely (though not entirely) LLM generated as of this commit --- ves.fish | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 ves.fish (limited to 'ves.fish') diff --git a/ves.fish b/ves.fish new file mode 100644 index 0000000..3315f61 --- /dev/null +++ b/ves.fish @@ -0,0 +1,104 @@ +# ves.fish +# +# fish shell shim for sh-ves. Runs each ves command in a POSIX sh subshell +# against the real implementation, then diffs the subshell's resulting +# environment against fish's and replays the changes with set -gx / set -e. +# The activation save-state is carried in exported SHVES_SAVED_* variables, +# so it survives between subshell invocations. +# +# Install this file to ~/.config/fish/functions/ves.fish (make install does +# this automatically when fish is present). + +function ves --description 'sh-ves: Bourne Shell Virtual Environment System' + if not set -q SHVES_SCRIPTS_DIR + set -gx SHVES_SCRIPTS_DIR $HOME/.local/bin/ves_scripts + end + + if not test -f "$SHVES_SCRIPTS_DIR/ves-init.sh" + printf "ERROR: sh-ves scripts not found in [%s]. Is sh-ves installed?\n" "$SHVES_SCRIPTS_DIR" >&2 + return 1 + end + + set -l dump (mktemp) + + # Resolve env(1) to an absolute path up front: the command may rewrite + # PATH inside the subshell, which would otherwise make the dump fail. + set -l envbin (command -v env) + if test -z "$envbin" + set envbin /usr/bin/env + end + + # Run the real command, then dump the subshell's final environment + # NUL-delimited (values may contain newlines) for replay into fish. + VES_ENV_DUMP=$dump VES_ENV_BIN=$envbin sh -c ' + . "$SHVES_SCRIPTS_DIR"/ves-init.sh + ves "$@" + _shves_status=$? + "$VES_ENV_BIN" -0 > "$VES_ENV_DUMP" + exit $_shves_status + ' ves $argv + set -l ret $status + + # An empty dump means the subshell could not write its environment at + # all; replaying it would wrongly erase every exported variable. + if not test -s $dump + printf "ERROR: failed to capture subshell environment, shell state unchanged.\n" >&2 + rm -f $dump + return 1 + end + + # Variables managed by the wrapper machinery itself, plus per-process + # bookkeeping that should never be replayed into the parent shell. + set -l skip VES_ENV_DUMP VES_ENV_BIN PWD OLDPWD SHLVL _ + + # fish list-style path variables: replay these split on ':' so fish + # keeps treating them as lists. + set -l pathvars PATH CDPATH MANPATH + + set -l seen + for entry in (string split0 < $dump) + set -l kv (string split -m1 = -- $entry) + set -l name $kv[1] + set -l value $kv[2] + + # entries that are not well-formed variable names (e.g. exported + # multiline values' continuation, or odd env entries) are skipped + if not string match -qr '^[a-zA-Z_][a-zA-Z0-9_]*$' -- $name + continue + end + if contains -- $name $skip + continue + end + + set -a seen $name + + if contains -- $name $pathvars + set -l new (string split : -- $value) + if not set -q $name + set -gx $name $new + else if test "$new" != "$$name" + set -gx $name $new + end + else + if not set -q $name + set -gx $name $value + else if test "$value" != "$$name" + set -gx $name $value + end + end + end + + # Anything fish exports that vanished from the subshell environment + # was unset by the command (e.g. by ves deactivate). + for name in (set -nx) + if contains -- $name $skip + continue + end + if not contains -- $name $seen + set -e $name + end + end + + rm -f $dump + return $ret +end -- cgit v1.2.3