Skip to content

Commit

Permalink
Fix Params case handling in the index, sort and where func
Browse files Browse the repository at this point in the history
This means that you can now do:

```
{{ range where .Site.Pages "Params.MYPARAM" "foo" }}
```
  • Loading branch information
bep committed Nov 22, 2019
1 parent cd07e6d commit a3fe5e5
Show file tree
Hide file tree
Showing 33 changed files with 313 additions and 151 deletions.
6 changes: 3 additions & 3 deletions commands/import_jekyll.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
31 changes: 25 additions & 6 deletions common/maps/maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 13 additions & 11 deletions common/maps/maps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand All @@ -30,7 +30,7 @@ func TestToLower(t *testing.T) {
map[string]interface{}{
"abC": 32,
},
map[string]interface{}{
Params{
"abc": 32,
},
},
Expand All @@ -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)
}
})
}
}

Expand Down
109 changes: 61 additions & 48 deletions common/maps/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion common/maps/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions common/para/para.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions common/para/para_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package para

import (
"context"
"runtime"
"sort"
"sync"
"sync/atomic"
Expand All @@ -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)

Expand Down
Loading

0 comments on commit a3fe5e5

Please sign in to comment.