diff --git a/pkg/templates/funcs.go b/pkg/templates/funcs.go index 70e9866..76fb7f6 100644 --- a/pkg/templates/funcs.go +++ b/pkg/templates/funcs.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "os" + "sort" "strings" "text/template" @@ -84,26 +85,34 @@ func TmplInt(path string, input interface{}) int { return value.(int) } -// TODO: Add description +// TmplArray parses a YAML path in the input parameter aas an arraay. +// +// The array can be of any type but order is only deterministic for strings. func TmplArray(path string, input interface{}) []interface{} { - value := TmplGet(path, input) - switch value.(type) { - case map[interface{}]interface{}: - values := []interface{}{} - for _, v := range input.(map[interface{}]interface{}) { - values = append(values, v) - } - return values - case map[string]interface{}: - values := []interface{}{} - for _, v := range input.(map[string]interface{}) { - values = append(values, v) + rawValue := TmplGet(path, input) + rawValues, ok := rawValue.([]interface{}) + if !ok { + return nil + } + + var stringValues []string + var values []interface{} + for _, v := range rawValues { + s, ok := v.(string) + if ok { + stringValues = append(stringValues, s) + continue } - return values - case []interface{}: - return value.([]interface{}) + values = append(values, v) } - return nil + sort.Slice(stringValues, func(i, j int) bool { + return stringValues[i] < stringValues[j] + }) + for _, v := range stringValues { + values = append(values, v) + } + + return values } // TODO: Add description @@ -112,21 +121,21 @@ func TmplObjectArray(path string, input interface{}) []KeyValuePair { return nil } value := TmplGet(path, input) + values := []KeyValuePair{} switch value.(type) { case map[interface{}]interface{}: - values := []KeyValuePair{} for k, v := range value.(map[interface{}]interface{}) { values = append(values, KeyValuePair{Key: k.(string), Value: v}) } - return values case map[string]interface{}: - values := []KeyValuePair{} for k, v := range value.(map[string]interface{}) { values = append(values, KeyValuePair{Key: k, Value: v}) } - return values } - return nil + sort.Slice(values, func(i, j int) bool { + return values[i].Key < values[j].Key + }) + return values } func TmplIs(a interface{}, b interface{}) bool { @@ -189,6 +198,9 @@ func TmplGetFiles(directoryPath string) []os.FileInfo { if err != nil { return []os.FileInfo{} // TODO: Print error to shuttle output } + sort.Slice(files, func(i, j int) bool { + return files[i].Name() < files[j].Name() + }) return files } diff --git a/pkg/templates/funcs_test.go b/pkg/templates/funcs_test.go index 37e8ba5..ed6d862 100644 --- a/pkg/templates/funcs_test.go +++ b/pkg/templates/funcs_test.go @@ -57,6 +57,128 @@ func TestTmplInt(t *testing.T) { } } +func TestTmplArray(t *testing.T) { + type input struct { + path string + data interface{} + } + tt := []struct { + name string + input input + output []interface{} + }{ + { + name: "nil input", + input: input{ + path: "b", + data: nil, + }, + output: nil, + }, + { + name: "empty input", + input: input{ + path: "b", + data: fromYaml(""), + }, + output: nil, + }, + { + name: "empty array", + input: input{ + path: "b", + data: fromYaml(` +b: +`), + }, + output: nil, + }, + { + name: "single value string array", + input: input{ + path: "b", + data: fromYaml(` +b: +- 'a' +`), + }, + output: []interface{}{ + "a", + }, + }, + { + name: "single value object array", + input: input{ + path: "b", + data: fromYaml(` +b: +- name: 'a' + field: 'b' +`), + }, + output: []interface{}{ + map[interface{}]interface{}{ + "name": "a", + "field": "b", + }, + }, + }, + { + name: "large string array testing for deterministic order", + input: input{ + path: "a", + data: fromYaml(`a: + - 'b' + - 'd' + - 'e' + - 'g' + - 'h' + - 'f' + - 'i' + - 's' + - 'k' + - 'c' + - 'l' + - 'm' + - 'n' + - 'o' + - 'r' + - 'j' + - 'p' + - 'q' +`), + }, + output: []interface{}{ + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + }, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + output := TmplArray(tc.input.path, tc.input.data) + + assert.Equal(t, tc.output, output, "output does not match the expected") + }) + } +} + func TestTmpObjArray(t *testing.T) { type input struct { path string @@ -73,7 +195,7 @@ func TestTmpObjArray(t *testing.T) { path: "b", data: nil, }, - output: []KeyValuePair{}, + output: nil, }, { name: "empty input", @@ -98,6 +220,52 @@ b: {Key: "h", Value: "ewff"}, }, }, + { + name: "large set testing for deterministic order", + input: input{ + path: "a", + data: fromYaml(`a: + 'b': 2 + 'd': 4 + 'e': 5 + 'g': 7 + 'h': 8 + 'f': 6 + 'i': 9 + 's': 19 + 'k': 11 + 'c': 3 + 'l': 12 + 'm': 13 + 'n': 14 + 'o': 15 + 'r': 18 + 'j': 10 + 'p': 16 + 'q': 17 +`), + }, + output: []KeyValuePair{ + {Key: "b", Value: 2}, + {Key: "c", Value: 3}, + {Key: "d", Value: 4}, + {Key: "e", Value: 5}, + {Key: "f", Value: 6}, + {Key: "g", Value: 7}, + {Key: "h", Value: 8}, + {Key: "i", Value: 9}, + {Key: "j", Value: 10}, + {Key: "k", Value: 11}, + {Key: "l", Value: 12}, + {Key: "m", Value: 13}, + {Key: "n", Value: 14}, + {Key: "o", Value: 15}, + {Key: "p", Value: 16}, + {Key: "q", Value: 17}, + {Key: "r", Value: 18}, + {Key: "s", Value: 19}, + }, + }, { name: "non object value in path", input: input{ @@ -111,7 +279,7 @@ b: t.Run(tc.name, func(t *testing.T) { output := TmplObjectArray(tc.input.path, tc.input.data) - assert.ElementsMatch(t, tc.output, output, "output does not match the expected") + assert.Equal(t, tc.output, output, "output does not match the expected") }) } }