From e3ce2356875400b6f26fdef449dbf14cd220dcbe Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Mon, 26 Oct 2020 17:23:17 +0700 Subject: [PATCH] Add `chunk` function to split array into smaller --- docs/lists.md | 10 ++++++++++ functions.go | 2 ++ list.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ list_test.go | 26 ++++++++++++++++++++++++++ 4 files changed, 83 insertions(+) diff --git a/docs/lists.md b/docs/lists.md index af294a87..d68fb283 100644 --- a/docs/lists.md +++ b/docs/lists.md @@ -168,6 +168,16 @@ equivalent of `list[n:m]`. `slice` panics if there is a problem while `mustSlice` returns an error to the template engine if there is a problem. +## chunk + +To split a list into chunks of given size, use `chunk size list`. This is useful for pagination. + +``` +chunk 3 (list 1 2 3 4 5 6 7 8) +``` + +This produces list of lists `[ [ 1 2 3 ] [ 4 5 6 ] [ 7 8 ] ]`. + ## A Note on List Internals A list is implemented in Go as a `[]interface{}`. For Go developers embedding diff --git a/functions.go b/functions.go index e9fb37e8..9e3376bc 100644 --- a/functions.go +++ b/functions.go @@ -313,6 +313,8 @@ var genericMap = map[string]interface{}{ "mustSlice": mustSlice, "concat": concat, "dig": dig, + "chunk": chunk, + "mustChunk": mustChunk, // Crypto: "bcrypt": bcrypt, diff --git a/list.go b/list.go index 063fe4f8..ca0fbb78 100644 --- a/list.go +++ b/list.go @@ -2,6 +2,7 @@ package sprig import ( "fmt" + "math" "reflect" "sort" ) @@ -72,6 +73,50 @@ func mustPrepend(list interface{}, v interface{}) ([]interface{}, error) { } } +func chunk(size int, list interface{}) [][]interface{} { + l, err := mustChunk(size, list) + if err != nil { + panic(err) + } + + return l +} + +func mustChunk(size int, list interface{}) ([][]interface{}, error) { + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + l2 := reflect.ValueOf(list) + + l := l2.Len() + + cs := int(math.Floor(float64(l-1)/float64(size)) + 1) + nl := make([][]interface{}, cs) + + for i := 0; i < cs; i++ { + clen := size + if i == cs-1 { + clen = int(math.Floor(math.Mod(float64(l), float64(size)))) + if clen == 0 { + clen = size + } + } + + nl[i] = make([]interface{}, clen) + + for j := 0; j < clen; j++ { + ix := i*size + j + nl[i][j] = l2.Index(ix).Interface() + } + } + + return nl, nil + + default: + return nil, fmt.Errorf("Cannot chunk type %s", tp) + } +} + func last(list interface{}) interface{} { l, err := mustLast(list) if err != nil { diff --git a/list_test.go b/list_test.go index 12b2a397..ec4c4c14 100644 --- a/list_test.go +++ b/list_test.go @@ -44,6 +44,32 @@ func TestMustPush(t *testing.T) { } } +func TestChunk(t *testing.T) { + tests := map[string]string{ + `{{ tuple 1 2 3 4 5 6 7 | chunk 3 | len }}`: "3", + `{{ tuple | chunk 3 | len }}`: "0", + `{{ range ( tuple 1 2 3 4 5 6 7 8 9 | chunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2-3|4-5-6|7-8-9|", + `{{ range ( tuple 1 2 3 4 5 6 7 8 | chunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2-3|4-5-6|7-8|", + `{{ range ( tuple 1 2 | chunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2|", + } + for tpl, expect := range tests { + assert.NoError(t, runt(tpl, expect)) + } +} + +func TestMustChunk(t *testing.T) { + tests := map[string]string{ + `{{ tuple 1 2 3 4 5 6 7 | mustChunk 3 | len }}`: "3", + `{{ tuple | mustChunk 3 | len }}`: "0", + `{{ range ( tuple 1 2 3 4 5 6 7 8 9 | mustChunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2-3|4-5-6|7-8-9|", + `{{ range ( tuple 1 2 3 4 5 6 7 8 | mustChunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2-3|4-5-6|7-8|", + `{{ range ( tuple 1 2 | mustChunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2|", + } + for tpl, expect := range tests { + assert.NoError(t, runt(tpl, expect)) + } +} + func TestPrepend(t *testing.T) { tests := map[string]string{ `{{ $t := tuple 1 2 3 }}{{ prepend $t 0 | len }}`: "4",