Argos is a C++ library providing classes for declaring and parsing a program's command line interface, as well as displaying a useful help text and helpful error messages. It is intended to be easy to use, yet support advanced command line interfaces.
Its features include:
- Automatic formatting of help texts to fit the current terminal's width.
- Support for sub-commands with their own options and arguments (like
git commitordocker run). - A flexible parser that supports arguments of many different types like for instance
int,unsigned long long,floatanddouble. - Takes care of most of the command line-related error handling automatically.
- Three different option styles:
- a combination of short,
-o, and long flags,--option, where multiple short options can be concatenated after a single dash. - Windows options: flags starting with a slash
/OPTION, case sensitivity can be turned on and off. - Dash options: arbitrary flags preceded by a single dash.
- a combination of short,
- More powerful features like linking two or more options, e.g.
--quietand--verbose, are trivial with aliases and callbacks. - Tested on Linux, macOS and Windows.
The documentation for Argos can be found at barsken.no/apidoc/argos.
Argos is made available under the Zero-Clause BSD licence.
The source code can be cloned or forked on GitHub: github.com/jebreimo/Argos.
Argos is self-contained. It has no dependencies apart from a compiler that supports C++17 (
with <string_view>, <variant>, <optional>, structured bindings etc.). It has been tested with
recent versions gcc (9.3.0), clang (AppleClang 12) and msvc (19.29).
The following is a simple "hello world" program using Argos.
#include <iostream>
#include <Argos/Argos.hpp>
int main(int argc, char* argv[])
{
const argos::ParsedArguments args = argos::ArgumentParser("hello")
.about("Displays a greeting to someone or something.")
.add(argos::Argument("NAME").optional(true)
.help("The person or thing to greet."))
.add(argos::Option{"-n", "--number"}.argument("NUM")
.help("The number of times to repeat the greeting."))
.parse(argc, argv);
int n = args.value("--number").as_int(1);
for (int i = 0; i < n; ++i)
{
std::cout << "Hello "
<< args.value("NAME").as_string("world")
<< "!\n";
}
return 0;
}By default it displays "Hello world!", but it also takes an optional argument, NAME, which
replaces world when it is provided (the complete example including build files can be found in
the Argos repository.
~/Argos/examples/tutorial/build $ ./hello
Hello world!
~/Argos/examples/tutorial/build $ ./hello planet
Hello planet!
~/Argos/examples/tutorial/build $ ./hello -n 2
Hello world!
Hello world!
~/Argos/examples/tutorial/build $ ./hello -h
USAGE
hello --help
hello [-n <NUM>] [<NAME>]
Displays a greeting to someone or something.
ARGUMENTS
[<NAME>] The person or thing to greet.
OPTIONS
-n <NUM>, --number <NUM> The number of times to repeat the greeting.
-h, --help Display the help text.
The Argos interface revolves around two main classes: ArgumentParser and ParsedArguments. The
ArgumentParser class is responsible for defining arguments and options, as well as setting various
configuration parameters. For example, you can use ArgumentParser::option_style() to switch to
Windows-style options or ArgumentParser::allow_abbreviated_options() to allow shortened option
flags, provided they uniquely identify an option.
The ParsedArguments class represents the outcome of calling ArgumentParser::parse(). This is where
you retrieve the values of arguments and options after parsing.
To define arguments and options, you add instances of Argument and Option to ArgumentParser. The
Argument constructor takes a name that appears in the help text, while the Option constructor
accepts a list of flags. Both classes include several properties that control their behavior;
important ones include help, which sets the help text, and argument, which specifies the name of an
option’s argument.
In contrast to some other argument parsers, Argos does not require you to declare value types and
default values with the arguments and options in ArgumentParser. Instead, you specify these details
when you retrieve the values.
int n = args.value("--number").as_int(1);converts the value of the --number option to int and assigns it to n (all values are stored as
strings internally). If the option was not given on the command line, the provided default
value, 1, is assigned to n instead. value("-n") could also have been used in place
of value("--number").
Similarly,
args.value("NAME").as_string("world")returns the value of the argument NAME as a std::string, or "world" if it wasn't given.
Running the program with one of the two auto-generated help options, -h and --help, displays the help text.
The help text looks a little bit sparse when there are so few arguments and options as in the above example, but the empty
lines will start to make more sense when there are a more significant number of options and
arguments, here is for instance the help message for the whereis example (
in examples/whereis):
~/Argos/examples/whereis/build $ ./whereis -h
USAGE
whereis --help
whereis --version
whereis [-p <PATH>[:<PATH>]...] [-e <EXT>[:<EXT>]...] [--] [-q] [-v]
<FILE> [<FILE>]...
Searches the directories in the PATH environment variable for the given
file (or files).
ARGUMENTS
<FILE> [<FILE>]...
The file or files to locate.
MAIN OPTIONS
-p <PATH>[:<PATH>]..., --paths <PATH>[:<PATH>]...
Search the given path or paths rather than the ones in the PATH
environment variable. Use : as separator between the different
paths.
-e <EXT>[:<EXT>]..., --extensions <EXT>[:<EXT>]...
File name extensions to test while looking for FILE. Must include
the leading '.'. This option can be used multiple times, multiple
extensions can be set at once by separating them with ':'.
--
Marks the end of the options. Makes it possible to look for file
names starting with dashes ('-').
OTHER OPTIONS
-q, --quiet
Do not show additional information (negates --verbose).
-v, --verbose
Show additional information.
-h, --help
Display the help text.
--version
Display the program version.
Running the program with any other flag, or incorrect number of arguments, results in an error message followed by the USAGE section of the help text:
~/Argos/examples/tutorial/build $ ./hello --option
hello: Unknown option: --option
USAGE
hello --help
hello [<NAME>]
~/Argos/examples/tutorial/build % ./hello planet Earth
hello: Too many arguments, starting from "Earth".
USAGE
hello --help
hello [<NAME>]
~/Argos/examples/tutorial/build % ./hello -n foo
hello: -n, --number: Invalid value: foo.
USAGE
hello --help
hello [-n <NUM>] [<NAME>]
Notice how there is no error handling code in the program itself, everything is taken care of by
Argos. After Argos has displayed the error message (or help message) it calls exit() to end the
program. The automatic exit is convenient in many cases, but when this is undesired, it can easily
be turned off with
const argos::ParsedArguments args = argos::ArgumentParser("hello")
.auto_exit(false)
...Argos supports sub-commands, like git commit or docker run. The following is a simple example
of a program with two sub-commands, add and remove, each with their own options and arguments.
#include <iostream>
#include <Argos/Argos.hpp>
int main(int argc, char* argv[])
{
auto args = argos::ArgumentParser("subcommands")
.about("A program with sub-commands.")
.add(argos::Command("add")
.help("Add a file to the repository.")
.add(argos::Argument("FILE")
.help("The file to add."))
.add(argos::Option("-f", "--force")
.help("Add the file even if it is ignored."))
)
.add(argos::Command("remove")
.help("Remove a file from the repository.")
.add(argos::Argument("FILE")
.help("The file to remove."))
.add(argos::Option("-f", "--force")
.help("Remove the file even if it is staged."))
)
.parse(argc, argv);
// Get the arguments and options of the sub-command.
// It is safe to assume that there is exactly one sub-command, as the
// parser will have exited with an error message if there were none or
// more than one.
auto cmd = args.subcommands().front();
if (cmd.name() == "add")
{
std::cout << "Adding " << cmd.value("FILE").as_string() << ".\n";
}
else if (cmd.name() == "remove")
{
std::cout << "Removing " << cmd.value("FILE").as_string() << ".\n";
}
return 0;
}There are several more examples on how to use Argos in the examples folder.
Building the program, or any program using Argos, can be done in multiple ways. My preferred way in CMake-based projects is to use the FetchContent module to automatically download Argos from its GitHub repository. For the hello program, the CMakeLists.txt would have to contain the following:
cmake_minimum_required(VERSION 3.14)
project(hello)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
FetchContent_Declare(argos
GIT_REPOSITORY "https://github.com/jebreimo/Argos.git"
GIT_TAG "master"
)
FetchContent_MakeAvailable(argos)
add_executable(hello hello.cpp)
target_link_libraries(hello
PRIVATE
Argos::Argos
)Alternatively one can clone the git repository and build and install it with CMake, for instance:
git clone https://github.com/jebreimo/Argos.git
mkdir Argos/build
cd Argos/build
cmake -DCMAKE_BUILD_TYPE=Release ..
make installWith CMake one can then
use find_package(Argos) and the same target_link_libraries as in the example above.
If one doesn't care about keeping a link to the Argos git repository, but just want to copy the Argos files
into the current project along with all the other files, there is a special two-file version of
Argos in the single_source directory, consisting of just Argos.hpp and Argos.cpp. They are the
concatenations of the files in the include and src respectively.