argparse

Python argparseモジュールにインスパイアされたC++17引数パーサー。モダンで直感的なAPIを提供します。

cppclicommand-linecpp17

フレームワーク

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)