From c7a6f6c6989e1e555343a7e70ff9fd8be4bfad02 Mon Sep 17 00:00:00 2001 From: Patrick D'appollonio <930925+patrickdappollonio@users.noreply.github.com> Date: Tue, 4 Oct 2022 02:00:22 +0000 Subject: [PATCH 1/3] Add shuffle, first, last. --- template_functions.go | 84 ++++++++++++++++++++++++++ template_functions_test.go | 120 +++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) diff --git a/template_functions.go b/template_functions.go index 364ea45..610e890 100644 --- a/template_functions.go +++ b/template_functions.go @@ -326,6 +326,90 @@ func after(index any, seq any) (any, error) { return seqv.Slice(indexv, seqv.Len()).Interface(), nil } +func shuffle(seq any) (any, error) { + if seq == nil { + return nil, errors.New("seq must be provided") + } + + seqv := reflect.ValueOf(seq) + seqv, isNil := indirectValue(seqv) + if isNil { + return nil, errors.New("can't iterate over a nil value") + } + + if seqv.Len() == 0 { + return nil, errors.New("can't shuffle an empty sequence") + } + + switch seqv.Kind() { + case reflect.Array, reflect.Slice, reflect.String: + // skip + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) + } + + shuffled := reflect.MakeSlice(reflect.TypeOf(seq), seqv.Len(), seqv.Len()) + + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + randomIndices := rnd.Perm(seqv.Len()) + + for index, value := range randomIndices { + shuffled.Index(value).Set(seqv.Index(index)) + } + + return shuffled.Interface(), nil +} + +func first(seq any) (any, error) { + if seq == nil { + return nil, errors.New("seq must be provided") + } + + seqv := reflect.ValueOf(seq) + seqv, isNil := indirectValue(seqv) + if isNil { + return nil, errors.New("can't iterate over a nil value") + } + + switch seqv.Kind() { + case reflect.Array, reflect.Slice, reflect.String: + // okay + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) + } + + if seqv.Len() == 0 { + return nil, errors.New("can't get first item of an empty sequence") + } + + return seqv.Index(0).Interface(), nil +} + +func last(seq any) (any, error) { + if seq == nil { + return nil, errors.New("seq must be provided") + } + + seqv := reflect.ValueOf(seq) + seqv, isNil := indirectValue(seqv) + if isNil { + return nil, errors.New("can't iterate over a nil value") + } + + switch seqv.Kind() { + case reflect.Array, reflect.Slice, reflect.String: + // okay + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) + } + + if seqv.Len() == 0 { + return nil, errors.New("can't get last item of an empty sequence") + } + + return seqv.Index(seqv.Len() - 1).Interface(), nil +} + func toInt(v interface{}) (int, bool) { switch v := v.(type) { case int: diff --git a/template_functions_test.go b/template_functions_test.go index 8e705c9..bf35203 100644 --- a/template_functions_test.go +++ b/template_functions_test.go @@ -164,3 +164,123 @@ func Test_after(t *testing.T) { }) } } + +func Test_shuffle(t *testing.T) { + tests := []struct { + name string + seq any + wantErr bool + }{ + { + name: "shuffle 1 2 3", + seq: []int{1, 2, 3}, + }, + { + name: "nil", + seq: nil, + wantErr: true, + }, + { + name: "empty", + seq: []int{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := shuffle(tt.seq) + if (err != nil) != tt.wantErr { + t.Errorf("shuffle() error = %v, wantErr %v", err, tt.wantErr) + return + } + + var l1, l2 int + + if got != nil { + l1 = reflect.ValueOf(got).Len() + } + + if tt.seq != nil { + l2 = reflect.ValueOf(tt.seq).Len() + } + + if !tt.wantErr && l1 != l2 { + t.Errorf("shuffle() got length = %d (original: %d)", l1, l2) + } + }) + } +} + +func Test_first(t *testing.T) { + tests := []struct { + name string + seq any + want any + wantErr bool + }{ + { + name: "first 1 2 3", + seq: []int{1, 2, 3}, + want: 1, + }, + { + name: "nil", + seq: nil, + wantErr: true, + }, + { + name: "empty", + seq: []int{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := first(tt.seq) + if (err != nil) != tt.wantErr { + t.Errorf("first() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("first() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_last(t *testing.T) { + tests := []struct { + name string + seq any + want any + wantErr bool + }{ + { + name: "last 1 2 3", + seq: []int{1, 2, 3}, + want: 3, + }, + { + name: "nil", + seq: nil, + wantErr: true, + }, + { + name: "empty", + seq: []int{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := last(tt.seq) + if (err != nil) != tt.wantErr { + t.Errorf("last() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("last() = %v, want %v", got, tt.want) + } + }) + } +} From f35a2f48352322c136231df0dede99d491c919a4 Mon Sep 17 00:00:00 2001 From: Patrick D'appollonio <930925+patrickdappollonio@users.noreply.github.com> Date: Tue, 4 Oct 2022 02:03:15 +0000 Subject: [PATCH 2/3] Expose functions. --- template_functions.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/template_functions.go b/template_functions.go index 610e890..8e995a6 100644 --- a/template_functions.go +++ b/template_functions.go @@ -75,6 +75,9 @@ func getTemplateFunctions(virtualKV map[string]string, strict bool) template.Fun "slice": slice, "after": after, "skip": after, + "shuffle": shuffle, + "first": first, + "last": last, } } From 71bb5adcfe356885f07bff19514d74303c6e07b9 Mon Sep 17 00:00:00 2001 From: Patrick D'appollonio <930925+patrickdappollonio@users.noreply.github.com> Date: Tue, 4 Oct 2022 02:04:18 +0000 Subject: [PATCH 3/3] Add alias for lowercase and uppercase. --- template_functions.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/template_functions.go b/template_functions.go index 8e995a6..8a7abc6 100644 --- a/template_functions.go +++ b/template_functions.go @@ -31,8 +31,10 @@ func getTemplateFunctions(virtualKV map[string]string, strict bool) template.Fun // Go built-ins "lowercase": strings.ToLower, "lower": strings.ToLower, + "tolower": strings.ToLower, "uppercase": strings.ToUpper, "upper": strings.ToUpper, + "toupper": strings.ToUpper, "title": cases.Title, "sprintf": fmt.Sprintf, "printf": fmt.Sprintf,