go generate 简单使用

东抄西抄拼装而成的学习笔记,go 版本为 1.12.7,自动生成代码的技术还是非常方便的

Hello world

go 的命令行工具 go generate 可以快速生成一些逻辑的代码,先写一个 hello world 的例子

1
2
3
4
5
6
7
8
9
10
// main.go
//go:generate echo hello world
//go:generate echo $GOARCH
//go:generate echo $GOOS
//go:generate echo $GOFILE
//go:generate echo $GOPACKAGE
package main

func main() {
}

执行 go generate main.go,会有如下的输出

1
2
3
4
5
hello world
amd64
darwin
main.go
main

从第二行开始就是一些 go generate 可以利用的和 go 有关的环境变量

这里记录两个例子:


模拟泛型

此例子取自《go in practice》

这里的程序逻辑就是一个队列,FIFO,但是希望可以由许多中类型使用。由于 go 中没有泛型,所以最安全的做法就是每种类型写一个队列实现,但是它们逻辑相同。

可以考虑使用 go generate + template 来实现,先为该逻辑编写模板,非常简单的队列逻辑

完整的代码如下:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 从第二个参数开始就是需要生成的队列的值类似,比如 ./queue int string MyInt
package main

import (
"fmt"
"html/template"
"os"
"strings"
)

var tpl = `// Code generated, DO NOT EDIT.
package {{.Package}}

type {{.MyType}}Queue struct {
q []{{.MyType}}
}

func New{{.MyType}}Queue() *{{.MyType}}Queue {
return &{{.MyType}}Queue{
q: []{{.MyType}}{},
}
}

func (o *{{.MyType}}Queue) Insert(v {{.MyType}}) {
o.q = append(o.q, v)
}

func (o *{{.MyType}}Queue) Remove() {{.MyType}} {
if len(o.q) == 0 {
panic("empty queue")
}
first := o.q[0]
o.q = o.q[1:]
return first
}
`

func main() {
tt := template.Must(template.New("queue").Parse(tpl))
for i := 1; i < len(os.Args); i++ { // 读入传参
var (
dest = strings.ToLower(os.Args[i]) + "_queue.go"
err error
file *os.File
)
if file, err = os.Create(dest); err != nil {
if !os.IsExist(err) {
fmt.Printf("Could not create %s: %s (skip)\n", dest, err)
continue
}
_ = os.Remove(dest)
}
vals := map[string]string{
"MyType": os.Args[i],
"Package": os.Getenv("GOPACKAGE"),
}
_ = tt.Execute(file, vals)
_ = file.Close()
}
}

最后在生成模板的时候,将 MyType 替换成用户指定的类型,而 Package 则会替换成调用该程序的文件所在的包名,举个例子:

1
2
3
4
── practiceuse
├── main.go
├── myint_queue.go
└── queue

queue 为自动生成代码的程序,main.go 为自动生成代码的程序的调用方,而 myint_queue.go 为自动生成的文件,它的包名和 main.go 相同,main.go 函数的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//go:generate ./queue MyInt
package main

import "fmt"

type MyInt int

func main() {
var one, two, three MyInt = 1, 2, 3
q := NewMyIntQueue()
q.Insert(one)
q.Insert(two)
q.Insert(three)
fmt.Printf("First value: %d\n", q.Remove())
}

执行 go generate main.go 生成的 myint_queue.go 的内容如下,这个逻辑就是根据模板生成

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
// Code generated, DO NOT EDIT.
package main

type MyIntQueue struct {
q []MyInt
}

func NewMyIntQueue() *MyIntQueue {
return &MyIntQueue{
q: []MyInt{},
}
}

func (o *MyIntQueue) Insert(v MyInt) {
o.q = append(o.q, v)
}

func (o *MyIntQueue) Remove() MyInt {
if len(o.q) == 0 {
panic("empty queue")
}
first := o.q[0]
o.q = o.q[1:]
return first
}

快速生成Web服务状态码

该部分摘自 深入理解Go之generate

有一个需求如下,给出一个 go 文件:

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

type ErrCode int

const (
OK ErrCode = iota // OK
INVALID_DATA // 无效参数
UNAUTHORIZED // 无权限
INTERNAL_ERROR // 服务器内部错误
NOT_FOUND // 无该资源
)

希望能根据它为 ErrCode 自动实现接口 Stringer,并且打印出的信息为注释的内容,比如下面的出处为 ”无权限“

1
fmt.Println(errorcode.UNAUTHORIZED)

此处可以使用 Go 官方提供的 Go Tools: stringer ,执行 go get golang.org/x/tools/cmd/stringer 或者 http_proxy=... go get -u golang.org/x/tools/cmd/stringer 使用代理下载,之后将其加入环境变量,就可以直接在命令行中调用 stringer 了。

将上述文件做如下修改

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

type ErrCode int

//go:generate stringer -type ErrCode -linecomment
const (
OK ErrCode = iota // OK
INVALID_DATA // 无效参数
UNAUTHORIZED // 无权限
INTERNAL_ERROR // 服务器内部错误
NOT_FOUND // 无该资源
)

-type 指定需要实现 Stringer 接口的类型名称,而 -linecomment 表示输出的将行注释作为输出的内容,自动生成的文件内容如下:

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
// Code generated by "stringer -type ErrCode -linecomment"; DO NOT EDIT.

package errorcode

import "strconv"

func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[OK-0]
_ = x[INVALID_DATA-1]
_ = x[UNAUTHORIZED-2]
_ = x[INTERNAL_ERROR-3]
_ = x[NOT_FOUND-4]
}

const _ErrCode_name = "OK无效参数无权限服务器内部错误无该资源"

var _ErrCode_index = [...]uint8{0, 2, 14, 23, 44, 56}

func (i ErrCode) String() string {
if i < 0 || i >= ErrCode(len(_ErrCode_index)-1) {
return "ErrCode(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _ErrCode_name[_ErrCode_index[i]:_ErrCode_index[i+1]]
}