# 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