2019-12-09

GoからRubyのDSLを読み込む方法

この記事はGo5 Advent Calendar 2019の9日目の記事です。


RubyのDSLは表現力が高く書きやすいのですが、Ruby以外の言語からDSLを読み出すには何かしらのRubyのランタイムが必要です。

そこで今回は、Go言語でmrubyを使ってRubyのDSLを読み込む方法を紹介します。

mitchellh/go-mrubyのインストール

mitchellh/go-mrubygo 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))
このエントリーをはてなブックマークに追加