Modern and safest C++ argument parser, using C++ modules and metaprogramming. This is still in an experimental state.
-
Static Type Argument Parsing: Ensures command line arguments are parsed with strict type adherence, enhancing reliability.
-
Type Safety for All Flags: Provides robust type safety for command line flags, reducing runtime errors and ensuring type preservation during retrieval.
-
Compile-Time Duplicate Argument Detection: Detects duplicate flags at compile time, providing a better development experience.
-
Rich Feature Set: Offers a comprehensive array of features for versatile command line parsing needs.
-
Fastest Parsing Mechanism: Delivers high-speed parsing, ideal for performance-critical applications.
-
Compile-Time Calculations: Performs certain operations at compile time for enhanced performance efficiency.
- Requirements
- Basic Example
- Defining Arguments
- Template Options
- Other Options
- Creating Multiple Parsers
- Adding Subcommands
- Help Generation
- Installation with CMake
- C++23 compiler clang >= 17
- libcxx standard library
- C++ Modules supported CMake cmake >= 3.28
import Argo;
import std;
// suppose ./main --arg1 42 --arg3 "Hello,World"
auto main(int argc, char* argv[]) -> int {
auto parser = Argo::Parser() //
.addArg<"arg1", int>()
.addArg<"arg2", float>(Argo::explicitDefault(12.34))
.addArg<"arg3", std::string>();
parser.parse(argc, argv);
std::println("{}", parser.getArg<"arg1">()); // 42
std::println("{}", parser.getArg<"arg2">()); // 12.34 (default value)
std::println("{}", parser.getArg<"arg3">()); // Hello,World
// std::println("{}", parser.getArg<"arg4">()); // compile error
// Static Typing
static_assert(
std::is_same_v<decltype(parser.getArg<"arg1">()), int>);
static_assert(
std::is_same_v<decltype(parser.getArg<"arg2">()), float>);
static_assert(
std::is_same_v<decltype(parser.getArg<"arg3">()), std::string>);
return 0;
}
Try this at Compiler Explorer.
Defining a parser in Argo can be somewhat intricate. It is not accurate to define it as shown below:
auto parser = Argo::Parser();
parser.addArg<"arg1", int>();
parser.addArg<"arg2", int>();
parser.parse(argc, argv);
In Argo, arguments are stored in the return types of the addArg
method. Therefore, the correct declaration should be as follows:
auto argo = Argo::Parser();
auto pre_parser = argo.addArg<"arg1", int>();
auto parser = argo.addArg<"arg2", int>();
parser.parse(argc, argv);
or
auto parser = Argo::Parser()
.addArg<"arg1", int>()
.addArg<"arg2", int>();
parser.parse(argc, argv);
This approach ensures that each argument is properly added and stored, enabling the parser to function as expected.
Specifying a char
character as the key
parameter designates the abbreviated
version of the argument flag. In the provided code, the flag -a
can be passed
to the parser.
Example:
Argo::Parser().addArg<"arg1,a", Type>();
Specifying true
as the addArg template parameter indicates that the argument
will be required
. The parser will throw an exception if that argument is not
specified.
Example:
Argo::Parser().addArg<"arg1", Type, true>();
By using Argo::nargs
as a template parameter in Argo::Parser
, you can
define the nargs
for a particular flag. Below is a table detailing the
options for nargs
.
narg | number of values | description |
---|---|---|
int | exactly n | Stores exactly n values in the std::vector<Type> . |
'?' | zero or one | An implicit default value is used if zero values are provided; an explicit value is set when the flag is specified. |
'+' | more than one | Stores n values in the std::vector<Type> . |
'*' | zero or more | An implicit default value is used if zero values are provided; otherwise, behaves the same as '+'. |
Example usage:
Argo::Parser().addArg<"arg1", Type, nargs('+')>();
This template parameter configuration specifies that the arg1
flag accepts
one or more arguments, which will be stored in a std::vector<Type>
.
required
and nargs
values are interchangable you can specify these value in the same time.
Argo::Parser().addArg<"arg1", Type, true, nargs('+')>();
Argo::Parser().addArg<"arg1", Type, nargs('+'), true>();
These are all the same.
You can specify the implicit or explicit default value as follows:
- Implicit Default
Argo::Parser().addArg<"arg1", int>(Argo::implicitDefault(12.34));
- Explicit Default
Argo::Parser().addArg<"arg1", int>(Argo::explicitDefault(12.34));
Specify a description for the argument like this:
Argo::Parser().addArg<"arg1", int>(Argo::withDescription("Description of arg1"));
Set a callback for the flag, which is triggered as soon as the option is parsed:
Argo::Parser().addArg<"arg1", int>([](auto value, auto raw_value){
// Implement some callback using value and raw_value
// value would be reference to the value, so you can modify them.
});
In this callback, the parsed value is modifiable. You can validate the value inside this function.
namespace fs = std::filesystem;
Argo::Parser(),addArg<"file_path", fs::path>([](fs::path& value, std::span<std::string_view> /* unused */){
value = fs::absolute(value);
if (!fs::exists(value)) {
throw Argo::ValidationError(std::format("file {} does not exist"), value);
}
});
You can use std::vector
and std::array
or std::tuple
for type,
specifying these types, Argo guess the nargs and automatically set it for you.
// suppose ./main.a --arg1 42 3.14 "Hello,World"
auto argo = Argo::Parser();
auto parser = argo.addArg<"arg1", std::tuple<int, double, std::string>>();
parser.parse(argc, argv);
auto [a1, a2, a3] = parser.getArg<"arg1">(); // 42 3.14 "Hello,World"
Because Argo
generates types for each argument and stores variables within
these types, using the same ID in multiple parsers might cause conflicts with
the arguments. This conflict can sometimes lead to variables in one parser
being overwritten by another. To prevent this, you should assign a unique ID.
auto argo1 = Argo::Parser<42>();
auto argo2 = Argo::Parser<"unique ID">();
#include <Argo.h>
int main() {
auto parser1 = Argo::Parser<"SubCommands1_cmd1">()
.addArg<"arg1,a", int>()
.addArg<"arg2,b", int>(Argo::explicitDefault(123));
auto parser2 = Argo::Parser<"SubCommands1_cmd2">()
.addArg<"arg3,a", int>()
.addArg<"arg4,b", int>();
auto parser = Argo::Parser<"SubCommands1">()
.addParser<"cmd1">(parser1, Argo::description("cmd1"))
.addParser<"cmd2">(parser2, Argo::description("cmd2"));
parser.parse(argc, argv);
}
To integrate a subparser into your main parser, use the addParser
method. The
template argument for this method will be the name of the subcommand. With this
setup, you can easily handle command line arguments like cmd1 --arg1 42
or
cmd2 -b 24
.
To obtain parse results:
To access a subcommand parser, use getParser<Identifier>()
. This returns the
same parser object that was initially added with addParser
. To determine if a
particular subcommand was invoked, evaluate the truth of the respective parser
object.
if (parser1) {
println("{}", parser1.getArg<"arg1">());
}
You can easily enable a help flag by passing "addHelp".
When the --help
or -h
flag is used, the program will immediately display the help message and then exit.
auto parser = Argo::Parser()
.addArg<"arg1", int>()
.addHelp();
To customize the help flag, pass a template argument.
auto parser = Argo::Parser()
.addArg<"arg1", int>()
.addHelp<"generate_help,g">();
You can fully customize the contents of the help section.
auto parser = Argo::Parser()
.addArg<"arg1", int>()
.addHelp(R"(
Help Description
Usage:
./main -h
)");
The help section is divided into five parts:
- Description
- Usage Help
- Subcommands Help
- Positional Argument Help
- Options Help
<description>
Usage:
<usage help>
Subcommands:
<subcommands help>
Positional Argument:
<positional argument help>
Options:
<options help>
You can tailor each section as follows.
auto parser = Argo::Parser("program name", "<description>")
.addArg<"arg1", int>()
.addHelp();
parser.addUsageHelp("<usage help>");
parser.addSubcommandHelp("<subcommands help>");
parser.addPositionalArgumentHelp("<positional argument help>");
parser.addOptionsHelp("<options help>");
- clone this repository into you project
git clone https://github.com/gen740/Argo.git
- Add subdirectory
add_subdirectory("path/to/Argo")
target_link_libraries(YOUR_TARGET Argo)
include(FetchContent)
FetchContent_Declare(
Argo
GIT_REPOSITORY https://github.com/gen740/Argo.git
GIT_TAG main # or use a specific commit/tag
)
FetchContent_MakeAvailable(Argo)
target_link_libraries(YOUR_TARGET PRIVATE Argo)
You can use this library as a single header file.
Just copy and paste include/Argo.hh
into your library.
- Boolean Flag support
- Support for short option
- Combining Boolean Flag
- Validation
- Support for multiple parser
- Argument duplication validation
- Optional argument
- "narg" argument
- Required argument
- Positional argument
- sub command
- Description and help auto generation
- Standard validations
- Custom class casting