argparse
A C++17 argument parser inspired by Python's argparse module. Provides a modern and intuitive API.
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)