shithub: hugo

Download patch

ref: 4a3efea7efe59cd3de7d0eb352836ab395a2b6b3
parent: c66dc6c74fa3bbe308ccaade8c76071b49908129
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Wed Jul 1 06:43:17 EDT 2020

Add support for inline partials

Fixes #7444

--- a/docs/content/en/templates/partials.md
+++ b/docs/content/en/templates/partials.md
@@ -81,6 +81,21 @@
 
 In addition to outputting markup, partials can be used to return a value of any type. In order to return a value, a partial must include a lone `return` statement.
 
+## Inline partials
+
+{{< new-in "0.74.0" >}}
+
+You can also define partials inline in the template. But remember that template namespace is global, so you need to make sure that the names are unique to avoid conflicts.
+
+```go-html-template
+Value: {{ partial "my-inline-partial" . }}
+
+{{ define "partials/my-inline-partial" }}
+{{ $value := 32 }}
+{{ return $value }}
+{{ end }}
+```
+
 ### Example GetFeatured
 ```go-html-template
 {{/* layouts/partials/GetFeatured.html */}}
--- a/hugolib/hugo_sites_build_errors_test.go
+++ b/hugolib/hugo_sites_build_errors_test.go
@@ -65,8 +65,7 @@
 			fileFixer: func(content string) string {
 				return strings.Replace(content, ".Title }}", ".Title }", 1)
 			},
-			// Base templates gets parsed at build time.
-			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
+			assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
 				a.assertLineNumber(4, err)
 			},
 		},
@@ -91,7 +90,7 @@
 				a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
 				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 1)
 				a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template")
-				a.assertErrorMessage("\"layouts/foo/single.html:5:1\": parse failed: template: foo/single.html:5: unexpected \"}\" in operand", fe.Error())
+				a.assertErrorMessage("\"layouts/_default/single.html:5:1\": parse failed: template: _default/single.html.___b:5: unexpected \"}\" in operand", fe.Error())
 
 			},
 		},
--- a/hugolib/template_test.go
+++ b/hugolib/template_test.go
@@ -597,3 +597,83 @@
 func ident(level int) string {
 	return strings.Repeat(" ", level)
 }
+
+func TestPartialInline(t *testing.T) {
+
+	b := newTestSitesBuilder(t)
+
+	b.WithContent("p1.md", "")
+
+	b.WithTemplates(
+		"index.html", `
+
+{{ $p1 := partial "p1" . }}
+{{ $p2 := partial "p2" . }}
+
+P1: {{ $p1 }}
+P2: {{ $p2 }}
+
+{{ define "partials/p1" }}Inline: p1{{ end }}
+
+{{ define "partials/p2" }}
+{{ $value := 32 }}
+{{ return $value }}
+{{ end }}
+
+
+`,
+	)
+
+	b.CreateSites().Build(BuildCfg{})
+
+	b.AssertFileContent("public/index.html",
+		`
+P1: Inline: p1
+P2: 32`,
+	)
+
+}
+
+func TestPartialInlineBase(t *testing.T) {
+
+	b := newTestSitesBuilder(t)
+
+	b.WithContent("p1.md", "")
+
+	b.WithTemplates(
+		"baseof.html", `{{ $p3 := partial "p3" . }}P3: {{ $p3 }}
+{{ block "main" . }}{{ end }}{{ define "partials/p3" }}Inline: p3{{ end }}`,
+		"index.html", `
+{{ define "main" }}
+
+{{ $p1 := partial "p1" . }}
+{{ $p2 := partial "p2" . }}
+
+P1: {{ $p1 }}
+P2: {{ $p2 }}
+
+{{ end }}
+
+
+{{ define "partials/p1" }}Inline: p1{{ end }}
+
+{{ define "partials/p2" }}
+{{ $value := 32 }}
+{{ return $value }}
+{{ end }}
+
+
+`,
+	)
+
+	b.CreateSites().Build(BuildCfg{})
+
+	b.AssertFileContent("public/index.html",
+		`
+P1: Inline: p1
+P2: 32
+P3: Inline: p3
+`,
+	)
+
+}
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -553,6 +553,12 @@
 	if isBaseTemplatePath(name) {
 		// Store it for later.
 		t.baseof[name] = tinfo
+		// Also parse and add it on its own to make sure we reach the inline partials.
+		tinfo.name = name + ".___b"
+		_, err := t.addTemplateTo(tinfo, t.main)
+		if err != nil {
+			return tinfo.errWithFileContext("parse failed", err)
+		}
 		return nil
 	}
 
@@ -559,6 +565,12 @@
 	needsBaseof := !t.noBaseNeeded(name) && needsBaseTemplate(tinfo.template)
 	if needsBaseof {
 		t.needsBaseof[name] = tinfo
+		// Also parse and add it on its own to make sure we reach the inline partials.
+		tinfo.name = name + ".___b"
+		_, err := t.addTemplateTo(tinfo, t.main)
+		if err != nil {
+			return tinfo.errWithFileContext("parse failed", err)
+		}
 		return nil
 	}
 
@@ -720,10 +732,51 @@
 }
 
 func (t *templateHandler) postTransform() error {
+	defineCheckedHTML := false
+	defineCheckedText := false
+
 	for _, v := range t.main.templates {
 		if v.typ == templateShortcode {
 			t.addShortcodeVariant(v)
 		}
+
+		if defineCheckedHTML && defineCheckedText {
+			continue
+		}
+
+		isText := isText(v.Template)
+		if isText {
+			if defineCheckedText {
+				continue
+			}
+			defineCheckedText = true
+		} else {
+			if defineCheckedHTML {
+				continue
+			}
+			defineCheckedHTML = true
+		}
+
+		templs := templates(v.Template)
+		for _, templ := range templs {
+			if templ.Name() == "" || !strings.HasPrefix(templ.Name(), "partials/") {
+				continue
+			}
+
+			ts := newTemplateState(templ, templateInfo{name: templ.Name()})
+			ts.typ = templatePartial
+
+			if _, found := t.main.templates[templ.Name()]; !found {
+				// This is a template defined inline.
+
+				_, err := applyTemplateTransformers(ts, t.main.newTemplateLookup(ts))
+				if err != nil {
+					return err
+				}
+				t.main.templates[templ.Name()] = ts
+
+			}
+		}
 	}
 
 	for name, source := range t.transformNotFound {
@@ -872,8 +925,13 @@
 }
 
 func (t *templateState) isText() bool {
-	_, isText := t.Template.(*texttemplate.Template)
+	return isText(t.Template)
+}
+
+func isText(templ tpl.Template) bool {
+	_, isText := templ.(*texttemplate.Template)
 	return isText
+
 }
 
 type templateStateMap struct {
@@ -959,4 +1017,23 @@
 		return ts.Template
 	}
 	return templ
+}
+
+func templates(in tpl.Template) []tpl.Template {
+	var templs []tpl.Template
+	in = unwrap(in)
+	if textt, ok := in.(*texttemplate.Template); ok {
+		for _, t := range textt.Templates() {
+			templs = append(templs, t)
+		}
+	}
+
+	if htmlt, ok := in.(*htmltemplate.Template); ok {
+		for _, t := range htmlt.Templates() {
+			templs = append(templs, t)
+		}
+	}
+
+	return templs
+
 }