Go言語便利だけど、文法をこねくり回して内部DSLを作るとかには向いていないので、ルビー風にDSLを読み込ませて設定と替えさせていただきます、と言うのが少し難しい。
いっそGo内部でRubyを組み込んで実行したい。mrubyならできるんじゃね? と思ってやってみたらそれっぽいのができた。
Vagrant、Packer、Consulなど、DevOps対応界隈でその名を知らない者はいない mitchellh さんの
をつかう。
多分だけど、ハシモト氏も同じようなこと考えてたんじゃないだろうか。結局
hashicorp/hcl · GitHub を使われてるけれど。
完成したそれっぽいの.go
package main import ( "fmt" "io/ioutil" "os" "github.com/mitchellh/go-mruby" ) func main() { mrb := mruby.NewMrb() defer mrb.Close() file, err := os.Open("./Samplefile") if err != nil { panic(err) } defer file.Close() data, err := ioutil.ReadAll(file) if err != nil { panic(err) } src := string(data[:]) fmt.Printf("src is:\n%v\n", src) inFoobar := false parsedConfig := make(map[string]string) mrb_foobarBlock := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { inFoobar = true args := m.GetArgs() _, err := m.Yield(args[0]) if err != nil { panic(err.Error()) } inFoobar = false return mruby.Nil, nil } mrb_buz := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { if !inFoobar { return mruby.Nil, nil } args := m.GetArgs() parsedConfig["buz"] = args[0].String() return m.TrueValue(), nil } mrb_fizz := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { if !inFoobar { return mruby.Nil, nil } args := m.GetArgs() parsedConfig["fizz"] = args[0].String() return m.TrueValue(), nil } kernel := mrb.KernelModule() kernel.DefineMethod("foobar_block", mrb_foobarBlock, mruby.ArgsBlock()) kernel.DefineMethod("buz", mrb_buz, mruby.ArgsReq(1)) kernel.DefineMethod("fizz", mrb_fizz, mruby.ArgsReq(1)) _, err = mrb.LoadString(src) if err != nil { panic(err.Error()) } fmt.Printf("parsed data is:\n%v\n", parsedConfig) }
Go、初心者なので、コードがひどいのは勘弁。
mruby - GoDoc を眺めて何となく書いたら大丈夫だった。ブロックも使える。たぶん、Cでmruby向けのコードを書いた経験がある人ならもっとスムーズに書けそう。
foobar_block do buz "hogehoge" fizz "fugafuga" end buz "waiwai" fizz "wakuwaku"
みたいなRubyのDSLを同じディレクトリに置いといて実行する。
$ go run sample.go src is: foobar_block do buz "hogehoge" fizz "fugafuga" end buz "waiwai" fizz "wakuwaku" parsed data is: map[buz:hogehoge fizz:fugafuga]
buz/fizz メソッドが、ちゃんとブロックの中で実行されたときのみ有効になっている。で、mruby側のメソッド実行結果がGoでのmapに対応している。
Tips
ビルドが、コツがあって、 go run/build を実行するディレクトリと同じ所に libmruby.a を置いとかないとコンパイルできなかった。逆に、好きなmrbgemを組み込んだ libmruby.a を用意して利用することもできるみたい。
ポータビリティ的にはビルド後のバイナリに libmruby.a も丸っと組み込まれるみたいなのでビルドしてコピーすれば使えそう。逆にクロスコンパイルとか... どうするんだろう。
あと、DSLのところ全然並行性とか考えていないけど、チャンネルとか本気で使えばうまくやれるかもしれない。
ソースですが、 GitHub にも置いといたのでいじってみてください。make一発で動きます →
udzura/dsl-with-go-mruby · GitHub