1. Introduction

Lyra is a simple to use, composing, command line parser for C++ 11 and beyond. It provides easy to use command line parsing for most use cases with a minimal source footprint. It doesn’t aim to provide all features for all users.

1.1. License

Distributed under the highly permissive Boost Software License, Version 1.0. (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

1.2. Features

  • Header only with no external dependencies (except the std library).

  • Define your interface once to get parsing, type conversions and usage strings with no redundancy.

  • Composing. Each opt or arg is an independent parser. Combine these to produce a composite parser — this can be done in stages across multiple function calls — or even projects.

  • Bind parsers directly to variables that will receive the results of the parse — no intermediate dictionaries to worry about.

  • Or can also bind parsers to lambdas for more custom handling.

  • Deduces types from bound variables or lambdas and performs type conversions (via ostream <<), with error handling, behind the scenes.

  • Bind parsers to vectors for args that can have multiple values.

  • Uses result types for error propagation, rather than exceptions (doesn’t yet build with exceptions disabled, but that will be coming later)

  • Models POSIX standards for short and long opt behavior.

  • Customizable option syntax.

  • Specify cardinality of arg-s from one to many.

  • Limit option values to a specified set of values.

2. Usage

To use, just #include <lyra/lyra.hpp>

2.1. Single Option

A parser for a single option can be created like this:

#include <cstdlib>
#include <iostream>
#include <lyra/lyra.hpp>

int main(int argc, const char** argv)
{
    // Where we read in the argument value:
    int width = 0;

    // The parser with the one option argument:
    auto cli = lyra::cli_parser()
        | lyra::opt(width, "width")
              ["-w"]["--width"]("How wide should it be?");

    // ...

You can use this parser by giving it the program arguments of main:

    // ...

    // Parse the program arguments:
    auto result = cli.parse({ argc, argv });

    // Check that the arguments where valid:
    if (!result)
    {
        std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
        return 1;
    }

    std::cout << "width = " << width << "\n";
    return 0;
}

Which could be used as:

> example1 -w 10
width = 10

2.2. Multiple Options

It’s rare that we are interested in accepting a single option. To parse multiple options we compose the options as alternatives with the or operator (|).

#include <cstdlib>
#include <iostream>
#include <lyra/lyra.hpp>

int main(int argc, const char** argv)
{
    // Where we read in the argument values:
    int width = 0;
    std::string name;
    bool doIt = false;

    // The parser with the multiple option arguments. They are composed
    // together by the "|" operator.
    auto cli
        = lyra::opt(width, "width")
              ["-w"]["--width"]("How wide should it be?")
        | lyra::opt(name, "name")
              ["-n"]["--name"]("By what name should I be known")
        | lyra::opt(doIt)
              ["-d"]["--doit"]("Do the thing");

    // ...

You can use this parser by giving it the program arguments of main:

    // ...

    // Parse the program arguments:
    auto result = cli.parse({ argc, argv });

    // Check that the arguments where valid:
    if (!result)
    {
        std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
        return 1;
    }

    std::cout << "width = " << width << ", name = " << name << ", doIt = " << doIt << "\n";
    return 0;
}

Which could be used as:

> example2 -w 10 --name=Lyra
width = 10, name = Lyra, doIt = 0

3. Alternate Specification

Using operators for specifying the parser argument is not everyone’s "cut of tea". Like all such interface choices there are advantages and disadvantages to any particular API one chooses. Because of this Lyra supports both the preceding operator model and a "named arguments using methods" model for specifying the arguments. Below are the various specification operations and corresponding operator and method call equivalents:

Help description

parser("Help")

parser.help("Help")

Optional argument

parser.optional()

parser.optional()

Required argument

parser.required()

parser.required()

Range of arguments

parser.cardinality(n,m)

parser.cardinality(n,m)

Value choices

parser.choices(a,b,c)

parser.choices(a,b,c)

Add argument

parser |= argument

parser.add_argument(argument)

Add option name

option["--name"]

option.name("--name")

Help option description

help.description("Description")

help.description("Description")

The method names try to follow the well known Python argparse nomenclature for familiarity. Using the alternative methods one could rewrite the previous two examples. The first one with the single option would be:

#include <cstdlib>
#include <iostream>
#include <lyra/lyra.hpp>

int main(int argc, const char** argv)
{
    // Where we read in the argument value:
    int width = 0;

    // The parser with the one option argument:
    auto cli = lyra::cli_parser();
    cli.add_argument(
        lyra::opt(width, "width")
            .name("-w")
            .name("--width")
            .help("How wide should it be?"));

    // Parse the program arguments:
    auto result = cli.parse({ argc, argv });

    // Check that the arguments where valid:
    if (!result)
    {
        std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
        return 1;
    }

    std::cout << "width = " << width << "\n";
    return 0;
}

And the second with multiple options would the become:

#include <cstdlib>
#include <iostream>
#include <lyra/lyra.hpp>

int main(int argc, const char** argv)
{
    // Where we read in the argument values:
    int width = 0;
    std::string name;
    bool doIt = false;

    // The parser with the multiple option arguments. They are composed
    // together by the "|" operator.
    auto cli = lyra::cli_parser();
    cli.add_argument(lyra::opt(width, "width")
        .name("-w").name("--width").help("How wide should it be?"));
    cli.add_argument(lyra::opt(name, "name")
        .name("-n").name("--name").help("By what name should I be known"));
    cli.add_argument(lyra::opt(doIt)
        .name("-d").name("--doit").help("Do the thing"));

    // Parse the program arguments:
    auto result = cli.parse({ argc, argv });

    // Check that the arguments where valid:
    if (!result)
    {
        std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
        return 1;
    }

    std::cout << "width = " << width << ", name = " << name << ", doIt = " << doIt << "\n";
    return 0;
}

4. Help Option

From the specified arguments parser we also get convenient help output.

int main(int argc, const char** argv)
{
    // Where we read in the argument values:
    int width = 0;
    std::string name;
    bool doIt = false;
    bool show_help = false; (1)

    // The parser with the multiple option arguments and help option.
    auto cli
        = lyra::help(show_help) (2)
        | lyra::opt(width, "width")
              ["-w"]["--width"]("How wide should it be?")
        | lyra::opt(name, "name")
              ["-n"]["--name"]("By what name should I be known")
        | lyra::opt(doIt)
              ["-d"]["--doit"]("Do the thing");

    // ...
1 Flag variable to indicate if we get the -h or --help option.
2 The help specific option parser.

We need some changes when using the parser to check if the help option was specified:

    // ...

    // Parse the program arguments:
    auto result = cli.parse({ argc, argv });

    // Check that the arguments where valid:
    if (!result)
    {
        std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
        std::cerr << cli << "\n"; (1)
        return 1;
    }

    // Show the help when asked for.
    if (show_help) (2)
    {
        std::cout << cli << "\n";
        return 0;
    }

    std::cout << "width = " << width << ", name = " << name << ", doIt = " << doIt << "\n";
    return 0;
}
1 We print out the help text on error.
2 And we also print it out when specified.

5. Value Choices

For value arguments, i.e. --name=value or positional arguments, one can specify a set of allowed values. You can indicate the choices explicitly by specifying them to the choices() call:

#include <iostream>
#include <lyra/lyra.hpp>

int main(int argc, const char** argv)
{
    std::string choice;
    // Ex: <exe> --choice=red
    auto cli = lyra::cli_parser()
        | lyra::opt(choice, "-c")["--choice"]
              .choices("red", "green", "blue");
    auto result = cli.parse({ argc, argv });
    if (result)
    {
        std::cout << "Your preferred color is " << choice << "\n";
        return 0;
    }
    else
    {
        std::cerr << result.errorMessage() << "\n";
        return 1;
    }
}

Or you can specify a complex set of values by giving choices() a function that validates if a given value is allowed:

#include <iostream>
#include <lyra/lyra.hpp>

int main(int argc, const char** argv)
{
    int choice = 5;
    // Ex: <exe> --choice=3
    auto cli = lyra::cli_parser()
        | lyra::opt(choice, "-c")["--choice"]
              .choices([](int value) { return 1 <= value && value <= 10; });
    auto result = cli.parse({ argc, argv });
    if (result)
    {
        std::cout << "Your number between one and ten is " << choice << "\n";
        return 0;
    }
    else
    {
        std::cerr << result.errorMessage() << "\n";
        return 1;
    }
}

For either case the default, initial, value is only modified if a valid value is given.

6. Option Values

For options that are bound to values, and hence that accept a user value on the command line, various styles of option+value syntax is allowed for the user as follows:

Long options

opt(v, "value").name("--option")

--option VALUE, --option=VALUE

Short options

opt(v, "value").name("-o")

-o VALUE, --o=VALUE, -oVALUE

7. Use Cases

7.1. Sub-commands

A common program structure is to have a "wrapper" or "shell" that performs various sub-commands. Even though Lyra doesn’t have a direct specification for sub-commands, it is possible to support this use case. In this example we double parse arguments. Once for the sub-command, and a second time for the sub-command arguments. This is possible because of Lyra’s composition of argument specifications.

#include <iostream>
#include <lyra/lyra.hpp>
#include <string>
#include <vector>

// Run a process, sub-command data.
struct run_command (1)
{
    static const char* name() { return "run"; } (2)
    std::vector<std::string> command; (3)
    bool verbose = false;

    run_command(lyra::cli_parser& cli) (4)
    {
        cli.add_argument(
            lyra::opt(verbose)
                .name("-v")
                .name("--verbose")
                .optional()
                .help("Show additional output as to what we are doing."));
        cli.add_argument(
            lyra::arg(command, "command")
                .required()
                .help("The command, and arguments, to attempt to run."));
    }
};

// Kill a named process, sub-command data.
struct kill_command (5)
{
    static const char* name() { return "kill"; }
    std::string process_name;
    int signal = 9;

    kill_command(lyra::cli_parser& cli)
    {
        cli.add_argument(
            lyra::opt(signal, "signal")
                .name("-s")
                .name("--signal")
                .optional()
                .help("The signal integer to post to the running process."));
        cli.add_argument(
            lyra::arg(process_name, "process_name")
                .required()
                .help("The name of the process to search and signal."));
    }
};

int main(int argc, const char** argv)
{
    auto cli = lyra::cli_parser();
    std::string command;
    bool show_help = false;
    cli.add_argument(lyra::help(show_help));
    cli.add_argument(lyra::arg(command, "command") (6)
                         .choices(run_command::name(), kill_command::name())
                         .required()
                         .help("Command to perform."));
    auto result = cli.parse({ argc, argv }); (7)
    if (show_help)
    {
        std::cout << cli;
        return 0;
    }
    if (command == run_command::name())
    {
        run_command run_cmd(cli); (8)
        result = cli.parse({ argc, argv });
    }
    else if (command == kill_command::name())
    {
        kill_command kill_cmd(cli);
        result = cli.parse({ argc, argv });
    }
    if (!result) (9)
    {
        std::cerr << result.errorMessage() << "\n";
    }
    return result ? 0 : 1;
}
1 A simple struct for information on the sub-commands. First for our run sub-command.
2 The sub-command name, for use in the choices for the main program.
3 The arguments for the sub-command.
4 The constructor defines the additional arguments for the sub-command in the given cli_parser
5 And now the information for our kill sub-comand.
6 The top-level command, i.e. program, just defines the set of possible sub-command choices.
7 At first we only parse with the sub-commands argument (and help). This limits what users see to just the sub-commands when they ask for help.
8 Once we have a valid sub-command we set, and hence add, the sub-command arguments. And re-parse with the full recognized sub-command arguments.
9 At the end we can do the regular error handling.

8. Reference

8.1. lyra::parser_customization

Customization interface for parsing of options.

virtual std::string token_delimiters() const = 0;

Specifies the characters to use for splitting a cli argument into the option and its value (if any).

virtual std::string option_prefix() const = 0;

Specifies the characters to use as possible prefix, either single or double, for all options.

8.2. lyra::default_parser_customization

Is-a lyra::parser_customization that defines token delimiters as space (" ") or equal ("="). And specifies the option prefix character as dash ("-") resulting in long options with -- and short options with -.

This customization is used as the default if none is given.

8.3. lyra::parser_result

The result of parsing arguments.

8.4. lyra::parser_base

Base for all argument parser types.

8.5. lyra::composable_parser

A parser that can be composed with other parsers using operator|.

8.6. lyra::bound_parser

Parser that binds a variable reference or callback to the value of an argument.

8.6.1. Construction

template <typename Derived>
template <typename Reference>
bound_parser<Derived>::bound_parser(Reference& ref, std::string const& hint);

template <typename Derived>
template <typename Lambda>
bound_parser<Derived>::bound_parser(Lambda const& ref, std::string const& hint);

Constructs a value option with a target typed variable or callback. These are options that take a value as in --opt=value. In the first form the given ref receives the value of the option after parsing. The second form the callback is called during the parse with the given value. Both take a hint that is used in the help text. When the option can be specified multiple times the callback will be called consecutively for each option value given. And if a container is given as a reference on the first form it will contain all the specified values.

8.6.2. Specification

8.6.2.1. lyra::bound_parser::help, lyra::bound_parser::operator(help)
template <typename Derived>
Derived& bound_parser<Derived>::help(std::string const& text);
template <typename Derived>
Derived& bound_parser<Derived>::operator()(std::string const& help);

Defines the help description of an argument.

8.6.2.2. lyra::bound_parser::optional
template <typename Derived>
Derived& bound_parser<Derived>::optional();

Indicates that the argument is optional. This is equivalent to specifying cardinality(0, 1).

8.6.2.3. lyra::bound_parser::required(n)
template <typename Derived>
Derived& bound_parser<Derived>::required(size_t n);

Specifies that the argument needs to given the number of n times (defaults to 1).

8.6.2.4. lyra::bound_parser::cardinality(n, m)
template <typename Derived>
Derived& bound_parser<Derived>::cardinality(size_t n);

template <typename Derived>
Derived& bound_parser<Derived>::cardinality(size_t n, size_t m);

Specifies the number of times the argument can and needs to appear in the list of arguments. In the first form the argument can appear exactly n times. In the second form it specifies that the argument can appear from n to m times inclusive.

8.6.2.5. lyra::bound_parser::choices
template <typename Derived>
template <typename T, typename... Rest>
lyra::opt& lyra::bound_parser<Derived>::choices(T val0, Rest... rest)

template <typename Derived>
template <typename Lambda>
lyra::opt& lyra::bound_parser<Derived>::choices(Lambda const &check_choice)

Limit the allowed values of an argument. In the first form the value is limited to the ones listed in the call (two or more values). In the second form the check_choice function is called with the parsed value and returns true if it’s an allowed value.

8.7. lyra::cli_parser

A Combined parser made up of any two or more other parsers. Creating and using one of these as a basis one can incrementally compose other parsers into this one. For example:

auto cli = lyra::cli_parser();
std::string what;
float when = 0;
std::string where;
cli |= lyra::opt(what, "what")["--make-it-so"]("Make it so.").required();
cli |= lyra::opt(when. "when")["--time"]("When to do <what>.").optional();
cli.add_argument(lyra::opt(where, "where").name("--where")
    .help("There you are.").optional());

8.7.1. Construction

8.7.1.1. Default
cli_parser() = default;

Default constructing a cli_parser is the starting point to adding arguments and options for parsing a command line.

8.7.1.2. Copy
cli_parser::cli_parser(const cli_parser& other);

8.7.2. Specification

8.7.2.1. lyra::cli_parser::add_argument
cli_parser& cli_parser::add_argument(exe_name const& exe_name);
cli_parser& cli_parser::operator|=(exe_name const& exe_name);
cli_parser& cli_parser::add_argument(parser_base const& p);
cli_parser& cli_parser::operator|=(parser_base const& p);
cli_parser& cli_parser::add_argument(cli_parser const& other);
cli_parser& cli_parser::operator|=(cli_parser const& other);

Adds the given argument parser to the considered arguments for this cli_parser. Depending on the parser given it will be: recorded as the exe name (for exe_name parser), directly added as an argument (for parser_base), or add the parsers from another cli_parser to this one.

8.7.2.2. lyra::cli_parser::parse
parse_result cli_parser::parse(
    args const& args, parser_customization const& customize) const;

Parses given arguments args and optional parser customization customize. The result indicates success or failure, and if failure what kind of failure it was. The state of variables bound to options is unspecified and any bound callbacks may have been called.

8.8. lyra::opt

A parser for one option with multiple possible names. The option value(s) are communicated through a reference to a variable, a container, or a callback.

8.8.1. Construction

8.8.1.1. Flags
lyra::opt::opt(bool& ref);

template <typename LambdaT>
lyra::opt::opt(LambdaT const& ref);

template <typename LambdaT>
lyra::opt::opt(LambdaT const& ref, std::string const& hint)

Constructs a flag option with a target bool to indicate if the flag is present. The first form takes a reference to a variable to receive the bool. The second takes a callback that is called with true when the option is present.

8.8.1.2. Values
template <typename T>
lyra::opt::opt(T& ref, std::string const& hint);

template <typename LambdaT>
lyra::opt::opt(LambdaT const& ref, std::string const& hint)

Constructs a value option with a target ref. The first form takes a reference to a variable to receive the value. The second takes a callback that is called with the value when the option is present.

8.8.2. Specification

8.8.2.1. lyra::opt::name
lyra::opt& lyra::opt::name(const std::string &opt_name)
lyra::opt& lyra::opt::operator[](const std::string &optName)

Add a spelling for the option of the form --<name> or -n. One can add multiple short spellings at once with -abc.

8.9. lyra::arg

A parser for regular arguments, i.e. not -- or - prefixed. This is simply a way to get values of arguments directly specified in the cli.

8.10. lyra::help

Utility function that defines a default --help option. You can specify a bool flag to indicate if the help option was specified and that you could display a help message.

The option accepts -?, -h, and --help as allowed option names.

help & help::description(const std::string &text)

Sets the given text as the general description to show with the help and usage output for CLI parser. This text is displayed between the "Usage" and "Options, arguments" sections.

8.11. lyra::exe_name

Specifies the name of the executable.

8.11.1. Construction

exe_name::exe_name(std::string& ref)

Constructs with a target string to receive the name of the executable. When the cli_parser is run the target string will contain the exec name.

template <typename LambdaT>
exe_name::exe_name(LambdaT const& lambda)

Construct with a callback that is called with the value of the executable name when the cli_parser runs.

8.11.2. Accessors

8.11.2.1. lyra::exe_name::name
std::string exe_name::name() const

Returns the executable name when available. Otherwise it returns a default value.

8.11.2.2. lyra::exe_name::set
parser_result exe_name::set(std::string const& newName)

Sets the executable name with the newName value. The new value is reflected in the bound string reference or callback.

8.12. lyra::args

Transport for raw args (copied from main args, or supplied via init list).

9. History

9.1. 1.4

Changes:

  • New Allow passing option value choices as an array. — Rene Rivera

  • Fix sample code in README to be correctly namespaced. — Jayesh Badwaik

  • Fix commands example to actually show help when specified. — Shreyas Potnis

  • Tested with Visual Studio 2017, VS 2019, MinGW-64 (gcc 8.1.0), Linux ( clang 3.8, 3.9, 4, 5, 6, 7, 8, 9, 10; gcc 4.8, 4.9, 5, 6, 7, 8, 9, 10), Xcode ( 10.1, 10.2.1, 10.3, 11.1, 11.2.1, 11.3.1, 11.4), on Azure Pipelines; and with VS 2015, MinGW-64 (gcc 6.3, 7.3) on AppVeyor.

9.2. 1.3

Changes:

  • New: Accept -o2 style option+value arguments. — Rene Rivera

  • New: Accept -abc option name spelling as shorthand for -a, -b, -c. — Rene Rivera

  • Fixed inconsistent handling of option values that look like options, like negative numbers. — Rene Rivera

  • Rewrote argument token parsing for clarity, to avoid extra token buffer allocation, and to allow for more option & value variations. — Rene Rivera

  • Fixed allowing junk extra characters after a non-string option value. — Rene Rivera

  • Support specifying a single value for choices of an argument. — Rene Rivera

  • Fix various problems with the commands example program. Also now the examples for the documentation are unit tested along with the regular unit tests. — Rene Rivera

  • Tested with Visual Studio 2015, VS 2017, VS 2019, MinGW-64 (gcc 8.1), Linux (clang 3.5, 3.6, 3.7, 3.8, 3.9, 4, 5, 6, 7, 8, 9; gcc 4.8, 4.9, 5, 6, 7, 8, 9), Xcode (9.0, 9.0.1, 9.1, 9.2, 9.3, 9.3.1, 9.4, 9.4.1, 10.0, 10.1, 10.2, 10.2.1, 10.3, 11.0, 11.1, 11.2, 11.2.1), on Azure Pipelines.

9.3. 1.2

Changes:

  • New: One can now accept 1-or-more on bound container arguments by only specifying required() on such arguments. — Rene Rivera

  • New: One can now define a description text to display as general command information with the help::description method. — Rene Rivera

  • New: Add named methods as alternates for operators to add and specify arguments. They follow the familiar Python argparse nomenclature. — Rene Rivera

  • New: Single header file alternative version of the library. For those that need or want such a thing. — Rene Rivera

  • Improve help for arguments that specify a cardinality. — Rene Rivera

  • Addition of more complicated use cases to demonstrate flexibility of library. — Rene Rivera

  • Continued internal clean up of code for stability and easier future enhancements. — Rene Rivera

  • Tested with Visual Studio 2015, VS 2017, VS 2019, MinGW-64 (gcc 8.1), Linux (clang 3.5, 3.6, 3.7, 3.8, 3.9, 4, 5, 6, 7, 8, 9; gcc 4.8, 4.9, 5, 6, 7, 8, 9), Xcode (9.0, 9.0.1, 9.1, 9.2, 9.3, 9.3.1, 9.4, 9.4.1, 10.0, 10.1, 10.2, 10.2.1, 10.3, 11.0, 11.1, 11.2, 11.2.1), on Azure Pipelines.

9.4. 1.1

Changes:

  • New: Add direct Meson Build use support. — RĂ©mi Gastaldi

  • New: Define argument value choices() as either a fixed set or checked by a custom lambda function. — Rene Rivera

  • Fix being able to parse straight from args outside of cli_parser. Which resulted in misleading parsing behavior. Rene Rivera

  • Fix use of cardinality() to correctly constrain bounded cardinality ranges like [0,n], [n,m], [n,n], and [0,0] (unbounded). Rene Rivera

  • Fix use of required() arguments such that the parse errors if they are absent. — girstsf

  • Remove uses of assert macro to avoid internal aborts and make it easier to use as a library. — Rene Rivera

  • Many documentation improvements. — Rene Rivera

  • Tested with Visual Studio 2015, VS 2017, VS 2019, MinGW-64 (gcc 8.1), Linux (clang 3.5, 3.6, 3.7, 3.8, 3.9, 4, 5, 6, 7, 8, 9; gcc 4.8, 4.9, 5, 6, 7, 8, 9), Xcode (9.0, 9.0.1, 9.1, 9.2, 9.3, 9.3.1, 9.4, 9.4.1, 10.0, 10.1, 10.2, 10.2.1, 10.3, 11.0), on Azure Pipelines.

9.5. 1.0

This is the initial base version based on Clara library. Changes from that library:

  • Documentation.

  • Zero dependencies, even internally, by removing TextFlow and Catch bundled libraries.

  • Conform to Pitchfork Layout R1.

  • Tested with Visual Studio 2015, VS 2017, MinGW (gcc 5.3), MinGW-64 (gcc 6.3, 7.3, 8.1), Linux (clang 3.5, 3.8, 3.9, 4, 5; gcc 4.8, 4.9, 5, 6, 7, 8), Xcode (8.3, 9, 9.1, 10.1).

  • Tested with C++ 11, 14, 17, and 2a.

  • New: customization of option prefix and separators.

  • New: support for repeated arguments, from one to many.