コンソールアプリの実装
それでは本番、key-value pairを操作するコンソールアプリ:ezdbの実装です。とは言ってもやることは単純、挿入(store)/検索(fetch)/一覧(list)およびDB作成(create,new)のいずれかのコマンドをオプションスイッチで指定し、コマンドに応じて--key/--valueでキーと値を指定します。長々と説明するよりコードを見てもらったほうがよさそう。gdbmの操作は、前回作ったwrapperを再利用しました。
#include <iostream>
#include <string>
#include <vector>
#include <boost/program_options.hpp>
#include <boost/optional.hpp>
#include "gdbm_db.h"
namespace po = boost::program_options;
using namespace std;
int main(int argc, char* argv[]) {
try {
po::options_description visible("Allowed options");
visible.add_options()
("help,h" , "produce help message")
("store,s" , "store key-value pair")
("fetch,f" , "fetch key")
("list,l" , "list all key-value pairs")
("create,c" , "create db (do nothing if exist)")
("new,n" , "create new db (even if exist)")
("block_size,b", po::value<int>()->value_name("<N>")->default_value(512), "block size on creation")
("key,k" , po::value<string>()->value_name("<key>"), "key")
("value,v" , po::value<string>()->value_name("<value>"), "value (default on fetch)")
;
po::options_description hidden("Hidden options");
hidden.add_options()
("gdbm_file", po::value<string>(), "gdbm_file")
;
po::options_description desc;
desc.add(visible).add(hidden);
po::positional_options_description pos;
pos.add("gdbm_file",1);
po::command_line_parser parser(argc,argv);
parser.style(po::command_line_style::default_style | po::command_line_style::allow_slash_for_short);
parser.options(desc).positional(pos);
po::variables_map vm;
po::store(parser.run(), vm);
po::notify(vm);
// ここまで↑がコマンドライン解析 ここから↓が本体
typedef boost::optional<po::variable_value> option_t;
auto get = [&vm](const string& name) {
return vm.count(name) ? option_t(vm[name]) : option_t();
};
if ( get("help") ) {
cout << "ezdb <option>... gdbm_file\n" << visible << endl;
return 0;
}
if ( !get("gdbm_file") ) {
cout << "<gdbm_file> missing." << endl;
return 1;
}
string gdbm_file = get("gdbm_file")->as<string>();
int block_size = get("block_size")->as<int>();
option_t key_opt = get("key");
option_t value_opt = get("value");
int open_mode;
int count = 0;
if ( get("store") ) { open_mode = GDBM_WRITER; ++count; }
if ( get("fetch") ) { open_mode = GDBM_READER; ++count; }
if ( get("list") ) { open_mode = GDBM_READER; ++count; }
if ( get("create") ) { open_mode = GDBM_WRCREAT; ++count; }
if ( get("new") ) { open_mode = GDBM_NEWDB; ++count; }
if ( count != 1 ) {
cout << "one, and only one of store/fetch/list/create/new needs to be chosen." << endl;
return 1;
}
gdbm::db db(gdbm_file, block_size, open_mode);
if ( !db.is_open() ) {
cout << gdbm_file << " open failure." << endl;
return 1;
}
auto to_datum = [](const string& s) { return gdbm::datum_converter<string>::to_datum(s); };
auto from_datum = [](const datum& d) { return gdbm::datum_converter<string>::from_datum(d); };
if ( get("store") ) {
if ( !key_opt ) {
cout << "<key> should be specified." << endl;
return 1;
}
if ( !value_opt ) {
cout << "<value> should be specified." << endl;
return 1;
}
db.store(to_datum(key_opt->as<string>()), to_datum(value_opt->as<string>()));
}
if ( get("fetch") ) {
if ( !key_opt ) {
cout << "<key> should be specified." << endl;
return 1;
}
gdbm::auto_datum value = db.fetch(to_datum(key_opt->as<string>()));
if ( value.dptr == nullptr ) {
if ( value_opt ) cout << value_opt->as<string>() << endl;
} else {
cout << from_datum(value) << endl;
}
}
if ( get("list") ) {
for ( gdbm::auto_datum key = db.firstkey(); !empty(key); key = db.nextkey(key) ) {
gdbm::auto_datum content = db.fetch(key);
cout << from_datum(key) << " = " << from_datum(content) << endl;
}
}
return 0;
} catch(exception& e) {
cerr << "error: " << e.what() << "\n";
} catch(...) {
cerr << "Exception of unknown type!\n";
}
return 1;
}
program_optionsを使ったコマンドライン解析のご紹介でした。さすがはBoost、よくできています。コレ使わずに力技でコマンドライン解析やったら、それだけで数百行書くハメになったんじゃないかしら。

