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