diff --git a/README.md b/README.md
index 04b56a631c7..272c55ce618 100644
--- a/README.md
+++ b/README.md
@@ -156,6 +156,16 @@ If there is sufficient interest, [create a proposal]. Do not submit a pull reque
For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
+## License
+
+For the Hugo source code, see [LICENSE](/LICENSE).
+
+We also bundle some libraries in binary/WASM form:
+
+* [libwebp](https://github.com/webmproject/libwebp), [BSD-3-Clause license](https://github.com/webmproject/libwebp?tab=BSD-3-Clause-1-ov-file#readme)
+* [Katex](https://github.com/KaTeX/KaTeX), [MIT license](https://github.com/KaTeX/KaTeX?tab=MIT-1-ov-file#readme)
+* [QuickJS](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme), [License](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme)
+
## Dependencies
Hugo stands on the shoulders of great open source libraries. Run `hugo env --logLevel info` to display a list of dependencies.
diff --git a/hugoreleaser.env b/hugoreleaser.env
index 6c21ef60c87..d85844f9c58 100644
--- a/hugoreleaser.env
+++ b/hugoreleaser.env
@@ -1,7 +1,8 @@
# Release env.
# These will be replaced by script before release.
-HUGORELEASER_TAG=v0.154.0
-HUGORELEASER_COMMITISH=0b71db299a2bd89be876d7dc972ded03a222f560
+HUGORELEASER_TAG=v0.154.1
+HUGORELEASER_COMMITISH=e2fd6764be86d0cde988a7de6334fda0f43de871
+
diff --git a/tpl/templates/decorator_integration_test.go b/tpl/templates/decorator_integration_test.go
index a441148ea17..253a09316ee 100644
--- a/tpl/templates/decorator_integration_test.go
+++ b/tpl/templates/decorator_integration_test.go
@@ -367,3 +367,89 @@ This construct creates a loop: {{PLACEHOLDER . }}
b.Assert(err.Error(), qt.Contains, "inner cannot be used inside a with block that wraps a partial decorator")
}
}
+
+func TestPartialDecoratorInParens(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+-- layouts/home.html --
+Home.
+{{ define "_partials/b.html" }}
+{{ inner . }}
+{{ end }}
+{{ with (partial "b.html" "Important!") }}Notice: {{ . }}{{ end }}
+`
+
+ b := hugolib.Test(t, files)
+
+ b.AssertFileContent("public/index.html", "Notice: Important!")
+}
+
+func TestPartialDecoratorBreakInWith(t *testing.T) {
+ t.Parallel()
+
+ filesTemplate := `
+-- hugo.toml --
+-- layouts/home.html --
+Home.
+{{ define "_partials/b.html" }}
+{{ inner . }}
+{{ end }}
+{{ with (partial "b.html" "Important!") }}
+{{ range seq 1 5 }}
+{{ if eq . 3 }}
+PLACEHOLDER
+{{ end }}
+Notice: {{ . }}
+{{ end }}
+{{ end }}
+`
+
+ for _, placeholder := range []string{"{{ break }}", "{{ continue }}"} {
+ files := strings.ReplaceAll(filesTemplate, "PLACEHOLDER", placeholder)
+ b := hugolib.Test(t, files)
+ b.AssertFileContent("public/index.html", "Notice: 1", "Notice: 2", "! Notice: 3")
+ if strings.Contains(placeholder, "continue") {
+ b.AssertFileContent("public/index.html", "Notice: 4", "Notice: 5")
+ } else {
+ b.AssertFileContent("public/index.html", "! Notice: 4", "! Notice: 5")
+ }
+ }
+}
+
+func TestPartialWithBreakOutsideRange14333(t *testing.T) {
+ t.Parallel()
+
+ filesTemplate := `
+-- hugo.toml --
+-- layouts/home.html --
+Home. {{ partial "a" }}:Done.
+{{- define "_partials/a" }}
+ {{- $items := slice "a" "b" "c" }}
+ {{- range $items }}
+ {{- with partial "b" . -}}
+ PLACEHOLDER
+ {{- else }}
+ else: {{ . -}}
+ {{- end }}
+ {{- end }}
+{{- end }}
+{{- define "_partials/b" }}
+{{ $b := true }}
+{{ if ne . "b" }}
+{{ $b = false }}
+{{ end }}
+{{ return $b }}
+{{ end }}
+`
+ for _, placeholder := range []string{"{{ break }}", "{{ continue }}", "{{- break }}"} {
+ files := strings.ReplaceAll(filesTemplate, "PLACEHOLDER", placeholder)
+ b := hugolib.Test(t, files)
+ if strings.Contains(placeholder, "continue") {
+ b.AssertFileContent("public/index.html", "else: c:Done.")
+ } else {
+ b.AssertFileContent("public/index.html", "else: a:Done.")
+ }
+ }
+}
diff --git a/tpl/tplimpl/templatetransform.go b/tpl/tplimpl/templatetransform.go
index e4cec981387..6ace0f109e9 100644
--- a/tpl/tplimpl/templatetransform.go
+++ b/tpl/tplimpl/templatetransform.go
@@ -231,11 +231,20 @@ func (c *templateTransformContext) isWithPartial(args []parse.Node) bool {
return false
}
- if id1, ok := args[0].(*parse.IdentifierNode); ok && (id1.Ident == "partial" || id1.Ident == "partialCached") {
+ first := args[0]
+
+ if pn, ok := first.(*parse.PipeNode); ok {
+ if len(pn.Cmds) == 0 || pn.Cmds[0] == nil {
+ return false
+ }
+ return c.isWithPartial(pn.Cmds[0].Args)
+ }
+
+ if id1, ok := first.(*parse.IdentifierNode); ok && (id1.Ident == "partial" || id1.Ident == "partialCached") {
return true
}
- if chain, ok := args[0].(*parse.ChainNode); ok {
+ if chain, ok := first.(*parse.ChainNode); ok {
if id2, ok := chain.Node.(*parse.IdentifierNode); !ok || (id2.Ident != "partials") {
return false
}
@@ -266,12 +275,52 @@ const PartialDecoratorPrefix = "_internal/decorator_"
var templatesInnerRe = regexp.MustCompile(`{{\s*(templates\.Inner\b|inner\b)`)
+// hasBreakOrContinueOutsideRange returns true if the given list node contains a break or continue statement without being nested in a range.
+func (c *templateTransformContext) hasBreakOrContinueOutsideRange(n *parse.ListNode) bool {
+ if n == nil {
+ return false
+ }
+ for _, node := range n.Nodes {
+ switch x := node.(type) {
+ case *parse.ListNode:
+ if c.hasBreakOrContinueOutsideRange(x) {
+ return true
+ }
+ case *parse.RangeNode:
+ // skip
+ case *parse.IfNode:
+ if c.hasBreakOrContinueOutsideRange(x.List) {
+ return true
+ }
+ if c.hasBreakOrContinueOutsideRange(x.ElseList) {
+ return true
+ }
+ case *parse.WithNode:
+ if c.hasBreakOrContinueOutsideRange(x.List) {
+ return true
+ }
+ if c.hasBreakOrContinueOutsideRange(x.ElseList) {
+ return true
+ }
+ case *parse.BreakNode, *parse.ContinueNode:
+ return true
+
+ }
+ }
+ return false
+}
+
func (c *templateTransformContext) handleWithPartial(withNode *parse.WithNode) {
withNodeInnerString := withNode.List.String()
if templatesInnerRe.MatchString(withNodeInnerString) {
c.err = fmt.Errorf("inner cannot be used inside a with block that wraps a partial decorator")
return
}
+
+ // See #14333. That is a very odd construct, but we need to guard against it.
+ if c.hasBreakOrContinueOutsideRange(withNode.List) {
+ return
+ }
innerHash := hashing.XxHashFromStringHexEncoded(c.t.Name() + withNodeInnerString)
internalPartialName := fmt.Sprintf("_partials/%s%s", PartialDecoratorPrefix, innerHash)
@@ -321,6 +370,9 @@ func (c *templateTransformContext) handleWithPartial(withNode *parse.WithNode) {
sn2 := setContext.(*parse.PipeNode).Cmds[0].Args[1].(*parse.PipeNode).Cmds[0].Args[0].(*parse.StringNode)
sn2.Text = innerHash
sn2.Quoted = fmt.Sprintf("%q", sn2.Text)
+ if pn, ok := withNode.Pipe.Cmds[0].Args[0].(*parse.PipeNode); ok {
+ withNode.Pipe.Cmds[0].Args = pn.Cmds[0].Args
+ }
withNode.Pipe.Cmds = append(orNode.Pipe.Cmds, withNode.Pipe.Cmds...)
withNode.List = newInner