この記事では、libucl を自前の C++ プログラムで使用する方法を記述します。
はじめに:libucl とは
libucl は、UCL: Universal Configuration Language (和訳すれば汎用設定言語) で書かれた設定ファイルを読み込むライブラリです。 GitHub の Readme.md にある Conclusion を一部引用します。
UCL は非常に読み書きしやすく設計されている。 また、同時に JSON を包括しており(上位互換であり)、libucl は JSON パーサーとしても使える。
さらに、以下の追加機能を持ちます。
- コメントの追加
- nginx 風の記述(key = value)の解釈
- マクロの使用
BSD 系で、既存の設定ファイルを置き換えることを目指している模様です。1
インストール
Arch Linux の場合
Arch Linux の場合、AUR からインストールできます。
$ git clone https://aur.archlinux.org/libucl.git $ cd libucl $ makepkg -si
ヘッダファイル (ucl++.h など) は /usr/include に、ライブラリファイル (libucl.so) は /usr/lib にインストールされます。
そのほかのディストリビューションや、Unix系OSもパッケージがある可能性があります。
本家 GitHub から取得する場合
本家の GitHub から取得します。
$ git clone https://github.com/vstakhov/libucl
CMakeLists.txt が用意されているため、CMake でコンパイルします。
$ mkdir build $ cd build $ cmake -G "Unix Makefiles" .. $ cmake --build .
libucl.a が build ディレクトリに生成されます。
プログラムへの取り込み
CMake を使っている場合
CMakefile.txt に以下のように記述します。私が作成しているプログラムでは、wxWidgets も使っているため、そのライブラリも指定しています。 ここでは、.h ファイル(ヘッダファイル)は /usr/include に、.so ファイル(ライブラリファイル)は /usr/lib にインストールされた状態の例となります。 それぞれのディレクトリはデフォルトで探索パスに含まれるので、ucl (libucl.so のlibと.soを除いた部分) だけを追加しています。2
target_link_libraries(${PROJ_NAME} ${wxWidgets_LIBRARIES} ucl)
g++ を使用してコンパイルする場合
公式の GitHub をチェックアウトした場合、次節で挙げる example/ucl_cpp.cc
は、以下のコマンドでコンパイルできます。3
$ cd libucl $ g++ -lucl example/ucl_cpp.cc
プログラム内での使用方法
公式の例
公式の GitHub の example ディレクトリに、ucl_cpp.cc が例として掲載されています。 説明を1行コメントとして追記した上で転記します。
#include <iostream> #include <string> #include "ucl++.h" // include path を通していれば、<ucl++.h> と書けます int main(int argc, char **argv) { std::string input, err; // 読み込む文字列を標準入力から取得します。 input.assign((std::istreambuf_iterator<char>(std::cin)), std::istreambuf_iterator<char>()); // 読み込んだ文字列を解析します。 // 解析に失敗すると nullptr が返り、理由が err にセットされます。 auto obj = ucl::Ucl::parse(input, err); if (obj) { // 解析したデータを一括で出力します。 std::cout << obj.dump(UCL_EMIT_CONFIG) << std::endl; // 解析したデータを1つずつ出力します。 for (const auto &o : obj) { std::cout << o.dump(UCL_EMIT_CONFIG) << std::endl; } } else { // 失敗した場合、その行と内容等が表示されます。 std::cerr << "Error: " << err << std::endl; return 1; } return 0; }
ファイルからの読み込み
auto obj = ucl::Ucl::parse(input, err);
の代わりに、auto obj = ucl::Ucl::parse_from_file(ファイル名, err)
で、指定したファイル名から直接 UCL データを読み込んで解析します。auto で受け取っていますが、ucl::Ucl 型が返ります。
値の参照
取得する値の型によって、メソッドを変えます。
"a": 10.0; "b": 20; "c": true; "d": "eee";
とあるとき、以下のメソッド呼び出しでそれぞれの値を取得できます。
obj["a"].number_value(); // double => 10.0 obj["b"].int_value(); // int64_t => 20 obj["c"].bool_value(); // bool => true obj["d"].string_value(); // std::string => "eee"
同一キーは配列として扱う
同一のキーで複数の設定を記載した場合、自動的に配列として解析されます。例えば、
"foo": "value1" "foo": "value2"
は "foo": ["value1", "value2"]
として扱われます。
配列の場合、添字でのアクセスが可能です。
obj["foo"][0].string_value() => "value1"
また、配列の場合のみ size メソッドが使用可能です。4
obj["foo"].size() => 2
入れ子構造
データは、入れ子構造で定義することができます。
{ "foo" { "bar" { "key": "value" } } }
このとき、obj["foo"]["bar"]["key"].string_value()
の形で "value"
を取得することができます。
なお、obj["foo"]
は、以下の ucl::Ucl 型の値となります。
{ "bar" { "key": "value" } }
出力
公式の例にある、dump メソッドを使用します。
"key": "value" の追加
キー "foo" がない状態で obj["foo"] = "something"
などとコード中に記述しても、"foo": "something"
は追加されません。
おそらく全体を一旦 dump し、"foo": "something" を追加したうえで再解釈するしかないように思われます。私の場合、以下の Add メソッドを自作しました。
#include <ucl++.h> ucl::Ucl Add(ucl::Ucl obj, const std::string key, const std::string value) { // \n# は、obj.dump() の出力にある最初の開きカッコ「{」をコメントアウトするために追加しています。 std::string in = "{" + key + ": " + value + "\n#" + obj.dump(); std::string err; obj = ucl::Ucl::parse(in, err); if (! obj) { std::cerr << "Error: " << err << std::endl; return ucl::Ucl(nullptr); // 空の ucl::Ucl を返す } return obj; }
その他
各関数は ucl++.h を参照することで詳細がわかります。 at, begin, end, 演算子 (= (既存の"key"に"value"を設定する), ==, <, !=, <=, >, >=) 等も定義されています。
- BSDCan 2015 UCL Working Group↩
-
target_link_libraries(${PROJ_NAME} ${wxWidgets_LIBRARIES} /usr/lib/libucl.so)
と絶対パスで指定してもコンパイラはリンクすることができます。自前でコンパイルした場合は、こちらになります。なお、includeパスを指定する場合はtarget_include_directories(${PROJ_NAME} PRIVATE /usr/include)
と書きます。↩ -
/usr/include を指定する場合は、
-I /usr/include
を含めます。このディレクトリはデフォルトで見に行くため本文では記載していません。libucl を自前でコンパイルした場合は、-I や -l オプションによって各ファイルを絶対パスで指定できます。↩ -
言い換えれば、
key: value;
を列挙している場合、その要素数はイテレータで数え上げるしか取得方法がありません。↩