Skip to content

Latest commit

 

History

History
1619 lines (1203 loc) · 59.9 KB

File metadata and controls

1619 lines (1203 loc) · 59.9 KB

Tutorial


Important

This tutorial covers the most common use cases and features of the library. For more in-depth information and advanced usage, please refer to the full documentation. Instructions for building the documentation are available in the Dev Notes page.


Setting Up CPP-ARGON

CMake Integration

For CMake projects you can simply fetch the library in your CMakeLists.txt file:

cmake_minimum_required(VERSION 3.12)

project(my_project LANGUAGES CXX)

# Include FetchContent module
include(FetchContent)

# Fetch CPP-ARGON library
FetchContent_Declare(
    cpp-argon
    GIT_REPOSITORY https://github.com/SpectraL519/cpp-argon.git
    GIT_TAG master # here you can specify the desired tag or branch name (use `master` for the latest version)
)

FetchContent_MakeAvailable(cpp-argon)

# Define the executable for the project
add_executable(my_project main.cpp)

set_target_properties(my_project PROPERTIES
    CXX_STANDARD 20
    CXX_STANDARD_REQUIRED YES
)

# Link against the cpp-argon library
target_link_libraries(my_project PRIVATE cpp-argon)

Bazel Build System

To use the CPP-ARGON in a Bazel project add the following in the MODULE.bazel (or WORKSPACE.bazel) file:

git_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")

git_repository(
    name = "cpp-argon",
    remote = "https://github.com/SpectraL519/cpp-argon.git",
    tag = "<version-name>" # here you can declare the desired CPP-ARGON version
)

Important

CPP-ARGON (CPP-AP) versions older than 2.5.0 do not support building with Bazel.

And then add the "@cpp-argon//:cpp-argon" dependency for the target you want to use CPP-ARGON for by adding it to the deps list. For instance:

# BUILD.bazel
cc_binary(
    name = "my_app",
    srcs = ["application.cpp"],
    includes = ["include"],
    deps = ["@cpp-argon//:cpp-argon"],
    cxxopts = ["-std=c++20"],
    visibility = ["//visibility:public"],
)

Downloading the Library

If you do not use CMake, you can download the desired library release, extract it in a desired directory and simply add <cpp-argon-dir>/include to the include directories of your project.




The Parser Class

To use the argument parser in your code you need to use the argon::argument_parser class.

argon::argument_parser parser("program");
parser.program_version("alpha")
      .program_description("Description of the program")
      .verbose();

Important

  • When creating an argument parser instance, you must provide a program name to the constructor.

    The program name given to the parser cannot be empty and must not contain whitespace characters.

  • Additional parameters you can specify for a parser instance include:

    • The program's version and description - used in the parser's configuration output (std::cout << parser).
    • Verbosity mode - false by default; if set to true the parser's configuration output will include more detailed info about arguments' parameters in addition to their names and help messages.
    • Arguments - specify the values/options accepted by the program.
    • Argument Groups - organize related optional arguments into sections and optionally enforce usage rules.
    • The unknown argument flags handling policy.

Tip

You can specify the program version using a string (like in the example above) or using the argon::version structure:

parser.program_version({0u, 0u, 0u})
parser.program_version({ .major = 1u, .minor = 1u, .patch = 1u });
argon::version ver{2u, 2u, 2u};
parser.program_version(ver);

NOTE: The argon::version struct

  • contains the three members - major, minor, patch - all of which are of type std::uint32_t,
  • defines a std::string str() const method which returns a v{major}.{minor}.{path} version string,
  • defines the std::ostream& operator<< for stream insertion.



Adding Arguments

The parser supports positional and optional arguments.

Note

The general rules for parsing arguments are described in the Parsing arguments section.

Syntax

To add an argument, use:

parser.add_positional_argument<value_type>("name");
parser.add_optional_argument<value_type>("name");

For optional arguments, you may also specify a secondary (short) name:

parser.add_optional_argument<value_type>("name", "n")

or use only the secondary name:

parser.add_optional_argument("n", argon::n_secondary);

Names

  • Positional arguments must have exactly one name (no secondary/short names allowed).
  • Optional arguments can have:
    • only a primary (long) name,
    • only a secondary (short) name, or
    • both a primary and a secondary name.

Value Types

Important

An argument's value type must be argon::none_type or satisfy all of the following requirements:

  • Constructible from const std::string& or overload std::istream& operator>>.
    • The parser will always try direct initialization from std::string first, and only fall back to the extraction operator if direct initialization fails.
  • Satisfy the std::semiregular concept (default-initializable and copyable).

Note

Boolean Flags

Flags are essentially optional arguments with a boolean value type.

parser.add_flag("enable_some_option", "eso").help("enables option: some option");
/* equivalent to:
parser.add_optional_argument<bool>("enable_some_option", "eso")
      .default_values(false)
      .implicit_values(true)
      .nargs(0)
      .help("enables option: some option");
*/

By default, flags store true when parsed from the command-line. You can invert this behavior:

parser.add_flag<false>("disable_another_option", "dao").help("disables option: another option");
/* equivalent to:
parser.add_optional_argument<bool>("disable_another_option", "dao")
      .default_values(true)
      .implicit_values(false)
      .nargs(0)
      .help("disables option: another option");
*/



Argument Parameters

Common Parameters

Parameters which can be specified for both positional and optional arguments include:

1. help - The argument's description which will be printed when printing the parser class instance.

parser.add_positional_argument<std::size_t>("number", "n")
      .help("a positive integer value");

2. hidden - If this option is set for an argument, then it will not be included in the program description.

By default all arguments are visible, but this can be modified using the hidden(bool) setter as follows:

argon::argument_parser("hidden-test")
parser.program_description("A simple test program for argument hiding")
      .default_arguments(argon::default_argument::o_help);

parser.add_optional_argument("hidden")
      .hidden()
      .help("A simple hidden argument");
parser.add_optional_argument("visible")
      .help("A simple visible argument");

parser.try_parse_args(argc, argv);

/*
> ./hidden-test --help
Program: hidden-test

  A simple test program for argument hiding

Optional arguments:

  --help, -h : Display the help message
  --visible  : A simple visible argument

3. required - If this option is set for an argument and it's value is not passed in the command-line, an exception will be thrown.

Important

  • By default positional arguments are set to be required, while optional arguments have this option disabled by default.
  • The default value of the value parameter of the required(bool) function is true for both positional and optional arguments.

Warning

  • If a positional argument is defined as non-required, then no required positional argument can be defined after (only other non-required positional arguments and optional arguments will be allowed).
  • If an argument is suppressing (see suppress arg checks and Suppressing Argument Group Checks), then it cannot be required (an exception will be thrown).
// example: positional arguments
parser.add_positional_argument("input");
parser.add_positional_argument("output").required(false);

parser.parse_args(argc, argv);

// input is guaranteed to have a value if parsing was successful
const auto data = read_data(parser.value("input"));

if (parser.has_value("output")) {
    std::ofstream os(parser.value("output"));
    os << data << std::endl;
}
else {
    std::cout << data << std::endl;
}

/*
Command                           Result
-----------------------------------------------------------------------------------------
./program                         Parsing error (no value for the input argument)
./program input.txt               Parsing success; Printing data to stdout
./program input.txt output.txt    Parsing success; Printing data to the `output.txt` file
// example: optional arguments
parser.add_optional_argument("input", "i").required();
parser.add_optional_argument("output", "o");

parser.parse_args(argc, argv);

// `input` is guaranteed to have a value if parsing was successful
const auto data = read_data(parser.value("input"));

if (parser.has_value("output")) {
    std::ofstream os(parser.value("output"));
    os << data << std::endl;
}
else {
    std::cout << data << std::endl;
}

/*
Command                                 Result
-----------------------------------------------------------------------------------------------
./program                               Parsing error (no value for the input argument)
./program --input input.txt             Parsing success; Printing data to stdout
./program -i input.txt -o output.txt    Parsing success; Printing data to the `output.txt` file
*/

4. suppress_arg_checks - Using a suppressing argument results in suppressing requirement checks for other arguments.

If an argument is defined with the suppress_arg_checks option enabled and such argument is explicitly used in the command-line, then requirement validation will be suppressed/skipped for other arguments. This includes validating whether:

  • a required argument has been parsed
  • the number of values parsed for an argument matches the specified nargs range.

Note

  • All arguments have the suppress_arg_checks option disabled by default.
  • The default value of the value parameter of the argument::suppress_arg_checks(bool) method is true for all arguments.

Warning

  • Enabling the suppress_arg_checks option has no effect on argument group requirements validation.
  • Enabling argument checks suppressing is not possible for required arguments (an exception will be thrown).
// example: optional arguments
parser.add_positional_argument("input");
parser.add_optional_argument("output", "o").required();
parser.add_optional_argument("version", "v").suppress_arg_checks();

parser.parse_args(argc, argv);

if (parser.count("version")) {
    std::cout << PROJECT_VERSION << std::endl;
    std::exit(EXIT_SUCCESS);
}

// may result in an `argon::argument_parser_exception`:
// `input` is not guaranteed to have a value at this point
const auto data = read_data(parser.value("input"));

// may result in an `argon::argument_parser_exception`:
// `output` is not guaranteed to have a value at this point
std::ofstream os(parser.value("output"));
os << data << std::endl;

5. nargs - Sets the allowed number of values to be parsed for an argument.

The nargs parameter can be set as:

  • Specific number:

    parser.add_optional_argument("input", "i").nargs(1);
  • Fully bound range:

    parser.add_optional_argument("input", "i").nargs(1, 3);
  • Partially bound range:

    parser.add_optional_argument("input", "i").nargs(argon::nargs::at_least(1));  // n >= 1
    parser.add_optional_argument("input", "i").nargs(argon::nargs::more_than(1)); // n > 1
    parser.add_optional_argument("input", "i").nargs(argon::nargs::less_than(5)); // n < 5
    parser.add_optional_argument("input", "i").nargs(argon::nargs::up_to(5));     // n <= 5
  • Unbound range:

    parser.add_optional_argument("input", "i").nargs(argon::nargs::any());

Important

The default nargs parameter value is:

  • argon::nargs::range(1ull) for positional arguments

  • argon::nargs::any() for optional arguments


6. greedy - If this option is set, the argument will consume ALL command-line values until it's upper nargs bound is reached.

Note

  • By default the greedy option is disabled for all arguments.
  • The default value of the parameter of the argument::greedy(bool) method is true for all arguments.

Tip

  • Enabling the greedy option for an argument only makes sense for arguments with string-like value types.
  • If no explicit nargs bound is set for a greedy argument, once parsing of such argument begins, it will consume all remaining command-line arguments.

Consider a simple example:

argon::argument_parser parser("run-script");
parser.default_arguments(argon::default_argument::o_help);

parser.add_positional_argument("script")
      .help("The name of the script to run");
parser.add_optional_argument("args")
      .greedy()
      .help("Set the execution option");

parser.try_parse_args(argc, argv);

// Application logic here
std::cout << "Executing: " << parser.value("script") << " " << argon::util::join(parser.values("args")) << std::endl;

Here the program execution should look something like this:

> ./run-script remove-comments --args module.py -v --type py
Executing: remove-comments module.py -v --type py

Notice that even though the -v and --type command-line arguments have flag prefixes and are not defined by the program, they are not treated as unknown arguments (and therefore no exception is thrown) because the --args argument is marked as greedy and it consumes these command-line arguments as its values.


7. choices - A list of valid argument values.

parser.add_optional_argument<char>("method", "m").choices('a', 'b', 'c');
// equivalent to: parser.add_optional_argument<char>("method", "m").choices({'a', 'b', 'c'});
// passing a value other than a, b or c for the `method` argument will result in an error

Important

  • The choices function can be used only if the argument's value_type is equality comparable (defines the == operator)
  • The choices function can be called with:

8. value actions - Functions that are called after parsing an argument's value.

Actions are represented as functions, which take the argument's value as an argument. The available action types are:

  • observe actions | void(const value_type&) - applied to the parsed value. No value is returned - this action type is used to perform some logic on the parsed value without modifying it.

    void is_valid_user_tag(const std::string& tag) {
        if (tag.empty() or tag.front() != '@')
            throw std::runtime_error(std::format("Invalid user tag: `{}` - must start with '@'", tag));
    }
    
    parser.add_optional_argument<std::string>("user", "u")
          .action<argon::action_type::observe>(is_valid_user_tag);
  • transform actions | value_type(const value_type&) - applied to the parsed value. The returned value will be used to initialize the argument's value.

    std::string to_lower(std::string s) {
        for (auto& c : s)
            c = static_cast<char>(std::tolower(c));
        return s;
    }
    
    parser.add_optional_argument<std::string>("key", "k")
          .action<argon::action_type::transform>(to_lower);
  • modify actions | void(value_type&) - applied to the initialized value of an argument.

    void capitalize(std::string& s) {
        s.at(0) = std::toupper(s.at(0));
    }
    
    parser.add_optional_argument<std::string>("name", "n")
          .action<argon::action_type::modify>(capitalize);

Tip

A single argument can have multiple value actions. Instead of writing complex logic in one action, consider composing several simple, focused actions for better readability and reusability.


9. default_values - A list of values which will be used if no values for an argument have been parsed

Warning

For both positional and optional arguments, setting the default_values parameter disables the required option.

// example: positional arguments
parser.add_positional_argument("input");
parser.add_positional_argument("output").default_values("output.txt");

parser.parse_args(argc, argv);

// `input` is guaranteed to have a value if parsing was successful
const auto data = read_data(parser.value("input"));

// `output` is guaranteed to have a value even if one was not specified in the command-line
std::ofstream os(parser.value("output"));
os << data << std::endl;

/*
Command                           Result
-----------------------------------------------------------------------------------------
./program                         Parsing error (no value for the input argument)
./program input.txt               Parsing success; Printing data to `output.txt`
./program input.txt myfile.txt    Parsing success; Printing data to the `myfile.txt` file
// example: optional arguments
parser.add_optional_argument("input", "i").required();
parser.add_optional_argument("output", "o").default_values("output.txt");

parser.parse_args(argc, argv);

// `input` is guaranteed to have a value if parsing was successful
const auto data = read_data(parser.value("input"));

// `output` is guaranteed to have a value even if one was not specified in the command-line
std::ofstream os(parser.value("output"));
os << data << std::endl;

/*
Command                                 Result
-----------------------------------------------------------------------------------------------
./program                               Parsing error (no value for the input argument)
./program --input input.txt             Parsing success; Printing data to `output.txt`
./program -i input.txt -o myfile.txt    Parsing success; Printing data to the `myfile.txt` file

Note

The default_values function can be called with:



Parameters Specific for Optional Arguments

Apart from the common parameters listed above, for optional arguments you can also specify the following parameters:

1. on-flag actions - Functions that are called immediately after parsing an argument's flag.

void print_debug_info() noexcept {
#ifdef NDEBUG
    std::cout << "Running in release mode.\n";
#else
    std::cout << "Running in debug mode.\n";
#endif
    std::exit(EXIT_SUCCESS);
};

parser.add_optional_argument("debug-info")
      .action<argon::action_type::on_flag>(print_debug_info);

Here the print_debug_info function will be called right after parsing the --debug-info flag and the program will exit, even if there are more arguments after this flag.


2. implicit_values - A list of values which will be set for an argument if only its flag but no values are parsed from the command-line.

// example
parser.add_optional_argument("save", "s").implicit_values("output.txt");

parser.parse_args(argc, argv);

const auto data = get_data(); // arbitrary program data

// `output` is not guaranteed to have a value
if (parser.has_value("save")) {
    std::ofstream os(parser.value("save"));
    os << data << std::endl;
}

/*
Command                       Result
--------------------------------------------------------------------
./program                     No data will be saved
./program -s                  The data will be saved to `output.txt`
./program --save myfile.txt   The data will be saved to `myfile.txt`

Note

The implicit_values function can be called with:

Tip

The implicit_values parameter is extremely useful when combined with default value (e.g. in case of boolean flags - see Adding Arguments).




Predefined Parameter Values

Actions

  • print_help | on-flag

    Prints the parser's help message to the output stream and optionally exits with the given code.

    typename argon::action_type::on_flag::type print_help(
        const argon::argument_parser& parser,
        const std::optional<int> exit_code = std::nullopt,
        std::ostream& os = std::cout
    ) noexcept;
  • check_file_exists | observe (value type: std::string)

    Throws if the provided file path does not exist.

    argon::action::util::callable_type<argon::action_type::observe, std::string> check_file_exists() noexcept;
  • gt | observe (value type: arithmetic)

    Validates that the value is strictly greater than lower_bound.

    template <argon::util::c_arithmetic T>
    argon::action::util::callable_type<argon::action_type::observe, T> gt(const T lower_bound) noexcept;
  • geq | observe (value type: arithmetic)

    Validates that the value is greater than or equal to lower_bound.

    template <argon::util::c_arithmetic T>
    argon::action::util::callable_type<argon::action_type::observe, T> geq(const T lower_bound) noexcept;
  • lt | observe (value type: arithmetic)

    Validates that the value is strictly less than upper_bound.

    template <argon::util::c_arithmetic T>
    argon::action::util::callable_type<argon::action_type::observe, T> lt(const T upper_bound) noexcept;
  • leq | observe (value type: arithmetic)

    Validates that the value is less than or equal to upper_bound.

    template <argon::util::c_arithmetic T>
    argon::action::util::callable_type<argon::action_type::observe, T> leq(const T upper_bound) noexcept;
  • within | observe (value type: arithmetic)

    Checks if the value is within the given interval. Bound inclusivity is customizable using template parameters.

    template <argon::util::c_arithmetic T, bool LeftInclusive = true, bool RightInclusive = true>
    argon::action::util::callable_type<argon::action_type::observe, T> within(
        const T lower_bound, const T upper_bound
    ) noexcept;



Default Arguments

The CPP-ARGON library defines several default arguments, which can be added to the parser's configuration as follows.

parser.default_arguments(<args>);

Note

The default_arguments function can be called with:

  • A variadic number of argon::default_argument values
  • An arbitrary std::ranges::range type with the argon::default_argument value type
  • p_input:

    // equivalent to:
    parser.add_positional_argument<std::string>("input")
          .action<argon::action_type::modify>(argon::action::check_file_exists())
          .help("Input file path");
  • p_output:

    // equivalent to:
    parser.add_positional_argument("output").help("Output file path");
  • o_help:

    // equivalent to:
    parser.add_optional_argument<argon::none_type>("help", "h")
          .action<action_type::on_flag>(argon::action::print_help(parser, EXIT_SUCCESS))
          .help("Display the help message");
  • o_version:

    // equivalent to:
    parser.add_optional_argument<none_type>("version", "v")
          .action<action_type::on_flag>([&parser]() {
              parser.print_version();
              std::exit(EXIT_SUCCESS);
          })
          .help("Display program version info");
  • o_input and o_multi_input:

    // input - equivalent to:
    parser.add_optional_argument("input", "i")
          .required()
          .nargs(1)
          .action<argon::action_type::observe>(argon::action::check_file_exists())
          .help("Input file path");
    
    // multi_input - equivalent to:
    parser.add_optional_argument("input", "i")
          .required()
          .nargs(argon::nargs::at_least(1))
          .action<argon::action_type::observe>(argon::action::check_file_exists())
          .help("Input files paths");
  • o_output and o_multi_output:

    // output - equivalent to:
    parser.add_optional_argument("output", "o")
          .required()
          .nargs(1)
          .help("Output file path");
    
    // multi_output - equivalent to:
    parser.add_optional_argument("output", "o")
          .required()
          .nargs(argon::nargs::at_least(1))
          .help("Output files paths");



Argument Groups

Argument groups provide a way to organize related arguments into logical sections and enforce group-wise requirements.

By default, every parser comes with two predefined groups:

  • Positional Arguments – contains all arguments added via add_positional_argument.
  • Optional Arguments – contains all arguments added via add_optional_argument or add_flag without explicitly specifying an argument group.

Note

All predefined arguments are bound to one of the predefined groups.

Creating New Groups

A new group can be created by calling the add_group method of an argument parser:

argon::argument_parser parser("myprog");
auto& out_opts = parser.add_group("Output Options");

The group’s name will appear as a dedicated section in the help message and arguments added to this group will be listed under Output Options instead of the default Optional Arguments section.

Note

If a group has no visible arguments, it will not be included in the parser's help message output at all.

Adding Arguments to Groups

Arguments are added to a group by passing the group reference as the first parameter to the add_optional_argument and add_flag functions:

parser.add_optional_argument(out_opts, "output", "o")
      .nargs(1)
      .help("Print output to the given file");

parser.add_flag(out_opts, "print", "p")
      .help("Print output to the console");

Group Attributes

User-defined groups can be configured with special attributes that change how the parser enforces their usage:

  • required() – at least one argument from the group must be provided by the user, otherwise parsing will fail.
  • mutually_exclusive() – at most one argument from the group can be provided; using more than one at the same time results in an error.

Both attributes are off by default, and they can be combined (e.g., a group can require that exactly one argument is chosen).

auto& out_opts = parser.add_group("Output Options")
                       .required()            // at least one option is required
                       .mutually_exclusive(); // but at most one can be chosen

Important

If a group is defined as mutually exclusive and an argument from this group is used, then the required and nargs attribute requirements of other arguments from the group will NOT be verified.

Consider the example in the section below. Normally the --output, -o argument would expect a value to be given in the command-line. However, if the --print, -p flag is used, then the nargs requirement of the --output, -o argument will not be verified, and therefore no exception will be thrown, even though the nargs requirement is not satisfied.

Additionally, argument groups can be marked hidden - a hidden group will not be visible in the parser's help output (even if it has visible arguments).

auto& hidden_opts = parser.add_group("Hidden Options").hidden();
parser.add_optional_argument("visible").help("A visible arg");

In the example above, neither the Hidden Options group nor the visible arg will be visible in the parser's help output.

Complete Example

Below is a small program that demonstrates how to use a mutually exclusive group of required arguments:

#include <argon/argument_parser.hpp>

int main(int argc, char* argv[]) {
    argon::argument_parser parser("myprog");
    parser.default_arguments(argon::default_argument::o_help);

    // create the argument group
    auto& out_opts = parser.add_group("Output Options")
                           .required()
                           .mutually_exclusive();

    // add arguments to the custom group
    parser.add_optional_argument(out_opts, "output", "o")
          .nargs(1)
          .help("Print output to a given file");

    parser.add_flag(out_opts, "print", "p")
          .help("Print output to the console");

    parser.try_parse_args(argc, argv);

    return 0;
}

When invoked with the --help flag, the above program produces a help message that clearly shows the group and its rules:

Program: myprog

Output Options: (required, mutually exclusive)

  --output, -o : Print output to a given file
  --print, -p  : Print output to the console

Suppressing Argument Group Checks

Similarly to suppressing argument checks, an argument can suppress the requirement checks of argument groups:

argument.suppress_group_checks();

If such argument is used, the requirement checks associated with the group attributes will not be validated.

Note

  • All arguments have the suppress_group_checks option disabled by default.
  • The default value of the value parameter of the argument::suppress_group_checks(bool) method is true for all arguments.

Warning

  • Enabling the suppress_group_checks option has no effect on argument requirements validation.
  • Enabling argument group checks suppressing is not possible for required arguments (an exception will be thrown).



Parsing Arguments

To parse the command-line arguments use the void argument_parser::parse_args(const AR& argv) method, where AR must be a type that satisfies the std::ranges::forward_range concept and its value type is convertible to std::string.

The argument_parser class also defines the void parse_args(int argc, char* argv[]) overload, which works directly with the argc and argv arguments of the main function.

Important

The parse_args(argc, argv) method ignores the first argument (the program name) and is equivalent to calling:

parse_args(std::span(argv + 1, argc - 1));

Tip

The parse_args function may throw an argon::argument_parser_exception if the configuration of the defined arguments is invalid or the parsed command-line arguments do not match the expected configuration. To simplify error handling, the argument_parser class provides a try_parse_args method which will automatically catch these exceptions, print the error message as well as the help message of the deepest used parser (see Subparsers), and exit with a failure status.

Internally, This is equivalent to:

try {
    parser.parse_args(...);
}
catch (const argon::argument_parser_exception& err) {
    std::cerr << "[argon::error] " << err.what() << std::endl << parser.resolved_parser() << std::endl;
    std::exit(EXIT_FAILURE);
}

The simple example below demonstrates how (in terms of the program's structure) the argument parsing should look like.

// include the main library header
#include <argon/argument_parser.hpp>

int main(int argc, char* argv[]) {
    // create the parser class instance
    argon::argument_parser parser("some-program");

    // define the parser's attributes and default arguments
    parser.program_version({0u, 0u, 0u})
          .program_description("The program does something with command-line arguments")
          .default_arguments(argon::default_argument::o_help);

    // define the program arguments
    parser.add_positional_argument("positional").help("A positional argument");
    parser.add_optional_argument("optional", "o").help("An optional argument");
    parser.add_flag("flag", "f").help("A boolean flag");

    // parse command-line arguments
    parser.try_parse_args(argc, argv);

    // use the program's arguments
    std::cout << "positional: " << parser.value("positional") << std::endl
              << "optional: " << argon::util::join(parser.values("optional")) << std::endl
              << "flag: " << std::boolalpha << parser.value<bool>("flag") << std::endl;

    return 0;
}

Basic Argument Parsing Rules

1. Optional arguments are parsed only with a flag

An optional argument is recognized only when its primary or secondary flag appears in the command-line input. For example:

parser.add_optional_argument("optional", "o");

Here, the argument is parsed only if either --optional (primary flag) or -o (secondary flag) is present. If neither flag is given, the argument is ignored.

Important

The parser will try to assign the values following such flag to the specified argument until:

  • A different argument flag is encountered:
// program.cpp
parser.add_optional_argument("first", "f");
parser.add_optional_argument("second", "s");

parser.try_parse_args(argc, argv);

std::cout << "first: " << argon::util::join(parser.values("first")) << std::endl
          << "second: " << argon::util::join(parser.values("second")) << std::endl;

/* Example execution:
> ./program --first value1 value2 --second value3 value4
first: value1, value2
second: value3, value4
  • The upper bound of the argument's nargs parameter is reached:

NOTE: By default an optional argument accepts an arbitrary number of values (the number of values has no upper bound).

parser.add_optional_argument<int>("numbers", "n")
      .nargs(argon::nargs::up_to(3))
      .help("A list of numbers");
> ./program --numbers 1 2 3 4 5
[ERROR] : Failed to deduce the argument for values [4, 5]
Program: program

  An example program

Optional arguments:

  --help, -h    : Display the help message
  --numbers, -n : A list of numbers

2. Positional arguments are parsed in the order of definition

Positional arguments are assigned values in the same order they are defined in the program. They are parsed from the command-line input excluding any values that have already been consumed by optional arguments. This means positional arguments no longer need to appear at the beginning of the argument list.

For example:

parser.add_positional_argument("positional1");
parser.add_positional_argument("positional2");

parser.try_parse_args(argc, argv);

std::cout << "positional1: " << parser.value("positional1") << std::endl
          << "positional2: " << parser.value("positional2") << std::endl;

/* Example execution:
> ./program value1 value2
positional1: value1
positional2: value2

Important

  • All positional arguments expect at most one value.
  • A positional argument's value doesn't have to be preset in the command-line only if the argument is defined as not required.

3. Positional arguments consume free values

A positional argument consumes only those values that cannot be assigned to optional arguments. This allows positional arguments to appear after optional arguments in the command-line input.

parser.add_positional_argument("positional1");
parser.add_positional_argument("positional2");
parser.add_optional_argument("optional").nargs(1); // limit the number of arguments

parser.try_parse_args(argc, argv);

std::cout << "positional1: " << parser.value("positional1") << std::endl
          << "positional2: " << parser.value("positional2") << std::endl
          << "optional: " << parser.value("optional") << std::endl;

/* Example executions:
> ./program pos1-value pos2-value --optional opt-value
positional1: pos1-value
positional2: pos2-value
optional: opt-value

> ./program --optional opt-value pos1-value pos2-value
positional1: pos1-value
positional2: pos2-value
optional: opt-value

> ./program pos1-value --optional opt-value pos2-value
positional1: pos1-value
positional2: pos2-value
optional: opt-value

Tip

Because optional arguments accept an arbitrary number of arguments by default, it is a good practice to set the nargs parameter for optional arguments (where it makes sense).


4. Unknown Argument Flag Handling

A command-line argument beginning with a flag prefix (-- or -) that doesn't match any of the specified optional arguments or a compound of optional arguments (only for short flags) is considered unknown or unrecognized.

By default an argument parser will throw an exception if an unknown argument flag is encountered.

This behavior can be modified using the unknown_arguments_policy method of the argument_parser class, which sets the policy for handling unknown argument flags.

Example:

#include <argon/argument_parser.hpp>

int main(int argc, char* argv[]) {
    argon::argument_parser parser("unknown-policy-test");

    parser.program_description("A simple test program for unknwon argument handling policies")
          .default_arguments(argon::default_argument::o_help)
          // set the unknown argument flags handling policy
          .unknown_arguments_policy(argon::unknown_policy::<policy>);

    parser.add_optional_argument("known", "k")
          .help("A known optional argument");

    parser.try_parse_args(argc, argv);

    std::cout << "known = " << argon::util::join(parser.values("known")) << std::endl;

    return 0;
}

The available policies are:

  • argon::unknown_policy::fail (default) - throws an exception if an unknown argument flag is encountered:

    > ./unknown-policy-test --known --unknown
    [argon::error] Unknown argument [--unknown].
    Program: unknown-policy-test
    
      A simple test program for unknwon argument handling policies
    
    Optional arguments:
    
      --help, -h  : Display the help message
      --known, -k : A known optional argument
  • argon::unknown_policy::warn - prints a warning message to the standard error stream and continues parsing the remaining arguments:

    > ./unknown-policy-test --known --unknown
    [argon::warning] Unknown argument '--unknown' will be ignored.
    known =
  • argon::unknown_policy::ignore - ignores unknown argument flags and continues parsing the remaining arguments:

    ./unknown-policy-test --known --unknown
    known =
  • argon::unknown_policy::as_values - treats unknown argument flags as values:

    > ./unknown-policy-test --known --unknown
    known = --unknown

Important

  • The unknown argument flags handling policy only affects the parser's behaviour when calling the parse_args or try_parse_args methods.
  • When parsing known args with parse_known_args or try_parse_known_args all unknown arguments (flags and values) are collected and returned as the parsing result, ignoring the specified policy for handling unknown arguments.

Consider a similar example as above with only the argument parsing function changed:

const auto unknown_args = parser.try_parse_known_args(argc, argv);
std::cout << "known = " << argon::util::join(parser.values("known")) << std::endl
          << "unknown = " << argon::util::join(unknown_args) << std::endl;

This would produce the following output regardless of the specified unknown arguments policy.

> ./test --known --unknown
known =
unknown = --unknown


Compound Arguments

Compound argument flags are secondary argument flags of which every character matches the secondary name of an optional argument.

Example:

parser.add_optional_argument("verbose", "v")
      .nargs(0)
      .help("Increase verbosity level");

parser.add_flag("option", "o")
      .help("Enable an option flag");

parser.add_optional_argument<int>("numbers", "n")
      .help("Provide integer values");

parser.try_parse_args(argc, argv);

std::cout << "Verbosity level: " << parser.count("verbose")
          << "\nOption used: " << std::boolalpha << parser.value<bool>("use-option")
          << "\nNumbers: " << argon::util::join(parser.values<int>("numbers"), ", ")
          << std::endl;

/*
> ./program -vvon 1 2 3
Verbosity level: 2
Option used: true
Numbers: 1, 2, 3

Important

  • If there exists an argument whose secondary name matches a possible compound of other arguments, the parser will still treat the flag as a flag of the single matching argument, not as multiple flags.
  • The argument parser will try to assign the values following a compound argument flag to the argument represented by the last character of the compound flag.

Parsing Known Arguments

If you wish to handle only the specified command-line arguments and leave all unknown/unrecognized arguments, you can use the parse_known_args method.

This method behaves similarly to parse_args() (see Parsing Arguments), however it does not throw an error if unknown arguments are detected. Instead it returnes all the unknown arguments detected during parsing as a std::vector<std::string>.

Consider a simple example:

parser.add_optional_argument("recognized", "r")
      .nargs(argon::nargs::up_to(2))
      .help("A recognized optional argument");

parser.parse_args(argc, argv);

std::cout << "recognized = " << argon::util::join(parser.values("recognized")) << std::endl;

/* Example executions:
> ./program --recognized value1 value2
recognized = value1, value2

> ./program --recognized value1 value2 value3
terminate called after throwing an instance of 'argon::parsing_failure'
  what():  Failed to deduce the argument for values [value3]
Aborted (core dumped)

> ./program value0 --recognized value1 value2
terminate called after throwing an instance of 'argon::parsing_failure'
  what():  Failed to deduce the argument for values [value0]
Aborted (core dumped)

> ./program --recognized value1 value2 --unrecognized value
terminate called after throwing an instance of 'argon::parsing_failure'
  what():  Unknown argument [--unrecognized].
Aborted (core dumped)
>

Here the parser throws exceptions for arguments it doesn't recognize. Now consider the same example with parse_known_args:

parser.add_optional_argument("recognized", "r")
      .nargs(argon::nargs::up_to(2))
      .help("A recognized optional argument");

const auto unknown_args = parser.parse_known_args(argc, argv);

std::cout << "recognized = " << argon::util::join(parser.values("recognized")) << std::endl
          << "unknown = " << argon::util::join(unknown_args) << std::endl;

/* Example execution:
> ./program value0 --recognized value1 value2 value3 --unrecognized value
recognized = value1, value2
unknown = value0, value3, --unrecognized, value

Now all the values, that caused an exception for the parse_args example, are collected and returned as the result of argument parsing.

Important

If a parser encounters an unrecognized argument flag during known args parsing, then the flag will be collected and the currently processed optional argument is reset. That means that any value following an unrecognized flag will be used to parse positional arguments or treated as an unknown argument as well (if there are no unparsed positional arguments). Let's consider an example:

parser.add_positional_argument("positional")
      .help("A positional argument");
parser.add_optional_argument("recognized", "r")
      .nargs(argon::nargs::any())
      .help("A recognized optional argument");

const auto unknown_args = parser.parse_known_args(argc, argv);

std::cout << "positional = " << parser.value("positional") << std::endl
          << "recognized = " << argon::util::join(parser.values("recognized")) << std::endl
          << "unknown = " << argon::util::join(unknown_args) << std::endl;

/* Example execution:
> ./program --recognized value1 value2 value3 --unrecognized value4 value5 --recognized value6
positional = value4
recognized = value1, value2, value3, value6
unknown = --unrecognized, value5

> ./program value0 --recognized value1 value2 value3 --unrecognized value4 --recognized value5
positional = value0
recognized = value1, value2, value3, value5
unknown = --unrecognized, value4

Here value is treated either as the positional argument's value or as an unknown argument (depending on the input arguments) even though the recognized optional argument still accepts values and only after the --recognized argument flag is encountered the parser continues collecting values for this argument.

Tip

Similarly to the parse_args method, parse_known_args has a try equivalent - try_parse_known_args - which will automatically catch these exceptions, print the error message, and exit with a failure status.




Retrieving Argument Values

You can retrieve the argument's value(s) with:

(const) value_type value = parser.value<value_type>("argument_name"); // (1)
(const) value_type value = parser.value_or<value_type>("argument_name", fallback_value); // (2)
(const) std::vector<value_type> values = parser.values<value_type>("argument_name"); // (3)
  1. Returns the given argument's value.

    • Returns the argument's parsed value if it has one.
    • If more than one value has been parsed for the argument, this function will return the first parsed value.
    • Returns the argument's predefined value if no value has been parsed for the argument.
  2. Returns the given argument's value or the specified fallback value if the argument has no values.

    • If the argument has a value (parsed or predefined), the behavior is the same as in case (1).
    • If the argument has no values, this will return value_type{std::forward<U>(fallback_value)} (where U is the deduced type of fallback_value).
  3. Returns a vector of the given argument's values.

    • If the argument has any values (parsed or predefined), they will be returned as a std::vector<value_type>.
    • If the argument has no values an empty vector will be returned.

Note

The argument value getter functions might throw an exception if:

  • An argument with the given name does not exist
  • The argument does not contain any values - parsed or predefined (only getter function (1))
  • The specified value_type does not match the value type of the argument






Subparsers

Subparsers allow you to build hierarchical command-line interfaces, where a top-level parser delegates parsing to its subcommands. This is particularly useful for creating CLI applications like git, where commands such as git add, git commit, and git push each have their own arguments.

Creating Subparsers

auto& subparser = parser.add_subparser("subprogram");

Each subparser is a separate instance of argon::argument_parser and therefore it can have its own parameters, including a description, arguments, argument groups, subparsers, etc.

For example:

// top-level parser
argon::argument_parser git("ap-git");
git.program_version({.major = 2u, .minor = 43u, .patch = 0u})
   .program_description("A version control system built with CPP-ARGON")
   .default_arguments(argon::default_argument::o_help, argon::default_argument::o_version);

// subcommand: status
auto& status = git.add_subparser("status");
status.default_arguments(argon::default_argument::o_help)
      .program_description("Show the working tree status");
status.add_flag("short", "s")
      .help("Give the output in the short-format");

This defines git and git status parsers, each with their own sets of arguments.

Using Multiple Subparsers

You can add as many subparsers as you like, each corresponding to a different command:

auto& init = git.add_subparser("init");
init.program_description("Create an empty Git repository or reinitialize an existing one");

auto& add = git.add_subparser("add");
add.program_description("Add file contents to the index");

auto& commit = git.add_subparser("commit");
commit.program_description("Record changes to the repository");

auto& status = git.add_subparser("status");
status.program_description("Show the working tree status");

auto& push = git.add_subparser("push");
push.program_description("Update remote refs along with associated objects");

All defined subparsers will be included in the parent parser's help message:

> ap-git --help
Program: ap-git (v2.43.0)

  A version control system built with CPP-ARGON

Commands:

  init   : Create an empty Git repository or reinitialize an existing one
  add    : Add file contents to the index
  commit : Record changes to the repository
  status : Show the working tree status
  push   : Update remote refs along with associated objects

Optional Arguments:

  --help, -h    : Display the help message
  --version, -v : Display program version info

Parsing Arguments with Subparsers

When parsing command-line arguments, the parent parser will attempt to match the first command-line token against the name of one of its subparsers.

  • If a match is found, the parser delegates the remaining arguments to the matched subparser.
  • This process repeats recursively, so each subparser may also match one of its own subparsers.
  • Parsing stops when no subparser matches the first token of the current argument list. At that point, the parser processes its own arguments.

For example:

argon::argument_parser git("ap-git");
auto& submodule = git.add_subparser("submodule");
auto& submodule_init = submodule.add_subparser("init");

Running ap-git submodule init <args> will result in <args> being parsed by the submodule_init parser.

Tracking Parser State

Each parser tracks its state during parsing. The methods described below let you inspect this state:

  • invoked() -> bool : Returns true if the parser’s name appeared on the command line.

    A parser becomes invoked as soon as the parser is selected during parsing, even if parsing is later delegated to one of its subparsers.

  • finalized() -> bool : Returns true if the parser has processed its own arguments.

    This is distinct from invoked(): a parser can be invoked but not finalized if one of its subparsers handled the arguments instead.

  • resolved_parser() -> argon::argument_parser& : Returns a reference to the deepest invoked parser.

    If no subparser was invoked, this simply returns the current parser.


Example: Inspecting Parsing States

// define the parser hierarchy
argon::argument_parser git("ap-git");
auto& submodule = git.add_subparser("submodule");
auto& submodule_init = submodule.add_subparser("init");

// parse arguments
git.try_parse_args(argc, argv);

// print state for each parser
std::cout << std::boolalpha;

std::cout << "git            : invoked=" << git.invoked()
          << ", finalized=" << git.finalized() << '\n';

std::cout << "submodule      : invoked=" << submodule.invoked()
          << ", finalized=" << submodule.finalized() << '\n';

std::cout << "submodule_init : invoked=" << submodule_init.invoked()
          << ", finalized=" << submodule_init.finalized() << '\n';

auto& active = git.resolved_parser();
std::cout << "\nResolved parser : " << active.name() << " (" << active.program_name() << ")\n";

If you run: ./ap-git submodule init, you will get the following state:

git            : invoked=true, finalized=false
submodule      : invoked=true, finalized=false
submodule_init : invoked=true, finalized=true

Resolved parser : init (ap-git submodule init)



Examples

The library usage examples and demo projects are included in the cpp-argon-demo submodule. To fetch the submodule content after cloning the main repository, run:

git submodule update --init --recursive

For more detailed information about the demo projects, see the cpp-argon-demo README.

The following table lists the projects provided in the cpp-argon-demo submodule:

Project Description
Power Calculator Calculates the value of a $b^e$ expression for the given base and exponents.
Demonstrates: The basic usage of positional and optional arguments.
File Merger Merges multiple text files into a single output file.
Demonstrates: The usage of default arguments.
Numbers Converter Converts numbers between different bases.
Demonstrates: The usage of argument parameters such as nargs, choices, and default values.
Verbosity Prints messages with varying levels of verbosity.
Demonstrates: The usage of none_type arguments and compound argument flags.
Logging Mode Logs a message depending on the selected logging mode (quiet, normal, verbose).
Demonstrates: The usage of custom argument value types (like enums).
Message Logger Outputs a message to a file, console, or not at all.
Demonstrates: The usage of argument groups.
AP-GIT A minimal Git CLI clone with subcommands (init, add, commit, status, push).
Demonstrates: The usage of subparsers for multi-command CLIs and complex argument configurations.



Common Utility

The CPP-ARGON library provides additional utilities, described on the Utility topic page.