ref: c507e2717df7dd4b870478033bc5ece0b039a8c4
parent: 93ca7c9e958e34469a337e4efcc7c75774ec50fd
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
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">««</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">«</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">»</span></a>- </li>
- {{ with $pag.Last }}- <li>
- <a href="{{ .URL }}" aria-label="Last"><span aria-hidden="true">»»</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("<b>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 <cathal@foo.bar>" | safeHTML}}-htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>"}}-htmlUnescape 1: {{htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}-htmlUnescape 2: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape | safeHTML}}-htmlUnescape 3: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape }}-htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlUnescape | safeHTML }}-htmlUnescape 5: {{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | 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 & The Sunshine Band <cathal@foo.bar>
-htmlEscape 2: Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;
-htmlUnescape 1: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
-htmlUnescape 2: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
-htmlUnescape 3: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
-htmlUnescape 4: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
-htmlUnescape 5: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
-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&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&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>`, `{{ . }}`, `<div></div>`, `<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">««</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">«</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">»</span></a>+ </li>
+ {{ with $pag.Last }}+ <li>
+ <a href="{{ .URL }}" aria-label="Last"><span aria-hidden="true">»»</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("<b>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 <cathal@foo.bar>" | safeHTML}}+htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>"}}+htmlUnescape 1: {{htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}+htmlUnescape 2: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape | safeHTML}}+htmlUnescape 3: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape }}+htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlUnescape | safeHTML }}+htmlUnescape 5: {{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | 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 & The Sunshine Band <cathal@foo.bar>
+htmlEscape 2: Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;
+htmlUnescape 1: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
+htmlUnescape 2: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
+htmlUnescape 3: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
+htmlUnescape 4: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
+htmlUnescape 5: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
+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&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&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>`, `{{ . }}`, `<div></div>`, `<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()
-}
--
⑨