github.com/bilus/oya@v0.0.3-0.20190301162104-da4acbd394c6/pkg/template/scope.go (about) 1 package template 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "github.com/pkg/errors" 9 ) 10 11 // ErrScopeMergeConflict indicates that a conflict occurred when merging two scopes 12 // at the specified path. 13 type ErrScopeMergeConflict struct { 14 Path string 15 } 16 17 func (e ErrScopeMergeConflict) Error() string { 18 return fmt.Sprintf("path %v already exists", e.Path) 19 } 20 21 // Scope represents a single lexical value scope. Scopes can be nested. 22 type Scope map[interface{}]interface{} 23 24 // ParseScope casts the value to Scope. 25 func ParseScope(v interface{}) (Scope, bool) { 26 if scope, ok := v.(Scope); ok { 27 return scope, true 28 } 29 if scope, ok := v.(map[interface{}]interface{}); ok { 30 return scope, true 31 } 32 return nil, false 33 } 34 35 // Merge combines two scopes together. If a key appears in both scopes, the other scope wins. 36 func (scope Scope) Merge(other Scope) Scope { 37 result := Scope{} 38 for k, v := range scope { 39 result[k] = v 40 } 41 for k, v := range other { 42 result[k] = v 43 } 44 return result 45 } 46 47 // Replace replaces contents of this scope with keys and values of the other one. 48 func (scope Scope) Replace(other Scope) { 49 if reflect.ValueOf(scope).Pointer() == reflect.ValueOf(other).Pointer() { 50 return 51 } 52 for k := range scope { 53 delete(scope, k) 54 } 55 for k, v := range other { 56 scope[k] = v 57 } 58 } 59 60 // UpdateScopeAt transforms a scope pointed to by the path (e.g. "foo.bar.baz"). 61 // It will create scopes along the way if they don't exist. 62 // In case the value pointed by the path already exists and cannot be interpreted 63 // as a Scope (see ParseScope), the function signals ErrScopeMergeConflict. 64 func (scope Scope) UpdateScopeAt(path string, f func(Scope) Scope) error { 65 var pathArr []string 66 if len(path) > 0 { 67 pathArr = strings.Split(path, ".") 68 } 69 targetScope, err := scope.resolveScope(pathArr, true) 70 if err != nil { 71 // Ignore the reason. 72 return ErrScopeMergeConflict{Path: path} 73 } 74 targetScope.Replace(f(targetScope)) 75 return nil 76 } 77 78 // GetScopeAt returns scope at the specified path. If path doesn't exist or points 79 // to a value that cannot be interpreted as a Scope (see ParseScope), the function 80 // signals an error. 81 func (scope Scope) GetScopeAt(path string) (Scope, error) { 82 pathArr := strings.Split(path, ".") 83 return scope.resolveScope(pathArr, false) 84 } 85 86 func (scope Scope) resolveScope(path []string, create bool) (Scope, error) { 87 if len(path) == 0 { 88 return scope, nil 89 } 90 91 scopeName := path[0] 92 potentialScope, ok := scope[scopeName] 93 if !ok { 94 if !create { 95 return nil, errors.Errorf("Missing key %q", scopeName) 96 97 } 98 // Create scope along the path. 99 potentialScope = Scope{} 100 scope[scopeName] = potentialScope 101 } 102 subScope, ok := ParseScope(potentialScope) 103 if !ok { 104 return nil, errors.Errorf("Unsupported value under %q", scopeName) 105 } 106 return subScope.resolveScope(path[1:], create) 107 }