pythonでgoを書くためのライブラリを作り始めました

github.com

pythonでgoを書くためのユーティリティ的なライブラリを作り始めました。良い名前が見つかったので進捗がありました。

できること

できることは少しずつ増えていく予定です。

structの定義

例えば、以下の様な感じでstructを定義できます。

from goaway import get_repository

r = get_repository()
f = r.package("value").file("value.go")

value = f.struct("Value", comment="value type")
value.define_field("Name", type=f.string)
value.define_field("Value", type=f.int)

print(r.writer.write(f))

以下のようなgoのコードを生成します。

package value

// Value : value type
type Value struct {
    Name string
    Value int
}

ちなみに現状ではwithを使って以下の様にも書けるようにしていますが。これは将来変更される可能性があります。

from goaway import get_repository

r = get_repository()
f = r.package("value").file("value.go")

with f.struct("Value", comment="value type") as field:
    field("Name", type=f.string)
    field("Value", type=f.int)

print(r.writer.write(f))

あと、この記事の例には載せていないですが。埋め込みやinterfaceも使えます

enum的なconstの定義

enum的なconstの定義も出来るようにしました。こういうものを手軽に書いていきたい感じです。 (とは言え、enumレベルのかんたんなコード生成の場合には、テンプレートエンジンベースの出力で十分だったり、go側でのAST変換の方が楽な気がします)

例えば、トランプを模した定義は以下のような感じになります。

from goaway import get_repository


def define(f):
    suit = f.enum("Cardsuit", f.string, comment="記号")
    suit.define_member("spadeds", "♠", comment="スペード")
    suit.define_member("hearts", "♥", comment="ハート")
    suit.define_member("diamonds", "♦", comment="ダイヤ")
    suit.define_member("clubs", "♣", comment="クラブ")

    card = f.struct("Card", comment="カード")
    card.define_field("Suit", type=suit)
    card.define_field("Value", type=f.int8)


def main():
    r = get_repository()
    f = r.package("card").file("card.go")
    define(f)
    print(r.writer.write(f))


if __name__ == "__main__":
    main()

結果は以下の様になります。

package card

import (
    "fmt"
)

// Cardsuit : 記号
type Cardsuit string

const (
    // CardsuitSpadeds : スペード
    CardsuitSpadeds = Cardsuit("♠")
    // CardsuitHearts : ハート
    CardsuitHearts = Cardsuit("♥")
    // CardsuitDiamonds : ダイヤ
    CardsuitDiamonds = Cardsuit("♦")
    // CardsuitClubs : クラブ
    CardsuitClubs = Cardsuit("♣")
)

// String : stringer implementation
func (c Cardsuit) String() string {
    switch c {
    case CardsuitSpadeds:
        return "spadeds"
    case CardsuitHearts:
        return "hearts"
    case CardsuitDiamonds:
        return "diamonds"
    case CardsuitClubs:
        return "clubs"
    default:
        panic(fmt.Sprintf("unexpected Cardsuit %s, in string()", string(c)))
    }

}
// ParseCardsuit : parse
func ParseCardsuit(c string) Cardsuit {
    switch c {
    case "♠":
        return CardsuitSpadeds
    case "♥":
        return CardsuitHearts
    case "♦":
        return CardsuitDiamonds
    case "♣":
        return CardsuitClubs
    default:
        panic(fmt.Sprintf("unexpected Cardsuit %v, in parse()", c))
    }

}

// Card : カード
type Card struct {
    Suit Cardsuit
    Value int8
}

出力先のファイルを分けたい場合

出力先のファイルを分けたい場合もあると思います。その場合は以下の様に書き換えると良いです。

# emit.py
from goaway import get_repository


def define(package):
    f = package.file("suit.go")
    suit = f.enum("Cardsuit", f.string, comment="記号")
    suit.define_member("spadeds", "♠", comment="スペード")
    suit.define_member("hearts", "♥", comment="ハート")
    suit.define_member("diamonds", "♦", comment="ダイヤ")
    suit.define_member("clubs", "♣", comment="クラブ")

    f = package.file("card.go")
    card = f.struct("Card", comment="カード")
    card.define_field("Suit", type=suit)
    card.define_field("Value", type=f.int8)


def main():
    r = get_repository()
    package = r.package("card")
    define(package)
    import logging
    logging.basicConfig(level=logging.INFO)
    r.emitter.emit_package(package, d="./card")


if __name__ == "__main__":
    main()

今度はcard.goとsuit.goに分割されて出力されます。

$ python emit.py
INFO:goaway.emitter:write: ./card/suit.go
INFO:goaway.emitter:write: ./card/card.go
$ tree card
card
├── card.go
└── suit.go

enum2go

ところで、変換先のgoの定義とほとんど同様の定義をpython上に直接記述するような形で書ける様になってもあんまり嬉しくありません。それなら直接go側で記述すれば良いだけなので。なので別種の定義ファイルを受け取ってコード生成するという形にしてみましょう。

enum2goというコマンドを作ってみました(インストールすると使える様になります)。

これは以下のようなyamlファイルを渡すと対応するgoのコードを出力するというようなものです。

# card.yaml
suit.go:
  cardsuit:
    type: string
    description: 記号
    enum:
      spadeds:
        value:description: スペード
      hearts:
        value:description: ハート
      diamonds:
        value:description: ダイヤ
      clubs:
        value:description: クラブ

これで先程のenumの定義と同様のものが生成されます。

$ enum2go --package card --position=. card.yaml
INFO:goaway.emitter:write: ./card/suit.go
# 通常は enum2go --package github.com/foo/bar bar.yaml みたいな形で使う