hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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 }