Fix Params case handling in the index, sort and where func

This means that you can now do:

```
{{ range where .Site.Pages "Params.MYPARAM" "foo" }}
```
This commit is contained in:
Bjørn Erik Pedersen
2019-11-21 21:59:38 +01:00
parent cd07e6d57b
commit a3fe5e5e35
33 changed files with 317 additions and 155 deletions

View File

@@ -22,6 +22,8 @@ import (
"testing"
"time"
"github.com/gohugoio/hugo/common/maps"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
@@ -891,6 +893,15 @@ type TstX struct {
unexported string
}
type TstParams struct {
params maps.Params
}
func (x TstParams) Params() maps.Params {
return x.params
}
type TstXIHolder struct {
XI TstXI
}

View File

@@ -17,6 +17,10 @@ import (
"errors"
"fmt"
"reflect"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/common/maps"
)
// Index returns the result of indexing its first argument by the following
@@ -34,6 +38,11 @@ func (ns *Namespace) Index(item interface{}, args ...interface{}) (interface{},
return nil, errors.New("index of untyped nil")
}
lowerm, ok := item.(maps.Params)
if ok {
return lowerm.Get(cast.ToStringSlice(args)...), nil
}
var indices []interface{}
if len(args) == 1 {
@@ -79,6 +88,7 @@ func (ns *Namespace) Index(item interface{}, args ...interface{}) (interface{},
if err != nil {
return nil, err
}
if x := v.MapIndex(index); x.IsValid() {
v = x
} else {

View File

@@ -17,6 +17,8 @@ import (
"fmt"
"testing"
"github.com/gohugoio/hugo/common/maps"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
)
@@ -42,7 +44,8 @@ func TestIndex(t *testing.T) {
{[]map[string]map[string]string{{"a": {"b": "c"}}}, []interface{}{0, "a", "b"}, "c", false},
{map[string]map[string]interface{}{"a": {"b": []string{"c", "d"}}}, []interface{}{"a", "b", 1}, "d", false},
{map[string]map[string]string{"a": {"b": "c"}}, []interface{}{[]string{"a", "b"}}, "c", false},
{maps.Params{"a": "av"}, []interface{}{"A"}, "av", false},
{maps.Params{"a": map[string]interface{}{"b": "bv"}}, []interface{}{"A", "B"}, "bv", false},
// errors
{nil, nil, nil, true},
{[]int{0, 1}, []interface{}{"1"}, nil, true},

View File

@@ -19,6 +19,7 @@ import (
"sort"
"strings"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/tpl/compare"
"github.com/spf13/cast"
)
@@ -75,11 +76,19 @@ func (ns *Namespace) Sort(seq interface{}, args ...interface{}) (interface{}, er
} else {
v := p.Pairs[i].Value
var err error
for _, elemName := range path {
for i, elemName := range path {
v, err = evaluateSubElem(v, elemName)
if err != nil {
return nil, err
}
if !v.IsValid() {
continue
}
// Special handling of lower cased maps.
if params, ok := v.Interface().(maps.Params); ok {
v = reflect.ValueOf(params.Get(path[i+1:]...))
break
}
}
p.Pairs[i].Key = v
}
@@ -89,6 +98,7 @@ func (ns *Namespace) Sort(seq interface{}, args ...interface{}) (interface{}, er
keys := seqv.MapKeys()
for i := 0; i < seqv.Len(); i++ {
p.Pairs[i].Value = seqv.MapIndex(keys[i])
if sortByField == "" {
p.Pairs[i].Key = keys[i]
} else if sortByField == "value" {
@@ -96,11 +106,19 @@ func (ns *Namespace) Sort(seq interface{}, args ...interface{}) (interface{}, er
} else {
v := p.Pairs[i].Value
var err error
for _, elemName := range path {
for i, elemName := range path {
v, err = evaluateSubElem(v, elemName)
if err != nil {
return nil, err
}
if !v.IsValid() {
continue
}
// Special handling of lower cased maps.
if params, ok := v.Interface().(maps.Params); ok {
v = reflect.ValueOf(params.Get(path[i+1:]...))
break
}
}
p.Pairs[i].Key = v
}
@@ -135,6 +153,7 @@ func (p pairList) Less(i, j int) bool {
// can only call Interface() on valid reflect Values
return sortComp.Lt(iv.Interface(), jv.Interface())
}
// if j is invalid, test i against i's zero value
return sortComp.Lt(iv.Interface(), reflect.Zero(iv.Type()))
}

View File

@@ -18,6 +18,8 @@ import (
"reflect"
"testing"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/deps"
)
@@ -100,6 +102,20 @@ func TestSort(t *testing.T) {
"asc",
[]*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
},
// Lower case Params, slice
{
[]TstParams{{params: maps.Params{"color": "indigo"}}, {params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}},
".Params.COLOR",
"asc",
[]TstParams{{params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}, {params: maps.Params{"color": "indigo"}}},
},
// Lower case Params, map
{
map[string]TstParams{"1": {params: maps.Params{"color": "indigo"}}, "2": {params: maps.Params{"color": "blue"}}, "3": {params: maps.Params{"color": "green"}}},
".Params.CoLoR",
"asc",
[]TstParams{{params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}, {params: maps.Params{"color": "indigo"}}},
},
// test map sorting by struct's method
{
map[string]TstX{"1": {A: "i", B: "j"}, "2": {A: "e", B: "f"}, "3": {A: "c", B: "d"}, "4": {A: "g", B: "h"}, "5": {A: "a", B: "b"}},

View File

@@ -18,6 +18,8 @@ import (
"fmt"
"reflect"
"strings"
"github.com/gohugoio/hugo/common/maps"
)
// Where returns a filtered subset of a given data type.
@@ -277,6 +279,7 @@ func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error)
if !obj.IsValid() {
return zero, errors.New("can't evaluate an invalid value")
}
typ := obj.Type()
obj, isNil := indirect(obj)
@@ -295,6 +298,7 @@ func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error)
if objPtr.Kind() != reflect.Interface && objPtr.CanAddr() {
objPtr = objPtr.Addr()
}
mt, ok := objPtr.Type().MethodByName(elemName)
if ok {
switch {
@@ -368,16 +372,22 @@ func parseWhereArgs(args ...interface{}) (mv reflect.Value, op string, err error
// Array or Slice.
func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string, op string) (interface{}, error) {
rv := reflect.MakeSlice(seqv.Type(), 0, 0)
for i := 0; i < seqv.Len(); i++ {
var vvv reflect.Value
rvv := seqv.Index(i)
if kv.Kind() == reflect.String {
vvv = rvv
for _, elemName := range path {
var err error
vvv, err = evaluateSubElem(vvv, elemName)
if err != nil {
continue
if params, ok := rvv.Interface().(maps.Params); ok {
vvv = reflect.ValueOf(params.Get(path...))
} else {
vvv = rvv
for _, elemName := range path {
var err error
vvv, err = evaluateSubElem(vvv, elemName)
if err != nil {
continue
}
}
}
} else {

View File

@@ -16,9 +16,12 @@ package collections
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/deps"
)
@@ -162,6 +165,37 @@ func TestWhere(t *testing.T) {
{1: "a", 2: "m"},
},
},
{
seq: []maps.Params{
{"a": "a1", "b": "b1"}, {"a": "a2", "b": "b2"},
},
key: "B", match: "b2",
expect: []maps.Params{
maps.Params{"a": "a2", "b": "b2"},
},
},
{
seq: []maps.Params{
maps.Params{
"a": map[string]interface{}{
"b": "b1",
},
},
maps.Params{
"a": map[string]interface{}{
"b": "b2",
},
},
},
key: "A.B", match: "b2",
expect: []maps.Params{
maps.Params{
"a": map[string]interface{}{
"b": "b2",
},
},
},
},
{
seq: []*TstX{
{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
@@ -557,11 +591,24 @@ func TestWhere(t *testing.T) {
"zap": []interface{}{map[interface{}]interface{}{"a": 5, "b": 6}},
},
},
{
seq: map[string]interface{}{
"foo": []interface{}{maps.Params{"a": 1, "b": 2}},
"bar": []interface{}{maps.Params{"a": 3, "b": 4}},
"zap": []interface{}{maps.Params{"a": 5, "b": 6}},
},
key: "B", op: ">", match: 3,
expect: map[string]interface{}{
"bar": []interface{}{maps.Params{"a": 3, "b": 4}},
"zap": []interface{}{maps.Params{"a": 5, "b": 6}},
},
},
} {
testVariants := createTestVariants(test)
for j, test := range testVariants {
name := fmt.Sprintf("[%d/%d] %T %s %s", i, j, test.seq, test.op, test.key)
name := fmt.Sprintf("%d/%d %T %s %s", i, j, test.seq, test.op, test.key)
name = strings.ReplaceAll(name, "[]", "slice-of-")
t.Run(name, func(t *testing.T) {
var results interface{}
var err error

View File

@@ -19,11 +19,11 @@ import (
"fmt"
"path/filepath"
_errors "github.com/pkg/errors"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource"
_errors "github.com/pkg/errors"
"github.com/gohugoio/hugo/resources/resource_factories/bundler"
"github.com/gohugoio/hugo/resources/resource_factories/create"
@@ -301,7 +301,7 @@ func (ns *Namespace) resolveArgs(args []interface{}) (resources.ResourceTransfor
return nil, nil, fmt.Errorf("type %T not supported in Resource transformations", args[0])
}
m, err := cast.ToStringMapE(args[0])
m, err := maps.ToStringMapE(args[0])
if err != nil {
return nil, nil, _errors.Wrap(err, "invalid options type")
}

View File

@@ -19,11 +19,10 @@ import (
texttemplate "text/template"
"text/template/parse"
"github.com/pkg/errors"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/tpl"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
"github.com/pkg/errors"
)
// decl keeps track of the variable mappings, i.e. $mysite => .Site etc.
@@ -315,7 +314,7 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
if s, ok := cmd.Args[0].(*parse.StringNode); ok {
errMsg := "failed to decode $_hugo_config in template"
m, err := cast.ToStringMapE(s.Text)
m, err := maps.ToStringMapE(s.Text)
if err != nil {
c.err = errors.Wrap(err, errMsg)
return