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  }