Jennifer is a code generator for Go

Overview

Build Status Go Report Card codecov stability-stable Sourcegraph

Jennifer

Jennifer is a code generator for Go.

package main

import (
    "fmt"

    . "github.com/dave/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"

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

Install

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

Need help?

If you get stuck, have a question, would like a code review, or just want a chat: I'm happy to help! Feel free to open an issue, email me or mention @dave in your PR.

Examples

Jennifer has a comprehensive suite of examples - see godoc for an index. Here's some examples of jennifer being used in the real-world:

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.

Identifiers

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Id

Id renders an identifier.

c := If(Id("i").Op("==").Id("j")).Block(
	Return(Id("i")),
)
fmt.Printf("%#v", c)
// Output:
// if i == j {
// 	return i
// }

Dot

Dot renders a period followed by an identifier. Use for fields and selectors.

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

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.
// }

Note that it is not possible to reliably determine the package name given an arbitrary package path, so a sensible name is guessed from the path and added as an alias. The names of all standard library packages are known so these do not need to be aliased. If more control is needed of the aliases, see File.ImportName or File.ImportAlias.

List

List renders a comma separated list. Use for multiple return functions.

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

Keywords

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Simple keywords, predeclared identifiers and built-in functions are self explanatory:

Construct Name
Keywords Break, Chan, Const, Continue, Default, Defer, Else, Fallthrough, Func, Go, Goto, Range, Select, Type, Var
Functions Append, Cap, Close, Complex, Copy, Delete, Imag, Len, Make, New, Panic, Print, Println, Real, Recover
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
Helpers Err

Built-in functions take a list of parameters and render them appropriately:

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

Special cases for If, For, Interface, Struct, Switch, Case, Return and Map are explained below.

Operators

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Op renders the provided operator / token.

c := Id("a").Op(":=").Id("b").Call()
fmt.Printf("%#v", c)
// Output:
// a := b()
c := Id("a").Op("=").Op("*").Id("b")
fmt.Printf("%#v", c)
// Output:
// a = *b
c := Id("a").Call(Id("b").Op("..."))
fmt.Printf("%#v", c)
// Output:
// a(b...)
c := If(Parens(Id("a").Op("||").Id("b")).Op("&&").Id("c")).Block()
fmt.Printf("%#v", c)
// Output:
// if (a || b) && c {
// }

Braces

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Several methods render curly braces, summarized below:

Name Prefix Separator Example
Block \n func a() { ... } or if a { ... }
Interface interface \n interface { ... }
Struct struct \n struct { ... }
Values , []int{1, 2} or A{B: "c"}

Block

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

c := Func().Id("foo").Params().String().Block(
	Id("a").Op("=").Id("b"),
	Id("b").Op("++"),
	Return(Id("b")),
)
fmt.Printf("%#v", c)
// Output:
// func foo() string {
// 	a = b
// 	b++
// 	return 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
// }

A special case applies when used directly after Case or Default, where the braces are omitted. This allows use in switch and select statements. See example.

Interface, Struct

Interface and Struct render the keyword followed by a statement list enclosed by curly braces.

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
// }
c := Id("c").Op(":=").Make(Chan().Struct())
fmt.Printf("%#v", c)
// Output:
// c := make(chan struct{})
c := Type().Id("foo").Struct(
	List(Id("x"), Id("y")).Int(),
	Id("u").Float32(),
)
fmt.Printf("%#v", c)
// Output:
// type foo struct {
// 	x, y int
// 	u    float32
// }

Parentheses

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Several methods output parenthesis, summarized below:

Name Prefix Separator Example
Call , fmt.Println(b, c)
Params , func (a *A) Foo(i int) { ... }
Defs \n const ( ... )
Parens []byte(s) or a / (b + c)
Assert . s, ok := i.(string)

Call

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

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

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"),
	Id("c").String(),
).String().Block(
	Return(Id("b").Op("+").Id("c")),
)
fmt.Printf("%#v", c)
// Output:
// func (a A) foo(b, c string) string {
// 	return b + c
// }

Defs

Defs renders a statement list 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"
// )

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 := List(Id("b"), Id("ok")).Op(":=").Id("a").Assert(Bool())
fmt.Printf("%#v", c)
// Output:
// b, ok := a.(bool)

Control flow

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

If, For

If and For render the 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
// }
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)
// }

Switch, Select

Switch, Select, Case and Block are used to build switch or select statements:

c := Switch(Id("value").Dot("Kind").Call()).Block(
	Case(Qual("reflect", "Float32"), Qual("reflect", "Float64")).Block(
		Return(Lit("float")),
	),
	Case(Qual("reflect", "Bool")).Block(
		Return(Lit("bool")),
	),
	Case(Qual("reflect", "Uintptr")).Block(
		Fallthrough(),
	),
	Default().Block(
		Return(Lit("none")),
	),
)
fmt.Printf("%#v", c)
// Output:
// switch value.Kind() {
// case reflect.Float32, reflect.Float64:
// 	return "float"
// case reflect.Bool:
// 	return "bool"
// case reflect.Uintptr:
// 	fallthrough
// default:
// 	return "none"
// }

Return

Return renders the keyword followed by a comma separated list.

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

Collections

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Map

Map renders the 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{}

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 or composite literals.

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

Dict renders as key/value pairs. Use with Values for map or composite literals.

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

DictFunc executes a func(Dict) to generate the value.

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

Note: the items are ordered by key when rendered to ensure repeatable code.

Literals

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Lit

Lit renders a literal. Lit supports only built-in types (bool, string, int, complex128, float64, float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr and complex64). Passing any other type will panic.

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

LitFunc generates the value to render by executing the provided function.

c := Id("a").Op(":=").LitFunc(func() interface{} { return 1 + 1 })
fmt.Printf("%#v", c)
// Output:
// a := 2

For the default constant types (bool, int, float64, string, complex128), Lit will render the untyped constant.

Code Output
Lit(true) true
Lit(1) 1
Lit(1.0) 1.0
Lit("foo") "foo"
Lit(0 + 1i) (0 + 1i)

For all other built-in types (float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, complex64), Lit will also render the type.

Code Output
Lit(float32(1)) float32(1)
Lit(int16(1)) int16(1)
Lit(uint8(0x1)) uint8(0x1)
Lit(complex64(0 + 1i)) complex64(0 + 1i)

The built-in alias types byte and rune need a special case. LitRune and LitByte render rune and byte literals.

Code Output
LitRune('x') 'x'
LitByte(byte(0x1)) byte(0x1)

Comments

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Comment

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

f := NewFile("a")
f.Comment("Foo returns the string \"foo\"")
f.Func().Id("Foo").Params().String().Block(
	Return(Lit("foo")).Comment("return the string foo"),
)
fmt.Printf("%#v", f)
// Output:
// package a
//
// // Foo returns the string "foo"
// func Foo() string {
// 	return "foo" // return the string foo
// }
c := Comment("a\nb")
fmt.Printf("%#v", c)
// Output:
// /*
// a
// b
// */

If the comment string starts with "//" or "/*", the automatic formatting is disabled and the string is rendered directly.

c := Id("foo").Call(Comment("/* inline */")).Comment("//no-space")
fmt.Printf("%#v", c)
// Output:
// foo( /* inline */ ) //no-space

Commentf

Commentf adds a comment, using a format string and a list of parameters.

name := "foo"
val := "bar"
c := Id(name).Op(":=").Lit(val).Commentf("%s is the string \"%s\"", name, val)
fmt.Printf("%#v", c)
// Output:
// foo := "bar" // foo is the string "bar"

Helpers

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Func methods

All constructs that accept a variadic list of items are paired with GroupFunc functions that accept a func(*Group). Use for embedding logic.

c := Id("numbers").Op(":=").Index().Int().ValuesFunc(func(g *Group) {
	for i := 0; i <= 5; i++ {
		g.Lit(i)
	}
})
fmt.Printf("%#v", c)
// Output:
// numbers := []int{0, 1, 2, 3, 4, 5}
increment := true
name := "a"
c := Func().Id("a").Params().BlockFunc(func(g *Group) {
	g.Id(name).Op("=").Lit(1)
	if increment {
		g.Id(name).Op("++")
	} else {
		g.Id(name).Op("--")
	}
})
fmt.Printf("%#v", c)
// Output:
// func a() {
// 	a = 1
// 	a++
// }

Add

Add appends 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 calls the provided function with the statement as a parameter. Use 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{}

Misc

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

Tag

Tag renders a struct tag

c := Type().Id("foo").Struct(
	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: the items are ordered by key when rendered to ensure repeatable code.

Null

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

In lists, nil will produce the same effect.

c := Func().Id("foo").Params(
	nil,
	Id("s").String(),
	Null(),
	Id("i").Int(),
).Block()
fmt.Printf("%#v", c)
// Output:
// func foo(s string, i int) {}

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(Lit(1), Empty())
fmt.Printf("%#v", c)
// Output:
// a := b[1:]

Line

Line inserts a blank line.

Clone

Be careful when passing *Statement. Consider the following...

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. Clone makes a copy of the Statement, so further tokens can be appended without affecting the original.

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

Cgo

The cgo "C" pseudo-package is a special case, and always renders without a package alias. The import can be added with Qual, Anon or by supplying a preamble. The preamble is added with File.CgoPreamble which has the same semantics as Comment. If a preamble is provided, the import is separated, and preceded by the preamble.

f := NewFile("a")
f.CgoPreamble(`#include <stdio.h>
#include <stdlib.h>

void myprint(char* s) {
printf("%s\n", s);
}
`)
f.Func().Id("init").Params().Block(
	Id("cs").Op(":=").Qual("C", "CString").Call(Lit("Hello from stdio\n")),
	Qual("C", "myprint").Call(Id("cs")),
	Qual("C", "free").Call(Qual("unsafe", "Pointer").Parens(Id("cs"))),
)
fmt.Printf("%#v", f)
// Output:
// package a
//
// import "unsafe"
//
// /*
// #include <stdio.h>
// #include <stdlib.h>
//
// void myprint(char* s) {
// 	printf("%s\n", s);
// }
// */
// import "C"
//
// func init() {
// 	cs := C.CString("Hello from stdio\n")
// 	C.myprint(cs)
// 	C.free(unsafe.Pointer(cs))
// }

File

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File

File represents a single source file. Package imports are managed automaticaly by 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()
// }

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

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

ImportName

ImportName provides the package name for a path. If specified, the alias will be omitted from the import block. This is optional. If not specified, a sensible package name is used based on the path and this is added as an alias in the import block.

f := NewFile("main")

// package a should use name "a"
f.ImportName("github.com/foo/a", "a")

// package b is not used in the code so will not be included
f.ImportName("github.com/foo/b", "b")

f.Func().Id("main").Params().Block(
	Qual("github.com/foo/a", "A").Call(),
)
fmt.Printf("%#v", f)

// Output:
// package main
//
// import "github.com/foo/a"
//
// func main() {
// 	a.A()
// }

ImportNames

ImportNames allows multiple names to be imported as a map. Use the gennames command to automatically generate a go file containing a map of a selection of package names.

ImportAlias

ImportAlias provides the alias for a package path that should be used in the import block. A period can be used to force a dot-import.

f := NewFile("main")

// package a should be aliased to "b"
f.ImportAlias("github.com/foo/a", "b")

// package c is not used in the code so will not be included
f.ImportAlias("github.com/foo/c", "c")

f.Func().Id("main").Params().Block(
	Qual("github.com/foo/a", "A").Call(),
)
fmt.Printf("%#v", f)

// Output:
// package main
//
// import b "github.com/foo/a"
//
// func main() {
// 	b.A()
// }

Comments

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

HeaderComment adds a comment to the top of the file, above any package comments. A blank line is rendered below the header comments, ensuring header comments are not included in the package doc.

CanonicalPath adds a canonical import path annotation to the package clause.

f := NewFile("c")
f.CanonicalPath = "d.e/f"
f.HeaderComment("Code generated by...")
f.PackageComment("Package c implements...")
f.Func().Id("init").Params().Block()
fmt.Printf("%#v", f)
// Output:
// // Code generated by...
//
// // Package c implements...
// package c // import "d.e/f"
//
// func init() {}

CgoPreamble adds a cgo preamble comment that is rendered directly before the "C" pseudo-package import.

PackagePrefix

If you're worried about generated package aliases conflicting with local variable names, you can set a prefix here. Package foo becomes {prefix}_foo.

f := NewFile("a")
f.PackagePrefix = "pkg"
f.Func().Id("main").Params().Block(
	Qual("b.c/d", "E").Call(),
)
fmt.Printf("%#v", f)
// Output:
// package a
//
// import pkg_d "b.c/d"
//
// func main() {
// 	pkg_d.E()
// }
Comments
  • Improper float formatting

    Improper float formatting

    This not is no longer true - maybe it was in a past version of Go? Seems that this should either be removed or the int version should be formatted in place of the %#v.0 so that we don't get .0.0 at the end for newer versions. This results in invalid generated code.

    https://github.com/dave/jennifer/blob/5ba2c0232d2f848cf1e390db03b100f0838f6d55/jen/tokens.go#L52

    opened by efritz 18
  • Adding imports manually

    Adding imports manually

    Is there a way to add imports manually just like jen.Anon without using jen.Qual. I have a case where I can not use jen.Qual but I need to specify some imports.

    Great project btw.

    opened by kujtimiihoxha 14
  • Import aliases for Qual

    Import aliases for Qual

    Hi, thanks for the great package. I had two questions regarding the generated import aliases.

    I noticed that doing something like Qual("context", "Context") would explicitly set the import alias, e.g. import context "context" even when it doesn't conflict with other imports. Is this by design to explicitly set the alias for all imports?

    The second question is being able to set the default import alias explicitly (in lieu of naming conflict resolution). For example, I have a package each having a sub-package that is named after the version: github.com/foo/bar/v1, github.com/foo/bar/v2. The package names for both are set to bar since they are the bar package but just for different versions of the API. However when using Qual the import alias is set to v1 and v2 rather than the actual package name.

    Since this is generated code, I realize this should not matter, however some of the generated code I am producing are stubs that will be further edited by humans, so this is why I am concerned with aesthetics. Thanks!

    opened by bruth 12
  • Handle case of nil literals in spec file

    Handle case of nil literals in spec file

    When handling null enums in spec files such as:

      sortOrder:
          nullable: true
          enum:
              - asc
              - desc
              - null
          readOnly: true
          type: string
    

    Jennifer would translate the null variable into a literal 'literal: ' and consequently throw a panic when the value type wouldn't match, instead of ignoring the null.

    Null enums are perfectly valid according to swagger: https://swagger.io/docs/specification/data-models/enums/

    Also fixed a test that was failing (due to mismatch in input/output, not related to this issue)

    opened by aandrushchak 11
  • Generate

    Generate "import C" for cgo

    I'm generating a source file that uses cgo. This means that following some comment lines, I need a import "C" line.

    // #include <stdio.h>
    // #include <errno.h>
    import "C"
    

    The import must immediately follow the preamble comments. That's a cgo requirement. And I cannot find a way to do that with the current api.

    As this is a somewhat special case, I think that it possibly merits a new api function.

    So perhaps something like this?

      f.Comment("#include <stdio.h>")
      f.Comment("#include <errno.h>")
      f.ImportC()    // <--- this would be an addition to the api.
    

    Or is there already support for this, and I'm overlooking it?

    opened by pekim 10
  • fix: alias can't have a first digit

    fix: alias can't have a first digit

    What this PR do fix an issue in guessAlias: alias can't have a first digit

    Before

    package test
    
    import (
        11a "github.com/xxx/11a" // this is invalid
    )
    

    After

    package test
    
    import (
        a "github.com/xxx/11a" // this is valid
    )
    
    opened by zoumo 9
  • Is `import .

    Is `import . "importpath"` supported ?

    I can see how to do regular imports and anonymous import ( import _ "importpath" ) but I am not sure of how to correctly code the case where package name is "."

    If package name is "." the specs say:

    If an explicit period (.) appears instead of a name, all the package's exported identifiers declared in that package's package block will be declared in the importing source file's file block and must be accessed without a qualifier.

    The behavior should be that if I, for example, import "fmt" with ".", then when I use Qual("fmt", "Println") the "fmt" package should be imported and the Qual() should expand to "Println").

    Is this possible ? I have tried using ImportAlias("fmt", ".") but this ends up generating "..Println".

    opened by raff 8
  • Example for multiline method invocation?

    Example for multiline method invocation?

    Hey Dave!

    I'm trying to render a method invocation like this small example:

    func foo(values ...interface{}) {
    }
    
    func bar() {
    	foo(
    		123, // test
    		456, // test
    		789, // test
    	)
    }
    

    I've found that I can get a straightforward invocation using something like:

    	c := Id("foo").CallFunc(func(g *Group) {
    		g.List(
    			Id("a"),
    			Id("b"),
    			Id("c"),
    		)
    	})
    	fmt.Printf("%#v", c)
    	// Output:
    	// foo(a, b, c)
    

    I can even put everything on its own line using:

    	c := Id("foo").CallFunc(func(g *Group) {
    		g.List(
    			Line().Id("a"),
    			Line().Id("b"),
    			Line().Id("c"),
    			Empty(),
    		)
    	})
    	fmt.Printf("%#v", c)
    	// Output:
    	// foo(
    	//     a,
    	//     b,
    	//     c)
    

    However, I can't seem to figure out how to get my desired formatting behavior. If I use Comment() or Line() at the end of a list item, things get rendered incorrectly, as the comment/line ending appears before the list item is terminated with ,

    	c := Id("foo").CallFunc(func(g *Group) {
    		g.List(
    			Id("a").Comment("test"),
    			Line().Comment("test"),
    			Line().Id("b"),
    			Line().Id("c"),
    			Empty(),
    		)
    	})
    	fmt.Printf("%#v", c)
    	// Output:
    	// %!v(PANIC=Error 1:29: missing ',' before newline in argument list while formatting source:
    	// foo (a // test,
    	//  // test,
    	//  b,
    	//  c,))
    

    I can use an empty list item to sort-of get what I want:

    	c := Id("foo").CallFunc(func(g *Group) {
    		g.List(Id("a"), Empty()).Comment("test").Line()
    		g.List(Id("b")).Comment("test")
    	})
    	fmt.Printf("%#v", c)
    	// Output:
    	// %!v(PANIC=Error 2:1: expected operand, found ',' (and 1 more errors) while formatting source:
    	// foo (a, // test
    	// ,b // test))
    

    Is this sort of formatting possible with jennifer? Of course this only affects aesthetics of the generated code, and I understand it may not be a design goal to allow this sort of thing, but for my use case I think it would be handy. Ideally I want the generated code to be readable/debuggable by a human.

    Cheers,

    Jonathan

    opened by jawnsy 8
  • Multi-line formatting for long parameter lists

    Multi-line formatting for long parameter lists

    Is there currently a way to trigger multi-line formatting for long parameter lists? If not, is this something that would be useful to contribute to the library? I find multi-line parameter lists easier to read and create more useful diffs.


    Here's a contrived example to demonstrate.

    Currently:

    package main
    
    import (
    	"os"
    
    	"github.com/dave/jennifer/jen"
    )
    
    func main() {
    	f := jen.NewFile("test")
    
    	f.Func().Id("PrintNumbers").Call().Block(
    		jen.Qual("fmt", "Println").ParamsFunc(func(g *jen.Group) {
    			for idx := 0; idx < 10; idx += 1 {
    				g.Lit(idx)
    			}
    		}),
    	)
    
    	if err := f.Render(os.Stdout); err != nil {
    		panic(err)
    	}
    }
    

    Outputs:

    package test
    
    import fmt "fmt"
    
    func PrintNumbers() {
    	fmt.Println(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    }
    

    Desired:

    package test
    
    import fmt "fmt"
    
    func PrintNumbers() {
    	fmt.Println(
    		0,
    		1,
    		2,
    		3,
    		4,
    		5,
    		6,
    		7,
    		8,
    		9,
    	)
    }
    
    opened by mickeyreiss 8
  • Insert arbitrary string as type

    Insert arbitrary string as type

    If I've read my types in as strings, for example I have a string which is "[2]uint16", how can I insert that into a statement? Id() isn't working, causing formatting errors to panic

    opened by dt-rush 7
  • Add Header function for adding header comments

    Add Header function for adding header comments

    There are times when you want to add content like // Code generated by...DO NOT EDIT. or MIT License - Copyright... to the top of a generated file for semantic purposes.

    You could use File.PackageComment, but then those comments show up in the Godoc, which isn't always desirable.

    The following code:

    f := NewFile("example")
    f.Header("Code generated by...")
    f.PackageComment("Package example implements...")
    f.Func().Id("init").Params().Block()
    fmt.Printf("%#v", f)
    

    Would generate the output:

    // Code generated by...
    
    // Package example implements...
    // package example
    
    func init() {}
    

    Cheers!

    opened by joshdk 7
  • How to Gennerate var()?

    How to Gennerate var()?

    Hello, thanks for your library. Could it generate go code like following ?

    var (
        request  = organization.NewCreateXXXRequest()
        response *organization.CreateXXXXXResponse
        uin      int64
    )
    
    opened by hellertang 1
  • Non-int untyped numeric literal

    Non-int untyped numeric literal

    Go constants are larger than int ("Represent integer constants with at least 256 bits." https://go.dev/ref/spec#Constants). If you try to use .Lit with even int64, jennifer makes it a typed constant.

    Maybe .Lit should support math/big, and just insert the stringification as literal value -- those non-builtin types can never be typed constants, so there's no other possible interpretation.

    opened by tv42 2
  • Feature: ability to provide custom imports sorter function

    Feature: ability to provide custom imports sorter function

    Give user ability to provide custom imports sorter function.

    https://github.com/dave/jennifer/blob/be82eb6738040b1b5934f0c463273c37d5fd5fd9/jen/jen.go#L132

    Also it would be nice to have ability to put empty lines to achieve imports like this:

    import (
    	"errors"
    	"fmt"
    	"testing"
    
    	"github.com/rs/zerolog"
    	"github.com/rzajac/zrr/zrrtest"
    	"github.com/stretchr/testify/assert"
    	"github.com/stretchr/testify/mock"
    	"github.com/stretchr/testify/require"
    	"github.com/stretchr/testify/suite"
    )
    
    opened by rzajac 0
  • Callback with errors?

    Callback with errors?

    Functions like StructFunc take a Group but don't return an error. I have conditions where iterating and creating the fields must error out. Right now i'm jumping through hoop to get that error. Since these callback functions are generated and I'm sure changing the function signature would cause havoc could another version be generated?

    Something like

    StructFuncErr(f func(*Group) error) (*Statement,error)
    
    opened by delaneyj 0
  • Looking for call chaining including line-feeds

    Looking for call chaining including line-feeds

    I am trying to generate a multi-line call chain but go requires the newline between the Dot and the Id, such as:

    cfg := NewConfigBuilder().
           WithValue("k", "v").
           WithValue("a", "b").
           Build()
    

    Currently Dot does not appear to support such a mechanism. I've added a DotLine method to Statement (below) but was wondering if there was some canonical way to do this already. If not, can a solution be added?

    // DotLine renders a period followed by an newline and an identifier. Use for call chaining.
    func (s *Statement) DotLine(name string) *Statement {
    	d := token{
    		typ:     delimiterToken,
    		content: ".\n",
    	}
    	t := token{
    		typ:     identifierToken,
    		content: name,
    	}
    	*s = append(*s, d, t)
    	return s
    }
    
    opened by mcrawfo2 1
  • Optionally write generated file when generated code is not valid (fmt error)

    Optionally write generated file when generated code is not valid (fmt error)

    Currently if generated code is not valid and fmt returns error, invalid code is written to stderr, so there should be additional steps executed to understand the problem - like passing output to .go file, cutting the actual error and checking IDE/editor highlights. The flow appears a bit redundant.

    Would be nice to have an option to keep invalid generated code in output file as configured, maybe add postfix _err to filename to indicate problem, resulting to smth like component_gen_err.go

    opened by valichek 0
Owner
Dave Brophy
Dave Brophy
General Golang Code Generator

gg gg is a General Golang Code Generator: A Good Game to play with Golang. package main import ( "fmt" . "github.com/Xuanwo/gg" ) func main() {

Xuanwo 77 Nov 2, 2022
Default godoc generator - make your first steps towards better code documentation

godoc-generate Overview godoc-generate is a simple command line tool that generates default godoc comments on all exported types, functions, consts an

Dimitar Petrov 19 Sep 14, 2022
Protocol Buffers to HTTP client code generator/converter

Proto2http Proto2http provides a code generation tool to convert your protocol buffers (.proto) files into invokable HTTP request. Usage proto2http -p

Kodiiing 6 Oct 23, 2022
The High Code Framework (low-code for devs)

hof - the high code framework The hof tool tries to remove redundent development activities by using high level designs, code generation, and diff3 wh

_Hofstadter 335 Nov 27, 2022
🎄 My code for the Advent of Code of year 2021 in Go.

Advent of Code 2021 This repository contains all code that I wrote for the Advent of Code 2021. This year I chose to try and learn Go. Enjoy! Built wi

Nick van Ravenzwaaij 0 Dec 9, 2021
Random fake data and struct generator for Go.

Faker Random fake data and struct generator for Go. More than 100 generator functions Struct generator Unique data generator Builtin types support Eas

Enrico 67 Oct 3, 2022
Random fake data generator written in go

Gofakeit Random data generator written in go Features 160+ Functions!!! Concurrent Global Rand Struct Generator Custom Functions Http Server Command L

Brian Voelker 2.7k Nov 20, 2022
A distributed unique ID generator of using Sonyflake and encoded by Base58

Indigo About A distributed unique ID generator of using Sonyflake and encoded by Base58. ID max length is 11 characters by unsigned int64 max value. A

Osamu TONOMORI 96 Sep 27, 2022
:guardsman: A teeny tiny and somewhat opinionated generator for your next golang project

A Yeoman Golang Generator We are very sorry Gophers, but other names for the generator where taken, so we choose go-lang. But we have gocreate as an a

Axel Springer SE 25 Sep 27, 2022
Unit tests generator for Go programming language

GoUnit GoUnit is a commandline tool that generates tests stubs based on source function or method signature. There are plugins for Vim Emacs Atom Subl

Max Chechel 64 Sep 27, 2022
GObject-introspection based bindings generator

WARNING! This project is no longer maintained. Probably doesn't even compile. GObject-introspection based bindings generator for Go. Work in progress

null 47 Jan 5, 2022
Typo/error resilient, human-readable token generator

toktok A human-friendly token generator Creates tokens which avoid characters that can be easily misinterpreted, like '1' and 'I' or '8' and 'B', as w

Christian Muehlhaeuser 66 Sep 16, 2022
Fast and secure initramfs generator

Booster - fast and secure initramfs generator Initramfs is a specially crafted small root filesystem that mounted at the early stages of Linux OS boot

Anatol Pomozov 297 Nov 12, 2022
accessor methods generator for Go programming language

accessory accessory is an accessor generator for Go programming language. What is accessory? Accessory is a tool that generates accessor methods from

masaushi 8 Nov 15, 2022
RinkWorks fantasy name generator in golang

RinkWorks fantasy name generator in golang

Alexei Shevchenko 12 Nov 10, 2022
Go Param Generator for golang

Go Param Generator Overview Generates struct's for you. No need to write it by hands Adds Getters and Setters Adds Constructor Easy to use Usage gopar

HaxiDenti 0 Dec 13, 2021
Model Generator for Firestore

volcago Automatically generate code used by Cloud Firestore. 日本語ドキュメント Installation Recommend that you drop the binary from the release and use it. Al

null 6 Oct 26, 2022
Epub generator backend for mdBook

gomdbook2epub An EPUB generator backend for mdBook. Getting Started Install bina

神楽坂帕琪 0 Dec 17, 2021
Mongo-backed Static Site Generator for fun

MDBSSG MongoBacked SSG next: set up casbin middleware after: factor out common or large patterns in existing handlers after: refactor UserModel.CheckS

Tyler Darnell 1 Feb 27, 2022