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  }