github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/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/diag" 13 "src.elv.sh/pkg/eval/vals" 14 "src.elv.sh/pkg/persistent/vector" 15 ) 16 17 var ( 18 pathListSeparator = string(os.PathListSeparator) 19 forbiddenInPath = pathListSeparator + "\x00" 20 ) 21 22 // Errors 23 var ( 24 ErrPathMustBeString = errors.New("path must be string") 25 ErrPathContainsForbiddenChar = errors.New("path cannot contain NUL byte, colon on UNIX or semicolon on Windows") 26 ) 27 28 // NewEnvListVar returns a variable whose value is a list synchronized with an 29 // environment variable with the elements joined by os.PathListSeparator. 30 // 31 // Elements in the value of the variable must be strings, and cannot contain 32 // os.PathListSeparator or \0; attempting to put any in its elements will result in 33 // an error. 34 func NewEnvListVar(name string) Var { 35 return &envListVar{envName: name} 36 } 37 38 type envListVar struct { 39 sync.RWMutex 40 envName string 41 cacheFor string 42 cacheValue interface{} 43 } 44 45 // Get returns a Value for an EnvPathList. 46 func (envli *envListVar) Get() interface{} { 47 envli.Lock() 48 defer envli.Unlock() 49 50 value := os.Getenv(envli.envName) 51 if value == envli.cacheFor { 52 return envli.cacheValue 53 } 54 envli.cacheFor = value 55 v := vector.Empty 56 for _, path := range strings.Split(value, pathListSeparator) { 57 v = v.Cons(path) 58 } 59 envli.cacheValue = v 60 return envli.cacheValue 61 } 62 63 // Set sets an EnvPathList. The underlying environment variable is set. 64 func (envli *envListVar) Set(v interface{}) error { 65 var ( 66 paths []string 67 errElement error 68 ) 69 errIterate := vals.Iterate(v, func(v interface{}) bool { 70 s, ok := v.(string) 71 if !ok { 72 errElement = ErrPathMustBeString 73 return false 74 } 75 path := s 76 if strings.ContainsAny(path, forbiddenInPath) { 77 errElement = ErrPathContainsForbiddenChar 78 return false 79 } 80 paths = append(paths, s) 81 return true 82 }) 83 84 if errElement != nil || errIterate != nil { 85 return diag.Errors(errElement, errIterate) 86 } 87 88 envli.Lock() 89 defer envli.Unlock() 90 os.Setenv(envli.envName, strings.Join(paths, pathListSeparator)) 91 return nil 92 }