github.com/dpolansky/go-poet/poet

A Go package for generating Go code


License
MIT
Install
go get github.com/dpolansky/go-poet/poet

Documentation

go-poet

MIT License GoDoc Build Status Go Report Card Codecov Coverage Report

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.

Installation

$ go get github.com/dpolansky/go-poet/poet

Example

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)

Getting Started

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.

Code Blocks

Functions

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

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

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
}

Globals

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
)

Type References

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.

Package Aliases

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

Custom Names

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

Unqualified Types

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

Templating

Format strings are used to construct statements in functions or values for variables.

We currently support these format specifiers:

  • Strings $S Takes a string as input, surrounding it with quotes and escaping quotes within the input
  • Literals $L Takes any value as input, and uses Go's Sprintf %v formatting to write the input
  • Types $T Takes a TypeReference as input, and writes its qualified/aliased name

Authors

Dave Polansky

Kevin Deenanauth