hugo/tpl/tplimpl/template_funcs.go
Bjørn Erik Pedersen 1cf29200b4 tplimpl: Allow text partials in HTML templates
Most obvius benefit of this is to include CSS partials with css file suffix into HTML templates.

A valid workaround would be to rename the file `mystyles.html`, but that doesn't work too good for external editors etc.

The css partial is  a method used in some themes before Hugo 0.20, but then it stopped working.

This commit reintroduces that behaviour.

Note that the regular layout lookups for text templates, i.e. "single.json" will be
prefixed with "_text/" on lookup and will only match in the text collection.

Fixes #3273
2017-04-16 09:17:47 +02:00

2226 lines
56 KiB
Go

// Copyright 2016 The Hugo Authors. All rights reserved.
//
// Portions Copyright The Go Authors.
// 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 tplimpl
import (
"bytes"
_md5 "crypto/md5"
_sha1 "crypto/sha1"
_sha256 "crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"html"
"html/template"
"image"
"math/rand"
"net/url"
"os"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
"github.com/spf13/hugo/hugofs"
"github.com/bep/inflect"
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/spf13/hugo/helpers"
// Importing image codecs for image.DecodeConfig
_ "image/gif"
_ "image/jpeg"
_ "image/png"
)
// eq returns the boolean truth of arg1 == arg2.
func eq(x, y interface{}) bool {
normalize := func(v interface{}) interface{} {
vv := reflect.ValueOf(v)
switch vv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return vv.Int()
case reflect.Float32, reflect.Float64:
return vv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return vv.Uint()
default:
return v
}
}
x = normalize(x)
y = normalize(y)
return reflect.DeepEqual(x, y)
}
// ne returns the boolean truth of arg1 != arg2.
func ne(x, y interface{}) bool {
return !eq(x, y)
}
// ge returns the boolean truth of arg1 >= arg2.
func ge(a, b interface{}) bool {
left, right := compareGetFloat(a, b)
return left >= right
}
// gt returns the boolean truth of arg1 > arg2.
func gt(a, b interface{}) bool {
left, right := compareGetFloat(a, b)
return left > right
}
// le returns the boolean truth of arg1 <= arg2.
func le(a, b interface{}) bool {
left, right := compareGetFloat(a, b)
return left <= right
}
// lt returns the boolean truth of arg1 < arg2.
func lt(a, b interface{}) bool {
left, right := compareGetFloat(a, b)
return left < right
}
// dictionary creates a map[string]interface{} from the given parameters by
// walking the parameters and treating them as key-value pairs. The number
// of parameters must be even.
func dictionary(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
}
// slice returns a slice of all passed arguments
func slice(args ...interface{}) []interface{} {
return args
}
func compareGetFloat(a interface{}, b interface{}) (float64, float64) {
var left, right float64
var leftStr, rightStr *string
av := reflect.ValueOf(a)
switch av.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
left = float64(av.Len())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
left = float64(av.Int())
case reflect.Float32, reflect.Float64:
left = av.Float()
case reflect.String:
var err error
left, err = strconv.ParseFloat(av.String(), 64)
if err != nil {
str := av.String()
leftStr = &str
}
case reflect.Struct:
switch av.Type() {
case timeType:
left = float64(toTimeUnix(av))
}
}
bv := reflect.ValueOf(b)
switch bv.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
right = float64(bv.Len())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
right = float64(bv.Int())
case reflect.Float32, reflect.Float64:
right = bv.Float()
case reflect.String:
var err error
right, err = strconv.ParseFloat(bv.String(), 64)
if err != nil {
str := bv.String()
rightStr = &str
}
case reflect.Struct:
switch bv.Type() {
case timeType:
right = float64(toTimeUnix(bv))
}
}
switch {
case leftStr == nil || rightStr == nil:
case *leftStr < *rightStr:
return 0, 1
case *leftStr > *rightStr:
return 1, 0
default:
return 0, 0
}
return left, right
}
// slicestr slices a string by specifying a half-open range with
// two indices, start and end. 1 and 4 creates a slice including elements 1 through 3.
// The end index can be omitted, it defaults to the string's length.
func slicestr(a interface{}, startEnd ...interface{}) (string, error) {
aStr, err := cast.ToStringE(a)
if err != nil {
return "", err
}
var argStart, argEnd int
argNum := len(startEnd)
if argNum > 0 {
if argStart, err = cast.ToIntE(startEnd[0]); err != nil {
return "", errors.New("start argument must be integer")
}
}
if argNum > 1 {
if argEnd, err = cast.ToIntE(startEnd[1]); err != nil {
return "", errors.New("end argument must be integer")
}
}
if argNum > 2 {
return "", errors.New("too many arguments")
}
asRunes := []rune(aStr)
if argNum > 0 && (argStart < 0 || argStart >= len(asRunes)) {
return "", errors.New("slice bounds out of range")
}
if argNum == 2 {
if argEnd < 0 || argEnd > len(asRunes) {
return "", errors.New("slice bounds out of range")
}
return string(asRunes[argStart:argEnd]), nil
} else if argNum == 1 {
return string(asRunes[argStart:]), nil
} else {
return string(asRunes[:]), nil
}
}
// hasPrefix tests whether the input s begins with prefix.
func hasPrefix(s, prefix interface{}) (bool, error) {
ss, err := cast.ToStringE(s)
if err != nil {
return false, err
}
sp, err := cast.ToStringE(prefix)
if err != nil {
return false, err
}
return strings.HasPrefix(ss, sp), nil
}
// substr extracts parts of a string, beginning at the character at the specified
// position, and returns the specified number of characters.
//
// It normally takes two parameters: start and length.
// It can also take one parameter: start, i.e. length is omitted, in which case
// the substring starting from start until the end of the string will be returned.
//
// To extract characters from the end of the string, use a negative start number.
//
// In addition, borrowing from the extended behavior described at http://php.net/substr,
// if length is given and is negative, then that many characters will be omitted from
// the end of string.
func substr(a interface{}, nums ...interface{}) (string, error) {
aStr, err := cast.ToStringE(a)
if err != nil {
return "", err
}
var start, length int
asRunes := []rune(aStr)
switch len(nums) {
case 0:
return "", errors.New("too less arguments")
case 1:
if start, err = cast.ToIntE(nums[0]); err != nil {
return "", errors.New("start argument must be integer")
}
length = len(asRunes)
case 2:
if start, err = cast.ToIntE(nums[0]); err != nil {
return "", errors.New("start argument must be integer")
}
if length, err = cast.ToIntE(nums[1]); err != nil {
return "", errors.New("length argument must be integer")
}
default:
return "", errors.New("too many arguments")
}
if start < -len(asRunes) {
start = 0
}
if start > len(asRunes) {
return "", fmt.Errorf("start position out of bounds for %d-byte string", len(aStr))
}
var s, e int
if start >= 0 && length >= 0 {
s = start
e = start + length
} else if start < 0 && length >= 0 {
s = len(asRunes) + start - length + 1
e = len(asRunes) + start + 1
} else if start >= 0 && length < 0 {
s = start
e = len(asRunes) + length
} else {
s = len(asRunes) + start
e = len(asRunes) + length
}
if s > e {
return "", fmt.Errorf("calculated start position greater than end position: %d > %d", s, e)
}
if e > len(asRunes) {
e = len(asRunes)
}
return string(asRunes[s:e]), nil
}
// split slices an input string into all substrings separated by delimiter.
func split(a interface{}, delimiter string) ([]string, error) {
aStr, err := cast.ToStringE(a)
if err != nil {
return []string{}, err
}
return strings.Split(aStr, delimiter), nil
}
// intersect returns the common elements in the given sets, l1 and l2. l1 and
// l2 must be of the same type and may be either arrays or slices.
func intersect(l1, l2 interface{}) (interface{}, error) {
if l1 == nil || l2 == nil {
return make([]interface{}, 0), nil
}
l1v := reflect.ValueOf(l1)
l2v := reflect.ValueOf(l2)
switch l1v.Kind() {
case reflect.Array, reflect.Slice:
switch l2v.Kind() {
case reflect.Array, reflect.Slice:
r := reflect.MakeSlice(l1v.Type(), 0, 0)
for i := 0; i < l1v.Len(); i++ {
l1vv := l1v.Index(i)
for j := 0; j < l2v.Len(); j++ {
l2vv := l2v.Index(j)
switch l1vv.Kind() {
case reflect.String:
if l1vv.Type() == l2vv.Type() && l1vv.String() == l2vv.String() && !in(r.Interface(), l2vv.Interface()) {
r = reflect.Append(r, l2vv)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch l2vv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if l1vv.Int() == l2vv.Int() && !in(r.Interface(), l2vv.Interface()) {
r = reflect.Append(r, l2vv)
}
}
case reflect.Float32, reflect.Float64:
switch l2vv.Kind() {
case reflect.Float32, reflect.Float64:
if l1vv.Float() == l2vv.Float() && !in(r.Interface(), l2vv.Interface()) {
r = reflect.Append(r, l2vv)
}
}
}
}
}
return r.Interface(), nil
default:
return nil, errors.New("can't iterate over " + reflect.ValueOf(l2).Type().String())
}
default:
return nil, errors.New("can't iterate over " + reflect.ValueOf(l1).Type().String())
}
}
// union returns the union of the given sets, l1 and l2. l1 and
// l2 must be of the same type and may be either arrays or slices.
// If l1 and l2 aren't of the same type then l1 will be returned.
// If either l1 or l2 is nil then the non-nil list will be returned.
func union(l1, l2 interface{}) (interface{}, error) {
if l1 == nil && l2 == nil {
return nil, errors.New("both arrays/slices have to be of the same type")
} else if l1 == nil && l2 != nil {
return l2, nil
} else if l1 != nil && l2 == nil {
return l1, nil
}
l1v := reflect.ValueOf(l1)
l2v := reflect.ValueOf(l2)
switch l1v.Kind() {
case reflect.Array, reflect.Slice:
switch l2v.Kind() {
case reflect.Array, reflect.Slice:
r := reflect.MakeSlice(l1v.Type(), 0, 0)
if l1v.Type() != l2v.Type() {
return r.Interface(), nil
}
for i := 0; i < l1v.Len(); i++ {
elem := l1v.Index(i)
if !in(r.Interface(), elem.Interface()) {
r = reflect.Append(r, elem)
}
}
for j := 0; j < l2v.Len(); j++ {
elem := l2v.Index(j)
if !in(r.Interface(), elem.Interface()) {
r = reflect.Append(r, elem)
}
}
return r.Interface(), nil
default:
return nil, errors.New("can't iterate over " + reflect.ValueOf(l2).Type().String())
}
default:
return nil, errors.New("can't iterate over " + reflect.ValueOf(l1).Type().String())
}
}
type imageHandler struct {
imageConfigCache map[string]image.Config
sync.RWMutex
fs *hugofs.Fs
}
// imageConfig returns the image.Config for the specified path relative to the
// working directory.
func (ic *imageHandler) config(path interface{}) (image.Config, error) {
filename, err := cast.ToStringE(path)
if err != nil {
return image.Config{}, err
}
if filename == "" {
return image.Config{}, errors.New("config needs a filename")
}
// Check cache for image config.
ic.RLock()
config, ok := ic.imageConfigCache[filename]
ic.RUnlock()
if ok {
return config, nil
}
f, err := ic.fs.WorkingDir.Open(filename)
if err != nil {
return image.Config{}, err
}
config, _, err = image.DecodeConfig(f)
ic.Lock()
ic.imageConfigCache[filename] = config
ic.Unlock()
return config, err
}
// in returns whether v is in the set l. l may be an array or slice.
func in(l interface{}, v interface{}) bool {
lv := reflect.ValueOf(l)
vv := reflect.ValueOf(v)
switch lv.Kind() {
case reflect.Array, reflect.Slice:
for i := 0; i < lv.Len(); i++ {
lvv := lv.Index(i)
lvv, isNil := indirect(lvv)
if isNil {
continue
}
switch lvv.Kind() {
case reflect.String:
if vv.Type() == lvv.Type() && vv.String() == lvv.String() {
return true
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch vv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if vv.Int() == lvv.Int() {
return true
}
}
case reflect.Float32, reflect.Float64:
switch vv.Kind() {
case reflect.Float32, reflect.Float64:
if vv.Float() == lvv.Float() {
return true
}
}
}
}
case reflect.String:
if vv.Type() == lv.Type() && strings.Contains(lv.String(), vv.String()) {
return true
}
}
return false
}
// first returns the first N items in a rangeable list.
func first(limit interface{}, seq interface{}) (interface{}, error) {
if limit == nil || seq == nil {
return nil, errors.New("both limit and seq must be provided")
}
limitv, err := cast.ToIntE(limit)
if err != nil {
return nil, err
}
if limitv < 1 {
return nil, errors.New("can't return negative/empty count of items from sequence")
}
seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
switch seqv.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
// okay
default:
return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String())
}
if limitv > seqv.Len() {
limitv = seqv.Len()
}
return seqv.Slice(0, limitv).Interface(), nil
}
// findRE returns a list of strings that match the regular expression. By default all matches
// will be included. The number of matches can be limited with an optional third parameter.
func findRE(expr string, content interface{}, limit ...interface{}) ([]string, error) {
re, err := reCache.Get(expr)
if err != nil {
return nil, err
}
conv, err := cast.ToStringE(content)
if err != nil {
return nil, err
}
if len(limit) == 0 {
return re.FindAllString(conv, -1), nil
}
lim, err := cast.ToIntE(limit[0])
if err != nil {
return nil, err
}
return re.FindAllString(conv, lim), nil
}
// last returns the last N items in a rangeable list.
func last(limit interface{}, seq interface{}) (interface{}, error) {
if limit == nil || seq == nil {
return nil, errors.New("both limit and seq must be provided")
}
limitv, err := cast.ToIntE(limit)
if err != nil {
return nil, err
}
if limitv < 1 {
return nil, errors.New("can't return negative/empty count of items from sequence")
}
seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
switch seqv.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
// okay
default:
return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String())
}
if limitv > seqv.Len() {
limitv = seqv.Len()
}
return seqv.Slice(seqv.Len()-limitv, seqv.Len()).Interface(), nil
}
// after returns all the items after the first N in a rangeable list.
func after(index interface{}, seq interface{}) (interface{}, error) {
if index == nil || seq == nil {
return nil, errors.New("both limit and seq must be provided")
}
indexv, err := cast.ToIntE(index)
if err != nil {
return nil, err
}
if indexv < 1 {
return nil, errors.New("can't return negative/empty count of items from sequence")
}
seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
switch seqv.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
// okay
default:
return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String())
}
if indexv >= seqv.Len() {
return nil, errors.New("no items left")
}
return seqv.Slice(indexv, seqv.Len()).Interface(), nil
}
// shuffle returns the given rangeable list in a randomised order.
func shuffle(seq interface{}) (interface{}, error) {
if seq == nil {
return nil, errors.New("both count and seq must be provided")
}
seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
switch seqv.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
// okay
default:
return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String())
}
shuffled := reflect.MakeSlice(reflect.TypeOf(seq), seqv.Len(), seqv.Len())
rand.Seed(time.Now().UTC().UnixNano())
randomIndices := rand.Perm(seqv.Len())
for index, value := range randomIndices {
shuffled.Index(value).Set(seqv.Index(index))
}
return shuffled.Interface(), nil
}
func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error) {
if !obj.IsValid() {
return zero, errors.New("can't evaluate an invalid value")
}
typ := obj.Type()
obj, isNil := indirect(obj)
// first, check whether obj has a method. In this case, obj is
// an interface, a struct or its pointer. If obj is a struct,
// to check all T and *T method, use obj pointer type Value
objPtr := obj
if objPtr.Kind() != reflect.Interface && objPtr.CanAddr() {
objPtr = objPtr.Addr()
}
mt, ok := objPtr.Type().MethodByName(elemName)
if ok {
if mt.PkgPath != "" {
return zero, fmt.Errorf("%s is an unexported method of type %s", elemName, typ)
}
// struct pointer has one receiver argument and interface doesn't have an argument
if mt.Type.NumIn() > 1 || mt.Type.NumOut() == 0 || mt.Type.NumOut() > 2 {
return zero, fmt.Errorf("%s is a method of type %s but doesn't satisfy requirements", elemName, typ)
}
if mt.Type.NumOut() == 1 && mt.Type.Out(0).Implements(errorType) {
return zero, fmt.Errorf("%s is a method of type %s but doesn't satisfy requirements", elemName, typ)
}
if mt.Type.NumOut() == 2 && !mt.Type.Out(1).Implements(errorType) {
return zero, fmt.Errorf("%s is a method of type %s but doesn't satisfy requirements", elemName, typ)
}
res := objPtr.Method(mt.Index).Call([]reflect.Value{})
if len(res) == 2 && !res[1].IsNil() {
return zero, fmt.Errorf("error at calling a method %s of type %s: %s", elemName, typ, res[1].Interface().(error))
}
return res[0], nil
}
// elemName isn't a method so next start to check whether it is
// a struct field or a map value. In both cases, it mustn't be
// a nil value
if isNil {
return zero, fmt.Errorf("can't evaluate a nil pointer of type %s by a struct field or map key name %s", typ, elemName)
}
switch obj.Kind() {
case reflect.Struct:
ft, ok := obj.Type().FieldByName(elemName)
if ok {
if ft.PkgPath != "" && !ft.Anonymous {
return zero, fmt.Errorf("%s is an unexported field of struct type %s", elemName, typ)
}
return obj.FieldByIndex(ft.Index), nil
}
return zero, fmt.Errorf("%s isn't a field of struct type %s", elemName, typ)
case reflect.Map:
kv := reflect.ValueOf(elemName)
if kv.Type().AssignableTo(obj.Type().Key()) {
return obj.MapIndex(kv), nil
}
return zero, fmt.Errorf("%s isn't a key of map type %s", elemName, typ)
}
return zero, fmt.Errorf("%s is neither a struct field, a method nor a map element of type %s", elemName, typ)
}
func checkCondition(v, mv reflect.Value, op string) (bool, error) {
v, vIsNil := indirect(v)
if !v.IsValid() {
vIsNil = true
}
mv, mvIsNil := indirect(mv)
if !mv.IsValid() {
mvIsNil = true
}
if vIsNil || mvIsNil {
switch op {
case "", "=", "==", "eq":
return vIsNil == mvIsNil, nil
case "!=", "<>", "ne":
return vIsNil != mvIsNil, nil
}
return false, nil
}
if v.Kind() == reflect.Bool && mv.Kind() == reflect.Bool {
switch op {
case "", "=", "==", "eq":
return v.Bool() == mv.Bool(), nil
case "!=", "<>", "ne":
return v.Bool() != mv.Bool(), nil
}
return false, nil
}
var ivp, imvp *int64
var svp, smvp *string
var slv, slmv interface{}
var ima []int64
var sma []string
if mv.Type() == v.Type() {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
iv := v.Int()
ivp = &iv
imv := mv.Int()
imvp = &imv
case reflect.String:
sv := v.String()
svp = &sv
smv := mv.String()
smvp = &smv
case reflect.Struct:
switch v.Type() {
case timeType:
iv := toTimeUnix(v)
ivp = &iv
imv := toTimeUnix(mv)
imvp = &imv
}
case reflect.Array, reflect.Slice:
slv = v.Interface()
slmv = mv.Interface()
}
} else {
if mv.Kind() != reflect.Array && mv.Kind() != reflect.Slice {
return false, nil
}
if mv.Len() == 0 {
return false, nil
}
if v.Kind() != reflect.Interface && mv.Type().Elem().Kind() != reflect.Interface && mv.Type().Elem() != v.Type() {
return false, nil
}
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
iv := v.Int()
ivp = &iv
for i := 0; i < mv.Len(); i++ {
if anInt := toInt(mv.Index(i)); anInt != -1 {
ima = append(ima, anInt)
}
}
case reflect.String:
sv := v.String()
svp = &sv
for i := 0; i < mv.Len(); i++ {
if aString := toString(mv.Index(i)); aString != "" {
sma = append(sma, aString)
}
}
case reflect.Struct:
switch v.Type() {
case timeType:
iv := toTimeUnix(v)
ivp = &iv
for i := 0; i < mv.Len(); i++ {
ima = append(ima, toTimeUnix(mv.Index(i)))
}
}
}
}
switch op {
case "", "=", "==", "eq":
if ivp != nil && imvp != nil {
return *ivp == *imvp, nil
} else if svp != nil && smvp != nil {
return *svp == *smvp, nil
}
case "!=", "<>", "ne":
if ivp != nil && imvp != nil {
return *ivp != *imvp, nil
} else if svp != nil && smvp != nil {
return *svp != *smvp, nil
}
case ">=", "ge":
if ivp != nil && imvp != nil {
return *ivp >= *imvp, nil
} else if svp != nil && smvp != nil {
return *svp >= *smvp, nil
}
case ">", "gt":
if ivp != nil && imvp != nil {
return *ivp > *imvp, nil
} else if svp != nil && smvp != nil {
return *svp > *smvp, nil
}
case "<=", "le":
if ivp != nil && imvp != nil {
return *ivp <= *imvp, nil
} else if svp != nil && smvp != nil {
return *svp <= *smvp, nil
}
case "<", "lt":
if ivp != nil && imvp != nil {
return *ivp < *imvp, nil
} else if svp != nil && smvp != nil {
return *svp < *smvp, nil
}
case "in", "not in":
var r bool
if ivp != nil && len(ima) > 0 {
r = in(ima, *ivp)
} else if svp != nil {
if len(sma) > 0 {
r = in(sma, *svp)
} else if smvp != nil {
r = in(*smvp, *svp)
}
} else {
return false, nil
}
if op == "not in" {
return !r, nil
}
return r, nil
case "intersect":
r, err := intersect(slv, slmv)
if err != nil {
return false, err
}
if reflect.TypeOf(r).Kind() == reflect.Slice {
s := reflect.ValueOf(r)
if s.Len() > 0 {
return true, nil
}
return false, nil
}
return false, errors.New("invalid intersect values")
default:
return false, errors.New("no such operator")
}
return false, nil
}
// parseWhereArgs parses the end arguments to the where function. Return a
// match value and an operator, if one is defined.
func parseWhereArgs(args ...interface{}) (mv reflect.Value, op string, err error) {
switch len(args) {
case 1:
mv = reflect.ValueOf(args[0])
case 2:
var ok bool
if op, ok = args[0].(string); !ok {
err = errors.New("operator argument must be string type")
return
}
op = strings.TrimSpace(strings.ToLower(op))
mv = reflect.ValueOf(args[1])
default:
err = errors.New("can't evaluate the array by no match argument or more than or equal to two arguments")
}
return
}
// checkWhereArray handles the where-matching logic when the seqv value is an
// Array or Slice.
func checkWhereArray(seqv, kv, mv reflect.Value, path []string, op string) (interface{}, error) {
rv := reflect.MakeSlice(seqv.Type(), 0, 0)
for i := 0; i < seqv.Len(); i++ {
var vvv reflect.Value
rvv := seqv.Index(i)
if kv.Kind() == reflect.String {
vvv = rvv
for _, elemName := range path {
var err error
vvv, err = evaluateSubElem(vvv, elemName)
if err != nil {
return nil, err
}
}
} else {
vv, _ := indirect(rvv)
if vv.Kind() == reflect.Map && kv.Type().AssignableTo(vv.Type().Key()) {
vvv = vv.MapIndex(kv)
}
}
if ok, err := checkCondition(vvv, mv, op); ok {
rv = reflect.Append(rv, rvv)
} else if err != nil {
return nil, err
}
}
return rv.Interface(), nil
}
// checkWhereMap handles the where-matching logic when the seqv value is a Map.
func checkWhereMap(seqv, kv, mv reflect.Value, path []string, op string) (interface{}, error) {
rv := reflect.MakeMap(seqv.Type())
keys := seqv.MapKeys()
for _, k := range keys {
elemv := seqv.MapIndex(k)
switch elemv.Kind() {
case reflect.Array, reflect.Slice:
r, err := checkWhereArray(elemv, kv, mv, path, op)
if err != nil {
return nil, err
}
switch rr := reflect.ValueOf(r); rr.Kind() {
case reflect.Slice:
if rr.Len() > 0 {
rv.SetMapIndex(k, elemv)
}
}
case reflect.Interface:
elemvv, isNil := indirect(elemv)
if isNil {
continue
}
switch elemvv.Kind() {
case reflect.Array, reflect.Slice:
r, err := checkWhereArray(elemvv, kv, mv, path, op)
if err != nil {
return nil, err
}
switch rr := reflect.ValueOf(r); rr.Kind() {
case reflect.Slice:
if rr.Len() > 0 {
rv.SetMapIndex(k, elemv)
}
}
}
}
}
return rv.Interface(), nil
}
// where returns a filtered subset of a given data type.
func where(seq, key interface{}, args ...interface{}) (interface{}, error) {
seqv, isNil := indirect(reflect.ValueOf(seq))
if isNil {
return nil, errors.New("can't iterate over a nil value of type " + reflect.ValueOf(seq).Type().String())
}
mv, op, err := parseWhereArgs(args...)
if err != nil {
return nil, err
}
var path []string
kv := reflect.ValueOf(key)
if kv.Kind() == reflect.String {
path = strings.Split(strings.Trim(kv.String(), "."), ".")
}
switch seqv.Kind() {
case reflect.Array, reflect.Slice:
return checkWhereArray(seqv, kv, mv, path, op)
case reflect.Map:
return checkWhereMap(seqv, kv, mv, path, op)
default:
return nil, fmt.Errorf("can't iterate over %v", seq)
}
}
// apply takes a map, array, or slice and returns a new slice with the function fname applied over it.
func (t *templateFuncster) 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")
}
fn, found := t.funcMap[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++ {
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)
}
// delimit takes a given sequence and returns a delimited HTML string.
// If last is passed to the function, it will be used as the final delimiter.
func delimit(seq, delimiter interface{}, last ...interface{}) (template.HTML, error) {
d, err := cast.ToStringE(delimiter)
if err != nil {
return "", err
}
var dLast *string
if len(last) > 0 {
l := last[0]
dStr, err := cast.ToStringE(l)
if err != nil {
dLast = nil
}
dLast = &dStr
}
seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
if isNil {
return "", errors.New("can't iterate over a nil value")
}
var str string
switch seqv.Kind() {
case reflect.Map:
sortSeq, err := sortSeq(seq)
if err != nil {
return "", err
}
seqv = reflect.ValueOf(sortSeq)
fallthrough
case reflect.Array, reflect.Slice, reflect.String:
for i := 0; i < seqv.Len(); i++ {
val := seqv.Index(i).Interface()
valStr, err := cast.ToStringE(val)
if err != nil {
continue
}
switch {
case i == seqv.Len()-2 && dLast != nil:
str += valStr + *dLast
case i == seqv.Len()-1:
str += valStr
default:
str += valStr + d
}
}
default:
return "", fmt.Errorf("can't iterate over %v", seq)
}
return template.HTML(str), nil
}
// sortSeq returns a sorted sequence.
func sortSeq(seq interface{}, args ...interface{}) (interface{}, error) {
if seq == nil {
return nil, errors.New("sequence must be provided")
}
seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv)
if isNil {
return nil, errors.New("can't iterate over a nil value")
}
switch seqv.Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
// ok
default:
return nil, errors.New("can't sort " + reflect.ValueOf(seq).Type().String())
}
// Create a list of pairs that will be used to do the sort
p := pairList{SortAsc: true, SliceType: reflect.SliceOf(seqv.Type().Elem())}
p.Pairs = make([]pair, seqv.Len())
var sortByField string
for i, l := range args {
dStr, err := cast.ToStringE(l)
switch {
case i == 0 && err != nil:
sortByField = ""
case i == 0 && err == nil:
sortByField = dStr
case i == 1 && err == nil && dStr == "desc":
p.SortAsc = false
case i == 1:
p.SortAsc = true
}
}
path := strings.Split(strings.Trim(sortByField, "."), ".")
switch seqv.Kind() {
case reflect.Array, reflect.Slice:
for i := 0; i < seqv.Len(); i++ {
p.Pairs[i].Value = seqv.Index(i)
if sortByField == "" || sortByField == "value" {
p.Pairs[i].Key = p.Pairs[i].Value
} else {
v := p.Pairs[i].Value
var err error
for _, elemName := range path {
v, err = evaluateSubElem(v, elemName)
if err != nil {
return nil, err
}
}
p.Pairs[i].Key = v
}
}
case reflect.Map:
keys := seqv.MapKeys()
for i := 0; i < seqv.Len(); i++ {
p.Pairs[i].Value = seqv.MapIndex(keys[i])
if sortByField == "" {
p.Pairs[i].Key = keys[i]
} else if sortByField == "value" {
p.Pairs[i].Key = p.Pairs[i].Value
} else {
v := p.Pairs[i].Value
var err error
for _, elemName := range path {
v, err = evaluateSubElem(v, elemName)
if err != nil {
return nil, err
}
}
p.Pairs[i].Key = v
}
}
}
return p.sort(), nil
}
// Credit for pair sorting method goes to Andrew Gerrand
// https://groups.google.com/forum/#!topic/golang-nuts/FT7cjmcL7gw
// A data structure to hold a key/value pair.
type pair struct {
Key reflect.Value
Value reflect.Value
}
// A slice of pairs that implements sort.Interface to sort by Value.
type pairList struct {
Pairs []pair
SortAsc bool
SliceType reflect.Type
}
func (p pairList) Swap(i, j int) { p.Pairs[i], p.Pairs[j] = p.Pairs[j], p.Pairs[i] }
func (p pairList) Len() int { return len(p.Pairs) }
func (p pairList) Less(i, j int) bool {
iv := p.Pairs[i].Key
jv := p.Pairs[j].Key
if iv.IsValid() {
if jv.IsValid() {
// can only call Interface() on valid reflect Values
return lt(iv.Interface(), jv.Interface())
}
// if j is invalid, test i against i's zero value
return lt(iv.Interface(), reflect.Zero(iv.Type()))
}
if jv.IsValid() {
// if i is invalid, test j against j's zero value
return lt(reflect.Zero(jv.Type()), jv.Interface())
}
return false
}
// sorts a pairList and returns a slice of sorted values
func (p pairList) sort() interface{} {
if p.SortAsc {
sort.Sort(p)
} else {
sort.Sort(sort.Reverse(p))
}
sorted := reflect.MakeSlice(p.SliceType, len(p.Pairs), len(p.Pairs))
for i, v := range p.Pairs {
sorted.Index(i).Set(v.Value)
}
return sorted.Interface()
}
// isSet returns whether a given array, channel, slice, or map has a key
// defined.
func isSet(a interface{}, key interface{}) bool {
av := reflect.ValueOf(a)
kv := reflect.ValueOf(key)
switch av.Kind() {
case reflect.Array, reflect.Chan, reflect.Slice:
if int64(av.Len()) > kv.Int() {
return true
}
case reflect.Map:
if kv.Type() == av.Type().Key() {
return av.MapIndex(kv).IsValid()
}
}
return false
}
// returnWhenSet returns a given value if it set. Otherwise, it returns an
// empty string.
func returnWhenSet(a, k interface{}) interface{} {
av, isNil := indirect(reflect.ValueOf(a))
if isNil {
return ""
}
var avv reflect.Value
switch av.Kind() {
case reflect.Array, reflect.Slice:
index, ok := k.(int)
if ok && av.Len() > index {
avv = av.Index(index)
}
case reflect.Map:
kv := reflect.ValueOf(k)
if kv.Type().AssignableTo(av.Type().Key()) {
avv = av.MapIndex(kv)
}
}
avv, isNil = indirect(avv)
if isNil {
return ""
}
if avv.IsValid() {
switch avv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return avv.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return avv.Uint()
case reflect.Float32, reflect.Float64:
return avv.Float()
case reflect.String:
return avv.String()
}
}
return ""
}
// highlight returns an HTML string with syntax highlighting applied.
func (t *templateFuncster) highlight(in interface{}, lang, opts string) (template.HTML, error) {
str, err := cast.ToStringE(in)
if err != nil {
return "", err
}
return template.HTML(helpers.Highlight(t.Cfg, html.UnescapeString(str), lang, opts)), nil
}
var markdownTrimPrefix = []byte("<p>")
var markdownTrimSuffix = []byte("</p>\n")
// markdownify renders a given string from Markdown to HTML.
func (t *templateFuncster) markdownify(in interface{}) (template.HTML, error) {
text, err := cast.ToStringE(in)
if err != nil {
return "", err
}
m := t.ContentSpec.RenderBytes(&helpers.RenderingContext{
Cfg: t.Cfg,
Content: []byte(text), PageFmt: "markdown",
Config: t.ContentSpec.NewBlackfriday()})
m = bytes.TrimPrefix(m, markdownTrimPrefix)
m = bytes.TrimSuffix(m, markdownTrimSuffix)
return template.HTML(m), nil
}
// jsonify encodes a given object to JSON.
func jsonify(v interface{}) (template.HTML, error) {
b, err := json.Marshal(v)
if err != nil {
return "", err
}
return template.HTML(b), nil
}
// emojify "emojifies" the given string.
//
// See http://www.emoji-cheat-sheet.com/
func emojify(in interface{}) (template.HTML, error) {
str, err := cast.ToStringE(in)
if err != nil {
return "", err
}
return template.HTML(helpers.Emojify([]byte(str))), nil
}
// plainify strips any HTML and returns the plain text version.
func plainify(in interface{}) (string, error) {
s, err := cast.ToStringE(in)
if err != nil {
return "", err
}
return helpers.StripHTML(s), nil
}
type reflinker interface {
Ref(refs ...string) (string, error)
RelRef(refs ...string) (string, error)
}
// ref returns the absolute URL path to a given content item.
func ref(in interface{}, refs ...string) (template.HTML, error) {
p, ok := in.(reflinker)
if !ok {
return "", errors.New("invalid Page received in ref")
}
s, err := p.Ref(refs...)
return template.HTML(s), err
}
// relRef returns the relative URL path to a given content item.
func relRef(in interface{}, refs ...string) (template.HTML, error) {
p, ok := in.(reflinker)
if !ok {
return "", errors.New("invalid Page received in relref")
}
s, err := p.RelRef(refs...)
return template.HTML(s), err
}
// chomp removes trailing newline characters from a string.
func chomp(text interface{}) (template.HTML, error) {
s, err := cast.ToStringE(text)
if err != nil {
return "", err
}
return template.HTML(strings.TrimRight(s, "\r\n")), nil
}
// lower returns a copy of the input s with all Unicode letters mapped to their
// lower case.
func lower(s interface{}) (string, error) {
ss, err := cast.ToStringE(s)
if err != nil {
return "", err
}
return strings.ToLower(ss), nil
}
// title returns a copy of the input s with all Unicode letters that begin words
// mapped to their title case.
func title(s interface{}) (string, error) {
ss, err := cast.ToStringE(s)
if err != nil {
return "", err
}
return strings.Title(ss), nil
}
// upper returns a copy of the input s with all Unicode letters mapped to their
// upper case.
func upper(s interface{}) (string, error) {
ss, err := cast.ToStringE(s)
if err != nil {
return "", err
}
return strings.ToUpper(ss), nil
}
// trim leading/trailing characters defined by b from a
func trim(a interface{}, b string) (string, error) {
aStr, err := cast.ToStringE(a)
if err != nil {
return "", err
}
return strings.Trim(aStr, b), nil
}
// replace all occurrences of b with c in a
func replace(a, b, c interface{}) (string, error) {
aStr, err := cast.ToStringE(a)
if err != nil {
return "", err
}
bStr, err := cast.ToStringE(b)
if err != nil {
return "", err
}
cStr, err := cast.ToStringE(c)
if err != nil {
return "", err
}
return strings.Replace(aStr, bStr, cStr, -1), nil
}
// partialCache represents a cache of partials protected by a mutex.
type partialCache struct {
sync.RWMutex
p map[string]interface{}
}
// Get retrieves partial output from the cache based upon the partial name.
// If the partial is not found in the cache, the partial is rendered and added
// to the cache.
func (t *templateFuncster) Get(key, name string, context interface{}) (p interface{}, err error) {
var ok bool
t.cachedPartials.RLock()
p, ok = t.cachedPartials.p[key]
t.cachedPartials.RUnlock()
if ok {
return
}
t.cachedPartials.Lock()
if p, ok = t.cachedPartials.p[key]; !ok {
t.cachedPartials.Unlock()
p, err = t.partial(name, context)
t.cachedPartials.Lock()
t.cachedPartials.p[key] = p
}
t.cachedPartials.Unlock()
return
}
// partialCached executes and caches partial templates. An optional variant
// string parameter (a string slice actually, but be only use a variadic
// argument to make it optional) can be passed so that a given partial can have
// multiple uses. The cache is created with name+variant as the key.
func (t *templateFuncster) partialCached(name string, context interface{}, variant ...string) (interface{}, error) {
key := name
if len(variant) > 0 {
for i := 0; i < len(variant); i++ {
key += variant[i]
}
}
return t.Get(key, name, context)
}
// regexpCache represents a cache of regexp objects protected by a mutex.
type regexpCache struct {
mu sync.RWMutex
re map[string]*regexp.Regexp
}
// Get retrieves a regexp object from the cache based upon the pattern.
// If the pattern is not found in the cache, create one
func (rc *regexpCache) Get(pattern string) (re *regexp.Regexp, err error) {
var ok bool
if re, ok = rc.get(pattern); !ok {
re, err = regexp.Compile(pattern)
if err != nil {
return nil, err
}
rc.set(pattern, re)
}
return re, nil
}
func (rc *regexpCache) get(key string) (re *regexp.Regexp, ok bool) {
rc.mu.RLock()
re, ok = rc.re[key]
rc.mu.RUnlock()
return
}
func (rc *regexpCache) set(key string, re *regexp.Regexp) {
rc.mu.Lock()
rc.re[key] = re
rc.mu.Unlock()
}
var reCache = regexpCache{re: make(map[string]*regexp.Regexp)}
// replaceRE exposes a regular expression replacement function to the templates.
func replaceRE(pattern, repl, src interface{}) (_ string, err error) {
patternStr, err := cast.ToStringE(pattern)
if err != nil {
return
}
replStr, err := cast.ToStringE(repl)
if err != nil {
return
}
srcStr, err := cast.ToStringE(src)
if err != nil {
return
}
re, err := reCache.Get(patternStr)
if err != nil {
return "", err
}
return re.ReplaceAllString(srcStr, replStr), nil
}
// asTime converts the textual representation of the datetime string into
// a time.Time interface.
func asTime(v interface{}) (interface{}, error) {
t, err := cast.ToTimeE(v)
if err != nil {
return nil, err
}
return t, nil
}
// dateFormat converts the textual representation of the datetime string into
// the other form or returns it of the time.Time value. These are formatted
// with the layout string
func dateFormat(layout string, v interface{}) (string, error) {
t, err := cast.ToTimeE(v)
if err != nil {
return "", err
}
return t.Format(layout), nil
}
// dfault checks whether a given value is set and returns a default value if it
// is not. "Set" in this context means non-zero for numeric types and times;
// non-zero length for strings, arrays, slices, and maps;
// any boolean or struct value; or non-nil for any other types.
func dfault(dflt interface{}, given ...interface{}) (interface{}, error) {
// given is variadic because the following construct will not pass a piped
// argument when the key is missing: {{ index . "key" | default "foo" }}
// The Go template will complain that we got 1 argument when we expectd 2.
if len(given) == 0 {
return dflt, nil
}
if len(given) != 1 {
return nil, fmt.Errorf("wrong number of args for default: want 2 got %d", len(given)+1)
}
g := reflect.ValueOf(given[0])
if !g.IsValid() {
return dflt, nil
}
set := false
switch g.Kind() {
case reflect.Bool:
set = true
case reflect.String, reflect.Array, reflect.Slice, reflect.Map:
set = g.Len() != 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
set = g.Int() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
set = g.Uint() != 0
case reflect.Float32, reflect.Float64:
set = g.Float() != 0
case reflect.Complex64, reflect.Complex128:
set = g.Complex() != 0
case reflect.Struct:
switch actual := given[0].(type) {
case time.Time:
set = !actual.IsZero()
default:
set = true
}
default:
set = !g.IsNil()
}
if set {
return given[0], nil
}
return dflt, nil
}
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
//
// Copied from Go stdlib src/text/template/exec.go.
func canBeNil(typ reflect.Type) bool {
switch typ.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return true
}
return false
}
// prepareArg checks if value can be used as an argument of type argType, and
// converts an invalid value to appropriate zero if possible.
//
// Copied from Go stdlib src/text/template/funcs.go.
func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) {
if !value.IsValid() {
if !canBeNil(argType) {
return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType)
}
value = reflect.Zero(argType)
}
if !value.Type().AssignableTo(argType) {
return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
}
return value, nil
}
// index returns the result of indexing its first argument by the following
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
// indexed item must be a map, slice, or array.
//
// Copied from Go stdlib src/text/template/funcs.go.
// Can hopefully be removed in Go 1.7, see https://github.com/golang/go/issues/14751
func index(item interface{}, indices ...interface{}) (interface{}, error) {
v := reflect.ValueOf(item)
if !v.IsValid() {
return nil, errors.New("index of untyped nil")
}
for _, i := range indices {
index := reflect.ValueOf(i)
var isNil bool
if v, isNil = indirect(v); isNil {
return nil, errors.New("index of nil pointer")
}
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
var x int64
switch index.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x = index.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
x = int64(index.Uint())
case reflect.Invalid:
return nil, errors.New("cannot index slice/array with nil")
default:
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
}
if x < 0 || x >= int64(v.Len()) {
// We deviate from stdlib here. Don't return an error if the
// index is out of range.
return nil, nil
}
v = v.Index(int(x))
case reflect.Map:
index, err := prepareArg(index, v.Type().Key())
if err != nil {
return nil, err
}
if x := v.MapIndex(index); x.IsValid() {
v = x
} else {
v = reflect.Zero(v.Type().Elem())
}
case reflect.Invalid:
// the loop holds invariant: v.IsValid()
panic("unreachable")
default:
return nil, fmt.Errorf("can't index item of type %s", v.Type())
}
}
return v.Interface(), nil
}
// readFile reads the file named by filename relative to the given basepath
// and returns the contents as a string.
// There is a upper size limit set at 1 megabytes.
func readFile(fs *afero.BasePathFs, filename string) (string, error) {
if filename == "" {
return "", errors.New("readFile needs a filename")
}
if info, err := fs.Stat(filename); err == nil {
if info.Size() > 1000000 {
return "", fmt.Errorf("File %q is too big", filename)
}
} else {
return "", err
}
b, err := afero.ReadFile(fs, filename)
if err != nil {
return "", err
}
return string(b), nil
}
// readFileFromWorkingDir reads the file named by filename relative to the
// configured WorkingDir.
// It returns the contents as a string.
// There is a upper size limit set at 1 megabytes.
func (t *templateFuncster) readFileFromWorkingDir(i interface{}) (string, error) {
s, err := cast.ToStringE(i)
if err != nil {
return "", err
}
return readFile(t.Fs.WorkingDir, s)
}
// readDirFromWorkingDir listst the directory content relative to the
// configured WorkingDir.
func (t *templateFuncster) readDirFromWorkingDir(i interface{}) ([]os.FileInfo, error) {
path, err := cast.ToStringE(i)
if err != nil {
return nil, err
}
list, err := afero.ReadDir(t.Fs.WorkingDir, path)
if err != nil {
return nil, fmt.Errorf("Failed to read Directory %s with error message %s", path, err)
}
return list, nil
}
// safeHTMLAttr returns a given string as html/template HTMLAttr content.
func safeHTMLAttr(a interface{}) (template.HTMLAttr, error) {
s, err := cast.ToStringE(a)
return template.HTMLAttr(s), err
}
// safeCSS returns a given string as html/template CSS content.
func safeCSS(a interface{}) (template.CSS, error) {
s, err := cast.ToStringE(a)
return template.CSS(s), err
}
// safeURL returns a given string as html/template URL content.
func safeURL(a interface{}) (template.URL, error) {
s, err := cast.ToStringE(a)
return template.URL(s), err
}
// safeHTML returns a given string as html/template HTML content.
func safeHTML(a interface{}) (template.HTML, error) {
s, err := cast.ToStringE(a)
return template.HTML(s), err
}
// safeJS returns the given string as a html/template JS content.
func safeJS(a interface{}) (template.JS, error) {
s, err := cast.ToStringE(a)
return template.JS(s), err
}
// mod returns a % b.
func mod(a, b interface{}) (int64, error) {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
var ai, bi int64
switch av.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
ai = av.Int()
default:
return 0, errors.New("Modulo operator can't be used with non integer value")
}
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
bi = bv.Int()
default:
return 0, errors.New("Modulo operator can't be used with non integer value")
}
if bi == 0 {
return 0, errors.New("The number can't be divided by zero at modulo operation")
}
return ai % bi, nil
}
// modBool returns the boolean of a % b. If a % b == 0, return true.
func modBool(a, b interface{}) (bool, error) {
res, err := mod(a, b)
if err != nil {
return false, err
}
return res == int64(0), nil
}
// base64Decode returns the base64 decoding of the given content.
func base64Decode(content interface{}) (string, error) {
conv, err := cast.ToStringE(content)
if err != nil {
return "", err
}
dec, err := base64.StdEncoding.DecodeString(conv)
return string(dec), err
}
// base64Encode returns the base64 encoding of the given content.
func base64Encode(content interface{}) (string, error) {
conv, err := cast.ToStringE(content)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString([]byte(conv)), nil
}
// countWords returns the approximate word count of the given content.
func countWords(content interface{}) (int, error) {
conv, err := cast.ToStringE(content)
if err != nil {
return 0, fmt.Errorf("Failed to convert content to string: %s", err.Error())
}
counter := 0
for _, word := range strings.Fields(helpers.StripHTML(conv)) {
runeCount := utf8.RuneCountInString(word)
if len(word) == runeCount {
counter++
} else {
counter += runeCount
}
}
return counter, nil
}
// countRunes returns the approximate rune count of the given content.
func countRunes(content interface{}) (int, error) {
conv, err := cast.ToStringE(content)
if err != nil {
return 0, fmt.Errorf("Failed to convert content to string: %s", err.Error())
}
counter := 0
for _, r := range helpers.StripHTML(conv) {
if !helpers.IsWhitespace(r) {
counter++
}
}
return counter, nil
}
// humanize returns the humanized form of a single parameter.
// If the parameter is either an integer or a string containing an integer
// value, the behavior is to add the appropriate ordinal.
// Example: "my-first-post" -> "My first post"
// Example: "103" -> "103rd"
// Example: 52 -> "52nd"
func humanize(in interface{}) (string, error) {
word, err := cast.ToStringE(in)
if err != nil {
return "", err
}
if word == "" {
return "", nil
}
_, ok := in.(int) // original param was literal int value
_, err = strconv.Atoi(word) // original param was string containing an int value
if ok || err == nil {
return inflect.Ordinalize(word), nil
}
return inflect.Humanize(word), nil
}
// pluralize returns the plural form of a single word.
func pluralize(in interface{}) (string, error) {
word, err := cast.ToStringE(in)
if err != nil {
return "", err
}
return inflect.Pluralize(word), nil
}
// singularize returns the singular form of a single word.
func singularize(in interface{}) (string, error) {
word, err := cast.ToStringE(in)
if err != nil {
return "", err
}
return inflect.Singularize(word), nil
}
// md5 hashes the given input and returns its MD5 checksum
func md5(in interface{}) (string, error) {
conv, err := cast.ToStringE(in)
if err != nil {
return "", err
}
hash := _md5.Sum([]byte(conv))
return hex.EncodeToString(hash[:]), nil
}
// sha1 hashes the given input and returns its SHA1 checksum
func sha1(in interface{}) (string, error) {
conv, err := cast.ToStringE(in)
if err != nil {
return "", err
}
hash := _sha1.Sum([]byte(conv))
return hex.EncodeToString(hash[:]), nil
}
// sha256 hashes the given input and returns its SHA256 checksum
func sha256(in interface{}) (string, error) {
conv, err := cast.ToStringE(in)
if err != nil {
return "", err
}
hash := _sha256.Sum256([]byte(conv))
return hex.EncodeToString(hash[:]), nil
}
// querify encodes the given parameters “URL encoded” form ("bar=baz&foo=quux") sorted by key.
func querify(params ...interface{}) (string, error) {
qs := url.Values{}
vals, err := dictionary(params...)
if err != nil {
return "", errors.New("querify keys must be strings")
}
for name, value := range vals {
qs.Add(name, fmt.Sprintf("%v", value))
}
return qs.Encode(), nil
}
func htmlEscape(in interface{}) (string, error) {
conv, err := cast.ToStringE(in)
if err != nil {
return "", err
}
return html.EscapeString(conv), nil
}
func htmlUnescape(in interface{}) (string, error) {
conv, err := cast.ToStringE(in)
if err != nil {
return "", err
}
return html.UnescapeString(conv), nil
}
func (t *templateFuncster) absURL(a interface{}) (template.HTML, error) {
s, err := cast.ToStringE(a)
if err != nil {
return "", nil
}
return template.HTML(t.PathSpec.AbsURL(s, false)), nil
}
func (t *templateFuncster) relURL(a interface{}) (template.HTML, error) {
s, err := cast.ToStringE(a)
if err != nil {
return "", nil
}
return template.HTML(t.PathSpec.RelURL(s, false)), nil
}
// getenv retrieves the value of the environment variable named by the key.
// It returns the value, which will be empty if the variable is not present.
func getenv(key interface{}) (string, error) {
skey, err := cast.ToStringE(key)
if err != nil {
return "", nil
}
return os.Getenv(skey), nil
}
func (t *templateFuncster) initFuncMap() {
funcMap := template.FuncMap{
"absURL": t.absURL,
"absLangURL": func(i interface{}) (template.HTML, error) {
s, err := cast.ToStringE(i)
if err != nil {
return "", err
}
return template.HTML(t.PathSpec.AbsURL(s, true)), nil
},
"add": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '+') },
"after": after,
"apply": t.apply,
"base64Decode": base64Decode,
"base64Encode": base64Encode,
"chomp": chomp,
"countrunes": countRunes,
"countwords": countWords,
"default": dfault,
"dateFormat": dateFormat,
"delimit": delimit,
"dict": dictionary,
"div": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '/') },
"echoParam": returnWhenSet,
"emojify": emojify,
"eq": eq,
"findRE": findRE,
"first": first,
"ge": ge,
"getCSV": t.getCSV,
"getJSON": t.getJSON,
"getenv": getenv,
"gt": gt,
"hasPrefix": hasPrefix,
"highlight": t.highlight,
"htmlEscape": htmlEscape,
"htmlUnescape": htmlUnescape,
"humanize": humanize,
"imageConfig": t.image.config,
"in": in,
"index": index,
"int": func(v interface{}) (int, error) { return cast.ToIntE(v) },
"intersect": intersect,
"isSet": isSet,
"isset": isSet,
"jsonify": jsonify,
"last": last,
"le": le,
"lower": lower,
"lt": lt,
"markdownify": t.markdownify,
"md5": md5,
"mod": mod,
"modBool": modBool,
"mul": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '*') },
"ne": ne,
"now": func() time.Time { return time.Now() },
"partial": t.partial,
"partialCached": t.partialCached,
"plainify": plainify,
"pluralize": pluralize,
"print": fmt.Sprint,
"printf": fmt.Sprintf,
"println": fmt.Sprintln,
"querify": querify,
"readDir": t.readDirFromWorkingDir,
"readFile": t.readFileFromWorkingDir,
"ref": ref,
"relURL": t.relURL,
"relLangURL": func(i interface{}) (template.HTML, error) {
s, err := cast.ToStringE(i)
if err != nil {
return "", err
}
return template.HTML(t.PathSpec.RelURL(s, true)), nil
},
"relref": relRef,
"replace": replace,
"replaceRE": replaceRE,
"safeCSS": safeCSS,
"safeHTML": safeHTML,
"safeHTMLAttr": safeHTMLAttr,
"safeJS": safeJS,
"safeURL": safeURL,
"sanitizeURL": helpers.SanitizeURL,
"sanitizeurl": helpers.SanitizeURL,
"seq": helpers.Seq,
"sha1": sha1,
"sha256": sha256,
"shuffle": shuffle,
"singularize": singularize,
"slice": slice,
"slicestr": slicestr,
"sort": sortSeq,
"split": split,
"string": func(v interface{}) (string, error) { return cast.ToStringE(v) },
"sub": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '-') },
"substr": substr,
"title": title,
"time": asTime,
"trim": trim,
"truncate": truncate,
"union": union,
"upper": upper,
"urlize": t.PathSpec.URLize,
"where": where,
"i18n": t.Translate,
"T": t.Translate,
}
t.funcMap = funcMap
t.Tmpl.(*templateHandler).setFuncs(funcMap)
}