github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/eval/env_path_list.go (about) 1 package eval 2 3 import ( 4 "errors" 5 "os" 6 "strings" 7 "sync" 8 ) 9 10 // Errors 11 var ( 12 ErrCanOnlyAssignList = errors.New("can only assign compatible values") 13 ErrPathMustBeString = errors.New("path must be string") 14 ErrPathCannotContainColonZero = errors.New(`path cannot contain colon or \0`) 15 ) 16 17 // EnvPathList is a variable whose value is constructed from an environment 18 // variable by splitting at colons. Changes to it are also propagated to the 19 // corresponding environment variable. Its elements cannot contain colons or 20 // \0; attempting to put colon or \0 in its elements will result in an error. 21 // 22 // EnvPathList implements both Value and Variable interfaces. It also satisfied 23 // ListLike. 24 type EnvPathList struct { 25 sync.RWMutex 26 envName string 27 cachedValue string 28 cachedPaths []string 29 } 30 31 var ( 32 _ Variable = (*EnvPathList)(nil) 33 _ Value = (*EnvPathList)(nil) 34 _ ListLike = (*EnvPathList)(nil) 35 ) 36 37 func (epl *EnvPathList) Get() Value { 38 return epl 39 } 40 41 func (epl *EnvPathList) Set(v Value) { 42 iterator, ok := v.(Iterator) 43 if !ok { 44 throw(ErrCanOnlyAssignList) 45 } 46 var paths []string 47 iterator.Iterate(func(v Value) bool { 48 s, ok := v.(String) 49 if !ok { 50 throw(ErrPathMustBeString) 51 } 52 path := string(s) 53 if strings.ContainsAny(path, ":\x00") { 54 throw(ErrPathCannotContainColonZero) 55 } 56 paths = append(paths, string(s)) 57 return true 58 }) 59 epl.set(paths) 60 } 61 62 func (epl *EnvPathList) Kind() string { 63 return "list" 64 } 65 66 func (epl *EnvPathList) Repr(indent int) string { 67 var b ListReprBuilder 68 b.Indent = indent 69 for _, path := range epl.get() { 70 b.WriteElem(quote(path)) 71 } 72 return b.String() 73 } 74 75 func (epl *EnvPathList) Len() int { 76 return len(epl.get()) 77 } 78 79 func (epl *EnvPathList) Iterate(f func(Value) bool) { 80 for _, p := range epl.get() { 81 if !f(String(p)) { 82 break 83 } 84 } 85 } 86 87 func (epl *EnvPathList) Elems() <-chan Value { 88 ch := make(chan Value) 89 go func() { 90 close(ch) 91 }() 92 return ch 93 } 94 95 func (epl *EnvPathList) IndexOne(idx Value) Value { 96 paths := epl.get() 97 slice, i, j := parseAndFixListIndex(ToString(idx), len(paths)) 98 if slice { 99 sliced := paths[i:j] 100 values := make([]Value, len(sliced)) 101 for i, p := range sliced { 102 values[i] = String(p) 103 } 104 return List{&values} 105 } 106 return String(paths[i]) 107 } 108 109 func (epl *EnvPathList) IndexSet(idx, v Value) { 110 s, ok := v.(String) 111 if !ok { 112 throw(ErrPathMustBeString) 113 } 114 115 paths := epl.get() 116 slice, i, _ := parseAndFixListIndex(ToString(idx), len(paths)) 117 if slice { 118 throw(errors.New("slice set unimplemented")) 119 } 120 121 epl.Lock() 122 defer epl.Unlock() 123 paths[i] = string(s) 124 epl.syncFromPaths() 125 } 126 127 func (epl *EnvPathList) get() []string { 128 epl.Lock() 129 defer epl.Unlock() 130 131 value := os.Getenv(epl.envName) 132 if value == epl.cachedValue { 133 return epl.cachedPaths 134 } 135 epl.cachedValue = value 136 epl.cachedPaths = strings.Split(value, ":") 137 return epl.cachedPaths 138 } 139 140 func (epl *EnvPathList) set(paths []string) { 141 epl.Lock() 142 defer epl.Unlock() 143 144 epl.cachedPaths = paths 145 epl.syncFromPaths() 146 } 147 148 func (epl *EnvPathList) syncFromPaths() { 149 epl.cachedValue = strings.Join(epl.cachedPaths, ":") 150 err := os.Setenv(epl.envName, epl.cachedValue) 151 maybeThrow(err) 152 }