mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +08:00 
			
		
		
		
	I needed "split" specifically so I can do something like:
```hcl
variable PLATFORMS {
  default = "linux/amd64"
}
target foo {
  platforms = split(",", "${PLATFORMS}")
  # other stuff
}
```
Where the existing "csvdecode" does not work for this because it parses
the string into a list of objects instead of a list of strings.
I went ahead and just added all the available new functions.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
		
	
		
			
				
	
	
		
			489 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			489 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package convert
 | 
						|
 | 
						|
import (
 | 
						|
	"github.com/zclconf/go-cty/cty"
 | 
						|
)
 | 
						|
 | 
						|
// conversionCollectionToList returns a conversion that will apply the given
 | 
						|
// conversion to all of the elements of a collection (something that supports
 | 
						|
// ForEachElement and LengthInt) and then returns the result as a list.
 | 
						|
//
 | 
						|
// "conv" can be nil if the elements are expected to already be of the
 | 
						|
// correct type and just need to be re-wrapped into a list. (For example,
 | 
						|
// if we're converting from a set into a list of the same element type.)
 | 
						|
func conversionCollectionToList(ety cty.Type, conv conversion) conversion {
 | 
						|
	return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
		elems := make([]cty.Value, 0, val.LengthInt())
 | 
						|
		i := int64(0)
 | 
						|
		elemPath := append(path.Copy(), nil)
 | 
						|
		it := val.ElementIterator()
 | 
						|
		for it.Next() {
 | 
						|
			_, val := it.Element()
 | 
						|
			var err error
 | 
						|
 | 
						|
			elemPath[len(elemPath)-1] = cty.IndexStep{
 | 
						|
				Key: cty.NumberIntVal(i),
 | 
						|
			}
 | 
						|
 | 
						|
			if conv != nil {
 | 
						|
				val, err = conv(val, elemPath)
 | 
						|
				if err != nil {
 | 
						|
					return cty.NilVal, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			elems = append(elems, val)
 | 
						|
 | 
						|
			i++
 | 
						|
		}
 | 
						|
 | 
						|
		if len(elems) == 0 {
 | 
						|
			if ety == cty.DynamicPseudoType {
 | 
						|
				ety = val.Type().ElementType()
 | 
						|
			}
 | 
						|
			return cty.ListValEmpty(ety), nil
 | 
						|
		}
 | 
						|
 | 
						|
		return cty.ListVal(elems), nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// conversionCollectionToSet returns a conversion that will apply the given
 | 
						|
// conversion to all of the elements of a collection (something that supports
 | 
						|
// ForEachElement and LengthInt) and then returns the result as a set.
 | 
						|
//
 | 
						|
// "conv" can be nil if the elements are expected to already be of the
 | 
						|
// correct type and just need to be re-wrapped into a set. (For example,
 | 
						|
// if we're converting from a list into a set of the same element type.)
 | 
						|
func conversionCollectionToSet(ety cty.Type, conv conversion) conversion {
 | 
						|
	return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
		elems := make([]cty.Value, 0, val.LengthInt())
 | 
						|
		i := int64(0)
 | 
						|
		elemPath := append(path.Copy(), nil)
 | 
						|
		it := val.ElementIterator()
 | 
						|
		for it.Next() {
 | 
						|
			_, val := it.Element()
 | 
						|
			var err error
 | 
						|
 | 
						|
			elemPath[len(elemPath)-1] = cty.IndexStep{
 | 
						|
				Key: cty.NumberIntVal(i),
 | 
						|
			}
 | 
						|
 | 
						|
			if conv != nil {
 | 
						|
				val, err = conv(val, elemPath)
 | 
						|
				if err != nil {
 | 
						|
					return cty.NilVal, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			elems = append(elems, val)
 | 
						|
 | 
						|
			i++
 | 
						|
		}
 | 
						|
 | 
						|
		if len(elems) == 0 {
 | 
						|
			// Prefer a concrete type over a dynamic type when returning an
 | 
						|
			// empty set
 | 
						|
			if ety == cty.DynamicPseudoType {
 | 
						|
				ety = val.Type().ElementType()
 | 
						|
			}
 | 
						|
			return cty.SetValEmpty(ety), nil
 | 
						|
		}
 | 
						|
 | 
						|
		return cty.SetVal(elems), nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// conversionCollectionToMap returns a conversion that will apply the given
 | 
						|
// conversion to all of the elements of a collection (something that supports
 | 
						|
// ForEachElement and LengthInt) and then returns the result as a map.
 | 
						|
//
 | 
						|
// "conv" can be nil if the elements are expected to already be of the
 | 
						|
// correct type and just need to be re-wrapped into a map.
 | 
						|
func conversionCollectionToMap(ety cty.Type, conv conversion) conversion {
 | 
						|
	return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
		elems := make(map[string]cty.Value, 0)
 | 
						|
		elemPath := append(path.Copy(), nil)
 | 
						|
		it := val.ElementIterator()
 | 
						|
		for it.Next() {
 | 
						|
			key, val := it.Element()
 | 
						|
			var err error
 | 
						|
 | 
						|
			elemPath[len(elemPath)-1] = cty.IndexStep{
 | 
						|
				Key: key,
 | 
						|
			}
 | 
						|
 | 
						|
			keyStr, err := Convert(key, cty.String)
 | 
						|
			if err != nil {
 | 
						|
				// Should never happen, because keys can only be numbers or
 | 
						|
				// strings and both can convert to string.
 | 
						|
				return cty.DynamicVal, elemPath.NewErrorf("cannot convert key type %s to string for map", key.Type().FriendlyName())
 | 
						|
			}
 | 
						|
 | 
						|
			if conv != nil {
 | 
						|
				val, err = conv(val, elemPath)
 | 
						|
				if err != nil {
 | 
						|
					return cty.NilVal, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			elems[keyStr.AsString()] = val
 | 
						|
		}
 | 
						|
 | 
						|
		if len(elems) == 0 {
 | 
						|
			// Prefer a concrete type over a dynamic type when returning an
 | 
						|
			// empty map
 | 
						|
			if ety == cty.DynamicPseudoType {
 | 
						|
				ety = val.Type().ElementType()
 | 
						|
			}
 | 
						|
			return cty.MapValEmpty(ety), nil
 | 
						|
		}
 | 
						|
 | 
						|
		if ety.IsCollectionType() || ety.IsObjectType() {
 | 
						|
			var err error
 | 
						|
			if elems, err = conversionUnifyCollectionElements(elems, path, false); err != nil {
 | 
						|
				return cty.NilVal, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if err := conversionCheckMapElementTypes(elems, path); err != nil {
 | 
						|
			return cty.NilVal, err
 | 
						|
		}
 | 
						|
 | 
						|
		return cty.MapVal(elems), nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// conversionTupleToSet returns a conversion that will take a value of the
 | 
						|
// given tuple type and return a set of the given element type.
 | 
						|
//
 | 
						|
// Will panic if the given tupleType isn't actually a tuple type.
 | 
						|
func conversionTupleToSet(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion {
 | 
						|
	tupleEtys := tupleType.TupleElementTypes()
 | 
						|
 | 
						|
	if len(tupleEtys) == 0 {
 | 
						|
		// Empty tuple short-circuit
 | 
						|
		return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
			return cty.SetValEmpty(listEty), nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if listEty == cty.DynamicPseudoType {
 | 
						|
		// This is a special case where the caller wants us to find
 | 
						|
		// a suitable single type that all elements can convert to, if
 | 
						|
		// possible.
 | 
						|
		listEty, _ = unify(tupleEtys, unsafe)
 | 
						|
		if listEty == cty.NilType {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	elemConvs := make([]conversion, len(tupleEtys))
 | 
						|
	for i, tupleEty := range tupleEtys {
 | 
						|
		if tupleEty.Equals(listEty) {
 | 
						|
			// no conversion required
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		elemConvs[i] = getConversion(tupleEty, listEty, unsafe)
 | 
						|
		if elemConvs[i] == nil {
 | 
						|
			// If any of our element conversions are impossible, then the our
 | 
						|
			// whole conversion is impossible.
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If we fall out here then a conversion is possible, using the
 | 
						|
	// element conversions in elemConvs
 | 
						|
	return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
		elems := make([]cty.Value, 0, len(elemConvs))
 | 
						|
		elemPath := append(path.Copy(), nil)
 | 
						|
		i := int64(0)
 | 
						|
		it := val.ElementIterator()
 | 
						|
		for it.Next() {
 | 
						|
			_, val := it.Element()
 | 
						|
			var err error
 | 
						|
 | 
						|
			elemPath[len(elemPath)-1] = cty.IndexStep{
 | 
						|
				Key: cty.NumberIntVal(i),
 | 
						|
			}
 | 
						|
 | 
						|
			conv := elemConvs[i]
 | 
						|
			if conv != nil {
 | 
						|
				val, err = conv(val, elemPath)
 | 
						|
				if err != nil {
 | 
						|
					return cty.NilVal, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			elems = append(elems, val)
 | 
						|
 | 
						|
			i++
 | 
						|
		}
 | 
						|
 | 
						|
		return cty.SetVal(elems), nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// conversionTupleToList returns a conversion that will take a value of the
 | 
						|
// given tuple type and return a list of the given element type.
 | 
						|
//
 | 
						|
// Will panic if the given tupleType isn't actually a tuple type.
 | 
						|
func conversionTupleToList(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion {
 | 
						|
	tupleEtys := tupleType.TupleElementTypes()
 | 
						|
 | 
						|
	if len(tupleEtys) == 0 {
 | 
						|
		// Empty tuple short-circuit
 | 
						|
		return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
			return cty.ListValEmpty(listEty), nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if listEty == cty.DynamicPseudoType {
 | 
						|
		// This is a special case where the caller wants us to find
 | 
						|
		// a suitable single type that all elements can convert to, if
 | 
						|
		// possible.
 | 
						|
		listEty, _ = unify(tupleEtys, unsafe)
 | 
						|
		if listEty == cty.NilType {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	elemConvs := make([]conversion, len(tupleEtys))
 | 
						|
	for i, tupleEty := range tupleEtys {
 | 
						|
		if tupleEty.Equals(listEty) {
 | 
						|
			// no conversion required
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		elemConvs[i] = getConversion(tupleEty, listEty, unsafe)
 | 
						|
		if elemConvs[i] == nil {
 | 
						|
			// If any of our element conversions are impossible, then the our
 | 
						|
			// whole conversion is impossible.
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If we fall out here then a conversion is possible, using the
 | 
						|
	// element conversions in elemConvs
 | 
						|
	return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
		elems := make([]cty.Value, 0, len(elemConvs))
 | 
						|
		elemPath := append(path.Copy(), nil)
 | 
						|
		i := int64(0)
 | 
						|
		it := val.ElementIterator()
 | 
						|
		for it.Next() {
 | 
						|
			_, val := it.Element()
 | 
						|
			var err error
 | 
						|
 | 
						|
			elemPath[len(elemPath)-1] = cty.IndexStep{
 | 
						|
				Key: cty.NumberIntVal(i),
 | 
						|
			}
 | 
						|
 | 
						|
			conv := elemConvs[i]
 | 
						|
			if conv != nil {
 | 
						|
				val, err = conv(val, elemPath)
 | 
						|
				if err != nil {
 | 
						|
					return cty.NilVal, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			elems = append(elems, val)
 | 
						|
 | 
						|
			i++
 | 
						|
		}
 | 
						|
 | 
						|
		return cty.ListVal(elems), nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// conversionObjectToMap returns a conversion that will take a value of the
 | 
						|
// given object type and return a map of the given element type.
 | 
						|
//
 | 
						|
// Will panic if the given objectType isn't actually an object type.
 | 
						|
func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) conversion {
 | 
						|
	objectAtys := objectType.AttributeTypes()
 | 
						|
 | 
						|
	if len(objectAtys) == 0 {
 | 
						|
		// Empty object short-circuit
 | 
						|
		return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
			return cty.MapValEmpty(mapEty), nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if mapEty == cty.DynamicPseudoType {
 | 
						|
		// This is a special case where the caller wants us to find
 | 
						|
		// a suitable single type that all elements can convert to, if
 | 
						|
		// possible.
 | 
						|
		objectAtysList := make([]cty.Type, 0, len(objectAtys))
 | 
						|
		for _, aty := range objectAtys {
 | 
						|
			objectAtysList = append(objectAtysList, aty)
 | 
						|
		}
 | 
						|
		mapEty, _ = unify(objectAtysList, unsafe)
 | 
						|
		if mapEty == cty.NilType {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	elemConvs := make(map[string]conversion, len(objectAtys))
 | 
						|
	for name, objectAty := range objectAtys {
 | 
						|
		if objectAty.Equals(mapEty) {
 | 
						|
			// no conversion required
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		elemConvs[name] = getConversion(objectAty, mapEty, unsafe)
 | 
						|
		if elemConvs[name] == nil {
 | 
						|
			// If any of our element conversions are impossible, then the our
 | 
						|
			// whole conversion is impossible.
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If we fall out here then a conversion is possible, using the
 | 
						|
	// element conversions in elemConvs
 | 
						|
	return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
		elems := make(map[string]cty.Value, len(elemConvs))
 | 
						|
		elemPath := append(path.Copy(), nil)
 | 
						|
		it := val.ElementIterator()
 | 
						|
		for it.Next() {
 | 
						|
			name, val := it.Element()
 | 
						|
			var err error
 | 
						|
 | 
						|
			elemPath[len(elemPath)-1] = cty.IndexStep{
 | 
						|
				Key: name,
 | 
						|
			}
 | 
						|
 | 
						|
			conv := elemConvs[name.AsString()]
 | 
						|
			if conv != nil {
 | 
						|
				val, err = conv(val, elemPath)
 | 
						|
				if err != nil {
 | 
						|
					return cty.NilVal, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			elems[name.AsString()] = val
 | 
						|
		}
 | 
						|
 | 
						|
		if mapEty.IsCollectionType() || mapEty.IsObjectType() {
 | 
						|
			var err error
 | 
						|
			if elems, err = conversionUnifyCollectionElements(elems, path, unsafe); err != nil {
 | 
						|
				return cty.NilVal, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if err := conversionCheckMapElementTypes(elems, path); err != nil {
 | 
						|
			return cty.NilVal, err
 | 
						|
		}
 | 
						|
 | 
						|
		return cty.MapVal(elems), nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// conversionMapToObject returns a conversion that will take a value of the
 | 
						|
// given map type and return an object of the given type. The object attribute
 | 
						|
// types must all be compatible with the map element type.
 | 
						|
//
 | 
						|
// Will panic if the given mapType and objType are not maps and objects
 | 
						|
// respectively.
 | 
						|
func conversionMapToObject(mapType cty.Type, objType cty.Type, unsafe bool) conversion {
 | 
						|
	objectAtys := objType.AttributeTypes()
 | 
						|
	mapEty := mapType.ElementType()
 | 
						|
 | 
						|
	elemConvs := make(map[string]conversion, len(objectAtys))
 | 
						|
	for name, objectAty := range objectAtys {
 | 
						|
		if objectAty.Equals(mapEty) {
 | 
						|
			// no conversion required
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		elemConvs[name] = getConversion(mapEty, objectAty, unsafe)
 | 
						|
		if elemConvs[name] == nil {
 | 
						|
			// If any of our element conversions are impossible, then the our
 | 
						|
			// whole conversion is impossible.
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If we fall out here then a conversion is possible, using the
 | 
						|
	// element conversions in elemConvs
 | 
						|
	return func(val cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
		elems := make(map[string]cty.Value, len(elemConvs))
 | 
						|
		elemPath := append(path.Copy(), nil)
 | 
						|
		it := val.ElementIterator()
 | 
						|
		for it.Next() {
 | 
						|
			name, val := it.Element()
 | 
						|
 | 
						|
			// if there is no corresponding attribute, we skip this key
 | 
						|
			if _, ok := objectAtys[name.AsString()]; !ok {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			var err error
 | 
						|
 | 
						|
			elemPath[len(elemPath)-1] = cty.IndexStep{
 | 
						|
				Key: name,
 | 
						|
			}
 | 
						|
 | 
						|
			conv := elemConvs[name.AsString()]
 | 
						|
			if conv != nil {
 | 
						|
				val, err = conv(val, elemPath)
 | 
						|
				if err != nil {
 | 
						|
					return cty.NilVal, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			elems[name.AsString()] = val
 | 
						|
		}
 | 
						|
 | 
						|
		return cty.ObjectVal(elems), nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func conversionUnifyCollectionElements(elems map[string]cty.Value, path cty.Path, unsafe bool) (map[string]cty.Value, error) {
 | 
						|
	elemTypes := make([]cty.Type, 0, len(elems))
 | 
						|
	for _, elem := range elems {
 | 
						|
		elemTypes = append(elemTypes, elem.Type())
 | 
						|
	}
 | 
						|
	unifiedType, _ := unify(elemTypes, unsafe)
 | 
						|
	if unifiedType == cty.NilType {
 | 
						|
	}
 | 
						|
 | 
						|
	unifiedElems := make(map[string]cty.Value)
 | 
						|
	elemPath := append(path.Copy(), nil)
 | 
						|
 | 
						|
	for name, elem := range elems {
 | 
						|
		if elem.Type().Equals(unifiedType) {
 | 
						|
			unifiedElems[name] = elem
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		conv := getConversion(elem.Type(), unifiedType, unsafe)
 | 
						|
		if conv == nil {
 | 
						|
		}
 | 
						|
		elemPath[len(elemPath)-1] = cty.IndexStep{
 | 
						|
			Key: cty.StringVal(name),
 | 
						|
		}
 | 
						|
		val, err := conv(elem, elemPath)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		unifiedElems[name] = val
 | 
						|
	}
 | 
						|
 | 
						|
	return unifiedElems, nil
 | 
						|
}
 | 
						|
 | 
						|
func conversionCheckMapElementTypes(elems map[string]cty.Value, path cty.Path) error {
 | 
						|
	elementType := cty.NilType
 | 
						|
	elemPath := append(path.Copy(), nil)
 | 
						|
 | 
						|
	for name, elem := range elems {
 | 
						|
		if elementType == cty.NilType {
 | 
						|
			elementType = elem.Type()
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if !elementType.Equals(elem.Type()) {
 | 
						|
			elemPath[len(elemPath)-1] = cty.IndexStep{
 | 
						|
				Key: cty.StringVal(name),
 | 
						|
			}
 | 
						|
			return elemPath.NewErrorf("%s is required", elementType.FriendlyName())
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 |