aboutsummaryrefslogtreecommitdiffstats
path: root/ves.fish
blob: 3315f61d77626de83d621de8ab5853c2f48d84e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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