diff options
Diffstat (limited to 'pkg/aflow/template.go')
| -rw-r--r-- | pkg/aflow/template.go | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/pkg/aflow/template.go b/pkg/aflow/template.go new file mode 100644 index 000000000..7b0efd194 --- /dev/null +++ b/pkg/aflow/template.go @@ -0,0 +1,100 @@ +// Copyright 2025 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package aflow + +import ( + "bytes" + "fmt" + "io" + "reflect" + "strings" + "text/template" + "text/template/parse" +) + +// formatTemplate formats template 'text' using the standard text/template logic. +// Panics on any errors, but these panics shouldn't happen if verifyTemplate +// was called for the template before. +func formatTemplate(text string, state map[string]any) string { + templ, err := parseTemplate(text) + if err != nil { + panic(err) + } + w := new(bytes.Buffer) + if err := templ.Execute(w, state); err != nil { + panic(err) + } + return w.String() +} + +// verifyTemplate checks that the template 'text' can be executed with the variables 'vars'. +// Returns the set of variables that are actually used in the template. +func verifyTemplate(text string, vars map[string]reflect.Type) (map[string]bool, error) { + templ, err := parseTemplate(text) + if err != nil { + return nil, err + } + used := make(map[string]bool) + walkTemplate(templ.Root, used, &err) + if err != nil { + return nil, err + } + vals := make(map[string]any) + for name := range used { + typ, ok := vars[name] + if !ok { + return nil, fmt.Errorf("input %v is not provided", name) + } + vals[name] = reflect.Zero(typ).Interface() + } + // Execute once just to make sure. + if err := templ.Execute(io.Discard, vals); err != nil { + return nil, err + } + return used, nil +} + +// walkTemplate recursively walks template nodes collecting used variables. +// It does not handle all node types, but enough to support reasonably simple templates. +func walkTemplate(n parse.Node, used map[string]bool, errp *error) { + if reflect.ValueOf(n).IsNil() { + return + } + switch n := n.(type) { + case *parse.ListNode: + for _, c := range n.Nodes { + walkTemplate(c, used, errp) + } + case *parse.IfNode: + walkTemplate(n.Pipe, used, errp) + walkTemplate(n.List, used, errp) + walkTemplate(n.ElseList, used, errp) + case *parse.ActionNode: + walkTemplate(n.Pipe, used, errp) + case *parse.PipeNode: + for _, c := range n.Decl { + walkTemplate(c, used, errp) + } + for _, c := range n.Cmds { + walkTemplate(c, used, errp) + } + case *parse.CommandNode: + for _, c := range n.Args { + walkTemplate(c, used, errp) + } + case *parse.FieldNode: + if len(n.Ident) != 1 { + noteError(errp, "compound values are not supported: .%v", strings.Join(n.Ident, ".")) + } + used[n.Ident[0]] = true + case *parse.VariableNode: + case *parse.TextNode: + default: + noteError(errp, "unhandled node type %T", n) + } +} + +func parseTemplate(prompt string) (*template.Template, error) { + return template.New("").Option("missingkey=error").Parse(prompt) +} |
