Jennifer 是一个 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) }
输出:
package main import "fmt" func main() { fmt.Println("Hello, world") }
go get -u github.com/dave/jennifer/jen
如果你遇到困难,有问题,想要代码审查,或只是想聊聊:我很乐意帮忙!随时可以开一个 issue,给我发邮件,或在你的 PR 中提到 @dave。
Jennifer 有一套全面的示例 - 请查看 godoc 索引。以下是一些 Jennifer 在实际项目中使用的例子:
对于测试,可以使用 fmt 包的 %#v 动词渲染 File 或 Statement。
c := Id("a").Call(Lit("b")) fmt.Printf("%#v", c) // 输出: // a("b")
不建议在生产环境中使用这种方式,因为任何错误都会导致 panic。对于生产环境,推荐使用 File.Render 或 File.Save。
标识符 关键字 运算符 括号 圆括号 控制流 集合 字面量 注释 泛型 辅助函数 其他 文件
Id 渲染一个标识符。
c := If(Id("i").Op("==").Id("j")).Block( Return(Id("i")), ) fmt.Printf("%#v", c) // 输出: // if i == j { // return i // }
Dot 渲染一个句点后跟一个标识符。用于字段和选择器。
c := Qual("a.b/c", "Foo").Call().Dot("Bar").Index(Lit(0)).Dot("Baz") fmt.Printf("%#v", c) // 输出: // c.Foo().Bar[0].Baz
Qual 渲染一个限定标识符。
c := Qual("encoding/gob", "NewEncoder").Call() fmt.Printf("%#v", c) // 输出: // gob.NewEncoder()
当与 File 一起使用时,会自动添加导入。如果路径与本地路径匹配,则省略包名。如果包名冲突,会自动重命名。
f := NewFilePath("a.b/c") f.Func().Id("init").Params().Block( Qual("a.b/c", "Foo").Call().Comment("本地包 - 省略包名。"), Qual("d.e/f", "Bar").Call().Comment("自动添加导入。"), Qual("g.h/f", "Baz").Call().Comment("冲突的包名会被重命名。"), ) fmt.Printf("%#v", f) // 输出: // package c // // import ( // f "d.e/f" // f1 "g.h/f" // ) // // func init() { // Foo() // 本地包 - 省略包名。 // f.Bar() // 自动添加导入。 // f1.Baz() // 冲突的包名会被重命名。 // }
注意,无法可靠地根据任意包路径确定包名,所以会根据路径猜测一个合理的名称并添加为别名。所有标准库包的名称都是已知的,因此不需要别名。如果需要更多地控制别名,请参见 File.ImportName 或 File.ImportAlias。
List 渲染一个逗号分隔的列表。用于多返回值函数。
c := List(Id("a"), Err()).Op(":=").Id("b").Call() fmt.Printf("%#v", c) // 输出: // a, err := b()
标识符 关键字 运算符 括号 圆括号 控制流 集合 字面量 注释 泛型 辅助函数 其他 文件
简单的关键字、预声明标识符和内置函数都是不言自明的:
构造 | 名称 |
---|---|
关键字 | Break, Chan, Const, Continue, Default, Defer, Else, Fallthrough, Func, Go, Goto, Range, Select, Type, Var |
函数 | Append, Cap, Clear, Close, Complex, Copy, Delete, Imag, Len, Make, Max, Min, New, Panic, Print, Println, Real, Recover |
类型 | Bool, Byte, Complex64, Complex128, Error, Float32, Float64, Int, Int8, Int16, Int32, Int64, Rune, String, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr |
常量 | True, False, Iota, Nil |
辅助函数 | Err |
内置函数接受一个参数列表并适当地渲染它们:
c := Id("a").Op("=").Append(Id("a"), Id("b").Op("...")) fmt.Printf("%#v", c) // 输出: // a = append(a, b...)
If, For、Interface, Struct、Switch, Case、Return 和 Map 的特殊情况将在下面解释。
标识符 关键字 运算符 括号 圆括号 控制流 集合 字面量 注释 泛型 辅助函数 其他 文件
Op 渲染提供的运算符/标记。
c := Id("a").Op(":=").Id("b").Call() fmt.Printf("%#v", c) // 输出: // a := b()
c := Id("a").Op("=").Op("*").Id("b") fmt.Printf("%#v", c) // 输出: // a = *b
c := Id("a").Call(Id("b").Op("...")) fmt.Printf("%#v", c) // 输出: // a(b...)
c := If(Parens(Id("a").Op("||").Id("b")).Op("&&").Id("c")).Block() fmt.Printf("%#v", c) // 输出: // if (a || b) && c { // }
标识符 关键字 运算符 大括号 小括号 流程控制 集合 字面量 注释 泛型 辅助函数 其他 文件
以下几种方法用于渲染大括号,总结如下:
名称 | 前缀 | 分隔符 | 示例 |
---|---|---|---|
Block | \n | func a() { ... } 或 if a { ... } | |
Interface | interface | \n | interface { ... } |
Struct | struct | \n | struct { ... } |
Values | , | []int{1, 2} 或 A{B: "c"} |
Block 渲染由大括号括起来的语句列表。用于代码块。
c := Func().Id("foo").Params().String().Block( Id("a").Op("=").Id("b"), Id("b").Op("++"), Return(Id("b")), ) fmt.Printf("%#v", c) // 输出: // 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) // 输出: // if a > 10 { // a = a / 2 // }
在 Case 或 Default 后直接使用时有一个特殊情况,此时会省略大括号。这允许在 switch 和 select 语句中使用。参见示例。
Interface 和 Struct 渲染关键字后跟由大括号括起来的语句列表。
c := Var().Id("a").Interface() fmt.Printf("%#v", c) // 输出: // var a interface{}
c := Type().Id("a").Interface( Id("b").Params().String(), ) fmt.Printf("%#v", c) // 输出: // type a interface { // b() string // }
c := Id("c").Op(":=").Make(Chan().Struct()) fmt.Printf("%#v", c) // 输出: // c := make(chan struct{})
c := Type().Id("foo").Struct( List(Id("x"), Id("y")).Int(), Id("u").Float32(), ) fmt.Printf("%#v", c) // 输出: // type foo struct { // x, y int // u float32 // }
标识符 关键字 运算符 大括号 小括号 流程控制 集合 字面量 注释 泛型 辅助函数 其他 文件
几种方法输出小括号,总结如下:
名称 | 前缀 | 分隔符 | 示例 |
---|---|---|---|
Call | , | fmt.Println(b, c) | |
Params | , | func (a *A) Foo(i int) { ... } | |
Defs | \n | const ( ... ) | |
Parens | []byte(s) 或 a / (b + c) | ||
Assert | . | s, ok := i.(string) |
Call 渲染由小括号括起来的逗号分隔列表。用于函数调用。
c := Qual("fmt", "Printf").Call( Lit("%#v: %T\n"), Id("a"), Id("b"), ) fmt.Printf("%#v", c) // 输出: // fmt.Printf("%#v: %T\n", a, b)
Params 渲染由小括号括起来的逗号分隔列表。用于函数参数和方法接收器。
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) // 输出: // func (a A) foo(b, c string) string { // return b + c // }
Defs 渲染由小括号括起来的语句列表。用于定义列表。
c := Const().Defs( Id("a").Op("=").Lit("a"), Id("b").Op("=").Lit("b"), ) fmt.Printf("%#v", c) // 输出: // const ( // a = "a" // b = "b" // )
Parens 在小括号中渲染单个项目。用于类型转换或指定求值顺序。
c := Id("b").Op(":=").Index().Byte().Parens(Id("s")) fmt.Printf("%#v", c) // 输出: // b := []byte(s)
c := Id("a").Op("/").Parens(Id("b").Op("+").Id("c")) fmt.Printf("%#v", c) // 输出: // a / (b + c)
Assert 渲染一个句点后跟由小括号括起来的单个项目。用于类型断言。
c := List(Id("b"), Id("ok")).Op(":=").Id("a").Assert(Bool()) fmt.Printf("%#v", c) // 输出: // b, ok := a.(bool)
标识符 关键字 运算符 大括号 小括号 流程控制 集合 字面量 注释 泛型 辅助函数 其他 文件
If 和 For 渲染关键字后跟分号分隔的列表。
c := If( Err().Op(":=").Id("a").Call(), Err().Op("!=").Nil(), ).Block( Return(Err()), ) fmt.Printf("%#v", c) // 输出: // 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) // 输出: // for i := 0; i < 10; i++ { // fmt.Println(i) // }
Switch、Select、Case和Block用于构建switch或select语句:
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) // 输出: // switch value.Kind() { // case reflect.Float32, reflect.Float64: // return "float" // case reflect.Bool: // return "bool" // case reflect.Uintptr: // fallthrough // default: // return "none" // }
Return渲染关键字,后跟逗号分隔的列表。
c := Return(Id("a"), Id("b")) fmt.Printf("%#v", c) // 输出: // return a, b
标识符 关键字 运算符 大括号 小括号 控制流 集合 字面量 注释 泛型 辅助函数 其他 文件
Map渲染关键字,后跟一个由方括号括起来的单个项目。用于map定义。
c := Id("a").Op(":=").Map(String()).String().Values() fmt.Printf("%#v", c) // 输出: // a := map[string]string{}
Index渲染由方括号括起来的冒号分隔列表。用于数组/切片索引和定义。
c := Var().Id("a").Index().String() fmt.Printf("%#v", c) // 输出: // var a []string
c := Id("a").Op(":=").Id("b").Index(Lit(0), Lit(1)) fmt.Printf("%#v", c) // 输出: // a := b[0:1]
c := Id("a").Op(":=").Id("b").Index(Lit(1), Empty()) fmt.Printf("%#v", c) // 输出: // a := b[1:]
Values渲染由大括号括起来的逗号分隔列表。用于切片或复合字面量。
c := Index().String().Values(Lit("a"), Lit("b")) fmt.Printf("%#v", c) // 输出: // []string{"a", "b"}
Dict渲染为键/值对。与Values一起用于map或复合字面量。
c := Map(String()).String().Values(Dict{ Lit("a"): Lit("b"), Lit("c"): Lit("d"), }) fmt.Printf("%#v", c) // 输出: // 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) // 输出: // &Person{ // Age: 1, // Name: "a", // }
DictFunc执行func(Dict)来生成值。
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) // 输出: // a := map[string]string{ // "a": "b", // "c": "d", // }
注意:渲染时项目按键排序,以确保生成的代码可重复。
标识符 关键字 运算符 大括号 小括号 控制流 集合 字面量 注释 泛型 辅助函数 其他 文件
Lit渲染字面量。Lit只支持内置类型(bool、string、int、complex128、float64、float32、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、uintptr和complex64)。 传递任何其他类型都会引发panic。
c := Id("a").Op(":=").Lit("a") fmt.Printf("%#v", c) // 输出: // a := "a"
c := Id("a").Op(":=").Lit(1.5) fmt.Printf("%#v", c) // 输出: // a := 1.5
LitFunc通过执行提供的函数来生成要渲染的值。
c := Id("a").Op(":=").LitFunc(func() interface{} { return 1 + 1 }) fmt.Printf("%#v", c) // 输出: // a := 2
对于默认常量类型(bool、int、float64、string、complex128),Lit 将渲染无类型常量。
代码 | 输出 |
---|---|
Lit(true) | true |
Lit(1) | 1 |
Lit(1.0) | 1.0 |
Lit("foo") | "foo" |
Lit(0 + 1i) | (0 + 1i) |
对于所有其他内置类型(float32、int8、int16、int32、int64、uint、uint8、 uint16、uint32、uint64、uintptr、complex64),Lit也会渲染类型。
代码 | 输出 |
---|---|
Lit(float32(1)) | float32(1) |
Lit(int16(1)) | int16(1) |
Lit(uint8(0x1)) | uint8(0x1) |
Lit(complex64(0 + 1i)) | complex64(0 + 1i) |
内置别名类型byte和rune需要特殊处理。LitRune和LitByte 渲染rune和byte字面量。
代码 | 输出 |
---|---|
LitRune('x') | 'x' |
LitByte(byte(0x1)) | byte(0x1) |
标识符 关键字 运算符 大括号 小括号 控制流 集合 字面量 注释 泛型 辅助函数 杂项 文件
Comment 添加一条注释。如果提供的字符串包含换行符,注释将以多行样式格式化。
f := NewFile("a") f.Comment("Foo 返回字符串 \"foo\"") f.Func().Id("Foo").Params().String().Block( Return(Lit("foo")).Comment("返回字符串 foo"), ) fmt.Printf("%#v", f) // 输出: // package a // // // Foo 返回字符串 "foo" // func Foo() string { // return "foo" // 返回字符串 foo // }
c := Comment("a\nb") fmt.Printf("%#v", c) // 输出: // /* // a // b // */
如果注释字符串以 "//" 或 "/*" 开头,自动格式化将被禁用,字符串将直接渲染。
c := Id("foo").Call(Comment("/* 内联 */")).Comment("//无空格") fmt.Printf("%#v", c) // 输出: // foo( /* 内联 */ ) //无空格
Commentf 使用格式字符串和参数列表添加注释。
name := "foo" val := "bar" c := Id(name).Op(":=").Lit(val).Commentf("%s 是字符串 \"%s\"", name, val) fmt.Printf("%#v", c) // 输出: // foo := "bar" // foo 是字符串 "bar"
标识符 关键字 运算符 大括号 小括号 控制流 集合 字面量 注释 泛型 辅助函数 杂项 文件
希望随着 Go 1.18 引入泛型,生成代码的需求会减少。然而,为了完整性,我们现在支持泛型,包括 any
和 comparable
预声明标识符,以及 Types
和 Union
列表。要生成近似(~
)标记,请使用 Op("~")
。
Types 渲染一个用方括号括起来的逗号分隔列表。用于类型参数和约束。
Union 渲染一个用管道符分隔的列表。用于联合类型约束。
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) // 输出: // 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) // 输出: // return Keys[int, string](m)
c := Type().Id("PredeclaredSignedInteger").Interface( Union(Int(), Int8(), Int16(), Int32(), Int64()), ) fmt.Printf("%#v", c) // 输出: // type PredeclaredSignedInteger interface { // int | int8 | int16 | int32 | int64 // }
c := Type().Id("AnyString").Interface( Op("~").String(), ) fmt.Printf("%#v", c) // 输出: // type AnyString interface { // ~string // }
标识符 关键字 运算符 大括号 小括号 控制流 集合 字面量 注释 泛型 辅助函数 杂项 文件
所有接受可变参数列表的构造都配对有 GroupFunc 函数,这些函数接受一个 func(*Group)。用于嵌入逻辑。
c := Id("numbers").Op(":=").Index().Int().ValuesFunc(func(g *Group) { for i := 0; i <= 5; i++ { g.Lit(i) } }) fmt.Printf("%#v", c) // 输出: // 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) // 输出: // func a() { // a = 1 // a++ // }
Add 将提供的项添加到语句中。
ptr := Op("*") c := Id("a").Op("=").Add(ptr).Id("b") fmt.Printf("%#v", c) // 输出: // a = *b
a := Id("a") i := Int() c := Var().Add(a, i) fmt.Printf("%#v", c) // 输出: // var a int
Do 使用语句作为参数调用提供的函数。用于嵌入逻辑。
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)) // 输出: // a := map[string]string{} // b := []string{}
标识符 关键字 运算符 大括号 小括号 控制流 集合 字面量 注释 泛型 辅助函数 杂项 文件
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) // 输出: // type foo struct { // A string `json:"a"` // B int `bar:"baz" json:"b"` // }
注意:渲染时项目按键排序,以确保代码可重复。
Null 添加一个空项。空项不渲染任何内容,在列表中也不会跟随分隔符。
在列表中,nil 将产生相同的效果。
c := Func().Id("foo").Params( nil, Id("s").String(), Null(), Id("i").Int(), ).Block() fmt.Printf("%#v", c) // 输出: // func foo(s string, i int) {}
Empty 添加一个空项。空项不会渲染任何内容,但在列表中会跟随一个分隔符。
c := Id("a").Op(":=").Id("b").Index(Lit(1), Empty()) fmt.Printf("%#v", c) // 输出: // a := b[1:]
Line 插入一个空行。
使用 *Statement 时要小心。考虑以下情况...
a := Id("a") c := Block( a.Call(), a.Call(), ) fmt.Printf("%#v", c) // 输出: // { // a()() // a()() // }
Id("a") 返回一个 *Statement,Call() 方法会对其进行两次追加。为避免这种情况,使用 Clone。Clone 会复制 Statement,这样可以追加更多的标记而不影响原始语句。
a := Id("a") c := Block( a.Clone().Call(), a.Clone().Call(), ) fmt.Printf("%#v", c) // 输出: // { // a() // a() // }
cgo 的 "C" 伪包是一个特殊情况,它总是渲染时不带包别名。可以使用 Qual
、Anon
或提供前言来添加导入。前言可以通过 File.CgoPreamble
添加,其语义与 Comment 相同。如果提供了前言,导入会被分开,并在前言之后。
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) // 输出: // 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)) // }