ref: c507e2717df7dd4b870478033bc5ece0b039a8c4
parent: 93ca7c9e958e34469a337e4efcc7c75774ec50fd
author: Bjørn Erik Pedersen <[email protected]>
date: Fri Feb 17 08:30:50 EST 2017
tpl: Refactor package Now: * The template API lives in /tpl * The rest lives in /tpl/tplimpl This is bound te be more improved in the future. Updates #2701
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -8,7 +8,7 @@
"github.com/spf13/hugo/config"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl"
jww "github.com/spf13/jwalterweatherman"
)
@@ -20,7 +20,7 @@
Log *jww.Notepad `json:"-"`
// The templates to use.
- Tmpl tplapi.Template `json:"-"`
+ Tmpl tpl.Template `json:"-"`
// The file systems to use.
Fs *hugofs.Fs `json:"-"`
@@ -40,7 +40,7 @@
Language *helpers.Language
templateProvider ResourceProvider
- WithTemplate func(templ tplapi.Template) error `json:"-"`
+ WithTemplate func(templ tpl.Template) error `json:"-"`
translationProvider ResourceProvider
}
@@ -147,7 +147,7 @@
// Template handling.
TemplateProvider ResourceProvider
- WithTemplate func(templ tplapi.Template) error
+ WithTemplate func(templ tpl.Template) error
// i18n handling.
TranslationProvider ResourceProvider
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -25,7 +25,7 @@
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl"
"github.com/stretchr/testify/require"
)
@@ -335,7 +335,7 @@
th = testHelper{cfg}
)
- withTemplate := func(templ tplapi.Template) error {
+ withTemplate := func(templ tpl.Template) error {
templ.Funcs(tweetFuncMap)
return nil
}
@@ -390,7 +390,7 @@
om:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
`(?s)<blockquote class="instagram-media" data-instgrm-version="7" style=" background:#FFF; border:0; .*<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
},
-den; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
+den; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
ow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`,
`(?s)<blockquote class="instagram-media" data-instgrm-version="7" style=" background:#FFF; border:0; .*<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>`,
},
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -24,7 +24,7 @@
"github.com/spf13/hugo/i18n"
"github.com/spf13/hugo/tpl"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl/tplimpl"
)
// HugoSites represents the sites to build. Each site represents a language.
@@ -72,7 +72,7 @@
func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
if cfg.TemplateProvider == nil {
- cfg.TemplateProvider = tpl.DefaultTemplateProvider
+ cfg.TemplateProvider = tplimpl.DefaultTemplateProvider
}
if cfg.TranslationProvider == nil {
@@ -121,8 +121,8 @@
return newHugoSites(cfg, sites...)
}
-func (s *Site) withSiteTemplates(withTemplates ...func(templ tplapi.Template) error) func(templ tplapi.Template) error {
- return func(templ tplapi.Template) error {
+func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.Template) error) func(templ tpl.Template) error {
+ return func(templ tpl.Template) error {
templ.LoadTemplates(s.absLayoutDir())
if s.hasTheme() {
templ.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")
@@ -191,7 +191,7 @@
h.Sites[i] = s.reset()
}
- tpl.ResetCaches()
+ tplimpl.ResetCaches()
}
func (h *HugoSites) createSitesFromConfig() error {
@@ -553,7 +553,7 @@
return h.Sites[0].AllPages
}
-func handleShortcodes(p *Page, t tplapi.Template, rawContentCopy []byte) ([]byte, error) {
+func handleShortcodes(p *Page, t tpl.Template, rawContentCopy []byte) ([]byte, error) {
if len(p.contentShortCodes) > 0 {
p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.contentShortCodes), p.BaseFileName())
shortcodes, err := executeShortcodeFuncMap(p.contentShortCodes)
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -26,7 +26,7 @@
bp "github.com/spf13/hugo/bufferpool"
"github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl"
)
// ShortcodeWithPage is the "." context in a shortcode template.
@@ -541,7 +541,7 @@
return source, nil
}
-func getShortcodeTemplate(name string, t tplapi.Template) *template.Template {
+func getShortcodeTemplate(name string, t tpl.Template) *template.Template {
if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
return x
}
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -25,12 +25,12 @@
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/source"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl"
"github.com/stretchr/testify/require"
)
// TODO(bep) remove
-func pageFromString(in, filename string, withTemplate ...func(templ tplapi.Template) error) (*Page, error) {
+func pageFromString(in, filename string, withTemplate ...func(templ tpl.Template) error) (*Page, error) {
s := newTestSite(nil)
if len(withTemplate) > 0 {
// Have to create a new site
@@ -47,11 +47,11 @@
return s.NewPageFrom(strings.NewReader(in), filename)
}
-func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error) {
+func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error) {
CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
}
-func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error, expectError bool) {
+func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error, expectError bool) {
cfg, fs := newTestCfg()
@@ -100,7 +100,7 @@
// Issue #929
func TestHyphenatedSC(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
return nil
}
@@ -111,7 +111,7 @@
// Issue #1753
func TestNoTrailingNewline(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
return nil
}
@@ -121,7 +121,7 @@
func TestPositionalParamSC(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
return nil
}
@@ -135,7 +135,7 @@
func TestPositionalParamIndexOutOfBounds(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
return nil
}
@@ -146,7 +146,7 @@
func TestNamedParamSC(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
return nil
}
@@ -161,7 +161,7 @@
// Issue #2294
func TestNestedNamedMissingParam(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("acc.html", `<div class="acc">{{ .Inner }}</div>`)
tem.AddInternalShortcode("div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
tem.AddInternalShortcode("div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
@@ -174,7 +174,7 @@
func TestIsNamedParamsSC(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("byposition.html", `<div id="{{ .Get 0 }}">`)
tem.AddInternalShortcode("byname.html", `<div id="{{ .Get "id" }}">`)
tem.AddInternalShortcode("ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
@@ -190,7 +190,7 @@
func TestInnerSC(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
@@ -201,7 +201,7 @@
func TestInnerSCWithMarkdown(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
@@ -215,7 +215,7 @@
func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
@@ -246,7 +246,7 @@
func TestNestedSC(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
return nil
@@ -258,7 +258,7 @@
func TestNestedComplexSC(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`)
tem.AddInternalShortcode("column.html", `-col-{{.Inner }}-colStop-`)
tem.AddInternalShortcode("aside.html", `-aside-{{ .Inner }}-asideStop-`)
@@ -274,7 +274,7 @@
func TestParentShortcode(t *testing.T) {
t.Parallel()
- wt := func(tem tplapi.Template) error {
+ wt := func(tem tpl.Template) error {
tem.AddInternalShortcode("r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
tem.AddInternalShortcode("r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
tem.AddInternalShortcode("r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
@@ -342,7 +342,7 @@
fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
} {
- p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error {
+ p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
templ.AddInternalShortcode("tag.html", `tag`)
templ.AddInternalShortcode("sc1.html", `sc1`)
templ.AddInternalShortcode("sc2.html", `sc2`)
@@ -514,7 +514,7 @@
sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)}
}
- addTemplates := func(templ tplapi.Template) error {
+ addTemplates := func(templ tpl.Template) error {
templ.AddTemplate("_default/single.html", "{{.Content}}")
templ.AddInternalShortcode("b.html", `b`)
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -40,7 +40,7 @@
"github.com/spf13/hugo/parser"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/target"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/transform"
"github.com/spf13/nitro"
"github.com/spf13/viper"
@@ -149,7 +149,7 @@
// NewSiteDefaultLang creates a new site in the default language.
// The site will have a template system loaded and ready to use.
// Note: This is mainly used in single site tests.
-func NewSiteDefaultLang(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
+func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) (*Site, error) {
v := viper.New()
loadDefaultSettingsFor(v)
return newSiteForLang(helpers.NewDefaultLanguage(v), withTemplate...)
@@ -158,7 +158,7 @@
// NewEnglishSite creates a new site in English language.
// The site will have a template system loaded and ready to use.
// Note: This is mainly used in single site tests.
-func NewEnglishSite(withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
+func NewEnglishSite(withTemplate ...func(templ tpl.Template) error) (*Site, error) {
v := viper.New()
loadDefaultSettingsFor(v)
return newSiteForLang(helpers.NewLanguage("en", v), withTemplate...)
@@ -165,8 +165,8 @@
}
// newSiteForLang creates a new site in the given language.
-func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tplapi.Template) error) (*Site, error) {
- withTemplates := func(templ tplapi.Template) error {
+func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tpl.Template) error) (*Site, error) {
+ withTemplates := func(templ tpl.Template) error {
for _, wt := range withTemplate {
if err := wt(templ); err != nil {
return err
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -19,7 +19,7 @@
"reflect"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl"
)
const sitemapTemplate = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@@ -48,7 +48,7 @@
depsCfg := deps.DepsCfg{Fs: fs, Cfg: cfg}
if !internal {
- depsCfg.WithTemplate = func(templ tplapi.Template) error {
+ depsCfg.WithTemplate = func(templ tpl.Template) error {
templ.AddTemplate("sitemap.xml", sitemapTemplate)
return nil
}
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -7,7 +7,7 @@
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/source"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl"
"github.com/spf13/viper"
"io/ioutil"
@@ -66,9 +66,9 @@
return jww.NewNotepad(jww.LevelDebug, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
}
-func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tplapi.Template) error {
+func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.Template) error {
- return func(templ tplapi.Template) error {
+ return func(templ tpl.Template) error {
for i := 0; i < len(additionalTemplates); i += 2 {
err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
if err != nil {
--- a/tpl/amber_compiler.go
+++ /dev/null
@@ -1,42 +1,0 @@
-// Copyright 2017 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tpl
-
-import (
- "html/template"
-
- "github.com/eknkc/amber"
-)
-
-func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *template.Template) (*template.Template, error) {
- c := amber.New()
-
- if err := c.ParseData(b, path); err != nil {
- return nil, err
- }
-
- data, err := c.CompileString()
-
- if err != nil {
- return nil, err
- }
-
- tpl, err := t.Funcs(gt.amberFuncMap).Parse(data)
-
- if err != nil {
- return nil, err
- }
-
- return tpl, nil
-}
--- a/tpl/reflect_helpers.go
+++ /dev/null
@@ -1,70 +1,0 @@
-// Copyright 2016 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tpl
-
-import (
- "reflect"
- "time"
-)
-
-// toInt returns the int value if possible, -1 if not.
-func toInt(v reflect.Value) int64 {
- switch v.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return v.Int()
- case reflect.Interface:
- return toInt(v.Elem())
- }
- return -1
-}
-
-// toString returns the string value if possible, "" if not.
-func toString(v reflect.Value) string {
- switch v.Kind() {
- case reflect.String:
- return v.String()
- case reflect.Interface:
- return toString(v.Elem())
- }
- return ""
-}
-
-var (
- zero reflect.Value
- errorType = reflect.TypeOf((*error)(nil)).Elem()
- timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
-)
-
-func toTimeUnix(v reflect.Value) int64 {
- if v.Kind() == reflect.Interface {
- return toTimeUnix(v.Elem())
- }
- if v.Type() != timeType {
- panic("coding error: argument must be time.Time type reflect Value")
- }
- return v.MethodByName("Unix").Call([]reflect.Value{})[0].Int()
-}
-
-// indirect is taken from 'text/template/exec.go'
-func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
- for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
- if v.IsNil() {
- return v, true
- }
- if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
- break
- }
- }
- return v, false
-}
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -1,575 +1,27 @@
-// Copyright 2016 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
package tpl
import (
- "fmt"
"html/template"
"io"
- "os"
- "path/filepath"
- "strings"
-
- "sync"
-
- "github.com/eknkc/amber"
- "github.com/spf13/afero"
- bp "github.com/spf13/hugo/bufferpool"
- "github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/helpers"
- "github.com/yosssi/ace"
)
-// TODO(bep) globals get rid of the rest of the jww.ERR etc.
-
-// Protecting global map access (Amber)
-var amberMu sync.Mutex
-
-type templateErr struct {
- name string
- err error
-}
-
-type GoHTMLTemplate struct {
- *template.Template
-
- clone *template.Template
-
- // a separate storage for the overlays created from cloned master templates.
- // note: No mutex protection, so we add these in one Go routine, then just read.
- overlays map[string]*template.Template
-
- errors []*templateErr
-
- funcster *templateFuncster
-
- amberFuncMap template.FuncMap
-
- *deps.Deps
-}
-
-type TemplateProvider struct{}
-
-var DefaultTemplateProvider *TemplateProvider
-
-// Update updates the Hugo Template System in the provided Deps.
-// with all the additional features, templates & functions
-func (*TemplateProvider) Update(deps *deps.Deps) error {
- // TODO(bep) check that this isn't called too many times.
- tmpl := &GoHTMLTemplate{
- Template: template.New(""),
- overlays: make(map[string]*template.Template),
- errors: make([]*templateErr, 0),
- Deps: deps,
- }
-
- deps.Tmpl = tmpl
-
- tmpl.initFuncs(deps)
-
- tmpl.LoadEmbedded()
-
- if deps.WithTemplate != nil {
- err := deps.WithTemplate(tmpl)
- if err != nil {
- tmpl.errors = append(tmpl.errors, &templateErr{"init", err})
- }
-
- }
-
- tmpl.MarkReady()
-
- return nil
-
-}
-
-// Clone clones
-func (*TemplateProvider) Clone(d *deps.Deps) error {
-
- t := d.Tmpl.(*GoHTMLTemplate)
-
- // 1. Clone the clone with new template funcs
- // 2. Clone any overlays with new template funcs
-
- tmpl := &GoHTMLTemplate{
- Template: template.Must(t.Template.Clone()),
- overlays: make(map[string]*template.Template),
- errors: make([]*templateErr, 0),
- Deps: d,
- }
-
- d.Tmpl = tmpl
- tmpl.initFuncs(d)
-
- for k, v := range t.overlays {
- vc := template.Must(v.Clone())
- // The extra lookup is a workaround, see
- // * https://github.com/golang/go/issues/16101
- // * https://github.com/spf13/hugo/issues/2549
- vc = vc.Lookup(vc.Name())
- vc.Funcs(tmpl.funcster.funcMap)
- tmpl.overlays[k] = vc
- }
-
- tmpl.MarkReady()
-
- return nil
-}
-
-func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) {
-
- t.funcster = newTemplateFuncster(d)
-
- // The URL funcs in the funcMap is somewhat language dependent,
- // so we need to wait until the language and site config is loaded.
- t.funcster.initFuncMap()
-
- t.amberFuncMap = template.FuncMap{}
-
- amberMu.Lock()
- for k, v := range amber.FuncMap {
- t.amberFuncMap[k] = v
- }
-
- for k, v := range t.funcster.funcMap {
- t.amberFuncMap[k] = v
- // Hacky, but we need to make sure that the func names are in the global map.
- amber.FuncMap[k] = func() string {
- panic("should never be invoked")
- }
- }
- amberMu.Unlock()
-
-}
-
-func (t *GoHTMLTemplate) Funcs(funcMap template.FuncMap) {
- t.Template.Funcs(funcMap)
-}
-
-func (t *GoHTMLTemplate) Partial(name string, contextList ...interface{}) template.HTML {
- if strings.HasPrefix("partials/", name) {
- name = name[8:]
- }
- var context interface{}
-
- if len(contextList) == 0 {
- context = nil
- } else {
- context = contextList[0]
- }
- return t.ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name)
-}
-
-func (t *GoHTMLTemplate) executeTemplate(context interface{}, w io.Writer, layouts ...string) {
- var worked bool
- for _, layout := range layouts {
- templ := t.Lookup(layout)
- if templ == nil {
- layout += ".html"
- templ = t.Lookup(layout)
- }
-
- if templ != nil {
- if err := templ.Execute(w, context); err != nil {
- helpers.DistinctErrorLog.Println(layout, err)
- }
- worked = true
- break
- }
- }
- if !worked {
- t.Log.ERROR.Println("Unable to render", layouts)
- t.Log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
- }
-}
-
-func (t *GoHTMLTemplate) ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML {
- b := bp.GetBuffer()
- defer bp.PutBuffer(b)
- t.executeTemplate(context, b, layouts...)
- return template.HTML(b.String())
-}
-
-func (t *GoHTMLTemplate) Lookup(name string) *template.Template {
-
- if templ := t.Template.Lookup(name); templ != nil {
- return templ
- }
-
- if t.overlays != nil {
- if templ, ok := t.overlays[name]; ok {
- return templ
- }
- }
-
- // The clone is used for the non-renderable HTML pages (p.IsRenderable == false) that is parsed
- // as Go templates late in the build process.
- if t.clone != nil {
- if templ := t.clone.Lookup(name); templ != nil {
- return templ
- }
- }
-
- return nil
-
-}
-
-func (t *GoHTMLTemplate) GetClone() *template.Template {
- return t.clone
-}
-
-func (t *GoHTMLTemplate) LoadEmbedded() {
- t.EmbedShortcodes()
- t.EmbedTemplates()
-}
-
-// MarkReady marks the template as "ready for execution". No changes allowed
-// after this is set.
-func (t *GoHTMLTemplate) MarkReady() {
- if t.clone == nil {
- t.clone = template.Must(t.Template.Clone())
- }
-}
-
-func (t *GoHTMLTemplate) checkState() {
- if t.clone != nil {
- panic("template is cloned and cannot be modfified")
- }
-}
-
-func (t *GoHTMLTemplate) AddInternalTemplate(prefix, name, tpl string) error {
- if prefix != "" {
- return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
- }
- return t.AddTemplate("_internal/"+name, tpl)
-}
-
-func (t *GoHTMLTemplate) AddInternalShortcode(name, content string) error {
- return t.AddInternalTemplate("shortcodes", name, content)
-}
-
-func (t *GoHTMLTemplate) AddTemplate(name, tpl string) error {
- t.checkState()
- templ, err := t.New(name).Parse(tpl)
- if err != nil {
- t.errors = append(t.errors, &templateErr{name: name, err: err})
- return err
- }
- if err := applyTemplateTransformers(templ); err != nil {
- return err
- }
-
- return nil
-}
-
-func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error {
-
- // There is currently no known way to associate a cloned template with an existing one.
- // This funky master/overlay design will hopefully improve in a future version of Go.
- //
- // Simplicity is hard.
- //
- // Until then we'll have to live with this hackery.
- //
- // See https://github.com/golang/go/issues/14285
- //
- // So, to do minimum amount of changes to get this to work:
- //
- // 1. Lookup or Parse the master
- // 2. Parse and store the overlay in a separate map
-
- masterTpl := t.Lookup(masterFilename)
-
- if masterTpl == nil {
- b, err := afero.ReadFile(t.Fs.Source, masterFilename)
- if err != nil {
- return err
- }
- masterTpl, err = t.New(masterFilename).Parse(string(b))
-
- if err != nil {
- // TODO(bep) Add a method that does this
- t.errors = append(t.errors, &templateErr{name: name, err: err})
- return err
- }
- }
-
- b, err := afero.ReadFile(t.Fs.Source, overlayFilename)
- if err != nil {
- return err
- }
-
- overlayTpl, err := template.Must(masterTpl.Clone()).Parse(string(b))
- if err != nil {
- t.errors = append(t.errors, &templateErr{name: name, err: err})
- } else {
- // The extra lookup is a workaround, see
- // * https://github.com/golang/go/issues/16101
- // * https://github.com/spf13/hugo/issues/2549
- overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
- if err := applyTemplateTransformers(overlayTpl); err != nil {
- return err
- }
- t.overlays[name] = overlayTpl
- }
-
- return err
-}
-
-func (t *GoHTMLTemplate) AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error {
- t.checkState()
- var base, inner *ace.File
- name = name[:len(name)-len(filepath.Ext(innerPath))] + ".html"
-
- // Fixes issue #1178
- basePath = strings.Replace(basePath, "\\", "/", -1)
- innerPath = strings.Replace(innerPath, "\\", "/", -1)
-
- if basePath != "" {
- base = ace.NewFile(basePath, baseContent)
- inner = ace.NewFile(innerPath, innerContent)
- } else {
- base = ace.NewFile(innerPath, innerContent)
- inner = ace.NewFile("", []byte{})
- }
- parsed, err := ace.ParseSource(ace.NewSource(base, inner, []*ace.File{}), nil)
- if err != nil {
- t.errors = append(t.errors, &templateErr{name: name, err: err})
- return err
- }
- templ, err := ace.CompileResultWithTemplate(t.New(name), parsed, nil)
- if err != nil {
- t.errors = append(t.errors, &templateErr{name: name, err: err})
- return err
- }
- return applyTemplateTransformers(templ)
-}
-
-func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) error {
- t.checkState()
- // get the suffix and switch on that
- ext := filepath.Ext(path)
- switch ext {
- case ".amber":
- templateName := strings.TrimSuffix(name, filepath.Ext(name)) + ".html"
- b, err := afero.ReadFile(t.Fs.Source, path)
-
- if err != nil {
- return err
- }
-
- amberMu.Lock()
- templ, err := t.CompileAmberWithTemplate(b, path, t.New(templateName))
- amberMu.Unlock()
- if err != nil {
- return err
- }
-
- return applyTemplateTransformers(templ)
- case ".ace":
- var innerContent, baseContent []byte
- innerContent, err := afero.ReadFile(t.Fs.Source, path)
-
- if err != nil {
- return err
- }
-
- if baseTemplatePath != "" {
- baseContent, err = afero.ReadFile(t.Fs.Source, baseTemplatePath)
- if err != nil {
- return err
- }
- }
-
- return t.AddAceTemplate(name, baseTemplatePath, path, baseContent, innerContent)
- default:
-
- if baseTemplatePath != "" {
- return t.AddTemplateFileWithMaster(name, path, baseTemplatePath)
- }
-
- b, err := afero.ReadFile(t.Fs.Source, path)
-
- if err != nil {
- return err
- }
-
- t.Log.DEBUG.Printf("Add template file from path %s", path)
-
- return t.AddTemplate(name, string(b))
- }
-
-}
-
-func (t *GoHTMLTemplate) GenerateTemplateNameFrom(base, path string) string {
- name, _ := filepath.Rel(base, path)
- return filepath.ToSlash(name)
-}
-
-func isDotFile(path string) bool {
- return filepath.Base(path)[0] == '.'
-}
-
-func isBackupFile(path string) bool {
- return path[len(path)-1] == '~'
-}
-
-const baseFileBase = "baseof"
-
-var aceTemplateInnerMarkers = [][]byte{[]byte("= content")}
-var goTemplateInnerMarkers = [][]byte{[]byte("{{define"), []byte("{{ define")}
-
-func isBaseTemplate(path string) bool {
- return strings.Contains(path, baseFileBase)
-}
-
-func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
- t.Log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix)
- walker := func(path string, fi os.FileInfo, err error) error {
- if err != nil {
- return nil
- }
- t.Log.DEBUG.Println("Template path", path)
- if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
- link, err := filepath.EvalSymlinks(absPath)
- if err != nil {
- t.Log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err)
- return nil
- }
- linkfi, err := t.Fs.Source.Stat(link)
- if err != nil {
- t.Log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
- return nil
- }
- if !linkfi.Mode().IsRegular() {
- t.Log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath)
- }
- return nil
- }
-
- if !fi.IsDir() {
- if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
- return nil
- }
-
- tplName := t.GenerateTemplateNameFrom(absPath, path)
-
- if prefix != "" {
- tplName = strings.Trim(prefix, "/") + "/" + tplName
- }
-
- var baseTemplatePath string
-
- // Ace and Go templates may have both a base and inner template.
- pathDir := filepath.Dir(path)
- if filepath.Ext(path) != ".amber" && !strings.HasSuffix(pathDir, "partials") && !strings.HasSuffix(pathDir, "shortcodes") {
-
- innerMarkers := goTemplateInnerMarkers
- baseFileName := fmt.Sprintf("%s.html", baseFileBase)
-
- if filepath.Ext(path) == ".ace" {
- innerMarkers = aceTemplateInnerMarkers
- baseFileName = fmt.Sprintf("%s.ace", baseFileBase)
- }
-
- // This may be a view that shouldn't have base template
- // Have to look inside it to make sure
- needsBase, err := helpers.FileContainsAny(path, innerMarkers, t.Fs.Source)
- if err != nil {
- return err
- }
- if needsBase {
-
- layoutDir := t.PathSpec.GetLayoutDirPath()
- currBaseFilename := fmt.Sprintf("%s-%s", helpers.Filename(path), baseFileName)
- templateDir := filepath.Dir(path)
- themeDir := filepath.Join(t.PathSpec.GetThemeDir())
- relativeThemeLayoutsDir := filepath.Join(t.PathSpec.GetRelativeThemeDir(), "layouts")
-
- var baseTemplatedDir string
-
- if strings.HasPrefix(templateDir, relativeThemeLayoutsDir) {
- baseTemplatedDir = strings.TrimPrefix(templateDir, relativeThemeLayoutsDir)
- } else {
- baseTemplatedDir = strings.TrimPrefix(templateDir, layoutDir)
- }
-
- baseTemplatedDir = strings.TrimPrefix(baseTemplatedDir, helpers.FilePathSeparator)
-
- // Look for base template in the follwing order:
- // 1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
- // 2. <current-path>/baseof.<suffix>
- // 3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
- // 4. _default/baseof.<suffix>
- // For each of the steps above, it will first look in the project, then, if theme is set,
- // in the theme's layouts folder.
-
- pairsToCheck := [][]string{
- []string{baseTemplatedDir, currBaseFilename},
- []string{baseTemplatedDir, baseFileName},
- []string{"_default", currBaseFilename},
- []string{"_default", baseFileName},
- }
-
- Loop:
- for _, pair := range pairsToCheck {
- pathsToCheck := basePathsToCheck(pair, layoutDir, themeDir)
- for _, pathToCheck := range pathsToCheck {
- if ok, err := helpers.Exists(pathToCheck, t.Fs.Source); err == nil && ok {
- baseTemplatePath = pathToCheck
- break Loop
- }
- }
- }
- }
- }
-
- if err := t.AddTemplateFile(tplName, baseTemplatePath, path); err != nil {
- t.Log.ERROR.Printf("Failed to add template %s in path %s: %s", tplName, path, err)
- }
-
- }
- return nil
- }
- if err := helpers.SymbolicWalk(t.Fs.Source, absPath, walker); err != nil {
- t.Log.ERROR.Printf("Failed to load templates: %s", err)
- }
-}
-
-func basePathsToCheck(path []string, layoutDir, themeDir string) []string {
- // Always look in the project.
- pathsToCheck := []string{filepath.Join((append([]string{layoutDir}, path...))...)}
-
- // May have a theme
- if themeDir != "" {
- pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{themeDir, "layouts"}, path...))...))
- }
-
- return pathsToCheck
-
-}
-
-func (t *GoHTMLTemplate) LoadTemplatesWithPrefix(absPath string, prefix string) {
- t.loadTemplates(absPath, prefix)
-}
-
-func (t *GoHTMLTemplate) LoadTemplates(absPath string) {
- t.loadTemplates(absPath, "")
-}
-
-func (t *GoHTMLTemplate) PrintErrors() {
- for i, e := range t.errors {
- t.Log.ERROR.Println(i, ":", e.err)
- }
+// TODO(bep) make smaller
+type Template interface {
+ ExecuteTemplate(wr io.Writer, name string, data interface{}) error
+ ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML
+ Lookup(name string) *template.Template
+ Templates() []*template.Template
+ New(name string) *template.Template
+ GetClone() *template.Template
+ LoadTemplates(absPath string)
+ LoadTemplatesWithPrefix(absPath, prefix string)
+ AddTemplate(name, tpl string) error
+ AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error
+ AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error
+ AddInternalTemplate(prefix, name, tpl string) error
+ AddInternalShortcode(name, tpl string) error
+ Partial(name string, contextList ...interface{}) template.HTML
+ PrintErrors()
+ Funcs(funcMap template.FuncMap)
+ MarkReady()
}
--- a/tpl/template_ast_transformers.go
+++ /dev/null
@@ -1,259 +1,0 @@
-// Copyright 2016 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tpl
-
-import (
- "errors"
- "html/template"
- "strings"
- "text/template/parse"
-)
-
-// decl keeps track of the variable mappings, i.e. $mysite => .Site etc.
-type decl map[string]string
-
-var paramsPaths = [][]string{
- {"Params"},
- {"Site", "Params"},
-
- // Site and Pag referenced from shortcodes
- {"Page", "Site", "Params"},
- {"Page", "Params"},
-
- {"Site", "Language", "Params"},
-}
-
-type templateContext struct {
- decl decl
- templ *template.Template
-}
-
-func newTemplateContext(templ *template.Template) *templateContext {
- return &templateContext{templ: templ, decl: make(map[string]string)}
-
-}
-
-func applyTemplateTransformers(templ *template.Template) error {
- if templ == nil || templ.Tree == nil {
- return errors.New("expected template, but none provided")
- }
-
- c := newTemplateContext(templ)
-
- c.paramsKeysToLower(templ.Tree.Root)
-
- return nil
-}
-
-// paramsKeysToLower is made purposely non-generic to make it not so tempting
-// to do more of these hard-to-maintain AST transformations.
-func (c *templateContext) paramsKeysToLower(n parse.Node) {
-
- switch x := n.(type) {
- case *parse.ListNode:
- if x != nil {
- c.paramsKeysToLowerForNodes(x.Nodes...)
- }
- case *parse.ActionNode:
- c.paramsKeysToLowerForNodes(x.Pipe)
- case *parse.IfNode:
- c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList)
- case *parse.WithNode:
- c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList)
- case *parse.RangeNode:
- c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList)
- case *parse.TemplateNode:
- subTempl := c.templ.Lookup(x.Name)
- if subTempl != nil {
- c.paramsKeysToLowerForNodes(subTempl.Tree.Root)
- }
- case *parse.PipeNode:
- for i, elem := range x.Decl {
- if len(x.Cmds) > i {
- // maps $site => .Site etc.
- c.decl[elem.Ident[0]] = x.Cmds[i].String()
- }
- }
-
- for _, cmd := range x.Cmds {
- c.paramsKeysToLower(cmd)
- }
-
- case *parse.CommandNode:
- for _, elem := range x.Args {
- switch an := elem.(type) {
- case *parse.FieldNode:
- c.updateIdentsIfNeeded(an.Ident)
- case *parse.VariableNode:
- c.updateIdentsIfNeeded(an.Ident)
- case *parse.PipeNode:
- c.paramsKeysToLower(an)
- }
-
- }
- }
-}
-
-func (c *templateContext) paramsKeysToLowerForNodes(nodes ...parse.Node) {
- for _, node := range nodes {
- c.paramsKeysToLower(node)
- }
-}
-
-func (c *templateContext) updateIdentsIfNeeded(idents []string) {
- index := c.decl.indexOfReplacementStart(idents)
-
- if index == -1 {
- return
- }
-
- for i := index; i < len(idents); i++ {
- idents[i] = strings.ToLower(idents[i])
- }
-}
-
-// indexOfReplacementStart will return the index of where to start doing replacement,
-// -1 if none needed.
-func (d decl) indexOfReplacementStart(idents []string) int {
-
- l := len(idents)
-
- if l == 0 {
- return -1
- }
-
- first := idents[0]
- firstIsVar := first[0] == '$'
-
- if l == 1 && !firstIsVar {
- // This can not be a Params.x
- return -1
- }
-
- if !firstIsVar {
- found := false
- for _, paramsPath := range paramsPaths {
- if first == paramsPath[0] {
- found = true
- break
- }
- }
- if !found {
- return -1
- }
- }
-
- var (
- resolvedIdents []string
- replacements []string
- replaced []string
- )
-
- // An Ident can start out as one of
- // [Params] [$blue] [$colors.Blue]
- // We need to resolve the variables, so
- // $blue => [Params Colors Blue]
- // etc.
- replacements = []string{idents[0]}
-
- // Loop until there are no more $vars to resolve.
- for i := 0; i < len(replacements); i++ {
-
- if i > 20 {
- // bail out
- return -1
- }
-
- potentialVar := replacements[i]
-
- if potentialVar == "$" {
- continue
- }
-
- if potentialVar == "" || potentialVar[0] != '$' {
- // leave it as is
- replaced = append(replaced, strings.Split(potentialVar, ".")...)
- continue
- }
-
- replacement, ok := d[potentialVar]
-
- if !ok {
- // Temporary range vars. We do not care about those.
- return -1
- }
-
- replacement = strings.TrimPrefix(replacement, ".")
-
- if replacement == "" {
- continue
- }
-
- if replacement[0] == '$' {
- // Needs further expansion
- replacements = append(replacements, strings.Split(replacement, ".")...)
- } else {
- replaced = append(replaced, strings.Split(replacement, ".")...)
- }
- }
-
- resolvedIdents = append(replaced, idents[1:]...)
-
- for _, paramPath := range paramsPaths {
- if index := indexOfFirstRealIdentAfterWords(resolvedIdents, idents, paramPath...); index != -1 {
- return index
- }
- }
-
- return -1
-
-}
-
-func indexOfFirstRealIdentAfterWords(resolvedIdents, idents []string, words ...string) int {
- if !sliceStartsWith(resolvedIdents, words...) {
- return -1
- }
-
- for i, ident := range idents {
- if ident == "" || ident[0] == '$' {
- continue
- }
- found := true
- for _, word := range words {
- if ident == word {
- found = false
- break
- }
- }
- if found {
- return i
- }
- }
-
- return -1
-}
-
-func sliceStartsWith(slice []string, words ...string) bool {
-
- if len(slice) < len(words) {
- return false
- }
-
- for i, word := range words {
- if word != slice[i] {
- return false
- }
- }
- return true
-}
--- a/tpl/template_ast_transformers_test.go
+++ /dev/null
@@ -1,269 +1,0 @@
-// Copyright 2016 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package tpl
-
-import (
- "bytes"
- "testing"
-
- "html/template"
-
- "github.com/stretchr/testify/require"
-)
-
-var (
- testFuncs = map[string]interface{}{
- "Echo": func(v interface{}) interface{} { return v },
- }
-
- paramsData = map[string]interface{}{
- "NotParam": "Hi There",
- "Slice": []int{1, 3},
- "Params": map[string]interface{}{
- "lower": "P1L",
- },
- "Site": map[string]interface{}{
- "Params": map[string]interface{}{
- "lower": "P2L",
- "slice": []int{1, 3},
- },
- "Language": map[string]interface{}{
- "Params": map[string]interface{}{
- "lower": "P22L",
- },
- },
- "Data": map[string]interface{}{
- "Params": map[string]interface{}{
- "NOLOW": "P3H",
- },
- },
- },
- }
-
- paramsTempl = `
-{{ $page := . }}
-{{ $pageParams := .Params }}
-{{ $site := .Site }}
-{{ $siteParams := .Site.Params }}
-{{ $data := .Site.Data }}
-{{ $notparam := .NotParam }}
-
-P1: {{ .Params.LOWER }}
-P1_2: {{ $.Params.LOWER }}
-P1_3: {{ $page.Params.LOWER }}
-P1_4: {{ $pageParams.LOWER }}
-P2: {{ .Site.Params.LOWER }}
-P2_2: {{ $.Site.Params.LOWER }}
-P2_3: {{ $site.Params.LOWER }}
-P2_4: {{ $siteParams.LOWER }}
-P22: {{ .Site.Language.Params.LOWER }}
-P3: {{ .Site.Data.Params.NOLOW }}
-P3_2: {{ $.Site.Data.Params.NOLOW }}
-P3_3: {{ $site.Data.Params.NOLOW }}
-P3_4: {{ $data.Params.NOLOW }}
-P4: {{ range $i, $e := .Site.Params.SLICE }}{{ $e }}{{ end }}
-P5: {{ Echo .Params.LOWER }}
-P5_2: {{ Echo $site.Params.LOWER }}
-{{ if .Params.LOWER }}
-IF: {{ .Params.LOWER }}
-{{ end }}
-{{ if .Params.NOT_EXIST }}
-{{ else }}
-ELSE: {{ .Params.LOWER }}
-{{ end }}
-
-
-{{ with .Params.LOWER }}
-WITH: {{ . }}
-{{ end }}
-
-
-{{ range .Slice }}
-RANGE: {{ . }}: {{ $.Params.LOWER }}
-{{ end }}
-{{ index .Slice 1 }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ $notparam }}
-
-
-{{ $lower := .Site.Params.LOWER }}
-F1: {{ printf "themes/%s-theme" .Site.Params.LOWER }}
-F2: {{ Echo (printf "themes/%s-theme" $lower) }}
-F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }}
-`
-)
-
-func TestParamsKeysToLower(t *testing.T) {
- t.Parallel()
-
- require.Error(t, applyTemplateTransformers(nil))
-
- templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
-
- require.NoError(t, err)
-
- c := newTemplateContext(templ)
-
- require.Equal(t, -1, c.decl.indexOfReplacementStart([]string{}))
-
- c.paramsKeysToLower(templ.Tree.Root)
-
- var b bytes.Buffer
-
- require.NoError(t, templ.Execute(&b, paramsData))
-
- result := b.String()
-
- require.Contains(t, result, "P1: P1L")
- require.Contains(t, result, "P1_2: P1L")
- require.Contains(t, result, "P1_3: P1L")
- require.Contains(t, result, "P1_4: P1L")
- require.Contains(t, result, "P2: P2L")
- require.Contains(t, result, "P2_2: P2L")
- require.Contains(t, result, "P2_3: P2L")
- require.Contains(t, result, "P2_4: P2L")
- require.Contains(t, result, "P22: P22L")
- require.Contains(t, result, "P3: P3H")
- require.Contains(t, result, "P3_2: P3H")
- require.Contains(t, result, "P3_3: P3H")
- require.Contains(t, result, "P3_4: P3H")
- require.Contains(t, result, "P4: 13")
- require.Contains(t, result, "P5: P1L")
- require.Contains(t, result, "P5_2: P2L")
-
- require.Contains(t, result, "IF: P1L")
- require.Contains(t, result, "ELSE: P1L")
-
- require.Contains(t, result, "WITH: P1L")
-
- require.Contains(t, result, "RANGE: 3: P1L")
-
- require.Contains(t, result, "Hi There")
-
- // Issue #2740
- require.Contains(t, result, "F1: themes/P2L-theme")
- require.Contains(t, result, "F2: themes/P2L-theme")
- require.Contains(t, result, "F3: themes/P2L-theme")
-
-}
-
-func BenchmarkTemplateParamsKeysToLower(b *testing.B) {
- templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
-
- if err != nil {
- b.Fatal(err)
- }
-
- templates := make([]*template.Template, b.N)
-
- for i := 0; i < b.N; i++ {
- templates[i], err = templ.Clone()
- if err != nil {
- b.Fatal(err)
- }
- }
-
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- c := newTemplateContext(templates[i])
- c.paramsKeysToLower(templ.Tree.Root)
- }
-}
-
-func TestParamsKeysToLowerVars(t *testing.T) {
- t.Parallel()
- var (
- ctx = map[string]interface{}{
- "Params": map[string]interface{}{
- "colors": map[string]interface{}{
- "blue": "Amber",
- },
- },
- }
-
- // This is how Amber behaves:
- paramsTempl = `
-{{$__amber_1 := .Params.Colors}}
-{{$__amber_2 := $__amber_1.Blue}}
-Color: {{$__amber_2}}
-Blue: {{ $__amber_1.Blue}}
-`
- )
-
- templ, err := template.New("foo").Parse(paramsTempl)
-
- require.NoError(t, err)
-
- c := newTemplateContext(templ)
-
- c.paramsKeysToLower(templ.Tree.Root)
-
- var b bytes.Buffer
-
- require.NoError(t, templ.Execute(&b, ctx))
-
- result := b.String()
-
- require.Contains(t, result, "Color: Amber")
-
-}
-
-func TestParamsKeysToLowerInBlockTemplate(t *testing.T) {
- t.Parallel()
-
- var (
- ctx = map[string]interface{}{
- "Params": map[string]interface{}{
- "lower": "P1L",
- },
- }
-
- master = `
-P1: {{ .Params.LOWER }}
-{{ block "main" . }}DEFAULT{{ end }}`
- overlay = `
-{{ define "main" }}
-P2: {{ .Params.LOWER }}
-{{ end }}`
- )
-
- masterTpl, err := template.New("foo").Parse(master)
- require.NoError(t, err)
-
- overlayTpl, err := template.Must(masterTpl.Clone()).Parse(overlay)
- require.NoError(t, err)
- overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
-
- c := newTemplateContext(overlayTpl)
-
- c.paramsKeysToLower(overlayTpl.Tree.Root)
-
- var b bytes.Buffer
-
- require.NoError(t, overlayTpl.Execute(&b, ctx))
-
- result := b.String()
-
- require.Contains(t, result, "P1: P1L")
- require.Contains(t, result, "P2: P1L")
-}
--- a/tpl/template_embedded.go
+++ /dev/null
@@ -1,266 +1,0 @@
-// Copyright 2015 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tpl
-
-type Tmpl struct {
- Name string
- Data string
-}
-
-func (t *GoHTMLTemplate) EmbedShortcodes() {
- t.AddInternalShortcode("ref.html", `{{ .Get 0 | ref .Page }}`)
- t.AddInternalShortcode("relref.html", `{{ .Get 0 | relref .Page }}`)
- t.AddInternalShortcode("highlight.html", `{{ if len .Params | eq 2 }}{{ highlight .Inner (.Get 0) (.Get 1) }}{{ else }}{{ highlight .Inner (.Get 0) "" }}{{ end }}`)
- t.AddInternalShortcode("test.html", `This is a simple Test`)
- t.AddInternalShortcode("figure.html", `<!-- image -->
-<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
- {{ with .Get "link"}}<a href="{{.}}">{{ end }}
- <img src="{{ .Get "src" }}" {{ if or (.Get "alt") (.Get "caption") }}alt="{{ with .Get "alt"}}{{.}}{{else}}{{ .Get "caption" }}{{ end }}" {{ end }}{{ with .Get "width" }}width="{{.}}" {{ end }}/>
- {{ if .Get "link"}}</a>{{ end }}
- {{ if or (or (.Get "title") (.Get "caption")) (.Get "attr")}}
- <figcaption>{{ if isset .Params "title" }}
- <h4>{{ .Get "title" }}</h4>{{ end }}
- {{ if or (.Get "caption") (.Get "attr")}}<p>
- {{ .Get "caption" }}
- {{ with .Get "attrlink"}}<a href="{{.}}"> {{ end }}
- {{ .Get "attr" }}
- {{ if .Get "attrlink"}}</a> {{ end }}
- </p> {{ end }}
- </figcaption>
- {{ end }}
-</figure>
-<!-- image -->`)
- t.AddInternalShortcode("speakerdeck.html", "<script async class='speakerdeck-embed' data-id='{{ index .Params 0 }}' data-ratio='1.33333333333333' src='//speakerdeck.com/assets/embed.js'></script>")
- t.AddInternalShortcode("youtube.html", `{{ if .IsNamedParams }}
-<div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
- <iframe src="//www.youtube.com/embed/{{ .Get "id" }}?{{ with .Get "autoplay" }}{{ if eq . "true" }}autoplay=1{{ end }}{{ end }}"
- {{ if not (.Get "class") }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0"></iframe>
-</div>{{ else }}
-<div {{ if len .Params | eq 2 }}class="{{ .Get 1 }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
- <iframe src="//www.youtube.com/embed/{{ .Get 0 }}" {{ if len .Params | eq 1 }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0"></iframe>
- </div>
-{{ end }}`)
- t.AddInternalShortcode("vimeo.html", `{{ if .IsNamedParams }}<div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
- <iframe src="//player.vimeo.com/video/{{ .Get "id" }}" {{ if not (.Get "class") }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
- </div>{{ else }}
-<div {{ if len .Params | eq 2 }}class="{{ .Get 1 }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
- <iframe src="//player.vimeo.com/video/{{ .Get 0 }}" {{ if len .Params | eq 1 }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
- </div>
-{{ end }}`)
- t.AddInternalShortcode("gist.html", `<script src="//gist.github.com/{{ index .Params 0 }}/{{ index .Params 1 }}.js{{if len .Params | eq 3 }}?file={{ index .Params 2 }}{{end}}"></script>`)
- t.AddInternalShortcode("tweet.html", `{{ (getJSON "https://api.twitter.com/1/statuses/oembed.json?id=" (index .Params 0)).html | safeHTML }}`)
- t.AddInternalShortcode("instagram.html", `{{ if len .Params | eq 2 }}{{ if eq (.Get 1) "hidecaption" }}{{ (getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=1").html | safeHTML }}{{ end }}{{ else }}{{ (getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=0").html | safeHTML }}{{ end }}`)
-}
-
-func (t *GoHTMLTemplate) EmbedTemplates() {
-
- t.AddInternalTemplate("_default", "rss.xml", `<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
- <channel>
- <title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
- <link>{{ .Permalink }}</link>
- <description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
- <generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
- <language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
- <managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
- <webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
- <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
- <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
- <atom:link href="{{.Permalink}}" rel="self" type="application/rss+xml" />
- {{ range first 15 .Data.Pages }}
- <item>
- <title>{{ .Title }}</title>
- <link>{{ .Permalink }}</link>
- <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
- {{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
- <guid>{{ .Permalink }}</guid>
- <description>{{ .Content | html }}</description>
- </item>
- {{ end }}
- </channel>
-</rss>`)
-
- t.AddInternalTemplate("_default", "sitemap.xml", `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
- {{ range .Data.Pages }}
- <url>
- <loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
- <lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
- <changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
- <priority>{{ .Sitemap.Priority }}</priority>{{ end }}
- </url>
- {{ end }}
-</urlset>`)
-
- // For multilanguage sites
- t.AddInternalTemplate("_default", "sitemapindex.xml", `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
- {{ range . }}
- <sitemap>
- <loc>{{ .SitemapAbsURL }}</loc>
- {{ if not .LastChange.IsZero }}
- <lastmod>{{ .LastChange.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</lastmod>
- {{ end }}
- </sitemap>
- {{ end }}
-</sitemapindex>
-`)
-
- t.AddInternalTemplate("", "pagination.html", `{{ $pag := $.Paginator }}
- {{ if gt $pag.TotalPages 1 }}
- <ul class="pagination">
- {{ with $pag.First }}
- <li>
- <a href="{{ .URL }}" aria-label="First"><span aria-hidden="true">««</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 <[email protected]>" | safeHTML}}
-htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>"}}
-htmlUnescape 1: {{htmlUnescape "Cathal Garvey & The Sunshine Band <[email protected]>" | safeHTML}}
-htmlUnescape 2: {{"Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | htmlUnescape | htmlUnescape | safeHTML}}
-htmlUnescape 3: {{"Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | htmlUnescape | htmlUnescape }}
-htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | htmlUnescape | safeHTML }}
-htmlUnescape 5: {{ htmlUnescape "Cathal Garvey & The Sunshine Band <[email protected]>" | 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 <[email protected]>
-htmlEscape 2: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
-htmlUnescape 1: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 2: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 3: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 4: Cathal Garvey & The Sunshine Band <[email protected]>
-htmlUnescape 5: Cathal Garvey & The Sunshine Band <[email protected]>
-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 <[email protected]>" | safeHTML}}
+htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>"}}
+htmlUnescape 1: {{htmlUnescape "Cathal Garvey & The Sunshine Band <[email protected]>" | safeHTML}}
+htmlUnescape 2: {{"Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | htmlUnescape | htmlUnescape | safeHTML}}
+htmlUnescape 3: {{"Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;" | htmlUnescape | htmlUnescape }}
+htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <[email protected]>" | htmlUnescape | safeHTML }}
+htmlUnescape 5: {{ htmlUnescape "Cathal Garvey & The Sunshine Band <[email protected]>" | 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 <[email protected]>
+htmlEscape 2: Cathal Garvey &amp; The Sunshine Band &lt;[email protected]&gt;
+htmlUnescape 1: Cathal Garvey & The Sunshine Band <[email protected]>
+htmlUnescape 2: Cathal Garvey & The Sunshine Band <[email protected]>
+htmlUnescape 3: Cathal Garvey & The Sunshine Band <[email protected]>
+htmlUnescape 4: Cathal Garvey & The Sunshine Band <[email protected]>
+htmlUnescape 5: Cathal Garvey & The Sunshine Band <[email protected]>
+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()
-}