150 lines
3.9 KiB
Go
150 lines
3.9 KiB
Go
package template
|
|
|
|
import (
|
|
"text/template"
|
|
"text/template/parse"
|
|
)
|
|
|
|
// rewritePropertiesCheck walks the template AST and rewrites .Function.Parameters.Properties
|
|
// to .Function.Parameters.HasProperties in if/with conditions to fix truthiness checking.
|
|
// This maintains backward compatibility with templates that check if Properties exist.
|
|
func rewritePropertiesCheck(tmpl *template.Template) {
|
|
walk(tmpl.Tree.Root)
|
|
}
|
|
|
|
func walk(n parse.Node) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
switch node := n.(type) {
|
|
case *parse.ListNode:
|
|
for _, child := range node.Nodes {
|
|
walk(child)
|
|
}
|
|
case *parse.ActionNode:
|
|
// Rewrite len calls in action nodes
|
|
rewritePipeProperties(node.Pipe)
|
|
case *parse.IfNode:
|
|
rewritePipeProperties(node.Pipe)
|
|
walk(&node.BranchNode)
|
|
case *parse.WithNode:
|
|
rewritePipeProperties(node.Pipe)
|
|
walk(&node.BranchNode)
|
|
case *parse.RangeNode:
|
|
// Don't rewrite the pipe for range nodes - they need .Properties for iteration
|
|
walk(&node.BranchNode)
|
|
case *parse.BranchNode:
|
|
if node.List != nil {
|
|
walk(node.List)
|
|
}
|
|
if node.ElseList != nil {
|
|
walk(node.ElseList)
|
|
}
|
|
}
|
|
}
|
|
|
|
func rewritePipeProperties(pipe *parse.PipeNode) {
|
|
if pipe == nil {
|
|
return
|
|
}
|
|
|
|
for _, cmd := range pipe.Cmds {
|
|
rewriteCommand(cmd)
|
|
}
|
|
}
|
|
|
|
// rewriteCommand recursively rewrites a command and all its nested command arguments
|
|
func rewriteCommand(cmd *parse.CommandNode) {
|
|
// Check if this is a "len .Function.Parameters.Properties" call
|
|
if isLenPropertiesCall(cmd) {
|
|
// Replace entire command with .Function.Parameters.Len field access
|
|
replaceLenWithLenMethod(cmd)
|
|
return
|
|
}
|
|
|
|
// Recursively process all arguments
|
|
for i, arg := range cmd.Args {
|
|
switch argNode := arg.(type) {
|
|
case *parse.FieldNode:
|
|
// Check for direct .Properties field access
|
|
if isPropertiesField(argNode.Ident) {
|
|
cmd.Args[i] = replaceWithHasProperties(argNode)
|
|
}
|
|
case *parse.CommandNode:
|
|
// Recursively process nested commands (e.g., inside "and", "gt", etc.)
|
|
rewriteCommand(argNode)
|
|
case *parse.PipeNode:
|
|
// Template function arguments can be wrapped in PipeNodes
|
|
rewritePipeProperties(argNode)
|
|
}
|
|
}
|
|
}
|
|
|
|
// isLenPropertiesCall checks if a command is "len .Function.Parameters.Properties"
|
|
func isLenPropertiesCall(cmd *parse.CommandNode) bool {
|
|
if len(cmd.Args) != 2 {
|
|
return false
|
|
}
|
|
|
|
// First arg should be the "len" identifier
|
|
if ident, ok := cmd.Args[0].(*parse.IdentifierNode); !ok || ident.Ident != "len" {
|
|
return false
|
|
}
|
|
|
|
// Second arg should be .Function.Parameters.Properties field
|
|
if field, ok := cmd.Args[1].(*parse.FieldNode); ok {
|
|
return isPropertiesField(field.Ident)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// replaceLenWithLenMethod replaces "len .Function.Parameters.Properties" with ".Function.Parameters.Len"
|
|
func replaceLenWithLenMethod(cmd *parse.CommandNode) {
|
|
if len(cmd.Args) < 2 {
|
|
return
|
|
}
|
|
|
|
field, ok := cmd.Args[1].(*parse.FieldNode)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Create new field node with .Len instead of .Properties
|
|
newIdent := make([]string, len(field.Ident))
|
|
copy(newIdent, field.Ident)
|
|
newIdent[len(newIdent)-1] = "Len"
|
|
|
|
newField := &parse.FieldNode{
|
|
NodeType: parse.NodeField,
|
|
Ident: newIdent,
|
|
Pos: field.Pos,
|
|
}
|
|
|
|
// Replace the command with just the field access (remove "len" function call)
|
|
cmd.Args = []parse.Node{newField}
|
|
}
|
|
|
|
func isPropertiesField(ident []string) bool {
|
|
// Match: .Function.Parameters.Properties
|
|
// We only rewrite if it ends with Parameters.Properties to avoid false positives
|
|
if len(ident) < 3 {
|
|
return false
|
|
}
|
|
return ident[len(ident)-1] == "Properties" && ident[len(ident)-2] == "Parameters"
|
|
}
|
|
|
|
func replaceWithHasProperties(field *parse.FieldNode) *parse.FieldNode {
|
|
// Clone the identifier slice and replace the last element
|
|
newIdent := make([]string, len(field.Ident))
|
|
copy(newIdent, field.Ident)
|
|
newIdent[len(newIdent)-1] = "HasProperties"
|
|
|
|
return &parse.FieldNode{
|
|
NodeType: parse.NodeField,
|
|
Ident: newIdent,
|
|
Pos: field.Pos,
|
|
}
|
|
}
|