jenny

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