あめだまふぁくとりー

Boost.Graphとかできますん

Boost.Program_optionsで名前を持たないオプションの扱い方

Boost.Program_optionsについて調べてみたので,備忘録として.

名前を持たないオプションの対応

以下が名前を持たないオプション値の例です.オプション値hogeはoptionという名前に関連づけされていますが,huga.txtには関連づけされた名前がありません.

$ command --option=hoge huga.txt

以下のような一般的なProgram_optionsの使用方法ではhuga.txtが手に入りません.

#include <iostream>
#include <boost/program_options.hpp>

using namespace boost::program_options;

int main(int argc, char* argv[])
{
    auto desc = options_description{"options"};
    desc.add_options()
        ("option", value<std::string>(), "some option");
    auto vm = variables_map{};
    store(parse_command_line(argc, argv, desc), vm);
    notify(vm);

    if (vm.count("option")) {
        std::cout << vm["option"].as<std::string>() << std::endl;
    }
}

positional_options_descriptionを使用する方法

この場合,huga.txtを扱うには二通り方法があります. 一つ目がpositional_options_descriptionを使用する方法です.これはドキュメントにも載っている方法なので例だけ示します.

auto desc = options_description{"options"};
desc.add_options()
    ("option", value<std::string>(), "some option")
    // 名前をつけないオプションにも名前をつける
    ("unnamed-option", value<std::string>(), "unnamed option")
    ;

auto vm = variables_map{};
store(command_line_parser{argc, argv}
        .options(desc)
        .positional(
            positional_options_description{}
                // 名前の付いていないオプション一つをunnamed-optionで名前づけする
                .add("unnamed-option", 1))
        .run()
    , vm);
notify(vm);

if (vm.count("option")) {
    std::cout << vm["option"].as<std::string>() << std::endl;
}

if (vm.count("unnamed-option")) {
    std::cout << vm["unnamed-option"].as<std::string>() << std::endl;
}

ちなみにpositional_options_description::addの第二引数の整数値は,第一引数に指定した名前を持つオプション値の個数を表します. 位置を指定している訳ではありませんので注意してください.-1を指定すると個数は無制限になります.

positional_options_description{}
    .add("unnamed1", 2)
    .add("unnamed2", 1)
    .add("unnamed3", -1)
    .add("unnamed4", 2);

上記のような場合,コマンドライン上に表れる名前を持たないオプション値のうち,左から二つはunnamed1に関連づけられ,その次の一つ,さらにその次の二つはそれぞれunnamed2,unnamed4に関連づけられます.残りのオプション値はunnamed3に関連づけられることになります.

さらに.add("unnamed5", -1)と書くとunnamed3にはオプション値は一つも関連づけられず,代わりにunnamed5に関連づけられます.

collect_unrecognizedを使用する方法

positional_options_description使用する方法の欠点は,名前を必要としないオプション値に対しても名前付けする必要があることです.

名前が不要のオプションに対して名前を付けてしまうと,helpオプション等の出力にoptions_descriptionインスタンスを用いた場合,不要なオプションが見えてしまいます.Program_optionsのtutorialでは,options_descriptionの複数のインスタンスを用いることでこの問題を解決していましたが,出力と実際のparsingで使用するインスタンスが異なるのはあまりよいとは思えません.

ここでは関数collect_unrecognizedを使用して,options_descriptionインスタンスに変更を加えずに名前を持たないオプションを取得します.

collect_unrecognizedは指定したoptions_descriptorインスタンスが知らないオプションを取得する関数です(デフォルトではparserは,'-'で始まるオプションが知らないものだった場合,例外を投げるため,これを許容するようにする必要があります).

collect_unrecognizedの第一引数はstd::vector<basic_option>であり,これはparsing結果から取得できます.

第二引数はcollect_unrecognized_mode型の値であり,include_positionalとexclude_optionalの二つの値が存在します.前者を指定すると'-'から始まらないオプションも取得することができます.

以下が例です.

auto desc = options_description{"options"};
desc.add_options()
    ("option", value<std::string>(), "some option");
auto vm = variables_map{};
// parsingの結果を保持
auto const parsing_result = parse_command_line(argc, argv, desc);
store(parsing_result, vm);
notify(vm);

if (vm.count("option")) {
    std::cout << vm["option"].as<std::string>() << std::endl;
}

std::cout << "unnamed options:" << std::endl;
// parsingの結果からoptions_descriptionに関係しないものを取得
for (auto const& str : collect_unrecognized(parsing_result.options, include_positional)) {
    std::cout << "    " << str << std::endl;
}