maps.go (4643B)
1 // Copyright 2018 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package maps
15
16 import (
17 "fmt"
18 "strings"
19
20 "github.com/gohugoio/hugo/common/types"
21
22 "github.com/gobwas/glob"
23 "github.com/spf13/cast"
24 )
25
26 // ToStringMapE converts in to map[string]interface{}.
27 func ToStringMapE(in any) (map[string]any, error) {
28 switch vv := in.(type) {
29 case Params:
30 return vv, nil
31 case map[string]string:
32 var m = map[string]any{}
33 for k, v := range vv {
34 m[k] = v
35 }
36 return m, nil
37
38 default:
39 return cast.ToStringMapE(in)
40 }
41 }
42
43 // ToParamsAndPrepare converts in to Params and prepares it for use.
44 // If in is nil, an empty map is returned.
45 // See PrepareParams.
46 func ToParamsAndPrepare(in any) (Params, bool) {
47 if types.IsNil(in) {
48 return Params{}, true
49 }
50 m, err := ToStringMapE(in)
51 if err != nil {
52 return nil, false
53 }
54 PrepareParams(m)
55 return m, true
56 }
57
58 // MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
59 func MustToParamsAndPrepare(in any) Params {
60 if p, ok := ToParamsAndPrepare(in); ok {
61 return p
62 } else {
63 panic(fmt.Sprintf("cannot convert %T to maps.Params", in))
64 }
65 }
66
67 // ToStringMap converts in to map[string]interface{}.
68 func ToStringMap(in any) map[string]any {
69 m, _ := ToStringMapE(in)
70 return m
71 }
72
73 // ToStringMapStringE converts in to map[string]string.
74 func ToStringMapStringE(in any) (map[string]string, error) {
75 m, err := ToStringMapE(in)
76 if err != nil {
77 return nil, err
78 }
79 return cast.ToStringMapStringE(m)
80 }
81
82 // ToStringMapString converts in to map[string]string.
83 func ToStringMapString(in any) map[string]string {
84 m, _ := ToStringMapStringE(in)
85 return m
86 }
87
88 // ToStringMapBool converts in to bool.
89 func ToStringMapBool(in any) map[string]bool {
90 m, _ := ToStringMapE(in)
91 return cast.ToStringMapBool(m)
92 }
93
94 // ToSliceStringMap converts in to []map[string]interface{}.
95 func ToSliceStringMap(in any) ([]map[string]any, error) {
96 switch v := in.(type) {
97 case []map[string]any:
98 return v, nil
99 case []any:
100 var s []map[string]any
101 for _, entry := range v {
102 if vv, ok := entry.(map[string]any); ok {
103 s = append(s, vv)
104 }
105 }
106 return s, nil
107 default:
108 return nil, fmt.Errorf("unable to cast %#v of type %T to []map[string]interface{}", in, in)
109 }
110 }
111
112 // LookupEqualFold finds key in m with case insensitive equality checks.
113 func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) {
114 if v, found := m[key]; found {
115 return v, true
116 }
117 for k, v := range m {
118 if strings.EqualFold(k, key) {
119 return v, true
120 }
121 }
122 var s T
123 return s, false
124 }
125
126 type keyRename struct {
127 pattern glob.Glob
128 newKey string
129 }
130
131 // KeyRenamer supports renaming of keys in a map.
132 type KeyRenamer struct {
133 renames []keyRename
134 }
135
136 // NewKeyRenamer creates a new KeyRenamer given a list of pattern and new key
137 // value pairs.
138 func NewKeyRenamer(patternKeys ...string) (KeyRenamer, error) {
139 var renames []keyRename
140 for i := 0; i < len(patternKeys); i += 2 {
141 g, err := glob.Compile(strings.ToLower(patternKeys[i]), '/')
142 if err != nil {
143 return KeyRenamer{}, err
144 }
145 renames = append(renames, keyRename{pattern: g, newKey: patternKeys[i+1]})
146 }
147
148 return KeyRenamer{renames: renames}, nil
149 }
150
151 func (r KeyRenamer) getNewKey(keyPath string) string {
152 for _, matcher := range r.renames {
153 if matcher.pattern.Match(keyPath) {
154 return matcher.newKey
155 }
156 }
157
158 return ""
159 }
160
161 // Rename renames the keys in the given map according
162 // to the patterns in the current KeyRenamer.
163 func (r KeyRenamer) Rename(m map[string]any) {
164 r.renamePath("", m)
165 }
166
167 func (KeyRenamer) keyPath(k1, k2 string) string {
168 k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
169 if k1 == "" {
170 return k2
171 }
172 return k1 + "/" + k2
173 }
174
175 func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]any) {
176 for key, val := range m {
177 keyPath := r.keyPath(parentKeyPath, key)
178 switch val.(type) {
179 case map[any]any:
180 val = cast.ToStringMap(val)
181 r.renamePath(keyPath, val.(map[string]any))
182 case map[string]any:
183 r.renamePath(keyPath, val.(map[string]any))
184 }
185
186 newKey := r.getNewKey(keyPath)
187
188 if newKey != "" {
189 delete(m, key)
190 m[newKey] = val
191 }
192 }
193 }