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 }