Looking for a simple CLI argument parser for your C++ project? Argengine
might be for you.
- Automatic help generation
- Based on simple lambda callbacks
- Builds with CMake
- Designed for modern C++
- Extensive unit tests
- Single .hpp/.cpp
- Very easy to use
- Argument values are passed as strings. The client is responsible for possible data conversions.
- Most of the data is passed by value for a cleaner API. It is assumed that argument parsing is not the performance bottleneck of your application
- The PIMPL idiom is used to maintain API/ABI compatibility
Just add src/argengine.hpp
and src/argengine.cpp
to your project and start using it!
Copy contents of Argengine
under your main project (or clone as a Git submodule).
In your CMakeLists.txt
:
add_subdirectory(Argengine EXCLUDE_FROM_ALL)
include_directories(Argengine/src)
Link to the library:
target_link_libraries(${YOUR_TARGET_NAME} Argengine_static)
In your code:
#include "argengine.hpp"
Build and install:
$ mkdir build && cd build
$ cmake ..
$ make
$ sudo make install
Link to libArgengine_static.a
or libArgengine.so
.
The basic principle is that for each option a lambda callback is added.
A valueless callback:
...
juzzlin::Argengine ae(argc, argv);
ae.addOption({"-f", "--foo"}, [] {
// Do something
});
ae.parse();
...
A single-value callback:
...
juzzlin::Argengine ae(argc, argv);
ae.addOption({"-f", "--foo"}, [] (std::string value) {
// Do something with value
});
ae.parse();
...
There can be as many option variants as liked, usually the short and long versions, e.g -f
and --foo
.
Argengine
doesn't care about the naming of the options and they can be anything: -f
, a
, /c
, foo
, --foo
...
Positional arguments (for example a file name for a text editor after other options) can be received with a single callback:
...
juzzlin::Argengine ae(argc, argv);
ae.setPositionalArgumentCallback([] (std::vector<std::string> args) {
// Do something with arguments
});
ae.parse();
...
If the callback for positional arguments is set, then no errors about unknown options
will occur as all additional options will be taken as positional arguments.
By default, Argengine
will create a simple help that is shown with -h
or --help
.
Without any additional options a possible output will look like this:
Usage: ./ex1 [OPTIONS]
Options:
-h, --help Show this help.
The help can be manually printed with Argengine::printHelp()
.
The default help can be disabled by constructing Argengine
with Argengine::Argengine(argc, argv, false)
.
A custom help can be added with:
void Argengine::addHelp(OptionVariants optionVariants, ValuelessCallback callback)
You'd still very likely want to call Argengine::printHelp()
in the callback and just add some stuff around it.
The sorting order of options can be selected with:
void Argengine::setHelpSorting(HelpSorting helpSorting)
The text printed before options can be set with:
void Argengine::setHelpText(std::string helpText)
Valueless options are options without any value, so they are just flags. The lambda callback is of the form [] {}
.
#include "argengine.hpp"
#include <iostream>
int main(int argc, char ** argv)
{
juzzlin::Argengine ae(argc, argv);
ae.addOption({"-f", "--foo"}, [] {
std::cout << "'-f' or '--foo' given!" << std::endl;
});
ae.parse();
return 0;
}
Single-value options are options that can have only one value.
As an example, for option -f
The following formats are allowed: -f 42
, -f42
, -f=42
.
Preferably there should be either space or '=
'. The spaceless format is accepted if not ambiguous.
The lambda callback is of the form [] (std::string value) {}
.
#include "argengine.hpp"
#include <iostream>
int main(int argc, char ** argv)
{
juzzlin::Argengine ae(argc, argv);
ae.addOption({"-f", "--foo"}, [] (std::string value) {
std::cout << "Value for '-f' or '--foo': " << value << std::endl;
});
ae.parse();
return 0;
}
In order to mark an option mandatory, there's an overload that accepts bool required
right after the callback:
...
juzzlin::Argengine ae(argc, argv);
ae.addOption({"-f", "--foo"}, [] {
// Do something
},
true); // Required
...
One can add option documentation for the auto-generated help.
Consider this example code:
...
juzzlin::Argengine ae(argc, argv);
ae.addOption(
{ "-p" }, [](std::string value) {
std::cout << value.size() << std::endl;
},
true, "Print length of given text. This option is required.", "TEXT");
...
Here we have added a documentation string Print length of given text. This option is required.
and also a variable name TEXT
for option -p
.
Thus, this will generate a help content like this:
Options:
-h, --help Show this help.
-p [TEXT] Print length of given text. This option is required.
Sometimes we want to prevent the user from giving a certain set of arguments.
Consider this example code:
...
juzzlin::Argengine ae(argc, argv);
ae.addOption(
{ "foo" }, [] {
std::cout << "Foo enabled!" << std::endl;
});
ae.addOption(
{ "bar" }, [] {
std::cout << "Bar enabled!" << std::endl;
});
// Set "foo" and "bar" as conflicting options.
ae.addConflictingOptions({ "foo", "bar" });
...
Now, if we give both foo
and bar
to the application, we'll get an error like this:
Argengine: Conflicting options: 'bar', 'foo'. These options cannot coexist.
Option groups will make the given options depend on each other.
Consider this example code:
...
juzzlin::Argengine ae(argc, argv);
ae.addOption(
{ "foo" }, [] {
std::cout << "Foo enabled!" << std::endl;
});
ae.addOption(
{ "bar" }, [] {
std::cout << "Bar enabled!" << std::endl;
});
// Add "foo" and "bar" as an option group.
ae.addOptionGroup({ "foo", "bar" });
...
Now, if we give only foo
to the application, we'll get an error like this:
Argengine: These options must coexist: 'bar', 'foo'. Missing options: 'bar'.
For error handling there are two options: exceptions or error value.
Argengine::parse()
will throw on error, Argengine::parse(Error & error)
will set and error.
Example of handling exceptions:
...
try {
ae.parse();
} catch(std::runtime_error & e) {
std::cerr << e.what() << std::endl;
ae.printHelp();
return EXIT_FAILURE;
}
...
Example of handling error values:
...
Argengine::Error error;
ae.parse(error);
if (error.code != Argengine::Error::Code::Ok) {
std::cerr << error.message << std::endl
<< std::endl;
ae.printHelp();
return EXIT_FAILURE;
}
...
C++17
MIT