mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-31 22:41:53 +02:00
@@ -25,16 +25,14 @@ import (
|
||||
|
||||
// Some of the template funcs are'nt entirely stateless.
|
||||
type templateFuncster struct {
|
||||
funcMap template.FuncMap
|
||||
cachedPartials partialCache
|
||||
funcMap template.FuncMap
|
||||
|
||||
*deps.Deps
|
||||
}
|
||||
|
||||
func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
|
||||
return &templateFuncster{
|
||||
Deps: deps,
|
||||
cachedPartials: partialCache{p: make(map[string]interface{})},
|
||||
Deps: deps,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2017-present The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Portions Copyright The Go Authors.
|
||||
|
||||
@@ -16,24 +16,24 @@
|
||||
package tplimpl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/hugo/tpl/internal"
|
||||
|
||||
// Init the namespaces
|
||||
_ "github.com/spf13/hugo/tpl/cast"
|
||||
_ "github.com/spf13/hugo/tpl/collections"
|
||||
_ "github.com/spf13/hugo/tpl/compare"
|
||||
_ "github.com/spf13/hugo/tpl/crypto"
|
||||
_ "github.com/spf13/hugo/tpl/data"
|
||||
_ "github.com/spf13/hugo/tpl/encoding"
|
||||
_ "github.com/spf13/hugo/tpl/fmt"
|
||||
_ "github.com/spf13/hugo/tpl/images"
|
||||
_ "github.com/spf13/hugo/tpl/inflect"
|
||||
_ "github.com/spf13/hugo/tpl/lang"
|
||||
_ "github.com/spf13/hugo/tpl/math"
|
||||
_ "github.com/spf13/hugo/tpl/os"
|
||||
_ "github.com/spf13/hugo/tpl/partials"
|
||||
_ "github.com/spf13/hugo/tpl/safe"
|
||||
_ "github.com/spf13/hugo/tpl/strings"
|
||||
_ "github.com/spf13/hugo/tpl/time"
|
||||
@@ -41,74 +41,15 @@ import (
|
||||
_ "github.com/spf13/hugo/tpl/urls"
|
||||
)
|
||||
|
||||
// Get retrieves partial output from the cache based upon the partial name.
|
||||
// If the partial is not found in the cache, the partial is rendered and added
|
||||
// to the cache.
|
||||
func (t *templateFuncster) Get(key, name string, context interface{}) (p interface{}, err error) {
|
||||
var ok bool
|
||||
|
||||
t.cachedPartials.RLock()
|
||||
p, ok = t.cachedPartials.p[key]
|
||||
t.cachedPartials.RUnlock()
|
||||
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
t.cachedPartials.Lock()
|
||||
if p, ok = t.cachedPartials.p[key]; !ok {
|
||||
t.cachedPartials.Unlock()
|
||||
p, err = t.partial(name, context)
|
||||
|
||||
t.cachedPartials.Lock()
|
||||
t.cachedPartials.p[key] = p
|
||||
|
||||
}
|
||||
t.cachedPartials.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// partialCache represents a cache of partials protected by a mutex.
|
||||
type partialCache struct {
|
||||
sync.RWMutex
|
||||
p map[string]interface{}
|
||||
}
|
||||
|
||||
// partialCached executes and caches partial templates. An optional variant
|
||||
// string parameter (a string slice actually, but be only use a variadic
|
||||
// argument to make it optional) can be passed so that a given partial can have
|
||||
// multiple uses. The cache is created with name+variant as the key.
|
||||
func (t *templateFuncster) partialCached(name string, context interface{}, variant ...string) (interface{}, error) {
|
||||
key := name
|
||||
if len(variant) > 0 {
|
||||
for i := 0; i < len(variant); i++ {
|
||||
key += variant[i]
|
||||
}
|
||||
}
|
||||
return t.Get(key, name, context)
|
||||
}
|
||||
|
||||
func (t *templateFuncster) initFuncMap() {
|
||||
funcMap := template.FuncMap{
|
||||
// Namespaces
|
||||
//"time": t.time.Namespace,
|
||||
"int": func(v interface{}) (int, error) { return cast.ToIntE(v) },
|
||||
"partial": t.partial,
|
||||
"partialCached": t.partialCached,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"string": func(v interface{}) (string, error) { return cast.ToStringE(v) },
|
||||
"urlize": t.PathSpec.URLize,
|
||||
}
|
||||
funcMap := template.FuncMap{}
|
||||
|
||||
// Merge the namespace funcs
|
||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
||||
ns := nsf(t.Deps)
|
||||
// TODO(bep) namespace ns.Context is a dummy func just to make this work.
|
||||
// Consider if we can add this context to the rendering context in an easy
|
||||
// way to make this cleaner. Maybe.
|
||||
if _, exists := funcMap[ns.Name]; exists {
|
||||
panic(ns.Name + " is a duplicate template func")
|
||||
}
|
||||
funcMap[ns.Name] = ns.Context
|
||||
for k, v := range ns.Aliases {
|
||||
funcMap[k] = v
|
||||
|
@@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"io/ioutil"
|
||||
@@ -92,6 +91,7 @@ func TestTemplateFuncsExamples(t *testing.T) {
|
||||
in, expected := example[0], example[1]
|
||||
d.WithTemplate = func(templ tpl.TemplateHandler) error {
|
||||
require.NoError(t, templ.AddTemplate("test", in))
|
||||
require.NoError(t, templ.AddTemplate("partials/header.html", "<title>Hugo Rocks!</title>"))
|
||||
return nil
|
||||
}
|
||||
require.NoError(t, d.LoadResources())
|
||||
@@ -106,134 +106,8 @@ func TestTemplateFuncsExamples(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
// TODO(bep) namespace remove when done
|
||||
in :=
|
||||
`
|
||||
crypto.MD5: {{ crypto.MD5 "Hello world, gophers!" }}
|
||||
dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }}
|
||||
htmlEscape 1: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}
|
||||
htmlEscape 2: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>"}}
|
||||
htmlUnescape 1: {{htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}
|
||||
htmlUnescape 2: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape | safeHTML}}
|
||||
htmlUnescape 3: {{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape }}
|
||||
htmlUnescape 4: {{ htmlEscape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlUnescape | safeHTML }}
|
||||
htmlUnescape 5: {{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlEscape | safeHTML }}
|
||||
print: {{ print "works!" }}
|
||||
printf: {{ printf "%s!" "works" }}
|
||||
println: {{ println "works!" -}}
|
||||
strings.TrimPrefix: {{ strings.TrimPrefix "Goodbye,, world!" "Goodbye," }}
|
||||
urlize: {{ "Bat Man" | urlize }}
|
||||
`
|
||||
|
||||
expected := `
|
||||
crypto.MD5: b3029f756f98f79e7f1b7f1d1f0dd53b
|
||||
dateFormat: Wednesday, Jan 21, 2015
|
||||
htmlEscape 1: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
|
||||
htmlEscape 2: Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;
|
||||
htmlUnescape 1: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
|
||||
htmlUnescape 2: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
|
||||
htmlUnescape 3: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
|
||||
htmlUnescape 4: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
|
||||
htmlUnescape 5: Cathal Garvey & The Sunshine Band <cathal@foo.bar>
|
||||
print: works!
|
||||
printf: works!
|
||||
println: works!
|
||||
strings.TrimPrefix: , world!
|
||||
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.TemplateHandler) error {
|
||||
if err := templ.AddTemplate("test", in); err != nil {
|
||||
t.Fatal("Got error on parse", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
config.Fs = fs
|
||||
|
||||
d, err := deps.New(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bep) it would be dandy to put this one into the partials package, but
|
||||
// we have some package cycle issues to solve first.
|
||||
func TestPartialCached(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := []struct {
|
||||
@@ -388,20 +262,3 @@ func newTestFuncsterWithViper(v *viper.Viper) *templateFuncster {
|
||||
|
||||
return d.Tmpl.(*templateHandler).html.funcster
|
||||
}
|
||||
|
||||
func newTestTemplate(t *testing.T, name, template string) tpl.Template {
|
||||
config := newDepsConfig(viper.New())
|
||||
config.WithTemplate = func(templ tpl.TemplateHandler) error {
|
||||
err := templ.AddTemplate(name, template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
de, err := deps.New(config)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, de.LoadResources())
|
||||
|
||||
return de.Tmpl.Lookup(name)
|
||||
}
|
||||
|
@@ -1,127 +0,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 tplimpl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/hugo/deps"
|
||||
|
||||
"github.com/spf13/hugo/tpl"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// 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.TemplateHandler) error {
|
||||
return templ.AddTemplate("fuzz", this.data)
|
||||
}
|
||||
|
||||
de, err := deps.New(config)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, de.LoadResources())
|
||||
|
||||
templ := de.Tmpl.(*templateHandler)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
tt := de.Tmpl.Lookup("fuzz")
|
||||
require.NotNil(t, tt)
|
||||
err = tt.Execute(ioutil.Discard, 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
|
||||
}
|
Reference in New Issue
Block a user