Giter Site home page Giter Site logo

jennifer's Introduction

docs stability-stable

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 Generics 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 Generics 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, Clear, Close, Complex, Copy, Delete, Imag, Len, Make, Max, Min, 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 Generics 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 Generics 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 Generics 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 Generics 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 Generics 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 Generics 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 Generics 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"

Generics

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

It is hoped that with the introduction of generics with Go 1.18, the need to generate code will be reduced. However, for the sake of completeness, we now support generics including the any and comparable predeclared identifiers, and the Types and Union lists. To emit the approximation (~) token, use Op("~").

Types

Types renders a comma separated list enclosed by square brackets. Use for type parameters and constraints.

Union

Union renders a pipe separated list. Use for union type constraints.

Examples

c := Func().Id("Keys").Types(
	Id("K").Comparable(),
	Id("V").Any(),
).Params(
	Id("m").Map(Id("K")).Id("V"),
).Index().Id("K").Block()
fmt.Printf("%#v", c)
// Output:
// func Keys[K comparable, V any](m map[K]V) []K {}
c := Return(Id("Keys").Types(Int(), String()).Call(Id("m")))
fmt.Printf("%#v", c)
// Output:
// return Keys[int, string](m)
c := Type().Id("PredeclaredSignedInteger").Interface(
	Union(Int(), Int8(), Int16(), Int32(), Int64()),
)
fmt.Printf("%#v", c)
// Output:
// type PredeclaredSignedInteger interface {
//	int | int8 | int16 | int32 | int64
// }
c := Type().Id("AnyString").Interface(
	Op("~").String(),
)
fmt.Printf("%#v", c)
// Output:
// type AnyString interface {
//	~string
// }

Helpers

Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Generics 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 Generics 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 Generics Helpers Misc File

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

NoFormat

NoFormat can be set to true to disable formatting of the generated source. This may be useful when performance is critical, and readable code is not required.

jennifer's People

Contributors

bmoylan avatar dave avatar dsxack avatar jackwakefield avatar joshdk avatar k3forx avatar mazrean avatar nyiyui avatar pekim avatar roopakv avatar testwill avatar vetcher avatar zellyn avatar zoumo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jennifer's Issues

Custom package names

Hi there,

Thank you for this package, it has really reduced the time I thought I would need to get into codegen.

Wanted to ask a question about whether custom package names are possible.
In my case, I am generating repository functions in a structure similar to the following:
mypkg/
|--> create.go
|--> read.go
|--> update.go
|--> destroy.go

In my code I am using: f := NewFile("create") which when saving, sets package to package create. I would however instead like to be able to set package mypkg, so that I can generate a whole package (in my example, mypkg).

Thank you for your time!

How generate a field of time.Time type in a struct?

Thanks for this awesome project.

I'm troubled to generate time.Time field of struct.

Hopeful go code is:

type MyStruct struct {
  Name string
  Birthday time.Time
}

How to write jennifer code?

Type().Id("MyStruct").Struct(
  Id("Name").String(),
  Id("Birthday").???    //   <-- How to write?
)

Generated source is always formatted

Formatting a generated file is nice. However it's causing me a performance problem during development.

I'm generating over 4,000,000 lines, spread over about 200 files, which takes 35 to 40 seconds. Profiling shows that most of this time is in jen. And a large part of the time in jen is spent in formatting the source.

I would like an option to not format the generated source.

Lit can't print byte literal

It's impossible to tell a byte from a uint8, so I will add a LitByte function to print byte literals of the form byte(0x1)

If multiply conditions

Hello. Thanks for your library. Im new in golang.
Could you help me.

I want generate multiply conditions for "if" in for cycle. But have problems.
Now im trying

var ifstringCode []Code
condition1 := Id("a").Op(params).Id(value)
condition1 := Id("a").Op(params).Id(value1)
ifstringCode =append(ifstringCode,If(Parens(condition1)))

and want add another conditions.

Some like this.

if (a>1)&&(b==1)
if (a>1)&&(b==1)&&(c==1)

Is `Params(...)` the correct way to render named returns?

Is Params(...) the correct way to render named returns? I tried searching the existing samples and docs (as well as other repos) but couldn't find any prior art

i.e.,

package main

import (
	"fmt"
	"log"

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

const want = `func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}`

func main() {
	c := Func().Id("split").Params(
		Id("sum").Int(),
	).Params(Id("x"), Id("y").Int()).Block(
		Id("x").Op("=").Id("sum").Op("*").Lit(4).Op("/").Lit(9),
		Id("y").Op("=").Id("sum").Op("-").Id("x"),
		Return(),
	)
	got := fmt.Sprintf("%#v", c)
	if got != want {
		log.Fatalf("got %q, want %q", got, want)
	}
}

Feature proposal: Qual from reflect.Type

Hi there!

Thanks for this fantastic library, I've been playing with it for a few days now, and it's been great. I'd just like to make a small suggestion (and am happy to submit a pull request) which would make things easier for my particular use case.

I'm using reflection to generate ORM-ish code at compile time. For example, for a given struct, I want to use reflection to determine type information, and then pass it to jennifer. Currently, this means writing code like:

t := reflect.TypeOf(api.Account{}),
account := jen.Qual(t.PkgPath(), t.Name())

It would be nice if there was a convenience function for this:

func QualType(t reflect.Type) *Statement {
	return newStatement().Qual(t.PkgPath(), t.Name())
}

This is a small thing and has the downside of cluttering the jennifer API a bit. However, it shouldn't have any new dependencies, since jennifer already needs fmt which depends on reflect. I'd be happy to implement this and submit a pull request, but would need some guidance.

Thanks for your consideration!

How to create an arbitrarily deep map?

I need to create map[string]map[int]map[string]string, and it looks nesting dicts doesn't work very well.

Here is my code:

// +build ignore

package main

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"

	"github.com/weaveworks/eksctl/pkg/ami"
	"github.com/weaveworks/eksctl/pkg/eks/api"

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

func main() {
	fmt.Println("generating list of AMIs for the static resolvers")

	f := NewFile("ami")

	f.Comment("Generated code")

	d := Dict{}

	client := newMultiRegionClient()

	for family := range ami.ImageSearchPatterns {
		familyImages := Dict{}
		log.Printf("looking up %s images", family)
		for class := range ami.ImageSearchPatterns[family] {
			classImages := Dict{}
			for _, region := range api.SupportedRegions {
				p := ami.ImageSearchPatterns[family][class]
				log.Printf("looking up images matching %q in %q", p, region)
				image, err := ami.FindImage(client[region], p)
				if err != nil {
					log.Fatal(err)
				}
				classImages[Lit(region)] = Lit(image)
			}
			familyImages[Id(ami.ImageClasses[class])] = classImages
		}
		d[Id(family)] = familyImages
	}

	// var expectedStaticImages = map[string]map[int]map[string]string{
	// 	ami.ImageFamilyAmazonLinux2: {
	// 		ami.ImageClassGeneral: {
	// 			"eu-west-1": "ami-0c7a4976cb6fafd3a",
	// 			"us-east-1": "ami-0440e4f6b9713faf6",
	// 			"us-west-2": "ami-0a54c984b9f908c81",
	// 		},
	// 		ami.ImageClassGPU: {
	// 			"eu-west-1": "ami-0706dc8a5eed2eed9",
	// 			"us-east-1": "ami-058bfb8c236caae89",
	// 			"us-west-2": "ami-0731694d53ef9604b",
	// 		},
	// 	},
	// }

	f.Var().Id("StaticImages").Op("=").
		Map(String()).Map(Int()).Map(String()).String().Values(d)

	if err := f.Save("static_resolver_ami.go"); err != nil {
		log.Fatal(err.Error())
	}

}

func newSession(region string) *session.Session {
	config := aws.NewConfig()
	config = config.WithRegion(region)
	config = config.WithCredentialsChainVerboseErrors(true)

	// Create the options for the session
	opts := session.Options{
		Config:                  *config,
		SharedConfigState:       session.SharedConfigEnable,
		AssumeRoleTokenProvider: stscreds.StdinTokenProvider,
	}

	return session.Must(session.NewSessionWithOptions(opts))
}

func newMultiRegionClient() map[string]*ec2.EC2 {
	clients := make(map[string]*ec2.EC2)
	for _, region := range api.SupportedRegions {
		clients[region] = ec2.New(newSession(region))
	}
	return clients
}

And here is what I see when I run it:

% go run ./static_resolver_ami_generate.go
generating list of AMIs for the static resolvers
2018/10/18 10:40:24 looking up AmazonLinux2 images
2018/10/18 10:40:24 looking up images matching "amazon-eks-node-*" in "us-west-2"
2018/10/18 10:40:26 looking up images matching "amazon-eks-node-*" in "us-east-1"
2018/10/18 10:40:29 looking up images matching "amazon-eks-node-*" in "eu-west-1"
2018/10/18 10:40:30 looking up images matching "amazon-eks-gpu-node-*" in "us-west-2"
2018/10/18 10:40:31 looking up images matching "amazon-eks-gpu-node-*" in "us-east-1"
2018/10/18 10:40:33 looking up images matching "amazon-eks-gpu-node-*" in "eu-west-1"
2018/10/18 10:40:34 Error 6:14: missing ',' in composite literal (and 2 more errors) while formatting source:
package ami


// Generated code
var StaticImages = map[string] map[int] map[string] string {AmazonLinux2:
ImageClassGPU:
"eu-west-1":"ami-0706dc8a5eed2eed9",
"us-east-1":"ami-058bfb8c236caae89",
"us-west-2":"ami-0731694d53ef9604b",
,
ImageClassGeneral:
"eu-west-1":"ami-0c7a4976cb6fafd3a",
"us-east-1":"ami-0440e4f6b9713faf6",
"us-west-2":"ami-0a54c984b9f908c81",
,
}
exit status 1
%

It's mostly missing { and } to open and close nested maps, anything I can do to fix this, or it's not possible with Dict currently?

Besides, I think it'd be very handy if I could just use built-in map type without having to use Dict.

No way to comment an anonymous import

Various linting tools will throw an error if an anonymous import isn't documented with a comment.

Something like:

import (
    "fmt"
    "log"

    // Import the MySQL driver for database/sql.
    _ "github.com/go-sql-driver/mysql"
)

Is this possible using Jennifer?

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!

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?

Jen doesn't panic on probably an invalid ast

So I've got something like this

// +build ignore

package main

import (
	j "github.com/dave/jennifer/jen"
)

func main() {
	f := j.NewFile("decimal")

	f.Func().Params(
		j.Id("dec").Id("*Decimal"),
	).Id("AddUInt32").Params(
		j.Id("number").Id("uint32"),
	).Return(j.Id("*Decimal")).Block(
		j.Return(j.Id("dec")),
	)

	f.Save("convenience_generated.go")
}

Jen doesn't save the respective file and doesn't panic, which is quite confusing.

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

Block following Case renders double newlines

func ExampleCaseBug() {
	c := Switch(Id("a")).Block(
		Case(Lit(1)).Block(
			Var().Id("i").Int(),
			Var().Id("j").Int(),
		),
	)
	fmt.Printf("%#v", c)
	// Output:
	// switch a {
	// case 1:
	// 	var i int
	// 	var j int
	// }
}

Result:

=== RUN   ExampleCaseBug
--- FAIL: ExampleCaseBug (0.00s)
got:
switch a {
case 1:
	var i int

	var j int
}
want:
switch a {
case 1:
	var i int
	var j int
}
FAIL

Comments are printed twice

package main

import (
	"github.com/dave/jennifer/jen"
	"bytes"
	"fmt"
)

func main() {
	file := jen.NewFile("main")
	file.Add(file.Commentf("%s", "protobuf=true"))
	requestType := jen.Type().Id("TestWithComment").Struct(
		jen.Id("Foo").Id("string"),
	)
	file.Add(requestType)
	var buf bytes.Buffer
	err := file.Render(&buf)
	if err != nil {
		panic(err)
	}

	fmt.Print(buf.String())


}

Output:

package main

// protobuf=true
// protobuf=true
type TestWithComment struct {
	Foo string
}

Dict and Values unnecessary?

Could Dict and Values be folded into Lit?

Lit([]Code{Lit("a"), Lit("b")})
// Output:
// {"a", "b"}
Lit(map[Code]Code{Lit("a"): Lit("b"), Lit("c"): Lit("d")})
// Output:
// {
//   "a": "b",
//   "c": "d",
// }

Qualify names when generating code for external _test package

Code generated by the following (semi-pseudo) example does not compile:

code := jen.NewFilePathName("path/to/pkg", "pkg_test")

code.ImportName("path/to/pkg", "pkg")

code.Qual("path/to/pkg", "MyStruct")

The problem is that the generated code in pkg_test will not properly be qualified with the package import (path/to/pkg), because the package path is actually the same. In my use case adding a _test suffix to the file package path solved the issue, but it doesn't feel right. (What if I actually have a pkg_test package?)

Not sure what the best solution would be here. Maybe handle _test suffixes? (The problem is that technically you can have other package suffixes in a single package, if you manually pass those files to go tool compile, but I think a special case for _test suffixes would suffice for the time being.)

(Sorry for the dummy example, but I think it demonstrates the issue)

Feature request: Method chaining with PackageComment/HeaderComment

Hi Dave,

I didn't see any discussion in #23 about this, though I understand these methods are different from most of jen's methods, since they are on File rather than Statement.

Currently, we can do something like:

	f.Comment("Hello").Line().
		Comment("World").Line()

and this would be rendered as:

// Hello
// World

However, this isn't possible with PackageComment or HeaderComment, since they don't return anything. If they at least returned *File, then they could be used to chain subsequent calls, e.g.

	f.PackageComment("Hello").
		PackageComment("World")

Additionally, neither of these support the fmt.Sprintf variant, ร  la stmt.Commentf. Do these seem like worthwhile enhancements to make? Or is there some reason why File cannot currently support a pattern like this?

Thanks for everything, you're awesome! ๐ŸŽ‰

Best,

Jonathan

Proposal: Generage Jennifer from Value

Hi Dave,

What would you feel about adding a utility that generates code using reflect.Value? The goal is to create something similar to the %#v fmt directive.

My immediate goal is building a tool that turns YAML files into pre-populated go structs, but I could see it being used other places where you want to create a reproducible instance of something. For example, a fuzzer could create a single file with a minimal working example of a struct that fails or a rules engine could compile together a bunch of rules generated from user-defined files.

Cheers!
- Joseph

Sel is cumbersome

Sel isn't ideal for chaining selectors together. A chain of selectors isn't really a list, so I'd prefer to keep the code flow consistent. Previously I've tried using Op("."), but this is laborious.

Could Id be modified similarly to new special case in Block so that it automatically inserts periods where necessary? This would be more complex.

[Feature request] `Idf` func

I find myself writing jen.Id(fmt.Sprintf("%sThing", model)) relatively often.

It would be convenient to add a helper method for this pattern, Idf.

Raw function to insert raw string into the output

Is it possible to toinsert a pile of code like

f.Insert(`
type Repository struct {
	ArchiveURL       string      `json:"archive_url"`
	AssigneesURL     string      `json:"assignees_url"`
	BlobsURL         string      `json:"blobs_url"`
	BranchesURL      string      `json:"branches_url"`
	CloneURL         string      `json:"clone_url"`
	CollaboratorsURL string      `json:"collaborators_url"`
	CommentsURL      string      `json:"comments_url"`
	CommitsURL       string      `json:"commits_url"`
	CompareURL       string      `json:"compare_url"`
	ContentsURL      string      `json:"contents_url"`
	ContributorsURL  string      `json:"contributors_url"`
	CreatedAt        string      `json:"created_at"`
	DefaultBranch    string      `json:"default_branch"`
	Description      string      `json:"description"`
	DownloadsURL     string      `json:"downloads_url"`
	EventsURL        string      `json:"events_url"`
	Fork             bool        `json:"fork"`
	Forks            float64     `json:"forks"`
	ForksCount       float64     `json:"forks_count"`
	ForksURL         string      `json:"forks_url"`
	FullName         string      `json:"full_name"`
	GitCommitsURL    string      `json:"git_commits_url"`
	GitRefsURL       string      `json:"git_refs_url"`
	GitTagsURL       string      `json:"git_tags_url"`
	GitURL           string      `json:"git_url"`
	HasDownloads     bool        `json:"has_downloads"`
	HasIssues        bool        `json:"has_issues"`
	HasWiki          bool        `json:"has_wiki"`
	Homepage         interface{} `json:"homepage"`
	HooksURL         string      `json:"hooks_url"`
	HtmlURL          string      `json:"html_url"`
	ID               float64     `json:"id"`
	IssueCommentURL  string      `json:"issue_comment_url"`
	IssueEventsURL   string      `json:"issue_events_url"`
	IssuesURL        string      `json:"issues_url"`
	KeysURL          string      `json:"keys_url"`
	LabelsURL        string      `json:"labels_url"`
	Language         string      `json:"language"`
	LanguagesURL     string      `json:"languages_url"`
	MasterBranch     string      `json:"master_branch"`
	MergesURL        string      `json:"merges_url"`
	MilestonesURL    string      `json:"milestones_url"`
	MirrorURL        interface{} `json:"mirror_url"`
	Name             string      `json:"name"`
	NetworkCount     float64     `json:"network_count"`
	NotificationsURL string      `json:"notifications_url"`
	OpenIssues       float64     `json:"open_issues"`
	OpenIssuesCount  float64     `json:"open_issues_count"`
	Owner            struct {
		AvatarURL         string  `json:"avatar_url"`
		EventsURL         string  `json:"events_url"`
		FollowersURL      string  `json:"followers_url"`
		FollowingURL      string  `json:"following_url"`
		GistsURL          string  `json:"gists_url"`
		GravatarID        string  `json:"gravatar_id"`
		HtmlURL           string  `json:"html_url"`
		ID                float64 `json:"id"`
		Login             string  `json:"login"`
		OrganizationsURL  string  `json:"organizations_url"`
		ReceivedEventsURL string  `json:"received_events_url"`
		ReposURL          string  `json:"repos_url"`
		SiteAdmin         bool    `json:"site_admin"`
		StarredURL        string  `json:"starred_url"`
		SubscriptionsURL  string  `json:"subscriptions_url"`
		Type              string  `json:"type"`
		URL               string  `json:"url"`
	} `json:"owner"`
	Private         bool    `json:"private"`
	PullsURL        string  `json:"pulls_url"`
	PushedAt        string  `json:"pushed_at"`
	Size            float64 `json:"size"`
	SshURL          string  `json:"ssh_url"`
	StargazersURL   string  `json:"stargazers_url"`
	StatusesURL     string  `json:"statuses_url"`
	SubscribersURL  string  `json:"subscribers_url"`
	SubscriptionURL string  `json:"subscription_url"`
	SvnURL          string  `json:"svn_url"`
	TagsURL         string  `json:"tags_url"`
	TeamsURL        string  `json:"teams_url"`
	TreesURL        string  `json:"trees_url"`
	UpdatedAt       string  `json:"updated_at"`
	URL             string  `json:"url"`
	Watchers        float64 `json:"watchers"`
	WatchersCount   float64 `json:"watchers_count"`
}
`)

such code is static and will not change.

Feature proposal: Render Statement with passed File

Sometimes I need to render only body of File without imports to append exists file. So I need to call GoString() method on Statement and pass File (with presetted import aliases) as param to use valid import aliases in the code.

concatenation "string" + Id("futureVariables")

`type APIparameters struct {
PathParam []pathParameters
Host string
Security string
}
var api APIparameters
api.Host = "api host"
group.List(Id("request"), Id("err")).Op(":=").Id("http").Dot("NewRequest").Call(Id("method"),LitFunc(func() interface{} { return Lit(api.Host).Id("endpoint") }), Id("body"))
There is an error when concatinating LitFunc(func() interface{} { return Lit(api.Host).Id("endpoint") })
panic: unsupported type for literal: *jen.Statement

goroutine 1 [running]:
github.com/dave/jennifer/jen.token.render(0x1184c8e, 0x7, 0x11827a0, 0xc0000db0e0, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0xc0000db0c0, 0xc0000d75b8, 0x1041959)
/Users/nartay/go/src/github.com/dave/jennifer/jen/tokens.go:64 +0xed4
github.com/dave/jennifer/jen.(*Statement).render(0xc0000db0c0, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0x0, 0x0, 0x0)
/Users/nartay/go/src/github.com/dave/jennifer/jen/statement.go:64 +0xcb
github.com/dave/jennifer/jen.(*Group).renderItems(0xc0000966c0, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0x1, 0x0, 0x0)
/Users/nartay/go/src/github.com/dave/jennifer/jen/group.go:110 +0x18a
github.com/dave/jennifer/jen.(*Group).render(0xc0000966c0, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0xc0000dafc0, 0x0, 0x0)
/Users/nartay/go/src/github.com/dave/jennifer/jen/group.go:53 +0x84
github.com/dave/jennifer/jen.(*Statement).render(0xc0000dafc0, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0x0, 0x0, 0x0)
/Users/nartay/go/src/github.com/dave/jennifer/jen/statement.go:64 +0xcb
github.com/dave/jennifer/jen.(*Group).renderItems(0xc0000964e0, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0x1, 0x0, 0x0)
/Users/nartay/go/src/github.com/dave/jennifer/jen/group.go:110 +0x18a
github.com/dave/jennifer/jen.(*Group).render(0xc0000964e0, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0xc0000da500, 0x0, 0x0)
/Users/nartay/go/src/github.com/dave/jennifer/jen/group.go:53 +0x84
github.com/dave/jennifer/jen.(*Statement).render(0xc0000da500, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0x0, 0x0, 0x0)
/Users/nartay/go/src/github.com/dave/jennifer/jen/statement.go:64 +0xcb
github.com/dave/jennifer/jen.(*Group).renderItems(0xc0000960c0, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0x1167a80, 0xc0000d0c01, 0xc0000c0750)
/Users/nartay/go/src/github.com/dave/jennifer/jen/group.go:110 +0x18a
github.com/dave/jennifer/jen.(*Group).render(0xc0000960c0, 0xc0000c80a0, 0x11b4160, 0xc0000c0750, 0x0, 0xc0000d09d0, 0xc0000c05d0)
/Users/nartay/go/src/github.com/dave/jennifer/jen/group.go:53 +0x84
github.com/dave/jennifer/jen.(*File).Render(0xc0000c80a0, 0x11b4160, 0xc0000c0720, 0x4, 0x20)
/Users/nartay/go/src/github.com/dave/jennifer/jen/jen.go:36 +0x82
github.com/dave/jennifer/jen.(*File).Save(0xc0000c80a0, 0x1184c9c, 0x7, 0x2, 0x3)
/Users/nartay/go/src/github.com/dave/jennifer/jen/jen.go:24 +0x5a
main.main()
/Users/nartay/Desktop/job/code_generate/readingJSON/readingJSON.go:130 +0x1e8f
exit status 2

`

Line comments are printed mid-line

Thanks to @magicsong for reporting this in #49:

Values(DictFunc) cannot add comment of each field like following:

opt := &LoginOption {
Login : "MUST_EDIT_IT", // [TODO] This field is REQUIRED
Password : "MUST_EDIT_IT", // [TODO] This field is REQUIRED
}

Line comments are inserted in the output where the Comment function is executed. Perhaps there should be a buffer of line comments that only gets written when we get to a \n?

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.

Feature proposal: Generate jennifer code from existing go code

I wanted to take an existing file like the one in #50 and convert it so that jennifer generates it. Right now I am doing this by hand. I would like a function that takes in a go file and outputs the code that generates that go file using jennifer.

This would discourage workarounds like Op and allow quick generation of static files.

Similar to #44
Solves #50

Syntax is cumbersome?

Should all functions take interface{} instead of Code and convert string to Id?

So instead of:

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

You would have:

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

I've intentionally avoided using interface{} to overload the method signatures, but it would definitely make some code simpler...

Using a defined type.

I can see how to define a type.

c := jen.Type().Id("mytype").Int32()
fmt.Printf("%#v\n", c)

That will produce this.

type mytype int32

But I can't figure out how to later define a var of that type.

var a mytype

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

Object literal incorrectly qualified when using Lit()

BLUF - Object literals will be qualified with a package name, even when rendered into a fully-qualified, identical package.

Let me illustrate with an example. Say I have a package called github.com/joshdk/example, which contains these two Go files.

When I run the TestRender function, I expect to see the following code printed out:

package example

var author = Person{Name: "Linus"}

But instead, what actually gets generated is this:

package example

var author = example.Person{Name: "Linus"}

Which would eventually result in an error if I wrote this contents to a file inside of the github.com/joshdk/example package. It would be helpful if a literal (via Lit()) compared its type against the declared package (and simplified if they matched) at render time.

Fantastic project by the way. Cheers!

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.

out = fmt.Sprintf("%#v.0", t.content)

Support for import comments

There is a construct called "import comment", which is a special comment on the line of the package declaration:

package unicode // import "golang.org/x/text/encoding/unicode"

They are used by the go command for import path checking, and also used by go mod init to figure out the module path.

It could be good to support generating this for completeness sake.

Feature Rquest: a better alias guess function

If the import alias is invalid or has been registered already, jennifer make it unique by appending a number. This led to an ugly generated code with imports block, for example

package test

import (
	v1 "k8s.io/core/v1"
    v11 "k8s.io/apps/v1"
    v12 "k8s.io/batch/v1"
    v13 "k8s.io/xxx/v1"
)

A better output is

package test

import (
	v1 "k8s.io/core/v1"
    appsv1 "k8s.io/apps/v1"
    batchv1 "k8s.io/batch/v1"
    xxxv1 "k8s.io/xxx/v1"
)

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

Calling an anonymous function causes an error

Hi,

Thanks for creating jennifer.

I`m trying to create and call an anonymous function, but it does not seem to work.

Here is an example program:

package main

import (
	"fmt"

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

func main() {
	// This works
	fmt.Println(func(x float64) float64 { return 0.5 * x }(0.5))

	// Building the anonymous function and passing a parameter works
	expression := jen.Func().Params(
		jen.Id("x").Id("float64"),
	).Id("float64").Block(
		jen.Return().Lit(0.5).Op("*").Id("x"),
	).Call(jen.Lit(0.5))

	// This fails
	fmt.Println(expression.GoString())
}

Here is the same program on the Go playground: https://play.golang.org/p/In4w6w8wEAs

The error message is:

panic: Error 1:36: expected '(', found '{' (and 2 more errors) while formatting source:
func (x float64) float64 {
return  0.5 * x
} (0.5)

goroutine 1 [running]:
github.com/dave/jennifer/jen.(*Statement).GoString(0x40a0f0, 0x43e360, 0x4, 0x4)
	/tmp/gopath017659477/pkg/mod/github.com/dave/[email protected]/jen/statement.go:81 +0x2c0
main.main()
	/tmp/sandbox296010062/prog.go:21 +0xd20

CaseBlock is cumbersome

The CaseBlock group is cumbersome. More natural would be to use Block, and vary the open/close tokens when it directly follows the Case group or the Default keyword.

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,
	)
}

NewFile from bytes or ast

Hi @dave, what you think about adding constructor for *jen.File, like NewFileFromSource() or NewFileFromAst(*ast.File), to create file from existing files or asts?

My use cases:

  1. I want to append missing functions to implement existing interface.
  2. I trying to make pluggable generator, in which each plugin can modify the previous.

[Help wanted]How to generate correct 'go:generate'?

AS go:generate was a special type of comments.
Codes generated by both

f.HeaderComment("go:generate go version")

and

f.Line().Comment("go:generate go version")

was

// go:generate go version

which could not run by go generate ./...

What's the correct way to generate code like below?

//go:generate go version

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.