結局boost::program_optionsではなく、🔗argparseというライブラリ(もちろんC++)を使うことになった。
というのも、boost::program_optionsではサブコマンドgit update ..., git init ...
などを支援する機能が提供されていないようなので、自前で実装する羽目になりかなり面倒で難しいことがわかったからである。
argparseではいろいろなが提供されているので簡単にサブコマンドを書くことができた。
実際に書いたもの。
🔗https://github.com/nisetynet/xtool/blob/ff122ba0c8d49627f243e31ef0a4b46af3ab9367/src/main.cpp#L175
強い理由がなければargparseを使うことをおすすめする。
地獄を見たいのであればboost poを使え。
状況 #
🔗boost::program_optionsで不正な引数が渡された際に、詳細なエラーメッセージを表示したい。
ライブラリの例外クラスではエラーメッセージを追加することはできないようだ。
std::exceptionの派生を投げる手もあるが、できればprogram_optionsのエラーメッセージを使いまわしたい。
方法 #
boost::program_options::error
かその派生クラスを更に継承してあげればOK。
boost::program_optionsのエラー(例外)関連クラスはboost/program_options/errors.hpp
内で定義されている。
boost::program_options::validation_error
が汎用的でおすすめ。
俺を信じろ。
以下、実装例。
boost::program_options::validation_error
を継承して自前の詳細エラーメッセージを付け加えるようにしただけ。
class invalid_option_error_with_msg
: public boost::program_options::validation_error {
public:
invalid_option_error_with_msg(std::string_view const option_name,
std::string_view const original_token,
std::string_view const error_message)
: boost::program_options::validation_error(
boost::program_options::validation_error::invalid_option_value,
option_name.data(), original_token.data()),
m_custom_message(error_message) {}
virtual const char *what() const noexcept override {
// 派生元のwhat()を呼んでエラーメッセージを取得。
auto const what = boost::program_options::validation_error::what();
// 取得した派生元のエラーメッセージに自分のメッセージを追加
// const char *を返すのでstd::stringが死なないように派生元クラスのメンバであるm_messageにメッセージを格納しておく
m_message = fmt::format("{}, got {}, detail: {}", what,
this->m_substitutions.at("original_token"), // <-- コンストラクタで渡した original_token は内部で格納されているので拝借。
m_custom_message);
return m_message.c_str();
}
private:
std::string m_custom_message;
};
使用例 #
boost::program_options::options_description config("Configuration");
config.add_options()(
"server-address,a",
boost::program_options::value<std::string>()
->notifier([](std::string_view address) {
if (address.starts_with("loc")) {
// after
// 継承したクラスを利用
throw invalid_option_error_with_msg(
"server-address", address,
"localhost is not allowed, use 127.0.0.1 instead"); // <-- 詳細エラーメッセージ
// before
throw boost::program_options::validation_error(
boost::program_options::validation_error::
invalid_option_value,
"server-address", address.data());
}
})
->required(),
"server address to connect")(
"server-port,p", boost::program_options::value<int>()->required(),
"server port");
// ...
int main(int argc, char** argv){
// ...
try {
// パース
} catch (boost::program_options::error &e) { // <-- boost::program_options::error から派生したクラスの例外はここでキャッチされる
spdlog::error("Invalid argument: {}", e.what());
return EXIT_FAILURE;
} catch (std::exception &e) {
spdlog::error("Exception: {}", e.what());
return EXIT_FAILURE;
}
// ...
}
エラーメッセージ サンプル #
root@2b7592ccxxxx:/xxxx/build# ./xxxx/client --server-address localhost11451 --server-port 12314
[2024-06-20 03:08:50.969] [error] Invalid argument: the argument for option 'server-address' is invalid, got localhost11451, detail: localhost is not allowed, use 127.0.0.1 instead
いかがでしたか?
Rustのcli引数パーサーライブラリ🔗clapは便利ですね。
C++は苦しいです。
くるC!