Allow for return partials with falsy arguments (#9298)
authorPaul Gottschling <paul.gottschling@gmail.com>
Fri, 17 Dec 2021 07:35:21 +0000 (02:35 -0500)
committerGitHub <noreply@github.com>
Fri, 17 Dec 2021 07:35:21 +0000 (08:35 +0100)
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

hugolib/template_test.go
tpl/partials/partials.go
tpl/tplimpl/template_ast_transformers.go

index abb6d32f9248c7519bcce9612d3f6e6fcd808c81..2908fdf71a182fffd7c4cd7785ffd491b5767af1 100644 (file)
@@ -456,22 +456,34 @@ complex: 80: 80
 `,
                )
        })
+}
 
-       c.Run("Zero argument", func(c *qt.C) {
-               b := newBuilder(c)
-
-               b.WithTemplatesAdded(
-                       "index.html", `
-Test Partials With Return Values:
-
-add42: fail: {{ partial "add42.tpl" 0 }}
+// Issue 7528
+func TestPartialWithZeroedArgs(t *testing.T) {
 
-`,
-               )
+       b := newTestSitesBuilder(t)
+       b.WithTemplatesAdded("index.html",
+               ` 
+X{{ partial "retval" dict }}X
+X{{ partial "retval" slice }}X
+X{{ partial "retval" "" }}X
+X{{ partial "retval" false }}X
+X{{ partial "retval" 0 }}X
+{{ define "partials/retval" }}
+  {{ return 123 }}
+{{ end }}`)
+
+       b.WithContentAdded("p.md", ``)
+       b.Build(BuildCfg{})
+       b.AssertFileContent("public/index.html",
+               `
+X123X
+X123X
+X123X
+X123X
+X123X
+`)
 
-               e := b.CreateSites().BuildE(BuildCfg{})
-               b.Assert(e, qt.Not(qt.IsNil))
-       })
 }
 
 func TestPartialCached(t *testing.T) {
index e8a8adc36d9a4fe36e730880f117c9ee43fed4bd..b0dc0a997ce40f6a2e1ac9eafaa3fead5b72c497 100644 (file)
@@ -25,7 +25,6 @@ import (
        "strings"
        "sync"
 
-       "github.com/gohugoio/hugo/common/hreflect"
        texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
 
        "github.com/gohugoio/hugo/helpers"
@@ -121,10 +120,6 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface
        var w io.Writer
 
        if info.HasReturn {
-               if !hreflect.IsTruthful(context) {
-                       // TODO(bep) we need to fix this, but it is non-trivial.
-                       return nil, errors.New("partial that returns a value needs a non-zero argument.")
-               }
                // Wrap the context sent to the template to capture the return value.
                // Note that the template is rewritten to make sure that the dot (".")
                // and the $ variable points to Arg.
index de9b5424fe4413699c296e09daec4ac1e47f0ab1..33461dc7d2343382bb06db7f5f4abfcb689e285b 100644 (file)
@@ -112,7 +112,11 @@ func getParseTree(templ tpl.Template) *parse.Tree {
 }
 
 const (
-       partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ with .Arg }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
+       // We parse this template and modify the nodes in order to assign
+       // the return value of a partial to a contextWrapper via Set. We use
+       // "range" over a one-element slice so we can shift dot to the
+       // partial's argument, Arg, while allowing Arg to be falsy.
+       partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ range (slice .Arg) }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
 )
 
 var partialReturnWrapper *parse.ListNode
@@ -125,16 +129,18 @@ func init() {
        partialReturnWrapper = templ.Tree.Root
 }
 
+// wrapInPartialReturnWrapper copies and modifies the parsed nodes of a
+// predefined partial return wrapper to insert those of a user-defined partial.
 func (c *templateContext) wrapInPartialReturnWrapper(n *parse.ListNode) *parse.ListNode {
        wrapper := partialReturnWrapper.CopyList()
-       withNode := wrapper.Nodes[2].(*parse.WithNode)
-       retn := withNode.List.Nodes[0]
+       rangeNode := wrapper.Nodes[2].(*parse.RangeNode)
+       retn := rangeNode.List.Nodes[0]
        setCmd := retn.(*parse.ActionNode).Pipe.Cmds[0]
        setPipe := setCmd.Args[1].(*parse.PipeNode)
        // Replace PLACEHOLDER with the real return value.
        // Note that this is a PipeNode, so it will be wrapped in parens.
        setPipe.Cmds = []*parse.CommandNode{c.returnNode}
-       withNode.List.Nodes = append(n.Nodes, retn)
+       rangeNode.List.Nodes = append(n.Nodes, retn)
 
        return wrapper
 }