mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
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:
@@ -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,6 +14,7 @@
|
||||
package maps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -21,7 +22,6 @@ import (
|
||||
)
|
||||
|
||||
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,16 +48,16 @@ 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,
|
||||
},
|
||||
},
|
||||
@@ -65,11 +65,13 @@ func TestToLower(t *testing.T) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
switch m := result.(type) {
|
||||
case map[string]interface{}:
|
||||
v, key, owner := traverseParams(rest, m)
|
||||
return v, key, owner, nil
|
||||
default:
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
first := lookupFn(keySegments[0])
|
||||
if first == nil {
|
||||
return nil, "", nil, nil
|
||||
}
|
||||
|
||||
if len(rest) == 0 {
|
||||
// That was the last key.
|
||||
return result, firstKey, m
|
||||
if len(keySegments) == 1 {
|
||||
return first, keySegments[0], nil, nil
|
||||
}
|
||||
|
||||
// That was not the last key.
|
||||
return traverseParams(rest, cast.ToStringMap(result))
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
Reference in New Issue
Block a user