shithub: hugo

Download patch

ref: c507e2717df7dd4b870478033bc5ece0b039a8c4
parent: 93ca7c9e958e34469a337e4efcc7c75774ec50fd
author: Bjørn Erik Pedersen <[email protected]>
date: Fri Feb 17 08:30:50 EST 2017

tpl: Refactor package

Now:

* The template API lives in /tpl
* The rest lives in /tpl/tplimpl

This is bound te be more improved in the future.

Updates #2701

--- a/deps/deps.go
+++ b/deps/deps.go
@@ -8,7 +8,7 @@
 	"github.com/spf13/hugo/config"
 	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/hugofs"
-	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/hugo/tpl"
 	jww "github.com/spf13/jwalterweatherman"
 )
 
@@ -20,7 +20,7 @@
 	Log *jww.Notepad `json:"-"`
 
 	// The templates to use.
-	Tmpl tplapi.Template `json:"-"`
+	Tmpl tpl.Template `json:"-"`
 
 	// The file systems to use.
 	Fs *hugofs.Fs `json:"-"`
@@ -40,7 +40,7 @@
 	Language *helpers.Language
 
 	templateProvider ResourceProvider
-	WithTemplate     func(templ tplapi.Template) error `json:"-"`
+	WithTemplate     func(templ tpl.Template) error `json:"-"`
 
 	translationProvider ResourceProvider
 }
@@ -147,7 +147,7 @@
 
 	// Template handling.
 	TemplateProvider ResourceProvider
-	WithTemplate     func(templ tplapi.Template) error
+	WithTemplate     func(templ tpl.Template) error
 
 	// i18n handling.
 	TranslationProvider ResourceProvider
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -25,7 +25,7 @@
 	"github.com/spf13/hugo/deps"
 
 	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/hugo/tpl"
 	"github.com/stretchr/testify/require"
 )
 
@@ -335,7 +335,7 @@
 			th      = testHelper{cfg}
 		)
 
-		withTemplate := func(templ tplapi.Template) error {
+		withTemplate := func(templ tpl.Template) error {
 			templ.Funcs(tweetFuncMap)
 			return nil
 		}
@@ -390,7 +390,7 @@
 om:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
 			`(?s)<blockquote class="instagram-media" data-instgrm-version="7" style=" background:#FFF; border:0; .*<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
 		},
-den; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
+den; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
 ow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
 			`(?s)<blockquote class="instagram-media" data-instgrm-version="7" style=" background:#FFF; border:0; .*<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
 		},
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -24,7 +24,7 @@
 
 	"github.com/spf13/hugo/i18n"
 	"github.com/spf13/hugo/tpl"
-	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/hugo/tpl/tplimpl"
 )
 
 // HugoSites represents the sites to build. Each site represents a language.
@@ -72,7 +72,7 @@
 
 func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
 	if cfg.TemplateProvider == nil {
-		cfg.TemplateProvider = tpl.DefaultTemplateProvider
+		cfg.TemplateProvider = tplimpl.DefaultTemplateProvider
 	}
 
 	if cfg.TranslationProvider == nil {
@@ -121,8 +121,8 @@
 	return newHugoSites(cfg, sites...)
 }
 
-func (s *Site) withSiteTemplates(withTemplates ...func(templ tplapi.Template) error) func(templ tplapi.Template) error {
-	return func(templ tplapi.Template) error {
+func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.Template) error) func(templ tpl.Template) error {
+	return func(templ tpl.Template) error {
 		templ.LoadTemplates(s.absLayoutDir())
 		if s.hasTheme() {
 			templ.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")
@@ -191,7 +191,7 @@
 		h.Sites[i] = s.reset()
 	}
 
-	tpl.ResetCaches()
+	tplimpl.ResetCaches()
 }
 
 func (h *HugoSites) createSitesFromConfig() error {
@@ -553,7 +553,7 @@
 	return h.Sites[0].AllPages
 }
 
-func handleShortcodes(p *Page, t tplapi.Template, rawContentCopy []byte) ([]byte, error) {
+func handleShortcodes(p *Page, t tpl.Template, rawContentCopy []byte) ([]byte, error) {
 	if len(p.contentShortCodes) > 0 {
 		p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.contentShortCodes), p.BaseFileName())
 		shortcodes, err := executeShortcodeFuncMap(p.contentShortCodes)
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -26,7 +26,7 @@
 
 	bp "github.com/spf13/hugo/bufferpool"
 	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/hugo/tpl"
 )
 
 // ShortcodeWithPage is the "." context in a shortcode template.
@@ -541,7 +541,7 @@
 	return source, nil
 }
 
-func getShortcodeTemplate(name string, t tplapi.Template) *template.Template {
+func getShortcodeTemplate(name string, t tpl.Template) *template.Template {
 	if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
 		return x
 	}
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -25,12 +25,12 @@
 	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/source"
-	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/hugo/tpl"
 	"github.com/stretchr/testify/require"
 )
 
 // TODO(bep) remove
-func pageFromString(in, filename string, withTemplate ...func(templ tplapi.Template) error) (*Page, error) {
+func pageFromString(in, filename string, withTemplate ...func(templ tpl.Template) error) (*Page, error) {
 	s := newTestSite(nil)
 	if len(withTemplate) > 0 {
 		// Have to create a new site
@@ -47,11 +47,11 @@
 	return s.NewPageFrom(strings.NewReader(in), filename)
 }
 
-func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error) {
+func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error) {
 	CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
 }
 
-func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error, expectError bool) {
+func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error, expectError bool) {
 
 	cfg, fs := newTestCfg()
 
@@ -100,7 +100,7 @@
 // Issue #929
 func TestHyphenatedSC(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
 		return nil
 	}
@@ -111,7 +111,7 @@
 // Issue #1753
 func TestNoTrailingNewline(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
 		return nil
 	}
@@ -121,7 +121,7 @@
 
 func TestPositionalParamSC(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
 		return nil
 	}
@@ -135,7 +135,7 @@
 
 func TestPositionalParamIndexOutOfBounds(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
 		return nil
 	}
@@ -146,7 +146,7 @@
 
 func TestNamedParamSC(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
 		return nil
 	}
@@ -161,7 +161,7 @@
 // Issue #2294
 func TestNestedNamedMissingParam(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("acc.html", `<div class="acc">{{ .Inner }}</div>`)
 		tem.AddInternalShortcode("div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
 		tem.AddInternalShortcode("div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
@@ -174,7 +174,7 @@
 
 func TestIsNamedParamsSC(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("byposition.html", `<div id="{{ .Get 0 }}">`)
 		tem.AddInternalShortcode("byname.html", `<div id="{{ .Get "id" }}">`)
 		tem.AddInternalShortcode("ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
@@ -190,7 +190,7 @@
 
 func TestInnerSC(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
 		return nil
 	}
@@ -201,7 +201,7 @@
 
 func TestInnerSCWithMarkdown(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
 		return nil
 	}
@@ -215,7 +215,7 @@
 
 func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
 		return nil
 	}
@@ -246,7 +246,7 @@
 
 func TestNestedSC(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
 		tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
 		return nil
@@ -258,7 +258,7 @@
 
 func TestNestedComplexSC(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`)
 		tem.AddInternalShortcode("column.html", `-col-{{.Inner    }}-colStop-`)
 		tem.AddInternalShortcode("aside.html", `-aside-{{    .Inner  }}-asideStop-`)
@@ -274,7 +274,7 @@
 
 func TestParentShortcode(t *testing.T) {
 	t.Parallel()
-	wt := func(tem tplapi.Template) error {
+	wt := func(tem tpl.Template) error {
 		tem.AddInternalShortcode("r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
 		tem.AddInternalShortcode("r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
 		tem.AddInternalShortcode("r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
@@ -342,7 +342,7 @@
 			fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
 	} {
 
-		p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
+		p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
 			templ.AddInternalShortcode("tag.html", `tag`)
 			templ.AddInternalShortcode("sc1.html", `sc1`)
 			templ.AddInternalShortcode("sc2.html", `sc2`)
@@ -514,7 +514,7 @@
 		sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)}
 	}
 
-	addTemplates := func(templ tplapi.Template) error {
+	addTemplates := func(templ tpl.Template) error {
 		templ.AddTemplate("_default/single.html", "{{.Content}}")
 
 		templ.AddInternalShortcode("b.html", `b`)
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -40,7 +40,7 @@
 	"github.com/spf13/hugo/parser"
 	"github.com/spf13/hugo/source"
 	"github.com/spf13/hugo/target"
-	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/hugo/tpl"
 	"github.com/spf13/hugo/transform"
 	"github.com/spf13/nitro"
 	"github.com/spf13/viper"
@@ -149,7 +149,7 @@
 // NewSiteDefaultLang creates a new site in the default language.
 // The site will have a template system loaded and ready to use.
 // Note: This is mainly used in single site tests.
-func NewSiteDefaultLang(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
+func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) (*Site, error) {
 	v := viper.New()
 	loadDefaultSettingsFor(v)
 	return newSiteForLang(helpers.NewDefaultLanguage(v), withTemplate...)
@@ -158,7 +158,7 @@
 // NewEnglishSite creates a new site in English language.
 // The site will have a template system loaded and ready to use.
 // Note: This is mainly used in single site tests.
-func NewEnglishSite(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
+func NewEnglishSite(withTemplate ...func(templ tpl.Template) error) (*Site, error) {
 	v := viper.New()
 	loadDefaultSettingsFor(v)
 	return newSiteForLang(helpers.NewLanguage("en", v), withTemplate...)
@@ -165,8 +165,8 @@
 }
 
 // newSiteForLang creates a new site in the given language.
-func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
-	withTemplates := func(templ tplapi.Template) error {
+func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tpl.Template) error) (*Site, error) {
+	withTemplates := func(templ tpl.Template) error {
 		for _, wt := range withTemplate {
 			if err := wt(templ); err != nil {
 				return err
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -19,7 +19,7 @@
 	"reflect"
 
 	"github.com/spf13/hugo/deps"
-	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/hugo/tpl"
 )
 
 const sitemapTemplate = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@@ -48,7 +48,7 @@
 	depsCfg := deps.DepsCfg{Fs: fs, Cfg: cfg}
 
 	if !internal {
-		depsCfg.WithTemplate = func(templ tplapi.Template) error {
+		depsCfg.WithTemplate = func(templ tpl.Template) error {
 			templ.AddTemplate("sitemap.xml", sitemapTemplate)
 			return nil
 		}
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -7,7 +7,7 @@
 	"github.com/spf13/hugo/deps"
 	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/source"
-	"github.com/spf13/hugo/tplapi"
+	"github.com/spf13/hugo/tpl"
 	"github.com/spf13/viper"
 
 	"io/ioutil"
@@ -66,9 +66,9 @@
 	return jww.NewNotepad(jww.LevelDebug, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
 }
 
-func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tplapi.Template) error {
+func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.Template) error {
 
-	return func(templ tplapi.Template) error {
+	return func(templ tpl.Template) error {
 		for i := 0; i < len(additionalTemplates); i += 2 {
 			err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
 			if err != nil {
--- a/tpl/amber_compiler.go
+++ /dev/null
@@ -1,42 +1,0 @@
-// 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 tpl
-
-import (
-	"html/template"
-
-	"github.com/eknkc/amber"
-)
-
-func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *template.Template) (*template.Template, error) {
-	c := amber.New()
-
-	if err := c.ParseData(b, path); err != nil {
-		return nil, err
-	}
-
-	data, err := c.CompileString()
-
-	if err != nil {
-		return nil, err
-	}
-
-	tpl, err := t.Funcs(gt.amberFuncMap).Parse(data)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return tpl, nil
-}
--- a/tpl/reflect_helpers.go
+++ /dev/null
@@ -1,70 +1,0 @@
-// Copyright 2016 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 tpl
-
-import (
-	"reflect"
-	"time"
-)
-
-// toInt returns the int value if possible, -1 if not.
-func toInt(v reflect.Value) int64 {
-	switch v.Kind() {
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return v.Int()
-	case reflect.Interface:
-		return toInt(v.Elem())
-	}
-	return -1
-}
-
-// toString returns the string value if possible, "" if not.
-func toString(v reflect.Value) string {
-	switch v.Kind() {
-	case reflect.String:
-		return v.String()
-	case reflect.Interface:
-		return toString(v.Elem())
-	}
-	return ""
-}
-
-var (
-	zero      reflect.Value
-	errorType = reflect.TypeOf((*error)(nil)).Elem()
-	timeType  = reflect.TypeOf((*time.Time)(nil)).Elem()
-)
-
-func toTimeUnix(v reflect.Value) int64 {
-	if v.Kind() == reflect.Interface {
-		return toTimeUnix(v.Elem())
-	}
-	if v.Type() != timeType {
-		panic("coding error: argument must be time.Time type reflect Value")
-	}
-	return v.MethodByName("Unix").Call([]reflect.Value{})[0].Int()
-}
-
-// 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
-}
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -1,575 +1,27 @@
-// Copyright 2016 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 tpl
 
 import (
-	"fmt"
 	"html/template"
 	"io"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"sync"
-
-	"github.com/eknkc/amber"
-	"github.com/spf13/afero"
-	bp "github.com/spf13/hugo/bufferpool"
-	"github.com/spf13/hugo/deps"
-	"github.com/spf13/hugo/helpers"
-	"github.com/yosssi/ace"
 )
 
-// TODO(bep) globals get rid of the rest of the jww.ERR etc.
-
-// Protecting global map access (Amber)
-var amberMu sync.Mutex
-
-type templateErr struct {
-	name string
-	err  error
-}
-
-type GoHTMLTemplate struct {
-	*template.Template
-
-	clone *template.Template
-
-	// a separate storage for the overlays created from cloned master templates.
-	// note: No mutex protection, so we add these in one Go routine, then just read.
-	overlays map[string]*template.Template
-
-	errors []*templateErr
-
-	funcster *templateFuncster
-
-	amberFuncMap template.FuncMap
-
-	*deps.Deps
-}
-
-type TemplateProvider struct{}
-
-var DefaultTemplateProvider *TemplateProvider
-
-// Update updates the Hugo Template System in the provided Deps.
-// with all the additional features, templates & functions
-func (*TemplateProvider) Update(deps *deps.Deps) error {
-	// TODO(bep) check that this isn't called too many times.
-	tmpl := &GoHTMLTemplate{
-		Template: template.New(""),
-		overlays: make(map[string]*template.Template),
-		errors:   make([]*templateErr, 0),
-		Deps:     deps,
-	}
-
-	deps.Tmpl = tmpl
-
-	tmpl.initFuncs(deps)
-
-	tmpl.LoadEmbedded()
-
-	if deps.WithTemplate != nil {
-		err := deps.WithTemplate(tmpl)
-		if err != nil {
-			tmpl.errors = append(tmpl.errors, &templateErr{"init", err})
-		}
-
-	}
-
-	tmpl.MarkReady()
-
-	return nil
-
-}
-
-// Clone clones
-func (*TemplateProvider) Clone(d *deps.Deps) error {
-
-	t := d.Tmpl.(*GoHTMLTemplate)
-
-	// 1. Clone the clone with new template funcs
-	// 2. Clone any overlays with new template funcs
-
-	tmpl := &GoHTMLTemplate{
-		Template: template.Must(t.Template.Clone()),
-		overlays: make(map[string]*template.Template),
-		errors:   make([]*templateErr, 0),
-		Deps:     d,
-	}
-
-	d.Tmpl = tmpl
-	tmpl.initFuncs(d)
-
-	for k, v := range t.overlays {
-		vc := template.Must(v.Clone())
-		// The extra lookup is a workaround, see
-		// * https://github.com/golang/go/issues/16101
-		// * https://github.com/spf13/hugo/issues/2549
-		vc = vc.Lookup(vc.Name())
-		vc.Funcs(tmpl.funcster.funcMap)
-		tmpl.overlays[k] = vc
-	}
-
-	tmpl.MarkReady()
-
-	return nil
-}
-
-func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) {
-
-	t.funcster = newTemplateFuncster(d)
-
-	// The URL funcs in the funcMap is somewhat language dependent,
-	// so we need to wait until the language and site config is loaded.
-	t.funcster.initFuncMap()
-
-	t.amberFuncMap = template.FuncMap{}
-
-	amberMu.Lock()
-	for k, v := range amber.FuncMap {
-		t.amberFuncMap[k] = v
-	}
-
-	for k, v := range t.funcster.funcMap {
-		t.amberFuncMap[k] = v
-		// Hacky, but we need to make sure that the func names are in the global map.
-		amber.FuncMap[k] = func() string {
-			panic("should never be invoked")
-		}
-	}
-	amberMu.Unlock()
-
-}
-
-func (t *GoHTMLTemplate) Funcs(funcMap template.FuncMap) {
-	t.Template.Funcs(funcMap)
-}
-
-func (t *GoHTMLTemplate) Partial(name string, contextList ...interface{}) template.HTML {
-	if strings.HasPrefix("partials/", name) {
-		name = name[8:]
-	}
-	var context interface{}
-
-	if len(contextList) == 0 {
-		context = nil
-	} else {
-		context = contextList[0]
-	}
-	return t.ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name)
-}
-
-func (t *GoHTMLTemplate) executeTemplate(context interface{}, w io.Writer, layouts ...string) {
-	var worked bool
-	for _, layout := range layouts {
-		templ := t.Lookup(layout)
-		if templ == nil {
-			layout += ".html"
-			templ = t.Lookup(layout)
-		}
-
-		if templ != nil {
-			if err := templ.Execute(w, context); err != nil {
-				helpers.DistinctErrorLog.Println(layout, err)
-			}
-			worked = true
-			break
-		}
-	}
-	if !worked {
-		t.Log.ERROR.Println("Unable to render", layouts)
-		t.Log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
-	}
-}
-
-func (t *GoHTMLTemplate) ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML {
-	b := bp.GetBuffer()
-	defer bp.PutBuffer(b)
-	t.executeTemplate(context, b, layouts...)
-	return template.HTML(b.String())
-}
-
-func (t *GoHTMLTemplate) Lookup(name string) *template.Template {
-
-	if templ := t.Template.Lookup(name); templ != nil {
-		return templ
-	}
-
-	if t.overlays != nil {
-		if templ, ok := t.overlays[name]; ok {
-			return templ
-		}
-	}
-
-	// The clone is used for the non-renderable HTML pages (p.IsRenderable == false) that is parsed
-	// as Go templates late in the build process.
-	if t.clone != nil {
-		if templ := t.clone.Lookup(name); templ != nil {
-			return templ
-		}
-	}
-
-	return nil
-
-}
-
-func (t *GoHTMLTemplate) GetClone() *template.Template {
-	return t.clone
-}
-
-func (t *GoHTMLTemplate) LoadEmbedded() {
-	t.EmbedShortcodes()
-	t.EmbedTemplates()
-}
-
-// MarkReady marks the template as "ready for execution". No changes allowed
-// after this is set.
-func (t *GoHTMLTemplate) MarkReady() {
-	if t.clone == nil {
-		t.clone = template.Must(t.Template.Clone())
-	}
-}
-
-func (t *GoHTMLTemplate) checkState() {
-	if t.clone != nil {
-		panic("template is cloned and cannot be modfified")
-	}
-}
-
-func (t *GoHTMLTemplate) AddInternalTemplate(prefix, name, tpl string) error {
-	if prefix != "" {
-		return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
-	}
-	return t.AddTemplate("_internal/"+name, tpl)
-}
-
-func (t *GoHTMLTemplate) AddInternalShortcode(name, content string) error {
-	return t.AddInternalTemplate("shortcodes", name, content)
-}
-
-func (t *GoHTMLTemplate) AddTemplate(name, tpl string) error {
-	t.checkState()
-	templ, err := t.New(name).Parse(tpl)
-	if err != nil {
-		t.errors = append(t.errors, &templateErr{name: name, err: err})
-		return err
-	}
-	if err := applyTemplateTransformers(templ); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error {
-
-	// There is currently no known way to associate a cloned template with an existing one.
-	// This funky master/overlay design will hopefully improve in a future version of Go.
-	//
-	// Simplicity is hard.
-	//
-	// Until then we'll have to live with this hackery.
-	//
-	// See https://github.com/golang/go/issues/14285
-	//
-	// So, to do minimum amount of changes to get this to work:
-	//
-	// 1. Lookup or Parse the master
-	// 2. Parse and store the overlay in a separate map
-
-	masterTpl := t.Lookup(masterFilename)
-
-	if masterTpl == nil {
-		b, err := afero.ReadFile(t.Fs.Source, masterFilename)
-		if err != nil {
-			return err
-		}
-		masterTpl, err = t.New(masterFilename).Parse(string(b))
-
-		if err != nil {
-			// TODO(bep) Add a method that does this
-			t.errors = append(t.errors, &templateErr{name: name, err: err})
-			return err
-		}
-	}
-
-	b, err := afero.ReadFile(t.Fs.Source, overlayFilename)
-	if err != nil {
-		return err
-	}
-
-	overlayTpl, err := template.Must(masterTpl.Clone()).Parse(string(b))
-	if err != nil {
-		t.errors = append(t.errors, &templateErr{name: name, err: err})
-	} else {
-		// The extra lookup is a workaround, see
-		// * https://github.com/golang/go/issues/16101
-		// * https://github.com/spf13/hugo/issues/2549
-		overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
-		if err := applyTemplateTransformers(overlayTpl); err != nil {
-			return err
-		}
-		t.overlays[name] = overlayTpl
-	}
-
-	return err
-}
-
-func (t *GoHTMLTemplate) AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error {
-	t.checkState()
-	var base, inner *ace.File
-	name = name[:len(name)-len(filepath.Ext(innerPath))] + ".html"
-
-	// Fixes issue #1178
-	basePath = strings.Replace(basePath, "\\", "/", -1)
-	innerPath = strings.Replace(innerPath, "\\", "/", -1)
-
-	if basePath != "" {
-		base = ace.NewFile(basePath, baseContent)
-		inner = ace.NewFile(innerPath, innerContent)
-	} else {
-		base = ace.NewFile(innerPath, innerContent)
-		inner = ace.NewFile("", []byte{})
-	}
-	parsed, err := ace.ParseSource(ace.NewSource(base, inner, []*ace.File{}), nil)
-	if err != nil {
-		t.errors = append(t.errors, &templateErr{name: name, err: err})
-		return err
-	}
-	templ, err := ace.CompileResultWithTemplate(t.New(name), parsed, nil)
-	if err != nil {
-		t.errors = append(t.errors, &templateErr{name: name, err: err})
-		return err
-	}
-	return applyTemplateTransformers(templ)
-}
-
-func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) error {
-	t.checkState()
-	// get the suffix and switch on that
-	ext := filepath.Ext(path)
-	switch ext {
-	case ".amber":
-		templateName := strings.TrimSuffix(name, filepath.Ext(name)) + ".html"
-		b, err := afero.ReadFile(t.Fs.Source, path)
-
-		if err != nil {
-			return err
-		}
-
-		amberMu.Lock()
-		templ, err := t.CompileAmberWithTemplate(b, path, t.New(templateName))
-		amberMu.Unlock()
-		if err != nil {
-			return err
-		}
-
-		return applyTemplateTransformers(templ)
-	case ".ace":
-		var innerContent, baseContent []byte
-		innerContent, err := afero.ReadFile(t.Fs.Source, path)
-
-		if err != nil {
-			return err
-		}
-
-		if baseTemplatePath != "" {
-			baseContent, err = afero.ReadFile(t.Fs.Source, baseTemplatePath)
-			if err != nil {
-				return err
-			}
-		}
-
-		return t.AddAceTemplate(name, baseTemplatePath, path, baseContent, innerContent)
-	default:
-
-		if baseTemplatePath != "" {
-			return t.AddTemplateFileWithMaster(name, path, baseTemplatePath)
-		}
-
-		b, err := afero.ReadFile(t.Fs.Source, path)
-
-		if err != nil {
-			return err
-		}
-
-		t.Log.DEBUG.Printf("Add template file from path %s", path)
-
-		return t.AddTemplate(name, string(b))
-	}
-
-}
-
-func (t *GoHTMLTemplate) GenerateTemplateNameFrom(base, path string) string {
-	name, _ := filepath.Rel(base, path)
-	return filepath.ToSlash(name)
-}
-
-func isDotFile(path string) bool {
-	return filepath.Base(path)[0] == '.'
-}
-
-func isBackupFile(path string) bool {
-	return path[len(path)-1] == '~'
-}
-
-const baseFileBase = "baseof"
-
-var aceTemplateInnerMarkers = [][]byte{[]byte("= content")}
-var goTemplateInnerMarkers = [][]byte{[]byte("{{define"), []byte("{{ define")}
-
-func isBaseTemplate(path string) bool {
-	return strings.Contains(path, baseFileBase)
-}
-
-func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
-	t.Log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix)
-	walker := func(path string, fi os.FileInfo, err error) error {
-		if err != nil {
-			return nil
-		}
-		t.Log.DEBUG.Println("Template path", path)
-		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
-			link, err := filepath.EvalSymlinks(absPath)
-			if err != nil {
-				t.Log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err)
-				return nil
-			}
-			linkfi, err := t.Fs.Source.Stat(link)
-			if err != nil {
-				t.Log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
-				return nil
-			}
-			if !linkfi.Mode().IsRegular() {
-				t.Log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath)
-			}
-			return nil
-		}
-
-		if !fi.IsDir() {
-			if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
-				return nil
-			}
-
-			tplName := t.GenerateTemplateNameFrom(absPath, path)
-
-			if prefix != "" {
-				tplName = strings.Trim(prefix, "/") + "/" + tplName
-			}
-
-			var baseTemplatePath string
-
-			// Ace and Go templates may have both a base and inner template.
-			pathDir := filepath.Dir(path)
-			if filepath.Ext(path) != ".amber" && !strings.HasSuffix(pathDir, "partials") && !strings.HasSuffix(pathDir, "shortcodes") {
-
-				innerMarkers := goTemplateInnerMarkers
-				baseFileName := fmt.Sprintf("%s.html", baseFileBase)
-
-				if filepath.Ext(path) == ".ace" {
-					innerMarkers = aceTemplateInnerMarkers
-					baseFileName = fmt.Sprintf("%s.ace", baseFileBase)
-				}
-
-				// This may be a view that shouldn't have base template
-				// Have to look inside it to make sure
-				needsBase, err := helpers.FileContainsAny(path, innerMarkers, t.Fs.Source)
-				if err != nil {
-					return err
-				}
-				if needsBase {
-
-					layoutDir := t.PathSpec.GetLayoutDirPath()
-					currBaseFilename := fmt.Sprintf("%s-%s", helpers.Filename(path), baseFileName)
-					templateDir := filepath.Dir(path)
-					themeDir := filepath.Join(t.PathSpec.GetThemeDir())
-					relativeThemeLayoutsDir := filepath.Join(t.PathSpec.GetRelativeThemeDir(), "layouts")
-
-					var baseTemplatedDir string
-
-					if strings.HasPrefix(templateDir, relativeThemeLayoutsDir) {
-						baseTemplatedDir = strings.TrimPrefix(templateDir, relativeThemeLayoutsDir)
-					} else {
-						baseTemplatedDir = strings.TrimPrefix(templateDir, layoutDir)
-					}
-
-					baseTemplatedDir = strings.TrimPrefix(baseTemplatedDir, helpers.FilePathSeparator)
-
-					// Look for base template in the follwing order:
-					//   1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
-					//   2. <current-path>/baseof.<suffix>
-					//   3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
-					//   4. _default/baseof.<suffix>
-					// For each of the steps above, it will first look in the project, then, if theme is set,
-					// in the theme's layouts folder.
-
-					pairsToCheck := [][]string{
-						[]string{baseTemplatedDir, currBaseFilename},
-						[]string{baseTemplatedDir, baseFileName},
-						[]string{"_default", currBaseFilename},
-						[]string{"_default", baseFileName},
-					}
-
-				Loop:
-					for _, pair := range pairsToCheck {
-						pathsToCheck := basePathsToCheck(pair, layoutDir, themeDir)
-						for _, pathToCheck := range pathsToCheck {
-							if ok, err := helpers.Exists(pathToCheck, t.Fs.Source); err == nil && ok {
-								baseTemplatePath = pathToCheck
-								break Loop
-							}
-						}
-					}
-				}
-			}
-
-			if err := t.AddTemplateFile(tplName, baseTemplatePath, path); err != nil {
-				t.Log.ERROR.Printf("Failed to add template %s in path %s: %s", tplName, path, err)
-			}
-
-		}
-		return nil
-	}
-	if err := helpers.SymbolicWalk(t.Fs.Source, absPath, walker); err != nil {
-		t.Log.ERROR.Printf("Failed to load templates: %s", err)
-	}
-}
-
-func basePathsToCheck(path []string, layoutDir, themeDir string) []string {
-	// Always look in the project.
-	pathsToCheck := []string{filepath.Join((append([]string{layoutDir}, path...))...)}
-
-	// May have a theme
-	if themeDir != "" {
-		pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{themeDir, "layouts"}, path...))...))
-	}
-
-	return pathsToCheck
-
-}
-
-func (t *GoHTMLTemplate) LoadTemplatesWithPrefix(absPath string, prefix string) {
-	t.loadTemplates(absPath, prefix)
-}
-
-func (t *GoHTMLTemplate) LoadTemplates(absPath string) {
-	t.loadTemplates(absPath, "")
-}
-
-func (t *GoHTMLTemplate) PrintErrors() {
-	for i, e := range t.errors {
-		t.Log.ERROR.Println(i, ":", e.err)
-	}
+// TODO(bep) make smaller
+type Template interface {
+	ExecuteTemplate(wr io.Writer, name string, data interface{}) error
+	ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML
+	Lookup(name string) *template.Template
+	Templates() []*template.Template
+	New(name string) *template.Template
+	GetClone() *template.Template
+	LoadTemplates(absPath string)
+	LoadTemplatesWithPrefix(absPath, prefix string)
+	AddTemplate(name, tpl string) error
+	AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error
+	AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error
+	AddInternalTemplate(prefix, name, tpl string) error
+	AddInternalShortcode(name, tpl string) error
+	Partial(name string, contextList ...interface{}) template.HTML
+	PrintErrors()
+	Funcs(funcMap template.FuncMap)
+	MarkReady()
 }
--- a/tpl/template_ast_transformers.go
+++ /dev/null
@@ -1,259 +1,0 @@
-// Copyright 2016 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 tpl
-
-import (
-	"errors"
-	"html/template"
-	"strings"
-	"text/template/parse"
-)
-
-// decl keeps track of the variable mappings, i.e. $mysite => .Site etc.
-type decl map[string]string
-
-var paramsPaths = [][]string{
-	{"Params"},
-	{"Site", "Params"},
-
-	// Site and Pag referenced from shortcodes
-	{"Page", "Site", "Params"},
-	{"Page", "Params"},
-
-	{"Site", "Language", "Params"},
-}
-
-type templateContext struct {
-	decl  decl
-	templ *template.Template
-}
-
-func newTemplateContext(templ *template.Template) *templateContext {
-	return &templateContext{templ: templ, decl: make(map[string]string)}
-
-}
-
-func applyTemplateTransformers(templ *template.Template) error {
-	if templ == nil || templ.Tree == nil {
-		return errors.New("expected template, but none provided")
-	}
-
-	c := newTemplateContext(templ)
-
-	c.paramsKeysToLower(templ.Tree.Root)
-
-	return nil
-}
-
-// paramsKeysToLower is made purposely non-generic to make it not so tempting
-// to do more of these hard-to-maintain AST transformations.
-func (c *templateContext) paramsKeysToLower(n parse.Node) {
-
-	switch x := n.(type) {
-	case *parse.ListNode:
-		if x != nil {
-			c.paramsKeysToLowerForNodes(x.Nodes...)
-		}
-	case *parse.ActionNode:
-		c.paramsKeysToLowerForNodes(x.Pipe)
-	case *parse.IfNode:
-		c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList)
-	case *parse.WithNode:
-		c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList)
-	case *parse.RangeNode:
-		c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList)
-	case *parse.TemplateNode:
-		subTempl := c.templ.Lookup(x.Name)
-		if subTempl != nil {
-			c.paramsKeysToLowerForNodes(subTempl.Tree.Root)
-		}
-	case *parse.PipeNode:
-		for i, elem := range x.Decl {
-			if len(x.Cmds) > i {
-				// maps $site => .Site etc.
-				c.decl[elem.Ident[0]] = x.Cmds[i].String()
-			}
-		}
-
-		for _, cmd := range x.Cmds {
-			c.paramsKeysToLower(cmd)
-		}
-
-	case *parse.CommandNode:
-		for _, elem := range x.Args {
-			switch an := elem.(type) {
-			case *parse.FieldNode:
-				c.updateIdentsIfNeeded(an.Ident)
-			case *parse.VariableNode:
-				c.updateIdentsIfNeeded(an.Ident)
-			case *parse.PipeNode:
-				c.paramsKeysToLower(an)
-			}
-
-		}
-	}
-}
-
-func (c *templateContext) paramsKeysToLowerForNodes(nodes ...parse.Node) {
-	for _, node := range nodes {
-		c.paramsKeysToLower(node)
-	}
-}
-
-func (c *templateContext) updateIdentsIfNeeded(idents []string) {
-	index := c.decl.indexOfReplacementStart(idents)
-
-	if index == -1 {
-		return
-	}
-
-	for i := index; i < len(idents); i++ {
-		idents[i] = strings.ToLower(idents[i])
-	}
-}
-
-// indexOfReplacementStart will return the index of where to start doing replacement,
-// -1 if none needed.
-func (d decl) indexOfReplacementStart(idents []string) int {
-
-	l := len(idents)
-
-	if l == 0 {
-		return -1
-	}
-
-	first := idents[0]
-	firstIsVar := first[0] == '$'
-
-	if l == 1 && !firstIsVar {
-		// This can not be a Params.x
-		return -1
-	}
-
-	if !firstIsVar {
-		found := false
-		for _, paramsPath := range paramsPaths {
-			if first == paramsPath[0] {
-				found = true
-				break
-			}
-		}
-		if !found {
-			return -1
-		}
-	}
-
-	var (
-		resolvedIdents []string
-		replacements   []string
-		replaced       []string
-	)
-
-	// An Ident can start out as one of
-	// [Params] [$blue] [$colors.Blue]
-	// We need to resolve the variables, so
-	// $blue => [Params Colors Blue]
-	// etc.
-	replacements = []string{idents[0]}
-
-	// Loop until there are no more $vars to resolve.
-	for i := 0; i < len(replacements); i++ {
-
-		if i > 20 {
-			// bail out
-			return -1
-		}
-
-		potentialVar := replacements[i]
-
-		if potentialVar == "$" {
-			continue
-		}
-
-		if potentialVar == "" || potentialVar[0] != '$' {
-			// leave it as is
-			replaced = append(replaced, strings.Split(potentialVar, ".")...)
-			continue
-		}
-
-		replacement, ok := d[potentialVar]
-
-		if !ok {
-			// Temporary range vars. We do not care about those.
-			return -1
-		}
-
-		replacement = strings.TrimPrefix(replacement, ".")
-
-		if replacement == "" {
-			continue
-		}
-
-		if replacement[0] == '$' {
-			// Needs further expansion
-			replacements = append(replacements, strings.Split(replacement, ".")...)
-		} else {
-			replaced = append(replaced, strings.Split(replacement, ".")...)
-		}
-	}
-
-	resolvedIdents = append(replaced, idents[1:]...)
-
-	for _, paramPath := range paramsPaths {
-		if index := indexOfFirstRealIdentAfterWords(resolvedIdents, idents, paramPath...); index != -1 {
-			return index
-		}
-	}
-
-	return -1
-
-}
-
-func indexOfFirstRealIdentAfterWords(resolvedIdents, idents []string, words ...string) int {
-	if !sliceStartsWith(resolvedIdents, words...) {
-		return -1
-	}
-
-	for i, ident := range idents {
-		if ident == "" || ident[0] == '$' {
-			continue
-		}
-		found := true
-		for _, word := range words {
-			if ident == word {
-				found = false
-				break
-			}
-		}
-		if found {
-			return i
-		}
-	}
-
-	return -1
-}
-
-func sliceStartsWith(slice []string, words ...string) bool {
-
-	if len(slice) < len(words) {
-		return false
-	}
-
-	for i, word := range words {
-		if word != slice[i] {
-			return false
-		}
-	}
-	return true
-}
--- a/tpl/template_ast_transformers_test.go
+++ /dev/null
@@ -1,269 +1,0 @@
-// Copyright 2016 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 tpl
-
-import (
-	"bytes"
-	"testing"
-
-	"html/template"
-
-	"github.com/stretchr/testify/require"
-)
-
-var (
-	testFuncs = map[string]interface{}{
-		"Echo": func(v interface{}) interface{} { return v },
-	}
-
-	paramsData = map[string]interface{}{
-		"NotParam": "Hi There",
-		"Slice":    []int{1, 3},
-		"Params": map[string]interface{}{
-			"lower": "P1L",
-		},
-		"Site": map[string]interface{}{
-			"Params": map[string]interface{}{
-				"lower": "P2L",
-				"slice": []int{1, 3},
-			},
-			"Language": map[string]interface{}{
-				"Params": map[string]interface{}{
-					"lower": "P22L",
-				},
-			},
-			"Data": map[string]interface{}{
-				"Params": map[string]interface{}{
-					"NOLOW": "P3H",
-				},
-			},
-		},
-	}
-
-	paramsTempl = `
-{{ $page := . }}
-{{ $pageParams := .Params }}
-{{ $site := .Site }}
-{{ $siteParams := .Site.Params }}
-{{ $data := .Site.Data }}
-{{ $notparam := .NotParam }}
-
-P1: {{ .Params.LOWER }}
-P1_2: {{ $.Params.LOWER }}
-P1_3: {{ $page.Params.LOWER }}
-P1_4: {{ $pageParams.LOWER }}
-P2: {{ .Site.Params.LOWER }}
-P2_2: {{ $.Site.Params.LOWER }}
-P2_3: {{ $site.Params.LOWER }}
-P2_4: {{ $siteParams.LOWER }}
-P22: {{ .Site.Language.Params.LOWER }}
-P3: {{ .Site.Data.Params.NOLOW }}
-P3_2: {{ $.Site.Data.Params.NOLOW }}
-P3_3: {{ $site.Data.Params.NOLOW }}
-P3_4: {{ $data.Params.NOLOW }}
-P4: {{ range $i, $e := .Site.Params.SLICE }}{{ $e }}{{ end }}
-P5: {{ Echo .Params.LOWER }}
-P5_2: {{ Echo $site.Params.LOWER }}
-{{ if .Params.LOWER }}
-IF: {{ .Params.LOWER }}
-{{ end }}
-{{ if .Params.NOT_EXIST }}
-{{ else }}
-ELSE: {{ .Params.LOWER }}
-{{ end }}
-
-
-{{ with .Params.LOWER }}
-WITH: {{ . }}
-{{ end }}
-
-
-{{ range .Slice }}
-RANGE: {{ . }}: {{ $.Params.LOWER }}
-{{ end }}
-{{ index .Slice 1 }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ $notparam }}
-
-
-{{ $lower := .Site.Params.LOWER }}
-F1: {{ printf "themes/%s-theme" .Site.Params.LOWER }}
-F2: {{ Echo (printf "themes/%s-theme" $lower) }}
-F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }}
-`
-)
-
-func TestParamsKeysToLower(t *testing.T) {
-	t.Parallel()
-
-	require.Error(t, applyTemplateTransformers(nil))
-
-	templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
-
-	require.NoError(t, err)
-
-	c := newTemplateContext(templ)
-
-	require.Equal(t, -1, c.decl.indexOfReplacementStart([]string{}))
-
-	c.paramsKeysToLower(templ.Tree.Root)
-
-	var b bytes.Buffer
-
-	require.NoError(t, templ.Execute(&b, paramsData))
-
-	result := b.String()
-
-	require.Contains(t, result, "P1: P1L")
-	require.Contains(t, result, "P1_2: P1L")
-	require.Contains(t, result, "P1_3: P1L")
-	require.Contains(t, result, "P1_4: P1L")
-	require.Contains(t, result, "P2: P2L")
-	require.Contains(t, result, "P2_2: P2L")
-	require.Contains(t, result, "P2_3: P2L")
-	require.Contains(t, result, "P2_4: P2L")
-	require.Contains(t, result, "P22: P22L")
-	require.Contains(t, result, "P3: P3H")
-	require.Contains(t, result, "P3_2: P3H")
-	require.Contains(t, result, "P3_3: P3H")
-	require.Contains(t, result, "P3_4: P3H")
-	require.Contains(t, result, "P4: 13")
-	require.Contains(t, result, "P5: P1L")
-	require.Contains(t, result, "P5_2: P2L")
-
-	require.Contains(t, result, "IF: P1L")
-	require.Contains(t, result, "ELSE: P1L")
-
-	require.Contains(t, result, "WITH: P1L")
-
-	require.Contains(t, result, "RANGE: 3: P1L")
-
-	require.Contains(t, result, "Hi There")
-
-	// Issue #2740
-	require.Contains(t, result, "F1: themes/P2L-theme")
-	require.Contains(t, result, "F2: themes/P2L-theme")
-	require.Contains(t, result, "F3: themes/P2L-theme")
-
-}
-
-func BenchmarkTemplateParamsKeysToLower(b *testing.B) {
-	templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
-
-	if err != nil {
-		b.Fatal(err)
-	}
-
-	templates := make([]*template.Template, b.N)
-
-	for i := 0; i < b.N; i++ {
-		templates[i], err = templ.Clone()
-		if err != nil {
-			b.Fatal(err)
-		}
-	}
-
-	b.ResetTimer()
-
-	for i := 0; i < b.N; i++ {
-		c := newTemplateContext(templates[i])
-		c.paramsKeysToLower(templ.Tree.Root)
-	}
-}
-
-func TestParamsKeysToLowerVars(t *testing.T) {
-	t.Parallel()
-	var (
-		ctx = map[string]interface{}{
-			"Params": map[string]interface{}{
-				"colors": map[string]interface{}{
-					"blue": "Amber",
-				},
-			},
-		}
-
-		// This is how Amber behaves:
-		paramsTempl = `
-{{$__amber_1 := .Params.Colors}}
-{{$__amber_2 := $__amber_1.Blue}}
-Color: {{$__amber_2}}
-Blue: {{ $__amber_1.Blue}}
-`
-	)
-
-	templ, err := template.New("foo").Parse(paramsTempl)
-
-	require.NoError(t, err)
-
-	c := newTemplateContext(templ)
-
-	c.paramsKeysToLower(templ.Tree.Root)
-
-	var b bytes.Buffer
-
-	require.NoError(t, templ.Execute(&b, ctx))
-
-	result := b.String()
-
-	require.Contains(t, result, "Color: Amber")
-
-}
-
-func TestParamsKeysToLowerInBlockTemplate(t *testing.T) {
-	t.Parallel()
-
-	var (
-		ctx = map[string]interface{}{
-			"Params": map[string]interface{}{
-				"lower": "P1L",
-			},
-		}
-
-		master = `
-P1: {{ .Params.LOWER }}
-{{ block "main" . }}DEFAULT{{ end }}`
-		overlay = `
-{{ define "main" }}
-P2: {{ .Params.LOWER }}
-{{ end }}`
-	)
-
-	masterTpl, err := template.New("foo").Parse(master)
-	require.NoError(t, err)
-
-	overlayTpl, err := template.Must(masterTpl.Clone()).Parse(overlay)
-	require.NoError(t, err)
-	overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
-
-	c := newTemplateContext(overlayTpl)
-
-	c.paramsKeysToLower(overlayTpl.Tree.Root)
-
-	var b bytes.Buffer
-
-	require.NoError(t, overlayTpl.Execute(&b, ctx))
-
-	result := b.String()
-
-	require.Contains(t, result, "P1: P1L")
-	require.Contains(t, result, "P2: P1L")
-}
--- a/tpl/template_embedded.go
+++ /dev/null
@@ -1,266 +1,0 @@
-// Copyright 2015 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 tpl
-
-type Tmpl struct {
-	Name string
-	Data string
-}
-
-func (t *GoHTMLTemplate) EmbedShortcodes() {
-	t.AddInternalShortcode("ref.html", `{{ .Get 0 | ref .Page }}`)
-	t.AddInternalShortcode("relref.html", `{{ .Get 0 | relref .Page }}`)
-	t.AddInternalShortcode("highlight.html", `{{ if len .Params | eq 2 }}{{ highlight .Inner (.Get 0) (.Get 1) }}{{ else }}{{ highlight .Inner (.Get 0) "" }}{{ end }}`)
-	t.AddInternalShortcode("test.html", `This is a simple Test`)
-	t.AddInternalShortcode("figure.html", `<!-- image -->
-<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
-    {{ with .Get "link"}}<a href="{{.}}">{{ end }}
-        <img src="{{ .Get "src" }}" {{ if or (.Get "alt") (.Get "caption") }}alt="{{ with .Get "alt"}}{{.}}{{else}}{{ .Get "caption" }}{{ end }}" {{ end }}{{ with .Get "width" }}width="{{.}}" {{ end }}/>
-    {{ if .Get "link"}}</a>{{ end }}
-    {{ if or (or (.Get "title") (.Get "caption")) (.Get "attr")}}
-    <figcaption>{{ if isset .Params "title" }}
-        <h4>{{ .Get "title" }}</h4>{{ end }}
-        {{ if or (.Get "caption") (.Get "attr")}}<p>
-        {{ .Get "caption" }}
-        {{ with .Get "attrlink"}}<a href="{{.}}"> {{ end }}
-            {{ .Get "attr" }}
-        {{ if .Get "attrlink"}}</a> {{ end }}
-        </p> {{ end }}
-    </figcaption>
-    {{ end }}
-</figure>
-<!-- image -->`)
-	t.AddInternalShortcode("speakerdeck.html", "<script async class='speakerdeck-embed' data-id='{{ index .Params 0 }}' data-ratio='1.33333333333333' src='//speakerdeck.com/assets/embed.js'></script>")
-	t.AddInternalShortcode("youtube.html", `{{ if .IsNamedParams }}
-<div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
-  <iframe src="//www.youtube.com/embed/{{ .Get "id" }}?{{ with .Get "autoplay" }}{{ if eq . "true" }}autoplay=1{{ end }}{{ end }}" 
-  {{ if not (.Get "class") }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0"></iframe>
-</div>{{ else }}
-<div {{ if len .Params | eq 2 }}class="{{ .Get 1 }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
-  <iframe src="//www.youtube.com/embed/{{ .Get 0 }}" {{ if len .Params | eq 1 }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0"></iframe>
- </div>
-{{ end }}`)
-	t.AddInternalShortcode("vimeo.html", `{{ if .IsNamedParams }}<div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
-  <iframe src="//player.vimeo.com/video/{{ .Get "id" }}" {{ if not (.Get "class") }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
- </div>{{ else }}
-<div {{ if len .Params | eq 2 }}class="{{ .Get 1 }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
-  <iframe src="//player.vimeo.com/video/{{ .Get 0 }}" {{ if len .Params | eq 1 }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
- </div>
-{{ end }}`)
-	t.AddInternalShortcode("gist.html", `<script src="//gist.github.com/{{ index .Params 0 }}/{{ index .Params 1 }}.js{{if len .Params | eq 3 }}?file={{ index .Params 2 }}{{end}}"></script>`)
-	t.AddInternalShortcode("tweet.html", `{{ (getJSON "https://api.twitter.com/1/statuses/oembed.json?id=" (index .Params 0)).html | safeHTML }}`)
-	t.AddInternalShortcode("instagram.html", `{{ if len .Params | eq 2 }}{{ if eq (.Get 1) "hidecaption" }}{{ (getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=1").html | safeHTML }}{{ end }}{{ else }}{{ (getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=0").html | safeHTML }}{{ end }}`)
-}
-
-func (t *GoHTMLTemplate) EmbedTemplates() {
-
-	t.AddInternalTemplate("_default", "rss.xml", `<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
-  <channel>
-    <title>{{ if eq  .Title  .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
-    <link>{{ .Permalink }}</link>
-    <description>Recent content {{ if ne  .Title  .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
-    <generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
-    <language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
-    <managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
-    <webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
-    <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
-    <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
-    <atom:link href="{{.Permalink}}" rel="self" type="application/rss+xml" />
-    {{ range first 15 .Data.Pages }}
-    <item>
-      <title>{{ .Title }}</title>
-      <link>{{ .Permalink }}</link>
-      <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
-      {{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
-      <guid>{{ .Permalink }}</guid>
-      <description>{{ .Content | html }}</description>
-    </item>
-    {{ end }}
-  </channel>
-</rss>`)
-
-	t.AddInternalTemplate("_default", "sitemap.xml", `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
-  {{ range .Data.Pages }}
-  <url>
-    <loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
-    <lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
-    <changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
-    <priority>{{ .Sitemap.Priority }}</priority>{{ end }}
-  </url>
-  {{ end }}
-</urlset>`)
-
-	// For multilanguage sites
-	t.AddInternalTemplate("_default", "sitemapindex.xml", `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
-	{{ range . }}
-	<sitemap>
-	   	<loc>{{ .SitemapAbsURL }}</loc>
-		{{ if not .LastChange.IsZero }}
-	   	<lastmod>{{ .LastChange.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</lastmod>
-		{{ end }}
-	</sitemap>
-	{{ end }}
-</sitemapindex>
-`)
-
-	t.AddInternalTemplate("", "pagination.html", `{{ $pag := $.Paginator }}
-    {{ if gt $pag.TotalPages 1 }}
-    <ul class="pagination">
-        {{ with $pag.First }}
-        <li>
-            <a href="{{ .URL }}" aria-label="First"><span aria-hidden="true">&laquo;&laquo;</span></a>
-        </li>
-        {{ end }}
-        <li
-        {{ if not $pag.HasPrev }}class="disabled"{{ end }}>
-        <a href="{{ if $pag.HasPrev }}{{ $pag.Prev.URL }}{{ end }}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>
-        </li>
-        {{ range $pag.Pagers }}
-        <li
-        {{ if eq . $pag }}class="active"{{ end }}><a href="{{ .URL }}">{{ .PageNumber }}</a></li>
-        {{ end }}
-        <li
-        {{ if not $pag.HasNext }}class="disabled"{{ end }}>
-        <a href="{{ if $pag.HasNext }}{{ $pag.Next.URL }}{{ end }}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>
-        </li>
-        {{ with $pag.Last }}
-        <li>
-            <a href="{{ .URL }}" aria-label="Last"><span aria-hidden="true">&raquo;&raquo;</span></a>
-        </li>
-        {{ end }}
-    </ul>
-    {{ end }}`)
-
-	t.AddInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}<div id="disqus_thread"></div>
-<script type="text/javascript">
-    var disqus_shortname = '{{ .Site.DisqusShortname }}';
-    var disqus_identifier = '{{with .GetParam "disqus_identifier" }}{{ . }}{{ else }}{{ .Permalink }}{{end}}';
-    var disqus_title = '{{with .GetParam "disqus_title" }}{{ . }}{{ else }}{{ .Title }}{{end}}';
-    var disqus_url = '{{with .GetParam "disqus_url" }}{{ . | html  }}{{ else }}{{ .Permalink }}{{end}}';
-
-    (function() {
-        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
-        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
-        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
-    })();
-</script>
-<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
-<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>{{end}}`)
-
-	// Add SEO & Social metadata
-	t.AddInternalTemplate("", "opengraph.html", `<meta property="og:title" content="{{ .Title }}" />
-<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}" />
-<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
-<meta property="og:url" content="{{ .Permalink }}" />
-{{ with .Params.images }}{{ range first 6 . }}
-  <meta property="og:image" content="{{ . | absURL }}" />
-{{ end }}{{ end }}
-
-{{ if .IsPage }}
-{{ if not .PublishDate.IsZero }}<meta property="article:published_time" content="{{ .PublishDate.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/>
-{{ else if not .Date.IsZero }}<meta property="article:published_time" content="{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/>{{ end }}
-{{ if not .Lastmod.IsZero }}<meta property="article:modified_time" content="{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/>{{ end }}
-{{ else }}
-{{ if not .Date.IsZero }}<meta property="article:modified_time" content="{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/>{{ end }}
-{{ end }}{{ with .Params.audio }}
-<meta property="og:audio" content="{{ . }}" />{{ end }}{{ with .Params.locale }}
-<meta property="og:locale" content="{{ . }}" />{{ end }}{{ with .Site.Params.title }}
-<meta property="og:site_name" content="{{ . }}" />{{ end }}{{ with .Params.videos }}
-{{ range .Params.videos }}
-  <meta property="og:video" content="{{ . | absURL }}" />
-{{ end }}{{ end }}
-
-<!-- If it is part of a series, link to related articles -->
-{{ $permalink := .Permalink }}
-{{ $siteSeries := .Site.Taxonomies.series }}{{ with .Params.series }}
-{{ range $name := . }}
-  {{ $series := index $siteSeries $name }}
-  {{ range $page := first 6 $series.Pages }}
-    {{ if ne $page.Permalink $permalink }}<meta property="og:see_also" content="{{ $page.Permalink }}" />{{ end }}
-  {{ end }}
-{{ end }}{{ end }}
-
-{{ if .IsPage }}
-{{ range .Site.Authors }}{{ with .Social.facebook }}
-<meta property="article:author" content="https://www.facebook.com/{{ . }}" />{{ end }}{{ with .Site.Social.facebook }}
-<meta property="article:publisher" content="https://www.facebook.com/{{ . }}" />{{ end }}
-<meta property="article:section" content="{{ .Section }}" />
-{{ with .Params.tags }}{{ range first 6 . }}
-  <meta property="article:tag" content="{{ . }}" />{{ end }}{{ end }}
-{{ end }}{{ end }}
-
-<!-- Facebook Page Admin ID for Domain Insights -->
-{{ with .Site.Social.facebook_admin }}<meta property="fb:admins" content="{{ . }}" />{{ end }}`)
-
-	t.AddInternalTemplate("", "twitter_cards.html", `{{ if .IsPage }}
-{{ with .Params.images }}
-<!-- Twitter summary card with large image must be at least 280x150px -->
-  <meta name="twitter:card" content="summary_large_image"/>
-  <meta name="twitter:image:src" content="{{ index . 0 | absURL }}"/>
-{{ else }}
-  <meta name="twitter:card" content="summary"/>
-{{ end }}
-
-<!-- Twitter Card data -->
-<meta name="twitter:title" content="{{ .Title }}"/>
-<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}"/>
-{{ with .Site.Social.twitter }}<meta name="twitter:site" content="@{{ . }}"/>{{ end }}
-{{ with .Site.Social.twitter_domain }}<meta name="twitter:domain" content="{{ . }}"/>{{ end }}
-{{ range .Site.Authors }}
-  {{ with .twitter }}<meta name="twitter:creator" content="@{{ . }}"/>{{ end }}
-{{ end }}{{ end }}`)
-
-	t.AddInternalTemplate("", "google_news.html", `{{ if .IsPage }}{{ with .Params.news_keywords }}
-  <meta name="news_keywords" content="{{ range $i, $kw := first 10 . }}{{ if $i }},{{ end }}{{ $kw }}{{ end }}" />
-{{ end }}{{ end }}`)
-
-	t.AddInternalTemplate("", "schema.html", `{{ with .Site.Social.GooglePlus }}<link rel="publisher" href="{{ . }}"/>{{ end }}
-<meta itemprop="name" content="{{ .Title }}">
-<meta itemprop="description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}">
-
-{{if .IsPage}}{{ $ISO8601 := "2006-01-02T15:04:05-07:00" }}{{ if not .PublishDate.IsZero }}
-<meta itemprop="datePublished" content="{{ .PublishDate.Format $ISO8601 | safeHTML }}" />{{ end }}
-{{ if not .Date.IsZero }}<meta itemprop="dateModified" content="{{ .Date.Format $ISO8601 | safeHTML }}" />{{ end }}
-<meta itemprop="wordCount" content="{{ .WordCount }}">
-{{ with .Params.images }}{{ range first 6 . }}
-  <meta itemprop="image" content="{{ . | absURL }}">
-{{ end }}{{ end }}
-
-<!-- Output all taxonomies as schema.org keywords -->
-<meta itemprop="keywords" content="{{ range $plural, $terms := .Site.Taxonomies }}{{ range $term, $val := $terms }}{{ printf "%s," $term }}{{ end }}{{ end }}" />
-{{ end }}`)
-
-	t.AddInternalTemplate("", "google_analytics.html", `{{ with .Site.GoogleAnalytics }}
-<script>
-(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
-
-ga('create', '{{ . }}', 'auto');
-ga('send', 'pageview');
-</script>
-{{ end }}`)
-
-	t.AddInternalTemplate("", "google_analytics_async.html", `{{ with .Site.GoogleAnalytics }}
-<script>
-window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
-ga('create', '{{ . }}', 'auto');
-ga('send', 'pageview');
-</script>
-<script async src='//www.google-analytics.com/analytics.js'></script>
-{{ end }}`)
-
-	t.AddInternalTemplate("_default", "robots.txt", "User-agent: *")
-}
--- a/tpl/template_func_truncate.go
+++ /dev/null
@@ -1,156 +1,0 @@
-// Copyright 2016 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 tpl
-
-import (
-	"errors"
-	"html"
-	"html/template"
-	"regexp"
-	"unicode"
-	"unicode/utf8"
-
-	"github.com/spf13/cast"
-)
-
-var (
-	tagRE        = regexp.MustCompile(`^<(/)?([^ ]+?)(?:(\s*/)| .*?)?>`)
-	htmlSinglets = map[string]bool{
-		"br": true, "col": true, "link": true,
-		"base": true, "img": true, "param": true,
-		"area": true, "hr": true, "input": true,
-	}
-)
-
-type htmlTag struct {
-	name    string
-	pos     int
-	openTag bool
-}
-
-func truncate(a interface{}, options ...interface{}) (template.HTML, error) {
-	length, err := cast.ToIntE(a)
-	if err != nil {
-		return "", err
-	}
-	var textParam interface{}
-	var ellipsis string
-
-	switch len(options) {
-	case 0:
-		return "", errors.New("truncate requires a length and a string")
-	case 1:
-		textParam = options[0]
-		ellipsis = " …"
-	case 2:
-		textParam = options[1]
-		ellipsis, err = cast.ToStringE(options[0])
-		if err != nil {
-			return "", errors.New("ellipsis must be a string")
-		}
-		if _, ok := options[0].(template.HTML); !ok {
-			ellipsis = html.EscapeString(ellipsis)
-		}
-	default:
-		return "", errors.New("too many arguments passed to truncate")
-	}
-	if err != nil {
-		return "", errors.New("text to truncate must be a string")
-	}
-	text, err := cast.ToStringE(textParam)
-	if err != nil {
-		return "", errors.New("text must be a string")
-	}
-
-	_, isHTML := textParam.(template.HTML)
-
-	if utf8.RuneCountInString(text) <= length {
-		if isHTML {
-			return template.HTML(text), nil
-		}
-		return template.HTML(html.EscapeString(text)), nil
-	}
-
-	tags := []htmlTag{}
-	var lastWordIndex, lastNonSpace, currentLen, endTextPos, nextTag int
-
-	for i, r := range text {
-		if i < nextTag {
-			continue
-		}
-
-		if isHTML {
-			// Make sure we keep tag of HTML tags
-			slice := text[i:]
-			m := tagRE.FindStringSubmatchIndex(slice)
-			if len(m) > 0 && m[0] == 0 {
-				nextTag = i + m[1]
-				tagname := slice[m[4]:m[5]]
-				lastWordIndex = lastNonSpace
-				_, singlet := htmlSinglets[tagname]
-				if !singlet && m[6] == -1 {
-					tags = append(tags, htmlTag{name: tagname, pos: i, openTag: m[2] == -1})
-				}
-
-				continue
-			}
-		}
-
-		currentLen++
-		if unicode.IsSpace(r) {
-			lastWordIndex = lastNonSpace
-		} else if unicode.In(r, unicode.Han, unicode.Hangul, unicode.Hiragana, unicode.Katakana) {
-			lastWordIndex = i
-		} else {
-			lastNonSpace = i + utf8.RuneLen(r)
-		}
-
-		if currentLen > length {
-			if lastWordIndex == 0 {
-				endTextPos = i
-			} else {
-				endTextPos = lastWordIndex
-			}
-			out := text[0:endTextPos]
-			if isHTML {
-				out += ellipsis
-				// Close out any open HTML tags
-				var currentTag *htmlTag
-				for i := len(tags) - 1; i >= 0; i-- {
-					tag := tags[i]
-					if tag.pos >= endTextPos || currentTag != nil {
-						if currentTag != nil && currentTag.name == tag.name {
-							currentTag = nil
-						}
-						continue
-					}
-
-					if tag.openTag {
-						out += ("</" + tag.name + ">")
-					} else {
-						currentTag = &tag
-					}
-				}
-
-				return template.HTML(out), nil
-			}
-			return template.HTML(html.EscapeString(out) + ellipsis), nil
-		}
-	}
-
-	if isHTML {
-		return template.HTML(text), nil
-	}
-	return template.HTML(html.EscapeString(text)), nil
-}
--- a/tpl/template_func_truncate_test.go
+++ /dev/null
@@ -1,83 +1,0 @@
-// Copyright 2016 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 tpl
-
-import (
-	"html/template"
-	"reflect"
-	"strings"
-	"testing"
-)
-
-func TestTruncate(t *testing.T) {
-	t.Parallel()
-	var err error
-	cases := []struct {
-		v1    interface{}
-		v2    interface{}
-		v3    interface{}
-		want  interface{}
-		isErr bool
-	}{
-		{10, "I am a test sentence", nil, template.HTML("I am a …"), false},
-		{10, "", "I am a test sentence", template.HTML("I am a"), false},
-		{10, "", "a b c d e f g h i j k", template.HTML("a b c d e"), false},
-		{12, "", "<b>Should be escaped</b>", template.HTML("&lt;b&gt;Should be"), false},
-		{10, template.HTML(" <a href='#'>Read more</a>"), "I am a test sentence", template.HTML("I am a <a href='#'>Read more</a>"), false},
-		{20, template.HTML("I have a <a href='/markdown'>Markdown link</a> inside."), nil, template.HTML("I have a <a href='/markdown'>Markdown …</a>"), false},
-		{10, "IamanextremelylongwordthatjustgoesonandonandonjusttoannoyyoualmostasifIwaswritteninGermanActuallyIbettheresagermanwordforthis", nil, template.HTML("Iamanextre …"), false},
-		{10, template.HTML("<p>IamanextremelylongwordthatjustgoesonandonandonjusttoannoyyoualmostasifIwaswritteninGermanActuallyIbettheresagermanwordforthis</p>"), nil, template.HTML("<p>Iamanextre …</p>"), false},
-		{13, template.HTML("With <a href=\"/markdown\">Markdown</a> inside."), nil, template.HTML("With <a href=\"/markdown\">Markdown …</a>"), false},
-		{14, "Hello中国 Good 好的", nil, template.HTML("Hello中国 Good 好 …"), false},
-		{15, "", template.HTML("A <br> tag that's not closed"), template.HTML("A <br> tag that's"), false},
-		{14, template.HTML("<p>Hello中国 Good 好的</p>"), nil, template.HTML("<p>Hello中国 Good 好 …</p>"), false},
-		{2, template.HTML("<p>P1</p><p>P2</p>"), nil, template.HTML("<p>P1 …</p>"), false},
-		{3, template.HTML(strings.Repeat("<p>P</p>", 20)), nil, template.HTML("<p>P</p><p>P</p><p>P …</p>"), false},
-		{18, template.HTML("<p>test <b>hello</b> test something</p>"), nil, template.HTML("<p>test <b>hello</b> test …</p>"), false},
-		{4, template.HTML("<p>a<b><i>b</b>c d e</p>"), nil, template.HTML("<p>a<b><i>b</b>c …</p>"), false},
-		{10, nil, nil, template.HTML(""), true},
-		{nil, nil, nil, template.HTML(""), true},
-	}
-	for i, c := range cases {
-		var result template.HTML
-		if c.v2 == nil {
-			result, err = truncate(c.v1)
-		} else if c.v3 == nil {
-			result, err = truncate(c.v1, c.v2)
-		} else {
-			result, err = truncate(c.v1, c.v2, c.v3)
-		}
-
-		if c.isErr {
-			if err == nil {
-				t.Errorf("[%d] Slice didn't return an expected error", i)
-			}
-		} else {
-			if err != nil {
-				t.Errorf("[%d] failed: %s", i, err)
-				continue
-			}
-			if !reflect.DeepEqual(result, c.want) {
-				t.Errorf("[%d] got '%s' but expected '%s'", i, result, c.want)
-			}
-		}
-	}
-
-	// Too many arguments
-	_, err = truncate(10, " ...", "I am a test sentence", "wrong")
-	if err == nil {
-		t.Errorf("Should have errored")
-	}
-
-}
--- a/tpl/template_funcs.go
+++ /dev/null
@@ -1,2217 +1,0 @@
-// 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 tpl
-
-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/bep/inflect"
-	"github.com/spf13/afero"
-	"github.com/spf13/cast"
-	"github.com/spf13/hugo/deps"
-	"github.com/spf13/hugo/helpers"
-	jww "github.com/spf13/jwalterweatherman"
-
-	// Importing image codecs for image.DecodeConfig
-	_ "image/gif"
-	_ "image/jpeg"
-	_ "image/png"
-)
-
-// Some of the template funcs are'nt entirely stateless.
-type templateFuncster struct {
-	funcMap        template.FuncMap
-	cachedPartials partialCache
-	*deps.Deps
-}
-
-func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
-	return &templateFuncster{
-		Deps:           deps,
-		cachedPartials: partialCache{p: make(map[string]template.HTML)},
-	}
-}
-
-// 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())
-	}
-}
-
-// ResetCaches resets all caches that might be used during build.
-// TODO(bep) globals move image config cache to funcster
-func ResetCaches() {
-	resetImageConfigCache()
-}
-
-// imageConfigCache is a lockable cache for image.Config objects. It must be
-// locked before reading or writing to config.
-type imageConfigCache struct {
-	config map[string]image.Config
-	sync.RWMutex
-}
-
-var defaultImageConfigCache = imageConfigCache{
-	config: map[string]image.Config{},
-}
-
-// resetImageConfigCache initializes and resets the imageConfig cache for the
-// imageConfig template function. This should be run once before every batch of
-// template renderers so the cache is cleared for new data.
-func resetImageConfigCache() {
-	defaultImageConfigCache.Lock()
-	defer defaultImageConfigCache.Unlock()
-
-	defaultImageConfigCache.config = map[string]image.Config{}
-}
-
-// imageConfig returns the image.Config for the specified path relative to the
-// working directory. resetImageConfigCache must be run beforehand.
-func (t *templateFuncster) imageConfig(path interface{}) (image.Config, error) {
-	filename, err := cast.ToStringE(path)
-	if err != nil {
-		return image.Config{}, err
-	}
-
-	if filename == "" {
-		return image.Config{}, errors.New("imageConfig needs a filename")
-	}
-
-	// Check cache for image config.
-	defaultImageConfigCache.RLock()
-	config, ok := defaultImageConfigCache.config[filename]
-	defaultImageConfigCache.RUnlock()
-
-	if ok {
-		return config, nil
-	}
-
-	f, err := t.Fs.WorkingDir.Open(filename)
-	if err != nil {
-		return image.Config{}, err
-	}
-
-	config, _, err = image.DecodeConfig(f)
-
-	defaultImageConfigCache.Lock()
-	defaultImageConfigCache.config[filename] = config
-	defaultImageConfigCache.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"})
-	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
-}
-
-func refPage(page interface{}, ref, methodName string) template.HTML {
-	value := reflect.ValueOf(page)
-
-	method := value.MethodByName(methodName)
-
-	if method.IsValid() && method.Type().NumIn() == 1 && method.Type().NumOut() == 2 {
-		result := method.Call([]reflect.Value{reflect.ValueOf(ref)})
-
-		url, err := result[0], result[1]
-
-		if !err.IsNil() {
-			jww.ERROR.Printf("%s", err.Interface())
-			return template.HTML(fmt.Sprintf("%s", err.Interface()))
-		}
-
-		if url.String() == "" {
-			jww.ERROR.Printf("ref %s could not be found\n", ref)
-			return template.HTML(ref)
-		}
-
-		return template.HTML(url.String())
-	}
-
-	jww.ERROR.Printf("Can only create references from Page and Node objects.")
-	return template.HTML(ref)
-}
-
-// ref returns the absolute URL path to a given content item.
-func ref(page interface{}, ref string) template.HTML {
-	return refPage(page, ref, "Ref")
-}
-
-// relRef returns the relative URL path to a given content item.
-func relRef(page interface{}, ref string) template.HTML {
-	return refPage(page, ref, "RelRef")
-}
-
-// 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]template.HTML
-}
-
-// 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 template.HTML) {
-	var ok bool
-
-	t.cachedPartials.RLock()
-	p, ok = t.cachedPartials.p[key]
-	t.cachedPartials.RUnlock()
-
-	if ok {
-		return p
-	}
-
-	t.cachedPartials.Lock()
-	if p, ok = t.cachedPartials.p[key]; !ok {
-		t.cachedPartials.Unlock()
-		p = t.Tmpl.Partial(name, context)
-
-		t.cachedPartials.Lock()
-		t.cachedPartials.p[key] = p
-
-	}
-	t.cachedPartials.Unlock()
-
-	return p
-}
-
-// 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) template.HTML {
-	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.imageConfig,
-		"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.Tmpl.Partial,
-		"partialCached": t.partialCached,
-		"plainify":      plainify,
-		"pluralize":     pluralize,
-		"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,
-		"upper":        upper,
-		"urlize":       t.PathSpec.URLize,
-		"where":        where,
-		"i18n":         t.Translate,
-		"T":            t.Translate,
-	}
-
-	t.funcMap = funcMap
-	t.Tmpl.Funcs(funcMap)
-}
--- a/tpl/template_funcs_test.go
+++ /dev/null
@@ -1,2993 +1,0 @@
-// Copyright 2016 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 tpl
-
-import (
-	"bytes"
-	"encoding/base64"
-	"errors"
-	"fmt"
-	"html/template"
-	"image"
-	"image/color"
-	"image/png"
-	"math/rand"
-	"path"
-	"path/filepath"
-	"reflect"
-	"runtime"
-	"strings"
-	"testing"
-	"time"
-
-	"github.com/spf13/hugo/tplapi"
-
-	"github.com/spf13/hugo/deps"
-	"github.com/spf13/hugo/helpers"
-
-	"io/ioutil"
-	"log"
-	"os"
-
-	"github.com/spf13/afero"
-	"github.com/spf13/cast"
-	"github.com/spf13/hugo/config"
-	"github.com/spf13/hugo/hugofs"
-	"github.com/spf13/hugo/i18n"
-	jww "github.com/spf13/jwalterweatherman"
-	"github.com/spf13/viper"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-var (
-	logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
-)
-
-func newDepsConfig(cfg config.Provider) deps.DepsCfg {
-	l := helpers.NewLanguage("en", cfg)
-	l.Set("i18nDir", "i18n")
-	return deps.DepsCfg{
-		Language:            l,
-		Cfg:                 cfg,
-		Fs:                  hugofs.NewMem(l),
-		Logger:              logger,
-		TemplateProvider:    DefaultTemplateProvider,
-		TranslationProvider: i18n.NewTranslationProvider(),
-	}
-}
-
-type tstNoStringer struct {
-}
-
-type tstCompareType int
-
-const (
-	tstEq tstCompareType = iota
-	tstNe
-	tstGt
-	tstGe
-	tstLt
-	tstLe
-)
-
-func tstIsEq(tp tstCompareType) bool {
-	return tp == tstEq || tp == tstGe || tp == tstLe
-}
-
-func tstIsGt(tp tstCompareType) bool {
-	return tp == tstGt || tp == tstGe
-}
-
-func tstIsLt(tp tstCompareType) bool {
-	return tp == tstLt || tp == tstLe
-}
-
-func TestFuncsInTemplate(t *testing.T) {
-	t.Parallel()
-
-	workingDir := "/home/hugo"
-
-	v := viper.New()
-
-	v.Set("workingDir", workingDir)
-	v.Set("multilingual", true)
-
-	fs := hugofs.NewMem(v)
-
-	afero.WriteFile(fs.Source, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755)
-
-	// Add the examples from the docs: As a smoke test and to make sure the examples work.
-	// TODO(bep): docs: fix title example
-	in :=
-		`absLangURL: {{ "index.html" | absLangURL }}
-absURL: {{ "http://gohugo.io/" | absURL }}
-absURL: {{ "mystyle.css" | absURL }}
-absURL: {{ 42 | absURL }}
-add: {{add 1 2}}
-base64Decode 1: {{ "SGVsbG8gd29ybGQ=" | base64Decode }}
-base64Decode 2: {{ 42 | base64Encode | base64Decode }}
-base64Encode: {{ "Hello world" | base64Encode }}
-chomp: {{chomp "<p>Blockhead</p>\n" }}
-dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }}
-delimit: {{ delimit (slice "A" "B" "C") ", " " and " }}
-div: {{div 6 3}}
-echoParam: {{ echoParam .Params "langCode" }}
-emojify: {{ "I :heart: Hugo" | emojify }}
-eq: {{ if eq .Section "blog" }}current{{ end }}
-findRE: {{ findRE "[G|g]o" "Hugo is a static side generator written in Go." "1" }}
-hasPrefix 1: {{ hasPrefix "Hugo" "Hu" }}
-hasPrefix 2: {{ hasPrefix "Hugo" "Fu" }}
-htmlEscape 1: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | safeHTML}}
-htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>"}}
-htmlUnescape 1: {{htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | safeHTML}}
-htmlUnescape 2: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape | safeHTML}}
-htmlUnescape 3: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape }}
-htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | htmlUnescape | safeHTML }}
-htmlUnescape 5: {{ htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | htmlEscape | safeHTML }}
-humanize 1: {{ humanize "my-first-post" }}
-humanize 2: {{ humanize "myCamelPost" }}
-humanize 3: {{ humanize "52" }}
-humanize 4: {{ humanize 103 }}
-in: {{ if in "this string contains a substring" "substring" }}Substring found!{{ end }}
-jsonify: {{ (slice "A" "B" "C") | jsonify }}
-lower: {{lower "BatMan"}}
-markdownify: {{ .Title | markdownify}}
-md5: {{ md5 "Hello world, gophers!" }}
-mod: {{mod 15 3}}
-modBool: {{modBool 15 3}}
-mul: {{mul 2 3}}
-plainify: {{ plainify  "Hello <strong>world</strong>, gophers!" }}
-pluralize: {{ "cat" | pluralize }}
-querify 1: {{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}
-querify 2: <a href="https://www.google.com?{{ (querify "q" "test" "page" 3) | safeURL }}">Search</a>
-readDir: {{ range (readDir ".") }}{{ .Name }}{{ end }}
-readFile: {{ readFile "README.txt" }}
-relLangURL: {{ "index.html" | relLangURL }}
-relURL 1: {{ "http://gohugo.io/" | relURL }}
-relURL 2: {{ "mystyle.css" | relURL }}
-relURL 3: {{ mul 2 21 | relURL }}
-replace: {{ replace "Batman and Robin" "Robin" "Catwoman" }}
-replaceRE: {{ "http://gohugo.io/docs" | replaceRE "^https?://([^/]+).*" "$1" }}
-safeCSS: {{ "Bat&Man" | safeCSS | safeCSS }}
-safeHTML: {{ "Bat&Man" | safeHTML | safeHTML }}
-safeHTML: {{ "Bat&Man" | safeHTML }}
-safeJS: {{ "(1*2)" | safeJS | safeJS }}
-safeURL: {{ "http://gohugo.io" | safeURL | safeURL }}
-seq: {{ seq 3 }}
-sha1: {{ sha1 "Hello world, gophers!" }}
-sha256: {{ sha256 "Hello world, gophers!" }}
-singularize: {{ "cats" | singularize }}
-slicestr: {{slicestr "BatMan" 0 3}}
-slicestr: {{slicestr "BatMan" 3}}
-sort: {{ slice "B" "C" "A" | sort }}
-sub: {{sub 3 2}}
-substr: {{substr "BatMan" 0 -3}}
-substr: {{substr "BatMan" 3 3}}
-title: {{title "Bat man"}}
-time: {{ (time "2015-01-21").Year }}
-trim: {{ trim "++Batman--" "+-" }}
-truncate: {{ "this is a very long text" | truncate 10 " ..." }}
-truncate: {{ "With [Markdown](/markdown) inside." | markdownify | truncate 14 }}
-upper: {{upper "BatMan"}}
-urlize: {{ "Bat Man" | urlize }}
-`
-
-	expected := `absLangURL: http://mysite.com/hugo/en/index.html
-absURL: http://gohugo.io/
-absURL: http://mysite.com/hugo/mystyle.css
-absURL: http://mysite.com/hugo/42
-add: 3
-base64Decode 1: Hello world
-base64Decode 2: 42
-base64Encode: SGVsbG8gd29ybGQ=
-chomp: <p>Blockhead</p>
-dateFormat: Wednesday, Jan 21, 2015
-delimit: A, B and C
-div: 2
-echoParam: en
-emojify: I ❤️ Hugo
-eq: current
-findRE: [go]
-hasPrefix 1: true
-hasPrefix 2: false
-htmlEscape 1: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
-htmlEscape 2: Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;
-htmlUnescape 1: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 2: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 3: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
-htmlUnescape 4: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 5: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
-humanize 1: My first post
-humanize 2: My camel post
-humanize 3: 52nd
-humanize 4: 103rd
-in: Substring found!
-jsonify: ["A","B","C"]
-lower: batman
-markdownify: <strong>BatMan</strong>
-md5: b3029f756f98f79e7f1b7f1d1f0dd53b
-mod: 0
-modBool: true
-mul: 6
-plainify: Hello world, gophers!
-pluralize: cats
-querify 1: bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose
-querify 2: <a href="https://www.google.com?page=3&amp;q=test">Search</a>
-readDir: README.txt
-readFile: Hugo Rocks!
-relLangURL: /hugo/en/index.html
-relURL 1: http://gohugo.io/
-relURL 2: /hugo/mystyle.css
-relURL 3: /hugo/42
-replace: Batman and Catwoman
-replaceRE: gohugo.io
-safeCSS: Bat&amp;Man
-safeHTML: Bat&Man
-safeHTML: Bat&Man
-safeJS: (1*2)
-safeURL: http://gohugo.io
-seq: [1 2 3]
-sha1: c8b5b0e33d408246e30f53e32b8f7627a7a649d4
-sha256: 6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46
-singularize: cat
-slicestr: Bat
-slicestr: Man
-sort: [A B C]
-sub: 1
-substr: Bat
-substr: Man
-title: Bat Man
-time: 2015
-trim: Batman
-truncate: this is a ...
-truncate: With <a href="/markdown">Markdown …</a>
-upper: BATMAN
-urlize: bat-man
-`
-
-	var b bytes.Buffer
-
-	var data struct {
-		Title   string
-		Section string
-		Params  map[string]interface{}
-	}
-
-	data.Title = "**BatMan**"
-	data.Section = "blog"
-	data.Params = map[string]interface{}{"langCode": "en"}
-
-	v.Set("baseURL", "http://mysite.com/hugo/")
-	v.Set("CurrentContentLanguage", helpers.NewLanguage("en", v))
-
-	config := newDepsConfig(v)
-	config.WithTemplate = func(templ tplapi.Template) error {
-		if _, err := templ.New("test").Parse(in); err != nil {
-			t.Fatal("Got error on parse", err)
-		}
-		return nil
-	}
-	config.Fs = fs
-
-	d := deps.New(config)
-	if err := d.LoadResources(); err != nil {
-		t.Fatal(err)
-	}
-
-	err := d.Tmpl.Lookup("test").Execute(&b, &data)
-
-	if err != nil {
-		t.Fatal("Got error on execute", err)
-	}
-
-	if b.String() != expected {
-		sl1 := strings.Split(b.String(), "\n")
-		sl2 := strings.Split(expected, "\n")
-		t.Errorf("Diff:\n%q", helpers.DiffStringSlices(sl1, sl2))
-	}
-}
-
-func TestCompare(t *testing.T) {
-	t.Parallel()
-	for _, this := range []struct {
-		tstCompareType
-		funcUnderTest func(a, b interface{}) bool
-	}{
-		{tstGt, gt},
-		{tstLt, lt},
-		{tstGe, ge},
-		{tstLe, le},
-		{tstEq, eq},
-		{tstNe, ne},
-	} {
-		doTestCompare(t, this.tstCompareType, this.funcUnderTest)
-	}
-}
-
-func doTestCompare(t *testing.T, tp tstCompareType, funcUnderTest func(a, b interface{}) bool) {
-	for i, this := range []struct {
-		left            interface{}
-		right           interface{}
-		expectIndicator int
-	}{
-		{5, 8, -1},
-		{8, 5, 1},
-		{5, 5, 0},
-		{int(5), int64(5), 0},
-		{int32(5), int(5), 0},
-		{int16(4), int(5), -1},
-		{uint(15), uint64(15), 0},
-		{-2, 1, -1},
-		{2, -5, 1},
-		{0.0, 1.23, -1},
-		{1.1, 1.1, 0},
-		{float32(1.0), float64(1.0), 0},
-		{1.23, 0.0, 1},
-		{"5", "5", 0},
-		{"8", "5", 1},
-		{"5", "0001", 1},
-		{[]int{100, 99}, []int{1, 2, 3, 4}, -1},
-		{cast.ToTime("2015-11-20"), cast.ToTime("2015-11-20"), 0},
-		{cast.ToTime("2015-11-19"), cast.ToTime("2015-11-20"), -1},
-		{cast.ToTime("2015-11-20"), cast.ToTime("2015-11-19"), 1},
-	} {
-		result := funcUnderTest(this.left, this.right)
-		success := false
-
-		if this.expectIndicator == 0 {
-			if tstIsEq(tp) {
-				success = result
-			} else {
-				success = !result
-			}
-		}
-
-		if this.expectIndicator < 0 {
-			success = result && (tstIsLt(tp) || tp == tstNe)
-			success = success || (!result && !tstIsLt(tp))
-		}
-
-		if this.expectIndicator > 0 {
-			success = result && (tstIsGt(tp) || tp == tstNe)
-			success = success || (!result && (!tstIsGt(tp) || tp != tstNe))
-		}
-
-		if !success {
-			t.Errorf("[%d][%s] %v compared to %v: %t", i, path.Base(runtime.FuncForPC(reflect.ValueOf(funcUnderTest).Pointer()).Name()), this.left, this.right, result)
-		}
-	}
-}
-
-func TestMod(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		a      interface{}
-		b      interface{}
-		expect interface{}
-	}{
-		{3, 2, int64(1)},
-		{3, 1, int64(0)},
-		{3, 0, false},
-		{0, 3, int64(0)},
-		{3.1, 2, false},
-		{3, 2.1, false},
-		{3.1, 2.1, false},
-		{int8(3), int8(2), int64(1)},
-		{int16(3), int16(2), int64(1)},
-		{int32(3), int32(2), int64(1)},
-		{int64(3), int64(2), int64(1)},
-	} {
-		result, err := mod(this.a, this.b)
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] modulo 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] modulo got %v but expected %v", i, result, this.expect)
-			}
-		}
-	}
-}
-
-func TestModBool(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		a      interface{}
-		b      interface{}
-		expect interface{}
-	}{
-		{3, 3, true},
-		{3, 2, false},
-		{3, 1, true},
-		{3, 0, nil},
-		{0, 3, true},
-		{3.1, 2, nil},
-		{3, 2.1, nil},
-		{3.1, 2.1, nil},
-		{int8(3), int8(3), true},
-		{int8(3), int8(2), false},
-		{int16(3), int16(3), true},
-		{int16(3), int16(2), false},
-		{int32(3), int32(3), true},
-		{int32(3), int32(2), false},
-		{int64(3), int64(3), true},
-		{int64(3), int64(2), false},
-	} {
-		result, err := modBool(this.a, this.b)
-		if this.expect == nil {
-			if err == nil {
-				t.Errorf("[%d] modulo 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] modulo got %v but expected %v", i, result, this.expect)
-			}
-		}
-	}
-}
-
-func TestFirst(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		count    interface{}
-		sequence interface{}
-		expect   interface{}
-	}{
-		{int(2), []string{"a", "b", "c"}, []string{"a", "b"}},
-		{int32(3), []string{"a", "b"}, []string{"a", "b"}},
-		{int64(2), []int{100, 200, 300}, []int{100, 200}},
-		{100, []int{100, 200}, []int{100, 200}},
-		{"1", []int{100, 200, 300}, []int{100}},
-		{int64(-1), []int{100, 200, 300}, false},
-		{"noint", []int{100, 200, 300}, false},
-		{1, nil, false},
-		{nil, []int{100}, false},
-		{1, t, false},
-		{1, (*string)(nil), false},
-	} {
-		results, err := first(this.count, this.sequence)
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] First didn't return an expected error", i)
-			}
-		} else {
-			if err != nil {
-				t.Errorf("[%d] failed: %s", i, err)
-				continue
-			}
-			if !reflect.DeepEqual(results, this.expect) {
-				t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect)
-			}
-		}
-	}
-}
-
-func TestLast(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		count    interface{}
-		sequence interface{}
-		expect   interface{}
-	}{
-		{int(2), []string{"a", "b", "c"}, []string{"b", "c"}},
-		{int32(3), []string{"a", "b"}, []string{"a", "b"}},
-		{int64(2), []int{100, 200, 300}, []int{200, 300}},
-		{100, []int{100, 200}, []int{100, 200}},
-		{"1", []int{100, 200, 300}, []int{300}},
-		{int64(-1), []int{100, 200, 300}, false},
-		{"noint", []int{100, 200, 300}, false},
-		{1, nil, false},
-		{nil, []int{100}, false},
-		{1, t, false},
-		{1, (*string)(nil), false},
-	} {
-		results, err := last(this.count, this.sequence)
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] First didn't return an expected error", i)
-			}
-		} else {
-			if err != nil {
-				t.Errorf("[%d] failed: %s", i, err)
-				continue
-			}
-			if !reflect.DeepEqual(results, this.expect) {
-				t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect)
-			}
-		}
-	}
-}
-
-func TestAfter(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		count    interface{}
-		sequence interface{}
-		expect   interface{}
-	}{
-		{int(2), []string{"a", "b", "c", "d"}, []string{"c", "d"}},
-		{int32(3), []string{"a", "b"}, false},
-		{int64(2), []int{100, 200, 300}, []int{300}},
-		{100, []int{100, 200}, false},
-		{"1", []int{100, 200, 300}, []int{200, 300}},
-		{int64(-1), []int{100, 200, 300}, false},
-		{"noint", []int{100, 200, 300}, false},
-		{1, nil, false},
-		{nil, []int{100}, false},
-		{1, t, false},
-		{1, (*string)(nil), false},
-	} {
-		results, err := after(this.count, this.sequence)
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] First didn't return an expected error", i)
-			}
-		} else {
-			if err != nil {
-				t.Errorf("[%d] failed: %s", i, err)
-				continue
-			}
-			if !reflect.DeepEqual(results, this.expect) {
-				t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect)
-			}
-		}
-	}
-}
-
-func TestShuffleInputAndOutputFormat(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		sequence interface{}
-		success  bool
-	}{
-		{[]string{"a", "b", "c", "d"}, true},
-		{[]int{100, 200, 300}, true},
-		{[]int{100, 200, 300}, true},
-		{[]int{100, 200}, true},
-		{[]string{"a", "b"}, true},
-		{[]int{100, 200, 300}, true},
-		{[]int{100, 200, 300}, true},
-		{[]int{100}, true},
-		{nil, false},
-		{t, false},
-		{(*string)(nil), false},
-	} {
-		results, err := shuffle(this.sequence)
-		if !this.success {
-			if err == nil {
-				t.Errorf("[%d] First didn't return an expected error", i)
-			}
-		} else {
-			resultsv := reflect.ValueOf(results)
-			sequencev := reflect.ValueOf(this.sequence)
-
-			if err != nil {
-				t.Errorf("[%d] failed: %s", i, err)
-				continue
-			}
-
-			if resultsv.Len() != sequencev.Len() {
-				t.Errorf("Expected %d items, got %d items", sequencev.Len(), resultsv.Len())
-			}
-		}
-	}
-}
-
-func TestShuffleRandomising(t *testing.T) {
-	t.Parallel()
-	// Note that this test can fail with false negative result if the shuffle
-	// of the sequence happens to be the same as the original sequence. However
-	// the propability of the event is 10^-158 which is negligible.
-	sequenceLength := 100
-	rand.Seed(time.Now().UTC().UnixNano())
-
-	for _, this := range []struct {
-		sequence []int
-	}{
-		{rand.Perm(sequenceLength)},
-	} {
-		results, _ := shuffle(this.sequence)
-
-		resultsv := reflect.ValueOf(results)
-
-		allSame := true
-		for index, value := range this.sequence {
-			allSame = allSame && (resultsv.Index(index).Interface() == value)
-		}
-
-		if allSame {
-			t.Error("Expected sequence to be shuffled but was in the same order")
-		}
-	}
-}
-
-func TestDictionary(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		v1            []interface{}
-		expecterr     bool
-		expectedValue map[string]interface{}
-	}{
-		{[]interface{}{"a", "b"}, false, map[string]interface{}{"a": "b"}},
-		{[]interface{}{5, "b"}, true, nil},
-		{[]interface{}{"a", 12, "b", []int{4}}, false, map[string]interface{}{"a": 12, "b": []int{4}}},
-		{[]interface{}{"a", "b", "c"}, true, nil},
-	} {
-		r, e := dictionary(this.v1...)
-
-		if (this.expecterr && e == nil) || (!this.expecterr && e != nil) {
-			t.Errorf("[%d] got an unexpected error: %s", i, e)
-		} else if !this.expecterr {
-			if !reflect.DeepEqual(r, this.expectedValue) {
-				t.Errorf("[%d] got %v but expected %v", i, r, this.expectedValue)
-			}
-		}
-	}
-}
-
-func blankImage(width, height int) []byte {
-	var buf bytes.Buffer
-	img := image.NewRGBA(image.Rect(0, 0, width, height))
-	if err := png.Encode(&buf, img); err != nil {
-		panic(err)
-	}
-	return buf.Bytes()
-}
-
-func TestImageConfig(t *testing.T) {
-	t.Parallel()
-
-	workingDir := "/home/hugo"
-
-	v := viper.New()
-
-	v.Set("workingDir", workingDir)
-
-	f := newTestFuncsterWithViper(v)
-
-	for i, this := range []struct {
-		resetCache bool
-		path       string
-		input      []byte
-		expected   image.Config
-	}{
-		// Make sure that the cache is initialized by default.
-		{
-			resetCache: false,
-			path:       "a.png",
-			input:      blankImage(10, 10),
-			expected: image.Config{
-				Width:      10,
-				Height:     10,
-				ColorModel: color.NRGBAModel,
-			},
-		},
-		{
-			resetCache: true,
-			path:       "a.png",
-			input:      blankImage(10, 10),
-			expected: image.Config{
-				Width:      10,
-				Height:     10,
-				ColorModel: color.NRGBAModel,
-			},
-		},
-		{
-			resetCache: false,
-			path:       "b.png",
-			input:      blankImage(20, 15),
-			expected: image.Config{
-				Width:      20,
-				Height:     15,
-				ColorModel: color.NRGBAModel,
-			},
-		},
-		{
-			resetCache: false,
-			path:       "a.png",
-			input:      blankImage(20, 15),
-			expected: image.Config{
-				Width:      10,
-				Height:     10,
-				ColorModel: color.NRGBAModel,
-			},
-		},
-		{
-			resetCache: true,
-			path:       "a.png",
-			input:      blankImage(20, 15),
-			expected: image.Config{
-				Width:      20,
-				Height:     15,
-				ColorModel: color.NRGBAModel,
-			},
-		},
-	} {
-		afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, this.path), this.input, 0755)
-
-		if this.resetCache {
-			resetImageConfigCache()
-		}
-
-		result, err := f.imageConfig(this.path)
-		if err != nil {
-			t.Errorf("imageConfig returned error: %s", err)
-		}
-
-		if !reflect.DeepEqual(result, this.expected) {
-			t.Errorf("[%d] imageConfig: expected '%v', got '%v'", i, this.expected, result)
-		}
-
-		if len(defaultImageConfigCache.config) == 0 {
-			t.Error("defaultImageConfigCache should have at least 1 item")
-		}
-	}
-
-	if _, err := f.imageConfig(t); err == nil {
-		t.Error("Expected error from imageConfig when passed invalid path")
-	}
-
-	if _, err := f.imageConfig("non-existent.png"); err == nil {
-		t.Error("Expected error from imageConfig when passed non-existent file")
-	}
-
-	if _, err := f.imageConfig(""); err == nil {
-		t.Error("Expected error from imageConfig when passed empty path")
-	}
-
-	// test cache clearing
-	ResetCaches()
-
-	if len(defaultImageConfigCache.config) != 0 {
-		t.Error("ResetCaches should have cleared defaultImageConfigCache")
-	}
-}
-
-func TestIn(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		v1     interface{}
-		v2     interface{}
-		expect bool
-	}{
-		{[]string{"a", "b", "c"}, "b", true},
-		{[]interface{}{"a", "b", "c"}, "b", true},
-		{[]interface{}{"a", "b", "c"}, "d", false},
-		{[]string{"a", "b", "c"}, "d", false},
-		{[]string{"a", "12", "c"}, 12, false},
-		{[]int{1, 2, 4}, 2, true},
-		{[]interface{}{1, 2, 4}, 2, true},
-		{[]interface{}{1, 2, 4}, nil, false},
-		{[]interface{}{nil}, nil, false},
-		{[]int{1, 2, 4}, 3, false},
-		{[]float64{1.23, 2.45, 4.67}, 1.23, true},
-		{[]float64{1.234567, 2.45, 4.67}, 1.234568, false},
-		{"this substring should be found", "substring", true},
-		{"this substring should not be found", "subseastring", false},
-	} {
-		result := in(this.v1, this.v2)
-
-		if result != this.expect {
-			t.Errorf("[%d] got %v but expected %v", i, result, this.expect)
-		}
-	}
-}
-
-func TestSlicestr(t *testing.T) {
-	t.Parallel()
-	var err error
-	for i, this := range []struct {
-		v1     interface{}
-		v2     interface{}
-		v3     interface{}
-		expect interface{}
-	}{
-		{"abc", 1, 2, "b"},
-		{"abc", 1, 3, "bc"},
-		{"abcdef", 1, int8(3), "bc"},
-		{"abcdef", 1, int16(3), "bc"},
-		{"abcdef", 1, int32(3), "bc"},
-		{"abcdef", 1, int64(3), "bc"},
-		{"abc", 0, 1, "a"},
-		{"abcdef", nil, nil, "abcdef"},
-		{"abcdef", 0, 6, "abcdef"},
-		{"abcdef", 0, 2, "ab"},
-		{"abcdef", 2, nil, "cdef"},
-		{"abcdef", int8(2), nil, "cdef"},
-		{"abcdef", int16(2), nil, "cdef"},
-		{"abcdef", int32(2), nil, "cdef"},
-		{"abcdef", int64(2), nil, "cdef"},
-		{123, 1, 3, "23"},
-		{"abcdef", 6, nil, false},
-		{"abcdef", 4, 7, false},
-		{"abcdef", -1, nil, false},
-		{"abcdef", -1, 7, false},
-		{"abcdef", 1, -1, false},
-		{tstNoStringer{}, 0, 1, false},
-		{"ĀĀĀ", 0, 1, "Ā"}, // issue #1333
-		{"a", t, nil, false},
-		{"a", 1, t, false},
-	} {
-		var result string
-		if this.v2 == nil {
-			result, err = slicestr(this.v1)
-		} else if this.v3 == nil {
-			result, err = slicestr(this.v1, this.v2)
-		} else {
-			result, err = slicestr(this.v1, this.v2, this.v3)
-		}
-
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] Slice 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] got %s but expected %s", i, result, this.expect)
-			}
-		}
-	}
-
-	// Too many arguments
-	_, err = slicestr("a", 1, 2, 3)
-	if err == nil {
-		t.Errorf("Should have errored")
-	}
-}
-
-func TestHasPrefix(t *testing.T) {
-	t.Parallel()
-	cases := []struct {
-		s      interface{}
-		prefix interface{}
-		want   interface{}
-		isErr  bool
-	}{
-		{"abcd", "ab", true, false},
-		{"abcd", "cd", false, false},
-		{template.HTML("abcd"), "ab", true, false},
-		{template.HTML("abcd"), "cd", false, false},
-		{template.HTML("1234"), 12, true, false},
-		{template.HTML("1234"), 34, false, false},
-		{[]byte("abcd"), "ab", true, false},
-	}
-
-	for i, c := range cases {
-		res, err := hasPrefix(c.s, c.prefix)
-		if (err != nil) != c.isErr {
-			t.Fatalf("[%d] unexpected isErr state: want %v, got %v, err = %v", i, c.isErr, err != nil, err)
-		}
-		if res != c.want {
-			t.Errorf("[%d] want %v, got %v", i, c.want, res)
-		}
-	}
-}
-
-func TestSubstr(t *testing.T) {
-	t.Parallel()
-	var err error
-	var n int
-	for i, this := range []struct {
-		v1     interface{}
-		v2     interface{}
-		v3     interface{}
-		expect interface{}
-	}{
-		{"abc", 1, 2, "bc"},
-		{"abc", 0, 1, "a"},
-		{"abcdef", -1, 2, "ef"},
-		{"abcdef", -3, 3, "bcd"},
-		{"abcdef", 0, -1, "abcde"},
-		{"abcdef", 2, -1, "cde"},
-		{"abcdef", 4, -4, false},
-		{"abcdef", 7, 1, false},
-		{"abcdef", 1, 100, "bcdef"},
-		{"abcdef", -100, 3, "abc"},
-		{"abcdef", -3, -1, "de"},
-		{"abcdef", 2, nil, "cdef"},
-		{"abcdef", int8(2), nil, "cdef"},
-		{"abcdef", int16(2), nil, "cdef"},
-		{"abcdef", int32(2), nil, "cdef"},
-		{"abcdef", int64(2), nil, "cdef"},
-		{"abcdef", 2, int8(3), "cde"},
-		{"abcdef", 2, int16(3), "cde"},
-		{"abcdef", 2, int32(3), "cde"},
-		{"abcdef", 2, int64(3), "cde"},
-		{123, 1, 3, "23"},
-		{1.2e3, 0, 4, "1200"},
-		{tstNoStringer{}, 0, 1, false},
-		{"abcdef", 2.0, nil, "cdef"},
-		{"abcdef", 2.0, 2, "cd"},
-		{"abcdef", 2, 2.0, "cd"},
-		{"ĀĀĀ", 1, 2, "ĀĀ"}, // # issue 1333
-		{"abcdef", "doo", nil, false},
-		{"abcdef", "doo", "doo", false},
-		{"abcdef", 1, "doo", false},
-	} {
-		var result string
-		n = i
-
-		if this.v3 == nil {
-			result, err = substr(this.v1, this.v2)
-		} else {
-			result, err = substr(this.v1, this.v2, this.v3)
-		}
-
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] Substr 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] got %s but expected %s", i, result, this.expect)
-			}
-		}
-	}
-
-	n++
-	_, err = substr("abcdef")
-	if err == nil {
-		t.Errorf("[%d] Substr didn't return an expected error", n)
-	}
-
-	n++
-	_, err = substr("abcdef", 1, 2, 3)
-	if err == nil {
-		t.Errorf("[%d] Substr didn't return an expected error", n)
-	}
-}
-
-func TestSplit(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		v1     interface{}
-		v2     string
-		expect interface{}
-	}{
-		{"a, b", ", ", []string{"a", "b"}},
-		{"a & b & c", " & ", []string{"a", "b", "c"}},
-		{"http://example.com", "http://", []string{"", "example.com"}},
-		{123, "2", []string{"1", "3"}},
-		{tstNoStringer{}, ",", false},
-	} {
-		result, err := split(this.v1, this.v2)
-
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] Split 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] got %s but expected %s", i, result, this.expect)
-			}
-		}
-	}
-}
-
-func TestIntersect(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		sequence1 interface{}
-		sequence2 interface{}
-		expect    interface{}
-	}{
-		{[]string{"a", "b", "c", "c"}, []string{"a", "b", "b"}, []string{"a", "b"}},
-		{[]string{"a", "b"}, []string{"a", "b", "c"}, []string{"a", "b"}},
-		{[]string{"a", "b", "c"}, []string{"d", "e"}, []string{}},
-		{[]string{}, []string{}, []string{}},
-		{[]string{"a", "b"}, nil, make([]interface{}, 0)},
-		{nil, []string{"a", "b"}, make([]interface{}, 0)},
-		{nil, nil, make([]interface{}, 0)},
-		{[]string{"1", "2"}, []int{1, 2}, []string{}},
-		{[]int{1, 2}, []string{"1", "2"}, []int{}},
-		{[]int{1, 2, 4}, []int{2, 4}, []int{2, 4}},
-		{[]int{2, 4}, []int{1, 2, 4}, []int{2, 4}},
-		{[]int{1, 2, 4}, []int{3, 6}, []int{}},
-		{[]float64{2.2, 4.4}, []float64{1.1, 2.2, 4.4}, []float64{2.2, 4.4}},
-	} {
-		results, err := intersect(this.sequence1, this.sequence2)
-		if err != nil {
-			t.Errorf("[%d] failed: %s", i, err)
-			continue
-		}
-		if !reflect.DeepEqual(results, this.expect) {
-			t.Errorf("[%d] got %v but expected %v", i, results, this.expect)
-		}
-	}
-
-	_, err1 := intersect("not an array or slice", []string{"a"})
-
-	if err1 == nil {
-		t.Error("Expected error for non array as first arg")
-	}
-
-	_, err2 := intersect([]string{"a"}, "not an array or slice")
-
-	if err2 == nil {
-		t.Error("Expected error for non array as second arg")
-	}
-}
-
-func TestIsSet(t *testing.T) {
-	t.Parallel()
-	aSlice := []interface{}{1, 2, 3, 5}
-	aMap := map[string]interface{}{"a": 1, "b": 2}
-
-	assert.True(t, isSet(aSlice, 2))
-	assert.True(t, isSet(aMap, "b"))
-	assert.False(t, isSet(aSlice, 22))
-	assert.False(t, isSet(aMap, "bc"))
-}
-
-func (x *TstX) TstRp() string {
-	return "r" + x.A
-}
-
-func (x TstX) TstRv() string {
-	return "r" + x.B
-}
-
-func (x TstX) unexportedMethod() string {
-	return x.unexported
-}
-
-func (x TstX) MethodWithArg(s string) string {
-	return s
-}
-
-func (x TstX) MethodReturnNothing() {}
-
-func (x TstX) MethodReturnErrorOnly() error {
-	return errors.New("some error occurred")
-}
-
-func (x TstX) MethodReturnTwoValues() (string, string) {
-	return "foo", "bar"
-}
-
-func (x TstX) MethodReturnValueWithError() (string, error) {
-	return "", errors.New("some error occurred")
-}
-
-func (x TstX) String() string {
-	return fmt.Sprintf("A: %s, B: %s", x.A, x.B)
-}
-
-type TstX struct {
-	A, B       string
-	unexported string
-}
-
-func TestTimeUnix(t *testing.T) {
-	t.Parallel()
-	var sec int64 = 1234567890
-	tv := reflect.ValueOf(time.Unix(sec, 0))
-	i := 1
-
-	res := toTimeUnix(tv)
-	if sec != res {
-		t.Errorf("[%d] timeUnix got %v but expected %v", i, res, sec)
-	}
-
-	i++
-	func(t *testing.T) {
-		defer func() {
-			if err := recover(); err == nil {
-				t.Errorf("[%d] timeUnix didn't return an expected error", i)
-			}
-		}()
-		iv := reflect.ValueOf(sec)
-		toTimeUnix(iv)
-	}(t)
-}
-
-func TestEvaluateSubElem(t *testing.T) {
-	t.Parallel()
-	tstx := TstX{A: "foo", B: "bar"}
-	var inner struct {
-		S fmt.Stringer
-	}
-	inner.S = tstx
-	interfaceValue := reflect.ValueOf(&inner).Elem().Field(0)
-
-	for i, this := range []struct {
-		value  reflect.Value
-		key    string
-		expect interface{}
-	}{
-		{reflect.ValueOf(tstx), "A", "foo"},
-		{reflect.ValueOf(&tstx), "TstRp", "rfoo"},
-		{reflect.ValueOf(tstx), "TstRv", "rbar"},
-		//{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), 1, "foo"},
-		{reflect.ValueOf(map[string]string{"key1": "foo", "key2": "bar"}), "key1", "foo"},
-		{interfaceValue, "String", "A: foo, B: bar"},
-		{reflect.Value{}, "foo", false},
-		//{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), 1.2, false},
-		{reflect.ValueOf(tstx), "unexported", false},
-		{reflect.ValueOf(tstx), "unexportedMethod", false},
-		{reflect.ValueOf(tstx), "MethodWithArg", false},
-		{reflect.ValueOf(tstx), "MethodReturnNothing", false},
-		{reflect.ValueOf(tstx), "MethodReturnErrorOnly", false},
-		{reflect.ValueOf(tstx), "MethodReturnTwoValues", false},
-		{reflect.ValueOf(tstx), "MethodReturnValueWithError", false},
-		{reflect.ValueOf((*TstX)(nil)), "A", false},
-		{reflect.ValueOf(tstx), "C", false},
-		{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), "1", false},
-		{reflect.ValueOf([]string{"foo", "bar"}), "1", false},
-	} {
-		result, err := evaluateSubElem(this.value, this.key)
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] evaluateSubElem didn't return an expected error", i)
-			}
-		} else {
-			if err != nil {
-				t.Errorf("[%d] failed: %s", i, err)
-				continue
-			}
-			if result.Kind() != reflect.String || result.String() != this.expect {
-				t.Errorf("[%d] evaluateSubElem with %v got %v but expected %v", i, this.key, result, this.expect)
-			}
-		}
-	}
-}
-
-func TestCheckCondition(t *testing.T) {
-	t.Parallel()
-	type expect struct {
-		result  bool
-		isError bool
-	}
-
-	for i, this := range []struct {
-		value reflect.Value
-		match reflect.Value
-		op    string
-		expect
-	}{
-		{reflect.ValueOf(123), reflect.ValueOf(123), "", expect{true, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf("foo"), "", expect{true, false}},
-		{
-			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
-			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
-			"",
-			expect{true, false},
-		},
-		{reflect.ValueOf(true), reflect.ValueOf(true), "", expect{true, false}},
-		{reflect.ValueOf(nil), reflect.ValueOf(nil), "", expect{true, false}},
-		{reflect.ValueOf(123), reflect.ValueOf(456), "!=", expect{true, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf("bar"), "!=", expect{true, false}},
-		{
-			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
-			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
-			"!=",
-			expect{true, false},
-		},
-		{reflect.ValueOf(true), reflect.ValueOf(false), "!=", expect{true, false}},
-		{reflect.ValueOf(123), reflect.ValueOf(nil), "!=", expect{true, false}},
-		{reflect.ValueOf(456), reflect.ValueOf(123), ">=", expect{true, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf("bar"), ">=", expect{true, false}},
-		{
-			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
-			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
-			">=",
-			expect{true, false},
-		},
-		{reflect.ValueOf(456), reflect.ValueOf(123), ">", expect{true, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf("bar"), ">", expect{true, false}},
-		{
-			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
-			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
-			">",
-			expect{true, false},
-		},
-		{reflect.ValueOf(123), reflect.ValueOf(456), "<=", expect{true, false}},
-		{reflect.ValueOf("bar"), reflect.ValueOf("foo"), "<=", expect{true, false}},
-		{
-			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
-			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
-			"<=",
-			expect{true, false},
-		},
-		{reflect.ValueOf(123), reflect.ValueOf(456), "<", expect{true, false}},
-		{reflect.ValueOf("bar"), reflect.ValueOf("foo"), "<", expect{true, false}},
-		{
-			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
-			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
-			"<",
-			expect{true, false},
-		},
-		{reflect.ValueOf(123), reflect.ValueOf([]int{123, 45, 678}), "in", expect{true, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf([]string{"foo", "bar", "baz"}), "in", expect{true, false}},
-		{
-			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
-			reflect.ValueOf([]time.Time{
-				time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC),
-				time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC),
-				time.Date(2015, time.June, 26, 19, 18, 56, 12345, time.UTC),
-			}),
-			"in",
-			expect{true, false},
-		},
-		{reflect.ValueOf(123), reflect.ValueOf([]int{45, 678}), "not in", expect{true, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf([]string{"bar", "baz"}), "not in", expect{true, false}},
-		{
-			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
-			reflect.ValueOf([]time.Time{
-				time.Date(2015, time.February, 26, 19, 18, 56, 12345, time.UTC),
-				time.Date(2015, time.March, 26, 19, 18, 56, 12345, time.UTC),
-				time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC),
-			}),
-			"not in",
-			expect{true, false},
-		},
-		{reflect.ValueOf("foo"), reflect.ValueOf("bar-foo-baz"), "in", expect{true, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf("bar--baz"), "not in", expect{true, false}},
-		{reflect.Value{}, reflect.ValueOf("foo"), "", expect{false, false}},
-		{reflect.ValueOf("foo"), reflect.Value{}, "", expect{false, false}},
-		{reflect.ValueOf((*TstX)(nil)), reflect.ValueOf("foo"), "", expect{false, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf((*TstX)(nil)), "", expect{false, false}},
-		{reflect.ValueOf(true), reflect.ValueOf("foo"), "", expect{false, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf(true), "", expect{false, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf(map[int]string{}), "", expect{false, false}},
-		{reflect.ValueOf("foo"), reflect.ValueOf([]int{1, 2}), "", expect{false, false}},
-		{reflect.ValueOf((*TstX)(nil)), reflect.ValueOf((*TstX)(nil)), ">", expect{false, false}},
-		{reflect.ValueOf(true), reflect.ValueOf(false), ">", expect{false, false}},
-		{reflect.ValueOf(123), reflect.ValueOf([]int{}), "in", expect{false, false}},
-		{reflect.ValueOf(123), reflect.ValueOf(123), "op", expect{false, true}},
-	} {
-		result, err := checkCondition(this.value, this.match, this.op)
-		if this.expect.isError {
-			if err == nil {
-				t.Errorf("[%d] checkCondition didn't return an expected error", i)
-			}
-		} else {
-			if err != nil {
-				t.Errorf("[%d] failed: %s", i, err)
-				continue
-			}
-			if result != this.expect.result {
-				t.Errorf("[%d] check condition %v %s %v, got %v but expected %v", i, this.value, this.op, this.match, result, this.expect.result)
-			}
-		}
-	}
-}
-
-func TestWhere(t *testing.T) {
-	t.Parallel()
-
-	type Mid struct {
-		Tst TstX
-	}
-
-	d1 := time.Now()
-	d2 := d1.Add(1 * time.Hour)
-	d3 := d2.Add(1 * time.Hour)
-	d4 := d3.Add(1 * time.Hour)
-	d5 := d4.Add(1 * time.Hour)
-	d6 := d5.Add(1 * time.Hour)
-
-	for i, this := range []struct {
-		sequence interface{}
-		key      interface{}
-		op       string
-		match    interface{}
-		expect   interface{}
-	}{
-		{
-			sequence: []map[int]string{
-				{1: "a", 2: "m"}, {1: "c", 2: "d"}, {1: "e", 3: "m"},
-			},
-			key: 2, match: "m",
-			expect: []map[int]string{
-				{1: "a", 2: "m"},
-			},
-		},
-		{
-			sequence: []map[string]int{
-				{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "x": 4},
-			},
-			key: "b", match: 4,
-			expect: []map[string]int{
-				{"a": 3, "b": 4},
-			},
-		},
-		{
-			sequence: []TstX{
-				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
-			},
-			key: "B", match: "f",
-			expect: []TstX{
-				{A: "e", B: "f"},
-			},
-		},
-		{
-			sequence: []*map[int]string{
-				{1: "a", 2: "m"}, {1: "c", 2: "d"}, {1: "e", 3: "m"},
-			},
-			key: 2, match: "m",
-			expect: []*map[int]string{
-				{1: "a", 2: "m"},
-			},
-		},
-		{
-			sequence: []*TstX{
-				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
-			},
-			key: "B", match: "f",
-			expect: []*TstX{
-				{A: "e", B: "f"},
-			},
-		},
-		{
-			sequence: []*TstX{
-				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "c"},
-			},
-			key: "TstRp", match: "rc",
-			expect: []*TstX{
-				{A: "c", B: "d"},
-			},
-		},
-		{
-			sequence: []TstX{
-				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "c"},
-			},
-			key: "TstRv", match: "rc",
-			expect: []TstX{
-				{A: "e", B: "c"},
-			},
-		},
-		{
-			sequence: []map[string]TstX{
-				{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}},
-			},
-			key: "foo.B", match: "d",
-			expect: []map[string]TstX{
-				{"foo": TstX{A: "c", B: "d"}},
-			},
-		},
-		{
-			sequence: []map[string]TstX{
-				{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}},
-			},
-			key: ".foo.B", match: "d",
-			expect: []map[string]TstX{
-				{"foo": TstX{A: "c", B: "d"}},
-			},
-		},
-		{
-			sequence: []map[string]TstX{
-				{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}},
-			},
-			key: "foo.TstRv", match: "rd",
-			expect: []map[string]TstX{
-				{"foo": TstX{A: "c", B: "d"}},
-			},
-		},
-		{
-			sequence: []map[string]*TstX{
-				{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}},
-			},
-			key: "foo.TstRp", match: "rc",
-			expect: []map[string]*TstX{
-				{"foo": &TstX{A: "c", B: "d"}},
-			},
-		},
-		{
-			sequence: []map[string]Mid{
-				{"foo": Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": Mid{Tst: TstX{A: "e", B: "f"}}},
-			},
-			key: "foo.Tst.B", match: "d",
-			expect: []map[string]Mid{
-				{"foo": Mid{Tst: TstX{A: "c", B: "d"}}},
-			},
-		},
-		{
-			sequence: []map[string]Mid{
-				{"foo": Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": Mid{Tst: TstX{A: "e", B: "f"}}},
-			},
-			key: "foo.Tst.TstRv", match: "rd",
-			expect: []map[string]Mid{
-				{"foo": Mid{Tst: TstX{A: "c", B: "d"}}},
-			},
-		},
-		{
-			sequence: []map[string]*Mid{
-				{"foo": &Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": &Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": &Mid{Tst: TstX{A: "e", B: "f"}}},
-			},
-			key: "foo.Tst.TstRp", match: "rc",
-			expect: []map[string]*Mid{
-				{"foo": &Mid{Tst: TstX{A: "c", B: "d"}}},
-			},
-		},
-		{
-			sequence: []map[string]int{
-				{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6},
-			},
-			key: "b", op: ">", match: 3,
-			expect: []map[string]int{
-				{"a": 3, "b": 4}, {"a": 5, "b": 6},
-			},
-		},
-		{
-			sequence: []TstX{
-				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
-			},
-			key: "B", op: "!=", match: "f",
-			expect: []TstX{
-				{A: "a", B: "b"}, {A: "c", B: "d"},
-			},
-		},
-		{
-			sequence: []map[string]int{
-				{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6},
-			},
-			key: "b", op: "in", match: []int{3, 4, 5},
-			expect: []map[string]int{
-				{"a": 3, "b": 4},
-			},
-		},
-		{
-			sequence: []map[string][]string{
-				{"a": []string{"A", "B", "C"}, "b": []string{"D", "E", "F"}}, {"a": []string{"G", "H", "I"}, "b": []string{"J", "K", "L"}}, {"a": []string{"M", "N", "O"}, "b": []string{"P", "Q", "R"}},
-			},
-			key: "b", op: "intersect", match: []string{"D", "P", "Q"},
-			expect: []map[string][]string{
-				{"a": []string{"A", "B", "C"}, "b": []string{"D", "E", "F"}}, {"a": []string{"M", "N", "O"}, "b": []string{"P", "Q", "R"}},
-			},
-		},
-		{
-			sequence: []map[string][]int{
-				{"a": []int{1, 2, 3}, "b": []int{4, 5, 6}}, {"a": []int{7, 8, 9}, "b": []int{10, 11, 12}}, {"a": []int{13, 14, 15}, "b": []int{16, 17, 18}},
-			},
-			key: "b", op: "intersect", match: []int{4, 10, 12},
-			expect: []map[string][]int{
-				{"a": []int{1, 2, 3}, "b": []int{4, 5, 6}}, {"a": []int{7, 8, 9}, "b": []int{10, 11, 12}},
-			},
-		},
-		{
-			sequence: []map[string][]int8{
-				{"a": []int8{1, 2, 3}, "b": []int8{4, 5, 6}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 11, 12}}, {"a": []int8{13, 14, 15}, "b": []int8{16, 17, 18}},
-			},
-			key: "b", op: "intersect", match: []int8{4, 10, 12},
-			expect: []map[string][]int8{
-				{"a": []int8{1, 2, 3}, "b": []int8{4, 5, 6}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 11, 12}},
-			},
-		},
-		{
-			sequence: []map[string][]int16{
-				{"a": []int16{1, 2, 3}, "b": []int16{4, 5, 6}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 11, 12}}, {"a": []int16{13, 14, 15}, "b": []int16{16, 17, 18}},
-			},
-			key: "b", op: "intersect", match: []int16{4, 10, 12},
-			expect: []map[string][]int16{
-				{"a": []int16{1, 2, 3}, "b": []int16{4, 5, 6}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 11, 12}},
-			},
-		},
-		{
-			sequence: []map[string][]int32{
-				{"a": []int32{1, 2, 3}, "b": []int32{4, 5, 6}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 11, 12}}, {"a": []int32{13, 14, 15}, "b": []int32{16, 17, 18}},
-			},
-			key: "b", op: "intersect", match: []int32{4, 10, 12},
-			expect: []map[string][]int32{
-				{"a": []int32{1, 2, 3}, "b": []int32{4, 5, 6}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 11, 12}},
-			},
-		},
-		{
-			sequence: []map[string][]int64{
-				{"a": []int64{1, 2, 3}, "b": []int64{4, 5, 6}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 11, 12}}, {"a": []int64{13, 14, 15}, "b": []int64{16, 17, 18}},
-			},
-			key: "b", op: "intersect", match: []int64{4, 10, 12},
-			expect: []map[string][]int64{
-				{"a": []int64{1, 2, 3}, "b": []int64{4, 5, 6}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 11, 12}},
-			},
-		},
-		{
-			sequence: []map[string][]float32{
-				{"a": []float32{1.0, 2.0, 3.0}, "b": []float32{4.0, 5.0, 6.0}}, {"a": []float32{7.0, 8.0, 9.0}, "b": []float32{10.0, 11.0, 12.0}}, {"a": []float32{13.0, 14.0, 15.0}, "b": []float32{16.0, 17.0, 18.0}},
-			},
-			key: "b", op: "intersect", match: []float32{4, 10, 12},
-			expect: []map[string][]float32{
-				{"a": []float32{1.0, 2.0, 3.0}, "b": []float32{4.0, 5.0, 6.0}}, {"a": []float32{7.0, 8.0, 9.0}, "b": []float32{10.0, 11.0, 12.0}},
-			},
-		},
-		{
-			sequence: []map[string][]float64{
-				{"a": []float64{1.0, 2.0, 3.0}, "b": []float64{4.0, 5.0, 6.0}}, {"a": []float64{7.0, 8.0, 9.0}, "b": []float64{10.0, 11.0, 12.0}}, {"a": []float64{13.0, 14.0, 15.0}, "b": []float64{16.0, 17.0, 18.0}},
-			},
-			key: "b", op: "intersect", match: []float64{4, 10, 12},
-			expect: []map[string][]float64{
-				{"a": []float64{1.0, 2.0, 3.0}, "b": []float64{4.0, 5.0, 6.0}}, {"a": []float64{7.0, 8.0, 9.0}, "b": []float64{10.0, 11.0, 12.0}},
-			},
-		},
-		{
-			sequence: []map[string]int{
-				{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6},
-			},
-			key: "b", op: "in", match: slice(3, 4, 5),
-			expect: []map[string]int{
-				{"a": 3, "b": 4},
-			},
-		},
-		{
-			sequence: []map[string]time.Time{
-				{"a": d1, "b": d2}, {"a": d3, "b": d4}, {"a": d5, "b": d6},
-			},
-			key: "b", op: "in", match: slice(d3, d4, d5),
-			expect: []map[string]time.Time{
-				{"a": d3, "b": d4},
-			},
-		},
-		{
-			sequence: []TstX{
-				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
-			},
-			key: "B", op: "not in", match: []string{"c", "d", "e"},
-			expect: []TstX{
-				{A: "a", B: "b"}, {A: "e", B: "f"},
-			},
-		},
-		{
-			sequence: []TstX{
-				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
-			},
-			key: "B", op: "not in", match: slice("c", t, "d", "e"),
-			expect: []TstX{
-				{A: "a", B: "b"}, {A: "e", B: "f"},
-			},
-		},
-		{
-			sequence: []map[string]int{
-				{"a": 1, "b": 2}, {"a": 3}, {"a": 5, "b": 6},
-			},
-			key: "b", op: "", match: nil,
-			expect: []map[string]int{
-				{"a": 3},
-			},
-		},
-		{
-			sequence: []map[string]int{
-				{"a": 1, "b": 2}, {"a": 3}, {"a": 5, "b": 6},
-			},
-			key: "b", op: "!=", match: nil,
-			expect: []map[string]int{
-				{"a": 1, "b": 2}, {"a": 5, "b": 6},
-			},
-		},
-		{
-			sequence: []map[string]int{
-				{"a": 1, "b": 2}, {"a": 3}, {"a": 5, "b": 6},
-			},
-			key: "b", op: ">", match: nil,
-			expect: []map[string]int{},
-		},
-		{
-			sequence: []map[string]bool{
-				{"a": true, "b": false}, {"c": true, "b": true}, {"d": true, "b": false},
-			},
-			key: "b", op: "", match: true,
-			expect: []map[string]bool{
-				{"c": true, "b": true},
-			},
-		},
-		{
-			sequence: []map[string]bool{
-				{"a": true, "b": false}, {"c": true, "b": true}, {"d": true, "b": false},
-			},
-			key: "b", op: "!=", match: true,
-			expect: []map[string]bool{
-				{"a": true, "b": false}, {"d": true, "b": false},
-			},
-		},
-		{
-			sequence: []map[string]bool{
-				{"a": true, "b": false}, {"c": true, "b": true}, {"d": true, "b": false},
-			},
-			key: "b", op: ">", match: false,
-			expect: []map[string]bool{},
-		},
-		{sequence: (*[]TstX)(nil), key: "A", match: "a", expect: false},
-		{sequence: TstX{A: "a", B: "b"}, key: "A", match: "a", expect: false},
-		{sequence: []map[string]*TstX{{"foo": nil}}, key: "foo.B", match: "d", expect: false},
-		{
-			sequence: []TstX{
-				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
-			},
-			key: "B", op: "op", match: "f",
-			expect: false,
-		},
-		{
-			sequence: map[string]interface{}{
-				"foo": []interface{}{map[interface{}]interface{}{"a": 1, "b": 2}},
-				"bar": []interface{}{map[interface{}]interface{}{"a": 3, "b": 4}},
-				"zap": []interface{}{map[interface{}]interface{}{"a": 5, "b": 6}},
-			},
-			key: "b", op: "in", match: slice(3, 4, 5),
-			expect: map[string]interface{}{
-				"bar": []interface{}{map[interface{}]interface{}{"a": 3, "b": 4}},
-			},
-		},
-		{
-			sequence: map[string]interface{}{
-				"foo": []interface{}{map[interface{}]interface{}{"a": 1, "b": 2}},
-				"bar": []interface{}{map[interface{}]interface{}{"a": 3, "b": 4}},
-				"zap": []interface{}{map[interface{}]interface{}{"a": 5, "b": 6}},
-			},
-			key: "b", op: ">", match: 3,
-			expect: map[string]interface{}{
-				"bar": []interface{}{map[interface{}]interface{}{"a": 3, "b": 4}},
-				"zap": []interface{}{map[interface{}]interface{}{"a": 5, "b": 6}},
-			},
-		},
-	} {
-		var results interface{}
-		var err error
-
-		if len(this.op) > 0 {
-			results, err = where(this.sequence, this.key, this.op, this.match)
-		} else {
-			results, err = where(this.sequence, this.key, this.match)
-		}
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] Where didn't return an expected error", i)
-			}
-		} else {
-			if err != nil {
-				t.Errorf("[%d] failed: %s", i, err)
-				continue
-			}
-			if !reflect.DeepEqual(results, this.expect) {
-				t.Errorf("[%d] Where clause matching %v with %v, got %v but expected %v", i, this.key, this.match, results, this.expect)
-			}
-		}
-	}
-
-	var err error
-	_, err = where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1)
-	if err == nil {
-		t.Errorf("Where called with none string op value didn't return an expected error")
-	}
-
-	_, err = where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2)
-	if err == nil {
-		t.Errorf("Where called with more than two variable arguments didn't return an expected error")
-	}
-
-	_, err = where(map[string]int{"a": 1, "b": 2}, "a")
-	if err == nil {
-		t.Errorf("Where called with no variable arguments didn't return an expected error")
-	}
-}
-
-func TestDelimit(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		sequence  interface{}
-		delimiter interface{}
-		last      interface{}
-		expect    template.HTML
-	}{
-		{[]string{"class1", "class2", "class3"}, " ", nil, "class1 class2 class3"},
-		{[]int{1, 2, 3, 4, 5}, ",", nil, "1,2,3,4,5"},
-		{[]int{1, 2, 3, 4, 5}, ", ", nil, "1, 2, 3, 4, 5"},
-		{[]string{"class1", "class2", "class3"}, " ", " and ", "class1 class2 and class3"},
-		{[]int{1, 2, 3, 4, 5}, ",", ",", "1,2,3,4,5"},
-		{[]int{1, 2, 3, 4, 5}, ", ", ", and ", "1, 2, 3, 4, and 5"},
-		// test maps with and without sorting required
-		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", nil, "10--20--30--40--50"},
-		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", nil, "30--20--10--40--50"},
-		{map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", nil, "10--20--30--40--50"},
-		{map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", nil, "30--20--10--40--50"},
-		{map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", nil, "50--40--10--30--20"},
-		{map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", nil, "10--20--30--40--50"},
-		{map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", nil, "30--20--10--40--50"},
-		{map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, "--", nil, "30--20--10--40--50"},
-		// test maps with a last delimiter
-		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", "--and--", "10--20--30--40--and--50"},
-		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", "--and--", "30--20--10--40--and--50"},
-		{map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", "--and--", "10--20--30--40--and--50"},
-		{map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", "--and--", "30--20--10--40--and--50"},
-		{map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", "--and--", "50--40--10--30--and--20"},
-		{map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", "--and--", "10--20--30--40--and--50"},
-		{map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", "--and--", "30--20--10--40--and--50"},
-		{map[float64]string{3.5: "10", 2.5: "20", 1.5: "30", 4.5: "40", 5.5: "50"}, "--", "--and--", "30--20--10--40--and--50"},
-	} {
-		var result template.HTML
-		var err error
-		if this.last == nil {
-			result, err = delimit(this.sequence, this.delimiter)
-		} else {
-			result, err = delimit(this.sequence, this.delimiter, this.last)
-		}
-		if err != nil {
-			t.Errorf("[%d] failed: %s", i, err)
-			continue
-		}
-		if !reflect.DeepEqual(result, this.expect) {
-			t.Errorf("[%d] Delimit called on sequence: %v | delimiter: `%v` | last: `%v`, got %v but expected %v", i, this.sequence, this.delimiter, this.last, result, this.expect)
-		}
-	}
-}
-
-func TestSort(t *testing.T) {
-	t.Parallel()
-	type ts struct {
-		MyInt    int
-		MyFloat  float64
-		MyString string
-	}
-	type mid struct {
-		Tst TstX
-	}
-
-	for i, this := range []struct {
-		sequence    interface{}
-		sortByField interface{}
-		sortAsc     string
-		expect      interface{}
-	}{
-		{[]string{"class1", "class2", "class3"}, nil, "asc", []string{"class1", "class2", "class3"}},
-		{[]string{"class3", "class1", "class2"}, nil, "asc", []string{"class1", "class2", "class3"}},
-		{[]int{1, 2, 3, 4, 5}, nil, "asc", []int{1, 2, 3, 4, 5}},
-		{[]int{5, 4, 3, 1, 2}, nil, "asc", []int{1, 2, 3, 4, 5}},
-		// test sort key parameter is focibly set empty
-		{[]string{"class3", "class1", "class2"}, map[int]string{1: "a"}, "asc", []string{"class1", "class2", "class3"}},
-		// test map sorting by keys
-		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, nil, "asc", []int{10, 20, 30, 40, 50}},
-		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, nil, "asc", []int{30, 20, 10, 40, 50}},
-		{map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, nil, "asc", []string{"10", "20", "30", "40", "50"}},
-		{map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}},
-		{map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, nil, "asc", []string{"50", "40", "10", "30", "20"}},
-		{map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, nil, "asc", []string{"10", "20", "30", "40", "50"}},
-		{map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}},
-		{map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}},
-		// test map sorting by value
-		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "value", "asc", []int{10, 20, 30, 40, 50}},
-		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "value", "asc", []int{10, 20, 30, 40, 50}},
-		// test map sorting by field value
-		{
-			map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}},
-			"MyInt",
-			"asc",
-			[]ts{{10, 10.5, "ten"}, {20, 20.5, "twenty"}, {30, 30.5, "thirty"}, {40, 40.5, "forty"}, {50, 50.5, "fifty"}},
-		},
-		{
-			map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}},
-			"MyFloat",
-			"asc",
-			[]ts{{10, 10.5, "ten"}, {20, 20.5, "twenty"}, {30, 30.5, "thirty"}, {40, 40.5, "forty"}, {50, 50.5, "fifty"}},
-		},
-		{
-			map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}},
-			"MyString",
-			"asc",
-			[]ts{{50, 50.5, "fifty"}, {40, 40.5, "forty"}, {10, 10.5, "ten"}, {30, 30.5, "thirty"}, {20, 20.5, "twenty"}},
-		},
-		// test sort desc
-		{[]string{"class1", "class2", "class3"}, "value", "desc", []string{"class3", "class2", "class1"}},
-		{[]string{"class3", "class1", "class2"}, "value", "desc", []string{"class3", "class2", "class1"}},
-		// test sort by struct's method
-		{
-			[]TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}},
-			"TstRv",
-			"asc",
-			[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
-		},
-		{
-			[]*TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}},
-			"TstRp",
-			"asc",
-			[]*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
-		},
-		// test map sorting by struct's method
-		{
-			map[string]TstX{"1": {A: "i", B: "j"}, "2": {A: "e", B: "f"}, "3": {A: "c", B: "d"}, "4": {A: "g", B: "h"}, "5": {A: "a", B: "b"}},
-			"TstRv",
-			"asc",
-			[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
-		},
-		{
-			map[string]*TstX{"1": {A: "i", B: "j"}, "2": {A: "e", B: "f"}, "3": {A: "c", B: "d"}, "4": {A: "g", B: "h"}, "5": {A: "a", B: "b"}},
-			"TstRp",
-			"asc",
-			[]*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
-		},
-		// test sort by dot chaining key argument
-		{
-			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
-			"foo.A",
-			"asc",
-			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
-		},
-		{
-			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
-			".foo.A",
-			"asc",
-			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
-		},
-		{
-			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
-			"foo.TstRv",
-			"asc",
-			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
-		},
-		{
-			[]map[string]*TstX{{"foo": &TstX{A: "e", B: "f"}}, {"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}},
-			"foo.TstRp",
-			"asc",
-			[]map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}},
-		},
-		{
-			[]map[string]mid{{"foo": mid{Tst: TstX{A: "e", B: "f"}}}, {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
-			"foo.Tst.A",
-			"asc",
-			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
-		},
-		{
-			[]map[string]mid{{"foo": mid{Tst: TstX{A: "e", B: "f"}}}, {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
-			"foo.Tst.TstRv",
-			"asc",
-			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
-		},
-		// test map sorting by dot chaining key argument
-		{
-			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
-			"foo.A",
-			"asc",
-			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
-		},
-		{
-			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
-			".foo.A",
-			"asc",
-			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
-		},
-		{
-			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
-			"foo.TstRv",
-			"asc",
-			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
-		},
-		{
-			map[string]map[string]*TstX{"1": {"foo": &TstX{A: "e", B: "f"}}, "2": {"foo": &TstX{A: "a", B: "b"}}, "3": {"foo": &TstX{A: "c", B: "d"}}},
-			"foo.TstRp",
-			"asc",
-			[]map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}},
-		},
-		{
-			map[string]map[string]mid{"1": {"foo": mid{Tst: TstX{A: "e", B: "f"}}}, "2": {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, "3": {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
-			"foo.Tst.A",
-			"asc",
-			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
-		},
-		{
-			map[string]map[string]mid{"1": {"foo": mid{Tst: TstX{A: "e", B: "f"}}}, "2": {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, "3": {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
-			"foo.Tst.TstRv",
-			"asc",
-			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
-		},
-		// interface slice with missing elements
-		{
-			[]interface{}{
-				map[interface{}]interface{}{"Title": "Foo", "Weight": 10},
-				map[interface{}]interface{}{"Title": "Bar"},
-				map[interface{}]interface{}{"Title": "Zap", "Weight": 5},
-			},
-			"Weight",
-			"asc",
-			[]interface{}{
-				map[interface{}]interface{}{"Title": "Bar"},
-				map[interface{}]interface{}{"Title": "Zap", "Weight": 5},
-				map[interface{}]interface{}{"Title": "Foo", "Weight": 10},
-			},
-		},
-		// test error cases
-		{(*[]TstX)(nil), nil, "asc", false},
-		{TstX{A: "a", B: "b"}, nil, "asc", false},
-		{
-			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
-			"foo.NotAvailable",
-			"asc",
-			false,
-		},
-		{
-			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
-			"foo.NotAvailable",
-			"asc",
-			false,
-		},
-		{nil, nil, "asc", false},
-	} {
-		var result interface{}
-		var err error
-		if this.sortByField == nil {
-			result, err = sortSeq(this.sequence)
-		} else {
-			result, err = sortSeq(this.sequence, this.sortByField, this.sortAsc)
-		}
-
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] Sort 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] Sort called on sequence: %v | sortByField: `%v` | got %v but expected %v", i, this.sequence, this.sortByField, result, this.expect)
-			}
-		}
-	}
-}
-
-func TestReturnWhenSet(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		data   interface{}
-		key    interface{}
-		expect interface{}
-	}{
-		{[]int{1, 2, 3}, 1, int64(2)},
-		{[]uint{1, 2, 3}, 1, uint64(2)},
-		{[]float64{1.1, 2.2, 3.3}, 1, float64(2.2)},
-		{[]string{"foo", "bar", "baz"}, 1, "bar"},
-		{[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}}, 1, ""},
-		{map[string]int{"foo": 1, "bar": 2, "baz": 3}, "bar", int64(2)},
-		{map[string]uint{"foo": 1, "bar": 2, "baz": 3}, "bar", uint64(2)},
-		{map[string]float64{"foo": 1.1, "bar": 2.2, "baz": 3.3}, "bar", float64(2.2)},
-		{map[string]string{"foo": "FOO", "bar": "BAR", "baz": "BAZ"}, "bar", "BAR"},
-		{map[string]TstX{"foo": {A: "a", B: "b"}, "bar": {A: "c", B: "d"}, "baz": {A: "e", B: "f"}}, "bar", ""},
-		{(*[]string)(nil), "bar", ""},
-	} {
-		result := returnWhenSet(this.data, this.key)
-		if !reflect.DeepEqual(result, this.expect) {
-			t.Errorf("[%d] ReturnWhenSet got %v (type %v) but expected %v (type %v)", i, result, reflect.TypeOf(result), this.expect, reflect.TypeOf(this.expect))
-		}
-	}
-}
-
-func TestMarkdownify(t *testing.T) {
-	t.Parallel()
-	v := viper.New()
-
-	f := newTestFuncsterWithViper(v)
-
-	for i, this := range []struct {
-		in     interface{}
-		expect interface{}
-	}{
-		{"Hello **World!**", template.HTML("Hello <strong>World!</strong>")},
-		{[]byte("Hello Bytes **World!**"), template.HTML("Hello Bytes <strong>World!</strong>")},
-	} {
-		result, err := f.markdownify(this.in)
-		if err != nil {
-			t.Fatalf("[%d] unexpected error in markdownify: %s", i, err)
-		}
-		if !reflect.DeepEqual(result, this.expect) {
-			t.Errorf("[%d] markdownify got %v (type %v) but expected %v (type %v)", i, result, reflect.TypeOf(result), this.expect, reflect.TypeOf(this.expect))
-		}
-	}
-
-	if _, err := f.markdownify(t); err == nil {
-		t.Fatalf("markdownify should have errored")
-	}
-}
-
-func TestApply(t *testing.T) {
-	t.Parallel()
-
-	f := newTestFuncster()
-
-	strings := []interface{}{"a\n", "b\n"}
-	noStringers := []interface{}{tstNoStringer{}, tstNoStringer{}}
-
-	chomped, _ := f.apply(strings, "chomp", ".")
-	assert.Equal(t, []interface{}{template.HTML("a"), template.HTML("b")}, chomped)
-
-	chomped, _ = f.apply(strings, "chomp", "c\n")
-	assert.Equal(t, []interface{}{template.HTML("c"), template.HTML("c")}, chomped)
-
-	chomped, _ = f.apply(nil, "chomp", ".")
-	assert.Equal(t, []interface{}{}, chomped)
-
-	_, err := f.apply(strings, "apply", ".")
-	if err == nil {
-		t.Errorf("apply with apply should fail")
-	}
-
-	var nilErr *error
-	_, err = f.apply(nilErr, "chomp", ".")
-	if err == nil {
-		t.Errorf("apply with nil in seq should fail")
-	}
-
-	_, err = f.apply(strings, "dobedobedo", ".")
-	if err == nil {
-		t.Errorf("apply with unknown func should fail")
-	}
-
-	_, err = f.apply(noStringers, "chomp", ".")
-	if err == nil {
-		t.Errorf("apply when func fails should fail")
-	}
-
-	_, err = f.apply(tstNoStringer{}, "chomp", ".")
-	if err == nil {
-		t.Errorf("apply with non-sequence should fail")
-	}
-}
-
-func TestChomp(t *testing.T) {
-	t.Parallel()
-	base := "\n This is\na story "
-	for i, item := range []string{
-		"\n", "\n\n",
-		"\r", "\r\r",
-		"\r\n", "\r\n\r\n",
-	} {
-		c, _ := chomp(base + item)
-		chomped := string(c)
-
-		if chomped != base {
-			t.Errorf("[%d] Chomp failed, got '%v'", i, chomped)
-		}
-
-		_, err := chomp(tstNoStringer{})
-
-		if err == nil {
-			t.Errorf("Chomp should fail")
-		}
-	}
-}
-
-func TestLower(t *testing.T) {
-	t.Parallel()
-	cases := []struct {
-		s     interface{}
-		want  string
-		isErr bool
-	}{
-		{"TEST", "test", false},
-		{template.HTML("LoWeR"), "lower", false},
-		{[]byte("BYTES"), "bytes", false},
-	}
-
-	for i, c := range cases {
-		res, err := lower(c.s)
-		if (err != nil) != c.isErr {
-			t.Fatalf("[%d] unexpected isErr state: want %v, got %v, err = %v", i, c.want, (err != nil), err)
-		}
-
-		if res != c.want {
-			t.Errorf("[%d] lower failed: want %v, got %v", i, c.want, res)
-		}
-	}
-}
-
-func TestTitle(t *testing.T) {
-	t.Parallel()
-	cases := []struct {
-		s     interface{}
-		want  string
-		isErr bool
-	}{
-		{"test", "Test", false},
-		{template.HTML("hypertext"), "Hypertext", false},
-		{[]byte("bytes"), "Bytes", false},
-	}
-
-	for i, c := range cases {
-		res, err := title(c.s)
-		if (err != nil) != c.isErr {
-			t.Fatalf("[%d] unexpected isErr state: want %v, got %v, err = %v", i, c.want, (err != nil), err)
-		}
-
-		if res != c.want {
-			t.Errorf("[%d] title failed: want %v, got %v", i, c.want, res)
-		}
-	}
-}
-
-func TestUpper(t *testing.T) {
-	t.Parallel()
-	cases := []struct {
-		s     interface{}
-		want  string
-		isErr bool
-	}{
-		{"test", "TEST", false},
-		{template.HTML("UpPeR"), "UPPER", false},
-		{[]byte("bytes"), "BYTES", false},
-	}
-
-	for i, c := range cases {
-		res, err := upper(c.s)
-		if (err != nil) != c.isErr {
-			t.Fatalf("[%d] unexpected isErr state: want %v, got %v, err = %v", i, c.want, (err != nil), err)
-		}
-
-		if res != c.want {
-			t.Errorf("[%d] upper failed: want %v, got %v", i, c.want, res)
-		}
-	}
-}
-
-func TestHighlight(t *testing.T) {
-	t.Parallel()
-	code := "func boo() {}"
-
-	f := newTestFuncster()
-
-	highlighted, err := f.highlight(code, "go", "")
-
-	if err != nil {
-		t.Fatal("Highlight returned error:", err)
-	}
-
-	// this depends on a Pygments installation, but will always contain the function name.
-	if !strings.Contains(string(highlighted), "boo") {
-		t.Errorf("Highlight mismatch,  got %v", highlighted)
-	}
-
-	_, err = f.highlight(t, "go", "")
-
-	if err == nil {
-		t.Error("Expected highlight error")
-	}
-}
-
-func TestInflect(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		inflectFunc func(i interface{}) (string, error)
-		in          interface{}
-		expected    string
-	}{
-		{humanize, "MyCamel", "My camel"},
-		{humanize, "", ""},
-		{humanize, "103", "103rd"},
-		{humanize, "41", "41st"},
-		{humanize, 103, "103rd"},
-		{humanize, int64(92), "92nd"},
-		{humanize, "5.5", "5.5"},
-		{pluralize, "cat", "cats"},
-		{pluralize, "", ""},
-		{singularize, "cats", "cat"},
-		{singularize, "", ""},
-	} {
-
-		result, err := this.inflectFunc(this.in)
-
-		if err != nil {
-			t.Errorf("[%d] Unexpected Inflect error: %s", i, err)
-		} else if result != this.expected {
-			t.Errorf("[%d] Inflect method error, got %v expected %v", i, result, this.expected)
-		}
-
-		_, err = this.inflectFunc(t)
-		if err == nil {
-			t.Errorf("[%d] Expected Inflect error", i)
-		}
-	}
-}
-
-func TestCounterFuncs(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		countFunc func(i interface{}) (int, error)
-		in        string
-		expected  int
-	}{
-		{countWords, "Do Be Do Be Do", 5},
-		{countWords, "旁边", 2},
-		{countRunes, "旁边", 2},
-	} {
-
-		result, err := this.countFunc(this.in)
-
-		if err != nil {
-			t.Errorf("[%d] Unexpected counter error: %s", i, err)
-		} else if result != this.expected {
-			t.Errorf("[%d] Count method error, got %v expected %v", i, result, this.expected)
-		}
-
-		_, err = this.countFunc(t)
-		if err == nil {
-			t.Errorf("[%d] Expected Count error", i)
-		}
-	}
-}
-
-func TestReplace(t *testing.T) {
-	t.Parallel()
-	v, _ := replace("aab", "a", "b")
-	assert.Equal(t, "bbb", v)
-	v, _ = replace("11a11", 1, 2)
-	assert.Equal(t, "22a22", v)
-	v, _ = replace(12345, 1, 2)
-	assert.Equal(t, "22345", v)
-	_, e := replace(tstNoStringer{}, "a", "b")
-	assert.NotNil(t, e, "tstNoStringer isn't trimmable")
-	_, e = replace("a", tstNoStringer{}, "b")
-	assert.NotNil(t, e, "tstNoStringer cannot be converted to string")
-	_, e = replace("a", "b", tstNoStringer{})
-	assert.NotNil(t, e, "tstNoStringer cannot be converted to string")
-}
-
-func TestReplaceRE(t *testing.T) {
-	t.Parallel()
-	for i, val := range []struct {
-		pattern interface{}
-		repl    interface{}
-		src     interface{}
-		expect  string
-		ok      bool
-	}{
-		{"^https?://([^/]+).*", "$1", "http://gohugo.io/docs", "gohugo.io", true},
-		{"^https?://([^/]+).*", "$2", "http://gohugo.io/docs", "", true},
-		{tstNoStringer{}, "$2", "http://gohugo.io/docs", "", false},
-		{"^https?://([^/]+).*", tstNoStringer{}, "http://gohugo.io/docs", "", false},
-		{"^https?://([^/]+).*", "$2", tstNoStringer{}, "", false},
-		{"(ab)", "AB", "aabbaab", "aABbaAB", true},
-		{"(ab", "AB", "aabb", "", false}, // invalid re
-	} {
-		v, err := replaceRE(val.pattern, val.repl, val.src)
-		if (err == nil) != val.ok {
-			t.Errorf("[%d] %s", i, err)
-		}
-		assert.Equal(t, val.expect, v)
-	}
-}
-
-func TestFindRE(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		expr    string
-		content interface{}
-		limit   interface{}
-		expect  []string
-		ok      bool
-	}{
-		{"[G|g]o", "Hugo is a static site generator written in Go.", 2, []string{"go", "Go"}, true},
-		{"[G|g]o", "Hugo is a static site generator written in Go.", -1, []string{"go", "Go"}, true},
-		{"[G|g]o", "Hugo is a static site generator written in Go.", 1, []string{"go"}, true},
-		{"[G|g]o", "Hugo is a static site generator written in Go.", "1", []string{"go"}, true},
-		{"[G|g]o", "Hugo is a static site generator written in Go.", nil, []string(nil), true},
-		{"[G|go", "Hugo is a static site generator written in Go.", nil, []string(nil), false},
-		{"[G|g]o", t, nil, []string(nil), false},
-	} {
-		var (
-			res []string
-			err error
-		)
-
-		res, err = findRE(this.expr, this.content, this.limit)
-		if err != nil && this.ok {
-			t.Errorf("[%d] returned an unexpected error: %s", i, err)
-		}
-
-		assert.Equal(t, this.expect, res)
-	}
-}
-
-func TestTrim(t *testing.T) {
-	t.Parallel()
-
-	for i, this := range []struct {
-		v1     interface{}
-		v2     string
-		expect interface{}
-	}{
-		{"1234 my way 13", "123 ", "4 my way"},
-		{"      my way  ", " ", "my way"},
-		{1234, "14", "23"},
-		{tstNoStringer{}, " ", false},
-	} {
-		result, err := trim(this.v1, this.v2)
-
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] trim 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] got '%s' but expected %s", i, result, this.expect)
-			}
-		}
-	}
-}
-
-func TestDateFormat(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		layout string
-		value  interface{}
-		expect interface{}
-	}{
-		{"Monday, Jan 2, 2006", "2015-01-21", "Wednesday, Jan 21, 2015"},
-		{"Monday, Jan 2, 2006", time.Date(2015, time.January, 21, 0, 0, 0, 0, time.UTC), "Wednesday, Jan 21, 2015"},
-		{"This isn't a date layout string", "2015-01-21", "This isn't a date layout string"},
-		// The following test case gives either "Tuesday, Jan 20, 2015" or "Monday, Jan 19, 2015" depending on the local time zone
-		{"Monday, Jan 2, 2006", 1421733600, time.Unix(1421733600, 0).Format("Monday, Jan 2, 2006")},
-		{"Monday, Jan 2, 2006", 1421733600.123, false},
-		{time.RFC3339, time.Date(2016, time.March, 3, 4, 5, 0, 0, time.UTC), "2016-03-03T04:05:00Z"},
-		{time.RFC1123, time.Date(2016, time.March, 3, 4, 5, 0, 0, time.UTC), "Thu, 03 Mar 2016 04:05:00 UTC"},
-		{time.RFC3339, "Thu, 03 Mar 2016 04:05:00 UTC", "2016-03-03T04:05:00Z"},
-		{time.RFC1123, "2016-03-03T04:05:00Z", "Thu, 03 Mar 2016 04:05:00 UTC"},
-	} {
-		result, err := dateFormat(this.layout, this.value)
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] DateFormat didn't return an expected error, got %v", i, result)
-			}
-		} else {
-			if err != nil {
-				t.Errorf("[%d] DateFormat failed: %s", i, err)
-				continue
-			}
-			if result != this.expect {
-				t.Errorf("[%d] DateFormat got %v but expected %v", i, result, this.expect)
-			}
-		}
-	}
-}
-
-func TestDefaultFunc(t *testing.T) {
-	t.Parallel()
-	then := time.Now()
-	now := time.Now()
-
-	for i, this := range []struct {
-		dflt     interface{}
-		given    interface{}
-		expected interface{}
-	}{
-		{true, false, false},
-		{"5", 0, "5"},
-
-		{"test1", "set", "set"},
-		{"test2", "", "test2"},
-		{"test3", nil, "test3"},
-
-		{[2]int{10, 20}, [2]int{1, 2}, [2]int{1, 2}},
-		{[2]int{10, 20}, [0]int{}, [2]int{10, 20}},
-		{[2]int{100, 200}, nil, [2]int{100, 200}},
-
-		{[]string{"one"}, []string{"uno"}, []string{"uno"}},
-		{[]string{"two"}, []string{}, []string{"two"}},
-		{[]string{"three"}, nil, []string{"three"}},
-
-		{map[string]int{"one": 1}, map[string]int{"uno": 1}, map[string]int{"uno": 1}},
-		{map[string]int{"one": 1}, map[string]int{}, map[string]int{"one": 1}},
-		{map[string]int{"two": 2}, nil, map[string]int{"two": 2}},
-
-		{10, 1, 1},
-		{10, 0, 10},
-		{20, nil, 20},
-
-		{float32(10), float32(1), float32(1)},
-		{float32(10), 0, float32(10)},
-		{float32(20), nil, float32(20)},
-
-		{complex(2, -2), complex(1, -1), complex(1, -1)},
-		{complex(2, -2), complex(0, 0), complex(2, -2)},
-		{complex(3, -3), nil, complex(3, -3)},
-
-		{struct{ f string }{f: "one"}, struct{ f string }{}, struct{ f string }{}},
-		{struct{ f string }{f: "two"}, nil, struct{ f string }{f: "two"}},
-
-		{then, now, now},
-		{then, time.Time{}, then},
-	} {
-		res, err := dfault(this.dflt, this.given)
-		if err != nil {
-			t.Errorf("[%d] default returned an error: %s", i, err)
-			continue
-		}
-		if !reflect.DeepEqual(this.expected, res) {
-			t.Errorf("[%d] default returned %v, but expected %v", i, res, this.expected)
-		}
-	}
-}
-
-func TestDefault(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		input    interface{}
-		tpl      string
-		expected string
-		ok       bool
-	}{
-		{map[string]string{"foo": "bar"}, `{{ index . "foo" | default "nope" }}`, `bar`, true},
-		{map[string]string{"foo": "pop"}, `{{ index . "bar" | default "nada" }}`, `nada`, true},
-		{map[string]string{"foo": "cat"}, `{{ default "nope" .foo }}`, `cat`, true},
-		{map[string]string{"foo": "dog"}, `{{ default "nope" .foo "extra" }}`, ``, false},
-		{map[string]interface{}{"images": []string{}}, `{{ default "default.jpg" (index .images 0) }}`, `default.jpg`, true},
-	} {
-
-		tmpl := newTestTemplate(t, "test", this.tpl)
-
-		buf := new(bytes.Buffer)
-		err := tmpl.Execute(buf, this.input)
-		if (err == nil) != this.ok {
-			t.Errorf("[%d] execute template returned unexpected error: %s", i, err)
-			continue
-		}
-
-		if buf.String() != this.expected {
-			t.Errorf("[%d] execute template got %v, but expected %v", i, buf.String(), this.expected)
-		}
-	}
-}
-
-func TestSafeHTML(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		str                 string
-		tmplStr             string
-		expectWithoutEscape string
-		expectWithEscape    string
-	}{
-		{`<div></div>`, `{{ . }}`, `&lt;div&gt;&lt;/div&gt;`, `<div></div>`},
-	} {
-		tmpl, err := template.New("test").Parse(this.tmplStr)
-		if err != nil {
-			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
-			continue
-		}
-
-		buf := new(bytes.Buffer)
-		err = tmpl.Execute(buf, this.str)
-		if err != nil {
-			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithoutEscape {
-			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
-		}
-
-		buf.Reset()
-		v, err := safeHTML(this.str)
-		if err != nil {
-			t.Fatalf("[%d] unexpected error in safeHTML: %s", i, err)
-		}
-
-		err = tmpl.Execute(buf, v)
-		if err != nil {
-			t.Errorf("[%d] execute template with an escaped string value by safeHTML returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithEscape {
-			t.Errorf("[%d] execute template with an escaped string value by safeHTML, got %v but expected %v", i, buf.String(), this.expectWithEscape)
-		}
-	}
-}
-
-func TestSafeHTMLAttr(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		str                 string
-		tmplStr             string
-		expectWithoutEscape string
-		expectWithEscape    string
-	}{
-		{`href="irc://irc.freenode.net/#golang"`, `<a {{ . }}>irc</a>`, `<a ZgotmplZ>irc</a>`, `<a href="irc://irc.freenode.net/#golang">irc</a>`},
-	} {
-		tmpl, err := template.New("test").Parse(this.tmplStr)
-		if err != nil {
-			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
-			continue
-		}
-
-		buf := new(bytes.Buffer)
-		err = tmpl.Execute(buf, this.str)
-		if err != nil {
-			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithoutEscape {
-			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
-		}
-
-		buf.Reset()
-		v, err := safeHTMLAttr(this.str)
-		if err != nil {
-			t.Fatalf("[%d] unexpected error in safeHTMLAttr: %s", i, err)
-		}
-
-		err = tmpl.Execute(buf, v)
-		if err != nil {
-			t.Errorf("[%d] execute template with an escaped string value by safeHTMLAttr returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithEscape {
-			t.Errorf("[%d] execute template with an escaped string value by safeHTMLAttr, got %v but expected %v", i, buf.String(), this.expectWithEscape)
-		}
-	}
-}
-
-func TestSafeCSS(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		str                 string
-		tmplStr             string
-		expectWithoutEscape string
-		expectWithEscape    string
-	}{
-		{`width: 60px;`, `<div style="{{ . }}"></div>`, `<div style="ZgotmplZ"></div>`, `<div style="width: 60px;"></div>`},
-	} {
-		tmpl, err := template.New("test").Parse(this.tmplStr)
-		if err != nil {
-			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
-			continue
-		}
-
-		buf := new(bytes.Buffer)
-		err = tmpl.Execute(buf, this.str)
-		if err != nil {
-			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithoutEscape {
-			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
-		}
-
-		buf.Reset()
-		v, err := safeCSS(this.str)
-		if err != nil {
-			t.Fatalf("[%d] unexpected error in safeCSS: %s", i, err)
-		}
-
-		err = tmpl.Execute(buf, v)
-		if err != nil {
-			t.Errorf("[%d] execute template with an escaped string value by safeCSS returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithEscape {
-			t.Errorf("[%d] execute template with an escaped string value by safeCSS, got %v but expected %v", i, buf.String(), this.expectWithEscape)
-		}
-	}
-}
-
-// TODO(bep) what is this? Also look above.
-func TestSafeJS(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		str                 string
-		tmplStr             string
-		expectWithoutEscape string
-		expectWithEscape    string
-	}{
-		{`619c16f`, `<script>var x{{ . }};</script>`, `<script>var x"619c16f";</script>`, `<script>var x619c16f;</script>`},
-	} {
-		tmpl, err := template.New("test").Parse(this.tmplStr)
-		if err != nil {
-			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
-			continue
-		}
-
-		buf := new(bytes.Buffer)
-		err = tmpl.Execute(buf, this.str)
-		if err != nil {
-			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithoutEscape {
-			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
-		}
-
-		buf.Reset()
-		v, err := safeJS(this.str)
-		if err != nil {
-			t.Fatalf("[%d] unexpected error in safeJS: %s", i, err)
-		}
-
-		err = tmpl.Execute(buf, v)
-		if err != nil {
-			t.Errorf("[%d] execute template with an escaped string value by safeJS returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithEscape {
-			t.Errorf("[%d] execute template with an escaped string value by safeJS, got %v but expected %v", i, buf.String(), this.expectWithEscape)
-		}
-	}
-}
-
-// TODO(bep) what is this?
-func TestSafeURL(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		str                 string
-		tmplStr             string
-		expectWithoutEscape string
-		expectWithEscape    string
-	}{
-		{`irc://irc.freenode.net/#golang`, `<a href="{{ . }}">IRC</a>`, `<a href="#ZgotmplZ">IRC</a>`, `<a href="irc://irc.freenode.net/#golang">IRC</a>`},
-	} {
-		tmpl, err := template.New("test").Parse(this.tmplStr)
-		if err != nil {
-			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
-			continue
-		}
-
-		buf := new(bytes.Buffer)
-		err = tmpl.Execute(buf, this.str)
-		if err != nil {
-			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithoutEscape {
-			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
-		}
-
-		buf.Reset()
-		v, err := safeURL(this.str)
-		if err != nil {
-			t.Fatalf("[%d] unexpected error in safeURL: %s", i, err)
-		}
-
-		err = tmpl.Execute(buf, v)
-		if err != nil {
-			t.Errorf("[%d] execute template with an escaped string value by safeURL returns unexpected error: %s", i, err)
-		}
-		if buf.String() != this.expectWithEscape {
-			t.Errorf("[%d] execute template with an escaped string value by safeURL, got %v but expected %v", i, buf.String(), this.expectWithEscape)
-		}
-	}
-}
-
-func TestBase64Decode(t *testing.T) {
-	t.Parallel()
-	testStr := "abc123!?$*&()'-=@~"
-	enc := base64.StdEncoding.EncodeToString([]byte(testStr))
-	result, err := base64Decode(enc)
-
-	if err != nil {
-		t.Error("base64Decode returned error:", err)
-	}
-
-	if result != testStr {
-		t.Errorf("base64Decode: got '%s', expected '%s'", result, testStr)
-	}
-
-	_, err = base64Decode(t)
-	if err == nil {
-		t.Error("Expected error from base64Decode")
-	}
-}
-
-func TestBase64Encode(t *testing.T) {
-	t.Parallel()
-	testStr := "YWJjMTIzIT8kKiYoKSctPUB+"
-	dec, err := base64.StdEncoding.DecodeString(testStr)
-
-	if err != nil {
-		t.Error("base64Encode: the DecodeString function of the base64 package returned an error:", err)
-	}
-
-	result, err := base64Encode(string(dec))
-
-	if err != nil {
-		t.Errorf("base64Encode: Can't cast arg '%s' into a string:", testStr)
-	}
-
-	if result != testStr {
-		t.Errorf("base64Encode: got '%s', expected '%s'", result, testStr)
-	}
-
-	_, err = base64Encode(t)
-	if err == nil {
-		t.Error("Expected error from base64Encode")
-	}
-}
-
-func TestMD5(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		input        string
-		expectedHash string
-	}{
-		{"Hello world, gophers!", "b3029f756f98f79e7f1b7f1d1f0dd53b"},
-		{"Lorem ipsum dolor", "06ce65ac476fc656bea3fca5d02cfd81"},
-	} {
-		result, err := md5(this.input)
-		if err != nil {
-			t.Errorf("md5 returned error: %s", err)
-		}
-
-		if result != this.expectedHash {
-			t.Errorf("[%d] md5: expected '%s', got '%s'", i, this.expectedHash, result)
-		}
-	}
-
-	_, err := md5(t)
-	if err == nil {
-		t.Error("Expected error from md5")
-	}
-}
-
-func TestSHA1(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		input        string
-		expectedHash string
-	}{
-		{"Hello world, gophers!", "c8b5b0e33d408246e30f53e32b8f7627a7a649d4"},
-		{"Lorem ipsum dolor", "45f75b844be4d17b3394c6701768daf39419c99b"},
-	} {
-		result, err := sha1(this.input)
-		if err != nil {
-			t.Errorf("sha1 returned error: %s", err)
-		}
-
-		if result != this.expectedHash {
-			t.Errorf("[%d] sha1: expected '%s', got '%s'", i, this.expectedHash, result)
-		}
-	}
-
-	_, err := sha1(t)
-	if err == nil {
-		t.Error("Expected error from sha1")
-	}
-}
-
-func TestSHA256(t *testing.T) {
-	t.Parallel()
-	for i, this := range []struct {
-		input        string
-		expectedHash string
-	}{
-		{"Hello world, gophers!", "6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46"},
-		{"Lorem ipsum dolor", "9b3e1beb7053e0f900a674dd1c99aca3355e1275e1b03d3cb1bc977f5154e196"},
-	} {
-		result, err := sha256(this.input)
-		if err != nil {
-			t.Errorf("sha256 returned error: %s", err)
-		}
-
-		if result != this.expectedHash {
-			t.Errorf("[%d] sha256: expected '%s', got '%s'", i, this.expectedHash, result)
-		}
-	}
-
-	_, err := sha256(t)
-	if err == nil {
-		t.Error("Expected error from sha256")
-	}
-}
-
-func TestReadFile(t *testing.T) {
-	t.Parallel()
-
-	workingDir := "/home/hugo"
-
-	v := viper.New()
-
-	v.Set("workingDir", workingDir)
-
-	f := newTestFuncsterWithViper(v)
-
-	afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755)
-	afero.WriteFile(f.Fs.Source, filepath.Join("/home", "f2.txt"), []byte("f2-content"), 0755)
-
-	for i, this := range []struct {
-		filename string
-		expect   interface{}
-	}{
-		{"", false},
-		{"b", false},
-		{filepath.FromSlash("/f/f1.txt"), "f1-content"},
-		{filepath.FromSlash("f/f1.txt"), "f1-content"},
-		{filepath.FromSlash("../f2.txt"), false},
-	} {
-		result, err := f.readFileFromWorkingDir(this.filename)
-		if b, ok := this.expect.(bool); ok && !b {
-			if err == nil {
-				t.Errorf("[%d] readFile didn't return an expected error", i)
-			}
-		} else {
-			if err != nil {
-				t.Errorf("[%d] readFile failed: %s", i, err)
-				continue
-			}
-			if result != this.expect {
-				t.Errorf("[%d] readFile got %q but expected %q", i, result, this.expect)
-			}
-		}
-	}
-}
-
-func TestPartialCached(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		name    string
-		partial string
-		tmpl    string
-		variant string
-	}{
-		// name and partial should match between test cases.
-		{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . }}`, ""},
-		{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "header"},
-		{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "footer"},
-		{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "header"},
-	}
-
-	var data struct {
-		Title   string
-		Section string
-		Params  map[string]interface{}
-	}
-
-	data.Title = "**BatMan**"
-	data.Section = "blog"
-	data.Params = map[string]interface{}{"langCode": "en"}
-
-	for i, tc := range testCases {
-		var tmp string
-		if tc.variant != "" {
-			tmp = fmt.Sprintf(tc.tmpl, tc.variant)
-		} else {
-			tmp = tc.tmpl
-		}
-
-		config := newDepsConfig(viper.New())
-
-		config.WithTemplate = func(templ tplapi.Template) error {
-			err := templ.AddTemplate("testroot", tmp)
-			if err != nil {
-				return err
-			}
-			err = templ.AddTemplate("partials/"+tc.name, tc.partial)
-			if err != nil {
-				return err
-			}
-
-			return nil
-		}
-
-		de := deps.New(config)
-		require.NoError(t, de.LoadResources())
-
-		buf := new(bytes.Buffer)
-		templ := de.Tmpl.Lookup("testroot")
-		err := templ.Execute(buf, &data)
-		if err != nil {
-			t.Fatalf("[%d] error executing template: %s", i, err)
-		}
-
-		for j := 0; j < 10; j++ {
-			buf2 := new(bytes.Buffer)
-			err := templ.Execute(buf2, nil)
-			if err != nil {
-				t.Fatalf("[%d] error executing template 2nd time: %s", i, err)
-			}
-
-			if !reflect.DeepEqual(buf, buf2) {
-				t.Fatalf("[%d] cached results do not match:\nResult 1:\n%q\nResult 2:\n%q", i, buf, buf2)
-			}
-		}
-	}
-}
-
-func BenchmarkPartial(b *testing.B) {
-	config := newDepsConfig(viper.New())
-	config.WithTemplate = func(templ tplapi.Template) error {
-		err := templ.AddTemplate("testroot", `{{ partial "bench1" . }}`)
-		if err != nil {
-			return err
-		}
-		err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
-		if err != nil {
-			return err
-		}
-
-		return nil
-	}
-
-	de := deps.New(config)
-	require.NoError(b, de.LoadResources())
-
-	buf := new(bytes.Buffer)
-	tmpl := de.Tmpl.Lookup("testroot")
-
-	b.ReportAllocs()
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		if err := tmpl.Execute(buf, nil); err != nil {
-			b.Fatalf("error executing template: %s", err)
-		}
-		buf.Reset()
-	}
-}
-
-func BenchmarkPartialCached(b *testing.B) {
-	config := newDepsConfig(viper.New())
-	config.WithTemplate = func(templ tplapi.Template) error {
-		err := templ.AddTemplate("testroot", `{{ partialCached "bench1" . }}`)
-		if err != nil {
-			return err
-		}
-		err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
-		if err != nil {
-			return err
-		}
-
-		return nil
-	}
-
-	de := deps.New(config)
-	require.NoError(b, de.LoadResources())
-
-	buf := new(bytes.Buffer)
-	tmpl := de.Tmpl.Lookup("testroot")
-
-	b.ReportAllocs()
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		if err := tmpl.Execute(buf, nil); err != nil {
-			b.Fatalf("error executing template: %s", err)
-		}
-		buf.Reset()
-	}
-}
-
-func newTestFuncster() *templateFuncster {
-	return newTestFuncsterWithViper(viper.New())
-}
-
-func newTestFuncsterWithViper(v *viper.Viper) *templateFuncster {
-	config := newDepsConfig(v)
-	d := deps.New(config)
-
-	if err := d.LoadResources(); err != nil {
-		panic(err)
-	}
-
-	return d.Tmpl.(*GoHTMLTemplate).funcster
-}
-
-func newTestTemplate(t *testing.T, name, template string) *template.Template {
-	config := newDepsConfig(viper.New())
-	config.WithTemplate = func(templ tplapi.Template) error {
-		err := templ.AddTemplate(name, template)
-		if err != nil {
-			return err
-		}
-		return nil
-	}
-
-	de := deps.New(config)
-	require.NoError(t, de.LoadResources())
-
-	return de.Tmpl.Lookup(name)
-}
--- a/tpl/template_resources.go
+++ /dev/null
@@ -1,253 +1,0 @@
-// Copyright 2016 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 tpl
-
-import (
-	"bytes"
-	"encoding/csv"
-	"encoding/json"
-	"errors"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-	"path/filepath"
-	"strings"
-	"sync"
-	"time"
-
-	"github.com/spf13/afero"
-	"github.com/spf13/hugo/config"
-	"github.com/spf13/hugo/helpers"
-	jww "github.com/spf13/jwalterweatherman"
-)
-
-var (
-	remoteURLLock = &remoteLock{m: make(map[string]*sync.Mutex)}
-	resSleep      = time.Second * 2 // if JSON decoding failed sleep for n seconds before retrying
-	resRetries    = 1               // number of retries to load the JSON from URL or local file system
-)
-
-type remoteLock struct {
-	sync.RWMutex
-	m map[string]*sync.Mutex
-}
-
-// URLLock locks an URL during download
-func (l *remoteLock) URLLock(url string) {
-	l.Lock()
-	if _, ok := l.m[url]; !ok {
-		l.m[url] = &sync.Mutex{}
-	}
-	l.Unlock() // call this Unlock before the next lock will be called. NFI why but defer doesn't work.
-	l.m[url].Lock()
-}
-
-// URLUnlock unlocks an URL when the download has been finished. Use only in defer calls.
-func (l *remoteLock) URLUnlock(url string) {
-	l.RLock()
-	defer l.RUnlock()
-	if um, ok := l.m[url]; ok {
-		um.Unlock()
-	}
-}
-
-// getCacheFileID returns the cache ID for a string
-func getCacheFileID(cfg config.Provider, id string) string {
-	return cfg.GetString("cacheDir") + url.QueryEscape(id)
-}
-
-// resGetCache returns the content for an ID from the file cache or an error
-// if the file is not found returns nil,nil
-func resGetCache(id string, fs afero.Fs, cfg config.Provider, ignoreCache bool) ([]byte, error) {
-	if ignoreCache {
-		return nil, nil
-	}
-	fID := getCacheFileID(cfg, id)
-	isExists, err := helpers.Exists(fID, fs)
-	if err != nil {
-		return nil, err
-	}
-	if !isExists {
-		return nil, nil
-	}
-
-	return afero.ReadFile(fs, fID)
-
-}
-
-// resWriteCache writes bytes to an ID into the file cache
-func resWriteCache(id string, c []byte, fs afero.Fs, cfg config.Provider, ignoreCache bool) error {
-	if ignoreCache {
-		return nil
-	}
-	fID := getCacheFileID(cfg, id)
-	f, err := fs.Create(fID)
-	if err != nil {
-		return errors.New("Error: " + err.Error() + ". Failed to create file: " + fID)
-	}
-	defer f.Close()
-	n, err := f.Write(c)
-	if n == 0 {
-		return errors.New("No bytes written to file: " + fID)
-	}
-	if err != nil {
-		return errors.New("Error: " + err.Error() + ". Failed to write to file: " + fID)
-	}
-	return nil
-}
-
-func resDeleteCache(id string, fs afero.Fs, cfg config.Provider) error {
-	return fs.Remove(getCacheFileID(cfg, id))
-}
-
-// resGetRemote loads the content of a remote file. This method is thread safe.
-func resGetRemote(url string, fs afero.Fs, cfg config.Provider, hc *http.Client) ([]byte, error) {
-	c, err := resGetCache(url, fs, cfg, cfg.GetBool("ignoreCache"))
-	if c != nil && err == nil {
-		return c, nil
-	}
-	if err != nil {
-		return nil, err
-	}
-
-	// avoid race condition with locks, block other goroutines if the current url is processing
-	remoteURLLock.URLLock(url)
-	defer func() { remoteURLLock.URLUnlock(url) }()
-
-	// avoid multiple locks due to calling resGetCache twice
-	c, err = resGetCache(url, fs, cfg, cfg.GetBool("ignoreCache"))
-	if c != nil && err == nil {
-		return c, nil
-	}
-	if err != nil {
-		return nil, err
-	}
-
-	jww.INFO.Printf("Downloading: %s ...", url)
-	res, err := hc.Get(url)
-	if err != nil {
-		return nil, err
-	}
-	c, err = ioutil.ReadAll(res.Body)
-	res.Body.Close()
-	if err != nil {
-		return nil, err
-	}
-	err = resWriteCache(url, c, fs, cfg, cfg.GetBool("ignoreCache"))
-	if err != nil {
-		return nil, err
-	}
-	jww.INFO.Printf("... and cached to: %s", getCacheFileID(cfg, url))
-	return c, nil
-}
-
-// resGetLocal loads the content of a local file
-func resGetLocal(url string, fs afero.Fs, cfg config.Provider) ([]byte, error) {
-	filename := filepath.Join(cfg.GetString("workingDir"), url)
-	if e, err := helpers.Exists(filename, fs); !e {
-		return nil, err
-	}
-
-	return afero.ReadFile(fs, filename)
-
-}
-
-// resGetResource loads the content of a local or remote file
-func (t *templateFuncster) resGetResource(url string) ([]byte, error) {
-	if url == "" {
-		return nil, nil
-	}
-	if strings.Contains(url, "://") {
-		return resGetRemote(url, t.Fs.Source, t.Cfg, http.DefaultClient)
-	}
-	return resGetLocal(url, t.Fs.Source, t.Cfg)
-}
-
-// getJSON expects one or n-parts of a URL to a resource which can either be a local or a remote one.
-// If you provide multiple parts they will be joined together to the final URL.
-// GetJSON returns nil or parsed JSON to use in a short code.
-func (t *templateFuncster) getJSON(urlParts ...string) interface{} {
-	var v interface{}
-	url := strings.Join(urlParts, "")
-
-	for i := 0; i <= resRetries; i++ {
-		c, err := t.resGetResource(url)
-		if err != nil {
-			jww.ERROR.Printf("Failed to get json resource %s with error message %s", url, err)
-			return nil
-		}
-
-		err = json.Unmarshal(c, &v)
-		if err != nil {
-			jww.ERROR.Printf("Cannot read json from resource %s with error message %s", url, err)
-			jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep)
-			time.Sleep(resSleep)
-			resDeleteCache(url, t.Fs.Source, t.Cfg)
-			continue
-		}
-		break
-	}
-	return v
-}
-
-// parseCSV parses bytes of CSV data into a slice slice string or an error
-func parseCSV(c []byte, sep string) ([][]string, error) {
-	if len(sep) != 1 {
-		return nil, errors.New("Incorrect length of csv separator: " + sep)
-	}
-	b := bytes.NewReader(c)
-	r := csv.NewReader(b)
-	rSep := []rune(sep)
-	r.Comma = rSep[0]
-	r.FieldsPerRecord = 0
-	return r.ReadAll()
-}
-
-// getCSV expects a data separator and one or n-parts of a URL to a resource which
-// can either be a local or a remote one.
-// The data separator can be a comma, semi-colon, pipe, etc, but only one character.
-// If you provide multiple parts for the URL they will be joined together to the final URL.
-// GetCSV returns nil or a slice slice to use in a short code.
-func (t *templateFuncster) getCSV(sep string, urlParts ...string) [][]string {
-	var d [][]string
-	url := strings.Join(urlParts, "")
-
-	var clearCacheSleep = func(i int, u string) {
-		jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep)
-		time.Sleep(resSleep)
-		resDeleteCache(url, t.Fs.Source, t.Cfg)
-	}
-
-	for i := 0; i <= resRetries; i++ {
-		c, err := t.resGetResource(url)
-
-		if err == nil && !bytes.Contains(c, []byte(sep)) {
-			err = errors.New("Cannot find separator " + sep + " in CSV.")
-		}
-
-		if err != nil {
-			jww.ERROR.Printf("Failed to read csv resource %s with error message %s", url, err)
-			clearCacheSleep(i, url)
-			continue
-		}
-
-		if d, err = parseCSV(c, sep); err != nil {
-			jww.ERROR.Printf("Failed to parse csv file %s with error message %s", url, err)
-			clearCacheSleep(i, url)
-			continue
-		}
-		break
-	}
-	return d
-}
--- a/tpl/template_resources_test.go
+++ /dev/null
@@ -1,302 +1,0 @@
-// Copyright 2016 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 tpl
-
-import (
-	"bytes"
-	"fmt"
-	"net/http"
-	"net/http/httptest"
-	"net/url"
-	"strings"
-	"testing"
-
-	"github.com/spf13/afero"
-	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/hugo/hugofs"
-	"github.com/spf13/viper"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestScpCache(t *testing.T) {
-	t.Parallel()
-
-	tests := []struct {
-		path    string
-		content []byte
-		ignore  bool
-	}{
-		{"http://Foo.Bar/foo_Bar-Foo", []byte(`T€st Content 123`), false},
-		{"fOO,bar:foo%bAR", []byte(`T€st Content 123 fOO,bar:foo%bAR`), false},
-		{"FOo/BaR.html", []byte(`FOo/BaR.html T€st Content 123`), false},
-		{"трям/трям", []byte(`T€st трям/трям Content 123`), false},
-		{"은행", []byte(`T€st C은행ontent 123`), false},
-		{"Банковский кассир", []byte(`Банковский кассир T€st Content 123`), false},
-		{"Банковский кассир", []byte(`Банковский кассир T€st Content 456`), true},
-	}
-
-	fs := new(afero.MemMapFs)
-
-	for _, test := range tests {
-		cfg := viper.New()
-		c, err := resGetCache(test.path, fs, cfg, test.ignore)
-		if err != nil {
-			t.Errorf("Error getting cache: %s", err)
-		}
-		if c != nil {
-			t.Errorf("There is content where there should not be anything: %s", string(c))
-		}
-
-		err = resWriteCache(test.path, test.content, fs, cfg, test.ignore)
-		if err != nil {
-			t.Errorf("Error writing cache: %s", err)
-		}
-
-		c, err = resGetCache(test.path, fs, cfg, test.ignore)
-		if err != nil {
-			t.Errorf("Error getting cache after writing: %s", err)
-		}
-		if test.ignore {
-			if c != nil {
-				t.Errorf("Cache ignored but content is not nil: %s", string(c))
-			}
-		} else {
-			if !bytes.Equal(c, test.content) {
-				t.Errorf("\nExpected: %s\nActual: %s\n", string(test.content), string(c))
-			}
-		}
-	}
-}
-
-func TestScpGetLocal(t *testing.T) {
-	t.Parallel()
-	v := viper.New()
-	fs := hugofs.NewMem(v)
-	ps := helpers.FilePathSeparator
-
-	tests := []struct {
-		path    string
-		content []byte
-	}{
-		{"testpath" + ps + "test.txt", []byte(`T€st Content 123 fOO,bar:foo%bAR`)},
-		{"FOo" + ps + "BaR.html", []byte(`FOo/BaR.html T€st Content 123`)},
-		{"трям" + ps + "трям", []byte(`T€st трям/трям Content 123`)},
-		{"은행", []byte(`T€st C은행ontent 123`)},
-		{"Банковский кассир", []byte(`Банковский кассир T€st Content 123`)},
-	}
-
-	for _, test := range tests {
-		r := bytes.NewReader(test.content)
-		err := helpers.WriteToDisk(test.path, r, fs.Source)
-		if err != nil {
-			t.Error(err)
-		}
-
-		c, err := resGetLocal(test.path, fs.Source, v)
-		if err != nil {
-			t.Errorf("Error getting resource content: %s", err)
-		}
-		if !bytes.Equal(c, test.content) {
-			t.Errorf("\nExpected: %s\nActual: %s\n", string(test.content), string(c))
-		}
-	}
-
-}
-
-func getTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*httptest.Server, *http.Client) {
-	testServer := httptest.NewServer(http.HandlerFunc(handler))
-	client := &http.Client{
-		Transport: &http.Transport{Proxy: func(r *http.Request) (*url.URL, error) {
-			// Remove when https://github.com/golang/go/issues/13686 is fixed
-			r.Host = "gohugo.io"
-			return url.Parse(testServer.URL)
-		}},
-	}
-	return testServer, client
-}
-
-func TestScpGetRemote(t *testing.T) {
-	t.Parallel()
-	fs := new(afero.MemMapFs)
-
-	tests := []struct {
-		path    string
-		content []byte
-		ignore  bool
-	}{
-		{"http://Foo.Bar/foo_Bar-Foo", []byte(`T€st Content 123`), false},
-		{"http://Doppel.Gänger/foo_Bar-Foo", []byte(`T€st Cont€nt 123`), false},
-		{"http://Doppel.Gänger/Fizz_Bazz-Foo", []byte(`T€st Банковский кассир Cont€nt 123`), false},
-		{"http://Doppel.Gänger/Fizz_Bazz-Bar", []byte(`T€st Банковский кассир Cont€nt 456`), true},
-	}
-
-	for _, test := range tests {
-
-		srv, cl := getTestServer(func(w http.ResponseWriter, r *http.Request) {
-			w.Write(test.content)
-		})
-		defer func() { srv.Close() }()
-
-		cfg := viper.New()
-
-		c, err := resGetRemote(test.path, fs, cfg, cl)
-		if err != nil {
-			t.Errorf("Error getting resource content: %s", err)
-		}
-		if !bytes.Equal(c, test.content) {
-			t.Errorf("\nNet Expected: %s\nNet Actual: %s\n", string(test.content), string(c))
-		}
-		cc, cErr := resGetCache(test.path, fs, cfg, test.ignore)
-		if cErr != nil {
-			t.Error(cErr)
-		}
-		if test.ignore {
-			if cc != nil {
-				t.Errorf("Cache ignored but content is not nil: %s", string(cc))
-			}
-		} else {
-			if !bytes.Equal(cc, test.content) {
-				t.Errorf("\nCache Expected: %s\nCache Actual: %s\n", string(test.content), string(cc))
-			}
-		}
-	}
-}
-
-func TestParseCSV(t *testing.T) {
-	t.Parallel()
-
-	tests := []struct {
-		csv []byte
-		sep string
-		exp string
-		err bool
-	}{
-		{[]byte("a,b,c\nd,e,f\n"), "", "", true},
-		{[]byte("a,b,c\nd,e,f\n"), "~/", "", true},
-		{[]byte("a,b,c\nd,e,f"), "|", "a,b,cd,e,f", false},
-		{[]byte("q,w,e\nd,e,f"), ",", "qwedef", false},
-		{[]byte("a|b|c\nd|e|f|g"), "|", "abcdefg", true},
-		{[]byte("z|y|c\nd|e|f"), "|", "zycdef", false},
-	}
-	for _, test := range tests {
-		csv, err := parseCSV(test.csv, test.sep)
-		if test.err && err == nil {
-			t.Error("Expecting an error")
-		}
-		if test.err {
-			continue
-		}
-		if !test.err && err != nil {
-			t.Error(err)
-		}
-
-		act := ""
-		for _, v := range csv {
-			act = act + strings.Join(v, "")
-		}
-
-		if act != test.exp {
-			t.Errorf("\nExpected: %s\nActual: %s\n%#v\n", test.exp, act, csv)
-		}
-
-	}
-}
-
-func TestGetJSONFailParse(t *testing.T) {
-	t.Parallel()
-
-	f := newTestFuncster()
-
-	reqCount := 0
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if reqCount > 0 {
-			w.Header().Add("Content-type", "application/json")
-			fmt.Fprintln(w, `{"gomeetup":["Sydney", "San Francisco", "Stockholm"]}`)
-		} else {
-			w.WriteHeader(http.StatusInternalServerError)
-			fmt.Fprintln(w, `ERROR 500`)
-		}
-		reqCount++
-	}))
-	defer ts.Close()
-	url := ts.URL + "/test.json"
-
-	want := map[string]interface{}{"gomeetup": []interface{}{"Sydney", "San Francisco", "Stockholm"}}
-	have := f.getJSON(url)
-	assert.NotNil(t, have)
-	if have != nil {
-		assert.EqualValues(t, want, have)
-	}
-}
-
-func TestGetCSVFailParseSep(t *testing.T) {
-	t.Parallel()
-	f := newTestFuncster()
-
-	reqCount := 0
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if reqCount > 0 {
-			w.Header().Add("Content-type", "application/json")
-			fmt.Fprintln(w, `gomeetup,city`)
-			fmt.Fprintln(w, `yes,Sydney`)
-			fmt.Fprintln(w, `yes,San Francisco`)
-			fmt.Fprintln(w, `yes,Stockholm`)
-		} else {
-			w.WriteHeader(http.StatusInternalServerError)
-			fmt.Fprintln(w, `ERROR 500`)
-		}
-		reqCount++
-	}))
-	defer ts.Close()
-	url := ts.URL + "/test.csv"
-
-	want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
-	have := f.getCSV(",", url)
-	assert.NotNil(t, have)
-	if have != nil {
-		assert.EqualValues(t, want, have)
-	}
-}
-
-func TestGetCSVFailParse(t *testing.T) {
-	t.Parallel()
-
-	f := newTestFuncster()
-
-	reqCount := 0
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Add("Content-type", "application/json")
-		if reqCount > 0 {
-			fmt.Fprintln(w, `gomeetup,city`)
-			fmt.Fprintln(w, `yes,Sydney`)
-			fmt.Fprintln(w, `yes,San Francisco`)
-			fmt.Fprintln(w, `yes,Stockholm`)
-		} else {
-			fmt.Fprintln(w, `gomeetup,city`)
-			fmt.Fprintln(w, `yes,Sydney,Bondi,`) // wrong number of fields in line
-			fmt.Fprintln(w, `yes,San Francisco`)
-			fmt.Fprintln(w, `yes,Stockholm`)
-		}
-		reqCount++
-	}))
-	defer ts.Close()
-	url := ts.URL + "/test.csv"
-
-	want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
-	have := f.getCSV(",", url)
-	assert.NotNil(t, have)
-	if have != nil {
-		assert.EqualValues(t, want, have)
-	}
-}
--- a/tpl/template_test.go
+++ /dev/null
@@ -1,347 +1,0 @@
-// Copyright 2016 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 tpl
-
-import (
-	"bytes"
-	"errors"
-	"html/template"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"runtime"
-	"strings"
-	"testing"
-
-	"github.com/spf13/afero"
-	"github.com/spf13/hugo/deps"
-
-	"github.com/spf13/hugo/tplapi"
-	"github.com/spf13/viper"
-	"github.com/stretchr/testify/require"
-)
-
-// Some tests for Issue #1178 -- Ace
-func TestAceTemplates(t *testing.T) {
-	t.Parallel()
-
-	for i, this := range []struct {
-		basePath     string
-		innerPath    string
-		baseContent  string
-		innerContent string
-		expect       string
-		expectErr    int
-	}{
-		{"", filepath.FromSlash("_default/single.ace"), "", "{{ . }}", "DATA", 0},
-		{filepath.FromSlash("_default/baseof.ace"), filepath.FromSlash("_default/single.ace"),
-			`= content main
-  h2 This is a content named "main" of an inner template. {{ . }}`,
-			`= doctype html
-html lang=en
-  head
-    meta charset=utf-8
-    title Base and Inner Template
-  body
-    h1 This is a base template {{ . }}
-    = yield main`, `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Base and Inner Template</title></head><body><h1>This is a base template DATA</h1></body></html>`, 0},
-	} {
-
-		for _, root := range []string{"", os.TempDir()} {
-
-			basePath := this.basePath
-			innerPath := this.innerPath
-
-			if basePath != "" && root != "" {
-				basePath = filepath.Join(root, basePath)
-			}
-
-			if innerPath != "" && root != "" {
-				innerPath = filepath.Join(root, innerPath)
-			}
-
-			d := "DATA"
-
-			config := newDepsConfig(viper.New())
-			config.WithTemplate = func(templ tplapi.Template) error {
-				return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
-					[]byte(this.baseContent), []byte(this.innerContent))
-			}
-
-			a := deps.New(config)
-
-			if err := a.LoadResources(); err != nil {
-				t.Fatal(err)
-			}
-
-			templ := a.Tmpl.(*GoHTMLTemplate)
-
-			if len(templ.errors) > 0 && this.expectErr == 0 {
-				t.Errorf("Test %d with root '%s' errored: %v", i, root, templ.errors)
-			} else if len(templ.errors) == 0 && this.expectErr == 1 {
-				t.Errorf("#1 Test %d with root '%s' should have errored", i, root)
-			}
-
-			var buff bytes.Buffer
-			err := a.Tmpl.ExecuteTemplate(&buff, "mytemplate.html", d)
-
-			if err != nil && this.expectErr == 0 {
-				t.Errorf("Test %d with root '%s' errored: %s", i, root, err)
-			} else if err == nil && this.expectErr == 2 {
-				t.Errorf("#2 Test with root '%s' %d should have errored", root, i)
-			} else {
-				result := buff.String()
-				if result != this.expect {
-					t.Errorf("Test %d  with root '%s' got\n%s\nexpected\n%s", i, root, result, this.expect)
-				}
-			}
-
-		}
-	}
-
-}
-
-func isAtLeastGo16() bool {
-	version := runtime.Version()
-	return strings.Contains(version, "1.6") || strings.Contains(version, "1.7")
-}
-
-func TestAddTemplateFileWithMaster(t *testing.T) {
-	t.Parallel()
-
-	if !isAtLeastGo16() {
-		t.Skip("This test only runs on Go >= 1.6")
-	}
-
-	for i, this := range []struct {
-		masterTplContent  string
-		overlayTplContent string
-		writeSkipper      int
-		expect            interface{}
-	}{
-		{`A{{block "main" .}}C{{end}}C`, `{{define "main"}}B{{end}}`, 0, "ABC"},
-		{`A{{block "main" .}}C{{end}}C{{block "sub" .}}D{{end}}E`, `{{define "main"}}B{{end}}`, 0, "ABCDE"},
-		{`A{{block "main" .}}C{{end}}C{{block "sub" .}}D{{end}}E`, `{{define "main"}}B{{end}}{{define "sub"}}Z{{end}}`, 0, "ABCZE"},
-		{`tpl`, `tpl`, 1, false},
-		{`tpl`, `tpl`, 2, false},
-		{`{{.0.E}}`, `tpl`, 0, false},
-		{`tpl`, `{{.0.E}}`, 0, false},
-	} {
-
-		overlayTplName := "ot"
-		masterTplName := "mt"
-		finalTplName := "tp"
-
-		config := newDepsConfig(viper.New())
-		config.WithTemplate = func(templ tplapi.Template) error {
-
-			err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
-
-			if b, ok := this.expect.(bool); ok && !b {
-				if err == nil {
-					t.Errorf("[%d] AddTemplateFileWithMaster didn't return an expected error", i)
-				}
-			} else {
-
-				if err != nil {
-					t.Errorf("[%d] AddTemplateFileWithMaster failed: %s", i, err)
-					return nil
-				}
-
-				resultTpl := templ.Lookup(finalTplName)
-
-				if resultTpl == nil {
-					t.Errorf("[%d] AddTemplateFileWithMaster: Result template not found", i)
-					return nil
-				}
-
-				var b bytes.Buffer
-				err := resultTpl.Execute(&b, nil)
-
-				if err != nil {
-					t.Errorf("[%d] AddTemplateFileWithMaster execute failed: %s", i, err)
-					return nil
-				}
-				resultContent := b.String()
-
-				if resultContent != this.expect {
-					t.Errorf("[%d] AddTemplateFileWithMaster got \n%s but expected \n%v", i, resultContent, this.expect)
-				}
-			}
-
-			return nil
-		}
-
-		if this.writeSkipper != 1 {
-			afero.WriteFile(config.Fs.Source, masterTplName, []byte(this.masterTplContent), 0644)
-		}
-		if this.writeSkipper != 2 {
-			afero.WriteFile(config.Fs.Source, overlayTplName, []byte(this.overlayTplContent), 0644)
-		}
-
-		deps.New(config)
-
-	}
-
-}
-
-// A Go stdlib test for linux/arm. Will remove later.
-// See #1771
-func TestBigIntegerFunc(t *testing.T) {
-	t.Parallel()
-	var func1 = func(v int64) error {
-		return nil
-	}
-	var funcs = map[string]interface{}{
-		"A": func1,
-	}
-
-	tpl, err := template.New("foo").Funcs(funcs).Parse("{{ A 3e80 }}")
-	if err != nil {
-		t.Fatal("Parse failed:", err)
-	}
-	err = tpl.Execute(ioutil.Discard, "foo")
-
-	if err == nil {
-		t.Fatal("Execute should have failed")
-	}
-
-	t.Log("Got expected error:", err)
-
-}
-
-// A Go stdlib test for linux/arm. Will remove later.
-// See #1771
-type BI struct {
-}
-
-func (b BI) A(v int64) error {
-	return nil
-}
-func TestBigIntegerMethod(t *testing.T) {
-	t.Parallel()
-
-	data := &BI{}
-
-	tpl, err := template.New("foo2").Parse("{{ .A 3e80 }}")
-	if err != nil {
-		t.Fatal("Parse failed:", err)
-	}
-	err = tpl.ExecuteTemplate(ioutil.Discard, "foo2", data)
-
-	if err == nil {
-		t.Fatal("Execute should have failed")
-	}
-
-	t.Log("Got expected error:", err)
-
-}
-
-// Test for bugs discovered by https://github.com/dvyukov/go-fuzz
-func TestTplGoFuzzReports(t *testing.T) {
-	t.Parallel()
-
-	// The following test case(s) also fail
-	// See https://github.com/golang/go/issues/10634
-	//{"{{ seq 433937734937734969526500969526500 }}", 2}}
-
-	for i, this := range []struct {
-		data      string
-		expectErr int
-	}{
-		// Issue #1089
-		//{"{{apply .C \"first\" }}", 2},
-		// Issue #1090
-		{"{{ slicestr \"000000\" 10}}", 2},
-		// Issue #1091
-		//{"{{apply .C \"first\" 0 0 0}}", 2},
-		{"{{seq 3e80}}", 2},
-		// Issue #1095
-		{"{{apply .C \"urlize\" " +
-			"\".\"}}", 2}} {
-
-		d := &Data{
-			A: 42,
-			B: "foo",
-			C: []int{1, 2, 3},
-			D: map[int]string{1: "foo", 2: "bar"},
-			E: Data1{42, "foo"},
-			F: []string{"a", "b", "c"},
-			G: []string{"a", "b", "c", "d", "e"},
-			H: "a,b,c,d,e,f",
-		}
-
-		config := newDepsConfig(viper.New())
-
-		config.WithTemplate = func(templ tplapi.Template) error {
-			return templ.AddTemplate("fuzz", this.data)
-		}
-
-		de := deps.New(config)
-		require.NoError(t, de.LoadResources())
-
-		templ := de.Tmpl.(*GoHTMLTemplate)
-
-		if len(templ.errors) > 0 && this.expectErr == 0 {
-			t.Errorf("Test %d errored: %v", i, templ.errors)
-		} else if len(templ.errors) == 0 && this.expectErr == 1 {
-			t.Errorf("#1 Test %d should have errored", i)
-		}
-
-		err := de.Tmpl.ExecuteTemplate(ioutil.Discard, "fuzz", d)
-
-		if err != nil && this.expectErr == 0 {
-			t.Fatalf("Test %d errored: %s", i, err)
-		} else if err == nil && this.expectErr == 2 {
-			t.Fatalf("#2 Test %d should have errored", i)
-		}
-
-	}
-}
-
-type Data struct {
-	A int
-	B string
-	C []int
-	D map[int]string
-	E Data1
-	F []string
-	G []string
-	H string
-}
-
-type Data1 struct {
-	A int
-	B string
-}
-
-func (Data1) Q() string {
-	return "foo"
-}
-
-func (Data1) W() (string, error) {
-	return "foo", nil
-}
-
-func (Data1) E() (string, error) {
-	return "foo", errors.New("Data.E error")
-}
-
-func (Data1) R(v int) (string, error) {
-	return "foo", nil
-}
-
-func (Data1) T(s string) (string, error) {
-	return s, nil
-}
--- /dev/null
+++ b/tpl/tplimpl/amber_compiler.go
@@ -1,0 +1,42 @@
+// 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 tplimpl
+
+import (
+	"html/template"
+
+	"github.com/eknkc/amber"
+)
+
+func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *template.Template) (*template.Template, error) {
+	c := amber.New()
+
+	if err := c.ParseData(b, path); err != nil {
+		return nil, err
+	}
+
+	data, err := c.CompileString()
+
+	if err != nil {
+		return nil, err
+	}
+
+	tpl, err := t.Funcs(gt.amberFuncMap).Parse(data)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return tpl, nil
+}
--- /dev/null
+++ b/tpl/tplimpl/reflect_helpers.go
@@ -1,0 +1,70 @@
+// 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 tplimpl
+
+import (
+	"reflect"
+	"time"
+)
+
+// toInt returns the int value if possible, -1 if not.
+func toInt(v reflect.Value) int64 {
+	switch v.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int()
+	case reflect.Interface:
+		return toInt(v.Elem())
+	}
+	return -1
+}
+
+// toString returns the string value if possible, "" if not.
+func toString(v reflect.Value) string {
+	switch v.Kind() {
+	case reflect.String:
+		return v.String()
+	case reflect.Interface:
+		return toString(v.Elem())
+	}
+	return ""
+}
+
+var (
+	zero      reflect.Value
+	errorType = reflect.TypeOf((*error)(nil)).Elem()
+	timeType  = reflect.TypeOf((*time.Time)(nil)).Elem()
+)
+
+func toTimeUnix(v reflect.Value) int64 {
+	if v.Kind() == reflect.Interface {
+		return toTimeUnix(v.Elem())
+	}
+	if v.Type() != timeType {
+		panic("coding error: argument must be time.Time type reflect Value")
+	}
+	return v.MethodByName("Unix").Call([]reflect.Value{})[0].Int()
+}
+
+// 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
+}
--- /dev/null
+++ b/tpl/tplimpl/template.go
@@ -1,0 +1,575 @@
+// Copyright 2016 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 tplimpl
+
+import (
+	"fmt"
+	"html/template"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"sync"
+
+	"github.com/eknkc/amber"
+	"github.com/spf13/afero"
+	bp "github.com/spf13/hugo/bufferpool"
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/helpers"
+	"github.com/yosssi/ace"
+)
+
+// TODO(bep) globals get rid of the rest of the jww.ERR etc.
+
+// Protecting global map access (Amber)
+var amberMu sync.Mutex
+
+type templateErr struct {
+	name string
+	err  error
+}
+
+type GoHTMLTemplate struct {
+	*template.Template
+
+	clone *template.Template
+
+	// a separate storage for the overlays created from cloned master templates.
+	// note: No mutex protection, so we add these in one Go routine, then just read.
+	overlays map[string]*template.Template
+
+	errors []*templateErr
+
+	funcster *templateFuncster
+
+	amberFuncMap template.FuncMap
+
+	*deps.Deps
+}
+
+type TemplateProvider struct{}
+
+var DefaultTemplateProvider *TemplateProvider
+
+// Update updates the Hugo Template System in the provided Deps.
+// with all the additional features, templates & functions
+func (*TemplateProvider) Update(deps *deps.Deps) error {
+	// TODO(bep) check that this isn't called too many times.
+	tmpl := &GoHTMLTemplate{
+		Template: template.New(""),
+		overlays: make(map[string]*template.Template),
+		errors:   make([]*templateErr, 0),
+		Deps:     deps,
+	}
+
+	deps.Tmpl = tmpl
+
+	tmpl.initFuncs(deps)
+
+	tmpl.LoadEmbedded()
+
+	if deps.WithTemplate != nil {
+		err := deps.WithTemplate(tmpl)
+		if err != nil {
+			tmpl.errors = append(tmpl.errors, &templateErr{"init", err})
+		}
+
+	}
+
+	tmpl.MarkReady()
+
+	return nil
+
+}
+
+// Clone clones
+func (*TemplateProvider) Clone(d *deps.Deps) error {
+
+	t := d.Tmpl.(*GoHTMLTemplate)
+
+	// 1. Clone the clone with new template funcs
+	// 2. Clone any overlays with new template funcs
+
+	tmpl := &GoHTMLTemplate{
+		Template: template.Must(t.Template.Clone()),
+		overlays: make(map[string]*template.Template),
+		errors:   make([]*templateErr, 0),
+		Deps:     d,
+	}
+
+	d.Tmpl = tmpl
+	tmpl.initFuncs(d)
+
+	for k, v := range t.overlays {
+		vc := template.Must(v.Clone())
+		// The extra lookup is a workaround, see
+		// * https://github.com/golang/go/issues/16101
+		// * https://github.com/spf13/hugo/issues/2549
+		vc = vc.Lookup(vc.Name())
+		vc.Funcs(tmpl.funcster.funcMap)
+		tmpl.overlays[k] = vc
+	}
+
+	tmpl.MarkReady()
+
+	return nil
+}
+
+func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) {
+
+	t.funcster = newTemplateFuncster(d)
+
+	// The URL funcs in the funcMap is somewhat language dependent,
+	// so we need to wait until the language and site config is loaded.
+	t.funcster.initFuncMap()
+
+	t.amberFuncMap = template.FuncMap{}
+
+	amberMu.Lock()
+	for k, v := range amber.FuncMap {
+		t.amberFuncMap[k] = v
+	}
+
+	for k, v := range t.funcster.funcMap {
+		t.amberFuncMap[k] = v
+		// Hacky, but we need to make sure that the func names are in the global map.
+		amber.FuncMap[k] = func() string {
+			panic("should never be invoked")
+		}
+	}
+	amberMu.Unlock()
+
+}
+
+func (t *GoHTMLTemplate) Funcs(funcMap template.FuncMap) {
+	t.Template.Funcs(funcMap)
+}
+
+func (t *GoHTMLTemplate) Partial(name string, contextList ...interface{}) template.HTML {
+	if strings.HasPrefix("partials/", name) {
+		name = name[8:]
+	}
+	var context interface{}
+
+	if len(contextList) == 0 {
+		context = nil
+	} else {
+		context = contextList[0]
+	}
+	return t.ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name)
+}
+
+func (t *GoHTMLTemplate) executeTemplate(context interface{}, w io.Writer, layouts ...string) {
+	var worked bool
+	for _, layout := range layouts {
+		templ := t.Lookup(layout)
+		if templ == nil {
+			layout += ".html"
+			templ = t.Lookup(layout)
+		}
+
+		if templ != nil {
+			if err := templ.Execute(w, context); err != nil {
+				helpers.DistinctErrorLog.Println(layout, err)
+			}
+			worked = true
+			break
+		}
+	}
+	if !worked {
+		t.Log.ERROR.Println("Unable to render", layouts)
+		t.Log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
+	}
+}
+
+func (t *GoHTMLTemplate) ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML {
+	b := bp.GetBuffer()
+	defer bp.PutBuffer(b)
+	t.executeTemplate(context, b, layouts...)
+	return template.HTML(b.String())
+}
+
+func (t *GoHTMLTemplate) Lookup(name string) *template.Template {
+
+	if templ := t.Template.Lookup(name); templ != nil {
+		return templ
+	}
+
+	if t.overlays != nil {
+		if templ, ok := t.overlays[name]; ok {
+			return templ
+		}
+	}
+
+	// The clone is used for the non-renderable HTML pages (p.IsRenderable == false) that is parsed
+	// as Go templates late in the build process.
+	if t.clone != nil {
+		if templ := t.clone.Lookup(name); templ != nil {
+			return templ
+		}
+	}
+
+	return nil
+
+}
+
+func (t *GoHTMLTemplate) GetClone() *template.Template {
+	return t.clone
+}
+
+func (t *GoHTMLTemplate) LoadEmbedded() {
+	t.EmbedShortcodes()
+	t.EmbedTemplates()
+}
+
+// MarkReady marks the template as "ready for execution". No changes allowed
+// after this is set.
+func (t *GoHTMLTemplate) MarkReady() {
+	if t.clone == nil {
+		t.clone = template.Must(t.Template.Clone())
+	}
+}
+
+func (t *GoHTMLTemplate) checkState() {
+	if t.clone != nil {
+		panic("template is cloned and cannot be modfified")
+	}
+}
+
+func (t *GoHTMLTemplate) AddInternalTemplate(prefix, name, tpl string) error {
+	if prefix != "" {
+		return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
+	}
+	return t.AddTemplate("_internal/"+name, tpl)
+}
+
+func (t *GoHTMLTemplate) AddInternalShortcode(name, content string) error {
+	return t.AddInternalTemplate("shortcodes", name, content)
+}
+
+func (t *GoHTMLTemplate) AddTemplate(name, tpl string) error {
+	t.checkState()
+	templ, err := t.New(name).Parse(tpl)
+	if err != nil {
+		t.errors = append(t.errors, &templateErr{name: name, err: err})
+		return err
+	}
+	if err := applyTemplateTransformers(templ); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error {
+
+	// There is currently no known way to associate a cloned template with an existing one.
+	// This funky master/overlay design will hopefully improve in a future version of Go.
+	//
+	// Simplicity is hard.
+	//
+	// Until then we'll have to live with this hackery.
+	//
+	// See https://github.com/golang/go/issues/14285
+	//
+	// So, to do minimum amount of changes to get this to work:
+	//
+	// 1. Lookup or Parse the master
+	// 2. Parse and store the overlay in a separate map
+
+	masterTpl := t.Lookup(masterFilename)
+
+	if masterTpl == nil {
+		b, err := afero.ReadFile(t.Fs.Source, masterFilename)
+		if err != nil {
+			return err
+		}
+		masterTpl, err = t.New(masterFilename).Parse(string(b))
+
+		if err != nil {
+			// TODO(bep) Add a method that does this
+			t.errors = append(t.errors, &templateErr{name: name, err: err})
+			return err
+		}
+	}
+
+	b, err := afero.ReadFile(t.Fs.Source, overlayFilename)
+	if err != nil {
+		return err
+	}
+
+	overlayTpl, err := template.Must(masterTpl.Clone()).Parse(string(b))
+	if err != nil {
+		t.errors = append(t.errors, &templateErr{name: name, err: err})
+	} else {
+		// The extra lookup is a workaround, see
+		// * https://github.com/golang/go/issues/16101
+		// * https://github.com/spf13/hugo/issues/2549
+		overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
+		if err := applyTemplateTransformers(overlayTpl); err != nil {
+			return err
+		}
+		t.overlays[name] = overlayTpl
+	}
+
+	return err
+}
+
+func (t *GoHTMLTemplate) AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error {
+	t.checkState()
+	var base, inner *ace.File
+	name = name[:len(name)-len(filepath.Ext(innerPath))] + ".html"
+
+	// Fixes issue #1178
+	basePath = strings.Replace(basePath, "\\", "/", -1)
+	innerPath = strings.Replace(innerPath, "\\", "/", -1)
+
+	if basePath != "" {
+		base = ace.NewFile(basePath, baseContent)
+		inner = ace.NewFile(innerPath, innerContent)
+	} else {
+		base = ace.NewFile(innerPath, innerContent)
+		inner = ace.NewFile("", []byte{})
+	}
+	parsed, err := ace.ParseSource(ace.NewSource(base, inner, []*ace.File{}), nil)
+	if err != nil {
+		t.errors = append(t.errors, &templateErr{name: name, err: err})
+		return err
+	}
+	templ, err := ace.CompileResultWithTemplate(t.New(name), parsed, nil)
+	if err != nil {
+		t.errors = append(t.errors, &templateErr{name: name, err: err})
+		return err
+	}
+	return applyTemplateTransformers(templ)
+}
+
+func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) error {
+	t.checkState()
+	// get the suffix and switch on that
+	ext := filepath.Ext(path)
+	switch ext {
+	case ".amber":
+		templateName := strings.TrimSuffix(name, filepath.Ext(name)) + ".html"
+		b, err := afero.ReadFile(t.Fs.Source, path)
+
+		if err != nil {
+			return err
+		}
+
+		amberMu.Lock()
+		templ, err := t.CompileAmberWithTemplate(b, path, t.New(templateName))
+		amberMu.Unlock()
+		if err != nil {
+			return err
+		}
+
+		return applyTemplateTransformers(templ)
+	case ".ace":
+		var innerContent, baseContent []byte
+		innerContent, err := afero.ReadFile(t.Fs.Source, path)
+
+		if err != nil {
+			return err
+		}
+
+		if baseTemplatePath != "" {
+			baseContent, err = afero.ReadFile(t.Fs.Source, baseTemplatePath)
+			if err != nil {
+				return err
+			}
+		}
+
+		return t.AddAceTemplate(name, baseTemplatePath, path, baseContent, innerContent)
+	default:
+
+		if baseTemplatePath != "" {
+			return t.AddTemplateFileWithMaster(name, path, baseTemplatePath)
+		}
+
+		b, err := afero.ReadFile(t.Fs.Source, path)
+
+		if err != nil {
+			return err
+		}
+
+		t.Log.DEBUG.Printf("Add template file from path %s", path)
+
+		return t.AddTemplate(name, string(b))
+	}
+
+}
+
+func (t *GoHTMLTemplate) GenerateTemplateNameFrom(base, path string) string {
+	name, _ := filepath.Rel(base, path)
+	return filepath.ToSlash(name)
+}
+
+func isDotFile(path string) bool {
+	return filepath.Base(path)[0] == '.'
+}
+
+func isBackupFile(path string) bool {
+	return path[len(path)-1] == '~'
+}
+
+const baseFileBase = "baseof"
+
+var aceTemplateInnerMarkers = [][]byte{[]byte("= content")}
+var goTemplateInnerMarkers = [][]byte{[]byte("{{define"), []byte("{{ define")}
+
+func isBaseTemplate(path string) bool {
+	return strings.Contains(path, baseFileBase)
+}
+
+func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
+	t.Log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix)
+	walker := func(path string, fi os.FileInfo, err error) error {
+		if err != nil {
+			return nil
+		}
+		t.Log.DEBUG.Println("Template path", path)
+		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
+			link, err := filepath.EvalSymlinks(absPath)
+			if err != nil {
+				t.Log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err)
+				return nil
+			}
+			linkfi, err := t.Fs.Source.Stat(link)
+			if err != nil {
+				t.Log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
+				return nil
+			}
+			if !linkfi.Mode().IsRegular() {
+				t.Log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath)
+			}
+			return nil
+		}
+
+		if !fi.IsDir() {
+			if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
+				return nil
+			}
+
+			tplName := t.GenerateTemplateNameFrom(absPath, path)
+
+			if prefix != "" {
+				tplName = strings.Trim(prefix, "/") + "/" + tplName
+			}
+
+			var baseTemplatePath string
+
+			// Ace and Go templates may have both a base and inner template.
+			pathDir := filepath.Dir(path)
+			if filepath.Ext(path) != ".amber" && !strings.HasSuffix(pathDir, "partials") && !strings.HasSuffix(pathDir, "shortcodes") {
+
+				innerMarkers := goTemplateInnerMarkers
+				baseFileName := fmt.Sprintf("%s.html", baseFileBase)
+
+				if filepath.Ext(path) == ".ace" {
+					innerMarkers = aceTemplateInnerMarkers
+					baseFileName = fmt.Sprintf("%s.ace", baseFileBase)
+				}
+
+				// This may be a view that shouldn't have base template
+				// Have to look inside it to make sure
+				needsBase, err := helpers.FileContainsAny(path, innerMarkers, t.Fs.Source)
+				if err != nil {
+					return err
+				}
+				if needsBase {
+
+					layoutDir := t.PathSpec.GetLayoutDirPath()
+					currBaseFilename := fmt.Sprintf("%s-%s", helpers.Filename(path), baseFileName)
+					templateDir := filepath.Dir(path)
+					themeDir := filepath.Join(t.PathSpec.GetThemeDir())
+					relativeThemeLayoutsDir := filepath.Join(t.PathSpec.GetRelativeThemeDir(), "layouts")
+
+					var baseTemplatedDir string
+
+					if strings.HasPrefix(templateDir, relativeThemeLayoutsDir) {
+						baseTemplatedDir = strings.TrimPrefix(templateDir, relativeThemeLayoutsDir)
+					} else {
+						baseTemplatedDir = strings.TrimPrefix(templateDir, layoutDir)
+					}
+
+					baseTemplatedDir = strings.TrimPrefix(baseTemplatedDir, helpers.FilePathSeparator)
+
+					// Look for base template in the follwing order:
+					//   1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
+					//   2. <current-path>/baseof.<suffix>
+					//   3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
+					//   4. _default/baseof.<suffix>
+					// For each of the steps above, it will first look in the project, then, if theme is set,
+					// in the theme's layouts folder.
+
+					pairsToCheck := [][]string{
+						[]string{baseTemplatedDir, currBaseFilename},
+						[]string{baseTemplatedDir, baseFileName},
+						[]string{"_default", currBaseFilename},
+						[]string{"_default", baseFileName},
+					}
+
+				Loop:
+					for _, pair := range pairsToCheck {
+						pathsToCheck := basePathsToCheck(pair, layoutDir, themeDir)
+						for _, pathToCheck := range pathsToCheck {
+							if ok, err := helpers.Exists(pathToCheck, t.Fs.Source); err == nil && ok {
+								baseTemplatePath = pathToCheck
+								break Loop
+							}
+						}
+					}
+				}
+			}
+
+			if err := t.AddTemplateFile(tplName, baseTemplatePath, path); err != nil {
+				t.Log.ERROR.Printf("Failed to add template %s in path %s: %s", tplName, path, err)
+			}
+
+		}
+		return nil
+	}
+	if err := helpers.SymbolicWalk(t.Fs.Source, absPath, walker); err != nil {
+		t.Log.ERROR.Printf("Failed to load templates: %s", err)
+	}
+}
+
+func basePathsToCheck(path []string, layoutDir, themeDir string) []string {
+	// Always look in the project.
+	pathsToCheck := []string{filepath.Join((append([]string{layoutDir}, path...))...)}
+
+	// May have a theme
+	if themeDir != "" {
+		pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{themeDir, "layouts"}, path...))...))
+	}
+
+	return pathsToCheck
+
+}
+
+func (t *GoHTMLTemplate) LoadTemplatesWithPrefix(absPath string, prefix string) {
+	t.loadTemplates(absPath, prefix)
+}
+
+func (t *GoHTMLTemplate) LoadTemplates(absPath string) {
+	t.loadTemplates(absPath, "")
+}
+
+func (t *GoHTMLTemplate) PrintErrors() {
+	for i, e := range t.errors {
+		t.Log.ERROR.Println(i, ":", e.err)
+	}
+}
--- /dev/null
+++ b/tpl/tplimpl/template_ast_transformers.go
@@ -1,0 +1,259 @@
+// Copyright 2016 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 tplimpl
+
+import (
+	"errors"
+	"html/template"
+	"strings"
+	"text/template/parse"
+)
+
+// decl keeps track of the variable mappings, i.e. $mysite => .Site etc.
+type decl map[string]string
+
+var paramsPaths = [][]string{
+	{"Params"},
+	{"Site", "Params"},
+
+	// Site and Pag referenced from shortcodes
+	{"Page", "Site", "Params"},
+	{"Page", "Params"},
+
+	{"Site", "Language", "Params"},
+}
+
+type templateContext struct {
+	decl  decl
+	templ *template.Template
+}
+
+func newTemplateContext(templ *template.Template) *templateContext {
+	return &templateContext{templ: templ, decl: make(map[string]string)}
+
+}
+
+func applyTemplateTransformers(templ *template.Template) error {
+	if templ == nil || templ.Tree == nil {
+		return errors.New("expected template, but none provided")
+	}
+
+	c := newTemplateContext(templ)
+
+	c.paramsKeysToLower(templ.Tree.Root)
+
+	return nil
+}
+
+// paramsKeysToLower is made purposely non-generic to make it not so tempting
+// to do more of these hard-to-maintain AST transformations.
+func (c *templateContext) paramsKeysToLower(n parse.Node) {
+
+	switch x := n.(type) {
+	case *parse.ListNode:
+		if x != nil {
+			c.paramsKeysToLowerForNodes(x.Nodes...)
+		}
+	case *parse.ActionNode:
+		c.paramsKeysToLowerForNodes(x.Pipe)
+	case *parse.IfNode:
+		c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList)
+	case *parse.WithNode:
+		c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList)
+	case *parse.RangeNode:
+		c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList)
+	case *parse.TemplateNode:
+		subTempl := c.templ.Lookup(x.Name)
+		if subTempl != nil {
+			c.paramsKeysToLowerForNodes(subTempl.Tree.Root)
+		}
+	case *parse.PipeNode:
+		for i, elem := range x.Decl {
+			if len(x.Cmds) > i {
+				// maps $site => .Site etc.
+				c.decl[elem.Ident[0]] = x.Cmds[i].String()
+			}
+		}
+
+		for _, cmd := range x.Cmds {
+			c.paramsKeysToLower(cmd)
+		}
+
+	case *parse.CommandNode:
+		for _, elem := range x.Args {
+			switch an := elem.(type) {
+			case *parse.FieldNode:
+				c.updateIdentsIfNeeded(an.Ident)
+			case *parse.VariableNode:
+				c.updateIdentsIfNeeded(an.Ident)
+			case *parse.PipeNode:
+				c.paramsKeysToLower(an)
+			}
+
+		}
+	}
+}
+
+func (c *templateContext) paramsKeysToLowerForNodes(nodes ...parse.Node) {
+	for _, node := range nodes {
+		c.paramsKeysToLower(node)
+	}
+}
+
+func (c *templateContext) updateIdentsIfNeeded(idents []string) {
+	index := c.decl.indexOfReplacementStart(idents)
+
+	if index == -1 {
+		return
+	}
+
+	for i := index; i < len(idents); i++ {
+		idents[i] = strings.ToLower(idents[i])
+	}
+}
+
+// indexOfReplacementStart will return the index of where to start doing replacement,
+// -1 if none needed.
+func (d decl) indexOfReplacementStart(idents []string) int {
+
+	l := len(idents)
+
+	if l == 0 {
+		return -1
+	}
+
+	first := idents[0]
+	firstIsVar := first[0] == '$'
+
+	if l == 1 && !firstIsVar {
+		// This can not be a Params.x
+		return -1
+	}
+
+	if !firstIsVar {
+		found := false
+		for _, paramsPath := range paramsPaths {
+			if first == paramsPath[0] {
+				found = true
+				break
+			}
+		}
+		if !found {
+			return -1
+		}
+	}
+
+	var (
+		resolvedIdents []string
+		replacements   []string
+		replaced       []string
+	)
+
+	// An Ident can start out as one of
+	// [Params] [$blue] [$colors.Blue]
+	// We need to resolve the variables, so
+	// $blue => [Params Colors Blue]
+	// etc.
+	replacements = []string{idents[0]}
+
+	// Loop until there are no more $vars to resolve.
+	for i := 0; i < len(replacements); i++ {
+
+		if i > 20 {
+			// bail out
+			return -1
+		}
+
+		potentialVar := replacements[i]
+
+		if potentialVar == "$" {
+			continue
+		}
+
+		if potentialVar == "" || potentialVar[0] != '$' {
+			// leave it as is
+			replaced = append(replaced, strings.Split(potentialVar, ".")...)
+			continue
+		}
+
+		replacement, ok := d[potentialVar]
+
+		if !ok {
+			// Temporary range vars. We do not care about those.
+			return -1
+		}
+
+		replacement = strings.TrimPrefix(replacement, ".")
+
+		if replacement == "" {
+			continue
+		}
+
+		if replacement[0] == '$' {
+			// Needs further expansion
+			replacements = append(replacements, strings.Split(replacement, ".")...)
+		} else {
+			replaced = append(replaced, strings.Split(replacement, ".")...)
+		}
+	}
+
+	resolvedIdents = append(replaced, idents[1:]...)
+
+	for _, paramPath := range paramsPaths {
+		if index := indexOfFirstRealIdentAfterWords(resolvedIdents, idents, paramPath...); index != -1 {
+			return index
+		}
+	}
+
+	return -1
+
+}
+
+func indexOfFirstRealIdentAfterWords(resolvedIdents, idents []string, words ...string) int {
+	if !sliceStartsWith(resolvedIdents, words...) {
+		return -1
+	}
+
+	for i, ident := range idents {
+		if ident == "" || ident[0] == '$' {
+			continue
+		}
+		found := true
+		for _, word := range words {
+			if ident == word {
+				found = false
+				break
+			}
+		}
+		if found {
+			return i
+		}
+	}
+
+	return -1
+}
+
+func sliceStartsWith(slice []string, words ...string) bool {
+
+	if len(slice) < len(words) {
+		return false
+	}
+
+	for i, word := range words {
+		if word != slice[i] {
+			return false
+		}
+	}
+	return true
+}
--- /dev/null
+++ b/tpl/tplimpl/template_ast_transformers_test.go
@@ -1,0 +1,269 @@
+// Copyright 2016 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 tplimpl
+
+import (
+	"bytes"
+	"testing"
+
+	"html/template"
+
+	"github.com/stretchr/testify/require"
+)
+
+var (
+	testFuncs = map[string]interface{}{
+		"Echo": func(v interface{}) interface{} { return v },
+	}
+
+	paramsData = map[string]interface{}{
+		"NotParam": "Hi There",
+		"Slice":    []int{1, 3},
+		"Params": map[string]interface{}{
+			"lower": "P1L",
+		},
+		"Site": map[string]interface{}{
+			"Params": map[string]interface{}{
+				"lower": "P2L",
+				"slice": []int{1, 3},
+			},
+			"Language": map[string]interface{}{
+				"Params": map[string]interface{}{
+					"lower": "P22L",
+				},
+			},
+			"Data": map[string]interface{}{
+				"Params": map[string]interface{}{
+					"NOLOW": "P3H",
+				},
+			},
+		},
+	}
+
+	paramsTempl = `
+{{ $page := . }}
+{{ $pageParams := .Params }}
+{{ $site := .Site }}
+{{ $siteParams := .Site.Params }}
+{{ $data := .Site.Data }}
+{{ $notparam := .NotParam }}
+
+P1: {{ .Params.LOWER }}
+P1_2: {{ $.Params.LOWER }}
+P1_3: {{ $page.Params.LOWER }}
+P1_4: {{ $pageParams.LOWER }}
+P2: {{ .Site.Params.LOWER }}
+P2_2: {{ $.Site.Params.LOWER }}
+P2_3: {{ $site.Params.LOWER }}
+P2_4: {{ $siteParams.LOWER }}
+P22: {{ .Site.Language.Params.LOWER }}
+P3: {{ .Site.Data.Params.NOLOW }}
+P3_2: {{ $.Site.Data.Params.NOLOW }}
+P3_3: {{ $site.Data.Params.NOLOW }}
+P3_4: {{ $data.Params.NOLOW }}
+P4: {{ range $i, $e := .Site.Params.SLICE }}{{ $e }}{{ end }}
+P5: {{ Echo .Params.LOWER }}
+P5_2: {{ Echo $site.Params.LOWER }}
+{{ if .Params.LOWER }}
+IF: {{ .Params.LOWER }}
+{{ end }}
+{{ if .Params.NOT_EXIST }}
+{{ else }}
+ELSE: {{ .Params.LOWER }}
+{{ end }}
+
+
+{{ with .Params.LOWER }}
+WITH: {{ . }}
+{{ end }}
+
+
+{{ range .Slice }}
+RANGE: {{ . }}: {{ $.Params.LOWER }}
+{{ end }}
+{{ index .Slice 1 }}
+{{ .NotParam }}
+{{ .NotParam }}
+{{ .NotParam }}
+{{ .NotParam }}
+{{ .NotParam }}
+{{ .NotParam }}
+{{ .NotParam }}
+{{ .NotParam }}
+{{ .NotParam }}
+{{ .NotParam }}
+{{ $notparam }}
+
+
+{{ $lower := .Site.Params.LOWER }}
+F1: {{ printf "themes/%s-theme" .Site.Params.LOWER }}
+F2: {{ Echo (printf "themes/%s-theme" $lower) }}
+F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }}
+`
+)
+
+func TestParamsKeysToLower(t *testing.T) {
+	t.Parallel()
+
+	require.Error(t, applyTemplateTransformers(nil))
+
+	templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
+
+	require.NoError(t, err)
+
+	c := newTemplateContext(templ)
+
+	require.Equal(t, -1, c.decl.indexOfReplacementStart([]string{}))
+
+	c.paramsKeysToLower(templ.Tree.Root)
+
+	var b bytes.Buffer
+
+	require.NoError(t, templ.Execute(&b, paramsData))
+
+	result := b.String()
+
+	require.Contains(t, result, "P1: P1L")
+	require.Contains(t, result, "P1_2: P1L")
+	require.Contains(t, result, "P1_3: P1L")
+	require.Contains(t, result, "P1_4: P1L")
+	require.Contains(t, result, "P2: P2L")
+	require.Contains(t, result, "P2_2: P2L")
+	require.Contains(t, result, "P2_3: P2L")
+	require.Contains(t, result, "P2_4: P2L")
+	require.Contains(t, result, "P22: P22L")
+	require.Contains(t, result, "P3: P3H")
+	require.Contains(t, result, "P3_2: P3H")
+	require.Contains(t, result, "P3_3: P3H")
+	require.Contains(t, result, "P3_4: P3H")
+	require.Contains(t, result, "P4: 13")
+	require.Contains(t, result, "P5: P1L")
+	require.Contains(t, result, "P5_2: P2L")
+
+	require.Contains(t, result, "IF: P1L")
+	require.Contains(t, result, "ELSE: P1L")
+
+	require.Contains(t, result, "WITH: P1L")
+
+	require.Contains(t, result, "RANGE: 3: P1L")
+
+	require.Contains(t, result, "Hi There")
+
+	// Issue #2740
+	require.Contains(t, result, "F1: themes/P2L-theme")
+	require.Contains(t, result, "F2: themes/P2L-theme")
+	require.Contains(t, result, "F3: themes/P2L-theme")
+
+}
+
+func BenchmarkTemplateParamsKeysToLower(b *testing.B) {
+	templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
+
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	templates := make([]*template.Template, b.N)
+
+	for i := 0; i < b.N; i++ {
+		templates[i], err = templ.Clone()
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+
+	b.ResetTimer()
+
+	for i := 0; i < b.N; i++ {
+		c := newTemplateContext(templates[i])
+		c.paramsKeysToLower(templ.Tree.Root)
+	}
+}
+
+func TestParamsKeysToLowerVars(t *testing.T) {
+	t.Parallel()
+	var (
+		ctx = map[string]interface{}{
+			"Params": map[string]interface{}{
+				"colors": map[string]interface{}{
+					"blue": "Amber",
+				},
+			},
+		}
+
+		// This is how Amber behaves:
+		paramsTempl = `
+{{$__amber_1 := .Params.Colors}}
+{{$__amber_2 := $__amber_1.Blue}}
+Color: {{$__amber_2}}
+Blue: {{ $__amber_1.Blue}}
+`
+	)
+
+	templ, err := template.New("foo").Parse(paramsTempl)
+
+	require.NoError(t, err)
+
+	c := newTemplateContext(templ)
+
+	c.paramsKeysToLower(templ.Tree.Root)
+
+	var b bytes.Buffer
+
+	require.NoError(t, templ.Execute(&b, ctx))
+
+	result := b.String()
+
+	require.Contains(t, result, "Color: Amber")
+
+}
+
+func TestParamsKeysToLowerInBlockTemplate(t *testing.T) {
+	t.Parallel()
+
+	var (
+		ctx = map[string]interface{}{
+			"Params": map[string]interface{}{
+				"lower": "P1L",
+			},
+		}
+
+		master = `
+P1: {{ .Params.LOWER }}
+{{ block "main" . }}DEFAULT{{ end }}`
+		overlay = `
+{{ define "main" }}
+P2: {{ .Params.LOWER }}
+{{ end }}`
+	)
+
+	masterTpl, err := template.New("foo").Parse(master)
+	require.NoError(t, err)
+
+	overlayTpl, err := template.Must(masterTpl.Clone()).Parse(overlay)
+	require.NoError(t, err)
+	overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
+
+	c := newTemplateContext(overlayTpl)
+
+	c.paramsKeysToLower(overlayTpl.Tree.Root)
+
+	var b bytes.Buffer
+
+	require.NoError(t, overlayTpl.Execute(&b, ctx))
+
+	result := b.String()
+
+	require.Contains(t, result, "P1: P1L")
+	require.Contains(t, result, "P2: P1L")
+}
--- /dev/null
+++ b/tpl/tplimpl/template_embedded.go
@@ -1,0 +1,266 @@
+// Copyright 2015 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 tplimpl
+
+type Tmpl struct {
+	Name string
+	Data string
+}
+
+func (t *GoHTMLTemplate) EmbedShortcodes() {
+	t.AddInternalShortcode("ref.html", `{{ .Get 0 | ref .Page }}`)
+	t.AddInternalShortcode("relref.html", `{{ .Get 0 | relref .Page }}`)
+	t.AddInternalShortcode("highlight.html", `{{ if len .Params | eq 2 }}{{ highlight .Inner (.Get 0) (.Get 1) }}{{ else }}{{ highlight .Inner (.Get 0) "" }}{{ end }}`)
+	t.AddInternalShortcode("test.html", `This is a simple Test`)
+	t.AddInternalShortcode("figure.html", `<!-- image -->
+<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
+    {{ with .Get "link"}}<a href="{{.}}">{{ end }}
+        <img src="{{ .Get "src" }}" {{ if or (.Get "alt") (.Get "caption") }}alt="{{ with .Get "alt"}}{{.}}{{else}}{{ .Get "caption" }}{{ end }}" {{ end }}{{ with .Get "width" }}width="{{.}}" {{ end }}/>
+    {{ if .Get "link"}}</a>{{ end }}
+    {{ if or (or (.Get "title") (.Get "caption")) (.Get "attr")}}
+    <figcaption>{{ if isset .Params "title" }}
+        <h4>{{ .Get "title" }}</h4>{{ end }}
+        {{ if or (.Get "caption") (.Get "attr")}}<p>
+        {{ .Get "caption" }}
+        {{ with .Get "attrlink"}}<a href="{{.}}"> {{ end }}
+            {{ .Get "attr" }}
+        {{ if .Get "attrlink"}}</a> {{ end }}
+        </p> {{ end }}
+    </figcaption>
+    {{ end }}
+</figure>
+<!-- image -->`)
+	t.AddInternalShortcode("speakerdeck.html", "<script async class='speakerdeck-embed' data-id='{{ index .Params 0 }}' data-ratio='1.33333333333333' src='//speakerdeck.com/assets/embed.js'></script>")
+	t.AddInternalShortcode("youtube.html", `{{ if .IsNamedParams }}
+<div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
+  <iframe src="//www.youtube.com/embed/{{ .Get "id" }}?{{ with .Get "autoplay" }}{{ if eq . "true" }}autoplay=1{{ end }}{{ end }}" 
+  {{ if not (.Get "class") }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0"></iframe>
+</div>{{ else }}
+<div {{ if len .Params | eq 2 }}class="{{ .Get 1 }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
+  <iframe src="//www.youtube.com/embed/{{ .Get 0 }}" {{ if len .Params | eq 1 }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0"></iframe>
+ </div>
+{{ end }}`)
+	t.AddInternalShortcode("vimeo.html", `{{ if .IsNamedParams }}<div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
+  <iframe src="//player.vimeo.com/video/{{ .Get "id" }}" {{ if not (.Get "class") }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
+ </div>{{ else }}
+<div {{ if len .Params | eq 2 }}class="{{ .Get 1 }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
+  <iframe src="//player.vimeo.com/video/{{ .Get 0 }}" {{ if len .Params | eq 1 }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
+ </div>
+{{ end }}`)
+	t.AddInternalShortcode("gist.html", `<script src="//gist.github.com/{{ index .Params 0 }}/{{ index .Params 1 }}.js{{if len .Params | eq 3 }}?file={{ index .Params 2 }}{{end}}"></script>`)
+	t.AddInternalShortcode("tweet.html", `{{ (getJSON "https://api.twitter.com/1/statuses/oembed.json?id=" (index .Params 0)).html | safeHTML }}`)
+	t.AddInternalShortcode("instagram.html", `{{ if len .Params | eq 2 }}{{ if eq (.Get 1) "hidecaption" }}{{ (getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=1").html | safeHTML }}{{ end }}{{ else }}{{ (getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=0").html | safeHTML }}{{ end }}`)
+}
+
+func (t *GoHTMLTemplate) EmbedTemplates() {
+
+	t.AddInternalTemplate("_default", "rss.xml", `<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
+  <channel>
+    <title>{{ if eq  .Title  .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
+    <link>{{ .Permalink }}</link>
+    <description>Recent content {{ if ne  .Title  .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
+    <generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
+    <language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
+    <managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
+    <webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
+    <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
+    <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
+    <atom:link href="{{.Permalink}}" rel="self" type="application/rss+xml" />
+    {{ range first 15 .Data.Pages }}
+    <item>
+      <title>{{ .Title }}</title>
+      <link>{{ .Permalink }}</link>
+      <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
+      {{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
+      <guid>{{ .Permalink }}</guid>
+      <description>{{ .Content | html }}</description>
+    </item>
+    {{ end }}
+  </channel>
+</rss>`)
+
+	t.AddInternalTemplate("_default", "sitemap.xml", `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+  {{ range .Data.Pages }}
+  <url>
+    <loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
+    <lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
+    <changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
+    <priority>{{ .Sitemap.Priority }}</priority>{{ end }}
+  </url>
+  {{ end }}
+</urlset>`)
+
+	// For multilanguage sites
+	t.AddInternalTemplate("_default", "sitemapindex.xml", `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+	{{ range . }}
+	<sitemap>
+	   	<loc>{{ .SitemapAbsURL }}</loc>
+		{{ if not .LastChange.IsZero }}
+	   	<lastmod>{{ .LastChange.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</lastmod>
+		{{ end }}
+	</sitemap>
+	{{ end }}
+</sitemapindex>
+`)
+
+	t.AddInternalTemplate("", "pagination.html", `{{ $pag := $.Paginator }}
+    {{ if gt $pag.TotalPages 1 }}
+    <ul class="pagination">
+        {{ with $pag.First }}
+        <li>
+            <a href="{{ .URL }}" aria-label="First"><span aria-hidden="true">&laquo;&laquo;</span></a>
+        </li>
+        {{ end }}
+        <li
+        {{ if not $pag.HasPrev }}class="disabled"{{ end }}>
+        <a href="{{ if $pag.HasPrev }}{{ $pag.Prev.URL }}{{ end }}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>
+        </li>
+        {{ range $pag.Pagers }}
+        <li
+        {{ if eq . $pag }}class="active"{{ end }}><a href="{{ .URL }}">{{ .PageNumber }}</a></li>
+        {{ end }}
+        <li
+        {{ if not $pag.HasNext }}class="disabled"{{ end }}>
+        <a href="{{ if $pag.HasNext }}{{ $pag.Next.URL }}{{ end }}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>
+        </li>
+        {{ with $pag.Last }}
+        <li>
+            <a href="{{ .URL }}" aria-label="Last"><span aria-hidden="true">&raquo;&raquo;</span></a>
+        </li>
+        {{ end }}
+    </ul>
+    {{ end }}`)
+
+	t.AddInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}<div id="disqus_thread"></div>
+<script type="text/javascript">
+    var disqus_shortname = '{{ .Site.DisqusShortname }}';
+    var disqus_identifier = '{{with .GetParam "disqus_identifier" }}{{ . }}{{ else }}{{ .Permalink }}{{end}}';
+    var disqus_title = '{{with .GetParam "disqus_title" }}{{ . }}{{ else }}{{ .Title }}{{end}}';
+    var disqus_url = '{{with .GetParam "disqus_url" }}{{ . | html  }}{{ else }}{{ .Permalink }}{{end}}';
+
+    (function() {
+        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
+        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
+        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
+    })();
+</script>
+<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
+<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>{{end}}`)
+
+	// Add SEO & Social metadata
+	t.AddInternalTemplate("", "opengraph.html", `<meta property="og:title" content="{{ .Title }}" />
+<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}" />
+<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
+<meta property="og:url" content="{{ .Permalink }}" />
+{{ with .Params.images }}{{ range first 6 . }}
+  <meta property="og:image" content="{{ . | absURL }}" />
+{{ end }}{{ end }}
+
+{{ if .IsPage }}
+{{ if not .PublishDate.IsZero }}<meta property="article:published_time" content="{{ .PublishDate.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/>
+{{ else if not .Date.IsZero }}<meta property="article:published_time" content="{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/>{{ end }}
+{{ if not .Lastmod.IsZero }}<meta property="article:modified_time" content="{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/>{{ end }}
+{{ else }}
+{{ if not .Date.IsZero }}<meta property="article:modified_time" content="{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/>{{ end }}
+{{ end }}{{ with .Params.audio }}
+<meta property="og:audio" content="{{ . }}" />{{ end }}{{ with .Params.locale }}
+<meta property="og:locale" content="{{ . }}" />{{ end }}{{ with .Site.Params.title }}
+<meta property="og:site_name" content="{{ . }}" />{{ end }}{{ with .Params.videos }}
+{{ range .Params.videos }}
+  <meta property="og:video" content="{{ . | absURL }}" />
+{{ end }}{{ end }}
+
+<!-- If it is part of a series, link to related articles -->
+{{ $permalink := .Permalink }}
+{{ $siteSeries := .Site.Taxonomies.series }}{{ with .Params.series }}
+{{ range $name := . }}
+  {{ $series := index $siteSeries $name }}
+  {{ range $page := first 6 $series.Pages }}
+    {{ if ne $page.Permalink $permalink }}<meta property="og:see_also" content="{{ $page.Permalink }}" />{{ end }}
+  {{ end }}
+{{ end }}{{ end }}
+
+{{ if .IsPage }}
+{{ range .Site.Authors }}{{ with .Social.facebook }}
+<meta property="article:author" content="https://www.facebook.com/{{ . }}" />{{ end }}{{ with .Site.Social.facebook }}
+<meta property="article:publisher" content="https://www.facebook.com/{{ . }}" />{{ end }}
+<meta property="article:section" content="{{ .Section }}" />
+{{ with .Params.tags }}{{ range first 6 . }}
+  <meta property="article:tag" content="{{ . }}" />{{ end }}{{ end }}
+{{ end }}{{ end }}
+
+<!-- Facebook Page Admin ID for Domain Insights -->
+{{ with .Site.Social.facebook_admin }}<meta property="fb:admins" content="{{ . }}" />{{ end }}`)
+
+	t.AddInternalTemplate("", "twitter_cards.html", `{{ if .IsPage }}
+{{ with .Params.images }}
+<!-- Twitter summary card with large image must be at least 280x150px -->
+  <meta name="twitter:card" content="summary_large_image"/>
+  <meta name="twitter:image:src" content="{{ index . 0 | absURL }}"/>
+{{ else }}
+  <meta name="twitter:card" content="summary"/>
+{{ end }}
+
+<!-- Twitter Card data -->
+<meta name="twitter:title" content="{{ .Title }}"/>
+<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}"/>
+{{ with .Site.Social.twitter }}<meta name="twitter:site" content="@{{ . }}"/>{{ end }}
+{{ with .Site.Social.twitter_domain }}<meta name="twitter:domain" content="{{ . }}"/>{{ end }}
+{{ range .Site.Authors }}
+  {{ with .twitter }}<meta name="twitter:creator" content="@{{ . }}"/>{{ end }}
+{{ end }}{{ end }}`)
+
+	t.AddInternalTemplate("", "google_news.html", `{{ if .IsPage }}{{ with .Params.news_keywords }}
+  <meta name="news_keywords" content="{{ range $i, $kw := first 10 . }}{{ if $i }},{{ end }}{{ $kw }}{{ end }}" />
+{{ end }}{{ end }}`)
+
+	t.AddInternalTemplate("", "schema.html", `{{ with .Site.Social.GooglePlus }}<link rel="publisher" href="{{ . }}"/>{{ end }}
+<meta itemprop="name" content="{{ .Title }}">
+<meta itemprop="description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}">
+
+{{if .IsPage}}{{ $ISO8601 := "2006-01-02T15:04:05-07:00" }}{{ if not .PublishDate.IsZero }}
+<meta itemprop="datePublished" content="{{ .PublishDate.Format $ISO8601 | safeHTML }}" />{{ end }}
+{{ if not .Date.IsZero }}<meta itemprop="dateModified" content="{{ .Date.Format $ISO8601 | safeHTML }}" />{{ end }}
+<meta itemprop="wordCount" content="{{ .WordCount }}">
+{{ with .Params.images }}{{ range first 6 . }}
+  <meta itemprop="image" content="{{ . | absURL }}">
+{{ end }}{{ end }}
+
+<!-- Output all taxonomies as schema.org keywords -->
+<meta itemprop="keywords" content="{{ range $plural, $terms := .Site.Taxonomies }}{{ range $term, $val := $terms }}{{ printf "%s," $term }}{{ end }}{{ end }}" />
+{{ end }}`)
+
+	t.AddInternalTemplate("", "google_analytics.html", `{{ with .Site.GoogleAnalytics }}
+<script>
+(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+
+ga('create', '{{ . }}', 'auto');
+ga('send', 'pageview');
+</script>
+{{ end }}`)
+
+	t.AddInternalTemplate("", "google_analytics_async.html", `{{ with .Site.GoogleAnalytics }}
+<script>
+window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
+ga('create', '{{ . }}', 'auto');
+ga('send', 'pageview');
+</script>
+<script async src='//www.google-analytics.com/analytics.js'></script>
+{{ end }}`)
+
+	t.AddInternalTemplate("_default", "robots.txt", "User-agent: *")
+}
--- /dev/null
+++ b/tpl/tplimpl/template_func_truncate.go
@@ -1,0 +1,156 @@
+// Copyright 2016 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 tplimpl
+
+import (
+	"errors"
+	"html"
+	"html/template"
+	"regexp"
+	"unicode"
+	"unicode/utf8"
+
+	"github.com/spf13/cast"
+)
+
+var (
+	tagRE        = regexp.MustCompile(`^<(/)?([^ ]+?)(?:(\s*/)| .*?)?>`)
+	htmlSinglets = map[string]bool{
+		"br": true, "col": true, "link": true,
+		"base": true, "img": true, "param": true,
+		"area": true, "hr": true, "input": true,
+	}
+)
+
+type htmlTag struct {
+	name    string
+	pos     int
+	openTag bool
+}
+
+func truncate(a interface{}, options ...interface{}) (template.HTML, error) {
+	length, err := cast.ToIntE(a)
+	if err != nil {
+		return "", err
+	}
+	var textParam interface{}
+	var ellipsis string
+
+	switch len(options) {
+	case 0:
+		return "", errors.New("truncate requires a length and a string")
+	case 1:
+		textParam = options[0]
+		ellipsis = " …"
+	case 2:
+		textParam = options[1]
+		ellipsis, err = cast.ToStringE(options[0])
+		if err != nil {
+			return "", errors.New("ellipsis must be a string")
+		}
+		if _, ok := options[0].(template.HTML); !ok {
+			ellipsis = html.EscapeString(ellipsis)
+		}
+	default:
+		return "", errors.New("too many arguments passed to truncate")
+	}
+	if err != nil {
+		return "", errors.New("text to truncate must be a string")
+	}
+	text, err := cast.ToStringE(textParam)
+	if err != nil {
+		return "", errors.New("text must be a string")
+	}
+
+	_, isHTML := textParam.(template.HTML)
+
+	if utf8.RuneCountInString(text) <= length {
+		if isHTML {
+			return template.HTML(text), nil
+		}
+		return template.HTML(html.EscapeString(text)), nil
+	}
+
+	tags := []htmlTag{}
+	var lastWordIndex, lastNonSpace, currentLen, endTextPos, nextTag int
+
+	for i, r := range text {
+		if i < nextTag {
+			continue
+		}
+
+		if isHTML {
+			// Make sure we keep tag of HTML tags
+			slice := text[i:]
+			m := tagRE.FindStringSubmatchIndex(slice)
+			if len(m) > 0 && m[0] == 0 {
+				nextTag = i + m[1]
+				tagname := slice[m[4]:m[5]]
+				lastWordIndex = lastNonSpace
+				_, singlet := htmlSinglets[tagname]
+				if !singlet && m[6] == -1 {
+					tags = append(tags, htmlTag{name: tagname, pos: i, openTag: m[2] == -1})
+				}
+
+				continue
+			}
+		}
+
+		currentLen++
+		if unicode.IsSpace(r) {
+			lastWordIndex = lastNonSpace
+		} else if unicode.In(r, unicode.Han, unicode.Hangul, unicode.Hiragana, unicode.Katakana) {
+			lastWordIndex = i
+		} else {
+			lastNonSpace = i + utf8.RuneLen(r)
+		}
+
+		if currentLen > length {
+			if lastWordIndex == 0 {
+				endTextPos = i
+			} else {
+				endTextPos = lastWordIndex
+			}
+			out := text[0:endTextPos]
+			if isHTML {
+				out += ellipsis
+				// Close out any open HTML tags
+				var currentTag *htmlTag
+				for i := len(tags) - 1; i >= 0; i-- {
+					tag := tags[i]
+					if tag.pos >= endTextPos || currentTag != nil {
+						if currentTag != nil && currentTag.name == tag.name {
+							currentTag = nil
+						}
+						continue
+					}
+
+					if tag.openTag {
+						out += ("</" + tag.name + ">")
+					} else {
+						currentTag = &tag
+					}
+				}
+
+				return template.HTML(out), nil
+			}
+			return template.HTML(html.EscapeString(out) + ellipsis), nil
+		}
+	}
+
+	if isHTML {
+		return template.HTML(text), nil
+	}
+	return template.HTML(html.EscapeString(text)), nil
+}
--- /dev/null
+++ b/tpl/tplimpl/template_func_truncate_test.go
@@ -1,0 +1,83 @@
+// Copyright 2016 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 tplimpl
+
+import (
+	"html/template"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+func TestTruncate(t *testing.T) {
+	t.Parallel()
+	var err error
+	cases := []struct {
+		v1    interface{}
+		v2    interface{}
+		v3    interface{}
+		want  interface{}
+		isErr bool
+	}{
+		{10, "I am a test sentence", nil, template.HTML("I am a …"), false},
+		{10, "", "I am a test sentence", template.HTML("I am a"), false},
+		{10, "", "a b c d e f g h i j k", template.HTML("a b c d e"), false},
+		{12, "", "<b>Should be escaped</b>", template.HTML("&lt;b&gt;Should be"), false},
+		{10, template.HTML(" <a href='#'>Read more</a>"), "I am a test sentence", template.HTML("I am a <a href='#'>Read more</a>"), false},
+		{20, template.HTML("I have a <a href='/markdown'>Markdown link</a> inside."), nil, template.HTML("I have a <a href='/markdown'>Markdown …</a>"), false},
+		{10, "IamanextremelylongwordthatjustgoesonandonandonjusttoannoyyoualmostasifIwaswritteninGermanActuallyIbettheresagermanwordforthis", nil, template.HTML("Iamanextre …"), false},
+		{10, template.HTML("<p>IamanextremelylongwordthatjustgoesonandonandonjusttoannoyyoualmostasifIwaswritteninGermanActuallyIbettheresagermanwordforthis</p>"), nil, template.HTML("<p>Iamanextre …</p>"), false},
+		{13, template.HTML("With <a href=\"/markdown\">Markdown</a> inside."), nil, template.HTML("With <a href=\"/markdown\">Markdown …</a>"), false},
+		{14, "Hello中国 Good 好的", nil, template.HTML("Hello中国 Good 好 …"), false},
+		{15, "", template.HTML("A <br> tag that's not closed"), template.HTML("A <br> tag that's"), false},
+		{14, template.HTML("<p>Hello中国 Good 好的</p>"), nil, template.HTML("<p>Hello中国 Good 好 …</p>"), false},
+		{2, template.HTML("<p>P1</p><p>P2</p>"), nil, template.HTML("<p>P1 …</p>"), false},
+		{3, template.HTML(strings.Repeat("<p>P</p>", 20)), nil, template.HTML("<p>P</p><p>P</p><p>P …</p>"), false},
+		{18, template.HTML("<p>test <b>hello</b> test something</p>"), nil, template.HTML("<p>test <b>hello</b> test …</p>"), false},
+		{4, template.HTML("<p>a<b><i>b</b>c d e</p>"), nil, template.HTML("<p>a<b><i>b</b>c …</p>"), false},
+		{10, nil, nil, template.HTML(""), true},
+		{nil, nil, nil, template.HTML(""), true},
+	}
+	for i, c := range cases {
+		var result template.HTML
+		if c.v2 == nil {
+			result, err = truncate(c.v1)
+		} else if c.v3 == nil {
+			result, err = truncate(c.v1, c.v2)
+		} else {
+			result, err = truncate(c.v1, c.v2, c.v3)
+		}
+
+		if c.isErr {
+			if err == nil {
+				t.Errorf("[%d] Slice didn't return an expected error", i)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("[%d] failed: %s", i, err)
+				continue
+			}
+			if !reflect.DeepEqual(result, c.want) {
+				t.Errorf("[%d] got '%s' but expected '%s'", i, result, c.want)
+			}
+		}
+	}
+
+	// Too many arguments
+	_, err = truncate(10, " ...", "I am a test sentence", "wrong")
+	if err == nil {
+		t.Errorf("Should have errored")
+	}
+
+}
--- /dev/null
+++ b/tpl/tplimpl/template_funcs.go
@@ -1,0 +1,2217 @@
+// 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/bep/inflect"
+	"github.com/spf13/afero"
+	"github.com/spf13/cast"
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/helpers"
+	jww "github.com/spf13/jwalterweatherman"
+
+	// Importing image codecs for image.DecodeConfig
+	_ "image/gif"
+	_ "image/jpeg"
+	_ "image/png"
+)
+
+// Some of the template funcs are'nt entirely stateless.
+type templateFuncster struct {
+	funcMap        template.FuncMap
+	cachedPartials partialCache
+	*deps.Deps
+}
+
+func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
+	return &templateFuncster{
+		Deps:           deps,
+		cachedPartials: partialCache{p: make(map[string]template.HTML)},
+	}
+}
+
+// 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())
+	}
+}
+
+// ResetCaches resets all caches that might be used during build.
+// TODO(bep) globals move image config cache to funcster
+func ResetCaches() {
+	resetImageConfigCache()
+}
+
+// imageConfigCache is a lockable cache for image.Config objects. It must be
+// locked before reading or writing to config.
+type imageConfigCache struct {
+	config map[string]image.Config
+	sync.RWMutex
+}
+
+var defaultImageConfigCache = imageConfigCache{
+	config: map[string]image.Config{},
+}
+
+// resetImageConfigCache initializes and resets the imageConfig cache for the
+// imageConfig template function. This should be run once before every batch of
+// template renderers so the cache is cleared for new data.
+func resetImageConfigCache() {
+	defaultImageConfigCache.Lock()
+	defer defaultImageConfigCache.Unlock()
+
+	defaultImageConfigCache.config = map[string]image.Config{}
+}
+
+// imageConfig returns the image.Config for the specified path relative to the
+// working directory. resetImageConfigCache must be run beforehand.
+func (t *templateFuncster) imageConfig(path interface{}) (image.Config, error) {
+	filename, err := cast.ToStringE(path)
+	if err != nil {
+		return image.Config{}, err
+	}
+
+	if filename == "" {
+		return image.Config{}, errors.New("imageConfig needs a filename")
+	}
+
+	// Check cache for image config.
+	defaultImageConfigCache.RLock()
+	config, ok := defaultImageConfigCache.config[filename]
+	defaultImageConfigCache.RUnlock()
+
+	if ok {
+		return config, nil
+	}
+
+	f, err := t.Fs.WorkingDir.Open(filename)
+	if err != nil {
+		return image.Config{}, err
+	}
+
+	config, _, err = image.DecodeConfig(f)
+
+	defaultImageConfigCache.Lock()
+	defaultImageConfigCache.config[filename] = config
+	defaultImageConfigCache.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"})
+	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
+}
+
+func refPage(page interface{}, ref, methodName string) template.HTML {
+	value := reflect.ValueOf(page)
+
+	method := value.MethodByName(methodName)
+
+	if method.IsValid() && method.Type().NumIn() == 1 && method.Type().NumOut() == 2 {
+		result := method.Call([]reflect.Value{reflect.ValueOf(ref)})
+
+		url, err := result[0], result[1]
+
+		if !err.IsNil() {
+			jww.ERROR.Printf("%s", err.Interface())
+			return template.HTML(fmt.Sprintf("%s", err.Interface()))
+		}
+
+		if url.String() == "" {
+			jww.ERROR.Printf("ref %s could not be found\n", ref)
+			return template.HTML(ref)
+		}
+
+		return template.HTML(url.String())
+	}
+
+	jww.ERROR.Printf("Can only create references from Page and Node objects.")
+	return template.HTML(ref)
+}
+
+// ref returns the absolute URL path to a given content item.
+func ref(page interface{}, ref string) template.HTML {
+	return refPage(page, ref, "Ref")
+}
+
+// relRef returns the relative URL path to a given content item.
+func relRef(page interface{}, ref string) template.HTML {
+	return refPage(page, ref, "RelRef")
+}
+
+// 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]template.HTML
+}
+
+// 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 template.HTML) {
+	var ok bool
+
+	t.cachedPartials.RLock()
+	p, ok = t.cachedPartials.p[key]
+	t.cachedPartials.RUnlock()
+
+	if ok {
+		return p
+	}
+
+	t.cachedPartials.Lock()
+	if p, ok = t.cachedPartials.p[key]; !ok {
+		t.cachedPartials.Unlock()
+		p = t.Tmpl.Partial(name, context)
+
+		t.cachedPartials.Lock()
+		t.cachedPartials.p[key] = p
+
+	}
+	t.cachedPartials.Unlock()
+
+	return p
+}
+
+// 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) template.HTML {
+	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.imageConfig,
+		"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.Tmpl.Partial,
+		"partialCached": t.partialCached,
+		"plainify":      plainify,
+		"pluralize":     pluralize,
+		"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,
+		"upper":        upper,
+		"urlize":       t.PathSpec.URLize,
+		"where":        where,
+		"i18n":         t.Translate,
+		"T":            t.Translate,
+	}
+
+	t.funcMap = funcMap
+	t.Tmpl.Funcs(funcMap)
+}
--- /dev/null
+++ b/tpl/tplimpl/template_funcs_test.go
@@ -1,0 +1,2993 @@
+// Copyright 2016 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 tplimpl
+
+import (
+	"bytes"
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"html/template"
+	"image"
+	"image/color"
+	"image/png"
+	"math/rand"
+	"path"
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/spf13/hugo/tpl"
+
+	"github.com/spf13/hugo/deps"
+	"github.com/spf13/hugo/helpers"
+
+	"io/ioutil"
+	"log"
+	"os"
+
+	"github.com/spf13/afero"
+	"github.com/spf13/cast"
+	"github.com/spf13/hugo/config"
+	"github.com/spf13/hugo/hugofs"
+	"github.com/spf13/hugo/i18n"
+	jww "github.com/spf13/jwalterweatherman"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+var (
+	logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+)
+
+func newDepsConfig(cfg config.Provider) deps.DepsCfg {
+	l := helpers.NewLanguage("en", cfg)
+	l.Set("i18nDir", "i18n")
+	return deps.DepsCfg{
+		Language:            l,
+		Cfg:                 cfg,
+		Fs:                  hugofs.NewMem(l),
+		Logger:              logger,
+		TemplateProvider:    DefaultTemplateProvider,
+		TranslationProvider: i18n.NewTranslationProvider(),
+	}
+}
+
+type tstNoStringer struct {
+}
+
+type tstCompareType int
+
+const (
+	tstEq tstCompareType = iota
+	tstNe
+	tstGt
+	tstGe
+	tstLt
+	tstLe
+)
+
+func tstIsEq(tp tstCompareType) bool {
+	return tp == tstEq || tp == tstGe || tp == tstLe
+}
+
+func tstIsGt(tp tstCompareType) bool {
+	return tp == tstGt || tp == tstGe
+}
+
+func tstIsLt(tp tstCompareType) bool {
+	return tp == tstLt || tp == tstLe
+}
+
+func TestFuncsInTemplate(t *testing.T) {
+	t.Parallel()
+
+	workingDir := "/home/hugo"
+
+	v := viper.New()
+
+	v.Set("workingDir", workingDir)
+	v.Set("multilingual", true)
+
+	fs := hugofs.NewMem(v)
+
+	afero.WriteFile(fs.Source, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755)
+
+	// Add the examples from the docs: As a smoke test and to make sure the examples work.
+	// TODO(bep): docs: fix title example
+	in :=
+		`absLangURL: {{ "index.html" | absLangURL }}
+absURL: {{ "http://gohugo.io/" | absURL }}
+absURL: {{ "mystyle.css" | absURL }}
+absURL: {{ 42 | absURL }}
+add: {{add 1 2}}
+base64Decode 1: {{ "SGVsbG8gd29ybGQ=" | base64Decode }}
+base64Decode 2: {{ 42 | base64Encode | base64Decode }}
+base64Encode: {{ "Hello world" | base64Encode }}
+chomp: {{chomp "<p>Blockhead</p>\n" }}
+dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }}
+delimit: {{ delimit (slice "A" "B" "C") ", " " and " }}
+div: {{div 6 3}}
+echoParam: {{ echoParam .Params "langCode" }}
+emojify: {{ "I :heart: Hugo" | emojify }}
+eq: {{ if eq .Section "blog" }}current{{ end }}
+findRE: {{ findRE "[G|g]o" "Hugo is a static side generator written in Go." "1" }}
+hasPrefix 1: {{ hasPrefix "Hugo" "Hu" }}
+hasPrefix 2: {{ hasPrefix "Hugo" "Fu" }}
+htmlEscape 1: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | safeHTML}}
+htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>"}}
+htmlUnescape 1: {{htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | safeHTML}}
+htmlUnescape 2: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape | safeHTML}}
+htmlUnescape 3: {{"Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;" | htmlUnescape | htmlUnescape }}
+htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | htmlUnescape | safeHTML }}
+htmlUnescape 5: {{ htmlUnescape "Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | htmlEscape | safeHTML }}
+humanize 1: {{ humanize "my-first-post" }}
+humanize 2: {{ humanize "myCamelPost" }}
+humanize 3: {{ humanize "52" }}
+humanize 4: {{ humanize 103 }}
+in: {{ if in "this string contains a substring" "substring" }}Substring found!{{ end }}
+jsonify: {{ (slice "A" "B" "C") | jsonify }}
+lower: {{lower "BatMan"}}
+markdownify: {{ .Title | markdownify}}
+md5: {{ md5 "Hello world, gophers!" }}
+mod: {{mod 15 3}}
+modBool: {{modBool 15 3}}
+mul: {{mul 2 3}}
+plainify: {{ plainify  "Hello <strong>world</strong>, gophers!" }}
+pluralize: {{ "cat" | pluralize }}
+querify 1: {{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}
+querify 2: <a href="https://www.google.com?{{ (querify "q" "test" "page" 3) | safeURL }}">Search</a>
+readDir: {{ range (readDir ".") }}{{ .Name }}{{ end }}
+readFile: {{ readFile "README.txt" }}
+relLangURL: {{ "index.html" | relLangURL }}
+relURL 1: {{ "http://gohugo.io/" | relURL }}
+relURL 2: {{ "mystyle.css" | relURL }}
+relURL 3: {{ mul 2 21 | relURL }}
+replace: {{ replace "Batman and Robin" "Robin" "Catwoman" }}
+replaceRE: {{ "http://gohugo.io/docs" | replaceRE "^https?://([^/]+).*" "$1" }}
+safeCSS: {{ "Bat&Man" | safeCSS | safeCSS }}
+safeHTML: {{ "Bat&Man" | safeHTML | safeHTML }}
+safeHTML: {{ "Bat&Man" | safeHTML }}
+safeJS: {{ "(1*2)" | safeJS | safeJS }}
+safeURL: {{ "http://gohugo.io" | safeURL | safeURL }}
+seq: {{ seq 3 }}
+sha1: {{ sha1 "Hello world, gophers!" }}
+sha256: {{ sha256 "Hello world, gophers!" }}
+singularize: {{ "cats" | singularize }}
+slicestr: {{slicestr "BatMan" 0 3}}
+slicestr: {{slicestr "BatMan" 3}}
+sort: {{ slice "B" "C" "A" | sort }}
+sub: {{sub 3 2}}
+substr: {{substr "BatMan" 0 -3}}
+substr: {{substr "BatMan" 3 3}}
+title: {{title "Bat man"}}
+time: {{ (time "2015-01-21").Year }}
+trim: {{ trim "++Batman--" "+-" }}
+truncate: {{ "this is a very long text" | truncate 10 " ..." }}
+truncate: {{ "With [Markdown](/markdown) inside." | markdownify | truncate 14 }}
+upper: {{upper "BatMan"}}
+urlize: {{ "Bat Man" | urlize }}
+`
+
+	expected := `absLangURL: http://mysite.com/hugo/en/index.html
+absURL: http://gohugo.io/
+absURL: http://mysite.com/hugo/mystyle.css
+absURL: http://mysite.com/hugo/42
+add: 3
+base64Decode 1: Hello world
+base64Decode 2: 42
+base64Encode: SGVsbG8gd29ybGQ=
+chomp: <p>Blockhead</p>
+dateFormat: Wednesday, Jan 21, 2015
+delimit: A, B and C
+div: 2
+echoParam: en
+emojify: I ❤️ Hugo
+eq: current
+findRE: [go]
+hasPrefix 1: true
+hasPrefix 2: false
+htmlEscape 1: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
+htmlEscape 2: Cathal Garvey &amp;amp; The Sunshine Band &amp;lt;[email protected]&amp;gt;
+htmlUnescape 1: Cathal Garvey & The Sunshine Band <[email protected]>
+htmlUnescape 2: Cathal Garvey & The Sunshine Band <[email protected]>
+htmlUnescape 3: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
+htmlUnescape 4: Cathal Garvey & The Sunshine Band <[email protected]>
+htmlUnescape 5: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
+humanize 1: My first post
+humanize 2: My camel post
+humanize 3: 52nd
+humanize 4: 103rd
+in: Substring found!
+jsonify: ["A","B","C"]
+lower: batman
+markdownify: <strong>BatMan</strong>
+md5: b3029f756f98f79e7f1b7f1d1f0dd53b
+mod: 0
+modBool: true
+mul: 6
+plainify: Hello world, gophers!
+pluralize: cats
+querify 1: bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose
+querify 2: <a href="https://www.google.com?page=3&amp;q=test">Search</a>
+readDir: README.txt
+readFile: Hugo Rocks!
+relLangURL: /hugo/en/index.html
+relURL 1: http://gohugo.io/
+relURL 2: /hugo/mystyle.css
+relURL 3: /hugo/42
+replace: Batman and Catwoman
+replaceRE: gohugo.io
+safeCSS: Bat&amp;Man
+safeHTML: Bat&Man
+safeHTML: Bat&Man
+safeJS: (1*2)
+safeURL: http://gohugo.io
+seq: [1 2 3]
+sha1: c8b5b0e33d408246e30f53e32b8f7627a7a649d4
+sha256: 6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46
+singularize: cat
+slicestr: Bat
+slicestr: Man
+sort: [A B C]
+sub: 1
+substr: Bat
+substr: Man
+title: Bat Man
+time: 2015
+trim: Batman
+truncate: this is a ...
+truncate: With <a href="/markdown">Markdown …</a>
+upper: BATMAN
+urlize: bat-man
+`
+
+	var b bytes.Buffer
+
+	var data struct {
+		Title   string
+		Section string
+		Params  map[string]interface{}
+	}
+
+	data.Title = "**BatMan**"
+	data.Section = "blog"
+	data.Params = map[string]interface{}{"langCode": "en"}
+
+	v.Set("baseURL", "http://mysite.com/hugo/")
+	v.Set("CurrentContentLanguage", helpers.NewLanguage("en", v))
+
+	config := newDepsConfig(v)
+	config.WithTemplate = func(templ tpl.Template) error {
+		if _, err := templ.New("test").Parse(in); err != nil {
+			t.Fatal("Got error on parse", err)
+		}
+		return nil
+	}
+	config.Fs = fs
+
+	d := deps.New(config)
+	if err := d.LoadResources(); err != nil {
+		t.Fatal(err)
+	}
+
+	err := d.Tmpl.Lookup("test").Execute(&b, &data)
+
+	if err != nil {
+		t.Fatal("Got error on execute", err)
+	}
+
+	if b.String() != expected {
+		sl1 := strings.Split(b.String(), "\n")
+		sl2 := strings.Split(expected, "\n")
+		t.Errorf("Diff:\n%q", helpers.DiffStringSlices(sl1, sl2))
+	}
+}
+
+func TestCompare(t *testing.T) {
+	t.Parallel()
+	for _, this := range []struct {
+		tstCompareType
+		funcUnderTest func(a, b interface{}) bool
+	}{
+		{tstGt, gt},
+		{tstLt, lt},
+		{tstGe, ge},
+		{tstLe, le},
+		{tstEq, eq},
+		{tstNe, ne},
+	} {
+		doTestCompare(t, this.tstCompareType, this.funcUnderTest)
+	}
+}
+
+func doTestCompare(t *testing.T, tp tstCompareType, funcUnderTest func(a, b interface{}) bool) {
+	for i, this := range []struct {
+		left            interface{}
+		right           interface{}
+		expectIndicator int
+	}{
+		{5, 8, -1},
+		{8, 5, 1},
+		{5, 5, 0},
+		{int(5), int64(5), 0},
+		{int32(5), int(5), 0},
+		{int16(4), int(5), -1},
+		{uint(15), uint64(15), 0},
+		{-2, 1, -1},
+		{2, -5, 1},
+		{0.0, 1.23, -1},
+		{1.1, 1.1, 0},
+		{float32(1.0), float64(1.0), 0},
+		{1.23, 0.0, 1},
+		{"5", "5", 0},
+		{"8", "5", 1},
+		{"5", "0001", 1},
+		{[]int{100, 99}, []int{1, 2, 3, 4}, -1},
+		{cast.ToTime("2015-11-20"), cast.ToTime("2015-11-20"), 0},
+		{cast.ToTime("2015-11-19"), cast.ToTime("2015-11-20"), -1},
+		{cast.ToTime("2015-11-20"), cast.ToTime("2015-11-19"), 1},
+	} {
+		result := funcUnderTest(this.left, this.right)
+		success := false
+
+		if this.expectIndicator == 0 {
+			if tstIsEq(tp) {
+				success = result
+			} else {
+				success = !result
+			}
+		}
+
+		if this.expectIndicator < 0 {
+			success = result && (tstIsLt(tp) || tp == tstNe)
+			success = success || (!result && !tstIsLt(tp))
+		}
+
+		if this.expectIndicator > 0 {
+			success = result && (tstIsGt(tp) || tp == tstNe)
+			success = success || (!result && (!tstIsGt(tp) || tp != tstNe))
+		}
+
+		if !success {
+			t.Errorf("[%d][%s] %v compared to %v: %t", i, path.Base(runtime.FuncForPC(reflect.ValueOf(funcUnderTest).Pointer()).Name()), this.left, this.right, result)
+		}
+	}
+}
+
+func TestMod(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		a      interface{}
+		b      interface{}
+		expect interface{}
+	}{
+		{3, 2, int64(1)},
+		{3, 1, int64(0)},
+		{3, 0, false},
+		{0, 3, int64(0)},
+		{3.1, 2, false},
+		{3, 2.1, false},
+		{3.1, 2.1, false},
+		{int8(3), int8(2), int64(1)},
+		{int16(3), int16(2), int64(1)},
+		{int32(3), int32(2), int64(1)},
+		{int64(3), int64(2), int64(1)},
+	} {
+		result, err := mod(this.a, this.b)
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] modulo 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] modulo got %v but expected %v", i, result, this.expect)
+			}
+		}
+	}
+}
+
+func TestModBool(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		a      interface{}
+		b      interface{}
+		expect interface{}
+	}{
+		{3, 3, true},
+		{3, 2, false},
+		{3, 1, true},
+		{3, 0, nil},
+		{0, 3, true},
+		{3.1, 2, nil},
+		{3, 2.1, nil},
+		{3.1, 2.1, nil},
+		{int8(3), int8(3), true},
+		{int8(3), int8(2), false},
+		{int16(3), int16(3), true},
+		{int16(3), int16(2), false},
+		{int32(3), int32(3), true},
+		{int32(3), int32(2), false},
+		{int64(3), int64(3), true},
+		{int64(3), int64(2), false},
+	} {
+		result, err := modBool(this.a, this.b)
+		if this.expect == nil {
+			if err == nil {
+				t.Errorf("[%d] modulo 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] modulo got %v but expected %v", i, result, this.expect)
+			}
+		}
+	}
+}
+
+func TestFirst(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		count    interface{}
+		sequence interface{}
+		expect   interface{}
+	}{
+		{int(2), []string{"a", "b", "c"}, []string{"a", "b"}},
+		{int32(3), []string{"a", "b"}, []string{"a", "b"}},
+		{int64(2), []int{100, 200, 300}, []int{100, 200}},
+		{100, []int{100, 200}, []int{100, 200}},
+		{"1", []int{100, 200, 300}, []int{100}},
+		{int64(-1), []int{100, 200, 300}, false},
+		{"noint", []int{100, 200, 300}, false},
+		{1, nil, false},
+		{nil, []int{100}, false},
+		{1, t, false},
+		{1, (*string)(nil), false},
+	} {
+		results, err := first(this.count, this.sequence)
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] First didn't return an expected error", i)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("[%d] failed: %s", i, err)
+				continue
+			}
+			if !reflect.DeepEqual(results, this.expect) {
+				t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect)
+			}
+		}
+	}
+}
+
+func TestLast(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		count    interface{}
+		sequence interface{}
+		expect   interface{}
+	}{
+		{int(2), []string{"a", "b", "c"}, []string{"b", "c"}},
+		{int32(3), []string{"a", "b"}, []string{"a", "b"}},
+		{int64(2), []int{100, 200, 300}, []int{200, 300}},
+		{100, []int{100, 200}, []int{100, 200}},
+		{"1", []int{100, 200, 300}, []int{300}},
+		{int64(-1), []int{100, 200, 300}, false},
+		{"noint", []int{100, 200, 300}, false},
+		{1, nil, false},
+		{nil, []int{100}, false},
+		{1, t, false},
+		{1, (*string)(nil), false},
+	} {
+		results, err := last(this.count, this.sequence)
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] First didn't return an expected error", i)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("[%d] failed: %s", i, err)
+				continue
+			}
+			if !reflect.DeepEqual(results, this.expect) {
+				t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect)
+			}
+		}
+	}
+}
+
+func TestAfter(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		count    interface{}
+		sequence interface{}
+		expect   interface{}
+	}{
+		{int(2), []string{"a", "b", "c", "d"}, []string{"c", "d"}},
+		{int32(3), []string{"a", "b"}, false},
+		{int64(2), []int{100, 200, 300}, []int{300}},
+		{100, []int{100, 200}, false},
+		{"1", []int{100, 200, 300}, []int{200, 300}},
+		{int64(-1), []int{100, 200, 300}, false},
+		{"noint", []int{100, 200, 300}, false},
+		{1, nil, false},
+		{nil, []int{100}, false},
+		{1, t, false},
+		{1, (*string)(nil), false},
+	} {
+		results, err := after(this.count, this.sequence)
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] First didn't return an expected error", i)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("[%d] failed: %s", i, err)
+				continue
+			}
+			if !reflect.DeepEqual(results, this.expect) {
+				t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect)
+			}
+		}
+	}
+}
+
+func TestShuffleInputAndOutputFormat(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		sequence interface{}
+		success  bool
+	}{
+		{[]string{"a", "b", "c", "d"}, true},
+		{[]int{100, 200, 300}, true},
+		{[]int{100, 200, 300}, true},
+		{[]int{100, 200}, true},
+		{[]string{"a", "b"}, true},
+		{[]int{100, 200, 300}, true},
+		{[]int{100, 200, 300}, true},
+		{[]int{100}, true},
+		{nil, false},
+		{t, false},
+		{(*string)(nil), false},
+	} {
+		results, err := shuffle(this.sequence)
+		if !this.success {
+			if err == nil {
+				t.Errorf("[%d] First didn't return an expected error", i)
+			}
+		} else {
+			resultsv := reflect.ValueOf(results)
+			sequencev := reflect.ValueOf(this.sequence)
+
+			if err != nil {
+				t.Errorf("[%d] failed: %s", i, err)
+				continue
+			}
+
+			if resultsv.Len() != sequencev.Len() {
+				t.Errorf("Expected %d items, got %d items", sequencev.Len(), resultsv.Len())
+			}
+		}
+	}
+}
+
+func TestShuffleRandomising(t *testing.T) {
+	t.Parallel()
+	// Note that this test can fail with false negative result if the shuffle
+	// of the sequence happens to be the same as the original sequence. However
+	// the propability of the event is 10^-158 which is negligible.
+	sequenceLength := 100
+	rand.Seed(time.Now().UTC().UnixNano())
+
+	for _, this := range []struct {
+		sequence []int
+	}{
+		{rand.Perm(sequenceLength)},
+	} {
+		results, _ := shuffle(this.sequence)
+
+		resultsv := reflect.ValueOf(results)
+
+		allSame := true
+		for index, value := range this.sequence {
+			allSame = allSame && (resultsv.Index(index).Interface() == value)
+		}
+
+		if allSame {
+			t.Error("Expected sequence to be shuffled but was in the same order")
+		}
+	}
+}
+
+func TestDictionary(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		v1            []interface{}
+		expecterr     bool
+		expectedValue map[string]interface{}
+	}{
+		{[]interface{}{"a", "b"}, false, map[string]interface{}{"a": "b"}},
+		{[]interface{}{5, "b"}, true, nil},
+		{[]interface{}{"a", 12, "b", []int{4}}, false, map[string]interface{}{"a": 12, "b": []int{4}}},
+		{[]interface{}{"a", "b", "c"}, true, nil},
+	} {
+		r, e := dictionary(this.v1...)
+
+		if (this.expecterr && e == nil) || (!this.expecterr && e != nil) {
+			t.Errorf("[%d] got an unexpected error: %s", i, e)
+		} else if !this.expecterr {
+			if !reflect.DeepEqual(r, this.expectedValue) {
+				t.Errorf("[%d] got %v but expected %v", i, r, this.expectedValue)
+			}
+		}
+	}
+}
+
+func blankImage(width, height int) []byte {
+	var buf bytes.Buffer
+	img := image.NewRGBA(image.Rect(0, 0, width, height))
+	if err := png.Encode(&buf, img); err != nil {
+		panic(err)
+	}
+	return buf.Bytes()
+}
+
+func TestImageConfig(t *testing.T) {
+	t.Parallel()
+
+	workingDir := "/home/hugo"
+
+	v := viper.New()
+
+	v.Set("workingDir", workingDir)
+
+	f := newTestFuncsterWithViper(v)
+
+	for i, this := range []struct {
+		resetCache bool
+		path       string
+		input      []byte
+		expected   image.Config
+	}{
+		// Make sure that the cache is initialized by default.
+		{
+			resetCache: false,
+			path:       "a.png",
+			input:      blankImage(10, 10),
+			expected: image.Config{
+				Width:      10,
+				Height:     10,
+				ColorModel: color.NRGBAModel,
+			},
+		},
+		{
+			resetCache: true,
+			path:       "a.png",
+			input:      blankImage(10, 10),
+			expected: image.Config{
+				Width:      10,
+				Height:     10,
+				ColorModel: color.NRGBAModel,
+			},
+		},
+		{
+			resetCache: false,
+			path:       "b.png",
+			input:      blankImage(20, 15),
+			expected: image.Config{
+				Width:      20,
+				Height:     15,
+				ColorModel: color.NRGBAModel,
+			},
+		},
+		{
+			resetCache: false,
+			path:       "a.png",
+			input:      blankImage(20, 15),
+			expected: image.Config{
+				Width:      10,
+				Height:     10,
+				ColorModel: color.NRGBAModel,
+			},
+		},
+		{
+			resetCache: true,
+			path:       "a.png",
+			input:      blankImage(20, 15),
+			expected: image.Config{
+				Width:      20,
+				Height:     15,
+				ColorModel: color.NRGBAModel,
+			},
+		},
+	} {
+		afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, this.path), this.input, 0755)
+
+		if this.resetCache {
+			resetImageConfigCache()
+		}
+
+		result, err := f.imageConfig(this.path)
+		if err != nil {
+			t.Errorf("imageConfig returned error: %s", err)
+		}
+
+		if !reflect.DeepEqual(result, this.expected) {
+			t.Errorf("[%d] imageConfig: expected '%v', got '%v'", i, this.expected, result)
+		}
+
+		if len(defaultImageConfigCache.config) == 0 {
+			t.Error("defaultImageConfigCache should have at least 1 item")
+		}
+	}
+
+	if _, err := f.imageConfig(t); err == nil {
+		t.Error("Expected error from imageConfig when passed invalid path")
+	}
+
+	if _, err := f.imageConfig("non-existent.png"); err == nil {
+		t.Error("Expected error from imageConfig when passed non-existent file")
+	}
+
+	if _, err := f.imageConfig(""); err == nil {
+		t.Error("Expected error from imageConfig when passed empty path")
+	}
+
+	// test cache clearing
+	ResetCaches()
+
+	if len(defaultImageConfigCache.config) != 0 {
+		t.Error("ResetCaches should have cleared defaultImageConfigCache")
+	}
+}
+
+func TestIn(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		v1     interface{}
+		v2     interface{}
+		expect bool
+	}{
+		{[]string{"a", "b", "c"}, "b", true},
+		{[]interface{}{"a", "b", "c"}, "b", true},
+		{[]interface{}{"a", "b", "c"}, "d", false},
+		{[]string{"a", "b", "c"}, "d", false},
+		{[]string{"a", "12", "c"}, 12, false},
+		{[]int{1, 2, 4}, 2, true},
+		{[]interface{}{1, 2, 4}, 2, true},
+		{[]interface{}{1, 2, 4}, nil, false},
+		{[]interface{}{nil}, nil, false},
+		{[]int{1, 2, 4}, 3, false},
+		{[]float64{1.23, 2.45, 4.67}, 1.23, true},
+		{[]float64{1.234567, 2.45, 4.67}, 1.234568, false},
+		{"this substring should be found", "substring", true},
+		{"this substring should not be found", "subseastring", false},
+	} {
+		result := in(this.v1, this.v2)
+
+		if result != this.expect {
+			t.Errorf("[%d] got %v but expected %v", i, result, this.expect)
+		}
+	}
+}
+
+func TestSlicestr(t *testing.T) {
+	t.Parallel()
+	var err error
+	for i, this := range []struct {
+		v1     interface{}
+		v2     interface{}
+		v3     interface{}
+		expect interface{}
+	}{
+		{"abc", 1, 2, "b"},
+		{"abc", 1, 3, "bc"},
+		{"abcdef", 1, int8(3), "bc"},
+		{"abcdef", 1, int16(3), "bc"},
+		{"abcdef", 1, int32(3), "bc"},
+		{"abcdef", 1, int64(3), "bc"},
+		{"abc", 0, 1, "a"},
+		{"abcdef", nil, nil, "abcdef"},
+		{"abcdef", 0, 6, "abcdef"},
+		{"abcdef", 0, 2, "ab"},
+		{"abcdef", 2, nil, "cdef"},
+		{"abcdef", int8(2), nil, "cdef"},
+		{"abcdef", int16(2), nil, "cdef"},
+		{"abcdef", int32(2), nil, "cdef"},
+		{"abcdef", int64(2), nil, "cdef"},
+		{123, 1, 3, "23"},
+		{"abcdef", 6, nil, false},
+		{"abcdef", 4, 7, false},
+		{"abcdef", -1, nil, false},
+		{"abcdef", -1, 7, false},
+		{"abcdef", 1, -1, false},
+		{tstNoStringer{}, 0, 1, false},
+		{"ĀĀĀ", 0, 1, "Ā"}, // issue #1333
+		{"a", t, nil, false},
+		{"a", 1, t, false},
+	} {
+		var result string
+		if this.v2 == nil {
+			result, err = slicestr(this.v1)
+		} else if this.v3 == nil {
+			result, err = slicestr(this.v1, this.v2)
+		} else {
+			result, err = slicestr(this.v1, this.v2, this.v3)
+		}
+
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] Slice 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] got %s but expected %s", i, result, this.expect)
+			}
+		}
+	}
+
+	// Too many arguments
+	_, err = slicestr("a", 1, 2, 3)
+	if err == nil {
+		t.Errorf("Should have errored")
+	}
+}
+
+func TestHasPrefix(t *testing.T) {
+	t.Parallel()
+	cases := []struct {
+		s      interface{}
+		prefix interface{}
+		want   interface{}
+		isErr  bool
+	}{
+		{"abcd", "ab", true, false},
+		{"abcd", "cd", false, false},
+		{template.HTML("abcd"), "ab", true, false},
+		{template.HTML("abcd"), "cd", false, false},
+		{template.HTML("1234"), 12, true, false},
+		{template.HTML("1234"), 34, false, false},
+		{[]byte("abcd"), "ab", true, false},
+	}
+
+	for i, c := range cases {
+		res, err := hasPrefix(c.s, c.prefix)
+		if (err != nil) != c.isErr {
+			t.Fatalf("[%d] unexpected isErr state: want %v, got %v, err = %v", i, c.isErr, err != nil, err)
+		}
+		if res != c.want {
+			t.Errorf("[%d] want %v, got %v", i, c.want, res)
+		}
+	}
+}
+
+func TestSubstr(t *testing.T) {
+	t.Parallel()
+	var err error
+	var n int
+	for i, this := range []struct {
+		v1     interface{}
+		v2     interface{}
+		v3     interface{}
+		expect interface{}
+	}{
+		{"abc", 1, 2, "bc"},
+		{"abc", 0, 1, "a"},
+		{"abcdef", -1, 2, "ef"},
+		{"abcdef", -3, 3, "bcd"},
+		{"abcdef", 0, -1, "abcde"},
+		{"abcdef", 2, -1, "cde"},
+		{"abcdef", 4, -4, false},
+		{"abcdef", 7, 1, false},
+		{"abcdef", 1, 100, "bcdef"},
+		{"abcdef", -100, 3, "abc"},
+		{"abcdef", -3, -1, "de"},
+		{"abcdef", 2, nil, "cdef"},
+		{"abcdef", int8(2), nil, "cdef"},
+		{"abcdef", int16(2), nil, "cdef"},
+		{"abcdef", int32(2), nil, "cdef"},
+		{"abcdef", int64(2), nil, "cdef"},
+		{"abcdef", 2, int8(3), "cde"},
+		{"abcdef", 2, int16(3), "cde"},
+		{"abcdef", 2, int32(3), "cde"},
+		{"abcdef", 2, int64(3), "cde"},
+		{123, 1, 3, "23"},
+		{1.2e3, 0, 4, "1200"},
+		{tstNoStringer{}, 0, 1, false},
+		{"abcdef", 2.0, nil, "cdef"},
+		{"abcdef", 2.0, 2, "cd"},
+		{"abcdef", 2, 2.0, "cd"},
+		{"ĀĀĀ", 1, 2, "ĀĀ"}, // # issue 1333
+		{"abcdef", "doo", nil, false},
+		{"abcdef", "doo", "doo", false},
+		{"abcdef", 1, "doo", false},
+	} {
+		var result string
+		n = i
+
+		if this.v3 == nil {
+			result, err = substr(this.v1, this.v2)
+		} else {
+			result, err = substr(this.v1, this.v2, this.v3)
+		}
+
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] Substr 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] got %s but expected %s", i, result, this.expect)
+			}
+		}
+	}
+
+	n++
+	_, err = substr("abcdef")
+	if err == nil {
+		t.Errorf("[%d] Substr didn't return an expected error", n)
+	}
+
+	n++
+	_, err = substr("abcdef", 1, 2, 3)
+	if err == nil {
+		t.Errorf("[%d] Substr didn't return an expected error", n)
+	}
+}
+
+func TestSplit(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		v1     interface{}
+		v2     string
+		expect interface{}
+	}{
+		{"a, b", ", ", []string{"a", "b"}},
+		{"a & b & c", " & ", []string{"a", "b", "c"}},
+		{"http://example.com", "http://", []string{"", "example.com"}},
+		{123, "2", []string{"1", "3"}},
+		{tstNoStringer{}, ",", false},
+	} {
+		result, err := split(this.v1, this.v2)
+
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] Split 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] got %s but expected %s", i, result, this.expect)
+			}
+		}
+	}
+}
+
+func TestIntersect(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		sequence1 interface{}
+		sequence2 interface{}
+		expect    interface{}
+	}{
+		{[]string{"a", "b", "c", "c"}, []string{"a", "b", "b"}, []string{"a", "b"}},
+		{[]string{"a", "b"}, []string{"a", "b", "c"}, []string{"a", "b"}},
+		{[]string{"a", "b", "c"}, []string{"d", "e"}, []string{}},
+		{[]string{}, []string{}, []string{}},
+		{[]string{"a", "b"}, nil, make([]interface{}, 0)},
+		{nil, []string{"a", "b"}, make([]interface{}, 0)},
+		{nil, nil, make([]interface{}, 0)},
+		{[]string{"1", "2"}, []int{1, 2}, []string{}},
+		{[]int{1, 2}, []string{"1", "2"}, []int{}},
+		{[]int{1, 2, 4}, []int{2, 4}, []int{2, 4}},
+		{[]int{2, 4}, []int{1, 2, 4}, []int{2, 4}},
+		{[]int{1, 2, 4}, []int{3, 6}, []int{}},
+		{[]float64{2.2, 4.4}, []float64{1.1, 2.2, 4.4}, []float64{2.2, 4.4}},
+	} {
+		results, err := intersect(this.sequence1, this.sequence2)
+		if err != nil {
+			t.Errorf("[%d] failed: %s", i, err)
+			continue
+		}
+		if !reflect.DeepEqual(results, this.expect) {
+			t.Errorf("[%d] got %v but expected %v", i, results, this.expect)
+		}
+	}
+
+	_, err1 := intersect("not an array or slice", []string{"a"})
+
+	if err1 == nil {
+		t.Error("Expected error for non array as first arg")
+	}
+
+	_, err2 := intersect([]string{"a"}, "not an array or slice")
+
+	if err2 == nil {
+		t.Error("Expected error for non array as second arg")
+	}
+}
+
+func TestIsSet(t *testing.T) {
+	t.Parallel()
+	aSlice := []interface{}{1, 2, 3, 5}
+	aMap := map[string]interface{}{"a": 1, "b": 2}
+
+	assert.True(t, isSet(aSlice, 2))
+	assert.True(t, isSet(aMap, "b"))
+	assert.False(t, isSet(aSlice, 22))
+	assert.False(t, isSet(aMap, "bc"))
+}
+
+func (x *TstX) TstRp() string {
+	return "r" + x.A
+}
+
+func (x TstX) TstRv() string {
+	return "r" + x.B
+}
+
+func (x TstX) unexportedMethod() string {
+	return x.unexported
+}
+
+func (x TstX) MethodWithArg(s string) string {
+	return s
+}
+
+func (x TstX) MethodReturnNothing() {}
+
+func (x TstX) MethodReturnErrorOnly() error {
+	return errors.New("some error occurred")
+}
+
+func (x TstX) MethodReturnTwoValues() (string, string) {
+	return "foo", "bar"
+}
+
+func (x TstX) MethodReturnValueWithError() (string, error) {
+	return "", errors.New("some error occurred")
+}
+
+func (x TstX) String() string {
+	return fmt.Sprintf("A: %s, B: %s", x.A, x.B)
+}
+
+type TstX struct {
+	A, B       string
+	unexported string
+}
+
+func TestTimeUnix(t *testing.T) {
+	t.Parallel()
+	var sec int64 = 1234567890
+	tv := reflect.ValueOf(time.Unix(sec, 0))
+	i := 1
+
+	res := toTimeUnix(tv)
+	if sec != res {
+		t.Errorf("[%d] timeUnix got %v but expected %v", i, res, sec)
+	}
+
+	i++
+	func(t *testing.T) {
+		defer func() {
+			if err := recover(); err == nil {
+				t.Errorf("[%d] timeUnix didn't return an expected error", i)
+			}
+		}()
+		iv := reflect.ValueOf(sec)
+		toTimeUnix(iv)
+	}(t)
+}
+
+func TestEvaluateSubElem(t *testing.T) {
+	t.Parallel()
+	tstx := TstX{A: "foo", B: "bar"}
+	var inner struct {
+		S fmt.Stringer
+	}
+	inner.S = tstx
+	interfaceValue := reflect.ValueOf(&inner).Elem().Field(0)
+
+	for i, this := range []struct {
+		value  reflect.Value
+		key    string
+		expect interface{}
+	}{
+		{reflect.ValueOf(tstx), "A", "foo"},
+		{reflect.ValueOf(&tstx), "TstRp", "rfoo"},
+		{reflect.ValueOf(tstx), "TstRv", "rbar"},
+		//{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), 1, "foo"},
+		{reflect.ValueOf(map[string]string{"key1": "foo", "key2": "bar"}), "key1", "foo"},
+		{interfaceValue, "String", "A: foo, B: bar"},
+		{reflect.Value{}, "foo", false},
+		//{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), 1.2, false},
+		{reflect.ValueOf(tstx), "unexported", false},
+		{reflect.ValueOf(tstx), "unexportedMethod", false},
+		{reflect.ValueOf(tstx), "MethodWithArg", false},
+		{reflect.ValueOf(tstx), "MethodReturnNothing", false},
+		{reflect.ValueOf(tstx), "MethodReturnErrorOnly", false},
+		{reflect.ValueOf(tstx), "MethodReturnTwoValues", false},
+		{reflect.ValueOf(tstx), "MethodReturnValueWithError", false},
+		{reflect.ValueOf((*TstX)(nil)), "A", false},
+		{reflect.ValueOf(tstx), "C", false},
+		{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), "1", false},
+		{reflect.ValueOf([]string{"foo", "bar"}), "1", false},
+	} {
+		result, err := evaluateSubElem(this.value, this.key)
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] evaluateSubElem didn't return an expected error", i)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("[%d] failed: %s", i, err)
+				continue
+			}
+			if result.Kind() != reflect.String || result.String() != this.expect {
+				t.Errorf("[%d] evaluateSubElem with %v got %v but expected %v", i, this.key, result, this.expect)
+			}
+		}
+	}
+}
+
+func TestCheckCondition(t *testing.T) {
+	t.Parallel()
+	type expect struct {
+		result  bool
+		isError bool
+	}
+
+	for i, this := range []struct {
+		value reflect.Value
+		match reflect.Value
+		op    string
+		expect
+	}{
+		{reflect.ValueOf(123), reflect.ValueOf(123), "", expect{true, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf("foo"), "", expect{true, false}},
+		{
+			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
+			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
+			"",
+			expect{true, false},
+		},
+		{reflect.ValueOf(true), reflect.ValueOf(true), "", expect{true, false}},
+		{reflect.ValueOf(nil), reflect.ValueOf(nil), "", expect{true, false}},
+		{reflect.ValueOf(123), reflect.ValueOf(456), "!=", expect{true, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf("bar"), "!=", expect{true, false}},
+		{
+			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
+			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
+			"!=",
+			expect{true, false},
+		},
+		{reflect.ValueOf(true), reflect.ValueOf(false), "!=", expect{true, false}},
+		{reflect.ValueOf(123), reflect.ValueOf(nil), "!=", expect{true, false}},
+		{reflect.ValueOf(456), reflect.ValueOf(123), ">=", expect{true, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf("bar"), ">=", expect{true, false}},
+		{
+			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
+			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
+			">=",
+			expect{true, false},
+		},
+		{reflect.ValueOf(456), reflect.ValueOf(123), ">", expect{true, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf("bar"), ">", expect{true, false}},
+		{
+			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
+			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
+			">",
+			expect{true, false},
+		},
+		{reflect.ValueOf(123), reflect.ValueOf(456), "<=", expect{true, false}},
+		{reflect.ValueOf("bar"), reflect.ValueOf("foo"), "<=", expect{true, false}},
+		{
+			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
+			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
+			"<=",
+			expect{true, false},
+		},
+		{reflect.ValueOf(123), reflect.ValueOf(456), "<", expect{true, false}},
+		{reflect.ValueOf("bar"), reflect.ValueOf("foo"), "<", expect{true, false}},
+		{
+			reflect.ValueOf(time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC)),
+			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
+			"<",
+			expect{true, false},
+		},
+		{reflect.ValueOf(123), reflect.ValueOf([]int{123, 45, 678}), "in", expect{true, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf([]string{"foo", "bar", "baz"}), "in", expect{true, false}},
+		{
+			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
+			reflect.ValueOf([]time.Time{
+				time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC),
+				time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC),
+				time.Date(2015, time.June, 26, 19, 18, 56, 12345, time.UTC),
+			}),
+			"in",
+			expect{true, false},
+		},
+		{reflect.ValueOf(123), reflect.ValueOf([]int{45, 678}), "not in", expect{true, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf([]string{"bar", "baz"}), "not in", expect{true, false}},
+		{
+			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
+			reflect.ValueOf([]time.Time{
+				time.Date(2015, time.February, 26, 19, 18, 56, 12345, time.UTC),
+				time.Date(2015, time.March, 26, 19, 18, 56, 12345, time.UTC),
+				time.Date(2015, time.April, 26, 19, 18, 56, 12345, time.UTC),
+			}),
+			"not in",
+			expect{true, false},
+		},
+		{reflect.ValueOf("foo"), reflect.ValueOf("bar-foo-baz"), "in", expect{true, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf("bar--baz"), "not in", expect{true, false}},
+		{reflect.Value{}, reflect.ValueOf("foo"), "", expect{false, false}},
+		{reflect.ValueOf("foo"), reflect.Value{}, "", expect{false, false}},
+		{reflect.ValueOf((*TstX)(nil)), reflect.ValueOf("foo"), "", expect{false, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf((*TstX)(nil)), "", expect{false, false}},
+		{reflect.ValueOf(true), reflect.ValueOf("foo"), "", expect{false, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf(true), "", expect{false, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf(map[int]string{}), "", expect{false, false}},
+		{reflect.ValueOf("foo"), reflect.ValueOf([]int{1, 2}), "", expect{false, false}},
+		{reflect.ValueOf((*TstX)(nil)), reflect.ValueOf((*TstX)(nil)), ">", expect{false, false}},
+		{reflect.ValueOf(true), reflect.ValueOf(false), ">", expect{false, false}},
+		{reflect.ValueOf(123), reflect.ValueOf([]int{}), "in", expect{false, false}},
+		{reflect.ValueOf(123), reflect.ValueOf(123), "op", expect{false, true}},
+	} {
+		result, err := checkCondition(this.value, this.match, this.op)
+		if this.expect.isError {
+			if err == nil {
+				t.Errorf("[%d] checkCondition didn't return an expected error", i)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("[%d] failed: %s", i, err)
+				continue
+			}
+			if result != this.expect.result {
+				t.Errorf("[%d] check condition %v %s %v, got %v but expected %v", i, this.value, this.op, this.match, result, this.expect.result)
+			}
+		}
+	}
+}
+
+func TestWhere(t *testing.T) {
+	t.Parallel()
+
+	type Mid struct {
+		Tst TstX
+	}
+
+	d1 := time.Now()
+	d2 := d1.Add(1 * time.Hour)
+	d3 := d2.Add(1 * time.Hour)
+	d4 := d3.Add(1 * time.Hour)
+	d5 := d4.Add(1 * time.Hour)
+	d6 := d5.Add(1 * time.Hour)
+
+	for i, this := range []struct {
+		sequence interface{}
+		key      interface{}
+		op       string
+		match    interface{}
+		expect   interface{}
+	}{
+		{
+			sequence: []map[int]string{
+				{1: "a", 2: "m"}, {1: "c", 2: "d"}, {1: "e", 3: "m"},
+			},
+			key: 2, match: "m",
+			expect: []map[int]string{
+				{1: "a", 2: "m"},
+			},
+		},
+		{
+			sequence: []map[string]int{
+				{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "x": 4},
+			},
+			key: "b", match: 4,
+			expect: []map[string]int{
+				{"a": 3, "b": 4},
+			},
+		},
+		{
+			sequence: []TstX{
+				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
+			},
+			key: "B", match: "f",
+			expect: []TstX{
+				{A: "e", B: "f"},
+			},
+		},
+		{
+			sequence: []*map[int]string{
+				{1: "a", 2: "m"}, {1: "c", 2: "d"}, {1: "e", 3: "m"},
+			},
+			key: 2, match: "m",
+			expect: []*map[int]string{
+				{1: "a", 2: "m"},
+			},
+		},
+		{
+			sequence: []*TstX{
+				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
+			},
+			key: "B", match: "f",
+			expect: []*TstX{
+				{A: "e", B: "f"},
+			},
+		},
+		{
+			sequence: []*TstX{
+				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "c"},
+			},
+			key: "TstRp", match: "rc",
+			expect: []*TstX{
+				{A: "c", B: "d"},
+			},
+		},
+		{
+			sequence: []TstX{
+				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "c"},
+			},
+			key: "TstRv", match: "rc",
+			expect: []TstX{
+				{A: "e", B: "c"},
+			},
+		},
+		{
+			sequence: []map[string]TstX{
+				{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}},
+			},
+			key: "foo.B", match: "d",
+			expect: []map[string]TstX{
+				{"foo": TstX{A: "c", B: "d"}},
+			},
+		},
+		{
+			sequence: []map[string]TstX{
+				{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}},
+			},
+			key: ".foo.B", match: "d",
+			expect: []map[string]TstX{
+				{"foo": TstX{A: "c", B: "d"}},
+			},
+		},
+		{
+			sequence: []map[string]TstX{
+				{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}},
+			},
+			key: "foo.TstRv", match: "rd",
+			expect: []map[string]TstX{
+				{"foo": TstX{A: "c", B: "d"}},
+			},
+		},
+		{
+			sequence: []map[string]*TstX{
+				{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}},
+			},
+			key: "foo.TstRp", match: "rc",
+			expect: []map[string]*TstX{
+				{"foo": &TstX{A: "c", B: "d"}},
+			},
+		},
+		{
+			sequence: []map[string]Mid{
+				{"foo": Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": Mid{Tst: TstX{A: "e", B: "f"}}},
+			},
+			key: "foo.Tst.B", match: "d",
+			expect: []map[string]Mid{
+				{"foo": Mid{Tst: TstX{A: "c", B: "d"}}},
+			},
+		},
+		{
+			sequence: []map[string]Mid{
+				{"foo": Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": Mid{Tst: TstX{A: "e", B: "f"}}},
+			},
+			key: "foo.Tst.TstRv", match: "rd",
+			expect: []map[string]Mid{
+				{"foo": Mid{Tst: TstX{A: "c", B: "d"}}},
+			},
+		},
+		{
+			sequence: []map[string]*Mid{
+				{"foo": &Mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": &Mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": &Mid{Tst: TstX{A: "e", B: "f"}}},
+			},
+			key: "foo.Tst.TstRp", match: "rc",
+			expect: []map[string]*Mid{
+				{"foo": &Mid{Tst: TstX{A: "c", B: "d"}}},
+			},
+		},
+		{
+			sequence: []map[string]int{
+				{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6},
+			},
+			key: "b", op: ">", match: 3,
+			expect: []map[string]int{
+				{"a": 3, "b": 4}, {"a": 5, "b": 6},
+			},
+		},
+		{
+			sequence: []TstX{
+				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
+			},
+			key: "B", op: "!=", match: "f",
+			expect: []TstX{
+				{A: "a", B: "b"}, {A: "c", B: "d"},
+			},
+		},
+		{
+			sequence: []map[string]int{
+				{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6},
+			},
+			key: "b", op: "in", match: []int{3, 4, 5},
+			expect: []map[string]int{
+				{"a": 3, "b": 4},
+			},
+		},
+		{
+			sequence: []map[string][]string{
+				{"a": []string{"A", "B", "C"}, "b": []string{"D", "E", "F"}}, {"a": []string{"G", "H", "I"}, "b": []string{"J", "K", "L"}}, {"a": []string{"M", "N", "O"}, "b": []string{"P", "Q", "R"}},
+			},
+			key: "b", op: "intersect", match: []string{"D", "P", "Q"},
+			expect: []map[string][]string{
+				{"a": []string{"A", "B", "C"}, "b": []string{"D", "E", "F"}}, {"a": []string{"M", "N", "O"}, "b": []string{"P", "Q", "R"}},
+			},
+		},
+		{
+			sequence: []map[string][]int{
+				{"a": []int{1, 2, 3}, "b": []int{4, 5, 6}}, {"a": []int{7, 8, 9}, "b": []int{10, 11, 12}}, {"a": []int{13, 14, 15}, "b": []int{16, 17, 18}},
+			},
+			key: "b", op: "intersect", match: []int{4, 10, 12},
+			expect: []map[string][]int{
+				{"a": []int{1, 2, 3}, "b": []int{4, 5, 6}}, {"a": []int{7, 8, 9}, "b": []int{10, 11, 12}},
+			},
+		},
+		{
+			sequence: []map[string][]int8{
+				{"a": []int8{1, 2, 3}, "b": []int8{4, 5, 6}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 11, 12}}, {"a": []int8{13, 14, 15}, "b": []int8{16, 17, 18}},
+			},
+			key: "b", op: "intersect", match: []int8{4, 10, 12},
+			expect: []map[string][]int8{
+				{"a": []int8{1, 2, 3}, "b": []int8{4, 5, 6}}, {"a": []int8{7, 8, 9}, "b": []int8{10, 11, 12}},
+			},
+		},
+		{
+			sequence: []map[string][]int16{
+				{"a": []int16{1, 2, 3}, "b": []int16{4, 5, 6}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 11, 12}}, {"a": []int16{13, 14, 15}, "b": []int16{16, 17, 18}},
+			},
+			key: "b", op: "intersect", match: []int16{4, 10, 12},
+			expect: []map[string][]int16{
+				{"a": []int16{1, 2, 3}, "b": []int16{4, 5, 6}}, {"a": []int16{7, 8, 9}, "b": []int16{10, 11, 12}},
+			},
+		},
+		{
+			sequence: []map[string][]int32{
+				{"a": []int32{1, 2, 3}, "b": []int32{4, 5, 6}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 11, 12}}, {"a": []int32{13, 14, 15}, "b": []int32{16, 17, 18}},
+			},
+			key: "b", op: "intersect", match: []int32{4, 10, 12},
+			expect: []map[string][]int32{
+				{"a": []int32{1, 2, 3}, "b": []int32{4, 5, 6}}, {"a": []int32{7, 8, 9}, "b": []int32{10, 11, 12}},
+			},
+		},
+		{
+			sequence: []map[string][]int64{
+				{"a": []int64{1, 2, 3}, "b": []int64{4, 5, 6}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 11, 12}}, {"a": []int64{13, 14, 15}, "b": []int64{16, 17, 18}},
+			},
+			key: "b", op: "intersect", match: []int64{4, 10, 12},
+			expect: []map[string][]int64{
+				{"a": []int64{1, 2, 3}, "b": []int64{4, 5, 6}}, {"a": []int64{7, 8, 9}, "b": []int64{10, 11, 12}},
+			},
+		},
+		{
+			sequence: []map[string][]float32{
+				{"a": []float32{1.0, 2.0, 3.0}, "b": []float32{4.0, 5.0, 6.0}}, {"a": []float32{7.0, 8.0, 9.0}, "b": []float32{10.0, 11.0, 12.0}}, {"a": []float32{13.0, 14.0, 15.0}, "b": []float32{16.0, 17.0, 18.0}},
+			},
+			key: "b", op: "intersect", match: []float32{4, 10, 12},
+			expect: []map[string][]float32{
+				{"a": []float32{1.0, 2.0, 3.0}, "b": []float32{4.0, 5.0, 6.0}}, {"a": []float32{7.0, 8.0, 9.0}, "b": []float32{10.0, 11.0, 12.0}},
+			},
+		},
+		{
+			sequence: []map[string][]float64{
+				{"a": []float64{1.0, 2.0, 3.0}, "b": []float64{4.0, 5.0, 6.0}}, {"a": []float64{7.0, 8.0, 9.0}, "b": []float64{10.0, 11.0, 12.0}}, {"a": []float64{13.0, 14.0, 15.0}, "b": []float64{16.0, 17.0, 18.0}},
+			},
+			key: "b", op: "intersect", match: []float64{4, 10, 12},
+			expect: []map[string][]float64{
+				{"a": []float64{1.0, 2.0, 3.0}, "b": []float64{4.0, 5.0, 6.0}}, {"a": []float64{7.0, 8.0, 9.0}, "b": []float64{10.0, 11.0, 12.0}},
+			},
+		},
+		{
+			sequence: []map[string]int{
+				{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6},
+			},
+			key: "b", op: "in", match: slice(3, 4, 5),
+			expect: []map[string]int{
+				{"a": 3, "b": 4},
+			},
+		},
+		{
+			sequence: []map[string]time.Time{
+				{"a": d1, "b": d2}, {"a": d3, "b": d4}, {"a": d5, "b": d6},
+			},
+			key: "b", op: "in", match: slice(d3, d4, d5),
+			expect: []map[string]time.Time{
+				{"a": d3, "b": d4},
+			},
+		},
+		{
+			sequence: []TstX{
+				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
+			},
+			key: "B", op: "not in", match: []string{"c", "d", "e"},
+			expect: []TstX{
+				{A: "a", B: "b"}, {A: "e", B: "f"},
+			},
+		},
+		{
+			sequence: []TstX{
+				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
+			},
+			key: "B", op: "not in", match: slice("c", t, "d", "e"),
+			expect: []TstX{
+				{A: "a", B: "b"}, {A: "e", B: "f"},
+			},
+		},
+		{
+			sequence: []map[string]int{
+				{"a": 1, "b": 2}, {"a": 3}, {"a": 5, "b": 6},
+			},
+			key: "b", op: "", match: nil,
+			expect: []map[string]int{
+				{"a": 3},
+			},
+		},
+		{
+			sequence: []map[string]int{
+				{"a": 1, "b": 2}, {"a": 3}, {"a": 5, "b": 6},
+			},
+			key: "b", op: "!=", match: nil,
+			expect: []map[string]int{
+				{"a": 1, "b": 2}, {"a": 5, "b": 6},
+			},
+		},
+		{
+			sequence: []map[string]int{
+				{"a": 1, "b": 2}, {"a": 3}, {"a": 5, "b": 6},
+			},
+			key: "b", op: ">", match: nil,
+			expect: []map[string]int{},
+		},
+		{
+			sequence: []map[string]bool{
+				{"a": true, "b": false}, {"c": true, "b": true}, {"d": true, "b": false},
+			},
+			key: "b", op: "", match: true,
+			expect: []map[string]bool{
+				{"c": true, "b": true},
+			},
+		},
+		{
+			sequence: []map[string]bool{
+				{"a": true, "b": false}, {"c": true, "b": true}, {"d": true, "b": false},
+			},
+			key: "b", op: "!=", match: true,
+			expect: []map[string]bool{
+				{"a": true, "b": false}, {"d": true, "b": false},
+			},
+		},
+		{
+			sequence: []map[string]bool{
+				{"a": true, "b": false}, {"c": true, "b": true}, {"d": true, "b": false},
+			},
+			key: "b", op: ">", match: false,
+			expect: []map[string]bool{},
+		},
+		{sequence: (*[]TstX)(nil), key: "A", match: "a", expect: false},
+		{sequence: TstX{A: "a", B: "b"}, key: "A", match: "a", expect: false},
+		{sequence: []map[string]*TstX{{"foo": nil}}, key: "foo.B", match: "d", expect: false},
+		{
+			sequence: []TstX{
+				{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"},
+			},
+			key: "B", op: "op", match: "f",
+			expect: false,
+		},
+		{
+			sequence: map[string]interface{}{
+				"foo": []interface{}{map[interface{}]interface{}{"a": 1, "b": 2}},
+				"bar": []interface{}{map[interface{}]interface{}{"a": 3, "b": 4}},
+				"zap": []interface{}{map[interface{}]interface{}{"a": 5, "b": 6}},
+			},
+			key: "b", op: "in", match: slice(3, 4, 5),
+			expect: map[string]interface{}{
+				"bar": []interface{}{map[interface{}]interface{}{"a": 3, "b": 4}},
+			},
+		},
+		{
+			sequence: map[string]interface{}{
+				"foo": []interface{}{map[interface{}]interface{}{"a": 1, "b": 2}},
+				"bar": []interface{}{map[interface{}]interface{}{"a": 3, "b": 4}},
+				"zap": []interface{}{map[interface{}]interface{}{"a": 5, "b": 6}},
+			},
+			key: "b", op: ">", match: 3,
+			expect: map[string]interface{}{
+				"bar": []interface{}{map[interface{}]interface{}{"a": 3, "b": 4}},
+				"zap": []interface{}{map[interface{}]interface{}{"a": 5, "b": 6}},
+			},
+		},
+	} {
+		var results interface{}
+		var err error
+
+		if len(this.op) > 0 {
+			results, err = where(this.sequence, this.key, this.op, this.match)
+		} else {
+			results, err = where(this.sequence, this.key, this.match)
+		}
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] Where didn't return an expected error", i)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("[%d] failed: %s", i, err)
+				continue
+			}
+			if !reflect.DeepEqual(results, this.expect) {
+				t.Errorf("[%d] Where clause matching %v with %v, got %v but expected %v", i, this.key, this.match, results, this.expect)
+			}
+		}
+	}
+
+	var err error
+	_, err = where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1)
+	if err == nil {
+		t.Errorf("Where called with none string op value didn't return an expected error")
+	}
+
+	_, err = where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2)
+	if err == nil {
+		t.Errorf("Where called with more than two variable arguments didn't return an expected error")
+	}
+
+	_, err = where(map[string]int{"a": 1, "b": 2}, "a")
+	if err == nil {
+		t.Errorf("Where called with no variable arguments didn't return an expected error")
+	}
+}
+
+func TestDelimit(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		sequence  interface{}
+		delimiter interface{}
+		last      interface{}
+		expect    template.HTML
+	}{
+		{[]string{"class1", "class2", "class3"}, " ", nil, "class1 class2 class3"},
+		{[]int{1, 2, 3, 4, 5}, ",", nil, "1,2,3,4,5"},
+		{[]int{1, 2, 3, 4, 5}, ", ", nil, "1, 2, 3, 4, 5"},
+		{[]string{"class1", "class2", "class3"}, " ", " and ", "class1 class2 and class3"},
+		{[]int{1, 2, 3, 4, 5}, ",", ",", "1,2,3,4,5"},
+		{[]int{1, 2, 3, 4, 5}, ", ", ", and ", "1, 2, 3, 4, and 5"},
+		// test maps with and without sorting required
+		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", nil, "10--20--30--40--50"},
+		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", nil, "30--20--10--40--50"},
+		{map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", nil, "10--20--30--40--50"},
+		{map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", nil, "30--20--10--40--50"},
+		{map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", nil, "50--40--10--30--20"},
+		{map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", nil, "10--20--30--40--50"},
+		{map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", nil, "30--20--10--40--50"},
+		{map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, "--", nil, "30--20--10--40--50"},
+		// test maps with a last delimiter
+		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "--", "--and--", "10--20--30--40--and--50"},
+		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "--", "--and--", "30--20--10--40--and--50"},
+		{map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, "--", "--and--", "10--20--30--40--and--50"},
+		{map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, "--", "--and--", "30--20--10--40--and--50"},
+		{map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, "--", "--and--", "50--40--10--30--and--20"},
+		{map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, "--", "--and--", "10--20--30--40--and--50"},
+		{map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, "--", "--and--", "30--20--10--40--and--50"},
+		{map[float64]string{3.5: "10", 2.5: "20", 1.5: "30", 4.5: "40", 5.5: "50"}, "--", "--and--", "30--20--10--40--and--50"},
+	} {
+		var result template.HTML
+		var err error
+		if this.last == nil {
+			result, err = delimit(this.sequence, this.delimiter)
+		} else {
+			result, err = delimit(this.sequence, this.delimiter, this.last)
+		}
+		if err != nil {
+			t.Errorf("[%d] failed: %s", i, err)
+			continue
+		}
+		if !reflect.DeepEqual(result, this.expect) {
+			t.Errorf("[%d] Delimit called on sequence: %v | delimiter: `%v` | last: `%v`, got %v but expected %v", i, this.sequence, this.delimiter, this.last, result, this.expect)
+		}
+	}
+}
+
+func TestSort(t *testing.T) {
+	t.Parallel()
+	type ts struct {
+		MyInt    int
+		MyFloat  float64
+		MyString string
+	}
+	type mid struct {
+		Tst TstX
+	}
+
+	for i, this := range []struct {
+		sequence    interface{}
+		sortByField interface{}
+		sortAsc     string
+		expect      interface{}
+	}{
+		{[]string{"class1", "class2", "class3"}, nil, "asc", []string{"class1", "class2", "class3"}},
+		{[]string{"class3", "class1", "class2"}, nil, "asc", []string{"class1", "class2", "class3"}},
+		{[]int{1, 2, 3, 4, 5}, nil, "asc", []int{1, 2, 3, 4, 5}},
+		{[]int{5, 4, 3, 1, 2}, nil, "asc", []int{1, 2, 3, 4, 5}},
+		// test sort key parameter is focibly set empty
+		{[]string{"class3", "class1", "class2"}, map[int]string{1: "a"}, "asc", []string{"class1", "class2", "class3"}},
+		// test map sorting by keys
+		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, nil, "asc", []int{10, 20, 30, 40, 50}},
+		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, nil, "asc", []int{30, 20, 10, 40, 50}},
+		{map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, nil, "asc", []string{"10", "20", "30", "40", "50"}},
+		{map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}},
+		{map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, nil, "asc", []string{"50", "40", "10", "30", "20"}},
+		{map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, nil, "asc", []string{"10", "20", "30", "40", "50"}},
+		{map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}},
+		{map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}},
+		// test map sorting by value
+		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "value", "asc", []int{10, 20, 30, 40, 50}},
+		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "value", "asc", []int{10, 20, 30, 40, 50}},
+		// test map sorting by field value
+		{
+			map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}},
+			"MyInt",
+			"asc",
+			[]ts{{10, 10.5, "ten"}, {20, 20.5, "twenty"}, {30, 30.5, "thirty"}, {40, 40.5, "forty"}, {50, 50.5, "fifty"}},
+		},
+		{
+			map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}},
+			"MyFloat",
+			"asc",
+			[]ts{{10, 10.5, "ten"}, {20, 20.5, "twenty"}, {30, 30.5, "thirty"}, {40, 40.5, "forty"}, {50, 50.5, "fifty"}},
+		},
+		{
+			map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}},
+			"MyString",
+			"asc",
+			[]ts{{50, 50.5, "fifty"}, {40, 40.5, "forty"}, {10, 10.5, "ten"}, {30, 30.5, "thirty"}, {20, 20.5, "twenty"}},
+		},
+		// test sort desc
+		{[]string{"class1", "class2", "class3"}, "value", "desc", []string{"class3", "class2", "class1"}},
+		{[]string{"class3", "class1", "class2"}, "value", "desc", []string{"class3", "class2", "class1"}},
+		// test sort by struct's method
+		{
+			[]TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}},
+			"TstRv",
+			"asc",
+			[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
+		},
+		{
+			[]*TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}},
+			"TstRp",
+			"asc",
+			[]*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
+		},
+		// test map sorting by struct's method
+		{
+			map[string]TstX{"1": {A: "i", B: "j"}, "2": {A: "e", B: "f"}, "3": {A: "c", B: "d"}, "4": {A: "g", B: "h"}, "5": {A: "a", B: "b"}},
+			"TstRv",
+			"asc",
+			[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
+		},
+		{
+			map[string]*TstX{"1": {A: "i", B: "j"}, "2": {A: "e", B: "f"}, "3": {A: "c", B: "d"}, "4": {A: "g", B: "h"}, "5": {A: "a", B: "b"}},
+			"TstRp",
+			"asc",
+			[]*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
+		},
+		// test sort by dot chaining key argument
+		{
+			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
+			"foo.A",
+			"asc",
+			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
+		},
+		{
+			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
+			".foo.A",
+			"asc",
+			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
+		},
+		{
+			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
+			"foo.TstRv",
+			"asc",
+			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
+		},
+		{
+			[]map[string]*TstX{{"foo": &TstX{A: "e", B: "f"}}, {"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}},
+			"foo.TstRp",
+			"asc",
+			[]map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}},
+		},
+		{
+			[]map[string]mid{{"foo": mid{Tst: TstX{A: "e", B: "f"}}}, {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
+			"foo.Tst.A",
+			"asc",
+			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
+		},
+		{
+			[]map[string]mid{{"foo": mid{Tst: TstX{A: "e", B: "f"}}}, {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
+			"foo.Tst.TstRv",
+			"asc",
+			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
+		},
+		// test map sorting by dot chaining key argument
+		{
+			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
+			"foo.A",
+			"asc",
+			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
+		},
+		{
+			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
+			".foo.A",
+			"asc",
+			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
+		},
+		{
+			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
+			"foo.TstRv",
+			"asc",
+			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
+		},
+		{
+			map[string]map[string]*TstX{"1": {"foo": &TstX{A: "e", B: "f"}}, "2": {"foo": &TstX{A: "a", B: "b"}}, "3": {"foo": &TstX{A: "c", B: "d"}}},
+			"foo.TstRp",
+			"asc",
+			[]map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}},
+		},
+		{
+			map[string]map[string]mid{"1": {"foo": mid{Tst: TstX{A: "e", B: "f"}}}, "2": {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, "3": {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
+			"foo.Tst.A",
+			"asc",
+			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
+		},
+		{
+			map[string]map[string]mid{"1": {"foo": mid{Tst: TstX{A: "e", B: "f"}}}, "2": {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, "3": {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
+			"foo.Tst.TstRv",
+			"asc",
+			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
+		},
+		// interface slice with missing elements
+		{
+			[]interface{}{
+				map[interface{}]interface{}{"Title": "Foo", "Weight": 10},
+				map[interface{}]interface{}{"Title": "Bar"},
+				map[interface{}]interface{}{"Title": "Zap", "Weight": 5},
+			},
+			"Weight",
+			"asc",
+			[]interface{}{
+				map[interface{}]interface{}{"Title": "Bar"},
+				map[interface{}]interface{}{"Title": "Zap", "Weight": 5},
+				map[interface{}]interface{}{"Title": "Foo", "Weight": 10},
+			},
+		},
+		// test error cases
+		{(*[]TstX)(nil), nil, "asc", false},
+		{TstX{A: "a", B: "b"}, nil, "asc", false},
+		{
+			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
+			"foo.NotAvailable",
+			"asc",
+			false,
+		},
+		{
+			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
+			"foo.NotAvailable",
+			"asc",
+			false,
+		},
+		{nil, nil, "asc", false},
+	} {
+		var result interface{}
+		var err error
+		if this.sortByField == nil {
+			result, err = sortSeq(this.sequence)
+		} else {
+			result, err = sortSeq(this.sequence, this.sortByField, this.sortAsc)
+		}
+
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] Sort 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] Sort called on sequence: %v | sortByField: `%v` | got %v but expected %v", i, this.sequence, this.sortByField, result, this.expect)
+			}
+		}
+	}
+}
+
+func TestReturnWhenSet(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		data   interface{}
+		key    interface{}
+		expect interface{}
+	}{
+		{[]int{1, 2, 3}, 1, int64(2)},
+		{[]uint{1, 2, 3}, 1, uint64(2)},
+		{[]float64{1.1, 2.2, 3.3}, 1, float64(2.2)},
+		{[]string{"foo", "bar", "baz"}, 1, "bar"},
+		{[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}}, 1, ""},
+		{map[string]int{"foo": 1, "bar": 2, "baz": 3}, "bar", int64(2)},
+		{map[string]uint{"foo": 1, "bar": 2, "baz": 3}, "bar", uint64(2)},
+		{map[string]float64{"foo": 1.1, "bar": 2.2, "baz": 3.3}, "bar", float64(2.2)},
+		{map[string]string{"foo": "FOO", "bar": "BAR", "baz": "BAZ"}, "bar", "BAR"},
+		{map[string]TstX{"foo": {A: "a", B: "b"}, "bar": {A: "c", B: "d"}, "baz": {A: "e", B: "f"}}, "bar", ""},
+		{(*[]string)(nil), "bar", ""},
+	} {
+		result := returnWhenSet(this.data, this.key)
+		if !reflect.DeepEqual(result, this.expect) {
+			t.Errorf("[%d] ReturnWhenSet got %v (type %v) but expected %v (type %v)", i, result, reflect.TypeOf(result), this.expect, reflect.TypeOf(this.expect))
+		}
+	}
+}
+
+func TestMarkdownify(t *testing.T) {
+	t.Parallel()
+	v := viper.New()
+
+	f := newTestFuncsterWithViper(v)
+
+	for i, this := range []struct {
+		in     interface{}
+		expect interface{}
+	}{
+		{"Hello **World!**", template.HTML("Hello <strong>World!</strong>")},
+		{[]byte("Hello Bytes **World!**"), template.HTML("Hello Bytes <strong>World!</strong>")},
+	} {
+		result, err := f.markdownify(this.in)
+		if err != nil {
+			t.Fatalf("[%d] unexpected error in markdownify: %s", i, err)
+		}
+		if !reflect.DeepEqual(result, this.expect) {
+			t.Errorf("[%d] markdownify got %v (type %v) but expected %v (type %v)", i, result, reflect.TypeOf(result), this.expect, reflect.TypeOf(this.expect))
+		}
+	}
+
+	if _, err := f.markdownify(t); err == nil {
+		t.Fatalf("markdownify should have errored")
+	}
+}
+
+func TestApply(t *testing.T) {
+	t.Parallel()
+
+	f := newTestFuncster()
+
+	strings := []interface{}{"a\n", "b\n"}
+	noStringers := []interface{}{tstNoStringer{}, tstNoStringer{}}
+
+	chomped, _ := f.apply(strings, "chomp", ".")
+	assert.Equal(t, []interface{}{template.HTML("a"), template.HTML("b")}, chomped)
+
+	chomped, _ = f.apply(strings, "chomp", "c\n")
+	assert.Equal(t, []interface{}{template.HTML("c"), template.HTML("c")}, chomped)
+
+	chomped, _ = f.apply(nil, "chomp", ".")
+	assert.Equal(t, []interface{}{}, chomped)
+
+	_, err := f.apply(strings, "apply", ".")
+	if err == nil {
+		t.Errorf("apply with apply should fail")
+	}
+
+	var nilErr *error
+	_, err = f.apply(nilErr, "chomp", ".")
+	if err == nil {
+		t.Errorf("apply with nil in seq should fail")
+	}
+
+	_, err = f.apply(strings, "dobedobedo", ".")
+	if err == nil {
+		t.Errorf("apply with unknown func should fail")
+	}
+
+	_, err = f.apply(noStringers, "chomp", ".")
+	if err == nil {
+		t.Errorf("apply when func fails should fail")
+	}
+
+	_, err = f.apply(tstNoStringer{}, "chomp", ".")
+	if err == nil {
+		t.Errorf("apply with non-sequence should fail")
+	}
+}
+
+func TestChomp(t *testing.T) {
+	t.Parallel()
+	base := "\n This is\na story "
+	for i, item := range []string{
+		"\n", "\n\n",
+		"\r", "\r\r",
+		"\r\n", "\r\n\r\n",
+	} {
+		c, _ := chomp(base + item)
+		chomped := string(c)
+
+		if chomped != base {
+			t.Errorf("[%d] Chomp failed, got '%v'", i, chomped)
+		}
+
+		_, err := chomp(tstNoStringer{})
+
+		if err == nil {
+			t.Errorf("Chomp should fail")
+		}
+	}
+}
+
+func TestLower(t *testing.T) {
+	t.Parallel()
+	cases := []struct {
+		s     interface{}
+		want  string
+		isErr bool
+	}{
+		{"TEST", "test", false},
+		{template.HTML("LoWeR"), "lower", false},
+		{[]byte("BYTES"), "bytes", false},
+	}
+
+	for i, c := range cases {
+		res, err := lower(c.s)
+		if (err != nil) != c.isErr {
+			t.Fatalf("[%d] unexpected isErr state: want %v, got %v, err = %v", i, c.want, (err != nil), err)
+		}
+
+		if res != c.want {
+			t.Errorf("[%d] lower failed: want %v, got %v", i, c.want, res)
+		}
+	}
+}
+
+func TestTitle(t *testing.T) {
+	t.Parallel()
+	cases := []struct {
+		s     interface{}
+		want  string
+		isErr bool
+	}{
+		{"test", "Test", false},
+		{template.HTML("hypertext"), "Hypertext", false},
+		{[]byte("bytes"), "Bytes", false},
+	}
+
+	for i, c := range cases {
+		res, err := title(c.s)
+		if (err != nil) != c.isErr {
+			t.Fatalf("[%d] unexpected isErr state: want %v, got %v, err = %v", i, c.want, (err != nil), err)
+		}
+
+		if res != c.want {
+			t.Errorf("[%d] title failed: want %v, got %v", i, c.want, res)
+		}
+	}
+}
+
+func TestUpper(t *testing.T) {
+	t.Parallel()
+	cases := []struct {
+		s     interface{}
+		want  string
+		isErr bool
+	}{
+		{"test", "TEST", false},
+		{template.HTML("UpPeR"), "UPPER", false},
+		{[]byte("bytes"), "BYTES", false},
+	}
+
+	for i, c := range cases {
+		res, err := upper(c.s)
+		if (err != nil) != c.isErr {
+			t.Fatalf("[%d] unexpected isErr state: want %v, got %v, err = %v", i, c.want, (err != nil), err)
+		}
+
+		if res != c.want {
+			t.Errorf("[%d] upper failed: want %v, got %v", i, c.want, res)
+		}
+	}
+}
+
+func TestHighlight(t *testing.T) {
+	t.Parallel()
+	code := "func boo() {}"
+
+	f := newTestFuncster()
+
+	highlighted, err := f.highlight(code, "go", "")
+
+	if err != nil {
+		t.Fatal("Highlight returned error:", err)
+	}
+
+	// this depends on a Pygments installation, but will always contain the function name.
+	if !strings.Contains(string(highlighted), "boo") {
+		t.Errorf("Highlight mismatch,  got %v", highlighted)
+	}
+
+	_, err = f.highlight(t, "go", "")
+
+	if err == nil {
+		t.Error("Expected highlight error")
+	}
+}
+
+func TestInflect(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		inflectFunc func(i interface{}) (string, error)
+		in          interface{}
+		expected    string
+	}{
+		{humanize, "MyCamel", "My camel"},
+		{humanize, "", ""},
+		{humanize, "103", "103rd"},
+		{humanize, "41", "41st"},
+		{humanize, 103, "103rd"},
+		{humanize, int64(92), "92nd"},
+		{humanize, "5.5", "5.5"},
+		{pluralize, "cat", "cats"},
+		{pluralize, "", ""},
+		{singularize, "cats", "cat"},
+		{singularize, "", ""},
+	} {
+
+		result, err := this.inflectFunc(this.in)
+
+		if err != nil {
+			t.Errorf("[%d] Unexpected Inflect error: %s", i, err)
+		} else if result != this.expected {
+			t.Errorf("[%d] Inflect method error, got %v expected %v", i, result, this.expected)
+		}
+
+		_, err = this.inflectFunc(t)
+		if err == nil {
+			t.Errorf("[%d] Expected Inflect error", i)
+		}
+	}
+}
+
+func TestCounterFuncs(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		countFunc func(i interface{}) (int, error)
+		in        string
+		expected  int
+	}{
+		{countWords, "Do Be Do Be Do", 5},
+		{countWords, "旁边", 2},
+		{countRunes, "旁边", 2},
+	} {
+
+		result, err := this.countFunc(this.in)
+
+		if err != nil {
+			t.Errorf("[%d] Unexpected counter error: %s", i, err)
+		} else if result != this.expected {
+			t.Errorf("[%d] Count method error, got %v expected %v", i, result, this.expected)
+		}
+
+		_, err = this.countFunc(t)
+		if err == nil {
+			t.Errorf("[%d] Expected Count error", i)
+		}
+	}
+}
+
+func TestReplace(t *testing.T) {
+	t.Parallel()
+	v, _ := replace("aab", "a", "b")
+	assert.Equal(t, "bbb", v)
+	v, _ = replace("11a11", 1, 2)
+	assert.Equal(t, "22a22", v)
+	v, _ = replace(12345, 1, 2)
+	assert.Equal(t, "22345", v)
+	_, e := replace(tstNoStringer{}, "a", "b")
+	assert.NotNil(t, e, "tstNoStringer isn't trimmable")
+	_, e = replace("a", tstNoStringer{}, "b")
+	assert.NotNil(t, e, "tstNoStringer cannot be converted to string")
+	_, e = replace("a", "b", tstNoStringer{})
+	assert.NotNil(t, e, "tstNoStringer cannot be converted to string")
+}
+
+func TestReplaceRE(t *testing.T) {
+	t.Parallel()
+	for i, val := range []struct {
+		pattern interface{}
+		repl    interface{}
+		src     interface{}
+		expect  string
+		ok      bool
+	}{
+		{"^https?://([^/]+).*", "$1", "http://gohugo.io/docs", "gohugo.io", true},
+		{"^https?://([^/]+).*", "$2", "http://gohugo.io/docs", "", true},
+		{tstNoStringer{}, "$2", "http://gohugo.io/docs", "", false},
+		{"^https?://([^/]+).*", tstNoStringer{}, "http://gohugo.io/docs", "", false},
+		{"^https?://([^/]+).*", "$2", tstNoStringer{}, "", false},
+		{"(ab)", "AB", "aabbaab", "aABbaAB", true},
+		{"(ab", "AB", "aabb", "", false}, // invalid re
+	} {
+		v, err := replaceRE(val.pattern, val.repl, val.src)
+		if (err == nil) != val.ok {
+			t.Errorf("[%d] %s", i, err)
+		}
+		assert.Equal(t, val.expect, v)
+	}
+}
+
+func TestFindRE(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		expr    string
+		content interface{}
+		limit   interface{}
+		expect  []string
+		ok      bool
+	}{
+		{"[G|g]o", "Hugo is a static site generator written in Go.", 2, []string{"go", "Go"}, true},
+		{"[G|g]o", "Hugo is a static site generator written in Go.", -1, []string{"go", "Go"}, true},
+		{"[G|g]o", "Hugo is a static site generator written in Go.", 1, []string{"go"}, true},
+		{"[G|g]o", "Hugo is a static site generator written in Go.", "1", []string{"go"}, true},
+		{"[G|g]o", "Hugo is a static site generator written in Go.", nil, []string(nil), true},
+		{"[G|go", "Hugo is a static site generator written in Go.", nil, []string(nil), false},
+		{"[G|g]o", t, nil, []string(nil), false},
+	} {
+		var (
+			res []string
+			err error
+		)
+
+		res, err = findRE(this.expr, this.content, this.limit)
+		if err != nil && this.ok {
+			t.Errorf("[%d] returned an unexpected error: %s", i, err)
+		}
+
+		assert.Equal(t, this.expect, res)
+	}
+}
+
+func TestTrim(t *testing.T) {
+	t.Parallel()
+
+	for i, this := range []struct {
+		v1     interface{}
+		v2     string
+		expect interface{}
+	}{
+		{"1234 my way 13", "123 ", "4 my way"},
+		{"      my way  ", " ", "my way"},
+		{1234, "14", "23"},
+		{tstNoStringer{}, " ", false},
+	} {
+		result, err := trim(this.v1, this.v2)
+
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] trim 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] got '%s' but expected %s", i, result, this.expect)
+			}
+		}
+	}
+}
+
+func TestDateFormat(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		layout string
+		value  interface{}
+		expect interface{}
+	}{
+		{"Monday, Jan 2, 2006", "2015-01-21", "Wednesday, Jan 21, 2015"},
+		{"Monday, Jan 2, 2006", time.Date(2015, time.January, 21, 0, 0, 0, 0, time.UTC), "Wednesday, Jan 21, 2015"},
+		{"This isn't a date layout string", "2015-01-21", "This isn't a date layout string"},
+		// The following test case gives either "Tuesday, Jan 20, 2015" or "Monday, Jan 19, 2015" depending on the local time zone
+		{"Monday, Jan 2, 2006", 1421733600, time.Unix(1421733600, 0).Format("Monday, Jan 2, 2006")},
+		{"Monday, Jan 2, 2006", 1421733600.123, false},
+		{time.RFC3339, time.Date(2016, time.March, 3, 4, 5, 0, 0, time.UTC), "2016-03-03T04:05:00Z"},
+		{time.RFC1123, time.Date(2016, time.March, 3, 4, 5, 0, 0, time.UTC), "Thu, 03 Mar 2016 04:05:00 UTC"},
+		{time.RFC3339, "Thu, 03 Mar 2016 04:05:00 UTC", "2016-03-03T04:05:00Z"},
+		{time.RFC1123, "2016-03-03T04:05:00Z", "Thu, 03 Mar 2016 04:05:00 UTC"},
+	} {
+		result, err := dateFormat(this.layout, this.value)
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] DateFormat didn't return an expected error, got %v", i, result)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("[%d] DateFormat failed: %s", i, err)
+				continue
+			}
+			if result != this.expect {
+				t.Errorf("[%d] DateFormat got %v but expected %v", i, result, this.expect)
+			}
+		}
+	}
+}
+
+func TestDefaultFunc(t *testing.T) {
+	t.Parallel()
+	then := time.Now()
+	now := time.Now()
+
+	for i, this := range []struct {
+		dflt     interface{}
+		given    interface{}
+		expected interface{}
+	}{
+		{true, false, false},
+		{"5", 0, "5"},
+
+		{"test1", "set", "set"},
+		{"test2", "", "test2"},
+		{"test3", nil, "test3"},
+
+		{[2]int{10, 20}, [2]int{1, 2}, [2]int{1, 2}},
+		{[2]int{10, 20}, [0]int{}, [2]int{10, 20}},
+		{[2]int{100, 200}, nil, [2]int{100, 200}},
+
+		{[]string{"one"}, []string{"uno"}, []string{"uno"}},
+		{[]string{"two"}, []string{}, []string{"two"}},
+		{[]string{"three"}, nil, []string{"three"}},
+
+		{map[string]int{"one": 1}, map[string]int{"uno": 1}, map[string]int{"uno": 1}},
+		{map[string]int{"one": 1}, map[string]int{}, map[string]int{"one": 1}},
+		{map[string]int{"two": 2}, nil, map[string]int{"two": 2}},
+
+		{10, 1, 1},
+		{10, 0, 10},
+		{20, nil, 20},
+
+		{float32(10), float32(1), float32(1)},
+		{float32(10), 0, float32(10)},
+		{float32(20), nil, float32(20)},
+
+		{complex(2, -2), complex(1, -1), complex(1, -1)},
+		{complex(2, -2), complex(0, 0), complex(2, -2)},
+		{complex(3, -3), nil, complex(3, -3)},
+
+		{struct{ f string }{f: "one"}, struct{ f string }{}, struct{ f string }{}},
+		{struct{ f string }{f: "two"}, nil, struct{ f string }{f: "two"}},
+
+		{then, now, now},
+		{then, time.Time{}, then},
+	} {
+		res, err := dfault(this.dflt, this.given)
+		if err != nil {
+			t.Errorf("[%d] default returned an error: %s", i, err)
+			continue
+		}
+		if !reflect.DeepEqual(this.expected, res) {
+			t.Errorf("[%d] default returned %v, but expected %v", i, res, this.expected)
+		}
+	}
+}
+
+func TestDefault(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		input    interface{}
+		tpl      string
+		expected string
+		ok       bool
+	}{
+		{map[string]string{"foo": "bar"}, `{{ index . "foo" | default "nope" }}`, `bar`, true},
+		{map[string]string{"foo": "pop"}, `{{ index . "bar" | default "nada" }}`, `nada`, true},
+		{map[string]string{"foo": "cat"}, `{{ default "nope" .foo }}`, `cat`, true},
+		{map[string]string{"foo": "dog"}, `{{ default "nope" .foo "extra" }}`, ``, false},
+		{map[string]interface{}{"images": []string{}}, `{{ default "default.jpg" (index .images 0) }}`, `default.jpg`, true},
+	} {
+
+		tmpl := newTestTemplate(t, "test", this.tpl)
+
+		buf := new(bytes.Buffer)
+		err := tmpl.Execute(buf, this.input)
+		if (err == nil) != this.ok {
+			t.Errorf("[%d] execute template returned unexpected error: %s", i, err)
+			continue
+		}
+
+		if buf.String() != this.expected {
+			t.Errorf("[%d] execute template got %v, but expected %v", i, buf.String(), this.expected)
+		}
+	}
+}
+
+func TestSafeHTML(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		str                 string
+		tmplStr             string
+		expectWithoutEscape string
+		expectWithEscape    string
+	}{
+		{`<div></div>`, `{{ . }}`, `&lt;div&gt;&lt;/div&gt;`, `<div></div>`},
+	} {
+		tmpl, err := template.New("test").Parse(this.tmplStr)
+		if err != nil {
+			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
+			continue
+		}
+
+		buf := new(bytes.Buffer)
+		err = tmpl.Execute(buf, this.str)
+		if err != nil {
+			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithoutEscape {
+			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
+		}
+
+		buf.Reset()
+		v, err := safeHTML(this.str)
+		if err != nil {
+			t.Fatalf("[%d] unexpected error in safeHTML: %s", i, err)
+		}
+
+		err = tmpl.Execute(buf, v)
+		if err != nil {
+			t.Errorf("[%d] execute template with an escaped string value by safeHTML returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithEscape {
+			t.Errorf("[%d] execute template with an escaped string value by safeHTML, got %v but expected %v", i, buf.String(), this.expectWithEscape)
+		}
+	}
+}
+
+func TestSafeHTMLAttr(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		str                 string
+		tmplStr             string
+		expectWithoutEscape string
+		expectWithEscape    string
+	}{
+		{`href="irc://irc.freenode.net/#golang"`, `<a {{ . }}>irc</a>`, `<a ZgotmplZ>irc</a>`, `<a href="irc://irc.freenode.net/#golang">irc</a>`},
+	} {
+		tmpl, err := template.New("test").Parse(this.tmplStr)
+		if err != nil {
+			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
+			continue
+		}
+
+		buf := new(bytes.Buffer)
+		err = tmpl.Execute(buf, this.str)
+		if err != nil {
+			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithoutEscape {
+			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
+		}
+
+		buf.Reset()
+		v, err := safeHTMLAttr(this.str)
+		if err != nil {
+			t.Fatalf("[%d] unexpected error in safeHTMLAttr: %s", i, err)
+		}
+
+		err = tmpl.Execute(buf, v)
+		if err != nil {
+			t.Errorf("[%d] execute template with an escaped string value by safeHTMLAttr returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithEscape {
+			t.Errorf("[%d] execute template with an escaped string value by safeHTMLAttr, got %v but expected %v", i, buf.String(), this.expectWithEscape)
+		}
+	}
+}
+
+func TestSafeCSS(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		str                 string
+		tmplStr             string
+		expectWithoutEscape string
+		expectWithEscape    string
+	}{
+		{`width: 60px;`, `<div style="{{ . }}"></div>`, `<div style="ZgotmplZ"></div>`, `<div style="width: 60px;"></div>`},
+	} {
+		tmpl, err := template.New("test").Parse(this.tmplStr)
+		if err != nil {
+			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
+			continue
+		}
+
+		buf := new(bytes.Buffer)
+		err = tmpl.Execute(buf, this.str)
+		if err != nil {
+			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithoutEscape {
+			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
+		}
+
+		buf.Reset()
+		v, err := safeCSS(this.str)
+		if err != nil {
+			t.Fatalf("[%d] unexpected error in safeCSS: %s", i, err)
+		}
+
+		err = tmpl.Execute(buf, v)
+		if err != nil {
+			t.Errorf("[%d] execute template with an escaped string value by safeCSS returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithEscape {
+			t.Errorf("[%d] execute template with an escaped string value by safeCSS, got %v but expected %v", i, buf.String(), this.expectWithEscape)
+		}
+	}
+}
+
+// TODO(bep) what is this? Also look above.
+func TestSafeJS(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		str                 string
+		tmplStr             string
+		expectWithoutEscape string
+		expectWithEscape    string
+	}{
+		{`619c16f`, `<script>var x{{ . }};</script>`, `<script>var x"619c16f";</script>`, `<script>var x619c16f;</script>`},
+	} {
+		tmpl, err := template.New("test").Parse(this.tmplStr)
+		if err != nil {
+			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
+			continue
+		}
+
+		buf := new(bytes.Buffer)
+		err = tmpl.Execute(buf, this.str)
+		if err != nil {
+			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithoutEscape {
+			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
+		}
+
+		buf.Reset()
+		v, err := safeJS(this.str)
+		if err != nil {
+			t.Fatalf("[%d] unexpected error in safeJS: %s", i, err)
+		}
+
+		err = tmpl.Execute(buf, v)
+		if err != nil {
+			t.Errorf("[%d] execute template with an escaped string value by safeJS returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithEscape {
+			t.Errorf("[%d] execute template with an escaped string value by safeJS, got %v but expected %v", i, buf.String(), this.expectWithEscape)
+		}
+	}
+}
+
+// TODO(bep) what is this?
+func TestSafeURL(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		str                 string
+		tmplStr             string
+		expectWithoutEscape string
+		expectWithEscape    string
+	}{
+		{`irc://irc.freenode.net/#golang`, `<a href="{{ . }}">IRC</a>`, `<a href="#ZgotmplZ">IRC</a>`, `<a href="irc://irc.freenode.net/#golang">IRC</a>`},
+	} {
+		tmpl, err := template.New("test").Parse(this.tmplStr)
+		if err != nil {
+			t.Errorf("[%d] unable to create new html template %q: %s", i, this.tmplStr, err)
+			continue
+		}
+
+		buf := new(bytes.Buffer)
+		err = tmpl.Execute(buf, this.str)
+		if err != nil {
+			t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithoutEscape {
+			t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape)
+		}
+
+		buf.Reset()
+		v, err := safeURL(this.str)
+		if err != nil {
+			t.Fatalf("[%d] unexpected error in safeURL: %s", i, err)
+		}
+
+		err = tmpl.Execute(buf, v)
+		if err != nil {
+			t.Errorf("[%d] execute template with an escaped string value by safeURL returns unexpected error: %s", i, err)
+		}
+		if buf.String() != this.expectWithEscape {
+			t.Errorf("[%d] execute template with an escaped string value by safeURL, got %v but expected %v", i, buf.String(), this.expectWithEscape)
+		}
+	}
+}
+
+func TestBase64Decode(t *testing.T) {
+	t.Parallel()
+	testStr := "abc123!?$*&()'-=@~"
+	enc := base64.StdEncoding.EncodeToString([]byte(testStr))
+	result, err := base64Decode(enc)
+
+	if err != nil {
+		t.Error("base64Decode returned error:", err)
+	}
+
+	if result != testStr {
+		t.Errorf("base64Decode: got '%s', expected '%s'", result, testStr)
+	}
+
+	_, err = base64Decode(t)
+	if err == nil {
+		t.Error("Expected error from base64Decode")
+	}
+}
+
+func TestBase64Encode(t *testing.T) {
+	t.Parallel()
+	testStr := "YWJjMTIzIT8kKiYoKSctPUB+"
+	dec, err := base64.StdEncoding.DecodeString(testStr)
+
+	if err != nil {
+		t.Error("base64Encode: the DecodeString function of the base64 package returned an error:", err)
+	}
+
+	result, err := base64Encode(string(dec))
+
+	if err != nil {
+		t.Errorf("base64Encode: Can't cast arg '%s' into a string:", testStr)
+	}
+
+	if result != testStr {
+		t.Errorf("base64Encode: got '%s', expected '%s'", result, testStr)
+	}
+
+	_, err = base64Encode(t)
+	if err == nil {
+		t.Error("Expected error from base64Encode")
+	}
+}
+
+func TestMD5(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		input        string
+		expectedHash string
+	}{
+		{"Hello world, gophers!", "b3029f756f98f79e7f1b7f1d1f0dd53b"},
+		{"Lorem ipsum dolor", "06ce65ac476fc656bea3fca5d02cfd81"},
+	} {
+		result, err := md5(this.input)
+		if err != nil {
+			t.Errorf("md5 returned error: %s", err)
+		}
+
+		if result != this.expectedHash {
+			t.Errorf("[%d] md5: expected '%s', got '%s'", i, this.expectedHash, result)
+		}
+	}
+
+	_, err := md5(t)
+	if err == nil {
+		t.Error("Expected error from md5")
+	}
+}
+
+func TestSHA1(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		input        string
+		expectedHash string
+	}{
+		{"Hello world, gophers!", "c8b5b0e33d408246e30f53e32b8f7627a7a649d4"},
+		{"Lorem ipsum dolor", "45f75b844be4d17b3394c6701768daf39419c99b"},
+	} {
+		result, err := sha1(this.input)
+		if err != nil {
+			t.Errorf("sha1 returned error: %s", err)
+		}
+
+		if result != this.expectedHash {
+			t.Errorf("[%d] sha1: expected '%s', got '%s'", i, this.expectedHash, result)
+		}
+	}
+
+	_, err := sha1(t)
+	if err == nil {
+		t.Error("Expected error from sha1")
+	}
+}
+
+func TestSHA256(t *testing.T) {
+	t.Parallel()
+	for i, this := range []struct {
+		input        string
+		expectedHash string
+	}{
+		{"Hello world, gophers!", "6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46"},
+		{"Lorem ipsum dolor", "9b3e1beb7053e0f900a674dd1c99aca3355e1275e1b03d3cb1bc977f5154e196"},
+	} {
+		result, err := sha256(this.input)
+		if err != nil {
+			t.Errorf("sha256 returned error: %s", err)
+		}
+
+		if result != this.expectedHash {
+			t.Errorf("[%d] sha256: expected '%s', got '%s'", i, this.expectedHash, result)
+		}
+	}
+
+	_, err := sha256(t)
+	if err == nil {
+		t.Error("Expected error from sha256")
+	}
+}
+
+func TestReadFile(t *testing.T) {
+	t.Parallel()
+
+	workingDir := "/home/hugo"
+
+	v := viper.New()
+
+	v.Set("workingDir", workingDir)
+
+	f := newTestFuncsterWithViper(v)
+
+	afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755)
+	afero.WriteFile(f.Fs.Source, filepath.Join("/home", "f2.txt"), []byte("f2-content"), 0755)
+
+	for i, this := range []struct {
+		filename string
+		expect   interface{}
+	}{
+		{"", false},
+		{"b", false},
+		{filepath.FromSlash("/f/f1.txt"), "f1-content"},
+		{filepath.FromSlash("f/f1.txt"), "f1-content"},
+		{filepath.FromSlash("../f2.txt"), false},
+	} {
+		result, err := f.readFileFromWorkingDir(this.filename)
+		if b, ok := this.expect.(bool); ok && !b {
+			if err == nil {
+				t.Errorf("[%d] readFile didn't return an expected error", i)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("[%d] readFile failed: %s", i, err)
+				continue
+			}
+			if result != this.expect {
+				t.Errorf("[%d] readFile got %q but expected %q", i, result, this.expect)
+			}
+		}
+	}
+}
+
+func TestPartialCached(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		name    string
+		partial string
+		tmpl    string
+		variant string
+	}{
+		// name and partial should match between test cases.
+		{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . }}`, ""},
+		{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "header"},
+		{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "footer"},
+		{"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "header"},
+	}
+
+	var data struct {
+		Title   string
+		Section string
+		Params  map[string]interface{}
+	}
+
+	data.Title = "**BatMan**"
+	data.Section = "blog"
+	data.Params = map[string]interface{}{"langCode": "en"}
+
+	for i, tc := range testCases {
+		var tmp string
+		if tc.variant != "" {
+			tmp = fmt.Sprintf(tc.tmpl, tc.variant)
+		} else {
+			tmp = tc.tmpl
+		}
+
+		config := newDepsConfig(viper.New())
+
+		config.WithTemplate = func(templ tpl.Template) error {
+			err := templ.AddTemplate("testroot", tmp)
+			if err != nil {
+				return err
+			}
+			err = templ.AddTemplate("partials/"+tc.name, tc.partial)
+			if err != nil {
+				return err
+			}
+
+			return nil
+		}
+
+		de := deps.New(config)
+		require.NoError(t, de.LoadResources())
+
+		buf := new(bytes.Buffer)
+		templ := de.Tmpl.Lookup("testroot")
+		err := templ.Execute(buf, &data)
+		if err != nil {
+			t.Fatalf("[%d] error executing template: %s", i, err)
+		}
+
+		for j := 0; j < 10; j++ {
+			buf2 := new(bytes.Buffer)
+			err := templ.Execute(buf2, nil)
+			if err != nil {
+				t.Fatalf("[%d] error executing template 2nd time: %s", i, err)
+			}
+
+			if !reflect.DeepEqual(buf, buf2) {
+				t.Fatalf("[%d] cached results do not match:\nResult 1:\n%q\nResult 2:\n%q", i, buf, buf2)
+			}
+		}
+	}
+}
+
+func BenchmarkPartial(b *testing.B) {
+	config := newDepsConfig(viper.New())
+	config.WithTemplate = func(templ tpl.Template) error {
+		err := templ.AddTemplate("testroot", `{{ partial "bench1" . }}`)
+		if err != nil {
+			return err
+		}
+		err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	de := deps.New(config)
+	require.NoError(b, de.LoadResources())
+
+	buf := new(bytes.Buffer)
+	tmpl := de.Tmpl.Lookup("testroot")
+
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if err := tmpl.Execute(buf, nil); err != nil {
+			b.Fatalf("error executing template: %s", err)
+		}
+		buf.Reset()
+	}
+}
+
+func BenchmarkPartialCached(b *testing.B) {
+	config := newDepsConfig(viper.New())
+	config.WithTemplate = func(templ tpl.Template) error {
+		err := templ.AddTemplate("testroot", `{{ partialCached "bench1" . }}`)
+		if err != nil {
+			return err
+		}
+		err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	de := deps.New(config)
+	require.NoError(b, de.LoadResources())
+
+	buf := new(bytes.Buffer)
+	tmpl := de.Tmpl.Lookup("testroot")
+
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if err := tmpl.Execute(buf, nil); err != nil {
+			b.Fatalf("error executing template: %s", err)
+		}
+		buf.Reset()
+	}
+}
+
+func newTestFuncster() *templateFuncster {
+	return newTestFuncsterWithViper(viper.New())
+}
+
+func newTestFuncsterWithViper(v *viper.Viper) *templateFuncster {
+	config := newDepsConfig(v)
+	d := deps.New(config)
+
+	if err := d.LoadResources(); err != nil {
+		panic(err)
+	}
+
+	return d.Tmpl.(*GoHTMLTemplate).funcster
+}
+
+func newTestTemplate(t *testing.T, name, template string) *template.Template {
+	config := newDepsConfig(viper.New())
+	config.WithTemplate = func(templ tpl.Template) error {
+		err := templ.AddTemplate(name, template)
+		if err != nil {
+			return err
+		}
+		return nil
+	}
+
+	de := deps.New(config)
+	require.NoError(t, de.LoadResources())
+
+	return de.Tmpl.Lookup(name)
+}
--- /dev/null
+++ b/tpl/tplimpl/template_resources.go
@@ -1,0 +1,253 @@
+// Copyright 2016 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 tplimpl
+
+import (
+	"bytes"
+	"encoding/csv"
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"path/filepath"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/spf13/afero"
+	"github.com/spf13/hugo/config"
+	"github.com/spf13/hugo/helpers"
+	jww "github.com/spf13/jwalterweatherman"
+)
+
+var (
+	remoteURLLock = &remoteLock{m: make(map[string]*sync.Mutex)}
+	resSleep      = time.Second * 2 // if JSON decoding failed sleep for n seconds before retrying
+	resRetries    = 1               // number of retries to load the JSON from URL or local file system
+)
+
+type remoteLock struct {
+	sync.RWMutex
+	m map[string]*sync.Mutex
+}
+
+// URLLock locks an URL during download
+func (l *remoteLock) URLLock(url string) {
+	l.Lock()
+	if _, ok := l.m[url]; !ok {
+		l.m[url] = &sync.Mutex{}
+	}
+	l.Unlock() // call this Unlock before the next lock will be called. NFI why but defer doesn't work.
+	l.m[url].Lock()
+}
+
+// URLUnlock unlocks an URL when the download has been finished. Use only in defer calls.
+func (l *remoteLock) URLUnlock(url string) {
+	l.RLock()
+	defer l.RUnlock()
+	if um, ok := l.m[url]; ok {
+		um.Unlock()
+	}
+}
+
+// getCacheFileID returns the cache ID for a string
+func getCacheFileID(cfg config.Provider, id string) string {
+	return cfg.GetString("cacheDir") + url.QueryEscape(id)
+}
+
+// resGetCache returns the content for an ID from the file cache or an error
+// if the file is not found returns nil,nil
+func resGetCache(id string, fs afero.Fs, cfg config.Provider, ignoreCache bool) ([]byte, error) {
+	if ignoreCache {
+		return nil, nil
+	}
+	fID := getCacheFileID(cfg, id)
+	isExists, err := helpers.Exists(fID, fs)
+	if err != nil {
+		return nil, err
+	}
+	if !isExists {
+		return nil, nil
+	}
+
+	return afero.ReadFile(fs, fID)
+
+}
+
+// resWriteCache writes bytes to an ID into the file cache
+func resWriteCache(id string, c []byte, fs afero.Fs, cfg config.Provider, ignoreCache bool) error {
+	if ignoreCache {
+		return nil
+	}
+	fID := getCacheFileID(cfg, id)
+	f, err := fs.Create(fID)
+	if err != nil {
+		return errors.New("Error: " + err.Error() + ". Failed to create file: " + fID)
+	}
+	defer f.Close()
+	n, err := f.Write(c)
+	if n == 0 {
+		return errors.New("No bytes written to file: " + fID)
+	}
+	if err != nil {
+		return errors.New("Error: " + err.Error() + ". Failed to write to file: " + fID)
+	}
+	return nil
+}
+
+func resDeleteCache(id string, fs afero.Fs, cfg config.Provider) error {
+	return fs.Remove(getCacheFileID(cfg, id))
+}
+
+// resGetRemote loads the content of a remote file. This method is thread safe.
+func resGetRemote(url string, fs afero.Fs, cfg config.Provider, hc *http.Client) ([]byte, error) {
+	c, err := resGetCache(url, fs, cfg, cfg.GetBool("ignoreCache"))
+	if c != nil && err == nil {
+		return c, nil
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	// avoid race condition with locks, block other goroutines if the current url is processing
+	remoteURLLock.URLLock(url)
+	defer func() { remoteURLLock.URLUnlock(url) }()
+
+	// avoid multiple locks due to calling resGetCache twice
+	c, err = resGetCache(url, fs, cfg, cfg.GetBool("ignoreCache"))
+	if c != nil && err == nil {
+		return c, nil
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	jww.INFO.Printf("Downloading: %s ...", url)
+	res, err := hc.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	c, err = ioutil.ReadAll(res.Body)
+	res.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+	err = resWriteCache(url, c, fs, cfg, cfg.GetBool("ignoreCache"))
+	if err != nil {
+		return nil, err
+	}
+	jww.INFO.Printf("... and cached to: %s", getCacheFileID(cfg, url))
+	return c, nil
+}
+
+// resGetLocal loads the content of a local file
+func resGetLocal(url string, fs afero.Fs, cfg config.Provider) ([]byte, error) {
+	filename := filepath.Join(cfg.GetString("workingDir"), url)
+	if e, err := helpers.Exists(filename, fs); !e {
+		return nil, err
+	}
+
+	return afero.ReadFile(fs, filename)
+
+}
+
+// resGetResource loads the content of a local or remote file
+func (t *templateFuncster) resGetResource(url string) ([]byte, error) {
+	if url == "" {
+		return nil, nil
+	}
+	if strings.Contains(url, "://") {
+		return resGetRemote(url, t.Fs.Source, t.Cfg, http.DefaultClient)
+	}
+	return resGetLocal(url, t.Fs.Source, t.Cfg)
+}
+
+// getJSON expects one or n-parts of a URL to a resource which can either be a local or a remote one.
+// If you provide multiple parts they will be joined together to the final URL.
+// GetJSON returns nil or parsed JSON to use in a short code.
+func (t *templateFuncster) getJSON(urlParts ...string) interface{} {
+	var v interface{}
+	url := strings.Join(urlParts, "")
+
+	for i := 0; i <= resRetries; i++ {
+		c, err := t.resGetResource(url)
+		if err != nil {
+			jww.ERROR.Printf("Failed to get json resource %s with error message %s", url, err)
+			return nil
+		}
+
+		err = json.Unmarshal(c, &v)
+		if err != nil {
+			jww.ERROR.Printf("Cannot read json from resource %s with error message %s", url, err)
+			jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep)
+			time.Sleep(resSleep)
+			resDeleteCache(url, t.Fs.Source, t.Cfg)
+			continue
+		}
+		break
+	}
+	return v
+}
+
+// parseCSV parses bytes of CSV data into a slice slice string or an error
+func parseCSV(c []byte, sep string) ([][]string, error) {
+	if len(sep) != 1 {
+		return nil, errors.New("Incorrect length of csv separator: " + sep)
+	}
+	b := bytes.NewReader(c)
+	r := csv.NewReader(b)
+	rSep := []rune(sep)
+	r.Comma = rSep[0]
+	r.FieldsPerRecord = 0
+	return r.ReadAll()
+}
+
+// getCSV expects a data separator and one or n-parts of a URL to a resource which
+// can either be a local or a remote one.
+// The data separator can be a comma, semi-colon, pipe, etc, but only one character.
+// If you provide multiple parts for the URL they will be joined together to the final URL.
+// GetCSV returns nil or a slice slice to use in a short code.
+func (t *templateFuncster) getCSV(sep string, urlParts ...string) [][]string {
+	var d [][]string
+	url := strings.Join(urlParts, "")
+
+	var clearCacheSleep = func(i int, u string) {
+		jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep)
+		time.Sleep(resSleep)
+		resDeleteCache(url, t.Fs.Source, t.Cfg)
+	}
+
+	for i := 0; i <= resRetries; i++ {
+		c, err := t.resGetResource(url)
+
+		if err == nil && !bytes.Contains(c, []byte(sep)) {
+			err = errors.New("Cannot find separator " + sep + " in CSV.")
+		}
+
+		if err != nil {
+			jww.ERROR.Printf("Failed to read csv resource %s with error message %s", url, err)
+			clearCacheSleep(i, url)
+			continue
+		}
+
+		if d, err = parseCSV(c, sep); err != nil {
+			jww.ERROR.Printf("Failed to parse csv file %s with error message %s", url, err)
+			clearCacheSleep(i, url)
+			continue
+		}
+		break
+	}
+	return d
+}
--- /dev/null
+++ b/tpl/tplimpl/template_resources_test.go
@@ -1,0 +1,302 @@
+// Copyright 2016 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 tplimpl
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"strings"
+	"testing"
+
+	"github.com/spf13/afero"
+	"github.com/spf13/hugo/helpers"
+	"github.com/spf13/hugo/hugofs"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestScpCache(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		path    string
+		content []byte
+		ignore  bool
+	}{
+		{"http://Foo.Bar/foo_Bar-Foo", []byte(`T€st Content 123`), false},
+		{"fOO,bar:foo%bAR", []byte(`T€st Content 123 fOO,bar:foo%bAR`), false},
+		{"FOo/BaR.html", []byte(`FOo/BaR.html T€st Content 123`), false},
+		{"трям/трям", []byte(`T€st трям/трям Content 123`), false},
+		{"은행", []byte(`T€st C은행ontent 123`), false},
+		{"Банковский кассир", []byte(`Банковский кассир T€st Content 123`), false},
+		{"Банковский кассир", []byte(`Банковский кассир T€st Content 456`), true},
+	}
+
+	fs := new(afero.MemMapFs)
+
+	for _, test := range tests {
+		cfg := viper.New()
+		c, err := resGetCache(test.path, fs, cfg, test.ignore)
+		if err != nil {
+			t.Errorf("Error getting cache: %s", err)
+		}
+		if c != nil {
+			t.Errorf("There is content where there should not be anything: %s", string(c))
+		}
+
+		err = resWriteCache(test.path, test.content, fs, cfg, test.ignore)
+		if err != nil {
+			t.Errorf("Error writing cache: %s", err)
+		}
+
+		c, err = resGetCache(test.path, fs, cfg, test.ignore)
+		if err != nil {
+			t.Errorf("Error getting cache after writing: %s", err)
+		}
+		if test.ignore {
+			if c != nil {
+				t.Errorf("Cache ignored but content is not nil: %s", string(c))
+			}
+		} else {
+			if !bytes.Equal(c, test.content) {
+				t.Errorf("\nExpected: %s\nActual: %s\n", string(test.content), string(c))
+			}
+		}
+	}
+}
+
+func TestScpGetLocal(t *testing.T) {
+	t.Parallel()
+	v := viper.New()
+	fs := hugofs.NewMem(v)
+	ps := helpers.FilePathSeparator
+
+	tests := []struct {
+		path    string
+		content []byte
+	}{
+		{"testpath" + ps + "test.txt", []byte(`T€st Content 123 fOO,bar:foo%bAR`)},
+		{"FOo" + ps + "BaR.html", []byte(`FOo/BaR.html T€st Content 123`)},
+		{"трям" + ps + "трям", []byte(`T€st трям/трям Content 123`)},
+		{"은행", []byte(`T€st C은행ontent 123`)},
+		{"Банковский кассир", []byte(`Банковский кассир T€st Content 123`)},
+	}
+
+	for _, test := range tests {
+		r := bytes.NewReader(test.content)
+		err := helpers.WriteToDisk(test.path, r, fs.Source)
+		if err != nil {
+			t.Error(err)
+		}
+
+		c, err := resGetLocal(test.path, fs.Source, v)
+		if err != nil {
+			t.Errorf("Error getting resource content: %s", err)
+		}
+		if !bytes.Equal(c, test.content) {
+			t.Errorf("\nExpected: %s\nActual: %s\n", string(test.content), string(c))
+		}
+	}
+
+}
+
+func getTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*httptest.Server, *http.Client) {
+	testServer := httptest.NewServer(http.HandlerFunc(handler))
+	client := &http.Client{
+		Transport: &http.Transport{Proxy: func(r *http.Request) (*url.URL, error) {
+			// Remove when https://github.com/golang/go/issues/13686 is fixed
+			r.Host = "gohugo.io"
+			return url.Parse(testServer.URL)
+		}},
+	}
+	return testServer, client
+}
+
+func TestScpGetRemote(t *testing.T) {
+	t.Parallel()
+	fs := new(afero.MemMapFs)
+
+	tests := []struct {
+		path    string
+		content []byte
+		ignore  bool
+	}{
+		{"http://Foo.Bar/foo_Bar-Foo", []byte(`T€st Content 123`), false},
+		{"http://Doppel.Gänger/foo_Bar-Foo", []byte(`T€st Cont€nt 123`), false},
+		{"http://Doppel.Gänger/Fizz_Bazz-Foo", []byte(`T€st Банковский кассир Cont€nt 123`), false},
+		{"http://Doppel.Gänger/Fizz_Bazz-Bar", []byte(`T€st Банковский кассир Cont€nt 456`), true},
+	}
+
+	for _, test := range tests {
+
+		srv, cl := getTestServer(func(w http.ResponseWriter, r *http.Request) {
+			w.Write(test.content)
+		})
+		defer func() { srv.Close() }()
+
+		cfg := viper.New()
+
+		c, err := resGetRemote(test.path, fs, cfg, cl)
+		if err != nil {
+			t.Errorf("Error getting resource content: %s", err)
+		}
+		if !bytes.Equal(c, test.content) {
+			t.Errorf("\nNet Expected: %s\nNet Actual: %s\n", string(test.content), string(c))
+		}
+		cc, cErr := resGetCache(test.path, fs, cfg, test.ignore)
+		if cErr != nil {
+			t.Error(cErr)
+		}
+		if test.ignore {
+			if cc != nil {
+				t.Errorf("Cache ignored but content is not nil: %s", string(cc))
+			}
+		} else {
+			if !bytes.Equal(cc, test.content) {
+				t.Errorf("\nCache Expected: %s\nCache Actual: %s\n", string(test.content), string(cc))
+			}
+		}
+	}
+}
+
+func TestParseCSV(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		csv []byte
+		sep string
+		exp string
+		err bool
+	}{
+		{[]byte("a,b,c\nd,e,f\n"), "", "", true},
+		{[]byte("a,b,c\nd,e,f\n"), "~/", "", true},
+		{[]byte("a,b,c\nd,e,f"), "|", "a,b,cd,e,f", false},
+		{[]byte("q,w,e\nd,e,f"), ",", "qwedef", false},
+		{[]byte("a|b|c\nd|e|f|g"), "|", "abcdefg", true},
+		{[]byte("z|y|c\nd|e|f"), "|", "zycdef", false},
+	}
+	for _, test := range tests {
+		csv, err := parseCSV(test.csv, test.sep)
+		if test.err && err == nil {
+			t.Error("Expecting an error")
+		}
+		if test.err {
+			continue
+		}
+		if !test.err && err != nil {
+			t.Error(err)
+		}
+
+		act := ""
+		for _, v := range csv {
+			act = act + strings.Join(v, "")
+		}
+
+		if act != test.exp {
+			t.Errorf("\nExpected: %s\nActual: %s\n%#v\n", test.exp, act, csv)
+		}
+
+	}
+}
+
+func TestGetJSONFailParse(t *testing.T) {
+	t.Parallel()
+
+	f := newTestFuncster()
+
+	reqCount := 0
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if reqCount > 0 {
+			w.Header().Add("Content-type", "application/json")
+			fmt.Fprintln(w, `{"gomeetup":["Sydney", "San Francisco", "Stockholm"]}`)
+		} else {
+			w.WriteHeader(http.StatusInternalServerError)
+			fmt.Fprintln(w, `ERROR 500`)
+		}
+		reqCount++
+	}))
+	defer ts.Close()
+	url := ts.URL + "/test.json"
+
+	want := map[string]interface{}{"gomeetup": []interface{}{"Sydney", "San Francisco", "Stockholm"}}
+	have := f.getJSON(url)
+	assert.NotNil(t, have)
+	if have != nil {
+		assert.EqualValues(t, want, have)
+	}
+}
+
+func TestGetCSVFailParseSep(t *testing.T) {
+	t.Parallel()
+	f := newTestFuncster()
+
+	reqCount := 0
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if reqCount > 0 {
+			w.Header().Add("Content-type", "application/json")
+			fmt.Fprintln(w, `gomeetup,city`)
+			fmt.Fprintln(w, `yes,Sydney`)
+			fmt.Fprintln(w, `yes,San Francisco`)
+			fmt.Fprintln(w, `yes,Stockholm`)
+		} else {
+			w.WriteHeader(http.StatusInternalServerError)
+			fmt.Fprintln(w, `ERROR 500`)
+		}
+		reqCount++
+	}))
+	defer ts.Close()
+	url := ts.URL + "/test.csv"
+
+	want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
+	have := f.getCSV(",", url)
+	assert.NotNil(t, have)
+	if have != nil {
+		assert.EqualValues(t, want, have)
+	}
+}
+
+func TestGetCSVFailParse(t *testing.T) {
+	t.Parallel()
+
+	f := newTestFuncster()
+
+	reqCount := 0
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Add("Content-type", "application/json")
+		if reqCount > 0 {
+			fmt.Fprintln(w, `gomeetup,city`)
+			fmt.Fprintln(w, `yes,Sydney`)
+			fmt.Fprintln(w, `yes,San Francisco`)
+			fmt.Fprintln(w, `yes,Stockholm`)
+		} else {
+			fmt.Fprintln(w, `gomeetup,city`)
+			fmt.Fprintln(w, `yes,Sydney,Bondi,`) // wrong number of fields in line
+			fmt.Fprintln(w, `yes,San Francisco`)
+			fmt.Fprintln(w, `yes,Stockholm`)
+		}
+		reqCount++
+	}))
+	defer ts.Close()
+	url := ts.URL + "/test.csv"
+
+	want := [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}
+	have := f.getCSV(",", url)
+	assert.NotNil(t, have)
+	if have != nil {
+		assert.EqualValues(t, want, have)
+	}
+}
--- /dev/null
+++ b/tpl/tplimpl/template_test.go
@@ -1,0 +1,347 @@
+// Copyright 2016 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 tplimpl
+
+import (
+	"bytes"
+	"errors"
+	"html/template"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"testing"
+
+	"github.com/spf13/afero"
+	"github.com/spf13/hugo/deps"
+
+	"github.com/spf13/hugo/tpl"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/require"
+)
+
+// Some tests for Issue #1178 -- Ace
+func TestAceTemplates(t *testing.T) {
+	t.Parallel()
+
+	for i, this := range []struct {
+		basePath     string
+		innerPath    string
+		baseContent  string
+		innerContent string
+		expect       string
+		expectErr    int
+	}{
+		{"", filepath.FromSlash("_default/single.ace"), "", "{{ . }}", "DATA", 0},
+		{filepath.FromSlash("_default/baseof.ace"), filepath.FromSlash("_default/single.ace"),
+			`= content main
+  h2 This is a content named "main" of an inner template. {{ . }}`,
+			`= doctype html
+html lang=en
+  head
+    meta charset=utf-8
+    title Base and Inner Template
+  body
+    h1 This is a base template {{ . }}
+    = yield main`, `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Base and Inner Template</title></head><body><h1>This is a base template DATA</h1></body></html>`, 0},
+	} {
+
+		for _, root := range []string{"", os.TempDir()} {
+
+			basePath := this.basePath
+			innerPath := this.innerPath
+
+			if basePath != "" && root != "" {
+				basePath = filepath.Join(root, basePath)
+			}
+
+			if innerPath != "" && root != "" {
+				innerPath = filepath.Join(root, innerPath)
+			}
+
+			d := "DATA"
+
+			config := newDepsConfig(viper.New())
+			config.WithTemplate = func(templ tpl.Template) error {
+				return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
+					[]byte(this.baseContent), []byte(this.innerContent))
+			}
+
+			a := deps.New(config)
+
+			if err := a.LoadResources(); err != nil {
+				t.Fatal(err)
+			}
+
+			templ := a.Tmpl.(*GoHTMLTemplate)
+
+			if len(templ.errors) > 0 && this.expectErr == 0 {
+				t.Errorf("Test %d with root '%s' errored: %v", i, root, templ.errors)
+			} else if len(templ.errors) == 0 && this.expectErr == 1 {
+				t.Errorf("#1 Test %d with root '%s' should have errored", i, root)
+			}
+
+			var buff bytes.Buffer
+			err := a.Tmpl.ExecuteTemplate(&buff, "mytemplate.html", d)
+
+			if err != nil && this.expectErr == 0 {
+				t.Errorf("Test %d with root '%s' errored: %s", i, root, err)
+			} else if err == nil && this.expectErr == 2 {
+				t.Errorf("#2 Test with root '%s' %d should have errored", root, i)
+			} else {
+				result := buff.String()
+				if result != this.expect {
+					t.Errorf("Test %d  with root '%s' got\n%s\nexpected\n%s", i, root, result, this.expect)
+				}
+			}
+
+		}
+	}
+
+}
+
+func isAtLeastGo16() bool {
+	version := runtime.Version()
+	return strings.Contains(version, "1.6") || strings.Contains(version, "1.7")
+}
+
+func TestAddTemplateFileWithMaster(t *testing.T) {
+	t.Parallel()
+
+	if !isAtLeastGo16() {
+		t.Skip("This test only runs on Go >= 1.6")
+	}
+
+	for i, this := range []struct {
+		masterTplContent  string
+		overlayTplContent string
+		writeSkipper      int
+		expect            interface{}
+	}{
+		{`A{{block "main" .}}C{{end}}C`, `{{define "main"}}B{{end}}`, 0, "ABC"},
+		{`A{{block "main" .}}C{{end}}C{{block "sub" .}}D{{end}}E`, `{{define "main"}}B{{end}}`, 0, "ABCDE"},
+		{`A{{block "main" .}}C{{end}}C{{block "sub" .}}D{{end}}E`, `{{define "main"}}B{{end}}{{define "sub"}}Z{{end}}`, 0, "ABCZE"},
+		{`tpl`, `tpl`, 1, false},
+		{`tpl`, `tpl`, 2, false},
+		{`{{.0.E}}`, `tpl`, 0, false},
+		{`tpl`, `{{.0.E}}`, 0, false},
+	} {
+
+		overlayTplName := "ot"
+		masterTplName := "mt"
+		finalTplName := "tp"
+
+		config := newDepsConfig(viper.New())
+		config.WithTemplate = func(templ tpl.Template) error {
+
+			err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
+
+			if b, ok := this.expect.(bool); ok && !b {
+				if err == nil {
+					t.Errorf("[%d] AddTemplateFileWithMaster didn't return an expected error", i)
+				}
+			} else {
+
+				if err != nil {
+					t.Errorf("[%d] AddTemplateFileWithMaster failed: %s", i, err)
+					return nil
+				}
+
+				resultTpl := templ.Lookup(finalTplName)
+
+				if resultTpl == nil {
+					t.Errorf("[%d] AddTemplateFileWithMaster: Result template not found", i)
+					return nil
+				}
+
+				var b bytes.Buffer
+				err := resultTpl.Execute(&b, nil)
+
+				if err != nil {
+					t.Errorf("[%d] AddTemplateFileWithMaster execute failed: %s", i, err)
+					return nil
+				}
+				resultContent := b.String()
+
+				if resultContent != this.expect {
+					t.Errorf("[%d] AddTemplateFileWithMaster got \n%s but expected \n%v", i, resultContent, this.expect)
+				}
+			}
+
+			return nil
+		}
+
+		if this.writeSkipper != 1 {
+			afero.WriteFile(config.Fs.Source, masterTplName, []byte(this.masterTplContent), 0644)
+		}
+		if this.writeSkipper != 2 {
+			afero.WriteFile(config.Fs.Source, overlayTplName, []byte(this.overlayTplContent), 0644)
+		}
+
+		deps.New(config)
+
+	}
+
+}
+
+// A Go stdlib test for linux/arm. Will remove later.
+// See #1771
+func TestBigIntegerFunc(t *testing.T) {
+	t.Parallel()
+	var func1 = func(v int64) error {
+		return nil
+	}
+	var funcs = map[string]interface{}{
+		"A": func1,
+	}
+
+	tpl, err := template.New("foo").Funcs(funcs).Parse("{{ A 3e80 }}")
+	if err != nil {
+		t.Fatal("Parse failed:", err)
+	}
+	err = tpl.Execute(ioutil.Discard, "foo")
+
+	if err == nil {
+		t.Fatal("Execute should have failed")
+	}
+
+	t.Log("Got expected error:", err)
+
+}
+
+// A Go stdlib test for linux/arm. Will remove later.
+// See #1771
+type BI struct {
+}
+
+func (b BI) A(v int64) error {
+	return nil
+}
+func TestBigIntegerMethod(t *testing.T) {
+	t.Parallel()
+
+	data := &BI{}
+
+	tpl, err := template.New("foo2").Parse("{{ .A 3e80 }}")
+	if err != nil {
+		t.Fatal("Parse failed:", err)
+	}
+	err = tpl.ExecuteTemplate(ioutil.Discard, "foo2", data)
+
+	if err == nil {
+		t.Fatal("Execute should have failed")
+	}
+
+	t.Log("Got expected error:", err)
+
+}
+
+// Test for bugs discovered by https://github.com/dvyukov/go-fuzz
+func TestTplGoFuzzReports(t *testing.T) {
+	t.Parallel()
+
+	// The following test case(s) also fail
+	// See https://github.com/golang/go/issues/10634
+	//{"{{ seq 433937734937734969526500969526500 }}", 2}}
+
+	for i, this := range []struct {
+		data      string
+		expectErr int
+	}{
+		// Issue #1089
+		//{"{{apply .C \"first\" }}", 2},
+		// Issue #1090
+		{"{{ slicestr \"000000\" 10}}", 2},
+		// Issue #1091
+		//{"{{apply .C \"first\" 0 0 0}}", 2},
+		{"{{seq 3e80}}", 2},
+		// Issue #1095
+		{"{{apply .C \"urlize\" " +
+			"\".\"}}", 2}} {
+
+		d := &Data{
+			A: 42,
+			B: "foo",
+			C: []int{1, 2, 3},
+			D: map[int]string{1: "foo", 2: "bar"},
+			E: Data1{42, "foo"},
+			F: []string{"a", "b", "c"},
+			G: []string{"a", "b", "c", "d", "e"},
+			H: "a,b,c,d,e,f",
+		}
+
+		config := newDepsConfig(viper.New())
+
+		config.WithTemplate = func(templ tpl.Template) error {
+			return templ.AddTemplate("fuzz", this.data)
+		}
+
+		de := deps.New(config)
+		require.NoError(t, de.LoadResources())
+
+		templ := de.Tmpl.(*GoHTMLTemplate)
+
+		if len(templ.errors) > 0 && this.expectErr == 0 {
+			t.Errorf("Test %d errored: %v", i, templ.errors)
+		} else if len(templ.errors) == 0 && this.expectErr == 1 {
+			t.Errorf("#1 Test %d should have errored", i)
+		}
+
+		err := de.Tmpl.ExecuteTemplate(ioutil.Discard, "fuzz", d)
+
+		if err != nil && this.expectErr == 0 {
+			t.Fatalf("Test %d errored: %s", i, err)
+		} else if err == nil && this.expectErr == 2 {
+			t.Fatalf("#2 Test %d should have errored", i)
+		}
+
+	}
+}
+
+type Data struct {
+	A int
+	B string
+	C []int
+	D map[int]string
+	E Data1
+	F []string
+	G []string
+	H string
+}
+
+type Data1 struct {
+	A int
+	B string
+}
+
+func (Data1) Q() string {
+	return "foo"
+}
+
+func (Data1) W() (string, error) {
+	return "foo", nil
+}
+
+func (Data1) E() (string, error) {
+	return "foo", errors.New("Data.E error")
+}
+
+func (Data1) R(v int) (string, error) {
+	return "foo", nil
+}
+
+func (Data1) T(s string) (string, error) {
+	return s, nil
+}
--- a/tplapi/template.go
+++ /dev/null
@@ -1,28 +1,0 @@
-package tplapi
-
-import (
-	"html/template"
-	"io"
-)
-
-// TODO(bep) make smaller
-// TODO(bep) consider putting this into /tpl and the implementation in /tpl/tplimpl or something
-type Template interface {
-	ExecuteTemplate(wr io.Writer, name string, data interface{}) error
-	ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML
-	Lookup(name string) *template.Template
-	Templates() []*template.Template
-	New(name string) *template.Template
-	GetClone() *template.Template
-	LoadTemplates(absPath string)
-	LoadTemplatesWithPrefix(absPath, prefix string)
-	AddTemplate(name, tpl string) error
-	AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error
-	AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error
-	AddInternalTemplate(prefix, name, tpl string) error
-	AddInternalShortcode(name, tpl string) error
-	Partial(name string, contextList ...interface{}) template.HTML
-	PrintErrors()
-	Funcs(funcMap template.FuncMap)
-	MarkReady()
-}