aboutsummaryrefslogtreecommitdiffhomepage
path: root/common/maps
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2019-11-21 21:59:38 +0100
committerBjørn Erik Pedersen <[email protected]>2019-11-22 18:41:50 +0100
commita3fe5e5e35f311f22b6b4fc38abfcf64cd2c7d6f (patch)
tree06cf1f647ae026b4fb3053c85370c2b203c7a089 /common/maps
parentcd07e6d57b158a76f812e8c4c9567dbc84f57939 (diff)
downloadhugo-a3fe5e5e35f311f22b6b4fc38abfcf64cd2c7d6f.tar.gz
hugo-a3fe5e5e35f311f22b6b4fc38abfcf64cd2c7d6f.zip
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" }} ```
Diffstat (limited to 'common/maps')
-rw-r--r--common/maps/maps.go31
-rw-r--r--common/maps/maps_test.go24
-rw-r--r--common/maps/params.go109
-rw-r--r--common/maps/params_test.go2
4 files changed, 100 insertions, 66 deletions
diff --git a/common/maps/maps.go b/common/maps/maps.go
index e0d4f964d..8b42ca764 100644
--- a/common/maps/maps.go
+++ b/common/maps/maps.go
@@ -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
diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go
index 8b0aa5eb9..6e4947adb 100644
--- 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 @@ 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)
+ }
+ })
}
}
diff --git a/common/maps/params.go b/common/maps/params.go
index 2d62ad752..ecb63d7a5 100644
--- a/common/maps/params.go
+++ b/common/maps/params.go
@@ -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
}
diff --git a/common/maps/params_test.go b/common/maps/params_test.go
index 6477de6f4..8016a8bd6 100644
--- a/common/maps/params_test.go
+++ b/common/maps/params_test.go
@@ -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