elvish/eval/envPathList.go

136 lines
2.7 KiB
Go
Raw Normal View History

package eval
import (
"errors"
"os"
"strings"
"sync"
)
// Errors
var (
2016-02-19 19:07:38 +08:00
ErrCanOnlyAssignList = errors.New("can only assign compatible values")
ErrPathMustBeString = errors.New("path must be string")
ErrPathCannotContainColonZero = errors.New(`path cannot contain colon or \0`)
)
// EnvPathList is a variable whose value is constructed from an environment
// variable by splitting at colons. Changes to it are also propagated to the
// corresponding environment variable. Its elements cannot contain colons or
// \0; attempting to put colon or \0 in its elements will result in an error.
//
// EnvPathList implements both Value and Variable interfaces. It also satisfied
// ListLike.
type EnvPathList struct {
sync.RWMutex
envName string
cachedValue string
cachedPaths []string
}
var (
_ Variable = (*EnvPathList)(nil)
_ Value = (*EnvPathList)(nil)
_ ListLike = (*EnvPathList)(nil)
)
func (epl *EnvPathList) Get() Value {
return epl
}
func (epl *EnvPathList) Set(v Value) {
2016-02-19 19:07:38 +08:00
elemser, ok := v.(Elemser)
if !ok {
throw(ErrCanOnlyAssignList)
}
2016-02-19 19:07:38 +08:00
var paths []string
for v := range elemser.Elems() {
s, ok := v.(String)
if !ok {
throw(ErrPathMustBeString)
}
path := string(s)
if strings.ContainsAny(path, ":\x00") {
throw(ErrPathCannotContainColonZero)
}
2016-02-19 19:07:38 +08:00
paths = append(paths, string(s))
}
epl.set(paths)
}
func (epl *EnvPathList) Kind() string {
return "list"
}
func (epl *EnvPathList) Repr(indent int) string {
var b ListReprBuilder
b.Indent = indent
for _, path := range epl.get() {
b.WriteElem(quote(path))
}
return b.String()
}
func (epl *EnvPathList) Len() int {
return len(epl.get())
}
func (epl *EnvPathList) Elems() <-chan Value {
ch := make(chan Value)
go func() {
for _, p := range epl.get() {
ch <- String(p)
}
close(ch)
}()
return ch
}
func (epl *EnvPathList) IndexOne(idx Value) Value {
paths := epl.get()
i := intIndexWithin(idx, len(paths))
return String(paths[i])
}
func (epl *EnvPathList) IndexSet(idx, v Value) {
s, ok := v.(String)
if !ok {
throw(ErrPathMustBeString)
}
paths := epl.get()
i := intIndexWithin(idx, len(paths))
epl.Lock()
defer epl.Unlock()
paths[i] = string(s)
epl.syncFromPaths()
}
func (epl *EnvPathList) get() []string {
epl.Lock()
defer epl.Unlock()
value := os.Getenv(epl.envName)
if value == epl.cachedValue {
return epl.cachedPaths
}
epl.cachedValue = value
epl.cachedPaths = strings.Split(value, ":")
return epl.cachedPaths
}
func (epl *EnvPathList) set(paths []string) {
epl.Lock()
defer epl.Unlock()
epl.cachedPaths = paths
epl.syncFromPaths()
}
func (epl *EnvPathList) syncFromPaths() {
epl.cachedValue = strings.Join(epl.cachedPaths, ":")
err := os.Setenv(epl.envName, epl.cachedValue)
maybeThrow(err)
}