Add support for inline partials
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 1 Jul 2020 08:43:17 +0000 (10:43 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 1 Jul 2020 21:10:21 +0000 (23:10 +0200)
Fixes #7444

docs/content/en/templates/partials.md
hugolib/hugo_sites_build_errors_test.go
hugolib/template_test.go
tpl/tplimpl/template.go

index 873f5e696ab83bf55551be808fcc3faa1977e9a1..d23622deda7480f6410622ecc184928c247cd632 100644 (file)
@@ -81,6 +81,21 @@ This means the partial will *only* be able to access those variables. The partia
 
 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 */}}
index d90a8b364088aa8c8ccfef4e63fbb0fab3c9c199..4de98a78875d1abe5b4e5277873083d78c432cb0 100644 (file)
@@ -65,8 +65,7 @@ func TestSiteBuildErrors(t *testing.T) {
                        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 @@ func TestSiteBuildErrors(t *testing.T) {
                                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())
 
                        },
                },
index 29993120d8ba2bdc67caa8930d6fae87786cc897..673d91b5c15ce73142950b79738a21d179ce826d 100644 (file)
@@ -597,3 +597,83 @@ func collectIdentities(set map[identity.Identity]bool, provider identity.Provide
 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
+`,
+       )
+
+}
index 81b62b3427ff8a4249d1e31a29bb284c220700c4..1243e6a15bd6eb03a7abec00854d800cc2b3d0dc 100644 (file)
@@ -553,12 +553,24 @@ func (t *templateHandler) addTemplateFile(name, path string) error {
        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
        }
 
        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) noBaseNeeded(name string) bool {
 }
 
 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) ParseInfo() tpl.ParseInfo {
 }
 
 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 {
@@ -960,3 +1018,22 @@ func unwrap(templ tpl.Template) tpl.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
+
+}