mrubyで簡単にCアプリの設定ファイルが作れるmruby-config作ってみた

C言語でサーバアプリやクライアントアプリを作っていると、じょじょに規模が大きくなり、アプリの設定を外出ししたいなぁ、なんて思いはじめるのはよくある事だと思います。

でも、なんとなくその設定ファイルのParser書いたり参照のインターフェイスを書くのも面倒だし引数で渡すようにするかぁ、なんて思いはじめたりもします。

でも結局引数が大量に増えだして、そのusage表示もカオスになって面倒になり(更新が止まり)、結局もう一度、「設定ファイル作るかー」なんてことになるのはよくある事だと思います。

そこで、そういう人のために,mrubyを使って簡単に設定ファイル(Rubyで書く)を外出しできるmruby-configというmrbgemを作ってみました。こういうことが簡単にできて、かつ、軽量なのがmrubyの良いところですね。

これを使うと、Rubyで書いた設定ファイルを、簡単にCソースの中で呼び出して、その値を参照することができます。

使い方

とりあえず、サーバアプリを実装中と見立てたC側のサンプルとRuby側(設定ファイル)のサンプルを見るのが一番分かりやすいでしょう。

C側は以下のように書きます。

[program lang=’c’ escaped=’true’]

#include <mruby.h>
#include <mruby/variable.h>
#include <mruby/hash.h>

static void create_config(mrb_state *mrb, char *key, char *val)
{
  mrb_value conf;
  conf = mrb_hash_new(mrb);
  mrb_hash_set(mrb, conf, mrb_str_new_cstr(mrb, key), mrb_str_new_cstr(mrb, val));
  mrb_funcall(mrb, mrb_top_self(mrb), "new_config", 1, conf);
}

int main() {

  FILE *fp;
  if ((fp = fopen("./mruby.conf", "r")) == NULL)
    return 1;

  mrb_state* mrb = mrb_open();

  // C側で先にconfigを作っておいてその値はRuby側で参照して動的にconfigを生成できるようにする
  create_config(mrb, "Version", "2");

  // Ruby設定ファイル実行
  mrb_load_file(mrb, fp);

  // 設定値の参照
  int port;
  char *droot;
  mrb_value user;

  /*
  mrb_get_config_value(mrb, key, format, ...)

  format specifiers (like mrb_get_args):

   o: Object [mrb_value]
   S: String [mrb_value]          //Not Implemented
   A: Array [mrb_value]           //Not Implemented
   H: Hash [mrb_value]            //Not Implemented
   s: String [char*,int]          //Not Implemented
   z: String [char*]
   a: Array [mrb_value*,mrb_int]  //Not Implemented
   f: Float [mrb_float]           //Not Implemented
   i: Integer [mrb_int]
  */

  mrb_get_config_value(mrb, "Listen",       "i", &port);
  mrb_get_config_value(mrb, "DocumentRoot", "z", &droot);
  mrb_get_config_value(mrb, "User",         "o", &user);

  // 設定の値を使って色々と処理を実装
  printf("=== global configuration ===¥n");
  printf("port=%d droot=%s¥n", port, droot);
  mrb_p(mrb, user);
  printf("¥n");

  mrb_close(mrb);

  return 0;
}

[/program]

このように、設定ファイルであるmruby.confを読み込んで(ここは引数で渡せるようにしておいた方が良いでしょう)そこから、設定ファイルの値をget_config_valueで参照しています。

このサンプルでは、C側で先に最低限の設定をcreate_configで作っておき、その設定に対して、Ruby設定ファイル側から設定値を上書きするような実装をしています。これによって、C側の値を参考に動的にRuby側で設定ファイルを作る事ができます。

では、その設定がかかれたRuby設定ファイルはどうなっているでしょうか。以下がその設定ファイルになります。

[program lang=’ruby’ escaped=’true’]

add_config(
    "Listen"            => 80,
    "DocumentRoot"      => "/var/www/html",
    "ExtendedStatus"    => nil,
    "User"              => "apache",
    "Group"             => "apache",
)

if get_config("Version").to_i < 2
  add_config "ExtendedStatus" => "Off"
else
  add_config "ExtendedStatus" => "On"
end

[/program]

このように、基本はハッシュで渡します。このサンプルでは、C側で先に「Version」に関する設定をnew_configメソッドで作成しておき、その後にこのRuby設定ファイルを呼び出す(mruby的には実行する)事で、設定ファイル内ではC側のバージョンに関する情報を元に、それに対応した設定を生成しています。

また、特にC側で事前に設定を作っておいて、それをRuby設定ファイルで連携する必要がない場合は、以下のようにnew_configメソッドで設定を作成します。

[program lang=’ruby’ escaped=’true’]

new_config(
    "Listen"            => 80,
    "DocumentRoot"      => "/var/www/html",
    "ExtendedStatus"    => "On",
    "User"              => "apache",
    "Group"             => "apache",
)

[/program]

C側での設定値の参照はシンプルに以下の様になります。

[program lang=’c’ escaped=’true’]

#include <mruby.h>

int main() {

  FILE *fp;
  if ((fp = fopen("./mruby.conf", "r")) == NULL)
    return 1;

  mrb_state* mrb = mrb_open();
  mrb_load_file(mrb, fp);

  int port;
  char *droot;
  mrb_value user;

  mrb_get_config_value(mrb, "Listen",       "i", &port);
  mrb_get_config_value(mrb, "DocumentRoot", "z", &droot);
  mrb_get_config_value(mrb, "User",         "o", &user);

  // 設定の値を使って色々と処理を実装

  mrb_close(mrb);

  return 0;
}

[/program]

これでOKです。簡単ですね!

また、設定を追加・修正したい時は、

[program lang=’ruby’ escaped=’true’]

add_config "ExtendedStatus" => "On"

[/program]

と書いたり、設定そのものを削除したい場合は、

[program lang=’ruby’ escaped=’true’]

del_config "ExtendedStatus"

[/program]

と書きます。

また、設定を参照したい場合は、以下のように書きます。

[program lang=’ruby’ escaped=’true’]

get_config "ExtendedStatus"

[/program]

あるいは、設定全てを呼び出したい時は、get_configを引数無しで実行します。get_configメソッドは大抵の場合、上記のサンプルのようにC側で設定ファイルの値を参照したい時に使うと思います。そのためのmruby APIであるmrb_get_config_value()を用意しています。

[program lang=’c’ escaped=’true’]

  /*
  mrb_get_config_value(mrb, key, format, ...)

  format specifiers (like mrb_get_args):

   o: Object [mrb_value]
   S: String [mrb_value]          //Not Implemented
   A: Array [mrb_value]           //Not Implemented
   H: Hash [mrb_value]            //Not Implemented
   s: String [char*,int]          //Not Implemented
   z: String [char*]
   a: Array [mrb_value*,mrb_int]  //Not Implemented
   f: Float [mrb_float]           //Not Implemented
   i: Integer [mrb_int]
  */

  int port;
  char *droot;
  mrb_value user;

  mrb_get_config_value(mrb, "Listen",       "i", &port);
  mrb_get_config_value(mrb, "DocumentRoot", "z", &droot);
  mrb_get_config_value(mrb, "User",         "o", &user);

[/program]

mruby-configで定義しているメソッドは全てC側からもRuby側からも値を共有できるように実装しているので、好きなように両者からメソッドを実行すると良いでしょう。

続きも書きました。

mruby-configにtag付のconfigを書けるようにした

最後に

今回はCで書いたサーバアプリやクライアントアプリでパラメータを柔軟に扱いたい場合の一つの手法として、mrubyを用いて簡単に設定値を外部のファイル(Rubyスクリプト)と連携するためのmruby-configを使った手法を紹介しました。これで、単純な設定であれば、ほぼ何も考える事なくCで書かれたアプリケーションの設定ファイルを外出しすることができるでしょう。

個人的にも、設定ファイルを作るのって面倒に思っていたので、今後はmruby-configを使って楽に外出しした設定ファイルからCアプリ内に値をひっぱってくるようにしたいと思います。