From a3fe5e5e35f311f22b6b4fc38abfcf64cd2c7d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Thu, 21 Nov 2019 21:59:38 +0100 Subject: 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" }} ``` --- tpl/collections/collections_test.go | 11 +++++++ tpl/collections/index.go | 10 +++++++ tpl/collections/index_test.go | 5 +++- tpl/collections/sort.go | 23 +++++++++++++-- tpl/collections/sort_test.go | 16 +++++++++++ tpl/collections/where.go | 22 ++++++++++---- tpl/collections/where_test.go | 49 +++++++++++++++++++++++++++++++- tpl/resources/resources.go | 6 ++-- tpl/tplimpl/template_ast_transformers.go | 7 ++--- 9 files changed, 132 insertions(+), 17 deletions(-) (limited to 'tpl') diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go index cfbcd312b..041a8e30c 100644 --- a/tpl/collections/collections_test.go +++ b/tpl/collections/collections_test.go @@ -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 } diff --git a/tpl/collections/index.go b/tpl/collections/index.go index d2989e22f..cd1d1577b 100644 --- a/tpl/collections/index.go +++ b/tpl/collections/index.go @@ -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 { diff --git a/tpl/collections/index_test.go b/tpl/collections/index_test.go index c4cded47c..0c380d8d5 100644 --- a/tpl/collections/index_test.go +++ b/tpl/collections/index_test.go @@ -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}, diff --git a/tpl/collections/sort.go b/tpl/collections/sort.go index 9639fe1d0..7ca764e9b 100644 --- a/tpl/collections/sort.go +++ b/tpl/collections/sort.go @@ -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())) } diff --git a/tpl/collections/sort_test.go b/tpl/collections/sort_test.go index 612a928cb..2bf6e85fe 100644 --- a/tpl/collections/sort_test.go +++ b/tpl/collections/sort_test.go @@ -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"}}, diff --git a/tpl/collections/where.go b/tpl/collections/where.go index 42f0d370f..cada675f3 100644 --- a/tpl/collections/where.go +++ b/tpl/collections/where.go @@ -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 { diff --git a/tpl/collections/where_test.go b/tpl/collections/where_test.go index cdef7aefb..d6a1dd141 100644 --- a/tpl/collections/where_test.go +++ b/tpl/collections/where_test.go @@ -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 diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go index e676a3412..20c4d1b3a 100644 --- a/tpl/resources/resources.go +++ b/tpl/resources/resources.go @@ -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") } diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go index d257d7a31..e25e70e35 100644 --- a/tpl/tplimpl/template_ast_transformers.go +++ b/tpl/tplimpl/template_ast_transformers.go @@ -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 -- cgit v1.2.3