Introduction

This library attempts to implement the POSIX.1-2017 standard for parsing arguments passed to an application. These arguments are delivered to the main function as the well known argc and argv parameters. This library allows you to parse the contents of these variables and then provides easy access to the information. The library also contains code to parse configuration files

Options and Operands

The POSIX standard defines two kinds of arguments, the ones whose name start with a hyphen are called options whereas the rest is called operands. An example is:

my-tool [-v] [-o option_arg] [operand...]

The option -o in the example above has an option-argument, the -v does not. Operands usually follow the options, but in the case of libmcfp options and operands can be mixed.

configuration files

The syntax for configuration files is the usual format of name followed by an equals character and then a value terminated by an end of line. E.g.:

name = value

The function parse_config_file() can be used to parse these files. The first variant of this function is noteworthy, it takes an option name and uses its option-argument if specified as replacement for the second parameter which holds the default configuration file name. This file is then searched in the list of directories in the third parameter and when found, the file is parsed and the options in the file are appended to the config instance. Options provided on the command line take precedence.

Installation

Use CMake to install libmcfp.

git clone https://github.com/mhekkel/libmcfp.git
cd libmcfp
cmake -S . -B build
cmake --build build
cmake --install build

Synopsis

// Example of using libmcfp

#include <iostream>
#include <filesystem>

#include <mcfp/mcfp.hpp>

int main(int argc, char * const argv[])
{
    // config is a singleton
    auto &config = mcfp::config::instance();

    // Initialise the config object. This can be done more than once,
    // e.g. when you have different sets of options depending on the
    // first operand.

    config.init(
        // The first parameter is the 'usage' line, used when printing out the options
        "usage: example [options] file",

        // Flag options (not taking a parameter)
        mcfp::make_option("help,h", "Print this help text"),
        mcfp::make_option("verbose,v", "Verbose level, can be specified more than once to increase level"),

        // A couple of options with parameter
        mcfp::make_option<std::string>("config", "Config file to use"),
        mcfp::make_option<std::string>("text", "The text string to echo"),

        // And options with a default parameter
        mcfp::make_option<int>("a", 1, "first parameter for multiplication"),
        mcfp::make_option<float>("b", 2.0f, "second parameter for multiplication"),

        // You can also allow multiple values
        mcfp::make_option<std::vector<std::string>>("c", "Option c, can be specified more than once"),

        // This option is not shown when printing out the options
        mcfp::make_hidden_option("d", "Debug mode")
    );

    // There are two flavors of calls, ones that take an error_code
    // and return the error in that code in case something is wrong.
    // The alternative is calling without an error_code, in which
    // case an exception is thrown when appropriate

    // Parse the command line arguments here

    std::error_code ec;
    config.parse(argc, argv, ec);
    if (ec)
    {
        std::cerr << "Error parsing arguments: " << ec.message() << std::endl;
        exit(1);
    }

    // First check, to see if we need to stop early on

    if (config.has("help") or config.operands().size() != 1)
    {
        // This will print out the 'usage' message with all the visible options
        std::cerr << config << std::endl;
        exit(config.has("help") ? 0 : 1);
    }

    // Configuration files, read it if it exists. If the users
    // specifies an alternative config file, it is an error if that
    // file cannot be found.

    config.parse_config_file("config", "example.conf", { "." }, ec);
    if (ec)
    {
        std::cerr << "Error parsing config file: " << ec.message() << std::endl;
        exit(1);
    }

    // If options are specified more than once, you can get the count

    int VERBOSE = config.count("verbose");

    // Operands are arguments that are not options, e.g. files to act upon

    std::cout << "The first operand is " << config.operands().front() << std::endl;

    // Getting the value of a string option

    auto text = config.get<std::string>("text", ec);
    if (ec)
    {
        std::cerr << "Error getting option text: " << ec.message() << std::endl;
        exit(1);
    }

    std::cout << "Text option is " << text << std::endl;

    // Likewise for numeric options

    int a = config.get<int>("a");
    float b = config.get<float>("b");

    std::cout << "a (" << a << ") * b (" << b << ") = " << a * b << std::endl;

    // And multiple strings

    for (std::string s : config.get<std::vector<std::string>>("c"))
        std::cout << "c: " << s << std::endl;

    return 0;
}