argparse
Python argparseモジュールにインスパイアされたC++17引数パーサー。モダンで直感的なAPIを提供します。
フレームワーク
argparse
概要
argparseは、Python argparseモジュールにインスパイアされたC++17引数パーサーです。モダンで直感的なAPIを提供し、Python開発者にとって親しみやすい設計となっています。C++17以降のプロジェクトで人気が高まっており、型安全で使いやすいコマンドライン解析を実現します。
詳細
argparseは、Pythonの標準ライブラリargparseの設計哲学をC++に移植したライブラリです。ヘッダーオンリーで提供され、C++17の機能を活用してモダンで表現力豊かなAPIを実現しています。
主な特徴
- PythonライクなAPI: Python argparseに慣れた開発者には直感的
- C++17対応: モダンなC++機能を活用した型安全な設計
- ヘッダーオンリー: 単一のヘッダーファイルで簡単に導入可能
- 豊富な引数型: 位置引数、オプション引数、フラグを包括的にサポート
- 自動ヘルプ生成: 美しく整理されたヘルプメッセージを自動生成
- サブコマンド: 複雑なCLIアプリケーションにも対応
- 型推論: C++17の機能を活用した自動型推論
- バリデーション: 引数値の検証機能を内蔵
メリット・デメリット
メリット
- 親しみやすいAPI: Python開発者には特に習得しやすい
- モダンなC++: C++17の機能を活用した洗練された設計
- 型安全: コンパイル時に型チェックが行われる
- 豊富な機能: サブコマンド、バリデーション、カスタムアクションなど
- 美しいヘルプ: 自動生成されるヘルプメッセージが見やすい
- ヘッダーオンリー: 導入が簡単で依存関係が最小限
デメリット
- C++17依存: 古いコンパイラでは使用できない
- 新しいライブラリ: 他のライブラリに比べて歴史が浅い
- 学習コスト: Python非経験者には初期学習が必要
- メモリ使用量: 機能豊富な分、メモリ使用量が多め
主要リンク
書き方の例
基本的な使用例
#include <argparse/argparse.hpp>
#include <iostream>
int main(int argc, char *argv[]) {
argparse::ArgumentParser program("MyApp");
program.add_argument("--name")
.help("お名前を入力してください");
program.add_argument("--count")
.help("繰り返し回数")
.scan<'i', int>()
.default_value(1);
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
}
auto name = program.get<std::string>("--name");
auto count = program.get<int>("--count");
for (int i = 0; i < count; ++i) {
std::cout << "こんにちは、" << name << "さん!" << std::endl;
}
return 0;
}
位置引数とフラグの例
#include <argparse/argparse.hpp>
#include <iostream>
#include <vector>
int main(int argc, char *argv[]) {
argparse::ArgumentParser program("FileProcessor");
// 位置引数
program.add_argument("files")
.help("処理するファイル一覧")
.nargs(argparse::nargs_pattern::at_least_one);
// フラグ
program.add_argument("-v", "--verbose")
.help("詳細な出力を表示")
.default_value(false)
.implicit_value(true);
program.add_argument("-r", "--recursive")
.help("再帰的に処理")
.default_value(false)
.implicit_value(true);
// オプション引数
program.add_argument("-o", "--output")
.help("出力ディレクトリ")
.default_value(std::string{"./output"});
program.add_argument("-f", "--format")
.help("出力形式")
.default_value(std::string{"json"})
.choices("json", "xml", "csv");
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
}
auto files = program.get<std::vector<std::string>>("files");
auto verbose = program.get<bool>("--verbose");
auto recursive = program.get<bool>("--recursive");
auto output = program.get<std::string>("--output");
auto format = program.get<std::string>("--format");
std::cout << "処理設定:" << std::endl;
std::cout << " 出力ディレクトリ: " << output << std::endl;
std::cout << " 出力形式: " << format << std::endl;
std::cout << " 詳細出力: " << (verbose ? "有効" : "無効") << std::endl;
std::cout << " 再帰処理: " << (recursive ? "有効" : "無効") << std::endl;
std::cout << "処理ファイル:" << std::endl;
for (const auto& file : files) {
std::cout << " - " << file << std::endl;
}
return 0;
}
サブコマンドの例
#include <argparse/argparse.hpp>
#include <iostream>
int main(int argc, char *argv[]) {
argparse::ArgumentParser program("GitTool");
argparse::ArgumentParser clone_command("clone");
clone_command.add_argument("repository")
.help("クローンするリポジトリURL");
clone_command.add_argument("--depth")
.help("履歴の深さ")
.scan<'i', int>()
.default_value(0);
argparse::ArgumentParser push_command("push");
push_command.add_argument("remote")
.help("プッシュ先リモート")
.default_value(std::string{"origin"});
push_command.add_argument("branch")
.help("プッシュするブランチ")
.default_value(std::string{"main"});
push_command.add_argument("-f", "--force")
.help("強制プッシュ")
.default_value(false)
.implicit_value(true);
argparse::ArgumentParser log_command("log");
log_command.add_argument("--oneline")
.help("ワンライン表示")
.default_value(false)
.implicit_value(true);
log_command.add_argument("-n", "--max-count")
.help("最大表示数")
.scan<'i', int>()
.default_value(10);
program.add_subparser(clone_command);
program.add_subparser(push_command);
program.add_subparser(log_command);
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
}
if (program.is_subcommand_used("clone")) {
auto repo = clone_command.get<std::string>("repository");
auto depth = clone_command.get<int>("--depth");
std::cout << "リポジトリをクローン中: " << repo << std::endl;
if (depth > 0) {
std::cout << "履歴の深さ: " << depth << std::endl;
}
}
else if (program.is_subcommand_used("push")) {
auto remote = push_command.get<std::string>("remote");
auto branch = push_command.get<std::string>("branch");
auto force = push_command.get<bool>("--force");
std::cout << "プッシュ中: " << remote << "/" << branch << std::endl;
if (force) {
std::cout << "強制プッシュが有効です" << std::endl;
}
}
else if (program.is_subcommand_used("log")) {
auto oneline = log_command.get<bool>("--oneline");
auto max_count = log_command.get<int>("--max-count");
std::cout << "コミットログ表示中" << std::endl;
std::cout << "表示形式: " << (oneline ? "ワンライン" : "詳細") << std::endl;
std::cout << "最大表示数: " << max_count << std::endl;
}
return 0;
}
カスタムアクションとバリデーション
#include <argparse/argparse.hpp>
#include <iostream>
#include <filesystem>
#include <regex>
int main(int argc, char *argv[]) {
argparse::ArgumentParser program("ConfigTool");
program.add_argument("--config-file")
.help("設定ファイルパス")
.action([](const std::string& value) {
if (!std::filesystem::exists(value)) {
throw std::runtime_error("設定ファイルが存在しません: " + value);
}
return value;
});
program.add_argument("--port")
.help("サーバーポート")
.scan<'i', int>()
.action([](const std::string& value) {
int port = std::stoi(value);
if (port < 1024 || port > 65535) {
throw std::runtime_error("ポートは1024-65535の範囲で指定してください");
}
return port;
});
program.add_argument("--email")
.help("メールアドレス")
.action([](const std::string& value) {
std::regex email_pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
if (!std::regex_match(value, email_pattern)) {
throw std::runtime_error("有効なメールアドレスを入力してください");
}
return value;
});
program.add_argument("--log-level")
.help("ログレベル")
.default_value(std::string{"INFO"})
.action([](const std::string& value) {
std::vector<std::string> valid_levels = {"DEBUG", "INFO", "WARN", "ERROR"};
auto it = std::find(valid_levels.begin(), valid_levels.end(), value);
if (it == valid_levels.end()) {
throw std::runtime_error("無効なログレベル: " + value);
}
return value;
});
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << "エラー: " << err.what() << std::endl;
std::cerr << program;
return 1;
}
std::cout << "設定完了:" << std::endl;
if (auto config_file = program.present("--config-file")) {
std::cout << " 設定ファイル: " << *config_file << std::endl;
}
if (auto port = program.present<int>("--port")) {
std::cout << " ポート: " << *port << std::endl;
}
if (auto email = program.present("--email")) {
std::cout << " メール: " << *email << std::endl;
}
auto log_level = program.get<std::string>("--log-level");
std::cout << " ログレベル: " << log_level << std::endl;
return 0;
}
高度な型処理の例
#include <argparse/argparse.hpp>
#include <iostream>
#include <vector>
#include <map>
int main(int argc, char *argv[]) {
argparse::ArgumentParser program("AdvancedApp");
// 複数の値を受け取る
program.add_argument("--numbers")
.help("数値のリスト")
.nargs(argparse::nargs_pattern::at_least_one)
.scan<'g', double>(); // doubleとして解析
// キー=値のペア
program.add_argument("--env")
.help("環境変数 (KEY=VALUE形式)")
.append()
.action([](const std::string& value) {
auto pos = value.find('=');
if (pos == std::string::npos) {
throw std::runtime_error("環境変数はKEY=VALUE形式で指定してください");
}
return std::make_pair(value.substr(0, pos), value.substr(pos + 1));
});
// 範囲指定
program.add_argument("--range")
.help("範囲指定 (start:end形式)")
.action([](const std::string& value) {
auto pos = value.find(':');
if (pos == std::string::npos) {
throw std::runtime_error("範囲はstart:end形式で指定してください");
}
int start = std::stoi(value.substr(0, pos));
int end = std::stoi(value.substr(pos + 1));
return std::make_pair(start, end);
});
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
}
if (auto numbers = program.present<std::vector<double>>("--numbers")) {
std::cout << "数値リスト:" << std::endl;
for (const auto& num : *numbers) {
std::cout << " " << num << std::endl;
}
}
if (auto env_vars = program.present<std::vector<std::pair<std::string, std::string>>>("--env")) {
std::cout << "環境変数:" << std::endl;
for (const auto& [key, value] : *env_vars) {
std::cout << " " << key << " = " << value << std::endl;
}
}
if (auto range = program.present<std::pair<int, int>>("--range")) {
auto [start, end] = *range;
std::cout << "範囲: " << start << " から " << end << " まで" << std::endl;
}
return 0;
}
CMakeでの使用例
# CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(MyArgparseApp)
# C++17を有効化
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# argparseを取得
include(FetchContent)
FetchContent_Declare(
argparse
GIT_REPOSITORY https://github.com/p-ranav/argparse.git
GIT_TAG v2.9
)
FetchContent_MakeAvailable(argparse)
add_executable(my_app main.cpp)
target_link_libraries(my_app argparse::argparse)