summaryrefslogtreecommitdiffstats
path: root/common/maps/params.go
diff options
context:
space:
mode:
Diffstat (limited to 'common/maps/params.go')
-rw-r--r--common/maps/params.go347
1 files changed, 347 insertions, 0 deletions
diff --git a/common/maps/params.go b/common/maps/params.go
new file mode 100644
index 000000000..d94d16f9d
--- /dev/null
+++ b/common/maps/params.go
@@ -0,0 +1,347 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maps
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/spf13/cast"
+)
+
+// Params is a map where all keys are lower case.
+type Params map[string]any
+
+// KeyParams is an utility struct for the WalkParams method.
+type KeyParams struct {
+ Key string
+ Params Params
+}
+
+// GetNested does a lower case and nested search in this map.
+// It will return nil if none found.
+// Make all of these methods internal somehow.
+func (p Params) GetNested(indices ...string) any {
+ v, _, _ := getNested(p, indices)
+ return v
+}
+
+// SetParams overwrites values in dst with values in src for common or new keys.
+// This is done recursively.
+func SetParams(dst, src Params) {
+ for k, v := range src {
+ vv, found := dst[k]
+ if !found {
+ dst[k] = v
+ } else {
+ switch vvv := vv.(type) {
+ case Params:
+ if pv, ok := v.(Params); ok {
+ SetParams(vvv, pv)
+ } else {
+ dst[k] = v
+ }
+ default:
+ dst[k] = v
+ }
+ }
+ }
+}
+
+// IsZero returns true if p is considered empty.
+func (p Params) IsZero() bool {
+ if p == nil || len(p) == 0 {
+ return true
+ }
+
+ if len(p) > 1 {
+ return false
+ }
+
+ for k := range p {
+ return k == MergeStrategyKey
+ }
+
+ return false
+
+}
+
+// MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given.
+// This is done recursively.
+func MergeParamsWithStrategy(strategy string, dst, src Params) {
+ dst.merge(ParamsMergeStrategy(strategy), src)
+}
+
+// MergeParams transfers values from src to dst for new keys using the merge encoded in dst.
+// This is done recursively.
+func MergeParams(dst, src Params) {
+ ms, _ := dst.GetMergeStrategy()
+ dst.merge(ms, src)
+}
+
+func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
+ ns, found := p.GetMergeStrategy()
+
+ var ms = ns
+ if !found && ps != "" {
+ ms = ps
+ }
+
+ noUpdate := ms == ParamsMergeStrategyNone
+ noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
+
+ for k, v := range pp {
+
+ if k == MergeStrategyKey {
+ continue
+ }
+ vv, found := p[k]
+
+ if found {
+ // Key matches, if both sides are Params, we try to merge.
+ if vvv, ok := vv.(Params); ok {
+ if pv, ok := v.(Params); ok {
+ vvv.merge(ms, pv)
+ }
+ }
+ } else if !noUpdate {
+ p[k] = v
+ }
+
+ }
+}
+
+// For internal use.
+func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
+ if v, found := p[MergeStrategyKey]; found {
+ if s, ok := v.(ParamsMergeStrategy); ok {
+ return s, true
+ }
+ }
+ return ParamsMergeStrategyShallow, false
+}
+
+// For internal use.
+func (p Params) DeleteMergeStrategy() bool {
+ if _, found := p[MergeStrategyKey]; found {
+ delete(p, MergeStrategyKey)
+ return true
+ }
+ return false
+}
+
+// For internal use.
+func (p Params) SetMergeStrategy(s ParamsMergeStrategy) {
+ switch s {
+ case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
+ default:
+ panic(fmt.Sprintf("invalid merge strategy %q", s))
+ }
+ p[MergeStrategyKey] = s
+}
+
+func getNested(m map[string]any, indices []string) (any, string, map[string]any) {
+ if len(indices) == 0 {
+ return nil, "", nil
+ }
+
+ first := indices[0]
+ v, found := m[strings.ToLower(cast.ToString(first))]
+ if !found {
+ if len(indices) == 1 {
+ return nil, first, m
+ }
+ 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]any:
+ 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 ...Params) (any, error) {
+ keyStr = strings.ToLower(keyStr)
+
+ // Try exact match first
+ for _, m := range candidates {
+ if v, ok := m[keyStr]; ok {
+ return v, nil
+ }
+ }
+
+ keySegments := strings.Split(keyStr, separator)
+ for _, m := range candidates {
+ if v := m.GetNested(keySegments...); v != nil {
+ return v, nil
+ }
+ }
+
+ return nil, nil
+}
+
+func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) any) (any, string, map[string]any, error) {
+ keySegments := strings.Split(keyStr, separator)
+ if len(keySegments) == 0 {
+ return nil, "", nil, nil
+ }
+
+ first := lookupFn(keySegments[0])
+ if first == nil {
+ return nil, "", nil, nil
+ }
+
+ if len(keySegments) == 1 {
+ return first, keySegments[0], nil, nil
+ }
+
+ switch m := first.(type) {
+ case map[string]any:
+ 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
+}
+
+// ParamsMergeStrategy tells what strategy to use in Params.Merge.
+type ParamsMergeStrategy string
+
+const (
+ // Do not merge.
+ ParamsMergeStrategyNone ParamsMergeStrategy = "none"
+ // Only add new keys.
+ ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow"
+ // Add new keys, merge existing.
+ ParamsMergeStrategyDeep ParamsMergeStrategy = "deep"
+
+ MergeStrategyKey = "_merge"
+)
+
+// CleanConfigStringMapString removes any processing instructions from m,
+// m will never be modified.
+func CleanConfigStringMapString(m map[string]string) map[string]string {
+ if m == nil || len(m) == 0 {
+ return m
+ }
+ if _, found := m[MergeStrategyKey]; !found {
+ return m
+ }
+ // Create a new map and copy all the keys except the merge strategy key.
+ m2 := make(map[string]string, len(m)-1)
+ for k, v := range m {
+ if k != MergeStrategyKey {
+ m2[k] = v
+ }
+ }
+ return m2
+}
+
+// CleanConfigStringMap is the same as CleanConfigStringMapString but for
+// map[string]any.
+func CleanConfigStringMap(m map[string]any) map[string]any {
+ if m == nil || len(m) == 0 {
+ return m
+ }
+ if _, found := m[MergeStrategyKey]; !found {
+ return m
+ }
+ // Create a new map and copy all the keys except the merge strategy key.
+ m2 := make(map[string]any, len(m)-1)
+ for k, v := range m {
+ if k != MergeStrategyKey {
+ m2[k] = v
+ }
+ switch v2 := v.(type) {
+ case map[string]any:
+ m2[k] = CleanConfigStringMap(v2)
+ case Params:
+ var p Params = CleanConfigStringMap(v2)
+ m2[k] = p
+ case map[string]string:
+ m2[k] = CleanConfigStringMapString(v2)
+ }
+
+ }
+ return m2
+
+}
+
+func toMergeStrategy(v any) ParamsMergeStrategy {
+ s := ParamsMergeStrategy(cast.ToString(v))
+ switch s {
+ case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
+ return s
+ default:
+ return ParamsMergeStrategyDeep
+ }
+}
+
+// PrepareParams
+// * makes all the keys in the given map lower cased and will do so
+// * This will modify the map given.
+// * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params.
+// * Any _merge value will be converted to proper type and value.
+func PrepareParams(m Params) {
+ for k, v := range m {
+ var retyped bool
+ lKey := strings.ToLower(k)
+ if lKey == MergeStrategyKey {
+ v = toMergeStrategy(v)
+ retyped = true
+ } else {
+ switch vv := v.(type) {
+ case map[any]any:
+ var p Params = cast.ToStringMap(v)
+ v = p
+ PrepareParams(p)
+ retyped = true
+ case map[string]any:
+ var p Params = v.(map[string]any)
+ v = p
+ PrepareParams(p)
+ retyped = true
+ case map[string]string:
+ p := make(Params)
+ for k, v := range vv {
+ p[k] = v
+ }
+ v = p
+ PrepareParams(p)
+ retyped = true
+ }
+ }
+
+ if retyped || k != lKey {
+ delete(m, k)
+ m[lKey] = v
+ }
+ }
+}