github.com/theclapp/sh@v2.6.4+incompatible/expand/environ.go (about) 1 // Copyright (c) 2018, Daniel Martà <mvdan@mvdan.cc> 2 // See LICENSE for licensing information 3 4 package expand 5 6 import ( 7 "runtime" 8 "sort" 9 "strings" 10 ) 11 12 // Environ is the base interface for a shell's environment, allowing it to fetch 13 // variables by name and to iterate over all the currently set variables. 14 type Environ interface { 15 // Get retrieves a variable by its name. To check if the variable is 16 // set, use Variable.IsSet. 17 Get(name string) Variable 18 19 // Each iterates over all the currently set variables, calling the 20 // supplied function on each variable. Iteration is stopped if the 21 // function returns false. 22 // 23 // The names used in the calls aren't required to be unique or sorted. 24 // If a variable name appears twice, the latest occurrence takes 25 // priority. 26 // 27 // Each is required to forward exported variables when executing 28 // programs. 29 Each(func(name string, vr Variable) bool) 30 } 31 32 // WriteEnviron is an extension on Environ that supports modifying and deleting 33 // variables. 34 type WriteEnviron interface { 35 Environ 36 // Set sets a variable by name. If !vr.IsSet(), the variable is being 37 // unset; otherwise, the variable is being replaced. 38 // 39 // It is the implementation's responsibility to handle variable 40 // attributes correctly. For example, changing an exported variable's 41 // value does not unexport it, and overwriting a name reference variable 42 // should modify its target. 43 Set(name string, vr Variable) 44 } 45 46 // Variable describes a shell variable, which can have a number of attributes 47 // and a value. 48 // 49 // A Variable is unset if its Value field is untyped nil, which can be checked 50 // via Variable.IsSet. The zero value of a Variable is thus a valid unset 51 // variable. 52 // 53 // If a variable is set, its Value field will be a []string if it is an indexed 54 // array, a map[string]string if it's an associative array, or a string 55 // otherwise. 56 type Variable struct { 57 Local bool 58 Exported bool 59 ReadOnly bool 60 NameRef bool // if true, Value must be string 61 Value interface{} // string, []string, or map[string]string 62 } 63 64 // IsSet returns whether the variable is set. An empty variable is set, but an 65 // undeclared variable is not. 66 func (v Variable) IsSet() bool { 67 return v.Value != nil 68 } 69 70 // String returns the variable's value as a string. In general, this only makes 71 // sense if the variable has a string value or no value at all. 72 func (v Variable) String() string { 73 switch x := v.Value.(type) { 74 case string: 75 return x 76 case []string: 77 if len(x) > 0 { 78 return x[0] 79 } 80 case map[string]string: 81 // nothing to do 82 } 83 return "" 84 } 85 86 // maxNameRefDepth defines the maximum number of times to follow references when 87 // resolving a variable. Otherwise, simple name reference loops could crash a 88 // program quite easily. 89 const maxNameRefDepth = 100 90 91 // Resolve follows a number of nameref variables, returning the last reference 92 // name that was followed and the variable that it points to. 93 func (v Variable) Resolve(env Environ) (string, Variable) { 94 name := "" 95 for i := 0; i < maxNameRefDepth; i++ { 96 if !v.NameRef { 97 return name, v 98 } 99 name = v.Value.(string) 100 v = env.Get(name) 101 } 102 return name, Variable{} 103 } 104 105 // FuncEnviron wraps a function mapping variable names to their string values, 106 // and implements Environ. Empty strings returned by the function will be 107 // treated as unset variables. All variables will be exported. 108 // 109 // Note that the returned Environ's Each method will be a no-op. 110 func FuncEnviron(fn func(string) string) Environ { 111 return funcEnviron(fn) 112 } 113 114 type funcEnviron func(string) string 115 116 func (f funcEnviron) Get(name string) Variable { 117 value := f(name) 118 if value == "" { 119 return Variable{} 120 } 121 return Variable{Exported: true, Value: value} 122 } 123 124 func (f funcEnviron) Each(func(name string, vr Variable) bool) {} 125 126 // ListEnviron returns an Environ with the supplied variables, in the form 127 // "key=value". All variables will be exported. 128 // 129 // On Windows, where environment variable names are case-insensitive, the 130 // resulting variable names will all be uppercase. 131 func ListEnviron(pairs ...string) Environ { 132 return listEnvironWithUpper(runtime.GOOS == "windows", pairs...) 133 } 134 135 // listEnvironWithUpper implements ListEnviron, but letting the tests specify 136 // whether to uppercase all names or not. 137 func listEnvironWithUpper(upper bool, pairs ...string) Environ { 138 list := append([]string{}, pairs...) 139 if upper { 140 // Uppercase before sorting, so that we can remove duplicates 141 // without the need for linear search nor a map. 142 for i, s := range list { 143 if sep := strings.IndexByte(s, '='); sep > 0 { 144 list[i] = strings.ToUpper(s[:sep]) + s[sep:] 145 } 146 } 147 } 148 sort.Strings(list) 149 last := "" 150 for i := 0; i < len(list); { 151 s := list[i] 152 sep := strings.IndexByte(s, '=') 153 if sep <= 0 { 154 // invalid element; remove it 155 list = append(list[:i], list[i+1:]...) 156 continue 157 } 158 name := s[:sep] 159 if last == name { 160 // duplicate; the last one wins 161 list = append(list[:i-1], list[i:]...) 162 continue 163 } 164 last = name 165 i++ 166 } 167 return listEnviron(list) 168 } 169 170 type listEnviron []string 171 172 func (l listEnviron) Get(name string) Variable { 173 // TODO: binary search 174 prefix := name + "=" 175 for _, pair := range l { 176 if val := strings.TrimPrefix(pair, prefix); val != pair { 177 return Variable{Exported: true, Value: val} 178 } 179 } 180 return Variable{} 181 } 182 183 func (l listEnviron) Each(fn func(name string, vr Variable) bool) { 184 for _, pair := range l { 185 i := strings.IndexByte(pair, '=') 186 if i < 0 { 187 // can't happen; see above 188 panic("expand.listEnviron: did not expect malformed name-value pair: " + pair) 189 } 190 name, value := pair[:i], pair[i+1:] 191 if !fn(name, Variable{Exported: true, Value: value}) { 192 return 193 } 194 } 195 }