简介

jennifer支持所有的 Go 语法和特性,可以用它来生成任何 Go 语言代码。

快速使用

先安装:

1
go get github.com/dave/jennifer

今天我们换个思路来介绍jennifer这个库。既然,它是用来生成 Go 语言代码的。我们就先写出想要生成的程序,然后看看如何使用jennifer来生成。先从第一个程序Hello World开始:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
  fmt.Println("Hello World")
}

我们如何用jennifer来生成上面的程序代码呢:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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)
}

Go 程序的基本组织单位是文件,每个文件属于一个包,可执行程序必须有一个main包,main包中又必须有一个main函数。所以,我们使用jennifer生成代码的大体步骤也是差不多的:

  • 先使用NewFile定义一个包文件对象,参数即为包名;
  • 然后调用这个包文件对象的Func()定义函数;
  • 函数可以使用Params()定义参数,通过Block()定义函数体;
  • 函数体是由一条一条语句组成的。语句的内容比较多样,后面会详细介绍。

上面代码中,我们首先定义一个main包文件对象。然后调用其Func()定义一个函数,Id()为函数命名为main,Params()传入空参数,Block()中传入函数体。函数体中,我们使用Qual(“fmt”, “Println”)来表示引用fmt.Println这个函数。使用Call()表示函数调用,然后Lit()将字符串"Hello World"字面量作为参数传给fmt.Println()。

Qual函数这里需要特意讲一下,我们不需要显示导入包,Qual函数的第一个参数就是包路径。如果是标准库,直接就是包名,例如这里的fmt。如果是第三方库,需要加上包路径限定,例如github.com/spf13/viper。这也是Qual名字的由来。jennifer在生成程序时会汇总所有用到的包,统一导入。

运行程序,我们最终输出了一开始想要生成的程序代码!

实际上,大多数编程语言的语法都有相通之处。Go 语言的语法比较简单:

  • 基本的概念:变量、函数、结构等,它们都有一个名字,又称为标识符。直接写在程序中的数字、字符串等被称为字面量,如上面的"Hello World";
  • 流程控制:条件(if)、循环(for);
  • 函数和方法;
  • 并发相关:goroutine 和 channel。

有几点注意:

  • 我们在导入jennifer包的时候在包路径前面加了一个.,使用这种方式导入,在后面使用该库的相关函数和变量时不需要添加jen.限定。一般是不建议这样做的。但是jennifer的函数比较多,如果不这样的话,每次都需要加上jen.比较繁琐。
  • jennifer的大部分方法都是可以链式调用的,每个方法处理完成之后都会返回当前对象,便于代码的编写。

下面我们从上面几个部分依次来介绍。

变量定义与运算

其实从语法层面来讲,变量就是标识符 + 类型。上面我们直接使用了字面量,这次我们想先定义一个变量存储欢迎信息:

1
2
3
4
func main() {
  var greeting = "Hello World"
  fmt.Println(greeting)
}

变量定义的方式有好几种,jennifer可以比较直观的表达我们的意图。例如,上面的var greeting = “Hello World”,我们基本上可以逐字翻译:

  • var是变量定义,jennifer中有对应的函数Var();
  • greeting实际上是一个标识符,我们可以用Id()来定义;
  • =是赋值操作符,我们使用Op("=")来表示;
  • “Hello World"是一个字符串字面量,最开始的例子中已经介绍过了,可以使用Lit()定义。

所以,这条语句翻译过来就是:

1
Var().Id("greeting").Op("=").Lit("Hello World")

同样的,我们可以试试另一种变量定义方式greeting := "Hello World"

1
Id("greeting").Op(":=").Lit("Hello World")

是不是很简单。整个程序如下(省略包名和导入,下同):

1
2
3
4
5
6
7
8
9
func main() {
  f := NewFile("main")
  f.Func().Id("main").Params().Block(
    // Var().Id("greeting").Op("=").Lit("Hello World"),
    Id("greeting").Op(":=").Lit("Hello World"),
    Qual("fmt", "Println").Call(Id("greeting")),
  )
  fmt.Printf("%#v\n", f)
}

接下来,我们用变量做一些运算。假设,我们要生成下面这段程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func main() {
  var a = 10
  var b = 2
  fmt.Printf("%d + %d = %d\n", a, b, a+b)
  fmt.Printf("%d + %d = %d\n", a, b, a-b)
  fmt.Printf("%d + %d = %d\n", a, b, a*b)
  fmt.Printf("%d + %d = %d\n", a, b, a/b)
}

变量定义这里不再赘述了,方法和函数调用实际上快速开始部分也介绍过。首先用Qual(“fmt”, “Printf”)表示取包fmt中的Printf函数这一概念。然后使用Call()表示函数调用,参数第一个是字符串字面量,用Lit()表示。第二个和第三个都是一个标识符,用Id(“a”)和Id(“b”)即可表示。最后一个参数是两个标识符之间的运算,运算用Op()表示,所以最终就是生成程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func main() {
  f := NewFile("main")
  f.Func().Id("main").Params().Block(
    Var().Id("a").Op("=").Lit(10),
    Var().Id("b").Op("=").Lit(2),
    Qual("fmt", "Printf").Call(Lit("%d + %d = %d\n"), Id("a"), Id("b"), Id("a").Op("+").Id("b")),
    Qual("fmt", "Printf").Call(Lit("%d + %d = %d\n"), Id("a"), Id("b"), Id("a").Op("-").Id("b")),
    Qual("fmt", "Printf").Call(Lit("%d + %d = %d\n"), Id("a"), Id("b"), Id("a").Op("*").Id("b")),
    Qual("fmt", "Printf").Call(Lit("%d + %d = %d\n"), Id("a"), Id("b"), Id("a").Op("/").Id("b")),
  )
  fmt.Printf("%#v\n", f)
}

逻辑运算是类似的。

条件和循环

假设我们要生成下面的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func main() {
  score := 70

  if score >= 90 {
    fmt.Println("优秀")
  } else if score >= 80 {
    fmt.Println("良好")
  } else if score >= 60 {
    fmt.Println("及格")
  } else {
    fmt.Println("不及格")
  }
}

依然采取我们的逐字翻译大法:

  • if关键字用If()来表示,条件语句是基本的标识符和常量操作。条件语句块与函数体一样,都使用Block();
  • else关键字用Else()来表示,else if就是Else()后面再调用If()即可。

完整的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func main() {
  f := NewFile("main")

  f.Func().Id("main").Params().Block(
    Id("score").Op(":=").Lit(70),

    If(Id("score").Op(">=").Lit(90)).Block(
      Qual("fmt", "Println").Call(Lit("优秀")),
    ).Else().If(Id("score").Op(">=").Lit(80)).Block(
      Qual("fmt", "Println").Call(Lit("良好")),
    ).Else().If(Id("score").Op(">=").Lit(60)).Block(
      Qual("fmt", "Println").Call(Lit("及格")),
    ).Else().Block(
      Qual("fmt", "Println").Call(Lit("不及格")),
    ),
  )

  fmt.Printf("%#v\n", f)
}

对于for循环也是类似的,如果我们要生成下面的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func main() {
  var sum int
  for i := 1; i <= 100; i++ {
    sum += i
  }

  fmt.Println(sum)
}

我们需要编写下面的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func main() {
  f := NewFile("main")

  f.Func().Id("main").Params().Block(
    Var().Id("sum").Int(),

    For(
      Id("i").Op(":=").Lit(0),
      Id("i").Op("<=").Lit(100),
      Id("i").Op("++"),
    ).Block(
      Id("sum").Op("+=").Id("i"),
    ),

    Qual("fmt", "Println").Call(Id("sum")),
  )

  fmt.Printf("%#v\n", f)
}

For()里面的 3 条语句对应实际for语句中的 3 个部分。

函数

函数是每个编程语言的必要元素。函数的核心要素是名字(标识符)、参数列表、返回值,最关键的就是函数体。我们之前编写main函数的时候大概介绍过。假设我们要编写一个计算两个数的和的函数:

1
2
3
func add(a, b int) int {
  return a + b
}
  • 函数我们使用Func()表示,参数用Params()表示,返回值使用Int()表示;
  • 函数体用Block();
  • return语句使用Return()函数表示,其他都是一样的。

看下面的完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func main() {
  f := NewFile("main")

  f.Func().Id("add").Params(Id("a"), Id("b").Int()).Int().Block(
    Return(Id("a").Op("+").Id("b")),
  )

  f.Func().Id("main").Params().Block(
    Id("a").Op(":=").Lit(1),
    Id("b").Op(":=").Lit(2),
    Qual("fmt", "Println").Call(Id("add").Call(Id("a"), Id("b"))),
  )

  fmt.Printf("%#v\n", f)
}

一定要注意,即使没有参数,Params()也一定要调用,否则生成的代码有语法错误。

结构和方法

下面我们看看结构和方法如何生成,假设我们想生成下面的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "fmt"

type User struct {
  Name string
  Age  int
}

func (u *User) Greeting() {
  fmt.Printf("Hello %s", u.Name)
}
func main() {
  u := User{Name: "dj", Age: 18}
  u.Greeting()
}

需要用到的新函数:

  • 结构体是一个类型,所以需要用到类型定义函数Type(),然后结构体的字段在Struct()内通过Id()+类型定义;
  • 方法其实也是一个函数,只不过多了一个接收器,我们还是使用Func()定义,接收者也可以用定义参数的Params()函数来指定,其它与函数没什么不同;
  • 然后结构体初始化,在Values()中给字段赋值;
  • 方法先用Dot(“方法名”)找到方法,然后Call()调用。

最后的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
  f := NewFile("main")

  f.Type().Id("User").Struct(
    Id("Name").String(),
    Id("Age").Int(),
  )

  f.Func().Params(Id("u").Id("*User")).Id("Greeting").Params().Block(
    Qual("fmt", "Printf").Call(Lit("Hello %s"), Id("u").Dot("Name")),
  )

  f.Func().Id("main").Params().Block(
    Id("u").Op(":=").Id("User").Values(
      Id("Name").Op(":").Lit("dj"),
      Id("Age").Op(":").Lit(18),
    ),
    Id("u").Dot("Greeting").Call(),
  )

  fmt.Printf("%#v\n", f)
}

并发支持

还是一样,假设我想生成下面的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "fmt"

func generate() chan int {
  out := make(chan int)
  go func() {
    for i := 1; i <= 100; i++ {
      out <- i
    }
    close(out)
  }()

  return out
}

func double(in <-chan int) chan int {
  out := make(chan int)

  go func() {
    for i := range in {
      out <- i * 2
    }
    close(out)
  }()

  return out
}

func main() {
  for i := range double(generate()) {
    fmt.Println(i)
  }
}

需要用到的新函数:

  • 首先是make一个chan,用Make(Chan().Int());
  • 然后启动一个 goroutine,用Go();
  • 关闭chan,用Close();
  • for … range对应使用Range()。

拼在一起就是这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func main() {
  f := NewFile("main")
  f.Func().Id("generate").Params().Chan().Int().Block(
    Id("out").Op(":=").Make(Chan().Int()),
    Go().Func().Params().Block(
      For(
        Id("i").Op(":=").Lit(1),
        Id("i").Op("<=").Lit(100),
        Id("i").Op("++"),
      ).Block(Id("out").Op("<-").Id("i")),
      Close(Id("out")),
    ).Call(),
    Return().Id("out"),
  )

  f.Func().Id("double").Params(Id("in").Op("<-").Chan().Int()).Chan().Int().Block(
    Id("out").Op(":=").Make(Chan().Int()),
    Go().Func().Params().Block(
      For().Id("i").Op(":=").Range().Id("in").Block(Id("out").Op("<-").Id("i").Op("*").Lit(2)),
      Close(Id("out")),
    ).Call(),
    Return().Id("out"),
  )

  f.Func().Id("main").Params().Block(
    For(
      Id("i").Op(":=").Range().Id("double").Call(Id("generate").Call()),
    ).Block(
      Qual("fmt", "Println").Call(Id("i")),
    ),
  )

  fmt.Printf("%#v\n", f)
}

上面的程序中,我们生成代码后直接输出了。在实际应用中,肯定是需要保存到文件中,然后编译运行的。jennifer也提供了保存到文件的方法File.Save(),直接传入文件名即可,这个File就是我们上面调用NewFile()生成的对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func main() {
  f := NewFile("main")
  f.Func().Id("main").Params().Block(
    Qual("fmt", "Println").Call(Lit("Hello, world")),
  )

  _, err := os.Stat("./generated")
  if os.IsNotExist(err) {
    os.Mkdir("./generated", 0666)
  }

  err = f.Save("./generated/main.go")
  if err != nil {
    log.Fatal(err)
  }
}

这种方式必须要保证generated目录存在。所以,我们使用os库在目录不存在时创建一个。

常见问题

jennifer在生成代码后会调用go fmt对代码进行格式化,如果代码存在语法错误,这时候会输出错误信息。我遇到最多的问题就是最后生成的程序代码以})结尾,这明显不符合语法。查了半天发现Func()后忘记加Params()。即使是空参数,这个Params()也不能省略!

API库

简介

Jennifer是Go的代码生成器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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:

1
2
3
4
5
6
7
package main

import "fmt"

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

安装

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

举例

詹妮弗(Jennifer)提供了一整套详尽的示例-有关索引,请参见godoc。以下是詹妮弗在现实世界中使用的一些示例:

呈现

为了进行测试,可以使用%#v动词通过​​fmt包呈现文件或语句。

1
2
3
4
c := Id("a").Call(Lit("b"))
fmt.Printf("%#v", c)
// Output:
// a("b")

不建议将其用于生产中,因为任何错误都会引起panic。对于生产用途,首选File.Render或File.Save。

Identifiers

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

Id

Id 呈现为一个标识符

1
2
3
4
5
6
7
8
c := If(Id("i").Op("==").Id("j")).Block(
	Return(Id("i")),
)
fmt.Printf("%#v", c)
// Output:
// if i == j {
// 	return i
// }
Dot

Dot 表示一个句点,后跟一个标识符。用于字段和选择器。

1
2
3
4
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.

1
2
3
4
c := Qual("encoding/gob", "NewEncoder").Call()
fmt.Printf("%#v", c)
// Output:
// gob.NewEncoder()

与文件一起使用时,导入会自动添加。如果路径与本地路径匹配,则将省略软件包名称。如果程序包名称冲突,它们将被自动重命名。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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.ImportNameFile.ImportAlias.

List

List呈现逗号分隔的列表。用于返回函数的多个值。

1
2
3
4
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

简单的关键字,预声明的标识符和内置函数很容易说明:

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

内置函数获取参数列表并适当地渲染它们:

1
2
3
4
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.

1
2
3
4
c := Id("a").Op(":=").Id("b").Call()
fmt.Printf("%#v", c)
// Output:
// a := b()
1
2
3
4
c := Id("a").Op("=").Op("*").Id("b")
fmt.Printf("%#v", c)
// Output:
// a = *b
1
2
3
4
c := Id("a").Call(Id("b").Op("..."))
fmt.Printf("%#v", c)
// Output:
// a(b...)
1
2
3
4
5
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

有几种方法可以显示花括号,总结如下:

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 呈现用大括号括起来的语句列表。用于代码块。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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
// }
1
2
3
4
5
6
7
8
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”之后直接使用时,有特殊情况适用,其中省略了花括号。这允许在switch和select语句中使用. See example.

Interface, Struct

Interface和Struct呈现keyword,后跟用大括号括起来的语句列表。

1
2
3
4
c := Var().Id("a").Interface()
fmt.Printf("%#v", c)
// Output:
// var a interface{}
1
2
3
4
5
6
7
8
c := Type().Id("a").Interface(
	Id("b").Params().String(),
)
fmt.Printf("%#v", c)
// Output:
// type a interface {
// 	b() string
// }
1
2
3
4
c := Id("c").Op(":=").Make(Chan().Struct())
fmt.Printf("%#v", c)
// Output:
// c := make(chan struct{})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

几种方法输出括号,总结如下:

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将呈现一个用圆括号括起来的逗号分隔列表。用于函数调用。

1
2
3
4
5
6
7
8
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呈现一个用括号括起来的逗号分隔列表。用于功能参数和方法接收器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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呈现括在括号中的语句列表。用于定义列表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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在圆括号中呈现单个项目。用于类型转换或指定评估顺序。

1
2
3
4
c := Id("b").Op(":=").Index().Byte().Parens(Id("s"))
fmt.Printf("%#v", c)
// Output:
// b := []byte(s)
1
2
3
4
c := Id("a").Op("/").Parens(Id("b").Op("+").Id("c"))
fmt.Printf("%#v", c)
// Output:
// a / (b + c)
Assert

Assert呈现一个句点,后跟一个用括号括起来的单个项目。用于类型断言。

1
2
3
4
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和For呈现关键字,后跟一个用分号分隔的列表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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
// }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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语句:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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呈现关键字,后跟一个逗号分隔的列表。

1
2
3
4
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呈现关键字,后跟方括号括起来的单个项目。用于map定义。

1
2
3
4
c := Id("a").Op(":=").Map(String()).String().Values()
fmt.Printf("%#v", c)
// Output:
// a := map[string]string{}
Index

Index呈现由冒号分隔的列表,并用方括号括起来。用于数组/切片索引和定义。

1
2
3
4
c := Var().Id("a").Index().String()
fmt.Printf("%#v", c)
// Output:
// var a []string
1
2
3
4
c := Id("a").Op(":=").Id("b").Index(Lit(0), Lit(1))
fmt.Printf("%#v", c)
// Output:
// a := b[0:1]
1
2
3
4
c := Id("a").Op(":=").Id("b").Index(Lit(1), Empty())
fmt.Printf("%#v", c)
// Output:
// a := b[1:]
Values

Values 将显示用大括号括起来的逗号分隔列表。用于切片或复合字面值。

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

Dict呈现为键/值对。与Values一起用于Map或复合字面值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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",
// }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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)生成值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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",
// }

注意:渲染时,这些项将按键排序,以确保代码可重复。

Literals

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)。通过任何其他类型都会感到恐慌。

1
2
3
4
c := Id("a").Op(":=").Lit("a")
fmt.Printf("%#v", c)
// Output:
// a := "a"
1
2
3
4
c := Id("a").Op(":=").Lit(1.5)
fmt.Printf("%#v", c)
// Output:
// a := 1.5

LitFunc通过执行提供的函数来生成要渲染的值。

1
2
3
4
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 添加评论。如果提供的字符串包含换行符,则注释将以多行样式设置格式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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
// }
1
2
3
4
5
6
7
c := Comment("a\nb")
fmt.Printf("%#v", c)
// Output:
// /*
// a
// b
// */

如果注释字符串以“ //”或“ / *”开头,则会禁用自动格式设置,并直接呈现该字符串。

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

Commentf使用格式字符串和参数列表添加注释。

1
2
3
4
5
6
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函数配对。用于嵌入逻辑。

1
2
3
4
5
6
7
8
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}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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将提供的项目追加到语句中。

1
2
3
4
5
ptr := Op("*")
c := Id("a").Op("=").Add(ptr).Id("b")
fmt.Printf("%#v", c)
// Output:
// a = *b
1
2
3
4
5
6
a := Id("a")
i := Int()
c := Var().Add(a, i)
fmt.Printf("%#v", c)
// Output:
// var a int
Do

Do以语句作为参数调用提供的函数。用于嵌入逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 呈现结构标签

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

NNull添加一个空项目。空项目不显示任何内容,并且列表中不包含分隔符。 在列表中,nil将产生相同的效果。

1
2
3
4
5
6
7
8
9
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添加一个空项目。空项目什么也没有呈现,但列表中紧跟着一个分隔符。

1
2
3
4
c := Id("a").Op(":=").Id("b").Index(Lit(1), Empty())
fmt.Printf("%#v", c)
// Output:
// a := b[1:]
Line

Line 插入空白行。

Clone

当传递*Statement要小心,参考以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
a := Id("a")
c := Block(
	a.Call(),
	a.Call(),
)
fmt.Printf("%#v", c)
// Output:
// {
// 	a()()
// 	a()()
// }

Id(“ a”)返回一个* Statement,Call()方法将其附加两次。为避免这种情况,请使用克隆。 Clone复制了Statement,因此可以附加更多标记而不影响原始标记。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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自动管理。

NewFile

NewFile使用指定的包名称创建一个新文件。

NewFilePath

NewFilePath在指定包路径的同时创建一个新文件-包名称是从路径中推断出来的。

NewFilePathName

NewFilePathName使用指定的包路径和名称创建一个新文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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将渲染文件并将其保存到提供的文件名中。

Render

Render将文件渲染到提供的writer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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添加了匿名导入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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提供路径的程序包名称。如果指定,别名将从导入块中省略。这是可选的。如果未指定,则根据路径使用合理的软件包名称,并将其作为别名添加到导入块中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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允许将多个名称作为map导入。使用gennames命令自动生成一个go文件,其中包含选择的软件包名称的映射。

ImportAlias

ImportAlias为应该在导入块中使用的包路径提供别名。句点可用于强制点导入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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在package关键字上方的文件顶部添加注释。

HeaderComment在文件顶部的任何程序包注释上方添加一个注释。标题注释下方呈现空白行,以确保标题注释不包含在包文档中。

CanonicalPath在package子句中添加了规范的导入路径注释。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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添加了直接在“C”伪软件包导入之前呈现的cgo前导注释。

PackagePrefix

如果您担心生成的程序包别名与本地变量名称冲突,则可以在此处设置前缀。软件包foo变成{prefix} _foo。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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()
// }

参考

https://studygolang.com/articles/27200