mirror of
https://github.com/gohugoio/hugo.git
synced 2025-09-02 22:52:51 +02:00
tpl/collections: Add group template func
This extends the page grouping in Hugo with a template function that allows for ad-hoc grouping. A made-up example: ``` {{ $cool := where .Site.RegularPages "Params.cool" true | group "cool" }} {{ $blue := where .Site.RegularPages "Params.blue" true | group "blue" }} {{ $paginator := .Paginate (slice $cool $blue) }} ``` Closes #4865
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/collections"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
@@ -311,6 +312,30 @@ func (ns *Namespace) Intersect(l1, l2 interface{}) (interface{}, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Group groups a set of elements by the given key.
|
||||
// This is currently only supported for Pages.
|
||||
func (ns *Namespace) Group(key interface{}, items interface{}) (interface{}, error) {
|
||||
if key == nil {
|
||||
return nil, errors.New("nil is not a valid key to group by")
|
||||
}
|
||||
|
||||
tp := reflect.TypeOf(items)
|
||||
switch tp.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
tp = tp.Elem()
|
||||
if tp.Kind() == reflect.Ptr {
|
||||
tp = tp.Elem()
|
||||
}
|
||||
in := reflect.Zero(tp).Interface()
|
||||
switch vv := in.(type) {
|
||||
case collections.Grouper:
|
||||
return vv.Group(key, items)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("grouping not supported for type %T", items)
|
||||
}
|
||||
|
||||
// IsSet returns whether a given array, channel, slice, or map has a key
|
||||
// defined.
|
||||
func (ns *Namespace) IsSet(a interface{}, key interface{}) (bool, error) {
|
||||
|
@@ -75,6 +75,47 @@ func TestAfter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type tstGrouper struct {
|
||||
}
|
||||
|
||||
type tstGroupers []*tstGrouper
|
||||
|
||||
func (g tstGrouper) Group(key interface{}, items interface{}) (interface{}, error) {
|
||||
ilen := reflect.ValueOf(items).Len()
|
||||
return fmt.Sprintf("%v(%d)", key, ilen), nil
|
||||
}
|
||||
|
||||
func TestGroup(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ns := New(&deps.Deps{})
|
||||
|
||||
for i, test := range []struct {
|
||||
key interface{}
|
||||
items interface{}
|
||||
expect interface{}
|
||||
}{
|
||||
{"a", []*tstGrouper{&tstGrouper{}, &tstGrouper{}}, "a(2)"},
|
||||
{"b", tstGroupers{&tstGrouper{}, &tstGrouper{}}, "b(2)"},
|
||||
{"a", []tstGrouper{tstGrouper{}, tstGrouper{}}, "a(2)"},
|
||||
{"a", []*tstGrouper{}, "a(0)"},
|
||||
{"a", []string{"a", "b"}, false},
|
||||
{nil, []*tstGrouper{&tstGrouper{}, &tstGrouper{}}, false},
|
||||
} {
|
||||
errMsg := fmt.Sprintf("[%d] %v", i, test)
|
||||
|
||||
result, err := ns.Group(test.key, test.items)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
require.Error(t, err, errMsg)
|
||||
continue
|
||||
}
|
||||
|
||||
require.NoError(t, err, errMsg)
|
||||
require.Equal(t, test.expect, result, errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@@ -138,6 +138,11 @@ func init() {
|
||||
[][2]string{},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Group,
|
||||
[]string{"group"},
|
||||
[][2]string{},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.Seq,
|
||||
[]string{"seq"},
|
||||
[][2]string{
|
||||
|
Reference in New Issue
Block a user