Tool for simple use of ast-based code generation.
In large codebase or in microservice architecture could be a bunch of codegen tools on many files. There often runs via go:generate
.
Which leads to slowdown of build process, because each call to go:generate
means that tool should parse file (slow), build ast.Tree
(slow), do some stuff with ast
(usually fast) and write new file (slow).
simplegen
do all slow stuff only once allowing developer to use ast
to collect data for codegen templates.
package main
import (
"flag"
"go/ast"
"golang.org/x/tools/go/packages"
"github.com/AlwxSin/simplegen"
)
var PaginatorTemplate = `
{{ range $key, $struct := .Specs }}
// {{$struct.Name}}ListPaginated represents {{$struct.Name}} list in a pagination container.
type {{$struct.Name}}ListPaginated struct {
CurrentCursor *string ` + "`json:\"currentCursor\"`\n" +
` NextCursor *string ` + "`json:\"nextCursor\"`\n" +
` Results []*{{$struct.Name}} ` + "`json:\"results\"`\n" +
`
isPaginated bool
limit int
offset int
}
`
func Paginator(
sg *simplegen.SimpleGenerator,
pkg *packages.Package,
node *ast.TypeSpec,
comment *ast.Comment,
) (templateData simplegen.SpecData, imports []string, err error) {
imports = append(imports, "strconv")
type PaginatorTypeSpec struct {
Name string
}
tmplData := &PaginatorTypeSpec{
Name: node.Name.Name,
}
return simplegen.SpecData(tmplData), imports, nil
}
// We want all our structs to be paginated in same way, but don't want to repeat boilerplate code for pagination.
// Use magic comment
// simplegen:{generator_name}
// simplegen:paginator
type User struct {
ID int
Email string
}
// simplegen:paginator
type Work struct {
ID int
UserID int
}
func main() {
var pn simplegen.PackageNames // it's an alias for []string type, need for multiple cli arguments
flag.Var(&pn, "package", "Package where simplegen should find magic comments")
flag.Parse()
// create simplegen instance with function map
// {generator_name} -> {
// Template -> string with template
// GeneratorFunc -> func to extract data from ast.Node
// }
sg, _ := simplegen.NewSimpleGenerator(pn, simplegen.GeneratorsMap{
"paginator": simplegen.TemplateGenerator{
Template: PaginatorTemplate,
GeneratorFunc: Paginator,
},
}, nil)
_ = sg.Generate()
}
And run it with
go run main.go -package github.com/my_project/main
-package
argument is required and should be in form of {module_in_go.mod}/{path}/{package}
It can be used by go:generate
as well
//go:generate go run github.com/AlwxSin/simplegen -package github.com/my_project/responses -package github.com/my_project/models
package main
func main() {
cmd.Execute()
}
See godoc for general API details.
See examples for more ways to use simplegen
.
simplegen
instance has several useful methods to work with ast
. For example, we have the following struct
package models
import "my_project/types"
// simplegen:my_command
type MyStruct struct {
ID int
Settings types.JSONB
}
And we want to generate some struct based on MyStruct
fields.
// Code generated by github.com/AlwxSin/simplegen, DO NOT EDIT.
package models
import "time"
import "my_project/types"
type MyStructGenerated struct {
// all fields from base MyStruct
ID int
Settings types.JSONB
// extra fields
CreatedAt time.Time
}
Our generator
function has pkg
in arguments, but it's a models
package.
When we parse MyStruct
fields:
- We need
StructType
ofMyStruct
to iterate over struct fields. - We should process
Settings
field with externalJSONB
type, we should know which package contains this type to import it in generated file.
package models
import (
"strings"
"strconv"
"github.com/AlwxSin/simplegen"
"golang.org/x/tools/go/packages"
"go/ast"
"fmt"
"go/types"
)
func MyCommandGenerator(
sg *simplegen.SimpleGenerator,
pkg *packages.Package,
node *ast.TypeSpec,
comment *ast.Comment,
) (templateData simplegen.SpecData, imports []string, err error) {
// 1. Get StructType of MyStruct
structType, _ := sg.GetStructType(pkg, node.Name.Name)
for i := 0; i < structType.NumFields(); i++ {
field := structType.Field(i)
// 2. get package of MyStruct field
pkgPath := field.Type().(*types.Named).Obj().Pkg().Path()
fieldPkg, _ := sg.GetPackage(pkgPath)
fmt.Println(fieldPkg)
}
}