tplimpl: Allow text partials in HTML templates

Most obvius benefit of this is to include CSS partials with css file suffix into HTML templates.

A valid workaround would be to rename the file `mystyles.html`, but that doesn't work too good for external editors etc.

The css partial is  a method used in some themes before Hugo 0.20, but then it stopped working.

This commit reintroduces that behaviour.

Note that the regular layout lookups for text templates, i.e. "single.json" will be
prefixed with "_text/" on lookup and will only match in the text collection.

Fixes #3273
This commit is contained in:
Bjørn Erik Pedersen 2017-04-15 11:33:53 +02:00
parent 8ccee6717c
commit 1cf29200b4
4 changed files with 37 additions and 33 deletions

View file

@ -97,23 +97,21 @@ func (t *templateHandler) PrintErrors() {
// Lookup tries to find a template with the given name in both template // Lookup tries to find a template with the given name in both template
// collections: First HTML, then the plain text template collection. // collections: First HTML, then the plain text template collection.
func (t *templateHandler) Lookup(name string) *tpl.TemplateAdapter { func (t *templateHandler) Lookup(name string) *tpl.TemplateAdapter {
var te *tpl.TemplateAdapter
isTextTemplate := strings.HasPrefix(name, textTmplNamePrefix) if strings.HasPrefix(name, textTmplNamePrefix) {
// The caller has explicitly asked for a text template, so only look
if isTextTemplate { // in the text template collection.
// The templates are stored without the prefix identificator. // The templates are stored without the prefix identificator.
name = strings.TrimPrefix(name, textTmplNamePrefix) name = strings.TrimPrefix(name, textTmplNamePrefix)
te = t.text.Lookup(name) return t.text.Lookup(name)
} else {
te = t.html.Lookup(name)
}
if te == nil {
return nil
} }
// Look in both
if te := t.html.Lookup(name); te != nil {
return te return te
}
return t.text.Lookup(name)
} }
func (t *templateHandler) clone(d *deps.Deps) *templateHandler { func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
@ -459,9 +457,10 @@ func (t *templateHandler) loadTemplates(absPath string, prefix string) {
func (t *templateHandler) initFuncs() { func (t *templateHandler) initFuncs() {
// The template funcs need separation between text and html templates. // Both template types will get their own funcster instance, which
// in the current case contains the same set of funcs.
for _, funcsterHolder := range []templateFuncsterTemplater{t.html, t.text} { for _, funcsterHolder := range []templateFuncsterTemplater{t.html, t.text} {
funcster := newTemplateFuncster(t.Deps, funcsterHolder) funcster := newTemplateFuncster(t.Deps)
// The URL funcs in the funcMap is somewhat language dependent, // The URL funcs in the funcMap is somewhat language dependent,
// so we need to wait until the language and site config is loaded. // so we need to wait until the language and site config is loaded.

View file

@ -17,6 +17,7 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"strings" "strings"
texttemplate "text/template"
bp "github.com/spf13/hugo/bufferpool" bp "github.com/spf13/hugo/bufferpool"
@ -31,17 +32,12 @@ type templateFuncster struct {
cachedPartials partialCache cachedPartials partialCache
image *imageHandler image *imageHandler
// Make sure each funcster gets its own TemplateFinder to get
// proper text and HTML template separation.
Tmpl templateFuncsterTemplater
*deps.Deps *deps.Deps
} }
func newTemplateFuncster(deps *deps.Deps, t templateFuncsterTemplater) *templateFuncster { func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
return &templateFuncster{ return &templateFuncster{
Deps: deps, Deps: deps,
Tmpl: t,
cachedPartials: partialCache{p: make(map[string]interface{})}, cachedPartials: partialCache{p: make(map[string]interface{})},
image: &imageHandler{fs: deps.Fs, imageConfigCache: map[string]image.Config{}}, image: &imageHandler{fs: deps.Fs, imageConfigCache: map[string]image.Config{}},
} }
@ -75,14 +71,12 @@ func (t *templateFuncster) partial(name string, contextList ...interface{}) (int
return "", err return "", err
} }
switch t.Tmpl.(type) { if _, ok := templ.Template.(*texttemplate.Template); ok {
case *htmlTemplates:
return template.HTML(b.String()), nil
case *textTemplates:
return b.String(), nil return b.String(), nil
default:
panic("Unknown type")
} }
return template.HTML(b.String()), nil
} }
} }

View file

@ -2221,5 +2221,5 @@ func (t *templateFuncster) initFuncMap() {
} }
t.funcMap = funcMap t.funcMap = funcMap
t.Tmpl.setFuncs(funcMap) t.Tmpl.(*templateHandler).setFuncs(funcMap)
} }

View file

@ -2869,20 +2869,27 @@ func TestPartialHTMLAndText(t *testing.T) {
} }
config.WithTemplate = func(templ tpl.TemplateHandler) error { config.WithTemplate = func(templ tpl.TemplateHandler) error {
if err := templ.AddTemplate("htmlTemplate.html", `HTML Test Partial: {{ partial "test.foo" . -}}`); err != nil { if err := templ.AddTemplate("htmlTemplate.html", `HTML Test|HTML:{{ partial "test.html" . -}}|Text:{{ partial "test.txt" . }}
CSS plain: <style type="text/css">{{ partial "mystyles.css" . -}}</style>
CSS safe: <style type="text/css">{{ partial "mystyles.css" . | safeCSS -}}</style>
`); err != nil {
return err return err
} }
if err := templ.AddTemplate("_text/textTemplate.txt", `Text Test Partial: {{ partial "test.foo" . -}}`); err != nil { if err := templ.AddTemplate("_text/textTemplate.txt", `Text Test|HTML:{{ partial "test.html" . -}}|Text:{{ partial "test.txt" . }}
CSS plain: <style type="text/css">{{ partial "mystyles.css" . -}}</style>`); err != nil {
return err return err
} }
// Use "foo" here to say that the extension doesn't really matter in this scenario. if err := templ.AddTemplate("partials/test.html", "HTML Name: {{ .Name }}"); err != nil {
// It will look for templates in "partials/test.foo" and "partials/test.foo.html".
if err := templ.AddTemplate("partials/test.foo", "HTML Name: {{ .Name }}"); err != nil {
return err return err
} }
if err := templ.AddTemplate("_text/partials/test.foo", "Text Name: {{ .Name }}"); err != nil { if err := templ.AddTemplate("_text/partials/test.txt", "Text Name: {{ .Name }}"); err != nil {
return err
}
if err := templ.AddTemplate("_text/partials/mystyles.css",
`body { background-color: blue; }
`); err != nil {
return err return err
} }
@ -2903,8 +2910,12 @@ func TestPartialHTMLAndText(t *testing.T) {
resultText, err := templ.ExecuteToString(data) resultText, err := templ.ExecuteToString(data)
require.NoError(t, err) require.NoError(t, err)
require.Contains(t, resultHTML, "HTML Test Partial: HTML Name: a&#43;b&#43;c") require.Contains(t, resultHTML, "HTML Test|HTML:HTML Name: a&#43;b&#43;c|Text:Text Name: a&#43;b&#43;c")
require.Contains(t, resultText, "Text Test Partial: Text Name: a+b+c") require.Contains(t, resultHTML, `CSS plain: <style type="text/css">ZgotmplZ</style>`)
require.Contains(t, resultHTML, `CSS safe: <style type="text/css">body { background-color: blue; }`)
require.Contains(t, resultText, "Text Test|HTML:HTML Name: a&#43;b&#43;c|Text:Text Name: a+b+c")
require.Contains(t, resultText, `CSS plain: <style type="text/css">body { background-color: blue; }`)
} }