argparse

A C++17 argument parser inspired by Python's argparse module. Provides a modern and intuitive API.

cppclicommand-linecpp17

Framework

argparse

Overview

argparse is a C++17 argument parser inspired by Python's argparse module. It provides a modern and intuitive API with a familiar design for Python developers. It's gaining popularity in C++17+ projects and achieves type-safe and easy-to-use command-line parsing.

Details

argparse is a library that ports the design philosophy of Python's standard library argparse to C++. It's provided as header-only and uses C++17 features to achieve a modern and expressive API.

Key Features

  • Python-like API: Intuitive for developers familiar with Python argparse
  • C++17 Support: Type-safe design utilizing modern C++ features
  • Header-Only: Easy integration with a single header file
  • Rich Argument Types: Comprehensive support for positional arguments, optional arguments, and flags
  • Automatic Help Generation: Automatically generates beautifully organized help messages
  • Subcommands: Supports complex CLI applications
  • Type Inference: Automatic type inference using C++17 features
  • Validation: Built-in argument value validation functionality

Pros and Cons

Pros

  • Familiar API: Especially easy to learn for Python developers
  • Modern C++: Sophisticated design utilizing C++17 features
  • Type Safe: Compile-time type checking
  • Rich Features: Subcommands, validation, custom actions, etc.
  • Beautiful Help: Auto-generated help messages are well-formatted
  • Header-Only: Easy integration with minimal dependencies

Cons

  • C++17 Dependency: Cannot be used with older compilers
  • New Library: Less mature compared to other libraries
  • Learning Curve: Initial learning required for non-Python developers
  • Memory Usage: Higher memory usage due to rich features

Key Links

Usage Examples

Basic Usage

#include <argparse/argparse.hpp>
#include <iostream>

int main(int argc, char *argv[]) {
    argparse::ArgumentParser program("MyApp");
    
    program.add_argument("--name")
        .help("Enter your name");
    
    program.add_argument("--count")
        .help("Number of repetitions")
        .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 << "Hello, " << name << "!" << std::endl;
    }
    
    return 0;
}

Positional Arguments and Flags

#include <argparse/argparse.hpp>
#include <iostream>
#include <vector>

int main(int argc, char *argv[]) {
    argparse::ArgumentParser program("FileProcessor");
    
    // Positional arguments
    program.add_argument("files")
        .help("Files to process")
        .nargs(argparse::nargs_pattern::at_least_one);
    
    // Flags
    program.add_argument("-v", "--verbose")
        .help("Enable verbose output")
        .default_value(false)
        .implicit_value(true);
    
    program.add_argument("-r", "--recursive")
        .help("Process recursively")
        .default_value(false)
        .implicit_value(true);
    
    // Optional arguments
    program.add_argument("-o", "--output")
        .help("Output directory")
        .default_value(std::string{"./output"});
    
    program.add_argument("-f", "--format")
        .help("Output format")
        .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 << "Processing settings:" << std::endl;
    std::cout << "  Output directory: " << output << std::endl;
    std::cout << "  Output format: " << format << std::endl;
    std::cout << "  Verbose output: " << (verbose ? "enabled" : "disabled") << std::endl;
    std::cout << "  Recursive processing: " << (recursive ? "enabled" : "disabled") << std::endl;
    
    std::cout << "Files to process:" << std::endl;
    for (const auto& file : files) {
        std::cout << "  - " << file << std::endl;
    }
    
    return 0;
}

Subcommands

#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("Repository URL to clone");
    clone_command.add_argument("--depth")
        .help("History depth")
        .scan<'i', int>()
        .default_value(0);
    
    argparse::ArgumentParser push_command("push");
    push_command.add_argument("remote")
        .help("Remote to push to")
        .default_value(std::string{"origin"});
    push_command.add_argument("branch")
        .help("Branch to push")
        .default_value(std::string{"main"});
    push_command.add_argument("-f", "--force")
        .help("Force push")
        .default_value(false)
        .implicit_value(true);
    
    argparse::ArgumentParser log_command("log");
    log_command.add_argument("--oneline")
        .help("One line display")
        .default_value(false)
        .implicit_value(true);
    log_command.add_argument("-n", "--max-count")
        .help("Maximum display count")
        .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 << "Cloning repository: " << repo << std::endl;
        if (depth > 0) {
            std::cout << "History depth: " << 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 << "Pushing to: " << remote << "/" << branch << std::endl;
        if (force) {
            std::cout << "Force push enabled" << 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 << "Displaying commit log" << std::endl;
        std::cout << "Display format: " << (oneline ? "one line" : "detailed") << std::endl;
        std::cout << "Maximum count: " << max_count << std::endl;
    }
    
    return 0;
}

Custom Actions and Validation

#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("Configuration file path")
        .action([](const std::string& value) {
            if (!std::filesystem::exists(value)) {
                throw std::runtime_error("Configuration file does not exist: " + value);
            }
            return value;
        });
    
    program.add_argument("--port")
        .help("Server port")
        .scan<'i', int>()
        .action([](const std::string& value) {
            int port = std::stoi(value);
            if (port < 1024 || port > 65535) {
                throw std::runtime_error("Port must be in range 1024-65535");
            }
            return port;
        });
    
    program.add_argument("--email")
        .help("Email address")
        .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("Please enter a valid email address");
            }
            return value;
        });
    
    program.add_argument("--log-level")
        .help("Log level")
        .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("Invalid log level: " + value);
            }
            return value;
        });
    
    try {
        program.parse_args(argc, argv);
    }
    catch (const std::exception& err) {
        std::cerr << "Error: " << err.what() << std::endl;
        std::cerr << program;
        return 1;
    }
    
    std::cout << "Configuration complete:" << std::endl;
    
    if (auto config_file = program.present("--config-file")) {
        std::cout << "  Config file: " << *config_file << std::endl;
    }
    
    if (auto port = program.present<int>("--port")) {
        std::cout << "  Port: " << *port << std::endl;
    }
    
    if (auto email = program.present("--email")) {
        std::cout << "  Email: " << *email << std::endl;
    }
    
    auto log_level = program.get<std::string>("--log-level");
    std::cout << "  Log level: " << log_level << std::endl;
    
    return 0;
}

Advanced Type Handling

#include <argparse/argparse.hpp>
#include <iostream>
#include <vector>
#include <map>

int main(int argc, char *argv[]) {
    argparse::ArgumentParser program("AdvancedApp");
    
    // Accept multiple values
    program.add_argument("--numbers")
        .help("List of numbers")
        .nargs(argparse::nargs_pattern::at_least_one)
        .scan<'g', double>();  // Parse as double
    
    // Key=value pairs
    program.add_argument("--env")
        .help("Environment variables (KEY=VALUE format)")
        .append()
        .action([](const std::string& value) {
            auto pos = value.find('=');
            if (pos == std::string::npos) {
                throw std::runtime_error("Environment variables must be in KEY=VALUE format");
            }
            return std::make_pair(value.substr(0, pos), value.substr(pos + 1));
        });
    
    // Range specification
    program.add_argument("--range")
        .help("Range specification (start:end format)")
        .action([](const std::string& value) {
            auto pos = value.find(':');
            if (pos == std::string::npos) {
                throw std::runtime_error("Range must be in start:end format");
            }
            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 << "Number list:" << 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 << "Environment variables:" << 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 << "Range: from " << start << " to " << end << std::endl;
    }
    
    return 0;
}

CMake Usage

# CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(MyArgparseApp)

# Enable C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Get 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)