問題
メタプログラミング的なことをするうえで、ある名前のpackageの中で定義されているstruct名を抽象的に取得したいことがあったので、その方法をメモ
方法
- go/ast
- go/parser
あたりを使う
準備
% cd % mkdir $GOPATH/src/foo % vi $GOPATH/src/foo/foo.go % vi $GOPATH/src/foo/hoge.go
$GOPATH/src/foo/foo.go
package foo type Foo struct{} type Bar struct{} type Buz struct{} type myPrivateType struct{}
$GOPATH/src/foo/hoge.go
package foo type Hoge struct{} type Fuga struct{} type Piyo struct{}
実装
取得側の実装
~/main.go
package main import "fmt" import "path/filepath" import "os" import "go/build" import "go/token" import "go/parser" import "go/ast" func main() { // get path to the target package gopath := build.Default.GOPATH pkgPath := filepath.Join(gopath, "src", "foo") // parse all files in this dir pkgs, _ := parser.ParseDir(token.NewFileSet(), pkgPath, func(f os.FileInfo) bool { // とりあえず全部オッケーでいいっしょ return true }, 0) // 一応fooパッケージしか無いディレクトリ走査したので、あるっしょ var pkg *ast.Package = pkgs["foo"] for _, f := range pkg.Files { // fooパッケージの全ファイルに含まれる宣言を取り出す for _, decl := range f.Decls { if genDecl, ok := decl.(*ast.GenDecl); ok { if genDecl.Tok != token.TYPE { // This is not `type` continue } spec := genDecl.Specs[0].(*ast.TypeSpec) fmt.Printf("%T\t%+v\tName: %s\n", spec, spec, spec.Name) } } } }
結果
% go run main.go *ast.TypeSpec &{Doc:<nil> Name:Foo Type:0x2103a6380 Comment:<nil>} Name: Foo *ast.TypeSpec &{Doc:<nil> Name:Bar Type:0x2103a63c0 Comment:<nil>} Name: Bar *ast.TypeSpec &{Doc:<nil> Name:Buz Type:0x2103a6420 Comment:<nil>} Name: Buz *ast.TypeSpec &{Doc:<nil> Name:myPrivateType Type:0x2103a6460 Comment:<nil>} Name: myPrivateType *ast.TypeSpec &{Doc:<nil> Name:Hoge Type:0x2103a6500 Comment:<nil>} Name: Hoge *ast.TypeSpec &{Doc:<nil> Name:Fuga Type:0x2103a6540 Comment:<nil>} Name: Fuga *ast.TypeSpec &{Doc:<nil> Name:Piyo Type:0x2103a65a0 Comment:<nil>} Name: Piyo %
雑感
- unexportedでも取れるのか。まあメタ的には取れないと変か
- GenDeclってなんだ?Generated?
- とりあえず取れたし、これであとreflectに渡してほげほげ
DRY