Skip to content

Commit 5758c37

Browse files
authored
Allow for return partials with falsy arguments (#9298)
Partials with returns values are parsed, then inserted into a partial return wrapper via wrapInPartialReturnWrapper in order to assign the return value via *contextWrapper.Set. The predefined wrapper template for partials inserts a partial's nodes into a "with" template action in order to set dot to a *contextWrapper within the partial. However, because "with" is skipped if its argument is falsy, partials with falsy arguments were not being evaluated. This replaces the "with" action in the partial wrapper with a "range" action that isn't skipped if .Arg is falsy. Fixes #7528
1 parent 8ee6de6 commit 5758c37

File tree

3 files changed

+35
-22
lines changed

3 files changed

+35
-22
lines changed

hugolib/template_test.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -456,22 +456,34 @@ complex: 80: 80
456456
`,
457457
)
458458
})
459+
}
459460

460-
c.Run("Zero argument", func(c *qt.C) {
461-
b := newBuilder(c)
462-
463-
b.WithTemplatesAdded(
464-
"index.html", `
465-
Test Partials With Return Values:
466-
467-
add42: fail: {{ partial "add42.tpl" 0 }}
461+
// Issue 7528
462+
func TestPartialWithZeroedArgs(t *testing.T) {
468463

469-
`,
470-
)
464+
b := newTestSitesBuilder(t)
465+
b.WithTemplatesAdded("index.html",
466+
`
467+
X{{ partial "retval" dict }}X
468+
X{{ partial "retval" slice }}X
469+
X{{ partial "retval" "" }}X
470+
X{{ partial "retval" false }}X
471+
X{{ partial "retval" 0 }}X
472+
{{ define "partials/retval" }}
473+
{{ return 123 }}
474+
{{ end }}`)
475+
476+
b.WithContentAdded("p.md", ``)
477+
b.Build(BuildCfg{})
478+
b.AssertFileContent("public/index.html",
479+
`
480+
X123X
481+
X123X
482+
X123X
483+
X123X
484+
X123X
485+
`)
471486

472-
e := b.CreateSites().BuildE(BuildCfg{})
473-
b.Assert(e, qt.Not(qt.IsNil))
474-
})
475487
}
476488

477489
func TestPartialCached(t *testing.T) {

tpl/partials/partials.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"strings"
2626
"sync"
2727

28-
"github.com/gohugoio/hugo/common/hreflect"
2928
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
3029

3130
"github.com/gohugoio/hugo/helpers"
@@ -121,10 +120,6 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface
121120
var w io.Writer
122121

123122
if info.HasReturn {
124-
if !hreflect.IsTruthful(context) {
125-
// TODO(bep) we need to fix this, but it is non-trivial.
126-
return nil, errors.New("partial that returns a value needs a non-zero argument.")
127-
}
128123
// Wrap the context sent to the template to capture the return value.
129124
// Note that the template is rewritten to make sure that the dot (".")
130125
// and the $ variable points to Arg.

tpl/tplimpl/template_ast_transformers.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,11 @@ func getParseTree(templ tpl.Template) *parse.Tree {
112112
}
113113

114114
const (
115-
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ with .Arg }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
115+
// We parse this template and modify the nodes in order to assign
116+
// the return value of a partial to a contextWrapper via Set. We use
117+
// "range" over a one-element slice so we can shift dot to the
118+
// partial's argument, Arg, while allowing Arg to be falsy.
119+
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ range (slice .Arg) }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
116120
)
117121

118122
var partialReturnWrapper *parse.ListNode
@@ -125,16 +129,18 @@ func init() {
125129
partialReturnWrapper = templ.Tree.Root
126130
}
127131

132+
// wrapInPartialReturnWrapper copies and modifies the parsed nodes of a
133+
// predefined partial return wrapper to insert those of a user-defined partial.
128134
func (c *templateContext) wrapInPartialReturnWrapper(n *parse.ListNode) *parse.ListNode {
129135
wrapper := partialReturnWrapper.CopyList()
130-
withNode := wrapper.Nodes[2].(*parse.WithNode)
131-
retn := withNode.List.Nodes[0]
136+
rangeNode := wrapper.Nodes[2].(*parse.RangeNode)
137+
retn := rangeNode.List.Nodes[0]
132138
setCmd := retn.(*parse.ActionNode).Pipe.Cmds[0]
133139
setPipe := setCmd.Args[1].(*parse.PipeNode)
134140
// Replace PLACEHOLDER with the real return value.
135141
// Note that this is a PipeNode, so it will be wrapped in parens.
136142
setPipe.Cmds = []*parse.CommandNode{c.returnNode}
137-
withNode.List.Nodes = append(n.Nodes, retn)
143+
rangeNode.List.Nodes = append(n.Nodes, retn)
138144

139145
return wrapper
140146
}

0 commit comments

Comments
 (0)