summaryrefslogtreecommitdiffstats
path: root/hugolib
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-03-27 20:43:49 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-04-02 23:13:10 +0200
commit8b5b558bb515e80da640f5e114169874771b61e4 (patch)
tree5755e77efe3120963d012dccbd91d916180485e0 /hugolib
parent27610ddd011e8172d00e02275f948c3f1ed43e4f (diff)
tpl: Rework to handle both text and HTML templates
Before this commit, Hugo used `html/template` for all Go templates. While this is a fine choice for HTML and maybe also RSS feeds, it is painful for plain text formats such as CSV, JSON etc. This commit fixes that by using the `IsPlainText` attribute on the output format to decide what to use. A couple of notes: * The above requires a nonambiguous template name to type mapping. I.e. `/layouts/_default/list.json` will only work if there is only one JSON output format, `/layouts/_default/list.mytype.json` will always work. * Ambiguous types will fall back to HTML. * Partials inherits the text vs HTML identificator of the container template. This also means that plain text templates can only include plain text partials. * Shortcode templates are, by definition, currently HTML templates only. Fixes #3221
Diffstat (limited to 'hugolib')
-rw-r--r--hugolib/alias.go27
-rw-r--r--hugolib/embedded_shortcodes_test.go8
-rw-r--r--hugolib/hugo_sites.go8
-rw-r--r--hugolib/menu_test.go4
-rw-r--r--hugolib/page.go3
-rw-r--r--hugolib/page_output.go24
-rw-r--r--hugolib/shortcode.go16
-rw-r--r--hugolib/shortcode_test.go103
-rw-r--r--hugolib/site.go32
-rw-r--r--hugolib/site_output_test.go75
-rw-r--r--hugolib/site_test.go18
-rw-r--r--hugolib/sitemap_test.go2
-rw-r--r--hugolib/testhelpers_test.go15
13 files changed, 201 insertions, 134 deletions
diff --git a/hugolib/alias.go b/hugolib/alias.go
index d5eb35777..d1a1b5534 100644
--- a/hugolib/alias.go
+++ b/hugolib/alias.go
@@ -22,6 +22,8 @@ import (
"runtime"
"strings"
+ "github.com/spf13/hugo/tpl"
+
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/hugo/helpers"
@@ -35,18 +37,19 @@ const (
var defaultAliasTemplates *template.Template
func init() {
+ //TODO(bep) consolidate
defaultAliasTemplates = template.New("")
template.Must(defaultAliasTemplates.New("alias").Parse(alias))
template.Must(defaultAliasTemplates.New("alias-xhtml").Parse(aliasXHtml))
}
type aliasHandler struct {
- Templates *template.Template
+ t tpl.TemplateHandler
log *jww.Notepad
allowRoot bool
}
-func newAliasHandler(t *template.Template, l *jww.Notepad, allowRoot bool) aliasHandler {
+func newAliasHandler(t tpl.TemplateHandler, l *jww.Notepad, allowRoot bool) aliasHandler {
return aliasHandler{t, l, allowRoot}
}
@@ -56,12 +59,19 @@ func (a aliasHandler) renderAlias(isXHTML bool, permalink string, page *Page) (i
t = "alias-xhtml"
}
- template := defaultAliasTemplates
- if a.Templates != nil {
- template = a.Templates
- t = "alias.html"
+ var templ *tpl.TemplateAdapter
+
+ if a.t != nil {
+ templ = a.t.Lookup("alias.html")
}
+ if templ == nil {
+ def := defaultAliasTemplates.Lookup(t)
+ if def != nil {
+ templ = &tpl.TemplateAdapter{Template: def}
+ }
+
+ }
data := struct {
Permalink string
Page *Page
@@ -71,7 +81,7 @@ func (a aliasHandler) renderAlias(isXHTML bool, permalink string, page *Page) (i
}
buffer := new(bytes.Buffer)
- err := template.ExecuteTemplate(buffer, t, data)
+ err := templ.Execute(buffer, data)
if err != nil {
return nil, err
}
@@ -83,8 +93,7 @@ func (s *Site) writeDestAlias(path, permalink string, p *Page) (err error) {
}
func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, p *Page) (err error) {
-
- handler := newAliasHandler(s.Tmpl.Lookup("alias.html"), s.Log, allowRoot)
+ handler := newAliasHandler(s.Tmpl, s.Log, allowRoot)
isXHTML := strings.HasSuffix(path, ".xhtml")
diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go
index 92821d0ef..45de0bf09 100644
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -335,8 +335,8 @@ func TestShortcodeTweet(t *testing.T) {
th = testHelper{cfg, fs, t}
)
- withTemplate := func(templ tpl.Template) error {
- templ.Funcs(tweetFuncMap)
+ withTemplate := func(templ tpl.TemplateHandler) error {
+ templ.(tpl.TemplateTestMocker).SetFuncs(tweetFuncMap)
return nil
}
@@ -390,8 +390,8 @@ func TestShortcodeInstagram(t *testing.T) {
th = testHelper{cfg, fs, t}
)
- withTemplate := func(templ tpl.Template) error {
- templ.Funcs(instagramFuncMap)
+ withTemplate := func(templ tpl.TemplateHandler) error {
+ templ.(tpl.TemplateTestMocker).SetFuncs(instagramFuncMap)
return nil
}
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 2682e524d..491bbd809 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -129,11 +129,11 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
return newHugoSites(cfg, sites...)
}
-func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.Template) error) func(templ tpl.Template) error {
- return func(templ tpl.Template) error {
- templ.LoadTemplates(s.PathSpec.GetLayoutDirPath())
+func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateHandler) error) func(templ tpl.TemplateHandler) error {
+ return func(templ tpl.TemplateHandler) error {
+ templ.LoadTemplates(s.PathSpec.GetLayoutDirPath(), "")
if s.PathSpec.ThemeSet() {
- templ.LoadTemplatesWithPrefix(s.PathSpec.GetThemeDir()+"/layouts", "theme")
+ templ.LoadTemplates(s.PathSpec.GetThemeDir()+"/layouts", "theme")
}
for _, wt := range withTemplates {
diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go
index 0c3badc7b..f044fb5e0 100644
--- a/hugolib/menu_test.go
+++ b/hugolib/menu_test.go
@@ -18,6 +18,8 @@ import (
"fmt"
+ "github.com/spf13/afero"
+
"github.com/stretchr/testify/require"
)
@@ -42,7 +44,7 @@ title = "Section Menu"
sectionPagesMenu = "sect"
`
- th, h := newTestSitesFromConfig(t, siteConfig,
+ th, h := newTestSitesFromConfig(t, afero.NewMemMapFs(), siteConfig,
"layouts/partials/menu.html", `{{- $p := .page -}}
{{- $m := .menu -}}
{{ range (index $p.Site.Menus $m) -}}
diff --git a/hugolib/page.go b/hugolib/page.go
index fa9f40922..5a04c6ce7 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -1384,13 +1384,14 @@ func (p *Page) prepareLayouts() error {
if p.Kind == KindPage {
if !p.IsRenderable() {
self := "__" + p.UniqueID()
- _, err := p.s.Tmpl.GetClone().New(self).Parse(string(p.Content))
+ err := p.s.Tmpl.AddLateTemplate(self, string(p.Content))
if err != nil {
return err
}
p.selfLayout = self
}
}
+
return nil
}
diff --git a/hugolib/page_output.go b/hugolib/page_output.go
index f47343cb5..58d09d688 100644
--- a/hugolib/page_output.go
+++ b/hugolib/page_output.go
@@ -110,9 +110,29 @@ func (p *PageOutput) Render(layout ...string) template.HTML {
l, err := p.layouts(layout...)
if err != nil {
helpers.DistinctErrorLog.Printf("in .Render: Failed to resolve layout %q for page %q", layout, p.pathOrTitle())
- return template.HTML("")
+ return ""
}
- return p.s.Tmpl.ExecuteTemplateToHTML(p, l...)
+
+ for _, layout := range l {
+ templ := p.s.Tmpl.Lookup(layout)
+ if templ == nil {
+ // This is legacy from when we had only one output format and
+ // HTML templates only. Some have references to layouts without suffix.
+ // We default to good old HTML.
+ templ = p.s.Tmpl.Lookup(layout + ".html")
+ }
+ if templ != nil {
+ res, err := templ.ExecuteToString(p)
+ if err != nil {
+ helpers.DistinctErrorLog.Printf("in .Render: Failed to execute template %q for page %q", layout, p.pathOrTitle())
+ return template.HTML("")
+ }
+ return template.HTML(res)
+ }
+ }
+
+ return ""
+
}
func (p *Page) Render(layout ...string) template.HTML {
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index d165c778b..d72a96faa 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -177,7 +177,7 @@ var isInnerShortcodeCache = struct {
// to avoid potential costly look-aheads for closing tags we look inside the template itself
// we could change the syntax to self-closing tags, but that would make users cry
// the value found is cached
-func isInnerShortcode(t *template.Template) (bool, error) {
+func isInnerShortcode(t tpl.TemplateExecutor) (bool, error) {
isInnerShortcodeCache.RLock()
m, ok := isInnerShortcodeCache.m[t.Name()]
isInnerShortcodeCache.RUnlock()
@@ -188,10 +188,7 @@ func isInnerShortcode(t *template.Template) (bool, error) {
isInnerShortcodeCache.Lock()
defer isInnerShortcodeCache.Unlock()
- if t.Tree == nil {
- return false, errors.New("Template failed to compile")
- }
- match, _ := regexp.MatchString("{{.*?\\.Inner.*?}}", t.Tree.Root.String())
+ match, _ := regexp.MatchString("{{.*?\\.Inner.*?}}", t.Tree())
isInnerShortcodeCache.m[t.Name()] = match
return match, nil
@@ -398,8 +395,6 @@ Loop:
case tScName:
sc.name = currItem.val
tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
- {
- }
if tmpl == nil {
return sc, fmt.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
}
@@ -570,7 +565,10 @@ func replaceShortcodeTokens(source []byte, prefix string, replacements map[strin
return source, nil
}
-func getShortcodeTemplate(name string, t tpl.Template) *template.Template {
+func getShortcodeTemplate(name string, t tpl.TemplateHandler) *tpl.TemplateAdapter {
+ isInnerShortcodeCache.RLock()
+ defer isInnerShortcodeCache.RUnlock()
+
if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
return x
}
@@ -580,7 +578,7 @@ func getShortcodeTemplate(name string, t tpl.Template) *template.Template {
return t.Lookup("_internal/shortcodes/" + name + ".html")
}
-func renderShortcodeWithPage(tmpl *template.Template, data *ShortcodeWithPage) string {
+func renderShortcodeWithPage(tmpl tpl.Template, data *ShortcodeWithPage) string {
buffer := bp.GetBuffer()
defer bp.PutBuffer(buffer)
diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
index 28b03aa9b..3d1922462 100644
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -30,7 +30,7 @@ import (
)
// TODO(bep) remove
-func pageFromString(in, filename string, withTemplate ...func(templ tpl.Template) error) (*Page, error) {
+func pageFromString(in, filename string, withTemplate ...func(templ tpl.TemplateHandler) error) (*Page, error) {
s := newTestSite(nil)
if len(withTemplate) > 0 {
// Have to create a new site
@@ -47,11 +47,11 @@ func pageFromString(in, filename string, withTemplate ...func(templ tpl.Template
return s.NewPageFrom(strings.NewReader(in), filename)
}
-func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error) {
+func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error) {
CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
}
-func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error, expectError bool) {
+func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error, expectError bool) {
cfg, fs := newTestCfg()
@@ -100,8 +100,9 @@ func TestNonSC(t *testing.T) {
// Issue #929
func TestHyphenatedSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.Template) error {
- tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
+ wt := func(tem tpl.TemplateHandler) error {
+
+ tem.AddTemplate("_internal/shortcodes/hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
return nil
}
@@ -111,8 +112,8 @@ func TestHyphenatedSC(t *testing.T) {
// Issue #1753
func TestNoTrailingNewline(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.Template) error {
- tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/a.html", `{{ .Get 0 }}`)
return nil
}
@@ -121,8 +122,8 @@ func TestNoTrailingNewline(t *testing.T) {
func TestPositionalParamSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.Template) error {
- tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ .Get 0 }}`)
return nil
}
@@ -135,8 +136,8 @@ func TestPositionalParamSC(t *testing.T) {
func TestPositionalParamIndexOutOfBounds(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.Template) error {
- tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ .Get 1 }}`)
return nil
}
CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video error: index out of range for positional param at position 1", wt)
@@ -146,8 +147,8 @@ func TestPositionalParamIndexOutOfBounds(t *testing.T) {
func TestNamedParamSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.Template) error {
- tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
return nil
}
CheckShortCodeMatch(t, `{{< img src="one" >}}`, `<img src="one">`, wt)
@@ -161,10 +162,10 @@ func TestNamedParamSC(t *testing.T) {
// Issue #2294
func TestNestedNamedMissingParam(t *testing.T) {
t.Parallel()
- 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>`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/acc.html", `<div class="acc">{{ .Inner }}</div>`)
+ tem.AddTemplate("_internal/shortcodes/div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
+ tem.AddTemplate("_internal/shortcodes/div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
return nil
}
CheckShortCodeMatch(t,
@@ -174,10 +175,10 @@ func TestNestedNamedMissingParam(t *testing.T) {
func TestIsNamedParamsSC(t *testing.T) {
t.Parallel()
- 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}}">`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/byposition.html", `<div id="{{ .Get 0 }}">`)
+ tem.AddTemplate("_internal/shortcodes/byname.html", `<div id="{{ .Get "id" }}">`)
+ tem.AddTemplate("_internal/shortcodes/ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
return nil
}
CheckShortCodeMatch(t, `{{< ifnamedparams id="name" >}}`, `<div id="name">`, wt)
@@ -190,8 +191,8 @@ func TestIsNamedParamsSC(t *testing.T) {
func TestInnerSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.Template) error {
- tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
CheckShortCodeMatch(t, `{{< inside class="aspen" >}}`, `<div class="aspen"></div>`, wt)
@@ -201,8 +202,8 @@ func TestInnerSC(t *testing.T) {
func TestInnerSCWithMarkdown(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.Template) error {
- tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
CheckShortCodeMatch(t, `{{% inside %}}
@@ -215,8 +216,8 @@ func TestInnerSCWithMarkdown(t *testing.T) {
func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.Template) error {
- tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
CheckShortCodeMatch(t, `{{% inside %}}
@@ -246,9 +247,9 @@ func TestEmbeddedSC(t *testing.T) {
func TestNestedSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.Template) error {
- tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
- tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
+ tem.AddTemplate("_internal/shortcodes/scn2.html", `<div>SC2</div>`)
return nil
}
CheckShortCodeMatch(t, `{{% scn1 %}}{{% scn2 %}}{{% /scn1 %}}`, "<div>Outer, inner is <div>SC2</div>\n</div>", wt)
@@ -258,10 +259,10 @@ func TestNestedSC(t *testing.T) {
func TestNestedComplexSC(t *testing.T) {
t.Parallel()
- 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-`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/row.html", `-row-{{ .Inner}}-rowStop-`)
+ tem.AddTemplate("_internal/shortcodes/column.html", `-col-{{.Inner }}-colStop-`)
+ tem.AddTemplate("_internal/shortcodes/aside.html", `-aside-{{ .Inner }}-asideStop-`)
return nil
}
CheckShortCodeMatch(t, `{{< row >}}1-s{{% column %}}2-**s**{{< aside >}}3-**s**{{< /aside >}}4-s{{% /column %}}5-s{{< /row >}}6-s`,
@@ -274,10 +275,10 @@ func TestNestedComplexSC(t *testing.T) {
func TestParentShortcode(t *testing.T) {
t.Parallel()
- 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 }}`)
+ wt := func(tem tpl.TemplateHandler) error {
+ tem.AddTemplate("_internal/shortcodes/r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
+ tem.AddTemplate("_internal/shortcodes/r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
+ tem.AddTemplate("_internal/shortcodes/r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
return nil
}
CheckShortCodeMatch(t, `{{< r1 pr1="p1" >}}1: {{< r2 pr2="p2" >}}2: {{< r3 pr3="p3" >}}{{< /r3 >}}{{< /r2 >}}{{< /r1 >}}`,
@@ -342,13 +343,13 @@ func TestExtractShortcodes(t *testing.T) {
fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
} {
- 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`)
- templ.AddInternalShortcode("inner.html", `{{with .Inner }}{{ . }}{{ end }}`)
- templ.AddInternalShortcode("inner2.html", `{{.Inner}}`)
- templ.AddInternalShortcode("inner3.html", `{{.Inner}}`)
+ p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.TemplateHandler) error {
+ templ.AddTemplate("_internal/shortcodes/tag.html", `tag`)
+ templ.AddTemplate("_internal/shortcodes/sc1.html", `sc1`)
+ templ.AddTemplate("_internal/shortcodes/sc2.html", `sc2`)
+ templ.AddTemplate("_internal/shortcodes/inner.html", `{{with .Inner }}{{ . }}{{ end }}`)
+ templ.AddTemplate("_internal/shortcodes/inner2.html", `{{.Inner}}`)
+ templ.AddTemplate("_internal/shortcodes/inner3.html", `{{.Inner}}`)
return nil
})
@@ -517,14 +518,14 @@ tags:
sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)}
}
- addTemplates := func(templ tpl.Template) error {
+ addTemplates := func(templ tpl.TemplateHandler) error {
templ.AddTemplate("_default/single.html", "{{.Content}}")
- templ.AddInternalShortcode("b.html", `b`)
- templ.AddInternalShortcode("c.html", `c`)
- templ.AddInternalShortcode("d.html", `d`)
- templ.AddInternalShortcode("menu.html", `{{ len (index .Page.Menus "main").Children }}`)
- templ.AddInternalShortcode("tags.html", `{{ len .Page.Site.Taxonomies.tags }}`)
+ templ.AddTemplate("_internal/shortcodes/b.html", `b`)
+ templ.AddTemplate("_internal/shortcodes/c.html", `c`)
+ templ.AddTemplate("_internal/shortcodes/d.html", `d`)
+ templ.AddTemplate("_internal/shortcodes/menu.html", `{{ len (index .Page.Menus "main").Children }}`)
+ templ.AddTemplate("_internal/shortcodes/tags.html", `{{ len .Page.Site.Taxonomies.tags }}`)
return nil
diff --git a/hugolib/site.go b/hugolib/site.go
index c42b938e8..40a7c44fb 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -188,7 +188,7 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) {
// 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 tpl.Template) error) (*Site, error) {
+func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateHandler) error) (*Site, error) {
v := viper.New()
loadDefaultSettingsFor(v)
return newSiteForLang(helpers.NewDefaultLanguage(v), withTemplate...)
@@ -197,15 +197,15 @@ func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) (*Site,
// 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 tpl.Template) error) (*Site, error) {
+func NewEnglishSite(withTemplate ...func(templ tpl.TemplateHandler) error) (*Site, error) {
v := viper.New()
loadDefaultSettingsFor(v)
return newSiteForLang(helpers.NewLanguage("en", v), withTemplate...)
}
// newSiteForLang creates a new site in the given language.
-func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tpl.Template) error) (*Site, error) {
- withTemplates := func(templ tpl.Template) error {
+func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tpl.TemplateHandler) error) (*Site, error) {
+ withTemplates := func(templ tpl.TemplateHandler) error {
for _, wt := range withTemplate {
if err := wt(templ); err != nil {
return err
@@ -1906,13 +1906,13 @@ Your rendered home page is blank: /index.html is zero-length
}
func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts ...string) error {
- layout, found := s.findFirstLayout(layouts...)
- if !found {
+ templ := s.findFirstTemplate(layouts...)
+ if templ == nil {
helpers.DistinctWarnLog.Printf("[%s] Unable to locate layout for %s: %s\n", s.Language.Lang, name, layouts)
return nil
}
- if err := s.renderThing(d, layout, w); err != nil {
+ if err := templ.Execute(w, d); err != nil {
// Behavior here should be dependent on if running in server or watch mode.
helpers.DistinctErrorLog.Printf("Error while rendering %q: %s", name, err)
@@ -1927,23 +1927,13 @@ func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts
return nil
}
-func (s *Site) findFirstLayout(layouts ...string) (string, bool) {
+func (s *Site) findFirstTemplate(layouts ...string) tpl.Template {
for _, layout := range layouts {
- if s.Tmpl.Lookup(layout) != nil {
- return layout, true
+ if templ := s.Tmpl.Lookup(layout); templ != nil {
+ return templ
}
}
- return "", false
-}
-
-func (s *Site) renderThing(d interface{}, layout string, w io.Writer) error {
-
- // If the template doesn't exist, then return, but leave the Writer open
- if templ := s.Tmpl.Lookup(layout); templ != nil {
- return templ.Execute(w, d)
- }
- return fmt.Errorf("Layout not found: %s", layout)
-
+ return nil
}
func (s *Site) publish(path string, r io.Reader) (err error) {
diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go
index 86e1a55ca..0edd8e1f0 100644
--- a/hugolib/site_output_test.go
+++ b/hugolib/site_output_test.go
@@ -18,6 +18,8 @@ import (
"strings"
"testing"
+ "github.com/spf13/afero"
+
"github.com/stretchr/testify/require"
"fmt"
@@ -75,6 +77,21 @@ disableKinds = ["page", "section", "taxonomy", "taxonomyTerm", "RSS", "sitemap",
[Taxonomies]
tag = "tags"
category = "categories"
+
+defaultContentLanguage = "en"
+
+[languages]
+
+[languages.en]
+title = "Title in English"
+languageName = "English"
+weight = 1
+
+[languages.nn]
+languageName = "Nynorsk"
+weight = 2
+title = "Tittel på Nynorsk"
+
`
pageTemplate := `---
@@ -84,27 +101,59 @@ outputs: %s
# Doc
`
- th, h := newTestSitesFromConfig(t, siteConfig,
- "layouts/_default/list.json", `List JSON|{{ .Title }}|{{ .Content }}|Alt formats: {{ len .AlternativeOutputFormats -}}|
+ mf := afero.NewMemMapFs()
+
+ writeToFs(t, mf, "i18n/en.toml", `
+[elbow]
+other = "Elbow"
+`)
+ writeToFs(t, mf, "i18n/nn.toml", `
+[elbow]
+other = "Olboge"
+`)
+
+ th, h := newTestSitesFromConfig(t, mf, siteConfig,
+
+ "layouts/_default/baseof.json", `START JSON:{{block "main" .}}default content{{ end }}:END JSON`,
+ "layouts/_default/baseof.html", `START HTML:{{block "main" .}}default content{{ end }}:END HTML`,
+
+ "layouts/_default/list.json", `{{ define "main" }}
+List JSON|{{ .Title }}|{{ .Content }}|Alt formats: {{ len .AlternativeOutputFormats -}}|
{{- range .AlternativeOutputFormats -}}
Alt Output: {{ .Name -}}|
{{- end -}}|
{{- range .OutputFormats -}}
-Output/Rel: {{ .Name -}}/{{ .Rel }}|
+Output/Rel: {{ .Name -}}/{{ .Rel }}|{{ .MediaType }}
+{{- end -}}
+ {{ with .OutputFormats.Get "JSON" }}
+<atom:link href={{ .Permalink }} rel="self" type="{{ .MediaType }}" />
+{{ end }}
+{{ .Site.Language.Lang }}: {{ T "elbow" -}}
+{{ end }}
+`,
+ "layouts/_default/list.html", `{{ define "main" }}
+List HTML|{{.Title }}|
+{{- with .OutputFormats.Get "HTML" -}}
+<atom:link href={{ .Permalink }} rel="self" type="{{ .MediaType }}" />
{{- end -}}
+{{ .Site.Language.Lang }}: {{ T "elbow" -}}
+{{ end }}
`,
)
- require.Len(t, h.Sites, 1)
+ require.Len(t, h.Sites, 2)
fs := th.Fs
writeSource(t, fs, "content/_index.md", fmt.Sprintf(pageTemplate, "JSON Home", outputsStr))
+ writeSource(t, fs, "content/_index.nn.md", fmt.Sprintf(pageTemplate, "JSON Nynorsk Heim", outputsStr))
err := h.Build(BuildCfg{})
require.NoError(t, err)
s := h.Sites[0]
+ require.Equal(t, "en", s.Language.Lang)
+
home := s.getPage(KindHome)
require.NotNil(t, home)
@@ -113,7 +162,6 @@ Output/Rel: {{ .Name -}}/{{ .Rel }}|
require.Len(t, home.outputFormats, lenOut)
- // TODO(bep) output assert template/text
// There is currently always a JSON output to make it simpler ...
altFormats := lenOut - 1
hasHTML := helpers.InStringArray(outputs, "html")
@@ -127,10 +175,27 @@ Output/Rel: {{ .Name -}}/{{ .Rel }}|
"Alt Output: HTML",
"Output/Rel: JSON/alternate|",
"Output/Rel: HTML/canonical|",
+ "en: Elbow",
+ )
+
+ th.assertFileContent("public/index.html",
+ // The HTML entity is a deliberate part of this test: The HTML templates are
+ // parsed wi