The basics¶
opzioni is made up of simple concepts: programs and arguments.
A program is either the root or is a command of another program.
Meanwhile, arguments describe the inputs that a program expects from the user.
In code, programs are represented by Program
and arguments by Arg
.
Including opzioni¶
Including opzioni is as simple as include <opzioni.hpp>
(or in quotes if building as an example).
For instance:
#include <string_view>
#include <fmt/format.h>
#include "opzioni.hpp"
int main(int argc, char const *argv[]) {
using namespace opzioni;
}
The using
directive for the opzioni
namespace is just to make things easier for now.
You can always import the desired names individually later.
Also, I'm using the awesome fmt library. You should check it out!
Declaring a program¶
In order to create a Program
, we call one of its two constructors:
one just takes a name; the other takes a name and a title:
constexpr auto curl = Program("curl", "transfer a URL");
Note that it is marked constexpr
, so it means that curl
is a constant expression, that is, its value is known at compile-time.
Adding arguments¶
An argument can be one of three types: positional, option, or flag.
These types are created via the functions Pos()
, Opt()
, and Flg()
, respectively.
Pos()
only takes a name, while Opt()
and Flg()
take a name and an abbreviation.
These functions are just helpers, since they all construct an Arg
, but they set the appropriate values for the type of argument being created.
These defaults are:
- positionals and options are initially of type
std::string_view
- flags are initially of type
bool
- positionals are required
- options and flags are optional
- options have
""
as default value - flags have
false
as default value
Although possible, if the constructor of Arg
were to be called, the argument type and all these defaults would have to be manually specified, requiring more typing.
Arg
is an aggregate, though, which eases things a bit.
Arguments are added to a program via its add
member function. Let's add some to our curl
example:
constexpr auto curl =
Program("curl", "transfer a URL")
.add(Pos("url").help("The URL to transfer"))
.add(Opt("request", "X").help("The HTTP method to use"))
.add(Flg("verbose", "v").help("Make the operation more talkative"))
.add(Help());
Since we get an Arg
after calling one of the argument factories, we can further customize it with its member functions.
The first one we used here is .help()
, which serves the purpose of setting descriptions to arguments.
These descriptions are what appears in the automatic help text that opzioni generates.
To tell opzioni that automatic help text is desired, we simply add a Help()
.
Similar to the previous functions, it's just a helper, but this time to create a very common flag, which is to show the program help.
Parsing the CLI¶
Now that our very simple curl
has some arguments, we can try parsing the CLI and see what we get.
We just need to call curl
with argc
and argv
:
auto const map = curl(argc, argv);
The result is a map of arguments of what was parsed from the CLI.
Note that map
is not constexpr
, since its value depends on runtime information.
opzioni also provide automatic error handling when calling the call operator of Program
.
This means that, if the user gives curl
some invalid arguments, the default behavior is to terminate the program and show an error message alongside the usage of the program.
This is explained later, but this behavior can be changed either while keeping the automatic error handling or not.
Getting the results¶
There are a few ways of getting the results out of the map:
std::string_view const url = map["url"]; // 1
auto const url = map.as<std::string_view>("url"); // 2
auto const url = map["url"].as<std::string_view>(); // 3
Choose whichever you like most.
However, it is always the long name of the argument that is used to get the result from the map.
So, for example, to get the value of the request
option, it's always via the name request
, never X
.
Now let's print them out!
std::string_view const url = map["url"];
std::string_view const request = map["request"];
bool const verbose = map["verbose"];
fmt::print("url: {}\n", url);
fmt::print("request: {}\n", request);
fmt::print("verbose: {}\n", verbose);
Full C++ code¶
#include <string_view>
#include <fmt/format.h>
#include "opzioni.hpp"
int main(int argc, char const *argv[]) {
using namespace opzioni;
constexpr auto curl =
Program("curl", "transfer a URL")
.add(Pos("url").help("The URL to transfer"))
.add(Opt("request", "X").help("The HTTP method to use"))
.add(Flg("verbose", "v").help("Make the operation more talkative"))
.add(Help());
auto const map = curl(argc, argv);
std::string_view const url = map["url"];
std::string_view const request = map["request"];
bool const verbose = map["verbose"];
fmt::print("url: {}\n", url);
fmt::print("request: {}\n", request);
fmt::print("verbose: {}\n", verbose);
}
Building as an example¶
-
Put the code above in a file in the
examples/
directory, say,curl.cpp
. -
Add an
executable
entry for it inexamples/meson.build
, just like the other examples.curl = executable( 'curl', 'curl.cpp', dependencies: [fmt_dep, opzioni_dep] )
-
Run
make build
-
Test your new CLI!
See the automatic help:
./build/examples/curl --help
Test that URL is really required by giving no arguments to the executable:
./build/examples/curl
Give it a valid set of arguments and see the output:
./build/examples/curl -X GET google.com
Supported formats of arguments¶
There are many ways in which a user might provide arguments to a program. The following list contains the rules that opzioni follows when parsing them.
-
Arguments that don't start with
-
or are just a single-
are considered positionals.Example:
rm file.txt
, wherefile.txt
is a positional argument ofrm
. -
Commands are a special case of positional arguments in which they exactly match the name of some program added as command.
Example:
git clone <clone args...>
, whereclone
is a command ofgit
. -
An argument starting with a single
-
followed by one or more letters can be one short flag or many short flags together if all characters match the abbreviation of existing flags; or a short option immediately followed by its value if the first character matches the abbreviation of an existing option. In the case of an option, the value may be optionally preceded by=
or it can be the next argument in the command-line.Example:
-
pip -h
, whereh
is a flag ofpip
. -
tar -vxz
, wherev
,x
, andz
are three flags oftar
. -
g++ -O2
org++ -O=2
, whereO
is an option ofg++
and2
is its value.Note that both
-O2
and-O=2
are a single argument. -
curl -X GET
, whereX
is an option ofcurl
andGET
is its value.This is a special case in which the argument (
-X
) is an existing option and the argument that immediately follows it would be parsed as a positional argument.
Passing arguments like
set -euxo pipefail
is intentionally not supported. The format-abc
always means many flags together or an optiona
followed by its valuebc
. -
-
An argument starting with two
-
followed by any number of characters is either a flag or an option. it is considered a flag If the name exactly matches an existing flag, otherwise it is considered an option. In the case of an option, the value may be optionally preceded by=
or it can be the next argument in the command-line.Example:
-
pip --help
, wherehelp
is a flag ofpip
. -
wget --timeout=10
, wheretimeout
is an option ofwget
and10
is its value.Note that
--timeout=10
is a single argument and--timeout10
would not work the same way (it would be parsed as a flag). -
wget --user-agent Firefox
, whereuser-agent
is an option ofwget
andFirefox
is its value.Similarly to short flags and options, this is a special case in which the argument (
--user-agent
) is an existing option and the argument that immediately follows it would be parsed as a positional argument.
-
-
An argument that is exactly
--
indicates that everything that follows should always be parsed as positional arguments.