From 79b9d5174d89cdff5fe1fa4a4537dcd0d4db0b8e Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 09:11:12 +0600 Subject: [PATCH] :sparkles: Created collections functions namespace for templates --- .../templateutil/collections/collections.go | 96 +++++++++++++++++++ .../collections/collections_test.go | 39 ++++++++ internal/templateutil/templateutil.go | 11 +++ 3 files changed, 146 insertions(+) create mode 100644 internal/templateutil/collections/collections.go create mode 100644 internal/templateutil/collections/collections_test.go diff --git a/internal/templateutil/collections/collections.go b/internal/templateutil/collections/collections.go new file mode 100644 index 0000000..9ce621f --- /dev/null +++ b/internal/templateutil/collections/collections.go @@ -0,0 +1,96 @@ +package collections + +import ( + "errors" +) + +type ( + Namespace struct{} + + seq struct { + first int + increment int + last int + } +) + +func New() *Namespace { + return &Namespace{} +} + +var ( + ErrSeqFew error = errors.New("seq must have at least one argument") + ErrSeqMany error = errors.New("seq must have no more than three arguments") + ErrSeqZero error = errors.New("seq increment must be not zero") + ErrSeqLess error = errors.New("seq increment must be less than zero") + ErrSeqMore error = errors.New("seq increment must be more than zero") + ErrIndexLess error = errors.New("index is less than zero") + ErrIndexMore error = errors.New("index is more than provided collection size") +) + +func (ns *Namespace) Slice(items ...any) []any { + return items +} + +func (ns *Namespace) Seq(args ...int) ([]int, error) { + if len(args) < 1 { + return nil, ErrSeqFew + } + + if 3 < len(args) { + return nil, ErrSeqMany + } + + opts := seq{ + first: 1, + increment: 1, + last: args[len(args)-1], + } + + switch len(args) { + case 3: // NOTE(toby3d): first, increment, last + opts.increment = args[1] + + switch { + case opts.increment == 0: + return nil, ErrSeqZero + case opts.first < opts.last && opts.increment < 0: + return nil, ErrSeqMore + case opts.last < opts.first && 0 < opts.increment: + return nil, ErrSeqMore + } + + fallthrough + case 2: // NOTE(toby3d): first, last + opts.first = args[0] + case 1: // NOTE(toby3d): last + if opts.last < 0 { + opts.first = opts.last + opts.last = -1 + } + } + + out := make([]int, 0) + + if opts.first < opts.last { + for i := opts.first; i <= opts.last; i += opts.increment { + out = append(out, i) + } + } else { + for i := opts.first; opts.last <= i; i -= opts.increment { + out = append(out, i) + } + } + + return out, nil +} + +func (ns *Namespace) Index(collection []any, index int) (any, error) { + if index < 0 { + return nil, ErrIndexLess + } else if len(collection) < index { + return nil, ErrIndexMore + } + + return collection[index], nil +} diff --git a/internal/templateutil/collections/collections_test.go b/internal/templateutil/collections/collections_test.go new file mode 100644 index 0000000..109fb60 --- /dev/null +++ b/internal/templateutil/collections/collections_test.go @@ -0,0 +1,39 @@ +package collections_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + + "source.toby3d.me/toby3d/home/internal/templateutil/collections" +) + +func TestNamespace_Seq(t *testing.T) { + t.Parallel() + + for name, tc := range map[string]struct { + input []int + expect []int + }{ + "incremental": {[]int{3}, []int{1, 2, 3}}, + "decremental": {[]int{-3}, []int{-3, -2, -1}}, + "stepped": {[]int{1, 2, 4}, []int{1, 3}}, + "positive-region": {[]int{1, 4}, []int{1, 2, 3, 4}}, + "negative-region": {[]int{1, -2}, []int{1, 0, -1, -2}}, + } { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual, err := collections.New().Seq(tc.input...) + if err != nil { + t.Error(err) + } + + if diff := cmp.Diff(tc.expect, actual); diff != "" { + t.Error(diff) + } + }) + } +} diff --git a/internal/templateutil/templateutil.go b/internal/templateutil/templateutil.go index 115a75a..3477c22 100644 --- a/internal/templateutil/templateutil.go +++ b/internal/templateutil/templateutil.go @@ -6,6 +6,7 @@ import ( "io/fs" "source.toby3d.me/toby3d/home/internal/site" + "source.toby3d.me/toby3d/home/internal/templateutil/collections" "source.toby3d.me/toby3d/home/internal/templateutil/partials" "source.toby3d.me/toby3d/home/internal/templateutil/safe" "source.toby3d.me/toby3d/home/internal/templateutil/strings" @@ -69,6 +70,16 @@ func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) { "markdownify": transformNamespace.Markdownify, }, }) + collectionsNamespace := collections.New() + funcs = append(funcs, Function{ + Name: "collections", + Handler: func(v ...any) any { return collectionsNamespace }, + Methods: template.FuncMap{ + "slice": collectionsNamespace.Slice, + "seq": collectionsNamespace.Seq, + "index": collectionsNamespace.Index, + }, + }) for _, f := range funcs { funcMap[f.Name] = f.Handler