Skip to content

Commit

Permalink
tpl/tplimpl: Fix template truth logic
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Mar 6, 2019
1 parent bdf47e8 commit 5cbf912
Show file tree
Hide file tree
Showing 16 changed files with 435 additions and 51 deletions.
110 changes: 110 additions & 0 deletions common/hreflect/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package hreflect contains reflect helpers.
package hreflect

import "reflect"

// Indirect returns the item at the end of indirection, and a bool to indicate
// if it's nil. If the returned bool is true, the returned value's kind will be
// either a pointer or interface.
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L918
func Indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}

// indirectInterface returns the concrete value in an interface value,
// or else the zero reflect.Value, and a boolean indicating if it is nil.
// That is, if v represents the interface value x, the result is the same as reflect.ValueOf(x):
// the fact that x was an interface value is forgotten.
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func IndirectInterface(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}

func IsTruthful(in interface{}) bool {
switch v := in.(type) {
case reflect.Value:
return IsTruthfulValue(v)
default:
return IsTruthfulValue(reflect.ValueOf(in))
}

}

type zeroer interface {
IsZero() bool
}

var zeroType = reflect.TypeOf((*zeroer)(nil)).Elem()

// IsTruthful returns whether the given value has a meaningful truth value.
// This is based on template.IsTrue in Go's stdlib, but also considers
// IsZero and any interface value will be unwrapped before it's considered
// for truthfulness.
//
// Based on:
// https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L306
func IsTruthfulValue(val reflect.Value) (truth bool) {
if !val.IsValid() {
// Something like var x interface{}, never set. It's a form of nil.
return false
}

val, _ = IndirectInterface(val)

if val.Type().Implements(zeroType) {
return !val.Interface().(zeroer).IsZero()
}

switch val.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
truth = val.Len() > 0
case reflect.Bool:
truth = val.Bool()
case reflect.Complex64, reflect.Complex128:
truth = val.Complex() != 0
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
truth = !val.IsNil()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
truth = val.Int() != 0
case reflect.Float32, reflect.Float64:
truth = val.Float() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
truth = val.Uint() != 0
case reflect.Struct:
truth = true // Struct values are always true.
default:
return
}
return truth
}
30 changes: 30 additions & 0 deletions common/hreflect/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hreflect

import (
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestIsTruthFul(t *testing.T) {
assert := require.New(t)

assert.True(IsTruthful(true))
assert.False(IsTruthful(false))
assert.True(IsTruthful(time.Now()))
assert.False(IsTruthful(time.Time{}))
}
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ github.com/magefile/mage v1.4.0 h1:RI7B1CgnPAuu2O9lWszwya61RLmfL0KCdo+QyyI/Bhk=
github.com/magefile/mage v1.4.0/go.mod h1:IUDi13rsHje59lecXokTfGX0QIzO45uVPlXnJYsXepA=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/markbates/inflect v0.0.0-20171215194931-a12c3aec81a6 h1:LZhVjIISSbj8qLf2qDPP0D8z0uvOWAW5C85ly5mJW6c=
github.com/markbates/inflect v0.0.0-20171215194931-a12c3aec81a6/go.mod h1:oTeZL2KHA7CUX6X+fovmK9OvIOFuqu0TwdQrZjLTh88=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
Expand Down
31 changes: 4 additions & 27 deletions tpl/collections/apply.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -19,6 +19,8 @@ import (
"reflect"
"strings"

"github.com/gohugoio/hugo/common/hreflect"

"github.com/gohugoio/hugo/tpl"
)

Expand All @@ -33,7 +35,7 @@ func (ns *Namespace) Apply(seq interface{}, fname string, args ...interface{}) (
}

seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
seqv, isNil := hreflect.Indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
Expand Down Expand Up @@ -135,28 +137,3 @@ func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
}
return m, true
}

// indirect is borrowed from the Go stdlib: 'text/template/exec.go'
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}

func indirectInterface(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}
25 changes: 13 additions & 12 deletions tpl/collections/collections.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -26,6 +26,7 @@ import (
"time"

"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/deps"
Expand Down Expand Up @@ -65,7 +66,7 @@ func (ns *Namespace) After(index interface{}, seq interface{}) (interface{}, err
}

seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
seqv, isNil := hreflect.Indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
Expand Down Expand Up @@ -103,7 +104,7 @@ func (ns *Namespace) Delimit(seq, delimiter interface{}, last ...interface{}) (t
}

seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
seqv, isNil := hreflect.Indirect(seqv)
if isNil {
return "", errors.New("can't iterate over a nil value")
}
Expand Down Expand Up @@ -165,7 +166,7 @@ func (ns *Namespace) Dictionary(values ...interface{}) (map[string]interface{},
// EchoParam returns a given value if it is set; otherwise, it returns an
// empty string.
func (ns *Namespace) EchoParam(a, key interface{}) interface{} {
av, isNil := indirect(reflect.ValueOf(a))
av, isNil := hreflect.Indirect(reflect.ValueOf(a))
if isNil {
return ""
}
Expand All @@ -184,7 +185,7 @@ func (ns *Namespace) EchoParam(a, key interface{}) interface{} {
}
}

avv, isNil = indirect(avv)
avv, isNil = hreflect.Indirect(avv)

if isNil {
return ""
Expand Down Expand Up @@ -222,7 +223,7 @@ func (ns *Namespace) First(limit interface{}, seq interface{}) (interface{}, err
}

seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
seqv, isNil := hreflect.Indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
Expand Down Expand Up @@ -254,7 +255,7 @@ func (ns *Namespace) In(l interface{}, v interface{}) bool {
case reflect.Array, reflect.Slice:
for i := 0; i < lv.Len(); i++ {
lvv := lv.Index(i)
lvv, isNil := indirect(lvv)
lvv, isNil := hreflect.Indirect(lvv)
if isNil {
continue
}
Expand Down Expand Up @@ -380,7 +381,7 @@ func (ns *Namespace) Last(limit interface{}, seq interface{}) (interface{}, erro
}

seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
seqv, isNil := hreflect.Indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
Expand Down Expand Up @@ -496,7 +497,7 @@ func (ns *Namespace) Shuffle(seq interface{}) (interface{}, error) {
}

seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
seqv, isNil := hreflect.Indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
Expand Down Expand Up @@ -600,7 +601,7 @@ func (ns *Namespace) Union(l1, l2 interface{}) (interface{}, error) {
)

for i := 0; i < l1v.Len(); i++ {
l1vv, isNil = indirectInterface(l1v.Index(i))
l1vv, isNil = hreflect.IndirectInterface(l1v.Index(i))

if !l1vv.Type().Comparable() {
return []interface{}{}, errors.New("union does not support slices or arrays of uncomparable types")
Expand Down Expand Up @@ -657,7 +658,7 @@ func (ns *Namespace) Uniq(l interface{}) (interface{}, error) {
}

lv := reflect.ValueOf(l)
lv, isNil := indirect(lv)
lv, isNil := hreflect.Indirect(lv)
if isNil {
return nil, errors.New("invalid nil argument to Uniq")
}
Expand All @@ -675,7 +676,7 @@ func (ns *Namespace) Uniq(l interface{}) (interface{}, error) {

for i := 0; i != lv.Len(); i++ {
lvv := lv.Index(i)
lvv, isNil := indirect(lvv)
lvv, isNil := hreflect.Indirect(lvv)
if isNil {
continue
}
Expand Down
4 changes: 3 additions & 1 deletion tpl/collections/complement.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"errors"
"fmt"
"reflect"

"github.com/gohugoio/hugo/common/hreflect"
)

// Complement gives the elements in the last element of seqs that are not in
Expand All @@ -43,7 +45,7 @@ func (ns *Namespace) Complement(seqs ...interface{}) (interface{}, error) {
case reflect.Array, reflect.Slice:
sl := reflect.MakeSlice(v.Type(), 0, 0)
for i := 0; i < v.Len(); i++ {
ev, _ := indirectInterface(v.Index(i))
ev, _ := hreflect.IndirectInterface(v.Index(i))
if !ev.Type().Comparable() {
return nil, errors.New("elements in complement must be comparable")
}
Expand Down
4 changes: 3 additions & 1 deletion tpl/collections/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"errors"
"fmt"
"reflect"

"github.com/gohugoio/hugo/common/hreflect"
)

// Index returns the result of indexing its first argument by the following
Expand All @@ -36,7 +38,7 @@ func (ns *Namespace) Index(item interface{}, indices ...interface{}) (interface{
for _, i := range indices {
index := reflect.ValueOf(i)
var isNil bool
if v, isNil = indirect(v); isNil {
if v, isNil = hreflect.Indirect(v); isNil {
return nil, errors.New("index of nil pointer")
}
switch v.Kind() {
Expand Down
3 changes: 2 additions & 1 deletion tpl/collections/reflect_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"reflect"
"time"

"github.com/gohugoio/hugo/common/hreflect"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -66,7 +67,7 @@ func collectIdentities(seqs ...interface{}) (map[interface{}]bool, error) {
switch v.Kind() {
case reflect.Array, reflect.Slice:
for i := 0; i < v.Len(); i++ {
ev, _ := indirectInterface(v.Index(i))
ev, _ := hreflect.IndirectInterface(v.Index(i))

if !ev.Type().Comparable() {
return nil, errors.New("elements must be comparable")
Expand Down
5 changes: 4 additions & 1 deletion tpl/collections/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ package collections

import (
"errors"

"github.com/gohugoio/hugo/common/hreflect"

"reflect"
"sort"
"strings"
Expand All @@ -32,7 +35,7 @@ func (ns *Namespace) Sort(seq interface{}, args ...interface{}) (interface{}, er
}

seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
seqv, isNil := hreflect.Indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
Expand Down
Loading

0 comments on commit 5cbf912

Please sign in to comment.