この記事はGo5 Advent Calendar 2019の9日目の記事です。
RubyのDSLは表現力が高く書きやすいのですが、Ruby以外の言語からDSLを読み出すには何かしらのRubyのランタイムが必要です。
そこで今回は、Go言語でmrubyを使ってRubyのDSLを読み込む方法を紹介します。
mitchellh/go-mrubyのインストール
mitchellh/go-mrubyはgo get
でインストールできないので、リポジトリを取ってきて事前にmakeしておく必要があります。
ghqだとこんな感じ。
$ ghq get mitchellh/go-mruby
$ cd ${GOPATH}/src/github.com/mitchellh/go-mruby
$ make
depを使う場合はdep ensure
した後、vendor配下でmakeすればOK
$ cd vendor/github.com/mitchellh/go-mruby
$ make
go-mrubyでRubyファイルの実行
以下のようにして、GoでRubyのファイルを実行することができます
package main
import (
"errors"
"fmt"
"github.com/mitchellh/go-mruby"
"io/ioutil"
)
func main() {
var username, value string
var isInConfig bool
var rubyErr error
mrb := mruby.NewMrb()
defer mrb.Close()
kernel := mrb.KernelModule()
kernel.DefineMethod("username", func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) {
username = m.GetArgs()[0].String()
return nil, nil
}, mruby.ArgsReq(1))
kernel.DefineMethod("config", func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) {
args := m.GetArgs()
isInConfig = true
mrb.Yield(args[0])
isInConfig = false
return nil, nil
}, mruby.ArgsReq(1))
kernel.DefineMethod("set", func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) {
args := m.GetArgs()
if !isInConfig {
rubyErr = errors.New("set should be called in config")
return nil, nil
}
value = args[0].String()
return nil, nil
}, mruby.ArgsReq(1))
b, err := ioutil.ReadFile("./config")
_, err = mrb.LoadString(string(b))
if err != nil {
panic(err.Error())
}
if rubyErr != nil {
panic(rubyErr.Error())
}
fmt.Println(username)
fmt.Println(value)
}
上の例だと以下のようなRubyのDSLを読み込めます
username 'hoge user'
config do
set :foo, :bar
end
mitchellh/go-mrubyはmrubyの定義・処理をGoで書くことができるので、標準のmrubyに足りないクラス・メソッドはGo側で追加できます。
例えば環境変数を操作するENVクラスは標準では定義されていないので、以下のようにしてGo側で定義をして環境変数を取得することができます。
envModule := mrb.DefineClass("ENV", mrb.ObjectClass())
envModule.DefineClassMethod("[]", func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) {
args := m.GetArgs()
key := args[0].String()
return mrb.StringValue(os.Getenv(key)), nil
}, mruby.ArgsReq(1))