github.com/whatlly/hugo@v0.47.1/tpl/compare/compare.go (about)

     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
    15  
    16  import (
    17  	"fmt"
    18  	"reflect"
    19  	"strconv"
    20  	"time"
    21  
    22  	"github.com/gohugoio/hugo/compare"
    23  )
    24  
    25  // New returns a new instance of the compare-namespaced template functions.
    26  func New() *Namespace {
    27  	return &Namespace{}
    28  }
    29  
    30  // Namespace provides template functions for the "compare" namespace.
    31  type Namespace struct {
    32  }
    33  
    34  // Default checks whether a given value is set and returns a default value if it
    35  // is not.  "Set" in this context means non-zero for numeric types and times;
    36  // non-zero length for strings, arrays, slices, and maps;
    37  // any boolean or struct value; or non-nil for any other types.
    38  func (*Namespace) Default(dflt interface{}, given ...interface{}) (interface{}, error) {
    39  	// given is variadic because the following construct will not pass a piped
    40  	// argument when the key is missing:  {{ index . "key" | default "foo" }}
    41  	// The Go template will complain that we got 1 argument when we expectd 2.
    42  
    43  	if len(given) == 0 {
    44  		return dflt, nil
    45  	}
    46  	if len(given) != 1 {
    47  		return nil, fmt.Errorf("wrong number of args for default: want 2 got %d", len(given)+1)
    48  	}
    49  
    50  	g := reflect.ValueOf(given[0])
    51  	if !g.IsValid() {
    52  		return dflt, nil
    53  	}
    54  
    55  	set := false
    56  
    57  	switch g.Kind() {
    58  	case reflect.Bool:
    59  		set = true
    60  	case reflect.String, reflect.Array, reflect.Slice, reflect.Map:
    61  		set = g.Len() != 0
    62  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    63  		set = g.Int() != 0
    64  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
    65  		set = g.Uint() != 0
    66  	case reflect.Float32, reflect.Float64:
    67  		set = g.Float() != 0
    68  	case reflect.Complex64, reflect.Complex128:
    69  		set = g.Complex() != 0
    70  	case reflect.Struct:
    71  		switch actual := given[0].(type) {
    72  		case time.Time:
    73  			set = !actual.IsZero()
    74  		default:
    75  			set = true
    76  		}
    77  	default:
    78  		set = !g.IsNil()
    79  	}
    80  
    81  	if set {
    82  		return given[0], nil
    83  	}
    84  
    85  	return dflt, nil
    86  }
    87  
    88  // Eq returns the boolean truth of arg1 == arg2.
    89  func (*Namespace) Eq(x, y interface{}) bool {
    90  
    91  	if e, ok := x.(compare.Eqer); ok {
    92  		return e.Eq(y)
    93  	}
    94  
    95  	if e, ok := y.(compare.Eqer); ok {
    96  		return e.Eq(x)
    97  	}
    98  
    99  	normalize := func(v interface{}) interface{} {
   100  		vv := reflect.ValueOf(v)
   101  		switch vv.Kind() {
   102  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   103  			return vv.Int()
   104  		case reflect.Float32, reflect.Float64:
   105  			return vv.Float()
   106  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   107  			return vv.Uint()
   108  		default:
   109  			return v
   110  		}
   111  	}
   112  	x = normalize(x)
   113  	y = normalize(y)
   114  	return reflect.DeepEqual(x, y)
   115  }
   116  
   117  // Ne returns the boolean truth of arg1 != arg2.
   118  func (n *Namespace) Ne(x, y interface{}) bool {
   119  	return !n.Eq(x, y)
   120  }
   121  
   122  // Ge returns the boolean truth of arg1 >= arg2.
   123  func (n *Namespace) Ge(a, b interface{}) bool {
   124  	left, right := n.compareGet(a, b)
   125  	return left >= right
   126  }
   127  
   128  // Gt returns the boolean truth of arg1 > arg2.
   129  func (n *Namespace) Gt(a, b interface{}) bool {
   130  	left, right := n.compareGet(a, b)
   131  	return left > right
   132  }
   133  
   134  // Le returns the boolean truth of arg1 <= arg2.
   135  func (n *Namespace) Le(a, b interface{}) bool {
   136  	left, right := n.compareGet(a, b)
   137  	return left <= right
   138  }
   139  
   140  // Lt returns the boolean truth of arg1 < arg2.
   141  func (n *Namespace) Lt(a, b interface{}) bool {
   142  	left, right := n.compareGet(a, b)
   143  	return left < right
   144  }
   145  
   146  // Conditional can be used as a ternary operator.
   147  // It returns a if condition, else b.
   148  func (n *Namespace) Conditional(condition bool, a, b interface{}) interface{} {
   149  	if condition {
   150  		return a
   151  	}
   152  	return b
   153  }
   154  
   155  func (*Namespace) compareGet(a interface{}, b interface{}) (float64, float64) {
   156  	if ac, ok := a.(compare.Comparer); ok {
   157  		c := ac.Compare(b)
   158  		if c < 0 {
   159  			return 1, 0
   160  		} else if c == 0 {
   161  			return 0, 0
   162  		} else {
   163  			return 0, 1
   164  		}
   165  	}
   166  
   167  	if bc, ok := b.(compare.Comparer); ok {
   168  		c := bc.Compare(a)
   169  		if c < 0 {
   170  			return 0, 1
   171  		} else if c == 0 {
   172  			return 0, 0
   173  		} else {
   174  			return 1, 0
   175  		}
   176  	}
   177  
   178  	var left, right float64
   179  	var leftStr, rightStr *string
   180  	av := reflect.ValueOf(a)
   181  
   182  	switch av.Kind() {
   183  	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
   184  		left = float64(av.Len())
   185  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   186  		left = float64(av.Int())
   187  	case reflect.Float32, reflect.Float64:
   188  		left = av.Float()
   189  	case reflect.String:
   190  		var err error
   191  		left, err = strconv.ParseFloat(av.String(), 64)
   192  		if err != nil {
   193  			str := av.String()
   194  			leftStr = &str
   195  		}
   196  	case reflect.Struct:
   197  		switch av.Type() {
   198  		case timeType:
   199  			left = float64(toTimeUnix(av))
   200  		}
   201  	}
   202  
   203  	bv := reflect.ValueOf(b)
   204  
   205  	switch bv.Kind() {
   206  	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
   207  		right = float64(bv.Len())
   208  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   209  		right = float64(bv.Int())
   210  	case reflect.Float32, reflect.Float64:
   211  		right = bv.Float()
   212  	case reflect.String:
   213  		var err error
   214  		right, err = strconv.ParseFloat(bv.String(), 64)
   215  		if err != nil {
   216  			str := bv.String()
   217  			rightStr = &str
   218  		}
   219  	case reflect.Struct:
   220  		switch bv.Type() {
   221  		case timeType:
   222  			right = float64(toTimeUnix(bv))
   223  		}
   224  	}
   225  
   226  	switch {
   227  	case leftStr == nil || rightStr == nil:
   228  	case *leftStr < *rightStr:
   229  		return 0, 1
   230  	case *leftStr > *rightStr:
   231  		return 1, 0
   232  	default:
   233  		return 0, 0
   234  	}
   235  
   236  	return left, right
   237  }
   238  
   239  var timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
   240  
   241  func toTimeUnix(v reflect.Value) int64 {
   242  	if v.Kind() == reflect.Interface {
   243  		return toTimeUnix(v.Elem())
   244  	}
   245  	if v.Type() != timeType {
   246  		panic("coding error: argument must be time.Time type reflect Value")
   247  	}
   248  	return v.MethodByName("Unix").Call([]reflect.Value{})[0].Int()
   249  }