src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/vars/env_list.go (about) 1 package vars 2 3 // Note: This doesn't have an associated env_list_tests.go because most of its functionality is 4 // tested by TestSetEnv_PATH and related tests. 5 6 import ( 7 "errors" 8 "os" 9 "strings" 10 "sync" 11 12 "src.elv.sh/pkg/errutil" 13 "src.elv.sh/pkg/eval/vals" 14 ) 15 16 var ( 17 pathListSeparator = string(os.PathListSeparator) 18 forbiddenInPath = pathListSeparator + "\x00" 19 ) 20 21 // Errors 22 var ( 23 ErrPathMustBeString = errors.New("path must be string") 24 ErrPathContainsForbiddenChar = errors.New("path cannot contain NUL byte, colon on Unix or semicolon on Windows") 25 ) 26 27 // NewEnvListVar returns a variable whose value is a list synchronized with an 28 // environment variable with the elements joined by os.PathListSeparator. 29 // 30 // Elements in the value of the variable must be strings, and cannot contain 31 // os.PathListSeparator or \0; attempting to put any in its elements will result in 32 // an error. 33 func NewEnvListVar(name string) Var { 34 return &envListVar{envName: name} 35 } 36 37 type envListVar struct { 38 sync.RWMutex 39 envName string 40 cacheFor string 41 cacheValue any 42 } 43 44 // Get returns a Value for an EnvPathList. 45 func (envli *envListVar) Get() any { 46 envli.Lock() 47 defer envli.Unlock() 48 49 value := os.Getenv(envli.envName) 50 if value == envli.cacheFor { 51 return envli.cacheValue 52 } 53 envli.cacheFor = value 54 v := vals.EmptyList 55 for _, path := range strings.Split(value, pathListSeparator) { 56 v = v.Conj(path) 57 } 58 envli.cacheValue = v 59 return envli.cacheValue 60 } 61 62 // Set sets an EnvPathList. The underlying environment variable is set. 63 func (envli *envListVar) Set(v any) error { 64 var ( 65 paths []string 66 errElement error 67 ) 68 errIterate := vals.Iterate(v, func(v any) bool { 69 s, ok := v.(string) 70 if !ok { 71 errElement = ErrPathMustBeString 72 return false 73 } 74 path := s 75 if strings.ContainsAny(path, forbiddenInPath) { 76 errElement = ErrPathContainsForbiddenChar 77 return false 78 } 79 paths = append(paths, s) 80 return true 81 }) 82 83 if errElement != nil || errIterate != nil { 84 return errutil.Multi(errElement, errIterate) 85 } 86 87 envli.Lock() 88 defer envli.Unlock() 89 os.Setenv(envli.envName, strings.Join(paths, pathListSeparator)) 90 return nil 91 }