github
https://github.com/dave/jennifer
Jennifer
Jennifer是Go的代码生成器。
package mainimport ("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 mainimport "fmt"func main() {fmt.Println("Hello, world")}
安装
go get -u github.com/dave/jennifer/jen
例子
Jennifer有一套完整的示例——参见godoc的索引。下面是珍妮弗在现实生活中的一些例子:
- genjen (which generates much of jennifer, using data in data.go)
- zerogen
- go-contentful-generator
渲染
对于测试,可以使用%#v动词用fmt包呈现文件或语句。
不建议在生产环境中使用这种方法,因为任何错误都会导致panic。用于生产环境,File.Render 或 File.Save 是被推荐的。c := Id("a").Call(Lit("b"))fmt.Printf("%#v", c)// Output:// a("b")
标识符
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 点呈现一个句点,后面跟着一个标识符。用于字段和选择器。
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 呈现一个限定标识符。
c := Qual("encoding/gob", "NewEncoder").Call()fmt.Printf("%#v", c)// Output:// gob.NewEncoder()
当与文件一起使用时,会自动添加导入。如果路径匹配本地路径,包的名字是省略。如果包名称冲突,则会自动重命名它们。
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.// }
注意,给定任意包路径,不可能可靠地确定包名,因此从路径中猜测合理的名称并添加为别名。所有标准库包的名字是已知的这些不需要别名。如果需要对别名进行更多控制,请阅读 File.ImportName 或 File.ImportAlias。
List
list 呈现一个逗号分隔的列表。用于多个返回函数。
c := List(Id("a"), Err()).Op(":=").Id("b").Call()fmt.Printf("%#v", c)// Output:// a, err := b()
关键字
Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File
简单的关键字、预先声明的标识符和内置函数是自解释的:
| 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 |
内置函数接受参数列表并适当地呈现它们:
c := Id("a").Op("=").Append(Id("a"), Id("b").Op("..."))fmt.Printf("%#v", c)// Output:// a = append(a, b...)
下面解释了If, for, Interface, Struct, Switch, Case, Return和Map的特殊情况。
操作符
Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File
Op呈现提供的操作符/令牌。
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 {// }
花括号
Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File
几种方法呈现花括号,总结如下:
| 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 呈现一个用大括号括起来的语句列表。用于代码块。
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// }
当直接在case或Default后面使用时,将应用一个特殊的case,其中省略了大括号。这允许在switch和select语句中使用。看到的例子。
接口, 结构体
Interface和struct 呈现关键字,后跟一个用大括号括起来的语句列表。
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// }
圆括号
Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File
输出括号的几种方法,总结如下:
| 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 呈现用括号括起来的逗号分隔的列表。用于函数调用。
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呈现一个用圆括号括起来的逗号分隔的列表。用于函数参数和方法接收器。
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 呈现一份声明列表包含在括号。用于定义列表。
c := Const().Defs(Id("a").Op("=").Lit("a"),Id("b").Op("=").Lit("b"),)fmt.Printf("%#v", c)// Output:// const (// a = "a"// b = "b"// )
括弧
Parens 在括号中呈现单个项。用于类型转换或指定计算顺序。
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 呈现一个句点,后跟一个用圆括号括起来的项。用于类型断言。
c := List(Id("b"), Id("ok")).Op(":=").Id("a").Assert(Bool())fmt.Printf("%#v", c)// Output:// b, ok := a.(bool)
控制流程
Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File
If, For
If 和 For 呈现关键字,后跟一个分号分隔的列表。
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和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)// 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 关键字后跟逗号分隔的列表。
c := Return(Id("a"), Id("b"))fmt.Printf("%#v", c)// Output:// return a, b
集合
Map
Map 呈现关键字,后跟一个用方括号括起来的项。用于映射定义。
c := Id("a").Op(":=").Map(String()).String().Values()fmt.Printf("%#v", c)// Output:// a := map[string]string{}
Index
Index 呈现用方括号括起来的冒号分隔的列表。用于数组/片索引和定义。
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 呈现用大括号括起来的逗号分隔的列表。用于切片或复合字面值。
c := Index().String().Values(Lit("a"), Lit("b"))fmt.Printf("%#v", c)// Output:// []string{"a", "b"}
Dict呈现为键/值对。与映射或复合文字的值一起使用。
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执行一个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)// Output:// a := map[string]string{// "a": "b",// "c": "d",// }
注意:项目是关键时候下令呈现,确保可重复的代码。
字面值
Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File
Lit
Lit 显示文字。Lit只支持内置类型(bool、string、int、complex128、float64、float32、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、uintptr和complex64)。传递任何其他类型的信息都会引起恐慌。
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 通过执行所提供的函数生成要呈现的值。
c := Id("a").Op(":=").LitFunc(func() interface{} { return 1 + 1 })fmt.Printf("%#v", c)// Output:// a := 2
对于默认的常量类型(bool, int, float64, string, complex128), Lit将呈现无类型常量。
| Code | Output |
|---|---|
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也会渲染该类型。
| Code | Output |
|---|---|
Lit(float32(1)) |
float32(1) |
Lit(int16(1)) |
int16(1) |
Lit(uint8(0x1)) |
uint8(0x1) |
Lit(complex64(0 + 1i)) |
complex64(0 + 1i) |
内置的别名类型字节和符文需要一个特殊的情况。LitRune和LitByte呈现rune和字节文字。
| Code | Output |
|---|---|
LitRune('x') |
'x' |
LitByte(byte(0x1)) |
byte(0x1) |
注释
Identifiers Keywords Operators Braces Parentheses Control flow Collections Literals Comments Helpers Misc File
Comment
添加了一个注释。如果提供的字符串包含换行符,则注释将以多行格式格式化。
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// */
如果注释字符串以”//“或”/*”开头,自动格式化将被禁用,字符串将被直接呈现。
c := Id("foo").Call(Comment("/* inline */")).Comment("//no-space")fmt.Printf("%#v", c)// Output:// foo( /* inline */ ) //no-space
Commentf
使用格式字符串和参数列表添加注释。
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
所有接受可变项列表的构造都与接受func(*Group)的GroupFunc函数配对。用于嵌入逻辑。
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 := truename := "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将提供的项追加到语句中。
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将语句作为参数调用所提供的函数。用于嵌入逻辑。
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
标记呈现一个结构标记
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"`// }
注意:这些项在呈现时按键排序,以确保代码可重复。
Null
添加一个空项。空项在列表中不呈现任何内容,并且后面没有分隔符。
在列表中,nil也会产生同样的效果。
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添加一个空项。空项不呈现任何内容,只是在列表中后跟一个分隔符。
c := Id("a").Op(":=").Id("b").Index(Lit(1), Empty())fmt.Printf("%#v", c)// Output:// a := b[1:]
Line
插入一个空行。
Clone
在传递*语句时要小心。考虑以下…
a := Id("a")c := Block(a.Call(),a.Call(),)fmt.Printf("%#v", c)// Output:// {// a()()// a()()// }
Id(“a”)返回一个*语句,Call()方法将该语句追加两次。为了避免这种情况,可以使用克隆。Clone复制该语句,因此可以追加更多的令牌,而不会影响原始的令牌。
a := Id("a")c := Block(a.Clone().Call(),a.Clone().Call(),)fmt.Printf("%#v", c)// Output:// {// a()// a()// }
Cgo
cgo“C”伪包是一种特殊的情况,总是没有包别名呈现。可以使用Qual、Anon或提供序言来添加导入。在文件中添加序言。与注释具有相同语义的CgoPreamble。如果提供了序言,则导入将被分隔,并且在其前面有序言。
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
**
表示单个源文件。包导入是根据文件自动管理的。
NewFile
用指定的包名创建一个新文件。
NewFilePath
NewFilePath在指定包路径时创建一个新文件—包名从路径推断。
NewFilePathName
用指定的包路径和名称创建一个新文件。
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
保存呈现文件并保存到提供的文件名。
Render
Render将文件呈现给所提供的写入器。
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添加了一个匿名导入。
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提供路径的包名。如果指定,别名将从导入块中省略。这是可选的。如果未指定,则根据路径使用合理的包名,并将其作为别名添加到导入块中。
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 includedf.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允许多个名称作为映射导入。使用gennames命令自动生成一个go文件,该文件包含选择的包名的映射。
ImportAlias
ImportAlias提供了应该在导入块中使用的包路径的别名。可以使用句点强制点导入。
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 includedf.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在文件的顶部package关键字的上方添加一个注释。
HeaderComment将注释添加到文件的顶部,任何包注释的上方。在头注释下面呈现一个空行,以确保头注释不包含在包文档中。
CanonicalPath将规范导入路径注释添加到包子句中。
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添加了一个cgo序言注释,直接呈现在“C”伪包导入之前。
PackagePrefix
如果你担心包生成别名冲突与局部变量的名字,你可以在这里设置一个前缀。包foo变成了{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()// }
