packageを指定してのgoコードの生成に対するgofmt(goimports)について

github.com

おそらくニッチな話になってしまっているけれど。メモ。

はじめに

goawayを使ってコード生成をしたときの典型的なコードの利用方法は以下の様な形になる。

$ python myscript.py --package=github.com/podhmo/myscript --position=.
# 実際にGOPATHに対応した位置に生成したい場合には --positionを外す
# python myscript.py --package=github.com/podhmo/myscript

ここで生成されるファイルのパスはpythonスクリプトに委ねられる。一方で生成されるファイルに対してgofmtやgoimportsを適用したい。 解決策は2つ

  • 指定したpackageのディレクトリに対して雑に gofmt -w *.go とかする
  • python側にgofmtを実行するコードを生成してもらう

今回は後者について考えてみた

典型的なコード

goを生成する典型的なコードは以下の様な感じになる。

import logging
from goaway import get_repository


def run(package_path, position):
    r = get_repository()

    package = r.package(package_path)

    f = package.file("person.go")
    person = f.struct("person")
    person.define_field("name", f.string)

    f = package.file("group.go")
    group = f.struct("group")
    group.define_field("name", f.string)
    group.define_field("members", person.slice)

    d = r.resolve_package_path(position, package)
    r.emitter.emit_package(package, d=d)


def main():
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--package", default=None)
    parser.add_argument("--position", default=None)
    args = parser.parse_args()

    logging.basicConfig(level=logging.INFO)
    run(args.package, args.position)


if __name__ == "__main__":
    main()

これは実行すると以下の様なperson.goとgroup.goを生成する。

$ python main.py --package="github.com/podhmo/goaway/onemit" --position=.
INFO:goaway.emitter:write: ./onemit/person.go
INFO:goaway.emitter:write: ./onemit/group.go

person.go

package onemit

type person struct {
    name string
}

group.go

package onemit

type group struct {
    name    string
    members []person
}

このpythonに委ねられて生成されるgoのファイルにどうやってgofmt(goimports)を適用しようかという話。

on emit hook

とりあえず雑な対応としてonemitというhookを取るようにした。

@@ -2,6 +2,10 @@
 from goaway import get_repository
 
 
+def onemit(f, fname):
+    print("gofmt -w {}".format(fname))
+
+
 def run(package_path, position):
     r = get_repository()
 
@@ -17,7 +21,7 @@
     group.define_field("members", person.slice)
 
     d = r.resolve_package_path(position, package)
-    r.emitter.emit_package(package, d=d)
+    r.emitter.emit_package(package, d=d, onemit=onemit)
 
 
 def main():

onemitのhookでは生成されるパス名が手に入るのでここでおもむろにgofmtを実行するコードをprintしてあげるようにする。 そして以下の様な感じでリダイレクトした結果をbashなどで実行して適用する。

$ python main.py --package="github.com/podhmo/goaway/onemit" --position=. > fmt.sh
INFO:goaway.emitter:write: ./onemit/person.go
INFO:goaway.emitter:write: ./onemit/group.go
$ bash -x fmt.sh
+ gofmt -w ./onemit/person.go
+ gofmt -w ./onemit/group.go

hookのところでsubprocessを作っても良いけれど。この方が楽な気がした。