Skip to content

Instantly share code, notes, and snippets.

@xkisu
Created February 18, 2023 11:30
Show Gist options
  • Save xkisu/c57ccd54eb3509323af3ccca3aa2e08f to your computer and use it in GitHub Desktop.
Save xkisu/c57ccd54eb3509323af3ccca3aa2e08f to your computer and use it in GitHub Desktop.
Generate matrix combinations from a source set of maps
package main
import (
"fmt"
"sort"
"strings"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/json"
)
func generateCombinations(m map[string]cty.Value, combination map[string]cty.Value, keys []string) []map[string]cty.Value {
var results []map[string]cty.Value
if len(keys) == 0 {
result := make(map[string]cty.Value)
for k, v := range combination {
result[k] = v
}
results = append(results, result)
return results
}
key := keys[0] // pop the first key off the key list to process
keys = keys[1:] // dereference after the first key to include keys to process
value := m[key] // get the value of the key to process
// Determine the type of value to decode
if value.Type().IsListType() { // decodes slices
collection := value.AsValueSlice()
for _, elem := range collection {
combination[key] = elem
results = append(results, generateCombinations(m, combination, keys)...)
}
} else if value.Type().IsMapType() || value.Type().IsObjectType(){ // decodes nested maps/objects
// TODO: Skip processing if the value is empty
// if !value.Type().IsObjectType() || value.Type() != cty.EmptyObject {
// do processing..
// }
for nestedKey, nestedValue := range value.AsValueMap() {
newKey := fmt.Sprintf("%s.%s", key, nestedKey)
combination[newKey] = nestedValue
results = append(results, generateCombinations(m, combination, keys)...)
delete(combination, newKey)
}
} else { // decodes primitive types
combination[key] = value
results = generateCombinations(m, combination, keys)
}
return results
}
func flattenSourceCombinations (m map[string]cty.Value)map[string]cty.Value {
results := make(map[string]cty.Value)
for key, value := range m {
if value.Type().IsObjectType() || value.Type().IsMapType() {
for nestedKey, nestedValue := range value.AsValueMap() {
if nestedValue.Type().IsObjectType() || nestedValue.Type().IsMapType() {
nestedResults := flattenSourceCombinations(nestedValue.AsValueMap())
for nestedResultKey, nestedResultValue := range nestedResults {
results[fmt.Sprintf("%s.%s.%s", key, nestedKey, nestedResultKey)] = nestedResultValue
}
} else {
results[fmt.Sprintf("%s.%s", key, nestedKey)] = nestedValue
}
}
} else { // Primates and slices are preserved
results[key] = value
}
}
return results
}
func MatrixCombinations(matrix map[string]cty.Value) []map[string]cty.Value {
// Recursively flatten any nested maps and objects defined
// in the matrix and fold them back into the matrix.
matrix = flattenSourceCombinations(matrix)
var keys []string
for key := range matrix {
keys = append(keys, key)
}
return generateCombinations(matrix, make(map[string]cty.Value), keys)
}
func main() {
res := MatrixCombinations(map[string]cty.Value{
"flavour": cty.ListVal([]cty.Value{
cty.StringVal("vanilla"),
cty.StringVal("spigot"),
}),
"version": cty.ObjectVal(map[string]cty.Value{
"number": cty.ListVal([]cty.Value{
cty.StringVal("1.18"),
cty.StringVal("1.19"),
}),
"experimental": cty.ListVal([]cty.Value{
cty.BoolVal(true),
cty.BoolVal(false),
}),
}),
})
fmt.Printf("%#v\n", res)
for _, combo := range res {
var marshaled []string
var keys []string
for key := range combo {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
value := combo[key]
b, err := json.Marshal(value, value.Type())
if err != nil {
panic(err)
}
marshaled = append(marshaled, fmt.Sprintf("%s: %s", key, string(b)))
}
fmt.Println(fmt.Sprintf("[%s]", strings.Join(marshaled, ", ")))
}
}
// Result:
// [flavour: "vanilla", version.experimental: true, version.number: "1.18"]
// [flavour: "vanilla", version.experimental: true, version.number: "1.19"]
// [flavour: "vanilla", version.experimental: false, version.number: "1.18"]
// [flavour: "vanilla", version.experimental: false, version.number: "1.19"]
// [flavour: "spigot", version.experimental: true, version.number: "1.18"]
// [flavour: "spigot", version.experimental: true, version.number: "1.19"]
// [flavour: "spigot", version.experimental: false, version.number: "1.18"]
// [flavour: "spigot", version.experimental: false, version.number: "1.19"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment