github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/eval/env_path_list.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"strings"
     7  	"sync"
     8  )
     9  
    10  // Errors
    11  var (
    12  	ErrCanOnlyAssignList          = errors.New("can only assign compatible values")
    13  	ErrPathMustBeString           = errors.New("path must be string")
    14  	ErrPathCannotContainColonZero = errors.New(`path cannot contain colon or \0`)
    15  )
    16  
    17  // EnvPathList is a variable whose value is constructed from an environment
    18  // variable by splitting at colons. Changes to it are also propagated to the
    19  // corresponding environment variable. Its elements cannot contain colons or
    20  // \0; attempting to put colon or \0 in its elements will result in an error.
    21  //
    22  // EnvPathList implements both Value and Variable interfaces. It also satisfied
    23  // ListLike.
    24  type EnvPathList struct {
    25  	sync.RWMutex
    26  	envName     string
    27  	cachedValue string
    28  	cachedPaths []string
    29  }
    30  
    31  var (
    32  	_ Variable = (*EnvPathList)(nil)
    33  	_ Value    = (*EnvPathList)(nil)
    34  	_ ListLike = (*EnvPathList)(nil)
    35  )
    36  
    37  func (epl *EnvPathList) Get() Value {
    38  	return epl
    39  }
    40  
    41  func (epl *EnvPathList) Set(v Value) {
    42  	iterator, ok := v.(Iterator)
    43  	if !ok {
    44  		throw(ErrCanOnlyAssignList)
    45  	}
    46  	var paths []string
    47  	iterator.Iterate(func(v Value) bool {
    48  		s, ok := v.(String)
    49  		if !ok {
    50  			throw(ErrPathMustBeString)
    51  		}
    52  		path := string(s)
    53  		if strings.ContainsAny(path, ":\x00") {
    54  			throw(ErrPathCannotContainColonZero)
    55  		}
    56  		paths = append(paths, string(s))
    57  		return true
    58  	})
    59  	epl.set(paths)
    60  }
    61  
    62  func (epl *EnvPathList) Kind() string {
    63  	return "list"
    64  }
    65  
    66  func (epl *EnvPathList) Repr(indent int) string {
    67  	var b ListReprBuilder
    68  	b.Indent = indent
    69  	for _, path := range epl.get() {
    70  		b.WriteElem(quote(path))
    71  	}
    72  	return b.String()
    73  }
    74  
    75  func (epl *EnvPathList) Len() int {
    76  	return len(epl.get())
    77  }
    78  
    79  func (epl *EnvPathList) Iterate(f func(Value) bool) {
    80  	for _, p := range epl.get() {
    81  		if !f(String(p)) {
    82  			break
    83  		}
    84  	}
    85  }
    86  
    87  func (epl *EnvPathList) Elems() <-chan Value {
    88  	ch := make(chan Value)
    89  	go func() {
    90  		close(ch)
    91  	}()
    92  	return ch
    93  }
    94  
    95  func (epl *EnvPathList) IndexOne(idx Value) Value {
    96  	paths := epl.get()
    97  	slice, i, j := parseAndFixListIndex(ToString(idx), len(paths))
    98  	if slice {
    99  		sliced := paths[i:j]
   100  		values := make([]Value, len(sliced))
   101  		for i, p := range sliced {
   102  			values[i] = String(p)
   103  		}
   104  		return List{&values}
   105  	}
   106  	return String(paths[i])
   107  }
   108  
   109  func (epl *EnvPathList) IndexSet(idx, v Value) {
   110  	s, ok := v.(String)
   111  	if !ok {
   112  		throw(ErrPathMustBeString)
   113  	}
   114  
   115  	paths := epl.get()
   116  	slice, i, _ := parseAndFixListIndex(ToString(idx), len(paths))
   117  	if slice {
   118  		throw(errors.New("slice set unimplemented"))
   119  	}
   120  
   121  	epl.Lock()
   122  	defer epl.Unlock()
   123  	paths[i] = string(s)
   124  	epl.syncFromPaths()
   125  }
   126  
   127  func (epl *EnvPathList) get() []string {
   128  	epl.Lock()
   129  	defer epl.Unlock()
   130  
   131  	value := os.Getenv(epl.envName)
   132  	if value == epl.cachedValue {
   133  		return epl.cachedPaths
   134  	}
   135  	epl.cachedValue = value
   136  	epl.cachedPaths = strings.Split(value, ":")
   137  	return epl.cachedPaths
   138  }
   139  
   140  func (epl *EnvPathList) set(paths []string) {
   141  	epl.Lock()
   142  	defer epl.Unlock()
   143  
   144  	epl.cachedPaths = paths
   145  	epl.syncFromPaths()
   146  }
   147  
   148  func (epl *EnvPathList) syncFromPaths() {
   149  	epl.cachedValue = strings.Join(epl.cachedPaths, ":")
   150  	err := os.Setenv(epl.envName, epl.cachedValue)
   151  	maybeThrow(err)
   152  }