Kazuhiki - コマンドライン引数パーサ for C++

C/C++だと、コマンドライン引数の解析って面倒ですよね。正規表現が使えれば…しかし余分なライブラリに依存するのもいただけない。
getoptも、やっぱり面倒。結局文字列からint型に変換したり、変換できなかったらエラーを出したりは、自分でやらないといけない。


そこで、型変換まで面倒を見てくれるC++用のコマンドライン引数パーサ Kazuhiki を作りました。MITライセンスで公開します。
ダウンロード: kazuhiki-0.1


ヘッダをインクルードするだけで利用でき、濫用された演算子オーバーロードで構文を記述すると、あとは良きに計らってくれます。たとえば以下のように記述します。

bool cmd_number;
int num = 0;
unsigned int un = 0;
unsigned int hex = 0;
double d = 0;

struct sockaddr_storage ss;    // IPv4/v6両対応

bool use_string;
std::string str;

using namespace Kazuhiki;

RootParser parser;
parser  << IgnoreOne()    // argv[0]はプログラム名
        << CommandParser()
                % ( CommandParser::Command("number", cmd_number)
                        << OptionParser()
                                % NumericKey(false, "number", "n", num)    // 符号付き整数
                                % NumericKey(false, "unsigned", "u", un)    // 負の数ならエラー
                                % NumericKey(true,  "hex", "h", hex, std::hex)    // 必須オプション, 16進数で指定
                                % NumericKey(false, "double", "d", d)    // 小数, 指数指定
                  )
                % ( CommandParser::Command("network")
                        << SwitchParser()
                                % ( SwitchParser::Switch("-s", use_string)    // "-s"なら
                                        << SingleString(true, str)    // 文字列を1つ受け取る(必須)
                                  )
                                % ( SwitchParser::Other()    // それ以外なら
                                        << SingleFlexibleActiveHost(true, ss, 12345)    // デフォルトポート番号1235番で良きに計らう
                                  )
                  )
                ;

try {
        parser.parse(argc, argv);
} catch ( const ArgumentError& ex ) {
        std::cout << ex.what() << std::endl;
        exit(1);
}

templateを多用しており、引数に渡した変数の型によって適切に変換してくれます。変換できなかったら、エラー(例外)を出してくれます。


CommandParserは、Parttyのように第一引数によってその後に取る引数が変わる場合に使います。コマンドはCommandParser::Commandをoperator%で渡して列挙します。operator<<よりoperator%の方が優先順位が高いことに注意してください。

コマンド名は途中までしか指定されていなくても受け付けます。たとえばこの場合、"nu"と指定されていれば"number"、"ne"と指定されて入れば"network"と判断します。"n"だけだとどちらか分からないので、エラーになります。このときのエラーメッセージは「ambiguity command "n" ( number network )」になります。


ほとんどの機能は基底クラスで定義されており、それを継承して新しいクラスを定義することで、独自の引数形式を作ることもできます。たとえば、指定された引数の1文字目を取り出すクラスは、以下のように定義できます。

////
// First Character
//
class FirstCharacter : public WriteOnce {
public:
        SingleString(bool required, char& val) :
                WriteOnce(required),
                m_val(val) {}
public:
        unsigned int write_value(int argc, const char* const argv[]) {
                m_val = argv[0][0];
                return 1;    // 引数を1つ消費
        }
private:
        char& m_val;
};


ちなみにParttyの引数パーサは、以下のようになっています。

std::string prog;

bool mode_host;
bool mode_guest;

bool use_local;
std::string local_path;

struct sockaddr_storage addr;

{
        using namespace Kazuhiki;

        RootParser parser; 
        parser  << IgnoreOne()
                << CommandParser()
                        % ( CommandParser::Command("host", mode_host)
                                << SwitchParser()
                                        % ( SwitchParser::Switch("-l", use_local)
                                                << SingleString(true, local_path)
                                          )       
                                        % ( SwitchParser::Other()
                                                << SingleFlexiblePassiveAddress(addr, DEFAULT_PORT)
                                          )
                          )

                        % ( CommandParser::Command("guest", mode_guest)
                                << SwitchParser()
                                        % ( SwitchParser::Switch("-l", use_local)
                                                << SingleString(true, local_path)
                                          )       
                                        % ( SwitchParser::Other()
                                                << SingleFlexibleActiveHost(true, addr, DEFAULT_PORT)
                                          )
                          )
                        ;

        try {
                parser.parse(argc, argv);
        } catch( const ArgumentError& ex ) {
                std::cerr << ex.what() << std::endl;
                usage();
                exit(1);
        }
}