ref: a3fe5e5e35f311f22b6b4fc38abfcf64cd2c7d6f
parent: cd07e6d57b158a76f812e8c4c9567dbc84f57939
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Thu Nov 21 16:59:38 EST 2019
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" }}
```
--- a/commands/import_jekyll.go
+++ b/commands/import_jekyll.go
@@ -30,12 +30,12 @@
"github.com/gohugoio/hugo/parser/metadecoders"
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/parser"
"github.com/spf13/afero"
- "github.com/spf13/cast"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
@@ -420,7 +420,7 @@
}
func convertJekyllMetaData(m interface{}, postName string, postDate time.Time, draft bool) (interface{}, error) {- metadata, err := cast.ToStringMapE(m)
+ metadata, err := maps.ToStringMapE(m)
if err != nil {return nil, err
}
@@ -472,7 +472,7 @@
}
func convertJekyllContent(m interface{}, content string) string {- metadata, _ := cast.ToStringMapE(m)
+ metadata, _ := maps.ToStringMapE(m)
lines := strings.Split(content, "\n")
var resultLines []string
--- a/common/maps/maps.go
+++ b/common/maps/maps.go
@@ -25,24 +25,43 @@
// recursively.
// Notes:
// * This will modify the map given.
-// * Any nested map[interface{}]interface{} will be converted to map[string]interface{}.-func ToLower(m map[string]interface{}) {+// * Any nested map[interface{}]interface{} will be converted to Params.+func ToLower(m Params) { for k, v := range m {+ var retyped bool
switch v.(type) { case map[interface{}]interface{}:- v = cast.ToStringMap(v)
- ToLower(v.(map[string]interface{}))+ var p Params = cast.ToStringMap(v)
+ v = p
+ ToLower(p)
+ retyped = true
case map[string]interface{}:- ToLower(v.(map[string]interface{}))+ var p Params = v.(map[string]interface{})+ v = p
+ ToLower(p)
+ retyped = true
}
lKey := strings.ToLower(k)
- if k != lKey {+ if retyped || k != lKey {delete(m, k)
m[lKey] = v
}
+ }
+}
+func ToStringMapE(in interface{}) (map[string]interface{}, error) {+ switch in.(type) {+ case Params:
+ return in.(Params), nil
+ default:
+ return cast.ToStringMapE(in)
}
+}
+
+func ToStringMap(in interface{}) map[string]interface{} {+ m, _ := ToStringMapE(in)
+ return m
}
type keyRename struct {--- a/common/maps/maps_test.go
+++ b/common/maps/maps_test.go
@@ -14,6 +14,7 @@
package maps
import (
+ "fmt"
"reflect"
"testing"
@@ -21,7 +22,6 @@
)
func TestToLower(t *testing.T) {-
tests := []struct { input map[string]interface{} expected map[string]interface{}@@ -30,7 +30,7 @@
map[string]interface{}{"abC": 32,
},
- map[string]interface{}{+ Params{"abc": 32,
},
},
@@ -48,16 +48,16 @@
"J": 25,
},
},
- map[string]interface{}{+ Params{"abc": 32,
- "def": map[string]interface{}{+ "def": Params{"23": "A value",
- "24": map[string]interface{}{+ "24": Params{"abcde": "A value",
"efghi": "Another value",
},
},
- "ghi": map[string]interface{}{+ "ghi": Params{"j": 25,
},
},
@@ -65,11 +65,13 @@
}
for i, test := range tests {- // ToLower modifies input.
- ToLower(test.input)
- if !reflect.DeepEqual(test.expected, test.input) {- t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)- }
+ t.Run(fmt.Sprint(i), func(t *testing.T) {+ // ToLower modifies input.
+ ToLower(test.input)
+ if !reflect.DeepEqual(test.expected, test.input) {+ t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)+ }
+ })
}
}
--- a/common/maps/params.go
+++ b/common/maps/params.go
@@ -19,76 +19,89 @@
"github.com/spf13/cast"
)
+// Params is a map where all keys are lower case.
+type Params map[string]interface{}+
+// Get does a lower case and nested search in this map.
+// It will return nil if none found.
+func (p Params) Get(indices ...string) interface{} {+ v, _, _ := getNested(p, indices)
+ return v
+}
+
+func getNested(m map[string]interface{}, indices []string) (interface{}, string, map[string]interface{}) {+ if len(indices) == 0 {+ return nil, "", nil
+ }
+
+ first := indices[0]
+ v, found := m[strings.ToLower(cast.ToString(first))]
+ if !found {+ return nil, "", nil
+ }
+
+ if len(indices) == 1 {+ return v, first, m
+ }
+
+ switch m2 := v.(type) {+ case Params:
+ return getNested(m2, indices[1:])
+ case map[string]interface{}:+ return getNested(m2, indices[1:])
+ default:
+ return nil, "", nil
+ }
+}
+
// GetNestedParam gets the first match of the keyStr in the candidates given.
// It will first try the exact match and then try to find it as a nested map value,
// using the given separator, e.g. "mymap.name".
// It assumes that all the maps given have lower cased keys.
-func GetNestedParam(keyStr, separator string, candidates ...map[string]interface{}) (interface{}, error) {+func GetNestedParam(keyStr, separator string, candidates ...Params) (interface{}, error) {keyStr = strings.ToLower(keyStr)
- lookupFn := func(key string) interface{} {- for _, m := range candidates {- if v, ok := m[key]; ok {- return v
- }
+ // Try exact match first
+ for _, m := range candidates {+ if v, ok := m[keyStr]; ok {+ return v, nil
}
-
- return nil
}
- v, _, _, err := GetNestedParamFn(keyStr, separator, lookupFn)
- return v, err
-}
-
-func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) {- result, _ := traverseDirectParams(keyStr, lookupFn)
- if result != nil {- return result, keyStr, nil, nil
- }
-
keySegments := strings.Split(keyStr, separator)
- if len(keySegments) == 1 {- return nil, keyStr, nil, nil
+ for _, m := range candidates {+ if v := m.Get(keySegments...); v != nil {+ return v, nil
+ }
}
- return traverseNestedParams(keySegments, lookupFn)
-}
+ return nil, nil
-func traverseDirectParams(keyStr string, lookupFn func(key string) interface{}) (interface{}, error) {- return lookupFn(keyStr), nil
}
-func traverseNestedParams(keySegments []string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) {- firstKey, rest := keySegments[0], keySegments[1:]
- result := lookupFn(firstKey)
- if result == nil || len(rest) == 0 {- return result, firstKey, nil, nil
+func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) {+ keySegments := strings.Split(strings.ToLower(keyStr), separator)
+ if len(keySegments) == 0 {+ return nil, "", nil, nil
}
- switch m := result.(type) {- case map[string]interface{}:- v, key, owner := traverseParams(rest, m)
- return v, key, owner, nil
- default:
+ first := lookupFn(keySegments[0])
+ if first == nil {return nil, "", nil, nil
}
-}
-func traverseParams(keys []string, m map[string]interface{}) (interface{}, string, map[string]interface{}) {- // Shift first element off.
- firstKey, rest := keys[0], keys[1:]
- result := m[firstKey]
-
- // No point in continuing here.
- if result == nil {- return result, "", nil
+ if len(keySegments) == 1 {+ return first, keySegments[0], nil, nil
}
- if len(rest) == 0 {- // That was the last key.
- return result, firstKey, m
+ switch m := first.(type) {+ case map[string]interface{}:+ v, key, owner := getNested(m, keySegments[1:])
+ return v, key, owner, nil
+ case Params:
+ v, key, owner := getNested(m, keySegments[1:])
+ return v, key, owner, nil
}
- // That was not the last key.
- return traverseParams(rest, cast.ToStringMap(result))
+ return nil, "", nil, nil
}
--- a/common/maps/params_test.go
+++ b/common/maps/params_test.go
@@ -35,7 +35,7 @@
c := qt.New(t)
- must := func(keyStr, separator string, candidates ...map[string]interface{}) interface{} {+ must := func(keyStr, separator string, candidates ...Params) interface{} {v, err := GetNestedParam(keyStr, separator, candidates...)
c.Assert(err, qt.IsNil)
return v
--- a/common/para/para.go
+++ b/common/para/para.go
@@ -37,8 +37,8 @@
type errGroupRunner struct {*errgroup.Group
- w *Workers
- ctx context.Context
+ w *Workers
+ ctx context.Context
}
func (g *errGroupRunner) Run(fn func() error) {@@ -68,6 +68,6 @@
return &errGroupRunner{Group: g,
ctx: ctx,
- w: w,
+ w: w,
}, ctx
}
--- a/common/para/para_test.go
+++ b/common/para/para_test.go
@@ -15,6 +15,7 @@
import (
"context"
+ "runtime"
"sort"
"sync"
"sync/atomic"
@@ -25,6 +26,9 @@
)
func TestPara(t *testing.T) {+ if runtime.NumCPU() < 4 {+ t.Skipf("skip para test, CPU count is %d", runtime.NumCPU())+ }
c := qt.New(t)
--- a/hugolib/case_insensitive_test.go
+++ b/hugolib/case_insensitive_test.go
@@ -61,7 +61,7 @@
hrefTargetBlank = false
[Languages.en.Colors]
BLUE = "blues"
-yellow = "golden"
+Yellow = "golden"
`
caseMixingPage1En = `
---
@@ -137,18 +137,6 @@
c := qt.New(t)
- // See issues 2615, 1129, 2590 and maybe some others
- // Also see 2598
- //
- // Viper is now, at least for the Hugo part, case insensitive
- // So we need tests for all of it, with needed adjustments on the Hugo side.
- // Not sure what that will be. Let us see.
-
- // So all the below with case variations:
- // config: regular fields, blackfriday config, param with nested map
- // language: new and overridden values, in regular fields and nested paramsmap
- // page frontmatter: regular fields, blackfriday config, param with nested map
-
mm := afero.NewMemMapFs()
caseMixingTestsWriteCommonSources(t, mm)
@@ -168,9 +156,19 @@
{{ define "main"}} Page Colors: {{ .Params.CoLOR }}|{{ .Params.Colors.Blue }} Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }}+{{ template "index-color" (dict "name" "Page" "params" .Params) }}+{{ template "index-color" (dict "name" "Site" "params" .Site.Params) }}+
{{ .Content }} {{ partial "partial.html" . }} {{ end }}+{{ define "index-color" }}+{{ $yellow := index .params "COLoRS" "yELLOW" }}+{{ $colors := index .params "COLoRS" }}+{{ $yellow2 := index $colors "yEllow" }}+index1|{{ .name }}: {{ $yellow }}|+index2|{{ .name }}: {{ $yellow2 }}|+{{ end }}`)
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `@@ -177,8 +175,8 @@
Page Title: {{ .Title }} Site Title: {{ .Site.Title }} Site Lang Mood: {{ .Site.Language.Params.MOoD }}-Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}-Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}+Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}|{{ index .Params "ColOR" }}+Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}|{{ index .Site.Params "ColOR" }} {{ $page2 := .Site.GetPage "/sect2/page2" }} {{ if $page2 }} Page2: {{ $page2.Params.ColoR }} @@ -200,8 +198,8 @@
}
th.assertFileContent(filepath.Join("public", "nn", "sect1", "page1", "index.html"),- "Page Colors: red|heavenly",
- "Site Colors: green|yellow",
+ "Page Colors: red|heavenly|red",
+ "Site Colors: green|yellow|green",
"Site Lang Mood: Happy",
"Shortcode Page: red|heavenly",
"Shortcode Site: green|yellow",
@@ -230,6 +228,10 @@
"Block Page Colors: black|sky",
"Partial Page: black|sky",
"Partial Site: green|yellow",
+ "index1|Page: flower|",
+ "index1|Site: yellow|",
+ "index2|Page: flower|",
+ "index2|Site: yellow|",
)
}
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -228,7 +228,7 @@
return resource.Param(p, p.s.Info.Params(), key)
}
-func (p *pageMeta) Params() map[string]interface{} {+func (p *pageMeta) Params() maps.Params {return p.params
}
@@ -312,7 +312,7 @@
return errors.New("missing frontmatter data")}
- pm.params = make(map[string]interface{})+ pm.params = make(maps.Params)
if frontmatter != nil {// Needed for case insensitive fetching of params values
@@ -320,7 +320,7 @@
if p.IsNode() {// Check for any cascade define on itself.
if cv, found := frontmatter["cascade"]; found {- cvm := cast.ToStringMap(cv)
+ cvm := maps.ToStringMap(cv)
if bucket.cascade == nil {bucket.cascade = cvm
} else {@@ -479,7 +479,7 @@
}
pm.params[loki] = pm.aliases
case "sitemap":
- p.m.sitemap = config.DecodeSitemap(p.s.siteCfg.sitemap, cast.ToStringMap(v))
+ p.m.sitemap = config.DecodeSitemap(p.s.siteCfg.sitemap, maps.ToStringMap(v))
pm.params[loki] = p.m.sitemap
sitemapSet = true
case "iscjklanguage":
@@ -495,7 +495,7 @@
switch vv := v.(type) { case []map[interface{}]interface{}: for _, vvv := range vv {- resources = append(resources, cast.ToStringMap(vvv))
+ resources = append(resources, maps.ToStringMap(vvv))
}
case []map[string]interface{}:resources = append(resources, vv...)
@@ -503,7 +503,7 @@
for _, vvv := range vv { switch vvvv := vvv.(type) { case map[interface{}]interface{}:- resources = append(resources, cast.ToStringMap(vvvv))
+ resources = append(resources, maps.ToStringMap(vvvv))
case map[string]interface{}:resources = append(resources, vvvv)
}
@@ -642,7 +642,7 @@
var renderingConfigOverrides map[string]interface{}bfParam := getParamToLower(p, "blackfriday")
if bfParam != nil {- renderingConfigOverrides = cast.ToStringMap(bfParam)
+ renderingConfigOverrides = maps.ToStringMap(bfParam)
}
cp := p.s.ContentSpec.Converters.Get(p.markup)
@@ -705,14 +705,9 @@
return helpers.SliceToLower(val)
}
return v
- case map[string]interface{}: // JSON and TOML+ default:
return v
- case map[interface{}]interface{}: // YAML- return v
}
-
- //p.s.Log.ERROR.Printf("GetParam(\"%s\"): Unknown type %s\n", key, reflect.TypeOf(v))- return nil
}
func getParamToLower(m resource.ResourceParamsProvider, key string) interface{} {--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -1573,7 +1573,8 @@
{{ $withStringParam := .Site.GetPage "withstringparam" }} Author page: {{ $withParam.Param "author.name" }}-Author page string: {{ $withStringParam.Param "author.name" }}|+Author name page string: {{ $withStringParam.Param "author.name" }}|+Author page string: {{ $withStringParam.Param "author" }}| Author site config: {{ $noParam.Param "author.name" }}`,
@@ -1603,8 +1604,10 @@
`)
b.Build(BuildCfg{})- b.AssertFileContent("public/index.html", "Author page: Ernest Miller Hemingway")- b.AssertFileContent("public/index.html", "Author page string: |")- b.AssertFileContent("public/index.html", "Author site config: Kurt Vonnegut")+ b.AssertFileContent("public/index.html",+ "Author page: Ernest Miller Hemingway",
+ "Author name page string: Kurt Vonnegut|",
+ "Author page string: Jo Nesbø|",
+ "Author site config: Kurt Vonnegut")
}
--- a/hugolib/pages_map.go
+++ b/hugolib/pages_map.go
@@ -20,6 +20,8 @@
"strings"
"sync"
+ "github.com/gohugoio/hugo/common/maps"
+
radix "github.com/armon/go-radix"
"github.com/spf13/cast"
@@ -359,7 +361,7 @@
func (m *pagesMap) mergeCascades(b1, b2 *pagesMapBucket) { if b1.cascade == nil {- b1.cascade = make(map[string]interface{})+ b1.cascade = make(maps.Params)
}
if b2 != nil && b2.cascade != nil { for k, v := range b2.cascade {--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -28,6 +28,8 @@
"strings"
"time"
+ "github.com/gohugoio/hugo/resources/resource"
+
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/hugofs/files"
@@ -581,7 +583,7 @@
return s.s.Taxonomies
}
-func (s *SiteInfo) Params() map[string]interface{} {+func (s *SiteInfo) Params() maps.Params {return s.s.Language().Params()
}
@@ -654,14 +656,9 @@
// Param is a convenience method to do lookups in SiteInfo's Params map.
//
-// This method is also implemented on Page and Node.
+// This method is also implemented on Page.
func (s *SiteInfo) Param(key interface{}) (interface{}, error) {- keyStr, err := cast.ToStringE(key)
- if err != nil {- return nil, err
- }
- keyStr = strings.ToLower(keyStr)
- return s.Params()[keyStr], nil
+ return resource.Param(s, nil, key)
}
func (s *SiteInfo) IsMultiLingual() bool {@@ -1272,7 +1269,7 @@
s.Log.DEBUG.Printf("found menu: %q, in site config\n", name) menuEntry := navigation.MenuEntry{Menu: name}- ime, err := cast.ToStringMapE(entry)
+ ime, err := maps.ToStringMapE(entry)
if err != nil { s.Log.ERROR.Printf("unable to process menus in site config\n")s.Log.ERROR.Println(err)
--- a/langs/config.go
+++ b/langs/config.go
@@ -171,7 +171,7 @@
i := 0
for lang, langConf := range l {- langsMap, err := cast.ToStringMapE(langConf)
+ langsMap, err := maps.ToStringMapE(langConf)
if err != nil { return nil, fmt.Errorf("Language config is not a map: %T", langConf)@@ -192,7 +192,7 @@
case "disabled":
language.Disabled = cast.ToBool(v)
case "params":
- m := cast.ToStringMap(v)
+ m := maps.ToStringMap(v)
// Needed for case insensitive fetching of params values
maps.ToLower(m)
for k, vv := range m {--- a/langs/language.go
+++ b/langs/language.go
@@ -177,7 +177,7 @@
// GetStringMap returns the value associated with the key as a map of interfaces.
func (l *Language) GetStringMap(key string) map[string]interface{} {- return cast.ToStringMap(l.Get(key))
+ return maps.ToStringMap(l.Get(key))
}
// GetStringMapString returns the value associated with the key as a map of strings.
--- a/navigation/menu.go
+++ b/navigation/menu.go
@@ -14,6 +14,7 @@
package navigation
import (
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/compare"
@@ -59,7 +60,7 @@
Section() string
Weight() int
IsPage() bool
- Params() map[string]interface{}+ Params() maps.Params
}
// Menu is a collection of menu entries.
--- a/navigation/pagemenus.go
+++ b/navigation/pagemenus.go
@@ -14,6 +14,8 @@
package navigation
import (
+ "github.com/gohugoio/hugo/common/maps"
+
"github.com/pkg/errors"
"github.com/spf13/cast"
)
@@ -73,7 +75,7 @@
}
// Could be a structured menu entry
- menus, err := cast.ToStringMapE(ms)
+ menus, err := maps.ToStringMapE(ms)
if err != nil {return pm, errors.Wrapf(err, "unable to process menus for %q", p.LinkTitle())
}
@@ -81,7 +83,7 @@
for name, menu := range menus { menuEntry := MenuEntry{Page: p, Name: p.LinkTitle(), Weight: p.Weight(), Menu: name} if menu != nil {- ime, err := cast.ToStringMapE(menu)
+ ime, err := maps.ToStringMapE(menu)
if err != nil {return pm, errors.Wrapf(err, "unable to process menus for %q", p.LinkTitle())
}
--- a/resources/page/page_nop.go
+++ b/resources/page/page_nop.go
@@ -300,7 +300,7 @@
return nil, nil
}
-func (p *nopPage) Params() map[string]interface{} {+func (p *nopPage) Params() maps.Params {return nil
}
--- a/resources/page/site.go
+++ b/resources/page/site.go
@@ -17,6 +17,8 @@
"html/template"
"time"
+ "github.com/gohugoio/hugo/common/maps"
+
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/common/hugo"
@@ -39,7 +41,7 @@
Taxonomies() interface{}LastChange() time.Time
Menus() navigation.Menus
- Params() map[string]interface{}+ Params() maps.Params
Data() map[string]interface{}}
@@ -107,7 +109,7 @@
return ""
}
-func (t testSite) Params() map[string]interface{} {+func (t testSite) Params() maps.Params {return nil
}
--- a/resources/page/testhelpers_test.go
+++ b/resources/page/testhelpers_test.go
@@ -370,7 +370,7 @@
return resource.Param(p, nil, key)
}
-func (p *testPage) Params() map[string]interface{} {+func (p *testPage) Params() maps.Params {return p.params
}
--- a/resources/resource.go
+++ b/resources/resource.go
@@ -30,9 +30,9 @@
"github.com/pkg/errors"
"github.com/gohugoio/hugo/common/hugio"
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
-
"github.com/spf13/afero"
"github.com/gohugoio/hugo/helpers"
@@ -228,7 +228,7 @@
return l.name
}
-func (l *genericResource) Params() map[string]interface{} {+func (l *genericResource) Params() maps.Params {return l.params
}
--- a/resources/resource/params.go
+++ b/resources/resource/params.go
@@ -19,10 +19,14 @@
"github.com/spf13/cast"
)
-func Param(r ResourceParamsProvider, fallback map[string]interface{}, key interface{}) (interface{}, error) {+func Param(r ResourceParamsProvider, fallback maps.Params, key interface{}) (interface{}, error) {keyStr, err := cast.ToStringE(key)
if err != nil {return nil, err
+ }
+
+ if fallback == nil {+ return maps.GetNestedParam(keyStr, ".", r.Params())
}
return maps.GetNestedParam(keyStr, ".", r.Params(), fallback)
--- a/resources/resource/resourcetypes.go
+++ b/resources/resource/resourcetypes.go
@@ -14,6 +14,7 @@
package resource
import (
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/images/exif"
@@ -85,7 +86,7 @@
type ResourceParamsProvider interface {// Params set in front matter for this resource.
- Params() map[string]interface{}+ Params() maps.Params
}
type ResourceDataProvider interface {--- a/resources/resource_metadata.go
+++ b/resources/resource_metadata.go
@@ -129,7 +129,7 @@
params, found := meta["params"]
if found {- m := cast.ToStringMap(params)
+ m := maps.ToStringMap(params)
// Needed for case insensitive fetching of params values
maps.ToLower(m)
ma.updateParams(m)
--- a/resources/transform.go
+++ b/resources/transform.go
@@ -26,11 +26,11 @@
bp "github.com/gohugoio/hugo/bufferpool"
- "github.com/gohugoio/hugo/resources/internal"
-
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hugio"
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/resources/internal"
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/media"
@@ -200,7 +200,7 @@
return r.target.Name()
}
-func (r *resourceAdapter) Params() map[string]interface{} {+func (r *resourceAdapter) Params() maps.Params {r.init(false, false)
return r.target.Params()
}
--- a/tpl/collections/collections_test.go
+++ b/tpl/collections/collections_test.go
@@ -22,6 +22,8 @@
"testing"
"time"
+ "github.com/gohugoio/hugo/common/maps"
+
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
@@ -889,6 +891,15 @@
type TstX struct {A, B string
unexported string
+}
+
+type TstParams struct {+ params maps.Params
+}
+
+func (x TstParams) Params() maps.Params {+ return x.params
+
}
type TstXIHolder struct {--- a/tpl/collections/index.go
+++ b/tpl/collections/index.go
@@ -17,6 +17,10 @@
"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 @@
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 @@
if err != nil {return nil, err
}
+
if x := v.MapIndex(index); x.IsValid() {v = x
} else {--- a/tpl/collections/index_test.go
+++ b/tpl/collections/index_test.go
@@ -17,6 +17,8 @@
"fmt"
"testing"
+ "github.com/gohugoio/hugo/common/maps"
+
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
)
@@ -42,7 +44,8 @@
{[]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},--- a/tpl/collections/sort.go
+++ b/tpl/collections/sort.go
@@ -19,6 +19,7 @@
"sort"
"strings"
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/tpl/compare"
"github.com/spf13/cast"
)
@@ -75,11 +76,19 @@
} 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 @@
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 @@
} 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 @@
// 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()))
}
--- a/tpl/collections/sort_test.go
+++ b/tpl/collections/sort_test.go
@@ -18,6 +18,8 @@
"reflect"
"testing"
+ "github.com/gohugoio/hugo/common/maps"
+
"github.com/gohugoio/hugo/deps"
)
@@ -99,6 +101,20 @@
"TstRp",
"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
{--- a/tpl/collections/where.go
+++ b/tpl/collections/where.go
@@ -18,6 +18,8 @@
"fmt"
"reflect"
"strings"
+
+ "github.com/gohugoio/hugo/common/maps"
)
// Where returns a filtered subset of a given data type.
@@ -277,6 +279,7 @@
if !obj.IsValid() { return zero, errors.New("can't evaluate an invalid value")}
+
typ := obj.Type()
obj, isNil := indirect(obj)
@@ -295,6 +298,7 @@
if objPtr.Kind() != reflect.Interface && objPtr.CanAddr() {objPtr = objPtr.Addr()
}
+
mt, ok := objPtr.Type().MethodByName(elemName)
if ok { switch {@@ -368,16 +372,22 @@
// 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 {--- a/tpl/collections/where_test.go
+++ b/tpl/collections/where_test.go
@@ -16,9 +16,12 @@
import (
"fmt"
"reflect"
+ "strings"
"testing"
"time"
+ "github.com/gohugoio/hugo/common/maps"
+
"github.com/gohugoio/hugo/deps"
)
@@ -163,6 +166,37 @@
},
},
{+ 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 @@
"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
--- a/tpl/resources/resources.go
+++ b/tpl/resources/resources.go
@@ -19,11 +19,11 @@
"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 @@
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")
}
--- a/tpl/tplimpl/template_ast_transformers.go
+++ b/tpl/tplimpl/template_ast_transformers.go
@@ -19,11 +19,10 @@
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 @@
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
--
⑨