src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/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/errutil"
    13  	"src.elv.sh/pkg/eval/vals"
    14  )
    15  
    16  var (
    17  	pathListSeparator = string(os.PathListSeparator)
    18  	forbiddenInPath   = pathListSeparator + "\x00"
    19  )
    20  
    21  // Errors
    22  var (
    23  	ErrPathMustBeString          = errors.New("path must be string")
    24  	ErrPathContainsForbiddenChar = errors.New("path cannot contain NUL byte, colon on Unix or semicolon on Windows")
    25  )
    26  
    27  // NewEnvListVar returns a variable whose value is a list synchronized with an
    28  // environment variable with the elements joined by os.PathListSeparator.
    29  //
    30  // Elements in the value of the variable must be strings, and cannot contain
    31  // os.PathListSeparator or \0; attempting to put any in its elements will result in
    32  // an error.
    33  func NewEnvListVar(name string) Var {
    34  	return &envListVar{envName: name}
    35  }
    36  
    37  type envListVar struct {
    38  	sync.RWMutex
    39  	envName    string
    40  	cacheFor   string
    41  	cacheValue any
    42  }
    43  
    44  // Get returns a Value for an EnvPathList.
    45  func (envli *envListVar) Get() any {
    46  	envli.Lock()
    47  	defer envli.Unlock()
    48  
    49  	value := os.Getenv(envli.envName)
    50  	if value == envli.cacheFor {
    51  		return envli.cacheValue
    52  	}
    53  	envli.cacheFor = value
    54  	v := vals.EmptyList
    55  	for _, path := range strings.Split(value, pathListSeparator) {
    56  		v = v.Conj(path)
    57  	}
    58  	envli.cacheValue = v
    59  	return envli.cacheValue
    60  }
    61  
    62  // Set sets an EnvPathList. The underlying environment variable is set.
    63  func (envli *envListVar) Set(v any) error {
    64  	var (
    65  		paths      []string
    66  		errElement error
    67  	)
    68  	errIterate := vals.Iterate(v, func(v any) bool {
    69  		s, ok := v.(string)
    70  		if !ok {
    71  			errElement = ErrPathMustBeString
    72  			return false
    73  		}
    74  		path := s
    75  		if strings.ContainsAny(path, forbiddenInPath) {
    76  			errElement = ErrPathContainsForbiddenChar
    77  			return false
    78  		}
    79  		paths = append(paths, s)
    80  		return true
    81  	})
    82  
    83  	if errElement != nil || errIterate != nil {
    84  		return errutil.Multi(errElement, errIterate)
    85  	}
    86  
    87  	envli.Lock()
    88  	defer envli.Unlock()
    89  	os.Setenv(envli.envName, strings.Join(paths, pathListSeparator))
    90  	return nil
    91  }