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  }