mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
tpl/collections: Fix WordCount (etc.) regression in Where, Sort, Delimit
Fixes #11234
This commit is contained in:
parent
f650e4d751
commit
5bec50838c
7 changed files with 86 additions and 27 deletions
|
@ -16,6 +16,7 @@
|
||||||
package collections
|
package collections
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
@ -99,7 +100,7 @@ func (ns *Namespace) After(n any, l any) (any, error) {
|
||||||
|
|
||||||
// Delimit takes a given list l and returns a string delimited by sep.
|
// Delimit takes a given list l and returns a string delimited by sep.
|
||||||
// If last is passed to the function, it will be used as the final delimiter.
|
// If last is passed to the function, it will be used as the final delimiter.
|
||||||
func (ns *Namespace) Delimit(l, sep any, last ...any) (template.HTML, error) {
|
func (ns *Namespace) Delimit(ctx context.Context, l, sep any, last ...any) (template.HTML, error) {
|
||||||
d, err := cast.ToStringE(sep)
|
d, err := cast.ToStringE(sep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -125,7 +126,7 @@ func (ns *Namespace) Delimit(l, sep any, last ...any) (template.HTML, error) {
|
||||||
var str string
|
var str string
|
||||||
switch lv.Kind() {
|
switch lv.Kind() {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
sortSeq, err := ns.Sort(l)
|
sortSeq, err := ns.Sort(ctx, l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
package collections
|
package collections
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -166,9 +167,9 @@ func TestDelimit(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if test.last == nil {
|
if test.last == nil {
|
||||||
result, err = ns.Delimit(test.seq, test.delimiter)
|
result, err = ns.Delimit(context.Background(), test.seq, test.delimiter)
|
||||||
} else {
|
} else {
|
||||||
result, err = ns.Delimit(test.seq, test.delimiter, test.last)
|
result, err = ns.Delimit(context.Background(), test.seq, test.delimiter, test.last)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil, errMsg)
|
c.Assert(err, qt.IsNil, errMsg)
|
||||||
|
|
|
@ -155,3 +155,44 @@ func TestAppendNilsToSliceWithNils(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 11234.
|
||||||
|
func TestWhereWithWordCount(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- config.toml --
|
||||||
|
baseURL = 'http://example.com/'
|
||||||
|
-- layouts/index.html --
|
||||||
|
Home: {{ range where site.RegularPages "WordCount" "gt" 50 }}{{ .Title }}|{{ end }}
|
||||||
|
-- layouts/shortcodes/lorem.html --
|
||||||
|
{{ "ipsum " | strings.Repeat (.Get 0 | int) }}
|
||||||
|
|
||||||
|
-- content/p1.md --
|
||||||
|
---
|
||||||
|
title: "p1"
|
||||||
|
---
|
||||||
|
{{< lorem 100 >}}
|
||||||
|
-- content/p2.md --
|
||||||
|
---
|
||||||
|
title: "p2"
|
||||||
|
---
|
||||||
|
{{< lorem 20 >}}
|
||||||
|
-- content/p3.md --
|
||||||
|
---
|
||||||
|
title: "p3"
|
||||||
|
---
|
||||||
|
{{< lorem 60 >}}
|
||||||
|
`
|
||||||
|
|
||||||
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
},
|
||||||
|
).Build()
|
||||||
|
|
||||||
|
b.AssertFileContent("public/index.html", `
|
||||||
|
Home: p1|p3|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
package collections
|
package collections
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -26,7 +27,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sort returns a sorted copy of the list l.
|
// Sort returns a sorted copy of the list l.
|
||||||
func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
func (ns *Namespace) Sort(ctx context.Context, l any, args ...any) (any, error) {
|
||||||
if l == nil {
|
if l == nil {
|
||||||
return nil, errors.New("sequence must be provided")
|
return nil, errors.New("sequence must be provided")
|
||||||
}
|
}
|
||||||
|
@ -36,6 +37,8 @@ func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||||
return nil, errors.New("can't iterate over a nil value")
|
return nil, errors.New("can't iterate over a nil value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctxv := reflect.ValueOf(ctx)
|
||||||
|
|
||||||
var sliceType reflect.Type
|
var sliceType reflect.Type
|
||||||
switch seqv.Kind() {
|
switch seqv.Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
|
@ -78,7 +81,7 @@ func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||||
v := p.Pairs[i].Value
|
v := p.Pairs[i].Value
|
||||||
var err error
|
var err error
|
||||||
for i, elemName := range path {
|
for i, elemName := range path {
|
||||||
v, err = evaluateSubElem(v, elemName)
|
v, err = evaluateSubElem(ctxv, v, elemName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -108,7 +111,7 @@ func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||||
v := p.Pairs[i].Value
|
v := p.Pairs[i].Value
|
||||||
var err error
|
var err error
|
||||||
for i, elemName := range path {
|
for i, elemName := range path {
|
||||||
v, err = evaluateSubElem(v, elemName)
|
v, err = evaluateSubElem(ctxv, v, elemName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
package collections
|
package collections
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -240,9 +241,9 @@ func TestSort(t *testing.T) {
|
||||||
var result any
|
var result any
|
||||||
var err error
|
var err error
|
||||||
if test.sortByField == nil {
|
if test.sortByField == nil {
|
||||||
result, err = ns.Sort(test.seq)
|
result, err = ns.Sort(context.Background(), test.seq)
|
||||||
} else {
|
} else {
|
||||||
result, err = ns.Sort(test.seq, test.sortByField, test.sortAsc)
|
result, err = ns.Sort(context.Background(), test.seq, test.sortByField, test.sortAsc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if b, ok := test.expect.(bool); ok && !b {
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
package collections
|
package collections
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -24,7 +25,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Where returns a filtered subset of collection c.
|
// Where returns a filtered subset of collection c.
|
||||||
func (ns *Namespace) Where(c, key any, args ...any) (any, error) {
|
func (ns *Namespace) Where(ctx context.Context, c, key any, args ...any) (any, error) {
|
||||||
seqv, isNil := indirect(reflect.ValueOf(c))
|
seqv, isNil := indirect(reflect.ValueOf(c))
|
||||||
if isNil {
|
if isNil {
|
||||||
return nil, errors.New("can't iterate over a nil value of type " + reflect.ValueOf(c).Type().String())
|
return nil, errors.New("can't iterate over a nil value of type " + reflect.ValueOf(c).Type().String())
|
||||||
|
@ -35,6 +36,8 @@ func (ns *Namespace) Where(c, key any, args ...any) (any, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctxv := reflect.ValueOf(ctx)
|
||||||
|
|
||||||
var path []string
|
var path []string
|
||||||
kv := reflect.ValueOf(key)
|
kv := reflect.ValueOf(key)
|
||||||
if kv.Kind() == reflect.String {
|
if kv.Kind() == reflect.String {
|
||||||
|
@ -43,9 +46,9 @@ func (ns *Namespace) Where(c, key any, args ...any) (any, error) {
|
||||||
|
|
||||||
switch seqv.Kind() {
|
switch seqv.Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
return ns.checkWhereArray(seqv, kv, mv, path, op)
|
return ns.checkWhereArray(ctxv, seqv, kv, mv, path, op)
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
return ns.checkWhereMap(seqv, kv, mv, path, op)
|
return ns.checkWhereMap(ctxv, seqv, kv, mv, path, op)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("can't iterate over %v", c)
|
return nil, fmt.Errorf("can't iterate over %v", c)
|
||||||
}
|
}
|
||||||
|
@ -275,7 +278,7 @@ func (ns *Namespace) checkCondition(v, mv reflect.Value, op string) (bool, error
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error) {
|
func evaluateSubElem(ctx, obj reflect.Value, elemName string) (reflect.Value, error) {
|
||||||
if !obj.IsValid() {
|
if !obj.IsValid() {
|
||||||
return zero, errors.New("can't evaluate an invalid value")
|
return zero, errors.New("can't evaluate an invalid value")
|
||||||
}
|
}
|
||||||
|
@ -301,12 +304,20 @@ func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error)
|
||||||
|
|
||||||
index := hreflect.GetMethodIndexByName(objPtr.Type(), elemName)
|
index := hreflect.GetMethodIndexByName(objPtr.Type(), elemName)
|
||||||
if index != -1 {
|
if index != -1 {
|
||||||
|
var args []reflect.Value
|
||||||
mt := objPtr.Type().Method(index)
|
mt := objPtr.Type().Method(index)
|
||||||
|
num := mt.Type.NumIn()
|
||||||
|
maxNumIn := 1
|
||||||
|
if num > 1 && mt.Type.In(1).Implements(hreflect.ContextInterface) {
|
||||||
|
args = []reflect.Value{ctx}
|
||||||
|
maxNumIn = 2
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case mt.PkgPath != "":
|
case mt.PkgPath != "":
|
||||||
return zero, fmt.Errorf("%s is an unexported method of type %s", elemName, typ)
|
return zero, fmt.Errorf("%s is an unexported method of type %s", elemName, typ)
|
||||||
case mt.Type.NumIn() > 1:
|
case mt.Type.NumIn() > maxNumIn:
|
||||||
return zero, fmt.Errorf("%s is a method of type %s but requires more than 1 parameter", elemName, typ)
|
return zero, fmt.Errorf("%s is a method of type %s but requires more than %d parameter", elemName, typ, maxNumIn)
|
||||||
case mt.Type.NumOut() == 0:
|
case mt.Type.NumOut() == 0:
|
||||||
return zero, fmt.Errorf("%s is a method of type %s but returns no output", elemName, typ)
|
return zero, fmt.Errorf("%s is a method of type %s but returns no output", elemName, typ)
|
||||||
case mt.Type.NumOut() > 2:
|
case mt.Type.NumOut() > 2:
|
||||||
|
@ -316,7 +327,7 @@ func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error)
|
||||||
case mt.Type.NumOut() == 2 && !mt.Type.Out(1).Implements(errorType):
|
case mt.Type.NumOut() == 2 && !mt.Type.Out(1).Implements(errorType):
|
||||||
return zero, fmt.Errorf("%s is a method of type %s returning two values but the second value is not an error type", elemName, typ)
|
return zero, fmt.Errorf("%s is a method of type %s returning two values but the second value is not an error type", elemName, typ)
|
||||||
}
|
}
|
||||||
res := objPtr.Method(mt.Index).Call([]reflect.Value{})
|
res := objPtr.Method(mt.Index).Call(args)
|
||||||
if len(res) == 2 && !res[1].IsNil() {
|
if len(res) == 2 && !res[1].IsNil() {
|
||||||
return zero, fmt.Errorf("error at calling a method %s of type %s: %s", elemName, typ, res[1].Interface().(error))
|
return zero, fmt.Errorf("error at calling a method %s of type %s: %s", elemName, typ, res[1].Interface().(error))
|
||||||
}
|
}
|
||||||
|
@ -371,7 +382,7 @@ func parseWhereArgs(args ...any) (mv reflect.Value, op string, err error) {
|
||||||
|
|
||||||
// checkWhereArray handles the where-matching logic when the seqv value is an
|
// checkWhereArray handles the where-matching logic when the seqv value is an
|
||||||
// Array or Slice.
|
// Array or Slice.
|
||||||
func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
func (ns *Namespace) checkWhereArray(ctxv, seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||||
rv := reflect.MakeSlice(seqv.Type(), 0, 0)
|
rv := reflect.MakeSlice(seqv.Type(), 0, 0)
|
||||||
|
|
||||||
for i := 0; i < seqv.Len(); i++ {
|
for i := 0; i < seqv.Len(); i++ {
|
||||||
|
@ -385,7 +396,7 @@ func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string,
|
||||||
vvv = rvv
|
vvv = rvv
|
||||||
for i, elemName := range path {
|
for i, elemName := range path {
|
||||||
var err error
|
var err error
|
||||||
vvv, err = evaluateSubElem(vvv, elemName)
|
vvv, err = evaluateSubElem(ctxv, vvv, elemName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
@ -417,14 +428,14 @@ func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string,
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkWhereMap handles the where-matching logic when the seqv value is a Map.
|
// checkWhereMap handles the where-matching logic when the seqv value is a Map.
|
||||||
func (ns *Namespace) checkWhereMap(seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
func (ns *Namespace) checkWhereMap(ctxv, seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||||
rv := reflect.MakeMap(seqv.Type())
|
rv := reflect.MakeMap(seqv.Type())
|
||||||
keys := seqv.MapKeys()
|
keys := seqv.MapKeys()
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
elemv := seqv.MapIndex(k)
|
elemv := seqv.MapIndex(k)
|
||||||
switch elemv.Kind() {
|
switch elemv.Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
r, err := ns.checkWhereArray(elemv, kv, mv, path, op)
|
r, err := ns.checkWhereArray(ctxv, elemv, kv, mv, path, op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -443,7 +454,7 @@ func (ns *Namespace) checkWhereMap(seqv, kv, mv reflect.Value, path []string, op
|
||||||
|
|
||||||
switch elemvv.Kind() {
|
switch elemvv.Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
r, err := ns.checkWhereArray(elemvv, kv, mv, path, op)
|
r, err := ns.checkWhereArray(ctxv, elemvv, kv, mv, path, op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
package collections
|
package collections
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -641,9 +642,9 @@ func TestWhere(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if len(test.op) > 0 {
|
if len(test.op) > 0 {
|
||||||
results, err = ns.Where(test.seq, test.key, test.op, test.match)
|
results, err = ns.Where(context.Background(), test.seq, test.key, test.op, test.match)
|
||||||
} else {
|
} else {
|
||||||
results, err = ns.Where(test.seq, test.key, test.match)
|
results, err = ns.Where(context.Background(), test.seq, test.key, test.match)
|
||||||
}
|
}
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if b, ok := test.expect.(bool); ok && !b {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -662,17 +663,17 @@ func TestWhere(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
_, err = ns.Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1)
|
_, err = ns.Where(context.Background(), map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Where called with none string op value didn't return an expected error")
|
t.Errorf("Where called with none string op value didn't return an expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ns.Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2)
|
_, err = ns.Where(context.Background(), map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Where called with more than two variable arguments didn't return an expected error")
|
t.Errorf("Where called with more than two variable arguments didn't return an expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ns.Where(map[string]int{"a": 1, "b": 2}, "a")
|
_, err = ns.Where(context.Background(), map[string]int{"a": 1, "b": 2}, "a")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Where called with no variable arguments didn't return an expected error")
|
t.Errorf("Where called with no variable arguments didn't return an expected error")
|
||||||
}
|
}
|
||||||
|
@ -842,7 +843,7 @@ func TestEvaluateSubElem(t *testing.T) {
|
||||||
{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), "1", false},
|
{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), "1", false},
|
||||||
{reflect.ValueOf([]string{"foo", "bar"}), "1", false},
|
{reflect.ValueOf([]string{"foo", "bar"}), "1", false},
|
||||||
} {
|
} {
|
||||||
result, err := evaluateSubElem(test.value, test.key)
|
result, err := evaluateSubElem(reflect.ValueOf(context.Background()), test.value, test.key)
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if b, ok := test.expect.(bool); ok && !b {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("[%d] evaluateSubElem didn't return an expected error", i)
|
t.Errorf("[%d] evaluateSubElem didn't return an expected error", i)
|
||||||
|
|
Loading…
Reference in a new issue