Skip to content

Commit

Permalink
Add support for inline partials
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Jul 1, 2020
1 parent c66dc6c commit e4fc642
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 4 deletions.
15 changes: 15 additions & 0 deletions docs/content/en/templates/partials.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 */}}
Expand Down
5 changes: 2 additions & 3 deletions hugolib/hugo_sites_build_errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
},
Expand All @@ -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())

},
},
Expand Down
80 changes: 80 additions & 0 deletions hugolib/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
`,
)

}
79 changes: 78 additions & 1 deletion tpl/tplimpl/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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

}
3 changes: 3 additions & 0 deletions tpl/tplimpl/template_ast_transformers.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@ func applyTemplateTransformers(
_, err := c.applyTransformations(tree.Root)

if err == nil && c.returnNode != nil {

// This is a partial with a return statement.
c.t.parseInfo.HasReturn = true
tree.Root = c.wrapInPartialReturnWrapper(tree.Root)

}

return c, err
Expand Down Expand Up @@ -179,6 +181,7 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
case *parse.CommandNode:
c.collectPartialInfo(x)
c.collectInner(x)

keep := c.collectReturnNode(x)

for _, elem := range x.Args {
Expand Down

0 comments on commit e4fc642

Please sign in to comment.