Help us shape the future of Go! Take the 2026 Go Developer Survey

jennifer

module
v0.9.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 3, 2017 License: MIT

README

Jennifer

Jennifer is a code generator for Go:

package main

import (
    "fmt"

    . "github.com/davelondon/jennifer/jen"
)

func main() {
    f := NewFile("main")
    f.Func().Id("main").Params().Block(
        Qual("fmt", "Println").Call(Lit("Hello, world")),
    )
    fmt.Printf("%#v", f)
}

Output:

package main

import fmt "fmt"

func main() {
    fmt.Println("Hello, world")
}

Install

go get -u github.com/davelondon/jennifer/jen

Examples

The tests are written mostly as examples - see godoc.org for an index.

Real world examples

Most of the code that powers jennifer is generated by jennifer itself, see the genjen package - it creates generated.go.

A much larger implementation of jennifer can be found in the kego project.

Rendering

For testing, a File or Statement can be rendered with the fmt package using the %#v verb:

c := Id("a").Call(Lit("b"))
fmt.Printf("%#v", c)
// Output: a("b")

This is not recommended for use in production because any error will cause a panic. For production use, File.Render or File.Save are preferred.

Id

Id renders an identifier:

c := Id("a")
fmt.Printf("%#v", c)
// Output: a

Qual

Qual renders a qualified identifier:

c := Qual("encoding/gob", "NewEncoder").Call()
fmt.Printf("%#v", c)
// Output: gob.NewEncoder()

Imports are automatically added when used with a File. If the path matches the local path, the package name is omitted. If package names conflict they are automatically renamed:

f := NewFilePath("a.b/c")
f.Func().Id("init").Params().Block(
    Qual("a.b/c", "Foo").Call().Comment("Local package - name is omitted."),
    Qual("d.e/f", "Bar").Call().Comment("Import is automatically added."),
    Qual("g.h/f", "Baz").Call().Comment("Colliding package name is renamed."),
)
fmt.Printf("%#v", f)
// Output: package c
// 
// import (
//  f "d.e/f"
//  f1 "g.h/f"
// )
// 
// func init() {
//  Foo()    // Local package - name is omitted.
//  f.Bar()  // Import is automatically added.
//  f1.Baz() // Colliding package name is renamed.
// } 

Op

Op renders the provided operator / token:

c := Id("a").Op(":=").Id("b").Call()
fmt.Printf("%#v", c)
// Output: a := b()
c := Op("*").Id("a")
fmt.Printf("%#v", c)
// Output: *a
c := Id("a").Call(Id("b").Op("..."))
fmt.Printf("%#v", c)
// Output: a(b...)

Identifiers

Identifiers are simple methods with no parameters. They render as the identifier token:

c := Break()
fmt.Printf("%#v", c)
// Output: break

Keywords: Break, Default, Func, Select, Defer, Go, Struct, Chan, Else, Goto, Const, Fallthrough, Range, Type, Continue, Var

Built-in types: Bool, Byte, Complex64, Complex128, Error, Float32, Float64, Int, Int8, Int16, Int32, Int64, Rune, String, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr

Constants: True, False, Iota, Nil

Also included is Err for the commonly used err variable.

Note: Interface, Map, Return, Switch, For, Case and If are special cases, and treated as groups - see below.

Note: The import and package keywords are always rendered automatically, so not included.

Built-in functions

Built in functions render the function name followed by a comma separated list enclosed by parenthesis:

c := Append(Id("a"), Id("b"))
fmt.Printf("%#v", c)
// Output: append(a, b)

Functions: Append, Cap, Close, Complex, Copy, Delete, Imag, Len, Make, New, Panic, Print, Println, Real, Recover

Groups

Groups take either a single item or a list of items. The items are rendered between open and closing tokens. Multiple items are separated by a separator token:

Groups accepting a list of items:
Group Opening Separator Closing Usage
Sel . foo.bar[0].baz()
List , a, b := c()
Call ( , ) fmt.Println(b, c)
Params ( , ) func (a *A) Foo(i int) { ... }
Index [ : ] a[1:2] or []int{}
Values { , } []int{1, 2}
Block { \n } func a() { ... }
Defs ( \n ) const ( ... )
Interface interface { \n } interface { ... }
Switch switch ; switch a { ... }
Case case , switch a {case "b", "c": ... }
CaseBlock : \n switch i {case 1: ... }
Return return , return a, b
If if ; if a, ok := b(); ok { ... }
For for ; for i := 0; i < 10; i++ { ... }
Groups accepting a single item:
Group Opening Closing Usage
Parens ( ) []byte(s) or a / (b + c)
Assert .( ) s, ok := i.(string)
Map map[ ] map[int]string
Sel

Sel renders a chain of selectors separated by periods:

c := Sel(
    Qual("a.b/c", "Foo"),
    Id("Bar").Call(),
    Id("Baz").Index(Lit(0)),
    Id("Qux"),
)
fmt.Printf("%#v", c)
// Output: c.Foo.Bar().Baz[0].Qux
List

List renders a comma separated list with no open or closing tokens. Use for multiple return functions:

c := List(Id("a"), Id("b")).Op(":=").Id("c").Call()
fmt.Printf("%#v", c)
// Output: a, b := c()
Call

Call renders a comma separated list enclosed by parenthesis. Use for function calls:

c := Id("a").Call(Id("b"), Id("c"))
fmt.Printf("%#v", c)
// Output: a(b, c)
Params

Params renders a comma separated list enclosed by parenthesis. Use for function parameters and method receivers:

c := Func().Params(Id("a").Id("A")).Id("foo").Params(Id("b").String()).String().Block()
fmt.Printf("%#v", c)
// Output: func (a A) foo(b string) string {}
Index

Index renders a colon separated list enclosed by square brackets. Use for array / slice indexes and definitions:

c := Var().Id("a").Index().String()
fmt.Printf("%#v", c)
// Output: var a []string
c := Id("a").Op(":=").Id("b").Index(Lit(0), Lit(1))
fmt.Printf("%#v", c)
// Output: a := b[0:1]
c := Id("a").Op(":=").Id("b").Index(Lit(1), Empty())
fmt.Printf("%#v", c)
// Output: a := b[1:]
Values

Values renders a comma separated list enclosed by curly braces. Use for slice literals:

c := Index().String().Values(Lit("a"), Lit("b"))
fmt.Printf("%#v", c)
// Output: []string{"a", "b"}
Block

Block renders a statement list enclosed by curly braces. Use for all code blocks:

c := Func().Id("main").Params().Block(
    Id("a").Op("++"),
    Id("b").Op("--"),
)
fmt.Printf("%#v", c)
// Output: func main() {
//  a++
//  b--
// }
c := If(Id("a").Op(">").Lit(10)).Block(
    Id("a").Op("=").Id("a").Op("/").Lit(2),
)
fmt.Printf("%#v", c)
// Output: if a > 10 {
//  a = a / 2
// }
Defs

Defs renders a list of statements enclosed in parenthesis. Use for definition lists:

c := Const().Defs(
    Id("a").Op("=").Lit("a"),
    Id("b").Op("=").Lit("b"),
)
fmt.Printf("%#v", c)
// Output: const (
// 	a = "a"
// 	b = "b"
// )
Interface

Interface renders the interface keyword followed by a statement block:

c := Var().Id("a").Interface()
fmt.Printf("%#v", c)
// Output: var a interface{}
c := Type().Id("a").Interface(
    Id("b").Params().String(),
)
fmt.Printf("%#v", c)
// Output: type a interface {
// 	b() string
// }
Switch, Case, CaseBlock

Switch, Case and CaseBlock can be used to build switch statements:

c := Switch(Id("a")).Block(
    Case(Lit("1")).CaseBlock(
        Return(Lit(1)),
    ),
    Case(Lit("2"), Lit("3")).CaseBlock(
        Return(Lit(2)),
    ),
    Case(Lit("4")).CaseBlock(
        Fallthrough(),
    ),
    Default().CaseBlock(
        Return(Lit(3)),
    ),
)
fmt.Printf("%#v", c)
// Output: switch a {
// case "1":
// 	return 1
// case "2", "3":
// 	return 2
// case "4":
// 	fallthrough
// default:
// 	return 3
// }
Return

Return renders the return keyword followed by a comma separated list:

c := Return(Id("a"), Id("b"))
fmt.Printf("%#v", c)
// Output: return a, b
If

If renders the if keyword followed by a semicolon separated list:

c := If(Err().Op(":=").Id("a").Call(), Err().Op("!=").Nil()).Block(
    Return(Err()),
)
fmt.Printf("%#v", c)
// Output: if err := a(); err != nil {
//  return err
// }
For

For renders the for keyword followed by a semicolon separated list:

c := For(Id("i").Op(":=").Lit(0), Id("i").Op("<").Lit(10), Id("i").Op("++")).Block(
    Qual("fmt", "Println").Call(Id("i")),
)
fmt.Printf("%#v", c)
// Output: for i := 0; i < 10; i++ {
//  fmt.Println(i)
// }
Parens

Parens renders a single item in parenthesis. Use for type conversion or to specify evaluation order:

c := Id("b").Op(":=").Index().Byte().Parens(Id("s"))
fmt.Printf("%#v", c)
// Output: b := []byte(s)
c := Id("a").Op("/").Parens(Id("b").Op("+").Id("c"))
fmt.Printf("%#v", c)
// Output: a / (b + c)
Assert

Assert renders a period followed by a single item enclosed by parenthesis. Use for type assertions:

c := Id("a").Op("=").Id("b").Assert(String())
fmt.Printf("%#v", c)
// Output: a = b.(string)
Map

Map renders the map keyword followed by a single item enclosed by square brackets. Use for map definitions:

c := Id("a").Op(":=").Map(String()).String().Values()
fmt.Printf("%#v", c)
// Output: a := map[string]string{}
Alternate GroupFunc methods

All of the Group functions are paired with GroupFunc functions that accept a func(*Group). Use these for embedding logic:

increment = true
c := Func().Id("a").Params().BlockFunc(func(g *Group) {
    if increment {
        g.Id("a").Op("++")
    } else {
        g.Id("a").Op("--")
    }
})
fmt.Printf("%#v", c)
// Output: func a() {
// 	a++
// }

Add

Add adds the provided items to the Statement.

ptr := Op("*")
c := Id("a").Op("=").Add(ptr).Id("b")
fmt.Printf("%#v", c)
// Output: a = *b
a := Id("a")
i := Int()
c := Var().Add(a, i)
fmt.Printf("%#v", c)
// Output: var a int

Do

Do takes a func(*Statement) and executes it on the current statement. Use this for embedding logic:

f := func(name string, isMap bool) *Statement {
    return Id(name).Op(":=").Do(func(s *Statement) {
        if isMap {
            s.Map(String()).String()
        } else {
            s.Index().String()
        }
    }).Values()
}
fmt.Printf("%#v\n%#v", f("a", true), f("b", false))
// Output: a := map[string]string{}
// b := []string{}

Lit, LitFunc

Lit renders a literal, using the format provided by fmt.Sprintf("%#v", ...).

TODO: This probably isn't good enough for all cases.

c := Id("a").Op(":=").Lit("a")
fmt.Printf("%#v", c)
// Output: a := "a"
c := Id("a").Op(":=").Lit(1.5)
fmt.Printf("%#v", c)
// Output: a := 1.5

Dict, DictFunc

Dict takes a map[Code]Code and renders a list of colon separated key value pairs, enclosed in curly braces. Use for map literals:

c := Id("a").Op(":=").Map(String()).String().Dict(map[Code]Code{
    Lit("a"): Lit("b"),
    Lit("c"): Lit("b"),
})
fmt.Printf("%#v", c)
// Output: a := map[string]string{
// 	"a": "b",
// 	"c": "d",
// }

DictFunc does the same by executing the provided func(map[Code]Code):

c := Id("a").Op(":=").Map(String()).String().DictFunc(func(m map[Code]Code) {
    m[Lit("a")] = Lit("b")
    m[Lit("c")] = Lit("d")
})
fmt.Printf("%#v", c)
// Output: a := map[string]string{
// 	"a": "b",
// 	"c": "d",
// }

Note: dicts are ordered by key when rendered.

Tag

Tag renders a struct tag:

c := Type().Id("foo").Struct().Block(
    Id("A").String().Tag(map[string]string{"json": "a"}),
    Id("B").Int().Tag(map[string]string{"json": "b", "bar": "baz"}),
)
fmt.Printf("%#v", c)
// Output: type foo struct {
// 	A string `json:"a"`
// 	B int    `bar:"baz" json:"b"`
// }

Note: struct tags are ordered by key when rendered.

Null

Null adds a null item. Null items render nothing and are not followed by a separator in lists.

c := Id("a").Op(":=").Id("b").Index(Null(), Lit(1))
fmt.Printf("%#v", c)
// Output: a := b[1]

Empty

Empty adds an empty item. Empty items render nothing but are followed by a separator in lists.

c := Id("a").Op(":=").Id("b").Index(Empty(), Lit(1))
fmt.Printf("%#v", c)
// Output: a := b[:1]

Line

Line inserts a blank line.

Comment, Commentf

Comment adds a comment. If the provided string contains a newline, the comment is formatted in multiline style:

c := Comment("a")
fmt.Printf("%#v", c)
// Output: // a
c := Comment("a\nb")
fmt.Printf("%#v", c)
// Output: /*
// a
// b
// */
c := Id("a").Call().Comment("b")
fmt.Printf("%#v", c)
// Output: a() // b

Commentf accepts a format string and a list of parameters:

c := Commentf("a %d", 1)
fmt.Printf("%#v", c)
// Output: // a 1
c := Id("a").Call().Commentf("b %d", 1)
fmt.Printf("%#v", c)
// Output: a() // b 1

File

NewFile

NewFile Creates a new file, with the specified package name.

NewFilePath

NewFilePath creates a new file while specifying the package path - the package name is inferred from the path.

NewFilePathName

NewFilePathName creates a new file with the specified package path and name.

f := NewFilePathName("a.b/c", "main")
f.Func().Id("main").Params().Block(
    Qual("a.b/c", "Foo").Call(),
)
fmt.Printf("%#v", f)
// Output: package main
//
// func main() {
// 	Foo()
// }
PackageComment

PackageComment adds a comment to the top of the file, above the package keyword:

f := NewFile("c")
f.PackageComment("a")
f.PackageComment("b")
f.Func().Id("init").Params().Block()
fmt.Printf("%#v", f)
// Output: // a
// // b
// package c
//
// func init() {}
Anon

Anon adds an anonymous import:

f := NewFile("c")
f.Anon("a")
f.Func().Id("init").Params().Block()
fmt.Printf("%#v", f)
// Output:
// package c
//
// import _ "a"
//
// func init() {}
PackagePrefix

If you're worried about package aliases conflicting with local variable names, you can set a prefix:

f := NewFile("c")
f.PackagePrefix = "pkg"
f.Func().Id("main").Params().Block(
    Qual("fmt", "Println").Call(),
)
fmt.Printf("%#v", f)
// Output:
// package c
//
// import pkg_fmt "fmt"
//
// func main() {
// 	pkg_fmt.Println()
// }
Save

Save renders the file and saves to the filename provided.

Render

Render renders the file to the provided writer:

f := NewFile("a")
f.Func().Id("main").Params().Block()
buf := &bytes.Buffer{}
err := f.Render(buf)
if err != nil {
    fmt.Println(err.Error())
} else {
    fmt.Println(buf.String())
}
// Output: package a
//
// func main() {}

Pointers

Be careful when passing *Statement around. Consider the following example:

a := Id("a")
c := Block(
    a.Call(),
    a.Call(),
)
fmt.Printf("%#v", c)
// Output: {
// 	a()()
// 	a()()
// }

Id("a") returns a *Statement, which the Call() method appends to twice. To avoid this, use Clone to create a new *Statement:

a := Id("a")
c := Block(
    a.Clone().Call(),
    a.Clone().Call(),
)
fmt.Printf("%#v", c)
// Output: {
// 	a()
// 	a()
// }

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL