Golang中如何支持Jinja2Schema

Golang中如何支持Jinja2Schema 是否有适用于Go的类似jinja2schema(https://jinja2schema.readthedocs.io/en/latest/)的库? 它包含一些实用工具,例如识别变量类型(推断方法),如标量、数组或对象?

4 回复

你在寻找处理 Go 模板(https://golang.org/pkg/text/template/)还是 Jinja 模板的库?

更多关于Golang中如何支持Jinja2Schema的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我正在寻找一个能够处理Jinja模板的库,它可以识别变量类型,例如是数组、对象还是标量值。

引用自 Jinja 官网

Jinja 是一种现代且对设计师友好的模板语言,专为 Python 设计

我怀疑在 Go 中对这种 Python 模板语言的支持会很少,尤其是在推断类型这种特殊用途方面。

在Go语言中,虽然没有直接对应jinja2schema的库,但可以通过标准库和第三方库实现类似功能。以下是一个使用text/template解析和类型推断的示例:

package main

import (
    "fmt"
    "go/ast"
    "go/parser"
    "go/token"
    "strings"
    "text/template"
    "text/template/parse"
)

// 类型推断结构
type VariableType struct {
    Name     string
    Type     string
    Children map[string]VariableType
}

func analyzeTemplate(tmplStr string) (map[string]VariableType, error) {
    tmpl, err := template.New("test").Parse(tmplStr)
    if err != nil {
        return nil, err
    }

    result := make(map[string]VariableType)
    
    // 遍历模板节点
    var walkNodes func([]parse.Node)
    walkNodes = func(nodes []parse.Node) {
        for _, node := range nodes {
            switch n := node.(type) {
            case *parse.ActionNode:
                // 解析变量使用
                if len(n.Pipe.Cmds) > 0 {
                    for _, arg := range n.Pipe.Cmds[0].Args {
                        if field, ok := arg.(*parse.FieldNode); ok {
                            inferTypeFromPath(field.Ident, result)
                        }
                    }
                }
            case *parse.IfNode:
                walkNodes(n.BranchNode.List.Nodes)
                walkNodes(n.BranchNode.ElseList.Nodes)
            case *parse.RangeNode:
                walkNodes(n.BranchNode.List.Nodes)
                walkNodes(n.BranchNode.ElseList.Nodes)
            case *parse.WithNode:
                walkNodes(n.BranchNode.List.Nodes)
                walkNodes(n.BranchNode.ElseList.Nodes)
            }
        }
    }
    
    walkNodes(tmpl.Root.Nodes)
    return result, nil
}

func inferTypeFromPath(ident []string, result map[string]VariableType) {
    if len(ident) == 0 {
        return
    }
    
    current := result
    for i, part := range ident {
        if i == len(ident)-1 {
            // 叶子节点,推断为标量
            current[part] = VariableType{
                Name: part,
                Type: "scalar",
            }
        } else {
            // 中间节点,推断为对象
            if _, exists := current[part]; !exists {
                current[part] = VariableType{
                    Name:     part,
                    Type:     "object",
                    Children: make(map[string]VariableType),
                }
            }
            current = current[part].Children
        }
    }
}

// 使用AST解析进行更精确的类型推断
func inferTypeFromGoCode(code, varName string) (string, error) {
    fset := token.NewFileSet()
    node, err := parser.ParseFile(fset, "", code, parser.AllErrors)
    if err != nil {
        return "", err
    }
    
    var foundType string
    ast.Inspect(node, func(n ast.Node) bool {
        if decl, ok := n.(*ast.GenDecl); ok {
            for _, spec := range decl.Specs {
                if valueSpec, ok := spec.(*ast.ValueSpec); ok {
                    for i, name := range valueSpec.Names {
                        if name.Name == varName && i < len(valueSpec.Values) {
                            switch expr := valueSpec.Values[i].(type) {
                            case *ast.BasicLit:
                                foundType = "scalar"
                            case *ast.CompositeLit:
                                switch expr.Type.(type) {
                                case *ast.ArrayType:
                                    foundType = "array"
                                case *ast.MapType:
                                    foundType = "object"
                                case *ast.StructType:
                                    foundType = "object"
                                }
                            }
                        }
                    }
                }
            }
        }
        return true
    })
    
    return foundType, nil
}

func main() {
    // 示例1:模板分析
    tmpl := `{{.User.Name}} is {{.User.Age}} years old. 
             {{range .Items}}{{.}}{{end}}
             {{with .Config}}{{.Setting}}{{end}}`
    
    vars, _ := analyzeTemplate(tmpl)
    fmt.Printf("模板变量分析:\n")
    for k, v := range vars {
        fmt.Printf("%s: %s\n", k, v.Type)
    }
    
    // 示例2:Go代码类型推断
    code := `package main
    var person = "John"
    var scores = []int{90, 85, 95}
    var config = map[string]string{"key": "value"}`
    
    types := []string{"person", "scores", "config"}
    for _, t := range types {
        if inferred, err := inferTypeFromGoCode(code, t); err == nil {
            fmt.Printf("%s: %s\n", t, inferred)
        }
    }
}

对于更复杂的模板分析,可以考虑以下第三方库:

  1. 模板解析增强
import "github.com/valyala/fasttemplate"

// 快速模板分析示例
func analyzeFastTemplate(tmpl string) {
    t, _ := fasttemplate.NewTemplate(tmpl, "{{", "}}")
    var variables []string
    t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
        variables = append(variables, strings.TrimSpace(tag))
        return 0, nil
    })
    fmt.Println("检测到的变量:", variables)
}
  1. JSON Schema生成(配合模板变量):
import "github.com/invopop/jsonschema"

func generateSchemaFromVars(vars map[string]string) *jsonschema.Schema {
    schema := &jsonschema.Schema{
        Type: "object",
        Properties: make(map[string]*jsonschema.Schema),
    }
    
    for name, varType := range vars {
        switch varType {
        case "scalar":
            schema.Properties[name] = &jsonschema.Schema{Type: "string"}
        case "array":
            schema.Properties[name] = &jsonschema.Schema{
                Type:  "array",
                Items: &jsonschema.Schema{Type: "string"},
            }
        case "object":
            schema.Properties[name] = &jsonschema.Schema{
                Type:       "object",
                Properties: make(map[string]*jsonschema.Schema),
            }
        }
    }
    return schema
}
  1. 完整模板分析工具
type TemplateAnalyzer struct {
    Variables map[string]VariableInfo
}

type VariableInfo struct {
    Path       []string
    InferredType string
    UsageCount   int
}

func (ta *TemplateAnalyzer) Analyze(tmplText string) error {
    tmpl, err := template.New("").Parse(tmplText)
    if err != nil {
        return err
    }
    
    ta.Variables = make(map[string]VariableInfo)
    
    // 收集所有变量引用
    ast.Walk(tmpl.Tree.Root, func(n parse.Node) bool {
        if field, ok := n.(*parse.FieldNode); ok {
            key := strings.Join(field.Ident, ".")
            info := ta.Variables[key]
            info.Path = field.Ident
            info.UsageCount++
            
            // 基于使用模式推断类型
            if len(field.Ident) > 1 {
                info.InferredType = "object"
            } else if strings.Contains(tmplText, "range ."+key) {
                info.InferredType = "array"
            } else {
                info.InferredType = "scalar"
            }
            
            ta.Variables[key] = info
        }
        return true
    })
    
    return nil
}

这些方法提供了:

  • 模板变量提取和路径分析
  • 基于使用模式的类型推断(标量/数组/对象)
  • 与Go类型系统的集成
  • JSON Schema生成能力

虽然没有直接等价于jinja2schema的库,但通过组合text/template解析、AST分析和第三方JSON Schema库,可以在Go中实现类似的模板分析和类型推断功能。

回到顶部