1package main 2 3import ( 4 "strings" 5 6 "github.com/direnv/direnv/gzenv" 7) 8 9// IgnoredKeys is list of keys we don't want to deal with 10var IgnoredKeys = map[string]bool{ 11 // direnv env config 12 "DIRENV_CONFIG": true, 13 "DIRENV_BASH": true, 14 15 // should only be available inside of the .envrc 16 "DIRENV_IN_ENVRC": true, 17 18 "COMP_WORDBREAKS": true, // Avoids segfaults in bash 19 "PS1": true, // PS1 should not be exported, fixes problem in bash 20 21 // variables that should change freely 22 "OLDPWD": true, 23 "PWD": true, 24 "SHELL": true, 25 "SHELLOPTS": true, 26 "SHLVL": true, 27 "_": true, 28} 29 30// EnvDiff represents the diff between two environments 31type EnvDiff struct { 32 Prev map[string]string `json:"p"` 33 Next map[string]string `json:"n"` 34} 35 36// NewEnvDiff is an empty constructor for EnvDiff 37func NewEnvDiff() *EnvDiff { 38 return &EnvDiff{make(map[string]string), make(map[string]string)} 39} 40 41// BuildEnvDiff analyses the changes between 'e1' and 'e2' and builds an 42// EnvDiff out of it. 43func BuildEnvDiff(e1, e2 Env) *EnvDiff { 44 diff := NewEnvDiff() 45 46 in := func(key string, e Env) bool { 47 _, ok := e[key] 48 return ok 49 } 50 51 for key := range e1 { 52 if IgnoredEnv(key) { 53 continue 54 } 55 if e2[key] != e1[key] || !in(key, e2) { 56 diff.Prev[key] = e1[key] 57 } 58 } 59 60 for key := range e2 { 61 if IgnoredEnv(key) { 62 continue 63 } 64 if e2[key] != e1[key] || !in(key, e1) { 65 diff.Next[key] = e2[key] 66 } 67 } 68 69 return diff 70} 71 72// LoadEnvDiff unmarshalls a gzenv string back into an EnvDiff. 73func LoadEnvDiff(gzenvStr string) (diff *EnvDiff, err error) { 74 diff = new(EnvDiff) 75 err = gzenv.Unmarshal(gzenvStr, diff) 76 return 77} 78 79// Any returns if the diff contains any changes. 80func (diff *EnvDiff) Any() bool { 81 return len(diff.Prev) > 0 || len(diff.Next) > 0 82} 83 84// ToShell applies the env diff as a set of commands that are understood by 85// the target `shell`. The outputted string is then meant to be evaluated in 86// the target shell. 87func (diff *EnvDiff) ToShell(shell Shell) string { 88 e := make(ShellExport) 89 90 for key := range diff.Prev { 91 _, ok := diff.Next[key] 92 if !ok { 93 e.Remove(key) 94 } 95 } 96 97 for key, value := range diff.Next { 98 e.Add(key, value) 99 } 100 101 return shell.Export(e) 102} 103 104// Patch applies the diff to the given env and returns a new env with the 105// changes applied. 106func (diff *EnvDiff) Patch(env Env) (newEnv Env) { 107 newEnv = make(Env) 108 109 for k, v := range env { 110 newEnv[k] = v 111 } 112 113 for key := range diff.Prev { 114 delete(newEnv, key) 115 } 116 117 for key, value := range diff.Next { 118 newEnv[key] = value 119 } 120 121 return newEnv 122} 123 124// Reverse flips the diff so that it applies the other way around. 125func (diff *EnvDiff) Reverse() *EnvDiff { 126 return &EnvDiff{diff.Next, diff.Prev} 127} 128 129// Serialize marshalls the environment diff to the gzenv format. 130func (diff *EnvDiff) Serialize() string { 131 return gzenv.Marshal(diff) 132} 133 134//// Utils 135 136// IgnoredEnv returns true if the key should be ignored in environment diffs. 137func IgnoredEnv(key string) bool { 138 if strings.HasPrefix(key, "__fish") { 139 return true 140 } 141 if strings.HasPrefix(key, "BASH_FUNC_") { 142 return true 143 } 144 _, found := IgnoredKeys[key] 145 return found 146} 147