| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package envvar implements utilities for processing environment variables. |
| // There are three representations of environment variables: |
| // 1) []"key=value" # hard to get and set, used by standard Go packages |
| // 2) map[key]value # simple to get and set, nicest syntax |
| // 3) *envvar.Vars # simple to get and set, also tracks deltas |
| // |
| // The slice form (1) is used by standard Go packages, presumably since it's |
| // similar to the underlying OS representation. The map form (2) is convenient |
| // to use, and has native Go map syntax. The Vars form (3) is also convenient |
| // to use, and tracks deltas when mutations are performed. |
| // |
| // This package provides utilities to easily use and convert between the three |
| // representations. |
| // |
| // Empty keys are invalid and silently skipped in operations over all |
| // representations. |
| package envvar |
| |
| import ( |
| "os" |
| "sort" |
| "strings" |
| ) |
| |
| // MergeMaps merges together maps, and returns a new map with the merged result. |
| // If the same key appears in more than one input map, the last one "wins"; the |
| // value is set based on the last map containing that key. |
| // |
| // As a result of its semantics, MergeMaps called with a single map returns a |
| // copy of the map, with empty keys dropped. |
| func MergeMaps(maps ...map[string]string) map[string]string { |
| merged := make(map[string]string) |
| for _, m := range maps { |
| for key, value := range m { |
| if key != "" { |
| merged[key] = value |
| } |
| } |
| } |
| return merged |
| } |
| |
| // CopyMap returns a copy of from, with empty keys dropped. |
| func CopyMap(from map[string]string) map[string]string { |
| return MergeMaps(from) |
| } |
| |
| // MergeSlices merges together slices, and returns a new slice with the merged |
| // result. If the same key appears more than once in a single input slice, or |
| // in more than one input slice, the last one "wins"; the value is set based on |
| // the last slice element in the last slice containing that key. |
| // |
| // As a result of its semantics, MergeSlices called with a single slice returns |
| // a copy of the slice, with empty keys dropped. |
| func MergeSlices(slices ...[]string) []string { |
| merged := make(map[string]string) |
| for _, slice := range slices { |
| for _, kv := range slice { |
| if key, value := SplitKeyValue(kv); key != "" { |
| merged[key] = value |
| } |
| } |
| } |
| return MapToSlice(merged) |
| } |
| |
| // CopySlice returns a copy of from, with empty keys dropped, and ordered by |
| // key. If the same key appears more than once the last one "wins"; the value |
| // is set based on the last slice element containing that key. |
| func CopySlice(from []string) []string { |
| return MergeSlices(from) |
| } |
| |
| // MapToSlice converts from the map to the slice representation. The returned |
| // slice is in sorted order. |
| func MapToSlice(from map[string]string) []string { |
| to := make([]string, 0, len(from)) |
| for key, value := range from { |
| if key != "" { |
| to = append(to, JoinKeyValue(key, value)) |
| } |
| } |
| SortByKey(to) |
| return to |
| } |
| |
| // SliceToMap converts from the slice to the map representation. If the same |
| // key appears more than once, the last one "wins"; the value is set based on |
| // the last slice element containing that key. |
| func SliceToMap(from []string) map[string]string { |
| to := make(map[string]string, len(from)) |
| for _, kv := range from { |
| if key, value := SplitKeyValue(kv); key != "" { |
| to[key] = value |
| } |
| } |
| return to |
| } |
| |
| // SplitKeyValue splits kv into its key and value components. The format of kv |
| // is "key=value"; the split is performed on the first '=' character. |
| func SplitKeyValue(kv string) (string, string) { |
| split := strings.SplitN(kv, "=", 2) |
| if len(split) == 2 { |
| return split[0], split[1] |
| } |
| return split[0], "" |
| } |
| |
| // JoinKeyValue joins key and value into a single string "key=value". |
| func JoinKeyValue(key, value string) string { |
| return key + "=" + value |
| } |
| |
| // SplitTokens is like strings.Split(value, separator), but also filters out |
| // empty tokens. Thus SplitTokens("", ":") returns a nil slice, unlike |
| // strings.SplitTokens which returns a slice with a single empty string. |
| func SplitTokens(value, separator string) []string { |
| var tokens []string |
| for _, token := range strings.Split(value, separator) { |
| if token != "" { |
| tokens = append(tokens, token) |
| } |
| } |
| return tokens |
| } |
| |
| // JoinTokens is like strings.Join(tokens, separator), but also filters out |
| // empty tokens. |
| func JoinTokens(tokens []string, separator string) string { |
| var value string |
| for _, token := range tokens { |
| if token == "" { |
| continue |
| } |
| if value != "" { |
| value += separator |
| } |
| value += token |
| } |
| return value |
| } |
| |
| // SortByKey sorts vars into ascending key order, where vars is expected to be |
| // in the []"key=value" slice representation. |
| func SortByKey(vars []string) { |
| sort.Sort(keySorter(vars)) |
| } |
| |
| type keySorter []string |
| |
| func (s keySorter) Len() int { return len(s) } |
| func (s keySorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| func (s keySorter) Less(i, j int) bool { |
| ikey, _ := SplitKeyValue(s[i]) |
| jkey, _ := SplitKeyValue(s[j]) |
| return ikey < jkey |
| } |
| |
| // Vars is a mutable set of environment variables that tracks deltas. |
| // |
| // Vars are initialized with a base environment, and may be mutated with calls |
| // to Set and SetTokens. The resulting environment is retrieved with calls to |
| // ToMap and ToSlice. |
| // |
| // Mutations are tracked separately from the base environment; call Deltas to |
| // retrieve only the environment variables that have been changed. |
| // |
| // The zero Vars has an empty environment, and supports all methods. |
| type Vars struct { |
| base map[string]string |
| deltaInsert map[string]string |
| deltaRemove map[string]bool |
| } |
| |
| // VarsFromMap returns a new Vars initialized from the given base map. |
| func VarsFromMap(base map[string]string) *Vars { return &Vars{base: MergeMaps(base)} } |
| |
| // VarsFromSlice returns a new Vars initialized from the given base slice. |
| func VarsFromSlice(base []string) *Vars { return &Vars{base: SliceToMap(base)} } |
| |
| // VarsFromOS returns a new Vars initialized from os.Environ. |
| func VarsFromOS() *Vars { return VarsFromSlice(os.Environ()) } |
| |
| // Contains returns true iff the key exists in the current set of variables. |
| func (x *Vars) Contains(key string) bool { |
| if x.deltaRemove[key] { |
| return false |
| } |
| if _, ok := x.deltaInsert[key]; ok { |
| return true |
| } |
| _, ok := x.base[key] |
| return ok |
| } |
| |
| // Get returns the value associated with key. Returns "" if the key doesn't |
| // exist, or if the key has an empty value. Use Contains to test for existence. |
| func (x *Vars) Get(key string) string { |
| if x.deltaRemove[key] { |
| return "" |
| } |
| if value, ok := x.deltaInsert[key]; ok { |
| return value |
| } |
| return x.base[key] |
| } |
| |
| // GetTokens is a convenience that calls SplitTokens(x.Get(key), separator). |
| func (x *Vars) GetTokens(key, separator string) []string { |
| return SplitTokens(x.Get(key), separator) |
| } |
| |
| // Set assigns key to the given value. |
| func (x *Vars) Set(key, value string) { |
| if key != "" { |
| if x.deltaInsert == nil { |
| x.deltaInsert = make(map[string]string) |
| } |
| x.deltaInsert[key] = value |
| delete(x.deltaRemove, key) |
| } |
| } |
| |
| // SetTokens is a convenience that calls x.Set(key, JoinTokens(tokens, separator)). |
| func (x *Vars) SetTokens(key string, tokens []string, separator string) { |
| x.Set(key, JoinTokens(tokens, separator)) |
| } |
| |
| // Delete removes the given keys. Subsequent calls to Contains on each key |
| // will return false. |
| func (x *Vars) Delete(keys ...string) { |
| for _, key := range keys { |
| if key != "" { |
| if x.deltaRemove == nil { |
| x.deltaRemove = make(map[string]bool) |
| } |
| x.deltaRemove[key] = true |
| delete(x.deltaInsert, key) |
| } |
| } |
| } |
| |
| // ToMap returns the map representation of the current set of variables. |
| // |
| // Mutating the returned map does not affect x. |
| func (x *Vars) ToMap() map[string]string { |
| snapshot := MergeMaps(x.base, x.deltaInsert) |
| for key, _ := range x.deltaRemove { |
| delete(snapshot, key) |
| } |
| return snapshot |
| } |
| |
| // ToSlice returns the slice representation of the current set of variables. |
| // |
| // Mutating the returned slice does not affect x. |
| func (x *Vars) ToSlice() []string { |
| return MapToSlice(x.ToMap()) |
| } |
| |
| // Base returns a copy of the original base environment. |
| // |
| // Mutating the returned map does not affect x. |
| func (x *Vars) Base() map[string]string { |
| return MergeMaps(x.base) |
| } |
| |
| // Deltas returns the set of variables that have been mutated after |
| // initialization. |
| // |
| // If the last mutation for key K was Set or SetTokens, map[K] contains a |
| // non-nil pointer to the last value that was set. If the last mutation for key |
| // K was Delete, map[K] contains a nil pointer. |
| // |
| // Mutating the returned map does not affect x. |
| func (x *Vars) Deltas() map[string]*string { |
| deltas := make(map[string]*string, len(x.deltaInsert)+len(x.deltaRemove)) |
| for key, value := range x.deltaInsert { |
| cp := value |
| deltas[key] = &cp |
| } |
| for key, _ := range x.deltaRemove { |
| deltas[key] = nil |
| } |
| return deltas |
| } |
| |
| // UpdateOS updates the OS with the current set of variables. All variables are |
| // visited in sorted order, and os.Setenv is called for each variable. |
| // |
| // Returns the first error encountered, if any. |
| func (x *Vars) UpdateOS() error { |
| var firstErr error |
| for _, kv := range x.ToSlice() { |
| key, value := SplitKeyValue(kv) |
| if err := os.Setenv(key, value); err != nil && firstErr == nil { |
| firstErr = err |
| } |
| } |
| return firstErr |
| } |