mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			228 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package cty
 | 
						|
 | 
						|
// Walk visits all of the values in a possibly-complex structure, calling
 | 
						|
// a given function for each value.
 | 
						|
//
 | 
						|
// For example, given a list of strings the callback would first be called
 | 
						|
// with the whole list and then called once for each element of the list.
 | 
						|
//
 | 
						|
// The callback function may prevent recursive visits to child values by
 | 
						|
// returning false. The callback function my halt the walk altogether by
 | 
						|
// returning a non-nil error. If the returned error is about the element
 | 
						|
// currently being visited, it is recommended to use the provided path
 | 
						|
// value to produce a PathError describing that context.
 | 
						|
//
 | 
						|
// The path passed to the given function may not be used after that function
 | 
						|
// returns, since its backing array is re-used for other calls.
 | 
						|
func Walk(val Value, cb func(Path, Value) (bool, error)) error {
 | 
						|
	var path Path
 | 
						|
	return walk(path, val, cb)
 | 
						|
}
 | 
						|
 | 
						|
func walk(path Path, val Value, cb func(Path, Value) (bool, error)) error {
 | 
						|
	deeper, err := cb(path, val)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if !deeper {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if val.IsNull() || !val.IsKnown() {
 | 
						|
		// Can't recurse into null or unknown values, regardless of type
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	ty := val.Type()
 | 
						|
	switch {
 | 
						|
	case ty.IsObjectType():
 | 
						|
		for it := val.ElementIterator(); it.Next(); {
 | 
						|
			nameVal, av := it.Element()
 | 
						|
			path := append(path, GetAttrStep{
 | 
						|
				Name: nameVal.AsString(),
 | 
						|
			})
 | 
						|
			err := walk(path, av, cb)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case val.CanIterateElements():
 | 
						|
		for it := val.ElementIterator(); it.Next(); {
 | 
						|
			kv, ev := it.Element()
 | 
						|
			path := append(path, IndexStep{
 | 
						|
				Key: kv,
 | 
						|
			})
 | 
						|
			err := walk(path, ev, cb)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Transformer is the interface used to optionally transform values in a
 | 
						|
// possibly-complex structure. The Enter method is called before traversing
 | 
						|
// through a given path, and the Exit method is called when traversal of a
 | 
						|
// path is complete.
 | 
						|
//
 | 
						|
// Use Enter when you want to transform a complex value before traversal
 | 
						|
// (preorder), and Exit when you want to transform a value after traversal
 | 
						|
// (postorder).
 | 
						|
//
 | 
						|
// The path passed to the given function may not be used after that function
 | 
						|
// returns, since its backing array is re-used for other calls.
 | 
						|
type Transformer interface {
 | 
						|
	Enter(Path, Value) (Value, error)
 | 
						|
	Exit(Path, Value) (Value, error)
 | 
						|
}
 | 
						|
 | 
						|
type postorderTransformer struct {
 | 
						|
	callback func(Path, Value) (Value, error)
 | 
						|
}
 | 
						|
 | 
						|
func (t *postorderTransformer) Enter(p Path, v Value) (Value, error) {
 | 
						|
	return v, nil
 | 
						|
}
 | 
						|
 | 
						|
func (t *postorderTransformer) Exit(p Path, v Value) (Value, error) {
 | 
						|
	return t.callback(p, v)
 | 
						|
}
 | 
						|
 | 
						|
// Transform visits all of the values in a possibly-complex structure,
 | 
						|
// calling a given function for each value which has an opportunity to
 | 
						|
// replace that value.
 | 
						|
//
 | 
						|
// Unlike Walk, Transform visits child nodes first, so for a list of strings
 | 
						|
// it would first visit the strings and then the _new_ list constructed
 | 
						|
// from the transformed values of the list items.
 | 
						|
//
 | 
						|
// This is useful for creating the effect of being able to make deep mutations
 | 
						|
// to a value even though values are immutable. However, it's the responsibility
 | 
						|
// of the given function to preserve expected invariants, such as homogenity of
 | 
						|
// element types in collections; this function can panic if such invariants
 | 
						|
// are violated, just as if new values were constructed directly using the
 | 
						|
// value constructor functions. An easy way to preserve invariants is to
 | 
						|
// ensure that the transform function never changes the value type.
 | 
						|
//
 | 
						|
// The callback function may halt the walk altogether by
 | 
						|
// returning a non-nil error. If the returned error is about the element
 | 
						|
// currently being visited, it is recommended to use the provided path
 | 
						|
// value to produce a PathError describing that context.
 | 
						|
//
 | 
						|
// The path passed to the given function may not be used after that function
 | 
						|
// returns, since its backing array is re-used for other calls.
 | 
						|
func Transform(val Value, cb func(Path, Value) (Value, error)) (Value, error) {
 | 
						|
	var path Path
 | 
						|
	return transform(path, val, &postorderTransformer{cb})
 | 
						|
}
 | 
						|
 | 
						|
// TransformWithTransformer allows the caller to more closely control the
 | 
						|
// traversal used for transformation. See the documentation for Transformer for
 | 
						|
// more details.
 | 
						|
func TransformWithTransformer(val Value, t Transformer) (Value, error) {
 | 
						|
	var path Path
 | 
						|
	return transform(path, val, t)
 | 
						|
}
 | 
						|
 | 
						|
func transform(path Path, val Value, t Transformer) (Value, error) {
 | 
						|
	val, err := t.Enter(path, val)
 | 
						|
	if err != nil {
 | 
						|
		return DynamicVal, err
 | 
						|
	}
 | 
						|
 | 
						|
	ty := val.Type()
 | 
						|
	var newVal Value
 | 
						|
 | 
						|
	switch {
 | 
						|
 | 
						|
	case val.IsNull() || !val.IsKnown():
 | 
						|
		// Can't recurse into null or unknown values, regardless of type
 | 
						|
		newVal = val
 | 
						|
 | 
						|
	case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
 | 
						|
		l := val.LengthInt()
 | 
						|
		switch l {
 | 
						|
		case 0:
 | 
						|
			// No deep transform for an empty sequence
 | 
						|
			newVal = val
 | 
						|
		default:
 | 
						|
			elems := make([]Value, 0, l)
 | 
						|
			for it := val.ElementIterator(); it.Next(); {
 | 
						|
				kv, ev := it.Element()
 | 
						|
				path := append(path, IndexStep{
 | 
						|
					Key: kv,
 | 
						|
				})
 | 
						|
				newEv, err := transform(path, ev, t)
 | 
						|
				if err != nil {
 | 
						|
					return DynamicVal, err
 | 
						|
				}
 | 
						|
				elems = append(elems, newEv)
 | 
						|
			}
 | 
						|
			switch {
 | 
						|
			case ty.IsListType():
 | 
						|
				newVal = ListVal(elems)
 | 
						|
			case ty.IsSetType():
 | 
						|
				newVal = SetVal(elems)
 | 
						|
			case ty.IsTupleType():
 | 
						|
				newVal = TupleVal(elems)
 | 
						|
			default:
 | 
						|
				panic("unknown sequence type") // should never happen because of the case we are in
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	case ty.IsMapType():
 | 
						|
		l := val.LengthInt()
 | 
						|
		switch l {
 | 
						|
		case 0:
 | 
						|
			// No deep transform for an empty map
 | 
						|
			newVal = val
 | 
						|
		default:
 | 
						|
			elems := make(map[string]Value)
 | 
						|
			for it := val.ElementIterator(); it.Next(); {
 | 
						|
				kv, ev := it.Element()
 | 
						|
				path := append(path, IndexStep{
 | 
						|
					Key: kv,
 | 
						|
				})
 | 
						|
				newEv, err := transform(path, ev, t)
 | 
						|
				if err != nil {
 | 
						|
					return DynamicVal, err
 | 
						|
				}
 | 
						|
				elems[kv.AsString()] = newEv
 | 
						|
			}
 | 
						|
			newVal = MapVal(elems)
 | 
						|
		}
 | 
						|
 | 
						|
	case ty.IsObjectType():
 | 
						|
		switch {
 | 
						|
		case ty.Equals(EmptyObject):
 | 
						|
			// No deep transform for an empty object
 | 
						|
			newVal = val
 | 
						|
		default:
 | 
						|
			atys := ty.AttributeTypes()
 | 
						|
			newAVs := make(map[string]Value)
 | 
						|
			for name := range atys {
 | 
						|
				av := val.GetAttr(name)
 | 
						|
				path := append(path, GetAttrStep{
 | 
						|
					Name: name,
 | 
						|
				})
 | 
						|
				newAV, err := transform(path, av, t)
 | 
						|
				if err != nil {
 | 
						|
					return DynamicVal, err
 | 
						|
				}
 | 
						|
				newAVs[name] = newAV
 | 
						|
			}
 | 
						|
			newVal = ObjectVal(newAVs)
 | 
						|
		}
 | 
						|
 | 
						|
	default:
 | 
						|
		newVal = val
 | 
						|
	}
 | 
						|
 | 
						|
	newVal, err = t.Exit(path, newVal)
 | 
						|
	if err != nil {
 | 
						|
		return DynamicVal, err
 | 
						|
	}
 | 
						|
	return newVal, err
 | 
						|
}
 |