Goのコード生成ツールを作った
September 20, 2020
knifeについて
knifeというツールを細々と作っています。
ざっくり言うとgo list
の型情報版です。
-f
オプションでテンプレートを指定してあげると表示する情報を変えることができます。
次の例では、net/http
パッケージ内でcontext.Context
型のフィールドを持つパッケージスコープの型を表示しています。
$ knife -f '{{- range .Types -}}
{{- $t := . -}}
{{- with struct . -}}
{{- range .Fields -}}
{{- if identical . (typeof "context.Context") -}}
{{- $t.Name}} - {{pos .}}{{br}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}' "net/http"
Request - /usr/local/go/src/net/http/request.go:319:2
http2ServeConnOpts - /usr/local/go/src/net/http/h2_bundle.go:3878:2
http2serverConn - /usr/local/go/src/net/http/h2_bundle.go:4065:2
http2stream - /usr/local/go/src/net/http/h2_bundle.go:4146:2
initALPNRequest - /usr/local/go/src/net/http/server.go:3393:2
timeoutHandler - /usr/local/go/src/net/http/server.go:3241:2
wantConn - /usr/local/go/src/net/http/transport.go:1162:2
hagane
haganeはknifeのサブプロジェクトで、knifeの機能を使ってコード生成を行うためのコマンドラインツールです。
例えば、次のようなコードがあった場合を考えます。
package sample
type DB interface {
Get(id string) int
Set(id string, v int)
}
type db struct {}
func (db) Get(id string) int {
return 0
}
func (db) Set(id string, v int) {}
このコードから次のようなDB
インタフェースを実装したモック用のコードを生成したいと思います。
package sample
type MockDB struct {
GetFunc func(id string) int
SetFunc func(id string, v int)
}
func (m *MockDB) Get(id string) int {
return m.GetFunc(id)
}
func (m *MockDB) Set(id string, v int) {
m.SetFunc(id, v)
}
haganeを使うと次のように、テンプレートと追加データを渡すことで、型情報を元にコード生成を行うことができます。
テンプレートには-data
で追加データが渡せ、テンプレート内で使えるdata
関数から取得できます。
$ hagane -template template.go.tmpl -o sample_mock.go -data {"type":"DB"} sample.go
テンプレートは次のように定義されています。
{{with index .Types (data "type")}}{{if interface .}}
package {{(pkg).Name}}
type Mock{{data "type"}} struct {
{{- range $n, $f := methods .}}
{{$n}}Func {{$f.Signature}}
{{- end}}
}
{{range $n, $f := methods .}}
func (m *Mock{{data "type"}}) {{$n}}({{range $f.Signature.Params}}
{{- .Name}} {{.Type}},
{{- end}}) ({{range $f.Signature.Results}}
{{- .Name}} {{.Type}},
{{- end}}) {
{{if $f.Signature.Results}}return {{end}}m.{{$n}}Func({{range $f.Signature.Params}}
{{- .Name}},
{{- end}})
}
{{end}}
{{end}}
{{end}}
テンプレートはGo標準のtext/template
パッケージを用いており、テンプレートに展開されるデータとして型情報として*knife.Package
型の値が渡されます。
knife.Package
型はtypes.Package
型をラップした型で、テンプレートに展開しやすいようにフィールドとして各種情報を保持しています。
型情報とテンプレート内で使える関数を駆使することで、自前で静的解析を行わなくてもコード生成が簡単に行えます。
これから
haganeはさっき(2020年9月25日)に作り始めたツールなので、私の都合でガンガン変更される可能性があります。 もし、こういう用途で使えそう!みたいなアイデアがあったら@tenntennまで教えて下さい。