Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-03-06 11:07:49 +0300
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-03-07 00:52:38 +0300
commit02eaddc2fbe92c26e67d9f82dd9aabecbbf2106c (patch)
tree517220ebb06a75e89d506183a621123d49ee20c6 /common
parentbdf47e8da80f87b7689badf48a6b8672c048d7e4 (diff)
tpl/tplimpl: Fix template truth logic
Before this commit, due to a bug in Go's `text/template` package, this would print different output for typed nil interface values: ``` {{ if .AuthenticatedUser }}User is authenticated!{{ else }}{{ end }} {{ if not .AuthenticatedUser }}{{ else }}}User is authenticated!{{ end }} ``` This commit works around this by wrapping every `if` and `with` with a custom `getif` template func with truth logic that matches `not`, `and` and `or`. Those 3 template funcs from Go's stdlib are now pulled into Hugo's source tree and adjusted to support custom zero values, e.g. types that implement `IsZero`. This means that you can now do: ``` {{ with .Date }}{{ . }}{{ end }} ``` And it would work as expected. Fixes #5738
Diffstat (limited to 'common')
-rw-r--r--common/hreflect/helpers.go91
-rw-r--r--common/hreflect/helpers_test.go42
-rw-r--r--common/types/types.go6
3 files changed, 139 insertions, 0 deletions
diff --git a/common/hreflect/helpers.go b/common/hreflect/helpers.go
new file mode 100644
index 000000000..db7b208b5
--- /dev/null
+++ b/common/hreflect/helpers.go
@@ -0,0 +1,91 @@
+// 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"
+
+ "github.com/gohugoio/hugo/common/types"
+)
+
+// IsTruthful returns whether in represents a truthful value.
+// See IsTruthfulValue
+func IsTruthful(in interface{}) bool {
+ switch v := in.(type) {
+ case reflect.Value:
+ return IsTruthfulValue(v)
+ default:
+ return IsTruthfulValue(reflect.ValueOf(in))
+ }
+
+}
+
+var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem()
+
+// IsTruthfulValue 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) {
+ val = indirectInterface(val)
+
+ if !val.IsValid() {
+ // Something like var x interface{}, never set. It's a form of nil.
+ return
+ }
+
+ if val.Type().Implements(zeroType) {
+ return !val.Interface().(types.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
+}
+
+// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
+func indirectInterface(v reflect.Value) reflect.Value {
+ if v.Kind() != reflect.Interface {
+ return v
+ }
+ if v.IsNil() {
+ return reflect.Value{}
+ }
+ return v.Elem()
+}
diff --git a/common/hreflect/helpers_test.go b/common/hreflect/helpers_test.go
new file mode 100644
index 000000000..3c9179394
--- /dev/null
+++ b/common/hreflect/helpers_test.go
@@ -0,0 +1,42 @@
+// 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 (
+ "reflect"
+ "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{}))
+}
+
+func BenchmarkIsTruthFul(b *testing.B) {
+ v := reflect.ValueOf("Hugo")
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ if !IsTruthfulValue(v) {
+ b.Fatal("not truthful")
+ }
+ }
+}
diff --git a/common/types/types.go b/common/types/types.go
index ca74391f8..95e72d99b 100644
--- a/common/types/types.go
+++ b/common/types/types.go
@@ -50,3 +50,9 @@ func NewKeyValuesStrings(key string, values ...string) KeyValues {
}
return KeyValues{Key: key, Values: iv}
}
+
+// Zeroer, as implemented by time.Time, will be used by the truth template
+// funcs in Hugo (if, with, not, and, or).
+type Zeroer interface {
+ IsZero() bool
+}