summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--helpers/general.go194
-rw-r--r--helpers/general_test.go131
-rw-r--r--hugolib/scratch.go5
-rw-r--r--tpl/collections/apply.go143
-rw-r--r--tpl/collections/apply_test.go92
-rw-r--r--tpl/collections/collections.go574
-rw-r--r--tpl/collections/collections_test.go610
-rw-r--r--tpl/collections/index.go107
-rw-r--r--tpl/collections/index_test.go60
-rw-r--r--tpl/collections/sort.go159
-rw-r--r--tpl/collections/sort_test.go237
-rw-r--r--tpl/collections/where.go421
-rw-r--r--tpl/collections/where_test.go606
-rw-r--r--tpl/compare/compare.go198
-rw-r--r--tpl/compare/compare_test.go197
-rw-r--r--tpl/crypto/crypto.go67
-rw-r--r--tpl/crypto/crypto_test.go111
-rw-r--r--tpl/data/data.go31
-rw-r--r--tpl/data/resources.go (renamed from tpl/tplimpl/template_resources.go)24
-rw-r--r--tpl/data/resources_test.go (renamed from tpl/tplimpl/template_resources_test.go)27
-rw-r--r--tpl/encoding/encoding.go64
-rw-r--r--tpl/encoding/encoding_test.go117
-rw-r--r--tpl/images/images.go82
-rw-r--r--tpl/images/images_test.go132
-rw-r--r--tpl/inflect/inflect.go79
-rw-r--r--tpl/inflect/inflect_test.go56
-rw-r--r--tpl/lang/lang.go49
-rw-r--r--tpl/math/math.go199
-rw-r--r--tpl/math/math_test.go228
-rw-r--r--tpl/os/os.go101
-rw-r--r--tpl/os/os_test.go65
-rw-r--r--tpl/safe/safe.go74
-rw-r--r--tpl/safe/safe_test.go222
-rw-r--r--tpl/strings/regexp.go109
-rw-r--r--tpl/strings/regexp_test.go86
-rw-r--r--tpl/strings/strings.go380
-rw-r--r--tpl/strings/strings_test.go639
-rw-r--r--tpl/strings/truncate.go (renamed from tpl/tplimpl/template_func_truncate.go)4
-rw-r--r--tpl/strings/truncate_test.go (renamed from tpl/tplimpl/template_func_truncate_test.go)11
-rw-r--r--tpl/time/time.go59
-rw-r--r--tpl/time/time_test.go67
-rw-r--r--tpl/tplimpl/reflect_helpers.go70
-rw-r--r--tpl/tplimpl/templateFuncster.go51
-rw-r--r--tpl/tplimpl/template_funcs.go2256
-rw-r--r--tpl/tplimpl/template_funcs_test.go2702
-rw-r--r--tpl/transform/transform.go119
-rw-r--r--tpl/transform/transform_test.go214
-rw-r--r--tpl/urls/urls.go108
48 files changed, 7069 insertions, 5268 deletions
diff --git a/helpers/general.go b/helpers/general.go
index ea3620119..4fd91133b 100644
--- a/helpers/general.go
+++ b/helpers/general.go
@@ -17,12 +17,10 @@ import (
"bytes"
"crypto/md5"
"encoding/hex"
- "errors"
"fmt"
"io"
"net"
"path/filepath"
- "reflect"
"strings"
"sync"
"unicode"
@@ -320,198 +318,6 @@ func IsWhitespace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
}
-// Seq creates a sequence of integers.
-// It's named and used as GNU's seq.
-// Examples:
-// 3 => 1, 2, 3
-// 1 2 4 => 1, 3
-// -3 => -1, -2, -3
-// 1 4 => 1, 2, 3, 4
-// 1 -2 => 1, 0, -1, -2
-func Seq(args ...interface{}) ([]int, error) {
- if len(args) < 1 || len(args) > 3 {
- return nil, errors.New("Seq, invalid number of args: 'first' 'increment' (optional) 'last' (optional)")
- }
-
- intArgs := cast.ToIntSlice(args)
-
- if len(intArgs) < 1 || len(intArgs) > 3 {
- return nil, errors.New("Invalid argument(s) to Seq")
- }
-
- var inc = 1
- var last int
- var first = intArgs[0]
-
- if len(intArgs) == 1 {
- last = first
- if last == 0 {
- return []int{}, nil
- } else if last > 0 {
- first = 1
- } else {
- first = -1
- inc = -1
- }
- } else if len(intArgs) == 2 {
- last = intArgs[1]
- if last < first {
- inc = -1
- }
- } else {
- inc = intArgs[1]
- last = intArgs[2]
- if inc == 0 {
- return nil, errors.New("'increment' must not be 0")
- }
- if first < last && inc < 0 {
- return nil, errors.New("'increment' must be > 0")
- }
- if first > last && inc > 0 {
- return nil, errors.New("'increment' must be < 0")
- }
- }
-
- // sanity check
- if last < -100000 {
- return nil, errors.New("size of result exceeds limit")
- }
- size := ((last - first) / inc) + 1
-
- // sanity check
- if size <= 0 || size > 2000 {
- return nil, errors.New("size of result exceeds limit")
- }
-
- seq := make([]int, size)
- val := first
- for i := 0; ; i++ {
- seq[i] = val
- val += inc
- if (inc < 0 && val < last) || (inc > 0 && val > last) {
- break
- }
- }
-
- return seq, nil
-}
-
-// DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to
-// determine the type of the two terms.
-func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
- av := reflect.ValueOf(a)
- bv := reflect.ValueOf(b)
- var ai, bi int64
- var af, bf float64
- var au, bu uint64
- switch av.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- ai = av.Int()
- switch bv.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- bi = bv.Int()
- case reflect.Float32, reflect.Float64:
- af = float64(ai) // may overflow
- ai = 0
- bf = bv.Float()
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- bu = bv.Uint()
- if ai >= 0 {
- au = uint64(ai)
- ai = 0
- } else {
- bi = int64(bu) // may overflow
- bu = 0
- }
- default:
- return nil, errors.New("Can't apply the operator to the values")
- }
- case reflect.Float32, reflect.Float64:
- af = av.Float()
- switch bv.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- bf = float64(bv.Int()) // may overflow
- case reflect.Float32, reflect.Float64:
- bf = bv.Float()
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- bf = float64(bv.Uint()) // may overflow
- default:
- return nil, errors.New("Can't apply the operator to the values")
- }
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- au = av.Uint()
- switch bv.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- bi = bv.Int()
- if bi >= 0 {
- bu = uint64(bi)
- bi = 0
- } else {
- ai = int64(au) // may overflow
- au = 0
- }
- case reflect.Float32, reflect.Float64:
- af = float64(au) // may overflow
- au = 0
- bf = bv.Float()
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- bu = bv.Uint()
- default:
- return nil, errors.New("Can't apply the operator to the values")
- }
- case reflect.String:
- as := av.String()
- if bv.Kind() == reflect.String && op == '+' {
- bs := bv.String()
- return as + bs, nil
- }
- return nil, errors.New("Can't apply the operator to the values")
- default:
- return nil, errors.New("Can't apply the operator to the values")
- }
-
- switch op {
- case '+':
- if ai != 0 || bi != 0 {
- return ai + bi, nil
- } else if af != 0 || bf != 0 {
- return af + bf, nil
- } else if au != 0 || bu != 0 {
- return au + bu, nil
- }
- return 0, nil
- case '-':
- if ai != 0 || bi != 0 {
- return ai - bi, nil
- } else if af != 0 || bf != 0 {
- return af - bf, nil
- } else if au != 0 || bu != 0 {
- return au - bu, nil
- }
- return 0, nil
- case '*':
- if ai != 0 || bi != 0 {
- return ai * bi, nil
- } else if af != 0 || bf != 0 {
- return af * bf, nil
- } else if au != 0 || bu != 0 {
- return au * bu, nil
- }
- return 0, nil
- case '/':
- if bi != 0 {
- return ai / bi, nil
- } else if bf != 0 {
- return af / bf, nil
- } else if bu != 0 {
- return au / bu, nil
- }
- return nil, errors.New("Can't divide the value by 0")
- default:
- return nil, errors.New("There is no such an operation")
- }
-}
-
// NormalizeHugoFlags facilitates transitions of Hugo command-line flags,
// e.g. --baseUrl to --baseURL, --uglyUrls to --uglyURLs
func NormalizeHugoFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
diff --git a/helpers/general_test.go b/helpers/general_test.go
index 3fa587e78..ee4ed2370 100644
--- a/helpers/general_test.go
+++ b/helpers/general_test.go
@@ -162,137 +162,6 @@ func TestFindAvailablePort(t *testing.T) {
assert.True(t, addr.Port > 0)
}
-func TestSeq(t *testing.T) {
- for i, this := range []struct {
- in []interface{}
- expect interface{}
- }{
- {[]interface{}{-2, 5}, []int{-2, -1, 0, 1, 2, 3, 4, 5}},
- {[]interface{}{1, 2, 4}, []int{1, 3}},
- {[]interface{}{1}, []int{1}},
- {[]interface{}{3}, []int{1, 2, 3}},
- {[]interface{}{3.2}, []int{1, 2, 3}},
- {[]interface{}{0}, []int{}},
- {[]interface{}{-1}, []int{-1}},
- {[]interface{}{-3}, []int{-1, -2, -3}},
- {[]interface{}{3, -2}, []int{3, 2, 1, 0, -1, -2}},
- {[]interface{}{6, -2, 2}, []int{6, 4, 2}},
- {[]interface{}{1, 0, 2}, false},
- {[]interface{}{1, -1, 2}, false},
- {[]interface{}{2, 1, 1}, false},
- {[]interface{}{2, 1, 1, 1}, false},
- {[]interface{}{2001}, false},
- {[]interface{}{}, false},
- // TODO(bep) {[]interface{}{t}, false},
- {nil, false},
- } {
-
- result, err := Seq(this.in...)
-
- if b, ok := this.expect.(bool); ok && !b {
- if err == nil {
- t.Errorf("[%d] TestSeq didn't return an expected error", i)
- }
- } else {
- if err != nil {
- t.Errorf("[%d] failed: %s", i, err)
- continue
- }
- if !reflect.DeepEqual(result, this.expect) {
- t.Errorf("[%d] TestSeq got %v but expected %v", i, result, this.expect)
- }
- }
- }
-}
-
-func TestDoArithmetic(t *testing.T) {
- for i, this := range []struct {
- a interface{}
- b interface{}
- op rune
- expect interface{}
- }{
- {3, 2, '+', int64(5)},
- {3, 2, '-', int64(1)},
- {3, 2, '*', int64(6)},
- {3, 2, '/', int64(1)},
- {3.0, 2, '+', float64(5)},
- {3.0, 2, '-', float64(1)},
- {3.0, 2, '*', float64(6)},
- {3.0, 2, '/', float64(1.5)},
- {3, 2.0, '+', float64(5)},
- {3, 2.0, '-', float64(1)},
- {3, 2.0, '*', float64(6)},
- {3, 2.0, '/', float64(1.5)},
- {3.0, 2.0, '+', float64(5)},
- {3.0, 2.0, '-', float64(1)},
- {3.0, 2.0, '*', float64(6)},
- {3.0, 2.0, '/', float64(1.5)},
- {uint(3), uint(2), '+', uint64(5)},
- {uint(3), uint(2), '-', uint64(1)},
- {uint(3), uint(2), '*', uint64(6)},
- {uint(3), uint(2), '/', uint64(1)},
- {uint(3), 2, '+', uint64(5)},
- {uint(3), 2, '-', uint64(1)},
- {uint(3), 2, '*', uint64(6)},
- {uint(3), 2, '/', uint64(1)},
- {3, uint(2), '+', uint64(5)},
- {3, uint(2), '-', uint64(1)},
- {3, uint(2), '*', uint64(6)},
- {3, uint(2), '/', uint64(1)},
- {uint(3), -2, '+', int64(1)},
- {uint(3), -2, '-', int64(5)},
- {uint(3), -2, '*', int64(-6)},
- {uint(3), -2, '/', int64(-1)},
- {-3, uint(2), '+', int64(-1)},
- {-3, uint(2), '-', int64(-5)},
- {-3, uint(2), '*', int64(-6)},
- {-3, uint(2), '/', int64(-1)},
- {uint(3), 2.0, '+', float64(5)},
- {uint(3), 2.0, '-', float64(1)},
- {uint(3), 2.0, '*', float64(6)},
- {uint(3), 2.0, '/', float64(1.5)},
- {3.0, uint(2), '+', float64(5)},
- {3.0, uint(2), '-', float64(1)},
- {3.0, uint(2), '*', float64(6)},
- {3.0, uint(2), '/', float64(1.5)},
- {0, 0, '+', 0},
- {0, 0, '-', 0},
- {0, 0, '*', 0},
- {"foo", "bar", '+', "foobar"},
- {3, 0, '/', false},
- {3.0, 0, '/', false},
- {3, 0.0, '/', false},
- {uint(3), uint(0), '/', false},
- {3, uint(0), '/', false},
- {-3, uint(0), '/', false},
- {uint(3), 0, '/', false},
- {3.0, uint(0), '/', false},
- {uint(3), 0.0, '/', false},
- {3, "foo", '+', false},
- {3.0, "foo", '+', false},
- {uint(3), "foo", '+', false},
- {"foo", 3, '+', false},
- {"foo", "bar", '-', false},
- {3, 2, '%', false},
- } {
- result, err := DoArithmetic(this.a, this.b, this.op)
- if b, ok := this.expect.(bool); ok && !b {
- if err == nil {
- t.Errorf("[%d] doArithmetic didn't return an expected error", i)
- }
- } else {
- if err != nil {
- t.Errorf("[%d] failed: %s", i, err)
- continue
- }
- if !reflect.DeepEqual(result, this.expect) {
- t.Errorf("[%d] doArithmetic got %v but expected %v", i, result, this.expect)
- }
- }
- }
-}
-
func TestToLowerMap(t *testing.T) {
tests := []struct {
diff --git a/hugolib/scratch.go b/hugolib/scratch.go
index b6a06cd79..4a80416f1 100644
--- a/hugolib/scratch.go
+++ b/hugolib/scratch.go
@@ -14,10 +14,11 @@
package hugolib
import (
- "github.com/spf13/hugo/helpers"
"reflect"
"sort"
"sync"
+
+ "github.com/spf13/hugo/tpl/math"
)
// Scratch is a writable context used for stateful operations in Page/Node rendering.
@@ -49,7 +50,7 @@ func (c *Scratch) Add(key string, newAddend interface{}) (string, error) {
newVal = reflect.Append(addendV, nav).Interface()
}
} else {
- newVal, err = helpers.DoArithmetic(existingAddend, newAddend, '+')
+ newVal, err = math.DoArithmetic(existingAddend, newAddend, '+')
if err != nil {
return "", err
}
diff --git a/tpl/collections/apply.go b/tpl/collections/apply.go
new file mode 100644
index 000000000..cb4dfa64e
--- /dev/null
+++ b/tpl/collections/apply.go
@@ -0,0 +1,143 @@
+// Copyright 2017 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 collections
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+)
+
+// Apply takes a map, array, or slice and returns a new slice with the function fname applied over it.
+func (ns *Namespace) Apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) {
+ if seq == nil {
+ return make([]interface{}, 0), nil
+ }
+
+ if fname == "apply" {
+ return nil, errors.New("can't apply myself (no turtles allowed)")
+ }
+
+ seqv := reflect.ValueOf(seq)
+ seqv, isNil := indirect(seqv)
+ if isNil {
+ return nil, errors.New("can't iterate over a nil value")
+ }
+
+ fnv, found := ns.lookupFunc(fname)
+ if !found {
+ return nil, errors.New("can't find function " + fname)
+ }
+
+ // fnv := reflect.ValueOf(fn)
+
+ switch seqv.Kind() {
+ case reflect.Array, reflect.Slice:
+ r := make([]interface{}, seqv.Len())
+ for i := 0; i < seqv.Len(); i++ {
+ vv := seqv.Index(i)
+
+ vvv, err := applyFnToThis(fnv, vv, args...)
+
+ if err != nil {
+ return nil, err
+ }
+
+ r[i] = vvv.Interface()
+ }
+
+ return r, nil
+ default:
+ return nil, fmt.Errorf("can't apply over %v", seq)
+ }
+}
+
+func applyFnToThis(fn, this reflect.Value, args ...interface{}) (reflect.Value, error) {
+ n := make([]reflect.Value, len(args))
+ for i, arg := range args {
+ if arg == "." {
+ n[i] = this
+ } else {
+ n[i] = reflect.ValueOf(arg)
+ }
+ }
+
+ num := fn.Type().NumIn()
+
+ if fn.Type().IsVariadic() {
+ num--
+ }
+
+ // TODO(bep) see #1098 - also see template_tests.go
+ /*if len(args) < num {
+ return reflect.ValueOf(nil), errors.New("Too few arguments")
+ } else if len(args) > num {
+ return reflect.ValueOf(nil), errors.New("Too many arguments")
+ }*/
+
+ for i := 0; i < num; i++ {
+ // AssignableTo reports whether xt is assignable to type targ.
+ if xt, targ := n[i].Type(), fn.Type().In(i); !xt.AssignableTo(targ) {
+ return reflect.ValueOf(nil), errors.New("called apply using " + xt.String() + " as type " + targ.String())
+ }
+ }
+
+ res := fn.Call(n)
+
+ if len(res) == 1 || res[1].IsNil() {
+ return res[0], nil
+ }
+ return reflect.ValueOf(nil), res[1].Interface().(error)
+}
+
+func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
+ if !strings.ContainsRune(fname, '.') {
+ fn, found := ns.funcMap[fname]
+ if !found {
+ return reflect.Value{}, false
+ }
+
+ return reflect.ValueOf(fn), true
+ }
+
+ ss := strings.SplitN(fname, ".", 2)
+
+ // namespace
+ nv, found := ns.lookupFunc(ss[0])
+ if !found {
+ return reflect.Value{}, false
+ }
+
+ // method
+ m := nv.MethodByName(ss[1])
+ // if reflect.DeepEqual(m, reflect.Value{}) {
+ if m.Kind() == reflect.Invalid {
+ return reflect.Value{}, false
+ }
+ return m, true
+}
+
+// indirect is taken from '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
+}
diff --git a/tpl/collections/apply_test.go b/tpl/collections/apply_test.go
new file mode 100644
index 000000000..9718570fd
--- /dev/null
+++ b/tpl/collections/apply_test.go
@@ -0,0 +1,92 @@
+// Copyright 2017 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 collections
+
+import (
+ "fmt"
+ "html/template"
+ "testing"
+
+ "github.com/spf13/hugo/deps"
+ "github.com/spf13/hugo/tpl/strings"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestApply(t *testing.T) {
+ t.Parallel()
+
+ hstrings := strings.New(&deps.Deps{})
+
+ ns := New(&deps.Deps{})
+ ns.Funcs(template.FuncMap{
+ "apply": ns.Apply,
+ "chomp": hstrings.Chomp,
+ "strings": hstrings,
+ "print": fmt.Sprint,
+ })
+
+ strings := []interface{}{"a\n", "b\n"}
+ noStringers := []interface{}{tstNoStringer{}, tstNoStringer{}}
+
+ result, _ := ns.Apply(strings, "chomp", ".")
+ assert.Equal(t, []interface{}{template.HTML("a"), template.HTML("b")}, result)
+
+ result, _ = ns.Apply(strings, "chomp", "c\n")
+ assert.Equal(t, []interface{}{template.HTML("c"), template.HTML("c")}, result)
+
+ result, _ = ns.Apply(strings, "strings.Chomp", "c\n")
+ assert.Equal(t, []interface{}{template.HTML("c"), template.HTML("c")}, result)
+
+ result, _ = ns.Apply(strings, "print", "a", "b", "c")
+ assert.Equal(t, []interface{}{"abc", "abc"}, result, "testing variadic")
+
+ result, _ = ns.Apply(nil, "chomp", ".")
+ assert.Equal(t, []interface{}{}, result)
+
+ _, err := ns.Apply(strings, "apply", ".")
+ if err == nil {
+ t.Errorf("apply with apply should fail")
+ }
+
+ var nilErr *error
+ _, err = ns.Apply(nilErr, "chomp", ".")
+ if err == nil {
+ t.Errorf("apply with nil in seq should fail")
+ }
+
+ _, err = ns.Apply(strings, "dobedobedo", ".")
+ if err == nil {
+ t.Errorf("apply with unknown func should fail")
+ }
+
+ _, err = ns.Apply(noStringers, "chomp", ".")