compare.go (8807B)
1 // Copyright 2017 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 compare provides template functions for comparing values.
15 package compare
16
17 import (
18 "fmt"
19 "reflect"
20 "strconv"
21 "time"
22
23 "github.com/gohugoio/hugo/compare"
24 "github.com/gohugoio/hugo/langs"
25
26 "github.com/gohugoio/hugo/common/hreflect"
27 "github.com/gohugoio/hugo/common/htime"
28 "github.com/gohugoio/hugo/common/types"
29 )
30
31 // New returns a new instance of the compare-namespaced template functions.
32 func New(loc *time.Location, caseInsensitive bool) *Namespace {
33 return &Namespace{loc: loc, caseInsensitive: caseInsensitive}
34 }
35
36 // Namespace provides template functions for the "compare" namespace.
37 type Namespace struct {
38 loc *time.Location
39 // Enable to do case insensitive string compares.
40 caseInsensitive bool
41 }
42
43 // Default checks whether a given value is set and returns a default value if it
44 // is not. "Set" in this context means non-zero for numeric types and times;
45 // non-zero length for strings, arrays, slices, and maps;
46 // any boolean or struct value; or non-nil for any other types.
47 func (*Namespace) Default(dflt any, given ...any) (any, error) {
48 // given is variadic because the following construct will not pass a piped
49 // argument when the key is missing: {{ index . "key" | default "foo" }}
50 // The Go template will complain that we got 1 argument when we expected 2.
51
52 if len(given) == 0 {
53 return dflt, nil
54 }
55 if len(given) != 1 {
56 return nil, fmt.Errorf("wrong number of args for default: want 2 got %d", len(given)+1)
57 }
58
59 g := reflect.ValueOf(given[0])
60 if !g.IsValid() {
61 return dflt, nil
62 }
63
64 set := false
65
66 switch g.Kind() {
67 case reflect.Bool:
68 set = true
69 case reflect.String, reflect.Array, reflect.Slice, reflect.Map:
70 set = g.Len() != 0
71 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
72 set = g.Int() != 0
73 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
74 set = g.Uint() != 0
75 case reflect.Float32, reflect.Float64:
76 set = g.Float() != 0
77 case reflect.Complex64, reflect.Complex128:
78 set = g.Complex() != 0
79 case reflect.Struct:
80 switch actual := given[0].(type) {
81 case time.Time:
82 set = !actual.IsZero()
83 default:
84 set = true
85 }
86 default:
87 set = !g.IsNil()
88 }
89
90 if set {
91 return given[0], nil
92 }
93
94 return dflt, nil
95 }
96
97 // Eq returns the boolean truth of arg1 == arg2 || arg1 == arg3 || arg1 == arg4.
98 func (n *Namespace) Eq(first any, others ...any) bool {
99 if n.caseInsensitive {
100 panic("caseInsensitive not implemented for Eq")
101 }
102 n.checkComparisonArgCount(1, others...)
103 normalize := func(v any) any {
104 if types.IsNil(v) {
105 return nil
106 }
107
108 if at, ok := v.(htime.AsTimeProvider); ok {
109 return at.AsTime(n.loc)
110 }
111
112 vv := reflect.ValueOf(v)
113 switch vv.Kind() {
114 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
115 return vv.Int()
116 case reflect.Float32, reflect.Float64:
117 return vv.Float()
118 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
119 return vv.Uint()
120 case reflect.String:
121 return vv.String()
122 default:
123 return v
124 }
125 }
126
127 normFirst := normalize(first)
128 for _, other := range others {
129 if e, ok := first.(compare.Eqer); ok {
130 if e.Eq(other) {
131 return true
132 }
133 continue
134 }
135
136 if e, ok := other.(compare.Eqer); ok {
137 if e.Eq(first) {
138 return true
139 }
140 continue
141 }
142
143 other = normalize(other)
144 if reflect.DeepEqual(normFirst, other) {
145 return true
146 }
147 }
148
149 return false
150 }
151
152 // Ne returns the boolean truth of arg1 != arg2 && arg1 != arg3 && arg1 != arg4.
153 func (n *Namespace) Ne(first any, others ...any) bool {
154 n.checkComparisonArgCount(1, others...)
155 for _, other := range others {
156 if n.Eq(first, other) {
157 return false
158 }
159 }
160 return true
161 }
162
163 // Ge returns the boolean truth of arg1 >= arg2 && arg1 >= arg3 && arg1 >= arg4.
164 func (n *Namespace) Ge(first any, others ...any) bool {
165 n.checkComparisonArgCount(1, others...)
166 for _, other := range others {
167 left, right := n.compareGet(first, other)
168 if !(left >= right) {
169 return false
170 }
171 }
172 return true
173 }
174
175 // Gt returns the boolean truth of arg1 > arg2 && arg1 > arg3 && arg1 > arg4.
176 func (n *Namespace) Gt(first any, others ...any) bool {
177 n.checkComparisonArgCount(1, others...)
178 for _, other := range others {
179 left, right := n.compareGet(first, other)
180 if !(left > right) {
181 return false
182 }
183 }
184 return true
185 }
186
187 // Le returns the boolean truth of arg1 <= arg2 && arg1 <= arg3 && arg1 <= arg4.
188 func (n *Namespace) Le(first any, others ...any) bool {
189 n.checkComparisonArgCount(1, others...)
190 for _, other := range others {
191 left, right := n.compareGet(first, other)
192 if !(left <= right) {
193 return false
194 }
195 }
196 return true
197 }
198
199 // Lt returns the boolean truth of arg1 < arg2 && arg1 < arg3 && arg1 < arg4.
200 // The provided collator will be used for string comparisons.
201 // This is for internal use.
202 func (n *Namespace) LtCollate(collator *langs.Collator, first any, others ...any) bool {
203 n.checkComparisonArgCount(1, others...)
204 for _, other := range others {
205 left, right := n.compareGetWithCollator(collator, first, other)
206 if !(left < right) {
207 return false
208 }
209 }
210 return true
211 }
212
213 // Lt returns the boolean truth of arg1 < arg2 && arg1 < arg3 && arg1 < arg4.
214 func (n *Namespace) Lt(first any, others ...any) bool {
215 return n.LtCollate(nil, first, others...)
216 }
217
218 func (n *Namespace) checkComparisonArgCount(min int, others ...any) bool {
219 if len(others) < min {
220 panic("missing arguments for comparison")
221 }
222 return true
223 }
224
225 // Conditional can be used as a ternary operator.
226 // It returns a if condition, else b.
227 func (n *Namespace) Conditional(condition bool, a, b any) any {
228 if condition {
229 return a
230 }
231 return b
232 }
233
234 func (ns *Namespace) compareGet(a any, b any) (float64, float64) {
235 return ns.compareGetWithCollator(nil, a, b)
236 }
237
238 func (ns *Namespace) compareGetWithCollator(collator *langs.Collator, a any, b any) (float64, float64) {
239 if ac, ok := a.(compare.Comparer); ok {
240 c := ac.Compare(b)
241 if c < 0 {
242 return 1, 0
243 } else if c == 0 {
244 return 0, 0
245 } else {
246 return 0, 1
247 }
248 }
249
250 if bc, ok := b.(compare.Comparer); ok {
251 c := bc.Compare(a)
252 if c < 0 {
253 return 0, 1
254 } else if c == 0 {
255 return 0, 0
256 } else {
257 return 1, 0
258 }
259 }
260
261 var left, right float64
262 var leftStr, rightStr *string
263 av := reflect.ValueOf(a)
264
265 switch av.Kind() {
266 case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
267 left = float64(av.Len())
268 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
269 left = float64(av.Int())
270 case reflect.Float32, reflect.Float64:
271 left = av.Float()
272 case reflect.String:
273 var err error
274 left, err = strconv.ParseFloat(av.String(), 64)
275 if err != nil {
276 str := av.String()
277 leftStr = &str
278 }
279 case reflect.Struct:
280 if hreflect.IsTime(av.Type()) {
281 left = float64(ns.toTimeUnix(av))
282 }
283 case reflect.Bool:
284 left = 0
285 if av.Bool() {
286 left = 1
287 }
288 }
289
290 bv := reflect.ValueOf(b)
291
292 switch bv.Kind() {
293 case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
294 right = float64(bv.Len())
295 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
296 right = float64(bv.Int())
297 case reflect.Float32, reflect.Float64:
298 right = bv.Float()
299 case reflect.String:
300 var err error
301 right, err = strconv.ParseFloat(bv.String(), 64)
302 if err != nil {
303 str := bv.String()
304 rightStr = &str
305 }
306 case reflect.Struct:
307 if hreflect.IsTime(bv.Type()) {
308 right = float64(ns.toTimeUnix(bv))
309 }
310 case reflect.Bool:
311 right = 0
312 if bv.Bool() {
313 right = 1
314 }
315 }
316
317 if (ns.caseInsensitive || collator != nil) && leftStr != nil && rightStr != nil {
318 var c int
319 if collator != nil {
320 c = collator.CompareStrings(*leftStr, *rightStr)
321 } else {
322 c = compare.Strings(*leftStr, *rightStr)
323 }
324 if c < 0 {
325 return 0, 1
326 } else if c > 0 {
327 return 1, 0
328 } else {
329 return 0, 0
330 }
331 }
332
333 switch {
334 case leftStr == nil || rightStr == nil:
335 case *leftStr < *rightStr:
336 return 0, 1
337 case *leftStr > *rightStr:
338 return 1, 0
339 default:
340 return 0, 0
341 }
342
343 return left, right
344 }
345
346 func (ns *Namespace) toTimeUnix(v reflect.Value) int64 {
347 t, ok := hreflect.AsTime(v, ns.loc)
348 if !ok {
349 panic("coding error: argument must be time.Time type reflect Value")
350 }
351 return t.Unix()
352 }