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  }