package env import ( "errors" "regexp" "sort" "strings" "sync" ) type box interface { Get() string Set(value string) bool } type env struct { // vars m map[string]string // forks c map[string]*env // key in parent env's map k string sync.Mutex // parent env p *env } type Env struct { *env } const namePattern = "[a-zA-Z][a-zA-Z0-9.]*" var isGoodName = regexp.MustCompile("^" + namePattern + "$").MatchString var varexpRe = regexp.MustCompile("@(" + namePattern + "|{" + namePattern + "})") var expandRe = regexp.MustCompile("@[" + namePattern + "]") var errBadVariable = errors.New("bad variable name") var errEmptyVariable = errors.New("empty variable") func New() Env { return Env{env: new(env)} } func (e Env) init() { if e.m == nil { e.m = make(map[string]string) } } func (e Env) Fork(path ...string) Env { t := New() t.p = e.env if len(path) > 0 { k := strings.Join(path, ".") e.Lock() if e.c == nil { e.c = make(map[string]*env) } else if _, ok := e.c[k]; ok { panic("env fork already exists: " + k) } e.c[k] = t.env e.Unlock() t.k = k } return t } func (e Env) Detach() { if e.p != nil { e.p.Lock() delete(e.p.c, e.k) e.p.Unlock() } } func (e *env) find(key string) (string, bool, func() (string, bool)) { e.Lock() defer e.Unlock() v, ok := e.m[key] if ok { return v, ok, nil } if e.c != nil { s := strings.Split(key, ".") for k := ""; len(s) > 1; s = s[1:] { if k == "" { k = s[0] } else { k += "." + s[0] } if c, ok := e.c[k]; ok { return "", false, func() (string, bool) { return c.Find(strings.Join(s[1:], ".")) } } } } if e.p != nil { return "", false, func() (string, bool) { return e.p.Find(key) } } return "", false, nil } func (e *env) Find(key string) (string, bool) { if key == "" { return "", false } v, ok, f := e.find(key) if ok { return v, ok } if f != nil { return f() } return "", false } func (e Env) Get(key string) string { v, _ := e.Find(key) return v } func validKeyValue(key string, value string) error { if !isGoodName(key) { return errBadVariable } if value == "" { return errEmptyVariable } return nil } func (e Env) Set(key string, value string) error { if err := validKeyValue(key, value); err != nil { return err } e.Lock() defer e.Unlock() e.init() e.m[key] = value return nil } func (e Env) Push(key string, value string) error { if err := validKeyValue(key, value); err != nil { return err } e.Lock() defer e.Unlock() e.init() if old, ok := e.m[key]; ok { e.m[key] = old + "," + value } else { e.m[key] = value } return nil } func (e Env) Del(key string) bool { e.Lock() defer e.Unlock() if e.m == nil { return false } if _, ok := e.m[key]; !ok { return false } delete(e.m, key) return true } func (e Env) Each(f func(string, string) bool) { var keys []string e.Lock() defer e.Unlock() for k := range e.m { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { if !f(k, e.m[k]) { break } } } func (e Env) Clear() { e.Lock() defer e.Unlock() e.m = nil } func (e Env) replaceWith(r *regexp.Regexp, s string, f func(string) string) string { const maxIter = 16 for iter := 0; ; iter++ { found := false t := r.ReplaceAllStringFunc(s, func(v string) string { found = true if iter >= maxIter { return "" } return f(v) }) if !found && t == s { break } s = t } return s } func (e Env) Eval(s string) string { return e.replaceWith(varexpRe, s, func(v string) string { key := v[1:] if key[0] == '{' { key = key[1 : len(key)-1] } return e.Get(key) }) } func (e Env) EvalStrings(s []string) []string { t := make([]string, len(s)) for n, v := range s { t[n] = e.Eval(v) } return t } func (e Env) getLocal(key string) string { if tunnel, ok := e.Find("tunnel"); ok { if v, ok := e.Find("tunnel." + tunnel + "." + key); ok { return v } } return e.Get(key) } func (e Env) Expand(s string) string { return e.replaceWith(expandRe, s, func(v string) string { return e.getLocal(v[2 : len(v)-1]) }) } func (e Env) Value(key string) string { return e.Expand(e.getLocal(key)) }