|
|
|
@@ -3,6 +3,7 @@ package tplimpl
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"regexp"
|
|
|
|
|
"slices"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
@@ -22,6 +23,7 @@ type templateTransformContext struct {
|
|
|
|
|
templateNotFound map[string]bool
|
|
|
|
|
deferNodes map[string]*parse.ListNode
|
|
|
|
|
lookupFn func(name string, in *TemplInfo) *TemplInfo
|
|
|
|
|
store *TemplateStore
|
|
|
|
|
|
|
|
|
|
// The last error encountered.
|
|
|
|
|
err error
|
|
|
|
@@ -53,11 +55,13 @@ func (c templateTransformContext) getIfNotVisited(name string) *TemplInfo {
|
|
|
|
|
|
|
|
|
|
func newTemplateTransformContext(
|
|
|
|
|
t *TemplInfo,
|
|
|
|
|
store *TemplateStore,
|
|
|
|
|
lookupFn func(name string, in *TemplInfo) *TemplInfo,
|
|
|
|
|
) *templateTransformContext {
|
|
|
|
|
return &templateTransformContext{
|
|
|
|
|
t: t,
|
|
|
|
|
lookupFn: lookupFn,
|
|
|
|
|
store: store,
|
|
|
|
|
visited: make(map[string]bool),
|
|
|
|
|
templateNotFound: make(map[string]bool),
|
|
|
|
|
deferNodes: make(map[string]*parse.ListNode),
|
|
|
|
@@ -66,28 +70,25 @@ func newTemplateTransformContext(
|
|
|
|
|
|
|
|
|
|
func applyTemplateTransformers(
|
|
|
|
|
t *TemplInfo,
|
|
|
|
|
store *TemplateStore,
|
|
|
|
|
lookupFn func(name string, in *TemplInfo) *TemplInfo,
|
|
|
|
|
) (*templateTransformContext, error) {
|
|
|
|
|
if t == nil {
|
|
|
|
|
return nil, errors.New("expected template, but none provided")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c := newTemplateTransformContext(t, lookupFn)
|
|
|
|
|
c := newTemplateTransformContext(t, store, lookupFn)
|
|
|
|
|
c.t.ParseInfo = defaultParseInfo
|
|
|
|
|
tree := getParseTree(t.Template)
|
|
|
|
|
if tree == nil {
|
|
|
|
|
panic(fmt.Errorf("template %s not parsed", t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, 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)
|
|
|
|
|
if err := c.applyTransformationsAndSetReturnWrapper(tree); err != nil {
|
|
|
|
|
return c, fmt.Errorf("failed to transform template %q: %w", t.Name(), err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c, err
|
|
|
|
|
return c, c.err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getParseTree(templ tpl.Template) *parse.Tree {
|
|
|
|
@@ -105,11 +106,17 @@ const (
|
|
|
|
|
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ range (slice .Arg) }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
|
|
|
|
|
|
|
|
|
|
doDeferTempl = `{{ doDefer ("PLACEHOLDER1") ("PLACEHOLDER2") }}`
|
|
|
|
|
|
|
|
|
|
// _pushPartialDecorator is always falsy.
|
|
|
|
|
pushPartialDecoratorTempl = `{{ if or (_pushPartialDecorator ("PLACEHOLDER")) }}{{ end }}`
|
|
|
|
|
popPartialDecoratorTempl = `{{ if (_popPartialDecorator ("PLACEHOLDER1")) }}{{ . }}{{ else }}("PLACEHOLDER2"){{ end }}`
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
partialReturnWrapper *parse.ListNode
|
|
|
|
|
doDefer *parse.ListNode
|
|
|
|
|
popPartialDecorator *parse.ListNode
|
|
|
|
|
pushPartialDecorator *parse.ListNode
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
@@ -124,6 +131,18 @@ func init() {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
doDefer = templ.Tree.Root
|
|
|
|
|
|
|
|
|
|
templ, err = texttemplate.New("").Funcs(texttemplate.FuncMap{"_popPartialDecorator": func(string) string { return "" }}).Parse(popPartialDecoratorTempl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
popPartialDecorator = templ.Tree.Root
|
|
|
|
|
|
|
|
|
|
templ, err = texttemplate.New("").Funcs(texttemplate.FuncMap{"_pushPartialDecorator": func(string) string { return "" }}).Parse(pushPartialDecoratorTempl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
pushPartialDecorator = templ.Tree.Root
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wrapInPartialReturnWrapper copies and modifies the parsed nodes of a
|
|
|
|
@@ -142,7 +161,20 @@ func (c *templateTransformContext) wrapInPartialReturnWrapper(n *parse.ListNode)
|
|
|
|
|
return wrapper
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// applyTransformations do 2 things:
|
|
|
|
|
func (c *templateTransformContext) applyTransformationsAndSetReturnWrapper(tree *parse.Tree) error {
|
|
|
|
|
_, err := c.applyTransformations(tree.Root)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if c.returnNode != nil {
|
|
|
|
|
// This is a partial with a return statement.
|
|
|
|
|
c.t.ParseInfo.HasReturn = true
|
|
|
|
|
tree.Root = c.wrapInPartialReturnWrapper(tree.Root)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// applyTransformations does 2 things:
|
|
|
|
|
// 1) Parses partial return statement.
|
|
|
|
|
// 2) Tracks template (partial) dependencies and some other info.
|
|
|
|
|
func (c *templateTransformContext) applyTransformations(n parse.Node) (bool, error) {
|
|
|
|
@@ -156,7 +188,7 @@ func (c *templateTransformContext) applyTransformations(n parse.Node) (bool, err
|
|
|
|
|
case *parse.IfNode:
|
|
|
|
|
c.applyTransformationsToNodes(x.Pipe, x.List, x.ElseList)
|
|
|
|
|
case *parse.WithNode:
|
|
|
|
|
c.handleDefer(x)
|
|
|
|
|
c.handleWith(x)
|
|
|
|
|
c.applyTransformationsToNodes(x.Pipe, x.List, x.ElseList)
|
|
|
|
|
case *parse.RangeNode:
|
|
|
|
|
c.applyTransformationsToNodes(x.Pipe, x.List, x.ElseList)
|
|
|
|
@@ -178,7 +210,8 @@ func (c *templateTransformContext) applyTransformations(n parse.Node) (bool, err
|
|
|
|
|
if x == nil {
|
|
|
|
|
return true, nil
|
|
|
|
|
}
|
|
|
|
|
c.collectInner(x)
|
|
|
|
|
c.collectInnerInShortcode(x)
|
|
|
|
|
c.collectInnerInPartial(x)
|
|
|
|
|
keep := c.collectReturnNode(x)
|
|
|
|
|
|
|
|
|
|
for _, elem := range x.Args {
|
|
|
|
@@ -193,14 +226,118 @@ func (c *templateTransformContext) applyTransformations(n parse.Node) (bool, err
|
|
|
|
|
return true, c.err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *templateTransformContext) handleDefer(withNode *parse.WithNode) {
|
|
|
|
|
func (c *templateTransformContext) isWithPartial(args []parse.Node) bool {
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if id1, ok := args[0].(*parse.IdentifierNode); ok && (id1.Ident == "partial" || id1.Ident == "partialCached") {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if chain, ok := args[0].(*parse.ChainNode); ok {
|
|
|
|
|
if id2, ok := chain.Node.(*parse.IdentifierNode); !ok || (id2.Ident != "partials") {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if len(chain.Field) != 1 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if chain.Field[0] != "Include" && chain.Field[0] != "IncludeCached" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *templateTransformContext) isWithDefer(idArg parse.Node) bool {
|
|
|
|
|
id, ok := idArg.(*parse.ChainNode)
|
|
|
|
|
if !ok || len(id.Field) != 1 || id.Field[0] != "Defer" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if id2, ok := id.Node.(*parse.IdentifierNode); !ok || id2.Ident != "templates" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PartialDecoratorPrefix is the prefix used for internal partial decorator templates.
|
|
|
|
|
const PartialDecoratorPrefix = "_internal/decorator_"
|
|
|
|
|
|
|
|
|
|
var templatesInnerRe = regexp.MustCompile(`{{\s*(templates\.Inner\b|inner\b)`)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
innerHash := hashing.XxHashFromStringHexEncoded(c.t.Name() + withNodeInnerString)
|
|
|
|
|
internalPartialName := fmt.Sprintf("_partials/%s%s", PartialDecoratorPrefix, innerHash)
|
|
|
|
|
|
|
|
|
|
if c.lookupFn(internalPartialName, c.t) == nil {
|
|
|
|
|
innerCopy := withNode.List.CopyList()
|
|
|
|
|
ti, err := c.store.addTransformedTemplateInsert(internalPartialName, SubCategoryInline)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.err = fmt.Errorf("failed to create internal partial decorator template %q: %w", internalPartialName, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if ti == nil {
|
|
|
|
|
c.err = fmt.Errorf("failed to find internal partial decorator template %q after insertion", internalPartialName)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cc := newTemplateTransformContext(ti, c.store, c.lookupFn)
|
|
|
|
|
|
|
|
|
|
tree, err := c.store.addTransformedTemplateSetTree(ti, innerCopy)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.err = fmt.Errorf("failed to add internal partial decorator template %q: %w", internalPartialName, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := cc.applyTransformationsAndSetReturnWrapper(tree); err != nil {
|
|
|
|
|
c.err = fmt.Errorf("failed to transform internal partial decorator template %q: %w", internalPartialName, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newInner := popPartialDecorator.CopyList()
|
|
|
|
|
ifNode := newInner.Nodes[0].(*parse.IfNode)
|
|
|
|
|
|
|
|
|
|
placeholderPipe := ifNode.Pipe.Cmds[0].Args[0].(*parse.PipeNode)
|
|
|
|
|
|
|
|
|
|
// Set PLACEHOLDER1 to the unique ID for this partial decorator.
|
|
|
|
|
sn1 := placeholderPipe.Cmds[0].Args[1].(*parse.PipeNode).Cmds[0].Args[0].(*parse.StringNode)
|
|
|
|
|
sn1.Text = innerHash
|
|
|
|
|
sn1.Quoted = fmt.Sprintf("%q", sn1.Text)
|
|
|
|
|
|
|
|
|
|
ifNode.ElseList = withNode.List.CopyList()
|
|
|
|
|
|
|
|
|
|
newPipe := pushPartialDecorator.CopyList()
|
|
|
|
|
orNode := newPipe.Nodes[0].(*parse.IfNode)
|
|
|
|
|
setContext := orNode.Pipe.Cmds[0].Args[1]
|
|
|
|
|
// Replace PLACEHOLDER with the unique ID for this partial decorator.
|
|
|
|
|
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)
|
|
|
|
|
withNode.Pipe.Cmds = append(orNode.Pipe.Cmds, withNode.Pipe.Cmds...)
|
|
|
|
|
|
|
|
|
|
withNode.List = newInner
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *templateTransformContext) handleWith(withNode *parse.WithNode) {
|
|
|
|
|
if len(withNode.Pipe.Cmds) != 1 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
cmd := withNode.Pipe.Cmds[0]
|
|
|
|
|
if len(cmd.Args) != 1 {
|
|
|
|
|
|
|
|
|
|
if c.isWithPartial(withNode.Pipe.Cmds[0].Args) {
|
|
|
|
|
c.handleWithPartial(withNode)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd := withNode.Pipe.Cmds[0]
|
|
|
|
|
|
|
|
|
|
idArg := cmd.Args[0]
|
|
|
|
|
|
|
|
|
|
p, ok := idArg.(*parse.PipeNode)
|
|
|
|
@@ -220,11 +357,7 @@ func (c *templateTransformContext) handleDefer(withNode *parse.WithNode) {
|
|
|
|
|
|
|
|
|
|
idArg = cmd.Args[0]
|
|
|
|
|
|
|
|
|
|
id, ok := idArg.(*parse.ChainNode)
|
|
|
|
|
if !ok || len(id.Field) != 1 || id.Field[0] != "Defer" {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if id2, ok := id.Node.(*parse.IdentifierNode); !ok || id2.Ident != "templates" {
|
|
|
|
|
if !c.isWithDefer(idArg) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -304,9 +437,9 @@ func (c *templateTransformContext) collectConfig(n *parse.PipeNode) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// collectInner determines if the given CommandNode represents a
|
|
|
|
|
// collectInnerInShortcode determines if the given CommandNode represents a
|
|
|
|
|
// shortcode call to its .Inner.
|
|
|
|
|
func (c *templateTransformContext) collectInner(n *parse.CommandNode) {
|
|
|
|
|
func (c *templateTransformContext) collectInnerInShortcode(n *parse.CommandNode) {
|
|
|
|
|
if c.t.category != CategoryShortcode {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
@@ -330,6 +463,29 @@ func (c *templateTransformContext) collectInner(n *parse.CommandNode) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *templateTransformContext) collectInnerInPartial(n *parse.CommandNode) {
|
|
|
|
|
if c.t.category != CategoryPartial {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if c.t.ParseInfo.HasPartialInner || len(n.Args) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch v := n.Args[0].(type) {
|
|
|
|
|
case *parse.IdentifierNode:
|
|
|
|
|
if v.Ident == "inner" {
|
|
|
|
|
c.t.ParseInfo.HasPartialInner = true
|
|
|
|
|
}
|
|
|
|
|
case *parse.ChainNode:
|
|
|
|
|
if v.Field[0] == "Inner" {
|
|
|
|
|
if id, ok := v.Node.(*parse.IdentifierNode); ok && id.Ident == "templates" {
|
|
|
|
|
c.t.ParseInfo.HasPartialInner = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *templateTransformContext) collectReturnNode(n *parse.CommandNode) bool {
|
|
|
|
|
if c.t.category != CategoryPartial || c.returnNode != nil {
|
|
|
|
|
return true
|
|
|
|
|