Skip to content

Latest commit

 

History

History
180 lines (150 loc) · 4.79 KB

README.md

File metadata and controls

180 lines (150 loc) · 4.79 KB

simplegen

Tool for simple use of ast-based code generation.

Go Reference

Problem

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).

Solution

simplegen do all slow stuff only once allowing developer to use ast to collect data for codegen templates.

Usage

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()
}

Documentation

See godoc for general API details. See examples for more ways to use simplegen.

Tools

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:

  1. We need StructType of MyStruct to iterate over struct fields.
  2. We should process Settings field with external JSONB 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)
	}
}