Skip to content
Permalink
Browse files

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" }}
```
  • Loading branch information
bep committed Nov 21, 2019
1 parent cd07e6d commit a3fe5e5e35f311f22b6b4fc38abfcf64cd2c7d6f
@@ -30,12 +30,12 @@ import (

"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 convertJekyllPost(s *hugolib.Site, path, relPath, targetDir string, draft b
}

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 convertJekyllMetaData(m interface{}, postName string, postDate time.Time, d
}

func convertJekyllContent(m interface{}, content string) string {
metadata, _ := cast.ToStringMapE(m)
metadata, _ := maps.ToStringMapE(m)

lines := strings.Split(content, "\n")
var resultLines []string
@@ -25,26 +25,45 @@ import (
// 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 {
pattern glob.Glob
newKey string
@@ -14,14 +14,14 @@
package maps

import (
"fmt"
"reflect"
"testing"

qt "github.com/frankban/quicktest"
)

func TestToLower(t *testing.T) {

tests := []struct {
input map[string]interface{}
expected map[string]interface{}
@@ -30,7 +30,7 @@ func TestToLower(t *testing.T) {
map[string]interface{}{
"abC": 32,
},
map[string]interface{}{
Params{
"abc": 32,
},
},
@@ -48,28 +48,30 @@ func TestToLower(t *testing.T) {
"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,
},
},
},
}

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)
}
})
}
}

@@ -19,76 +19,89 @@ import (
"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
}
@@ -35,7 +35,7 @@ func TestGetNestedParam(t *testing.T) {

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
@@ -37,8 +37,8 @@ type Runner interface {

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 @@ func (w *Workers) Start(ctx context.Context) (Runner, context.Context) {
return &errGroupRunner{
Group: g,
ctx: ctx,
w: w,
w: w,
}, ctx
}
@@ -15,6 +15,7 @@ package para

import (
"context"
"runtime"
"sort"
"sync"
"sync/atomic"
@@ -25,6 +26,9 @@ import (
)

func TestPara(t *testing.T) {
if runtime.NumCPU() < 4 {
t.Skipf("skip para test, CPU count is %d", runtime.NumCPU())
}

c := qt.New(t)

0 comments on commit a3fe5e5

Please sign in to comment.
You can’t perform that action at this time.