Introduction
arparse-cpp is a single header only library for parsing command line arguments in C++. It is also ideal for creating various CLI tools thanks to its clean and comprehensible API.
Contributing
argparse-cpp is free and open source. You can find the source code here. Issues and feature requests can be posted on the GitHub issue tracker. If you'd like to contribute, consider opening a pull request.
License
The whole argparse-cpp project is released under MIT License.
Installation
There are different ways of installing the library. Choose whatever you think is the best for you.
Using conan package manager
Starting from release v0.1.0 it is possible to use conan in order to install the library from the artifactory remote
For these steps to work you will need both conan and CMake. Check their respective installation guides for further information on how to get those tools.
-
Create a simple conanfile.txt in the root of your project containing the requirements
[requires] argparse-cpp/0.1.0@dead/stable [generators] cmake
-
Move into the build folder
$ cd <build-folder>
-
Add the artifactory remote to your conan client
$ conan remote add argparse-cpp https://argparsecpp.jfrog.io/artifactory/api/conan/argparse-cpp-conan-local
-
Run
conan install
$ conan install .. -r argparse-cpp
-
Configure cmake to use the installed library from conan
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup(NO_OUTPUT_DIRS TARGETS) ... target_link_libraries(your_exe PUBLIC CONAN_PKG::argparse-cpp)
Download source code from latest release
Click on the releases header on the right side of the GitHub repo or navigate here
Scroll down to the assets section and download the source code (zip or tar.gz).
Downloading the header file
Get the header file through wget.
$ wget https://raw.githubusercontent.com/dead-tech/argparse-cpp/main/include/argparse/argparse.hpp
Cloning the repo
-
Clone the repo
$ git clone https://github.com/dead-tech/argparse-cpp.git
-
Change current directory to the cloned folder
$ cd argparse-cpp
-
Move the header file
$ mv include/argparse.hpp <your-project-include-path>
Running the tests
This project uses Catch2 testing framework. You can find the whole test suite here.
If you would like to, you can run the test suite yourself by following the instructions below.
-
Clone the repository.
$ git clone https://github.com/dead-tech/argparse-cpp
-
Change directory to the repository root.
$ cd argparse-cpp
-
Create a build folder and cd into it.
$ mkdir build && cd build
-
Install the conan dependecies.
$ conan install ..
If this does not work for you checkout the installation section for further instructions.
-
Configure cmake and compile the tests
$ cmake .. && make
You can also specify a number of jobs as an argument to the
make
command to speed up the compilation process like somake -j5
. -
Run the tests
$ ./argparse-cpp_tests
Currently all the tests are expected to:
Basic Example
Simple example program that emulates an hypothetically compiler CLI. You may use this to get a general idea of what it would like to crete an application using this library.
#include <argparse/argparse.hpp>
#include <fstream>
#include <iostream>
#include <sstream>
int main(int argc, const char **argv)
{
argparse::ArgumentParser parser(argc, argv);
parser.add_argument("files")
.set_type(argparse::ArgTypes::STRING)
.set_default("test.txt")
.set_help("Paths to the files to compile")
.set_flags(argparse::ArgFlags::REQUIRED)
.set_metavar("FILE_PATH")
.set_nargs('+');
parser.add_argument("--release", "-R")
.set_type(argparse::ArgTypes::BOOL)
.set_help("Build in release version");
const auto args = parser.parse_args();
const auto files = args.at("files").as<std::vector<std::string>>();
const auto is_release = args.at("--release").as<bool>();
const auto result = build_files(files, is_release);
}
After compiling the program can ben run at the command line as follows:
$ ./program_name args...
Note: You will get automatically generated help and version optional arguments that will respectively produce the following output
Version optional argument:
$ ./main --version
0.0.1
Help optional argument:
$ ./main --help
usage: ./main [-H] [--release --RELEASE] files FILE_PATH
required arguments:
files FILE_PATH Paths to the files to compile
optional arguments:
-H, --help show this help message and exit
--release, -R --RELEASE Build in release version
When run with the appropriate arguments it should print the following output:
$ ./main files.txt bar.json --release
Built files:
files.txt bar.json
ArgumentParser objects
Function signature
class ArgumentParser
{
public:
ArgumentParser(const int argc, const char **argv, std::string version = "0.0.1");
}
The constructor creates a new ArgumentParser
object. All the parameters except for the version are required. Each of the parameters is described more in detail below.
argc
- The number of arguments passed to the programargv
- The array of arguments passed to the programversion
- The version of the program (optional - defaults to "0.0.1")
Detail
argc
This parameter is used to determine the number of arguments passed to the program. Not only in C++ there is no practical way of eliminating this parameter from the function signature, but this makes it also convenient to test the library.
argv
This parameter represent the array of arguments passed to the program. As for argc not only in C++ there is no practical way of eliminating this parameter from the function signature, but this makes it also convenient to test the library or perhaps providing your own arguments for whatever reason.
version
This parameter is used to provide a custom version of the program. If it is left blank it will default to "0.0.1". Note that the library will automatically generate an optional argument, as a builtin, for displaying the version of the program on command.
The builtin can be invoked with the --version
or -V
argument as shown below.
$ ./program_name --version
0.0.1
The add_argument() method
Function signature
template<utils::StringLike... Names>
mapped_type &add_argument(Names &&...names);
-
utils::StringLike
- A C++20 concept that allows you to pass any typeT
which is convertible to astd::string
-
Names &&...names
- A variadic pack ofutils::StringLike
s containing all the names for the argument, including the primary name and the optional aliases -
mapped_type &
- The return type of the method which is a reference to the argument object. By returning a non-const
reference to the newly created argument object, you can modify the argument object by using the object chaining method, which our API is based on. mapped_type is just an aliased type:using map_type = std::unordered_map<std::string, Arg>; using mapped_type = map_type::mapped_type;
Constraints
Important: As shown by the static_assert
below you must provide at least one argument as a name for the argument itself. If you're trying to register an optional argument one of the names must also start with a --
. The latter name will be used as the primary name of the argument, for example to index and retrieve the argument in the internal storage. Whereas for positional arguments you should pass only one argument, the others will be discard and only the first one will be used.
static_assert(
sizeof...(Names) > 0,
"[argparse] error: add_argument() needs at least one argument as a name (starting with '--' for "
"positional arguments)");
Runtime dispatch based on argument kind
As we have briefly mentioned above the same method can be used to create both optional and positional arguments. The system will automatically make its way between the two and dispatch to the appropriate method.
if (arg_kind == ArgKind::Positional) {
return this->add_positional_argument(data);
} else if (arg_kind == ArgKind::Optional) {
return this->add_optional_argument(data, primary_name.value());
}
The parse_args() method
Function signature
[[nodiscard]] map_type parse_args();
Explanation of how it works
map_type
is equivalent tostd::unordered_map<std::string, Arg>
- The function will immediately create the usage and help messages to be display on command
this->create_usage_message(); this->create_help_message();
- The function will also before even parsing the command line arguments check if any builtins are present and if so, execute them
if (const auto builtin = this->get_builtin_if(); builtin.has_value()) { const auto fn = builtin.value(); fn(); exit(0); }
- The function will then split the program arguments into positional and optional arguments
const auto [positional_args, optional_args] = this->split_program_args();
- The function will then parse the positional arguments
this->parse_positional_args(positional_args);
- The function will then throw if a not registered optional argument is found in the program args
this->throw_if_unrecognized(optional_args); this->parse_optional_args(optional_args);
Whatever is returned from the function can be then accessed through the .at() method and then cast to the appropriate type as it is explained here.
Exceptions
Be also aware that this function may throw an exception in one of these cases:
- If not enough positional arguments were provided
- If an optional argument that is not registered was provided
NOTE: The return type of the method is marked as [[nodiscard]]
which means that the result of the call to this method cannot be ignored and the value has necessarily to be stored in a variable.
set_type()
Rationale
This method allows you to set the type of type of the argument that has to be parsed from the command line arguments.
It is used to parse differently based on the type of the argument. For example, boolean arguments do not expect after their name any kind of value, whereas string or int arguments do.
List of supported argument types
It is worth be aware of that if you do not call set_type()
, on a Arg
instance, the type of the argument will default to argparse::ArgTypes::STRING
.
enum class ArgTypes
{
STRING = 0,
INT,
BOOL
};
Example usage
parser.add_argument("--string", "-S").set_type(argparse::ArgTypes::STRING);
parser.add_argument("--int", "-I").set_type(argparse::ArgTypes::INT);
parser.add_argument("--bool", "-B").set_type(argparse::ArgTypes::BOOL);
Source Code
Arg &set_type(const ArgTypes &type)
{
this->type = type;
return *this;
}
set_flags()
Rationale
This method allows you to set some flags for the argument that has to be parsed from the command line arguments.
We currently use a bitmask to encode all the various flags to ease the usage.
List of flags you may specify
It is worth be aware of that if you do not call set_flags()
, on a Arg
instance, the type of the argument will default to argparse::ArgFlags::DEFAULT
which is only STORE_TRUE
at the moment as shown below.
enum class ArgFlags : int64_t
{
NONE = 0,
REQUIRED = (1LL << 1),
STORE_TRUE = (1LL << 2),
STORE_FALSE = (1LL << 3),
DEFAULT = STORE_TRUE,
};
Example usage
parser.add_argument("--release", "-R")
.set_flags(argparse::ArgFlags::REQUIRED | argparse::ArgFlags::STORE_TRUE);
Source Code
Arg &set_flags(const ArgFlags &flags)
{
this->flags = flags;
return *this;
}
set_help()
Rationale
This method allows you to set a custom help message that has to be printed next to the argument name when invoking the --help
builtin.
Example usage
parser.add_argument("--release", "-R").set_help("Set build process in release mode");
Source Code
Arg &set_help(const std::string &help_message)
{
this->help_message = help_message;
return *this;
}
set_default()
Rationale
This method allows you to set a default value for an argument. This value will be used if the user does not provide a value for the argument.
NOTE: Be aware that if a required argument is not provived the default value will be used and no error will be thrown.
IMPORTANT: Executing a builting command such as --help
or --version
will quit the program with zero exit code.
Example usage
parser.add_argument("--jobs", "-J").set_default(1);
Source Code
template<SupportedArgumentType T>
Arg &set_default(T &&value)
{
if constexpr (std::is_convertible_v<T, std::string>) {
this->values.front() = std::forward<T>(value);
} else if constexpr (std::is_same_v<T, bool>) {
this->values.front() = utils::bool_to_str(std::forward<T>(value));
} else {
this->values.front() = std::to_string(std::forward<T>(value));
}
return *this;
}
This templated function is restricted in its types by the concept SupportedArgumentTypes
. The latter allows you to pass as an argument to the call just the types that are supported by the library (see set_type() for more info).
Moreover the function uses C++17 if constexpr
to generate at compile time the right branch of the if
statement, reducing the overhead during runtime.
set_metavar()
Rationale
This method allows you to specify a custom metavar. A metavar is the string it is printed next to the argument name when invoking the --help
builtin that helps you understand where you have to specify the value of the argument.
Example: program_name --out output_file
In the example above output_file is the metavar.
Example usage
parser.add_argument("--out", "-O").set_metavar("output_file");
Source Code
Arg &set_metavar(const std::string &metavar)
{
this->metavar = metavar;
return *this;
}
set_nargs()
Rationale
This method allows you to set how many values are expected for an argument.
Possible values
You may specify as an argument to this function one of the following values:
- x -> any integer number
- * -> which means at zero or more values
- + -> which means at least one or more values
Example usage
parser.add_argument("files").set_nargs('*');
Source Code
Arg &set_nargs(const auto nargs)
{
static constexpr auto is_number = std::is_same_v<decltype(nargs), const int>;
static constexpr auto is_symbol = std::is_same_v<decltype(nargs), const char>;
if constexpr (is_number) {
this->nargs = nargs;
} else if constexpr (is_symbol) {
this->nargs = nargs - '0';
} else {
throw exceptions::ArgparseException(
std::source_location::current(),
"set_nargs() error: unsupported type: %\nSupported types are: int, std::string\n",
typeid(nargs).name());
}
return *this;
}
Moreover the function uses C++17 if constexpr
to generate at compile time the right branch of the if
statement, reducing the overhead during runtime.
Note: Be aware that the function will throw an exception if the argument is not a number or one of the supported symbols symbol.
count()
Rationale
This method allows you to count the occurence of an argument in the command line arguments.
Example usage
parser.add_argument("--verbose").count();
Source Code
Arg &count()
{
this->count_occurence = true;
this->type = ArgTypes::BOOL;
return *this;
}
as()
Rationale
This method allows you to cast the Arg
value (a.k.a. std::string
) you get when invoking the at()
method on the return value of the parse_args()
method (more info) to the type of your needs.
List of supported types
int
-std::is_integral_v<T>
- actually every integral typestd::vector<int>
std::string
std::vector<std::string>
bool
Example usage
const auto args = parser.parse_args();
const auto value = args.at('--value').as<int>();
Source Code
template<typename ReturnType>
[[nodiscard]] decltype(auto) as() const
{
static constexpr auto is_int = std::is_integral_v<ReturnType> && !std::is_same_v<ReturnType, bool>;
static constexpr auto is_vec_int = std::is_same_v<ReturnType, std::vector<int>>;
static constexpr auto is_string = std::is_same_v<ReturnType, std::string>;
static constexpr auto is_vec_string = std::is_same_v<ReturnType, std::vector<std::string>>;
static constexpr auto is_bool = std::is_same_v<ReturnType, bool>;
if constexpr (is_int) {
return utils::impl::str_to_int_helper(this->values.front());
} else if constexpr (is_vec_int) {
return utils::str_to_int(this->values);
} else if constexpr (is_string) {
return values.front();
} else if constexpr (is_vec_string) {
return std::vector(values.data(), values.data() + this->actual_size);
} else if constexpr (is_bool) {
return utils::str_to_bool(this->values.front());
}
throw exceptions::ArgparseException(
std::source_location::current(),
"as<%>() error: unsupported return type\nSupported types are: int, std::vector<int>, std::string, "
"std::vector<std::string>, bool\n",
typeid(ReturnType).name());
}
The function uses C++17 if constexpr
to generate at compile time the right branch of the if
statement, reducing the overhead during runtime.
NOTE: If the type passed as a template argument to this method is not one of the supported types, the function will throw an exception.