mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
committed by
GitHub
parent
4ea4359ac1
commit
d6000a208c
@@ -30,10 +30,8 @@ import (
|
||||
"github.com/yosssi/ace"
|
||||
)
|
||||
|
||||
var localTemplates *template.Template
|
||||
|
||||
// TODO(bep) globals get rid of the reset of the jww.ERR etc.
|
||||
var tmpl *GoHTMLTemplate
|
||||
// TODO(bep) globals get rid of the rest of the jww.ERR etc.
|
||||
//var tmpl *GoHTMLTemplate
|
||||
|
||||
// TODO(bep) an interface with hundreds of methods ... remove it.
|
||||
// And unexport most of these methods.
|
||||
@@ -45,13 +43,13 @@ type Template interface {
|
||||
GetClone() *template.Template
|
||||
LoadTemplates(absPath string)
|
||||
LoadTemplatesWithPrefix(absPath, prefix string)
|
||||
MarkReady()
|
||||
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
|
||||
PrintErrors()
|
||||
Funcs(funcMap template.FuncMap)
|
||||
}
|
||||
|
||||
type templateErr struct {
|
||||
@@ -60,7 +58,8 @@ type templateErr struct {
|
||||
}
|
||||
|
||||
type GoHTMLTemplate struct {
|
||||
template.Template
|
||||
*template.Template
|
||||
|
||||
clone *template.Template
|
||||
|
||||
// a separate storage for the overlays created from cloned master templates.
|
||||
@@ -69,41 +68,54 @@ type GoHTMLTemplate struct {
|
||||
|
||||
errors []*templateErr
|
||||
|
||||
funcster *templateFuncster
|
||||
|
||||
// TODO(bep) globals template
|
||||
log *jww.Notepad
|
||||
}
|
||||
|
||||
// InitializeT resets the internal template state to its initial state
|
||||
func InitializeT(logger *jww.Notepad) *GoHTMLTemplate {
|
||||
tmpl = New(logger)
|
||||
return tmpl
|
||||
}
|
||||
|
||||
// New returns a new Hugo Template System
|
||||
// with all the additional features, templates & functions
|
||||
func New(logger *jww.Notepad) *GoHTMLTemplate {
|
||||
var templates = &GoHTMLTemplate{
|
||||
Template: *template.New(""),
|
||||
func New(logger *jww.Notepad, withTemplate ...func(templ Template) error) *GoHTMLTemplate {
|
||||
tmpl := &GoHTMLTemplate{
|
||||
Template: template.New(""),
|
||||
overlays: make(map[string]*template.Template),
|
||||
errors: make([]*templateErr, 0),
|
||||
log: logger,
|
||||
}
|
||||
|
||||
localTemplates = &templates.Template
|
||||
tmpl.funcster = newTemplateFuncster(tmpl)
|
||||
|
||||
// The URL funcs in the funcMap is somewhat language dependent,
|
||||
// so we need to wait until the language and site config is loaded.
|
||||
initFuncMap()
|
||||
// TODO(bep) globals
|
||||
tmpl.funcster.initFuncMap()
|
||||
|
||||
for k, v := range funcMap {
|
||||
// TODO(bep) globals
|
||||
for k, v := range tmpl.funcster.funcMap {
|
||||
amber.FuncMap[k] = v
|
||||
}
|
||||
templates.Funcs(funcMap)
|
||||
templates.LoadEmbedded()
|
||||
return templates
|
||||
|
||||
tmpl.LoadEmbedded()
|
||||
|
||||
for _, wt := range withTemplate {
|
||||
err := wt(tmpl)
|
||||
if err != nil {
|
||||
tmpl.errors = append(tmpl.errors, &templateErr{"init", err})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tmpl.markReady()
|
||||
|
||||
return tmpl
|
||||
}
|
||||
|
||||
func partial(name string, contextList ...interface{}) template.HTML {
|
||||
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:]
|
||||
}
|
||||
@@ -114,16 +126,16 @@ func partial(name string, contextList ...interface{}) template.HTML {
|
||||
} else {
|
||||
context = contextList[0]
|
||||
}
|
||||
return ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name)
|
||||
return t.ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name)
|
||||
}
|
||||
|
||||
func executeTemplate(context interface{}, w io.Writer, layouts ...string) {
|
||||
func (t *GoHTMLTemplate) executeTemplate(context interface{}, w io.Writer, layouts ...string) {
|
||||
var worked bool
|
||||
for _, layout := range layouts {
|
||||
templ := Lookup(layout)
|
||||
templ := t.Lookup(layout)
|
||||
if templ == nil {
|
||||
layout += ".html"
|
||||
templ = Lookup(layout)
|
||||
templ = t.Lookup(layout)
|
||||
}
|
||||
|
||||
if templ != nil {
|
||||
@@ -136,28 +148,20 @@ func executeTemplate(context interface{}, w io.Writer, layouts ...string) {
|
||||
}
|
||||
}
|
||||
if !worked {
|
||||
tmpl.log.ERROR.Println("Unable to render", layouts)
|
||||
tmpl.log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
|
||||
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 ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML {
|
||||
func (t *GoHTMLTemplate) ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML {
|
||||
b := bp.GetBuffer()
|
||||
defer bp.PutBuffer(b)
|
||||
executeTemplate(context, b, layouts...)
|
||||
t.executeTemplate(context, b, layouts...)
|
||||
return template.HTML(b.String())
|
||||
}
|
||||
|
||||
func Lookup(name string) *template.Template {
|
||||
return tmpl.Lookup(name)
|
||||
}
|
||||
|
||||
func (t *GoHTMLTemplate) Lookup(name string) *template.Template {
|
||||
|
||||
if templ := localTemplates.Lookup(name); templ != nil {
|
||||
return templ
|
||||
}
|
||||
|
||||
if t.overlays != nil {
|
||||
if templ, ok := t.overlays[name]; ok {
|
||||
return templ
|
||||
@@ -183,9 +187,9 @@ func (t *GoHTMLTemplate) LoadEmbedded() {
|
||||
t.EmbedTemplates()
|
||||
}
|
||||
|
||||
// MarkReady marks the template as "ready for execution". No changes allowed
|
||||
// markReady marks the template as "ready for execution". No changes allowed
|
||||
// after this is set.
|
||||
func (t *GoHTMLTemplate) MarkReady() {
|
||||
func (t *GoHTMLTemplate) markReady() {
|
||||
if t.clone == nil {
|
||||
t.clone = template.Must(t.Template.Clone())
|
||||
}
|
||||
@@ -522,7 +526,7 @@ func (t *GoHTMLTemplate) LoadTemplates(absPath string) {
|
||||
}
|
||||
|
||||
func (t *GoHTMLTemplate) PrintErrors() {
|
||||
for _, e := range t.errors {
|
||||
t.log.ERROR.Println(e.err)
|
||||
for i, e := range t.errors {
|
||||
t.log.ERROR.Println(i, ":", e.err)
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ import (
|
||||
|
||||
"html/template"
|
||||
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -265,7 +264,3 @@ P2: {{ .Params.LOWER }}
|
||||
require.Contains(t, result, "P1: P1L")
|
||||
require.Contains(t, result, "P2: P1L")
|
||||
}
|
||||
|
||||
func init() {
|
||||
jww.SetStdoutThreshold(jww.LevelCritical)
|
||||
}
|
||||
|
@@ -54,9 +54,19 @@ import (
|
||||
_ "image/png"
|
||||
)
|
||||
|
||||
var (
|
||||
funcMap template.FuncMap
|
||||
)
|
||||
// Some of the template funcs are'nt entirely stateless.
|
||||
type templateFuncster struct {
|
||||
t *GoHTMLTemplate
|
||||
funcMap template.FuncMap
|
||||
cachedPartials partialCache
|
||||
}
|
||||
|
||||
func newTemplateFuncster(t *GoHTMLTemplate) *templateFuncster {
|
||||
return &templateFuncster{
|
||||
t: t,
|
||||
cachedPartials: partialCache{p: make(map[string]template.HTML)},
|
||||
}
|
||||
}
|
||||
|
||||
// eq returns the boolean truth of arg1 == arg2.
|
||||
func eq(x, y interface{}) bool {
|
||||
@@ -1003,7 +1013,7 @@ func where(seq, key interface{}, args ...interface{}) (interface{}, error) {
|
||||
}
|
||||
|
||||
// apply takes a map, array, or slice and returns a new slice with the function fname applied over it.
|
||||
func apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) {
|
||||
func (tf *templateFuncster) apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) {
|
||||
if seq == nil {
|
||||
return make([]interface{}, 0), nil
|
||||
}
|
||||
@@ -1018,7 +1028,7 @@ func apply(seq interface{}, fname string, args ...interface{}) (interface{}, err
|
||||
return nil, errors.New("can't iterate over a nil value")
|
||||
}
|
||||
|
||||
fn, found := funcMap[fname]
|
||||
fn, found := tf.funcMap[fname]
|
||||
if !found {
|
||||
return nil, errors.New("can't find function " + fname)
|
||||
}
|
||||
@@ -1518,41 +1528,39 @@ type partialCache struct {
|
||||
// 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 (c *partialCache) Get(key, name string, context interface{}) (p template.HTML) {
|
||||
func (tf *templateFuncster) Get(key, name string, context interface{}) (p template.HTML) {
|
||||
var ok bool
|
||||
|
||||
c.RLock()
|
||||
p, ok = c.p[key]
|
||||
c.RUnlock()
|
||||
tf.cachedPartials.RLock()
|
||||
p, ok = tf.cachedPartials.p[key]
|
||||
tf.cachedPartials.RUnlock()
|
||||
|
||||
if ok {
|
||||
return p
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
if p, ok = c.p[key]; !ok {
|
||||
p = partial(name, context)
|
||||
c.p[key] = p
|
||||
tf.cachedPartials.Lock()
|
||||
if p, ok = tf.cachedPartials.p[key]; !ok {
|
||||
p = tf.t.partial(name, context)
|
||||
tf.cachedPartials.p[key] = p
|
||||
}
|
||||
c.Unlock()
|
||||
tf.cachedPartials.Unlock()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
var cachedPartials = partialCache{p: make(map[string]template.HTML)}
|
||||
|
||||
// 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 partialCached(name string, context interface{}, variant ...string) template.HTML {
|
||||
func (tf *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 cachedPartials.Get(key, name, context)
|
||||
return tf.Get(key, name, context)
|
||||
}
|
||||
|
||||
// regexpCache represents a cache of regexp objects protected by a mutex.
|
||||
@@ -2090,8 +2098,8 @@ func getenv(key interface{}) (string, error) {
|
||||
return os.Getenv(skey), nil
|
||||
}
|
||||
|
||||
func initFuncMap() {
|
||||
funcMap = template.FuncMap{
|
||||
func (tf *templateFuncster) initFuncMap() {
|
||||
funcMap := template.FuncMap{
|
||||
"absURL": absURL,
|
||||
"absLangURL": func(i interface{}) (template.HTML, error) {
|
||||
s, err := cast.ToStringE(i)
|
||||
@@ -2102,7 +2110,7 @@ func initFuncMap() {
|
||||
},
|
||||
"add": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '+') },
|
||||
"after": after,
|
||||
"apply": apply,
|
||||
"apply": tf.apply,
|
||||
"base64Decode": base64Decode,
|
||||
"base64Encode": base64Encode,
|
||||
"chomp": chomp,
|
||||
@@ -2147,8 +2155,8 @@ func initFuncMap() {
|
||||
"mul": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '*') },
|
||||
"ne": ne,
|
||||
"now": func() time.Time { return time.Now() },
|
||||
"partial": partial,
|
||||
"partialCached": partialCached,
|
||||
"partial": tf.t.partial,
|
||||
"partialCached": tf.partialCached,
|
||||
"plainify": plainify,
|
||||
"pluralize": pluralize,
|
||||
"querify": querify,
|
||||
@@ -2195,4 +2203,7 @@ func initFuncMap() {
|
||||
"i18n": i18nTranslate,
|
||||
"T": i18nTranslate,
|
||||
}
|
||||
|
||||
tf.funcMap = funcMap
|
||||
tf.t.Funcs(funcMap)
|
||||
}
|
||||
|
@@ -1960,40 +1960,43 @@ func TestMarkdownify(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApply(t *testing.T) {
|
||||
|
||||
f := newTestFuncster()
|
||||
|
||||
strings := []interface{}{"a\n", "b\n"}
|
||||
noStringers := []interface{}{tstNoStringer{}, tstNoStringer{}}
|
||||
|
||||
chomped, _ := apply(strings, "chomp", ".")
|
||||
chomped, _ := f.apply(strings, "chomp", ".")
|
||||
assert.Equal(t, []interface{}{template.HTML("a"), template.HTML("b")}, chomped)
|
||||
|
||||
chomped, _ = apply(strings, "chomp", "c\n")
|
||||
chomped, _ = f.apply(strings, "chomp", "c\n")
|
||||
assert.Equal(t, []interface{}{template.HTML("c"), template.HTML("c")}, chomped)
|
||||
|
||||
chomped, _ = apply(nil, "chomp", ".")
|
||||
chomped, _ = f.apply(nil, "chomp", ".")
|
||||
assert.Equal(t, []interface{}{}, chomped)
|
||||
|
||||
_, err := apply(strings, "apply", ".")
|
||||
_, err := f.apply(strings, "apply", ".")
|
||||
if err == nil {
|
||||
t.Errorf("apply with apply should fail")
|
||||
}
|
||||
|
||||
var nilErr *error
|
||||
_, err = apply(nilErr, "chomp", ".")
|
||||
_, err = f.apply(nilErr, "chomp", ".")
|
||||
if err == nil {
|
||||
t.Errorf("apply with nil in seq should fail")
|
||||
}
|
||||
|
||||
_, err = apply(strings, "dobedobedo", ".")
|
||||
_, err = f.apply(strings, "dobedobedo", ".")
|
||||
if err == nil {
|
||||
t.Errorf("apply with unknown func should fail")
|
||||
}
|
||||
|
||||
_, err = apply(noStringers, "chomp", ".")
|
||||
_, err = f.apply(noStringers, "chomp", ".")
|
||||
if err == nil {
|
||||
t.Errorf("apply when func fails should fail")
|
||||
}
|
||||
|
||||
_, err = apply(tstNoStringer{}, "chomp", ".")
|
||||
_, err = f.apply(tstNoStringer{}, "chomp", ".")
|
||||
if err == nil {
|
||||
t.Errorf("apply with non-sequence should fail")
|
||||
}
|
||||
@@ -2780,7 +2783,6 @@ func TestPartialCached(t *testing.T) {
|
||||
data.Params = map[string]interface{}{"langCode": "en"}
|
||||
|
||||
tstInitTemplates()
|
||||
InitializeT(logger)
|
||||
for i, tc := range testCases {
|
||||
var tmp string
|
||||
if tc.variant != "" {
|
||||
@@ -2831,7 +2833,6 @@ func TestPartialCached(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkPartial(b *testing.B) {
|
||||
InitializeT(logger)
|
||||
tmpl, err := New(logger).New("testroot").Parse(`{{ partial "bench1" . }}`)
|
||||
if err != nil {
|
||||
b.Fatalf("unable to create new html template: %s", err)
|
||||
@@ -2851,7 +2852,6 @@ func BenchmarkPartial(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkPartialCached(b *testing.B) {
|
||||
InitializeT(logger)
|
||||
tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . }}`)
|
||||
if err != nil {
|
||||
b.Fatalf("unable to create new html template: %s", err)
|
||||
@@ -2871,7 +2871,6 @@ func BenchmarkPartialCached(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkPartialCachedVariants(b *testing.B) {
|
||||
InitializeT(logger)
|
||||
tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . "header" }}`)
|
||||
if err != nil {
|
||||
b.Fatalf("unable to create new html template: %s", err)
|
||||
@@ -2889,3 +2888,7 @@ func BenchmarkPartialCachedVariants(b *testing.B) {
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func newTestFuncster() *templateFuncster {
|
||||
return New(logger).funcster
|
||||
}
|
||||
|
@@ -55,8 +55,6 @@ html lang=en
|
||||
|
||||
for _, root := range []string{"", os.TempDir()} {
|
||||
|
||||
templ := New(logger)
|
||||
|
||||
basePath := this.basePath
|
||||
innerPath := this.innerPath
|
||||
|
||||
@@ -70,17 +68,20 @@ html lang=en
|
||||
|
||||
d := "DATA"
|
||||
|
||||
err := templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
|
||||
[]byte(this.baseContent), []byte(this.innerContent))
|
||||
templ := New(logger, func(templ Template) error {
|
||||
return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
|
||||
[]byte(this.baseContent), []byte(this.innerContent))
|
||||
|
||||
if err != nil && this.expectErr == 0 {
|
||||
t.Errorf("Test %d with root '%s' errored: %s", i, root, err)
|
||||
} else if err == nil && this.expectErr == 1 {
|
||||
})
|
||||
|
||||
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 = templ.ExecuteTemplate(&buff, "mytemplate.html", d)
|
||||
err := templ.ExecuteTemplate(&buff, "mytemplate.html", d)
|
||||
|
||||
if err != nil && this.expectErr == 0 {
|
||||
t.Errorf("Test %d with root '%s' errored: %s", i, root, err)
|
||||
@@ -245,7 +246,6 @@ func TestTplGoFuzzReports(t *testing.T) {
|
||||
// Issue #1095
|
||||
{"{{apply .C \"urlize\" " +
|
||||
"\".\"}}", 2}} {
|
||||
templ := New(logger)
|
||||
|
||||
d := &Data{
|
||||
A: 42,
|
||||
@@ -258,15 +258,17 @@ func TestTplGoFuzzReports(t *testing.T) {
|
||||
H: "a,b,c,d,e,f",
|
||||
}
|
||||
|
||||
err := templ.AddTemplate("fuzz", this.data)
|
||||
templ := New(logger, func(templ Template) error {
|
||||
return templ.AddTemplate("fuzz", this.data)
|
||||
|
||||
if err != nil && this.expectErr == 0 {
|
||||
t.Fatalf("Test %d errored: %s", i, err)
|
||||
} else if err == nil && this.expectErr == 1 {
|
||||
t.Fatalf("#1 Test %d should have errored", i)
|
||||
})
|
||||
|
||||
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 = templ.ExecuteTemplate(ioutil.Discard, "fuzz", d)
|
||||
err := templ.ExecuteTemplate(ioutil.Discard, "fuzz", d)
|
||||
|
||||
if err != nil && this.expectErr == 0 {
|
||||
t.Fatalf("Test %d errored: %s", i, err)
|
||||
|
Reference in New Issue
Block a user