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