github.com/tooploox/oya@v0.0.21-0.20230524103240-1cda1861aad6/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 type ErrPathEmpty struct{} 22 23 func (e ErrPathEmpty) Error() string { 24 return "Path must not be empty" 25 } 26 27 // Scope represents a single lexical value scope. Scopes can be nested. 28 type Scope map[interface{}]interface{} 29 30 // ParseScope casts the value to Scope. 31 func ParseScope(v interface{}) (Scope, bool) { 32 if scope, ok := v.(Scope); ok { 33 return scope, true 34 } 35 if scope, ok := v.(map[interface{}]interface{}); ok { 36 return scope, true 37 } 38 return nil, false 39 } 40 41 // Merge combines two scopes together. If a key appears in both scopes, the other scope wins. 42 func (scope Scope) Merge(other Scope) Scope { 43 result := Scope{} 44 for k, v := range scope { 45 result[k] = v 46 } 47 for k, v := range other { 48 existing, ok := result[k] 49 if !ok { 50 result[k] = v 51 } else { 52 result[k] = merge(existing, v) 53 } 54 result[k] = merge(existing, v) 55 } 56 return result 57 } 58 59 func merge(e, n interface{}) interface{} { 60 es, ok := ParseScope(e) 61 if !ok { 62 return n // Overwrite. 63 } 64 ns, ok := ParseScope(n) 65 if !ok { 66 return n // Overwrite. 67 } 68 // Merge: 69 return map[interface{}]interface{}(es.Merge(ns)) 70 } 71 72 // Replace replaces contents of this scope with keys and values of the other one. 73 func (scope Scope) Replace(other Scope) { 74 if reflect.ValueOf(scope).Pointer() == reflect.ValueOf(other).Pointer() { 75 return 76 } 77 for k := range scope { 78 delete(scope, k) 79 } 80 for k, v := range other { 81 scope[k] = v 82 } 83 } 84 85 // UpdateScopeAt transforms a scope pointed to by the path (e.g. "foo.bar.baz"). 86 // It will create scopes along the way if they don't exist. 87 // In case the value pointed by the path already exists and cannot be interpreted 88 // as a Scope (see ParseScope), the function signals ErrScopeMergeConflict unless 89 // force is true. If that's the case, the value is overridden. 90 func (scope Scope) UpdateScopeAt(path string, f func(Scope) Scope, force bool) error { 91 var pathArr []string 92 if len(path) > 0 { 93 pathArr = strings.Split(path, ".") 94 } 95 targetScope, err := scope.resolveScope(pathArr, true, force) 96 if err != nil { 97 // Ignore the reason. 98 return ErrScopeMergeConflict{Path: path} 99 } 100 targetScope.Replace(f(targetScope)) 101 return nil 102 } 103 104 // AssocAt modifies scope by setting value at the provided path. 105 // It will create scopes along the way if they don't exist. 106 // In case the value pointed by the path already exists and cannot be interpreted 107 // as a Scope (see ParseScope), the function signals ErrScopeMergeConflict unless 108 // force is true. If that's the case, the value is overridden. 109 func (scope Scope) AssocAt(path string, value interface{}, force bool) error { 110 if len(path) == 0 { 111 return ErrPathEmpty{} 112 } 113 pathArr := strings.Split(path, ".") 114 scopePath := strings.Join(pathArr[0:len(pathArr)-1], ".") 115 key := pathArr[len(pathArr)-1] 116 return scope.UpdateScopeAt(scopePath, func(s Scope) Scope { 117 s[key] = value 118 return s 119 }, force) 120 } 121 122 // GetScopeAt returns scope at the specified path. If path doesn't exist or points 123 // to a value that cannot be interpreted as a Scope (see ParseScope), the function 124 // signals an error. 125 func (scope Scope) GetScopeAt(path string) (Scope, error) { 126 pathArr := strings.Split(path, ".") 127 return scope.resolveScope(pathArr, false, false) 128 } 129 130 // Flat returns a flattened scope. 131 132 func (scope Scope) resolveScope(path []string, create, force bool) (Scope, error) { 133 if len(path) == 0 { 134 return scope, nil 135 } 136 137 scopeName := path[0] 138 potentialScope, ok := scope[scopeName] 139 if !ok { 140 if !create { 141 return nil, errors.Errorf("Missing key %q", scopeName) 142 143 } 144 // Create scope along the path. 145 potentialScope = Scope{} 146 scope[scopeName] = potentialScope 147 } 148 subScope, ok := ParseScope(potentialScope) 149 if !ok { 150 if !force { 151 return nil, errors.Errorf("Unsupported value under %q", scopeName) 152 } 153 subScope = Scope{} 154 scope[scopeName] = subScope 155 } 156 return subScope.resolveScope(path[1:], create, force) 157 } 158 159 func (scope Scope) Flat() Scope { 160 result := make(Scope) 161 flatten(scope, &result, "") 162 return result 163 } 164 165 func flatten(value interface{}, result *Scope, parent string) { 166 if scope, ok := ParseScope(value); ok { 167 for k, v := range scope { 168 ks, ok := k.(string) 169 if !ok { 170 panic("Scope keys are expected to be strings!") 171 } 172 key := ks 173 if len(parent) > 0 { 174 // E.g. parent.key 175 key = fmt.Sprintf("%s.%s", parent, ks) 176 } 177 flatten(v, result, key) 178 } 179 } else if xs := reflect.ValueOf(value); xs.Kind() == reflect.Slice || xs.Kind() == reflect.Array { 180 for i := 0; i < xs.Len(); i++ { 181 x := xs.Index(i) 182 var key string 183 if len(parent) > 0 { 184 // E.g. parent.0 185 key = fmt.Sprintf("%s.%v", parent, i) 186 } else { 187 key = fmt.Sprintf("%v", i) 188 } 189 flatten(x, result, key) 190 } 191 } else if m := reflect.ValueOf(value); m.Kind() == reflect.Map { 192 for _, k := range m.MapKeys() { 193 v := m.MapIndex(k) 194 var key string 195 if len(parent) > 0 { 196 // E.g. parent.0 197 key = fmt.Sprintf("%s.%v", parent, k) 198 } else { 199 key = fmt.Sprintf("%v", k) 200 } 201 flatten(v, result, key) 202 } 203 } else { 204 (*result)[parent] = value 205 } 206 }