File: tags_macro.go

package info (click to toggle)
golang-github-flosch-pongo2.v4 4.0.2-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bookworm-backports, trixie
  • size: 860 kB
  • sloc: makefile: 3
file content (149 lines) | stat: -rw-r--r-- 3,852 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package pongo2

import (
	"bytes"
	"fmt"
)

type tagMacroNode struct {
	position  *Token
	name      string
	argsOrder []string
	args      map[string]IEvaluator
	exported  bool

	wrapper *NodeWrapper
}

func (node *tagMacroNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
	ctx.Private[node.name] = func(args ...*Value) *Value {
		return node.call(ctx, args...)
	}

	return nil
}

func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
	argsCtx := make(Context)

	for k, v := range node.args {
		if v == nil {
			// User did not provided a default value
			argsCtx[k] = nil
		} else {
			// Evaluate the default value
			valueExpr, err := v.Evaluate(ctx)
			if err != nil {
				ctx.Logf(err.Error())
				return AsSafeValue(err.Error())
			}

			argsCtx[k] = valueExpr
		}
	}

	if len(args) > len(node.argsOrder) {
		// Too many arguments, we're ignoring them and just logging into debug mode.
		err := ctx.Error(fmt.Sprintf("Macro '%s' called with too many arguments (%d instead of %d).",
			node.name, len(args), len(node.argsOrder)), nil).updateFromTokenIfNeeded(ctx.template, node.position)

		ctx.Logf(err.Error()) // TODO: This is a workaround, because the error is not returned yet to the Execution()-methods
		return AsSafeValue(err.Error())
	}

	// Make a context for the macro execution
	macroCtx := NewChildExecutionContext(ctx)

	// Register all arguments in the private context
	macroCtx.Private.Update(argsCtx)

	for idx, argValue := range args {
		macroCtx.Private[node.argsOrder[idx]] = argValue.Interface()
	}

	var b bytes.Buffer
	err := node.wrapper.Execute(macroCtx, &b)
	if err != nil {
		return AsSafeValue(err.updateFromTokenIfNeeded(ctx.template, node.position).Error())
	}

	return AsSafeValue(b.String())
}

func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
	macroNode := &tagMacroNode{
		position: start,
		args:     make(map[string]IEvaluator),
	}

	nameToken := arguments.MatchType(TokenIdentifier)
	if nameToken == nil {
		return nil, arguments.Error("Macro-tag needs at least an identifier as name.", nil)
	}
	macroNode.name = nameToken.Val

	if arguments.MatchOne(TokenSymbol, "(") == nil {
		return nil, arguments.Error("Expected '('.", nil)
	}

	for arguments.Match(TokenSymbol, ")") == nil {
		argNameToken := arguments.MatchType(TokenIdentifier)
		if argNameToken == nil {
			return nil, arguments.Error("Expected argument name as identifier.", nil)
		}
		macroNode.argsOrder = append(macroNode.argsOrder, argNameToken.Val)

		if arguments.Match(TokenSymbol, "=") != nil {
			// Default expression follows
			argDefaultExpr, err := arguments.ParseExpression()
			if err != nil {
				return nil, err
			}
			macroNode.args[argNameToken.Val] = argDefaultExpr
		} else {
			// No default expression
			macroNode.args[argNameToken.Val] = nil
		}

		if arguments.Match(TokenSymbol, ")") != nil {
			break
		}
		if arguments.Match(TokenSymbol, ",") == nil {
			return nil, arguments.Error("Expected ',' or ')'.", nil)
		}
	}

	if arguments.Match(TokenKeyword, "export") != nil {
		macroNode.exported = true
	}

	if arguments.Remaining() > 0 {
		return nil, arguments.Error("Malformed macro-tag.", nil)
	}

	// Body wrapping
	wrapper, endargs, err := doc.WrapUntilTag("endmacro")
	if err != nil {
		return nil, err
	}
	macroNode.wrapper = wrapper

	if endargs.Count() > 0 {
		return nil, endargs.Error("Arguments not allowed here.", nil)
	}

	if macroNode.exported {
		// Now register the macro if it wants to be exported
		_, has := doc.template.exportedMacros[macroNode.name]
		if has {
			return nil, doc.Error(fmt.Sprintf("another macro with name '%s' already exported", macroNode.name), start)
		}
		doc.template.exportedMacros[macroNode.name] = macroNode
	}

	return macroNode, nil
}

func init() {
	RegisterTag("macro", tagMacroParser)
}