go generate

Created

2024-10-28 13:33:40

Updated

2024-10-28 13:33:44

todo…

1 介绍

Tip
  1. Go 语言的标准库中的 cmd/go/internal/generate
  2. go generate 命令会遍历指定的包路径下的所有 Go 源文件,并解析这些源文件中的 //go:generate 注释,这些注释包含了代码生成指令
  3. //go:generate 后面就是会执行的命令,比如 //go:generate echo hello 执行go generate 会显示 hello

2 例子

提醒

早期笔记, 下面的例子粗糙,{==给struct类型生成getset方法==}, 等整理了ast后, 我会再完善一下.
建议去学习 ==stringer==

tree
.
├── abc.go
├── codegen
   └── gen.go
├── go.mod
└── pkg
    └── cat.go
//go:generate echo 123

// goto hell

// xyz
package main

//go:generate go run codegen/gen.go
//go:generate echo hello--world

//helloworld33
type Person struct { // this is person
    Name string // this is name
    Age  int32
}

//go:generate echo 99
type Cup struct {
    Price  float64
    Origin string
}
codegen/gen.go
package main

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

type Field struct {
    Name string
    Type string
}

type StructInfo struct {
    Name        string
    Fields      []Field
    PackageName string
}

func generateCode() {
    fset := token.NewFileSet()

    // 解析当前目录下所有的 Go 源文件,不会递归解析子目录里的文件的.
    pkgs, err := parser.ParseDir(fset, ".", nil, parser.ParseComments)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Failed to parse directory: %v\n", err)
        os.Exit(1)
    }

    for _, pkg := range pkgs {
        for _, file := range pkg.Files {
            packageName := file.Name.Name
            // for _, l := range file.Doc.List {
            //  fmt.Println("file-doc", l.Text)
            // }
            // fmt.Println("---", packageName, file.Doc.Text(), "===")
            // for _, c := range file.Comments {
            //  // 挨在一起的2行注释算一个file的Comment
            //  fmt.Print("[")
            //  for _, l := range c.List {
            //      fmt.Println("cc", l.Text)
            //  }
            //  fmt.Println("]")
            // }
            // for _, d := range file.Decls {
            //  x := d.(*ast.GenDecl)
            //  if x.Doc != nil {
            //      fmt.Println(len(x.Doc.List))
            //      for _, l := range x.Doc.List {
            //          fmt.Println("ooo", l.Text)
            //      }
            //      fmt.Println("gg", x.Doc.Text(), "--")
            //  }
            //  ts := x.Specs[0].(*ast.TypeSpec)
            //  fmt.Println(ts.Name)

            // }
            // continue
            ast.Inspect(file, func(n ast.Node) bool {

                switch x := n.(type) {
                // 注释写在type struct 上面的注释 是算一个节点. 不是属于type struct的内部东西
                case *ast.GenDecl:
                    // fmt.Println("注释内容2:", x.Doc.Text())
                case *ast.CommentGroup:
                    // fmt.Println("注释内容:", x.Text())
                case *ast.TypeSpec:
                    if structType, ok := x.Type.(*ast.StructType); ok {
                        fmt.Println("结构体名称:", x.Name.Name)
                        structInfo := StructInfo{
                            Name:        x.Name.Name,
                            PackageName: packageName,
                        }

                        // 提取结构体字段信息
                        for _, field := range structType.Fields.List {
                            for _, ident := range field.Names {
                                structInfo.Fields = append(structInfo.Fields, Field{
                                    Name: ident.Name,
                                    Type: fileSetToString(fset, field.Type),
                                })
                            }
                        }

                        // 生成 Get 和 Set 方法的代码
                        generateGetSetMethods(structInfo)

                    }
                }
                // ast.Print(fset, n)
                return true
            })
        }
    }
}

func generateGetSetMethods(structInfo StructInfo) {
    tmpl := template.Must(template.New("").Parse(getSetMethodsTemplate))

    fileName := structInfo.Name + "_getset.go"
    fmt.Println(fileName)
    file, err := os.Create(fileName)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Failed to create file: %v\n", err)
        os.Exit(1)
    }
    defer file.Close()

    err = tmpl.Execute(file, structInfo)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Failed to generate code: %v\n", err)
        os.Exit(1)
    }

    fmt.Printf("生成的文件 %s\n", fileName)
}

func fileSetToString(fset *token.FileSet, node ast.Node) string {
    var buf strings.Builder
    if err := printer.Fprint(&buf, fset, node); err != nil {
        return ""
    }
    return buf.String()
}

const getSetMethodsTemplate = `
// Code generated by go generate; DO NOT EDIT.

package {{.PackageName}}

{{range .Fields}}
func (s *{{$.Name}}) Get{{.Name}}() {{.Type}} {
    return s.{{.Name}}
}

func (s *{{$.Name}}) Set{{.Name}}(value {{.Type}}) {
    s.{{.Name}} = value
}
{{end}}
`

func main() {
    fmt.Println(111)
    generateCode()
}
# 只会解析当前目录下的所有go文件
go generate # 会创建struct类型的 getset 方法 的go文件

# 或者
cd codegen
go build .
mv codegen /usr/local/bin
然后在注释中 这样写 也是ok的
//go:generate codegen

# 解析子目录 pkg 需要进去目录?
cd pkg
go generate

3 stringer

Back to top