go-poet is a Go package for generating Go code, inspired by javapoet.
Typically, code generation uses text templating which can be error prone and hard to maintain. This project aims to fix these issues by giving you an API to generate Go constructs in a typesafe way, while handling imports and formatting for you.
$ go get github.com/dpolansky/go-poet/poet
Here's a Hello World Go file
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello world!")
}
and the go-poet code to generate it
main := poet.NewFuncSpec("main").
Statement("$T($S)", poet.TypeReferenceFromInstance(fmt.Println), "Hello world!")
file := poet.NewFileSpec("main").
CodeBlock(main)
To get started, import "github.com/dpolansky/go-poet/poet"
The end goal of go-poet is to create a compilable file. To construct a new file with package main
file := poet.NewFileSpec("main")
Files contain CodeBlocks which can be global variables, functions, structs, and interfaces. go-poet will handle any imports for you via TypeReferences. The types that you create or reference can be used in code via Templates.
Functions can have parameters, result parameters, and statements within them.
add := poet.NewFuncSpec("add").
Parameter("a", poet.Int).
Parameter("b", poet.Int).
ResultParameter("", poet.Int).
Statement("return a + b")
which produces
func add(a int, b int) int {
return a + b
}
To add control flow statements, use BlockStart and BlockEnd. Indentation will be handled for you.
loop := poet.NewFuncSpec("loop").
BlockStart("for i := 0; i < 5; i++").
Statement("$T($L)", poet.TypeReferenceFromInstance(fmt.Println), "i").
BlockEnd()
produces
func loop() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}
Interfaces can have other interfaces embedded within them, as well as method declarations.
inter := poet.NewInterfaceSpec("AddWriter").
EmbedInterface(poet.TypeReferenceFromInstance((*io.Writer)(nil))).
Method(add)
produces
type AddWriter interface {
io.Writer
add(a int, b int) int
}
Structs can have fields, directly attached methods, and a comment.
foo := poet.NewStructSpec("foo").
Field("buf", poet.TypeReferenceFromInstance(&bytes.Buffer{}))
m := foo.MethodFromFunction("f", true, add)
foo.AttachMethod(m)
produces
type foo struct {
buf *bytes.Buffer
}
func (f *foo) add(a int, b int) int {
return a + b
}
Global variables and constants can be added directly to a file, either standalone or in groups.
file.GlobalVariable("a", poet.String, "$S", "hello")
file.GlobalConstant("b", poet.Int, "$L", 1)
var a string = "hello"
const b int = 1
or if you want to group them
file.VariableGrouping().
Variable("a", poet.Int, "$L", 7).
Variable("b", poet.Int, "$L", 2).
Constant("c", poet.Int, "$L", 3).
Constant("d", poet.Int, "$L", 43)
const (
c int = 3
d int = 43
)
var (
a int = 7
b int = 2
)
To ensure type safe code and handle a generated file's imports, use TypeReferences.
For example, to use bytes.Buffer
as a parameter to a function
poet.NewFuncSpec("foo").Parameter("buf", poet.TypeReferenceFromInstance(&bytes.Buffer{}))
produces
func foo(buf *bytes.Buffer) {
}
The poet.TypeReferenceFromInstance
function takes an instance of a variable or a function and uses reflection to determine its type and package.
To use an aliased package's name from a TypeReference, use poet.TypeReferenceFromInstanceWithAlias
.
poet.TypeReferenceFromInstanceWithAlias(&bytes.Buffer{}, "myAlias")
produces a TypeReference with type
*myAlias.Buffer
For type aliases, you may want to reference the aliased name instead of the underlying type.
To do this, use poet.TypeReferenceFromInstanceWithCustomName
poet.TypeReferenceFromInstanceWithCustomName(uint8(0), "byte")
If you want a type to be unqualified, create a type alias with the prefix _unqualified
followed by the name
type _unqualifiedBuffer bytes.Buffer
typeRef := TypeReferenceFromInstance(_unqualifiedBuffer{})
produces the type Buffer
Format strings are used to construct statements in functions or values for variables.
We currently support these format specifiers:
- Strings
$S
Takes astring
as input, surrounding it with quotes and escaping quotes within the input - Literals
$L
Takes any value as input, and uses Go'sSprintf
%v
formatting to write the input - Types
$T
Takes a TypeReference as input, and writes its qualified/aliased name