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)
}
|