Coder Social home page Coder Social logo

kamchatka-volcano / cmdlime Goto Github PK

View Code? Open in Web Editor NEW
85.0 3.0 3.0 709 KB

Possibly the least verbose command line parsing library for C++

License: Microsoft Public License

C++ 99.27% CMake 0.57% C 0.16%
cpp17 header-only command-line-parser cli argument-parser

cmdlime's Introduction

build & test (clang, gcc, MSVC)

cmdlime is a C++17 header-only library for command line parsing that provides a concise, declarative interface without many details to remember. See for yourself:

///examples/ex01.cpp
///
#include <cmdlime/commandlinereader.h>
#include <iostream>

struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int);
    CMDLIME_PARAM(name, std::string);
    CMDLIME_FLAG(verbose);
};

int main(int argc, char** argv)
{
    auto reader = cmdlime::CommandLineReader{"person-finder"};
    auto cfg = reader.read<Cfg>(argc, argv);

    //At this point your config is ready to use
    std::cout << "Looking for person " << cfg.name << " in the region with zip code: " << cfg.zipCode;
    return 0;
}

The default configuration follows the GNU command line options convention, so you can run the program like this:

kamchatka-volcano@home:~$ ./person-finder 684007 --name John --verbose
Looking for person John in the region with zip code: 684007

Please note that in the example above, --name is a parameter, --verbose is a flag, and 684007 is an argument. This naming convention is used throughout this document and in the cmdlime interface.

Table of Contents

Usage

Declaring the config structure

To use cmdlime, you need to create a structure with fields corresponding to the parameters, flags, and arguments that will be read from the command line.

To do this, subclass cmdlime::Config and declare fields using the following macros:

  • CMDLIME_ARG(name, type) - creates a type name; config field and registers it in the parser. Arguments are mapped to the config fields in the order of declaration. Arguments cannot have default values and must be specified in the command line.

  • CMDLIME_ARGLIST(name, listType) - creates listType name; config field and registers it in the parser. listType can be any sequence container that supports the emplace_back operation; within the STL, this includes vector, deque, or list. A config can have only one argument list, and elements are placed into it after all other config arguments have been set, regardless of the order of declaration. The declaration form CMDLIME_ARGLIST(name, listType)(list-initialization) sets the default value of an argument list, making it optional and allowing it to be omitted from the command line without raising an error.

  • CMDLIME_PARAM(name, type) - creates a type name; config field and registers it in the parser. The declaration form CMDLIME_PARAM(name, type)(default value) sets the default value of a parameter, making it optional and allowing it to be omitted from the command line without raising an error. Parameters can also be declared optional by placing them in cmdlime::optional (a std::optional-like wrapper with a similar interface).

  • CMDLIME_PARAMLIST(name, listType) - creates listType name; config field and registers it in the parser. listType can be any sequence container that supports the emplace_back operation; within the STL, this includes vector, deque, or list. A parameter list can be filled by specifying it multiple times in the command line (e.g., --param-list val1 --param-list val2) or by passing a comma-separated value (e.g., --param-list val1,val2). The declaration form CMDLIME_PARAMLIST(name, type)(list-initialization) sets the default value of a parameter list, making it optional and allowing it to be omitted from the command line without raising an error.

  • CMDLIME_FLAG(name) - creates a bool name; config field and registers it in the parser. Flags are always optional and have a default value of false.

  • CMDLIME_EXITFLAG(name) - creates a bool name; config field and registers it in the parser. If at least one exit flag is set, no parsing errors will be raised regardless of the command line's content. The other config fields will be left in an unspecified state. This is useful for flags like --help or --version, when you need to print a message and exit the program without checking the other fields.

  • CMDLIME_SUBCOMMAND(name, type) - creates a cmdlime::optional<type> name; config field for a nested configuration structure and registers it in the parser. type must be a subclass of cmdlime::Config. Subcommands are always optional and have a default value of cmdlime::optional<type>{}.

  • CMDLIME_COMMAND(name, type) - creates a cmdlime::optional<type> name; config field for a nested configuration structure and registers it in the parser. type must be a subclass of cmdlime::Config. Commands are always optional and have a default value of cmdlime::optional<type>{}. If a command is encountered, no parsing errors will be raised for the other config fields, and they will be left in an unspecified state.

Note: Types used for config fields must be default constructable and copyable.

Another note: You don't need to change your code style when declaring config fields - camelCase, snake_case and PascalCase names are supported and read from the kebab-case named parameters in the command line.

Let's alter the config for the person-finder program by adding a required parameter surname and making the name parameter optional:

///examples/ex02.cpp
///
struct Cfg : public cmdlime::Config{ 
    CMDLIME_ARG(zipCode, int);
    CMDLIME_PARAM(surname, std::string);
    CMDLIME_PARAM(name, std::string)();
    CMDLIME_FLAG(verbose);
};

Now parameter --name can be skipped without raising an error:

kamchatka-volcano@home:~$ ./person-finder 684007 --surname Deer
Looking for person Deer in region with zip code: 684007

Supporting non-aggregate config structures

cmdlime relies on aggregate initialization of user-provided structures. If your config object needs to contain private data or virtual functions, it becomes a non-aggregate type. In this case, you must use the following using declaration to inherit cmdlime::Config's constructors: using Config::Config;

struct Cfg : public cmdlime::Config{
    using Config::Config;
    CMDLIME_ARG(zipCode, int);
    CMDLIME_PARAM(name, std::string);
    CMDLIME_FLAG(verbose);
    virtual void write(){}; //virtual method makes Cfg non-aggregate
};

Avoiding macros

If you have a low tolerance for macros, it's possible to register structure fields using the similarly named cmdlime::Config's methods:

    struct Cfg : public cmdlime::Config{
        int zipCode      = arg<&Cfg::zipCode>();
        std::string name = param<&Cfg::name>();
        bool verbose     = flag<&Cfg::verbose>();
    };

Internally, these methods use the nameof library to get config fields' names and types as strings. By default, cmdlime ships without it and these methods aren't available. To use them, you can enable the CMDLIME_USE_NAMEOF CMake variable to automatically download and configure the nameof library, or install it on your system yourself. Note that on the MSVC compiler, some nameof features used by cmdlime require the C++20 standard. This is handled automatically by CMake configuration if MSVC is your default compiler, otherwise you will need to enable the C++20 standard manually.
nameof relies on non-standard functionality of C++ compilers, so if you don't like it, you can use cmdlime without it by providing the names yourself:

    struct Cfg : public cmdlime::Config{
        int zipCode      = arg<&Cfg::zipCode>("zipCode", "int");
        std::string name = param<&Cfg::name>("name", "string");
        bool verbose     = flag<&Cfg::verbose>("verbose"); //flag are always booleans, so we don't need to specify a type's name here
    };

Config structures declared using the macros-free methods are fully compatible with all cmdlime's functionality. Examples use registration with macros as it's the least verbose method.

Using CommandLineReader::exec()

CommandLineReader::exec() is a helper method that hides the error handling boilerplate and adds --help and --version flags processing to your config.

The --help flag shows a detailed help message, which can otherwise be accessed through the CommandLineReader::usageInfoDetailed() method.

The --version flag is enabled only if version info is set in the config with the CommandLineReader::setVersionInfo method.

To use CommandLineReader::exec(), you need to provide an alternative entry point function for your program, which takes a processed config structure object and returns a result code. Let's modify person-finder and see how it works.

///examples/ex03.cpp
///
#include <cmdlime/commandlinereader.h>
#include <iostream>

struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int);
    CMDLIME_PARAM(surname, std::string);
    CMDLIME_PARAM(name, std::string)();
    CMDLIME_FLAG(verbose);
};

int mainApp(const Cfg& cfg)
{
    //Here your config is ready to use
    std::cout << "Looking for person " << cfg.name << " " << cfg.surname << " in the region with zip code: " << cfg.zipCode;
    return 0;
}

int main(int argc, char** argv)
{
    auto reader = cmdlime::CommandLineReader{"person-finder"};
    reader.setVersionInfo("person-finder 1.0");
    return reader.exec<Cfg>(argc, argv, mainApp);
}
kamchatka-volcano@home:~$ ./person-finder --version
person-finder 1.0
kamchatka-volcano@home:~$ ./person-finder --help
Usage: person-finder <zip-code> --surname <string> [params] [flags] 
Arguments:
    <zip-code> (int)          
Parameters:
   -s, --surname <string>     
   -n, --name <string>        optional
Flags:
   -v, --verbose              
       --help                 show usage info and exit
       --version              show version info and exit

As mentioned before, CommandLineReader::exec() is just a helper method, so if you prefer to type a lot, it's possible to implement the same program without using it:

///examples/ex04.cpp
///
#include <cmdlime/commandlinereader.h>
#include <iostream>

struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int);
    CMDLIME_PARAM(name, std::string);
    CMDLIME_FLAG(verbose);
    CMDLIME_EXITFLAG(help);
    CMDLIME_EXITFLAG(version);
};

int main(int argc, char** argv)
{
    auto reader = cmdlime::CommandLineReader{"person-finder"};
    auto cfg = Cfg{};
    try{
        cfg = reader.read<Cfg>(argc, argv);
    }
    catch(const cmdlime::Error& e){
        std::cerr << e.what();
        std::cout << reader.usageInfo<Cfg>();
        return -1;
    }
    if (cfg.help){
        std::cout << reader.usageInfoDetailed<Cfg>();
        return 0;
    }
    if (cfg.version){
        std::cout << "person-finder 1.0";
        return 0;
    }
    //At this point your config is ready to use
    std::cout << "Looking for person " << cfg.name << " in the region with zip code: " << cfg.zipCode;
    return 0;
}

Try to run it and...

Usage: person-finder <zip-code> --name <string> [--verbose] [--help] [--version]
Flag's short name 'v' is already used.

you'll get this error. The thing is, the default command line format supports short names and our flags --verbose and --version ended up having the same short name -v. Read the next section to learn how to fix it.

Unicode support

cmdlime stores strings in std::string, and all operations only require the used encoding to be compatible with ASCII. This means that UTF-8 command line arguments are supported by default. On Windows Unicode command line arguments encoded with UTF-16 can be automatically converted to UTF-8 with the CommandLineReader::exec() overload, which takes a wide string array. In this case, the wmain entry point function should be used:

int wmain(int argc, wchar_t** argv)
{
    auto reader = cmdlime::CommandLineReader{"person-finder"};
    reader.setVersionInfo("person-finder 1.0");
    return reader.exec<Cfg>(argc, argv, mainApp);
}

Additionally, on Windows, cmdlime parameters of types std::wstring and std::filesystem::path are stored with UTF-16 encoding by default. This allows using filesystem paths from your cmdlime config structure with std::fstream and std::filesystem functions without any manual conversions. This functionality can be disabled by setting a CMake variable CMDLIME_NO_WINDOWS_UNICODE, or by manually adding a compiler definition CMDLIME_NO_WINDOWS_UNICODE_SUPPORT to your target.

Filesystem paths support

The std::filesystem::path parameters are automatically converted to the canonical form using the std::filesystem::weakly_canonical() function.

This functionality can be disabled by either setting a CMake variable CMDLIME_NO_CANONICAL_PATHS or manually adding a compiler definition CMDLIME_NO_CANONICAL_PATHS.

Custom names

///examples/ex05.cpp
///
struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int);
    CMDLIME_PARAM(name, std::string);
    CMDLIME_FLAG(verbose);
    CMDLIME_EXITFLAG(help)    << cmdlime::WithoutShortName{};
    CMDLIME_EXITFLAG(version) << cmdlime::WithoutShortName{};
};

Here's the fixed config. Turning off the short name generation for the flag --version resolves the name conflict. When you rely on CommandLineReader::exec() for handling of --help and --version flags, it creates them without short names. At this point, we should do this as well, and all following examples will be based on the version of person-finder program that uses CommandLineReader::exec().

You can use the following objects to customize names generation:
cmdlime::Name{"customName"} - overrides the command line option's name.
cmdlime::ShortName{"customShortName"} - overrides the command line option's short name.
cmdlime::WithoutShortName{} - removes the command line option's short name.
cmdlime::ValueName{} - overrides the parameter's value name in the usage info.

And it's time for another person-finder's rewrite:

///examples/ex06.cpp
///
struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int);
    CMDLIME_PARAM(surname, std::string)  << cmdlime::ValueName{"A-Z..."};
    CMDLIME_PARAM(name, std::string)()   << cmdlime::Name{"first-name"};
    CMDLIME_FLAG(verbose);
};
kamchatka-volcano@home:~$ ./person-finder --help
Usage: person-finder <zip-code> --surname <A-Z...> [params] [flags] 
Arguments:
    <zip-code> (int)             
Parameters:
   -s, --surname <A-Z...>     
   -n, --first-name <string>     optional
Flags:
   -v, --verbose                 
       --help                    show usage info and exit
       --version                 show version info and exit

Auto-generated usage info

cmdlime can generate help messages with the CommandLineReader::usageInfo() and CommandLineReader::usageInfoDetailed() methods. The former is a compact version that can be shown with error messages, while the latter is a detailed version that is printed when the --help flag is set.

You can add more information to the detailed usage info by setting parameter descriptions:

///examples/ex07.cpp
///
struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int)              << "zip code of the searched region";
    CMDLIME_PARAM(surname, std::string)    << "surname of the person to find"       << cmdlime::ValueName{"A-Z..."};
    CMDLIME_PARAM(name, std::string)()     << "name of the person to find"          << cmdlime::Name{"first-name"};
    CMDLIME_FLAG(verbose)                  << "adds more information to the output";
};
kamchatka-volcano@home:~$ ./person-finder --help
Usage: person-finder <zip-code> --surname <A-Z...> [params] [flags] 
Arguments:
    <zip-code> (int)             zip code of the searched region
Parameters:
   -s, --surname <A-Z...>     surname of the person to find
   -n, --first-name <string>     name of the person to find
                                   (optional)
Flags:
   -v, --verbose                 adds more information to the output
       --help                    show usage info and exit
       --version                 show version info and exit

If you don't like auto-generated usage info message you can set your own with CommandLineReader::setUsageInfo() and CommandLineReader::setUsageInfoDetailed()

Supported formats

cmdlime supports several command line naming conventions and unlike other parsing libraries it enforces them strictly, so you can't mix different formats together.

All formats support the -- argument delimiter. After encountering it, all command line options are treated as arguments, even if they start with hyphens.

GNU

All names are in kebab-case.
Parameters and flags prefix: --
Short names are supported. Short names prefix: -
Parameters usage: --parameter value, --parameter=value, -p value or -pvalue
Flags usage: --flag, -f
Flags in short form can be "glued" together: -abc or with one parameter: -fp value

This is the default command line format used by cmdlime. You can choose this format explicitly by using the CommandLineReader<cmdlime::Format::GNU> specialization or its alias GNUCommandLineReader.

///examples/ex08.cpp
///
int main(int argc, char** argv)
{
    auto reader = cmdlime::GNUCommandLineReader{"person-finder"};
    reader.setVersionInfo("person-finder 1.0");
    return reader.exec<Cfg>(argc, argv, mainApp);
}
kamchatka-volcano@home:~$ ./person-finder --help
Usage: person-finder <zip-code> --surname <string> [params] [flags] 
Arguments:
    <zip-code> (int)          zip code of the searched region
Parameters:
   -s, --surname <string>     surname of the person to find
   -n, --name <string>        name of the person to find
                                (optional)
Flags:
   -v, --verbose              adds more information to the output
       --help                 show usage info and exit
       --version              show version info and exit

POSIX

All names consist of a single alphanumeric character.
Parameters and flags prefix: -
Short names aren't supported (the default names are already short enough).
Parameters usage: -p value or -pvalue
Flags usage: -f
Flags in short form can be "glued" together: -abc or with one parameter: -fp value

Parameters and flags must precede the arguments. Other than that, this format is a subset of the GNU format.

You can choose this format by using the CommandLineReader<cmdlime::Format::POSIX> specialization or its alias POSIXCommandLineReader.

///examples/ex09.cpp
///
int main(int argc, char** argv)
{
    auto reader = cmdlime::POSIXCommandLineReader{"person-finder"};
    reader.setVersionInfo("person-finder 1.0");
    return reader.exec<Cfg>(argc, argv, mainApp);
}
kamchatka-volcano@home:~$ ./person-finder -h
Usage: person-finder <zip-code> -s <string> [params] [flags] 
Arguments:
    <zip-code> (int)     zip code of the searched region
Parameters:
   -s <string>           surname of the person to find
   -n <string>           name of the person to find
                           (optional)
Flags:
   -V                    adds more information to the output
   -h                    show usage info and exit
   -v                    show version info and exit

X11

All names are in lowercase.
Parameters and flags prefix: -
Short names aren't supported.
Parameters usage: -parameter value
Flags usage: -flag

You can choose this format by using the CommandLineReader<cmdlime::Format::X11> specialization or its alias X11CommandLineReader.

///examples/ex10.cpp
///
int main(int argc, char** argv)
{
    auto reader = cmdlime::X11CommandLineReader{"person-finder"};
    reader.setVersionInfo("person-finder 1.0");
    return reader.exec<Cfg>(argc, argv, mainApp);
}
kamchatka-volcano@home:~$ ./person-finder -help
Usage: person-finder <zipcode> -surname <string> [params] [flags] 
Arguments:
    <zipcode> (int)      zip code of the searched region
Parameters:
   -surname <string>     surname of the person to find
   -name <string>        name of the person to find
                           (optional)
Flags:
   -verbose              adds more information to the output
   -help                 show usage info and exit
   -version              show version info and exit

Simple format

This format is intended for development purposes of cmdlime, as it's the easiest one to parse. As a result, cmdlime unit tests are probably the only software that uses it.

All names are in camelCase.
Parameters prefix: -
Flags prefix: --
Short names aren't supported.
Parameters usage: -parameter=value
Flags usage: --flag

You can choose this format by using the CommandLineReader<cmdlime::Format::Simple> specialization or its alias SimpleCommandLineReader.

///examples/ex11.cpp
///
int main(int argc, char** argv)
{
    auto reader = cmdlime::SimpleCommandLineReader{"person-finder"};
    reader.setVersionInfo("person-finder 1.0");
    return reader.exec<Cfg>(argc, argv, mainApp);
}
kamchatka-volcano@home:~$ ./person-finder --help
Usage: person-finder <zipCode> -surname=<string> [params] [flags] 
Arguments:
    <zipCode> (int)      zip code of the searched region
Parameters:
   -surname=<string>     surname of the person to find
   -name=<string>        name of the person to find
                           (optional)
Flags:
  --verbose              adds more information to the output
  --help                 show usage info and exit
  --version              show version info and exit

User-defined types support

To use user-defined types in the config, you need to add a specialization of the cmdlime::StringConverter struct and implement its static methods toString and fromString.

For example, let's add a coordinate parameter --coord to the person-finder program.

///examples/ex12.cpp
///
#include <cmdlime/commandlinereader.h>
#include <iostream>

struct Coord{
    double lat;
    double lon;
};

namespace cmdlime{
template<>
struct StringConverter<Coord>{
    static std::optional<std::string> toString(const Coord& coord)
    {
        auto stream = std::stringstream{};
        stream << coord.lat << "-" << coord.lon;
        return stream.str();
    }

    static std::optional<Coord> fromString(const std::string& data)
    {
        auto delimPos = data.find('-');
        if (delimPos == std::string::npos)
            return {};
        auto coord = Coord{};
        coord.lat = std::stod(data.substr(0, delimPos));
        coord.lon = std::stod(data.substr(delimPos + 1, data.size() - delimPos - 1));
        return coord;
    }
};
}

struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int)              << "zip code of the searched region";
    CMDLIME_PARAM(surname, std::string)    << "surname of the person to find";
    CMDLIME_PARAM(name, std::string)()     << "name of the person to find";
    CMDLIME_PARAM(coord, Coord)            << "possible location";
    CMDLIME_FLAG(verbose)                  << "adds more information to the output";
};

int mainApp(const Cfg& cfg)
{
    std::cout << "Looking for person " << cfg.name << " " << cfg.surname << " in the region with zip code: " << cfg.zipCode << std::endl;
    std::cout << "Possible location:" << cfg.coord.lat << " " << cfg.coord.lon;
    return 0;
}

int main(int argc, char** argv)
{
    auto reader = cmdlime::CommandLineReader{"person-finder"};
    reader.setVersionInfo("person-finder 1.0");
    return reader.exec<Cfg>(argc, argv, mainApp);
}
kamchatka-volcano@home:~$ ./person-finder 684007 --surname Deer --coord 53.0-157.25
Looking for person  Deer in the region with zip code: 684007
Possible location:53 157.25

To provide additional information in the error message of the StringConverter::fromString method, you can use the cmdlime::ValidationError exception:

    static std::optional<Coord> fromString(const std::string& data)
    {
        auto delimPos = data.find('-');
        if (delimPos == std::string::npos)
            throw ValidationError{"the coord parameter must be in the format 'lat-lon'"};
        auto coord = Coord{};
        coord.lat = std::stod(data.substr(0, delimPos));
        coord.lon = std::stod(data.substr(delimPos + 1, data.size() - delimPos - 1));
        return coord;
    }

Using subcommands

With cmdlime, it's possible to place a config structure inside another config field by creating a subcommand. Subcommands are specified in the command line by their full name, and all following parameters are used to fill the subcommand's structure instead of the main one.
Let's enhance person-finder program by adding a result recording mode.

///examples/ex13.cpp
///
#include <cmdlime/commandlinereader.h>

struct RecordCfg: public cmdlime::Config{
    CMDLIME_PARAM(file, std::string)() << "save result to file";
    CMDLIME_PARAM(db, std::string)()   << "save result to database";
    CMDLIME_FLAG(detailed)             << "adds more information to the result" << cmdlime::WithoutShortName{};
};

struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int)               << "zip code of the searched region";
    CMDLIME_PARAM(surname, std::string)     << "surname of the person to find";
    CMDLIME_PARAM(name, std::string)()      << "name of the person to find";
    CMDLIME_FLAG(verbose)                   << "adds more information to the output";
    CMDLIME_SUBCOMMAND(record, RecordCfg)   << "record search result";
};

int mainApp(const Cfg& cfg)
{
    std::cout << "Looking for person " << cfg.name << " " << cfg.surname << " in the region with zip code: " << cfg.zipCode << std::endl;
    if (cfg.record.has_value())
        std::cout << "Record settings: " << "file:" << cfg.record->file << " db:" << cfg.record->db << " detailed:" << cfg.record->detailed << std::endl;
    return 0;
}

int main(int argc, char** argv)
{
    return cmdlime::CommandLineReader{"person-finder"}.exec<Cfg>(argc, argv, mainApp);
}

Now, person-finder can be launched like this:

kamchatka-volcano@home:~$ ./person-finder 684007 --surname Deer record --file res.txt --detailed
Looking for person  Deer in the region with zip code: 684007
Record settings: file:res.txt db: detailed:1

Note that all required config fields, such as the zipCode positional argument and the surname parameter, must still be specified. However, some subcommands don't need those parameters. For example, imagine that the person-finder program has a search history mode that doesn't require them and can be launched like this: ./person-finder history without raising a parsing error.

This can be easily achieved by registering history as a command instead of a subcommand. The main difference is that, while a command is also stored in the main config's field, logically it's an alternative configuration, not a part of the original one. When a command is present in the command line, other config fields aren't read at all and are left in an unspecified state.

Let's see how it works:

///examples/ex14.cpp
///
#include <cmdlime/commandlinereader.h>

struct RecordCfg: public cmdlime::Config{
    CMDLIME_PARAM(file, std::string)() << "save result to file";
    CMDLIME_PARAM(db, std::string)()   << "save result to database";
    CMDLIME_FLAG(detailed)             << "hide search results" << cmdlime::WithoutShortName{};
};

struct HistoryCfg: public cmdlime::Config{
    CMDLIME_PARAM(surname, std::string)() << "filter search queries by surname";
    CMDLIME_FLAG(noResults)               << "hide search results";
};

struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int)             << "zip code of the searched region";
    CMDLIME_PARAM(surname, std::string)   << "surname of the person to find";
    CMDLIME_PARAM(name, std::string)()    << "name of the person to find";
    CMDLIME_FLAG(verbose)                 << "adds more information to the output";
    CMDLIME_SUBCOMMAND(record, RecordCfg) << "record search result";
    CMDLIME_COMMAND(history, HistoryCfg)  << "show search history";
};

int mainApp(const Cfg& cfg)
{
    if (cfg.history.has_value()){
        std::cout << "Preparing search history with surname filter:" << cfg.history->surname << std::endl;
        return 0;
    }

    std::cout << "Looking for person " << cfg.name << " " << cfg.surname << " in the region with zip code: " << cfg.zipCode << std::endl;
    if (cfg.record.has_value())
        std::cout << "Record settings: " << "file:" << cfg.record->file << " db:" << cfg.record->db << " detailed:" << cfg.record->detailed << std::endl;

    return 0;
}

int main(int argc, char** argv)
{
    return cmdlime::CommandLineReader{"person-finder"}.exec<Cfg>(argc, argv, mainApp);
}
kamchatka-volcano@home:~$ ./person-finder history --surname Doe
Preparing search history with surname filter:Doe

As you can see, a config structure can have multiple commands, but only one can be specified for each config.

Using validators

Processed command line options can be validated by registering constraint checking functions or callable objects. The signature must be compatible with void (const T&) where T is the type of the validated config structure field. If an option's value is invalid, a validator is required to throw an exception of type cmdlime::ValidationError:

struct Cfg : cmdlime::Config{
    CMDLIME_PARAM(number, int) 
        << [](int paramValue){
            if (paramValue < 0)
                throw cmdlime::ValidationError{"value can't be negative."};
        };
};

Let's improve person-finder by checking that either file or db parameter of the record subcommand is set and all names contain only alphabet characters:

///examples/ex15.cpp
///
#include <cmdlime/commandlinereader.h>
#include <algorithm>

struct EnsureAlpha{
    void operator()(const std::string& name)
    {
        if (!std::all_of(std::begin(name), std::end(name),
                         [](auto ch){
                             return std::isalpha(static_cast<int>(ch));
                         }))
            throw cmdlime::ValidationError{"value must contain alphabet characters only."};
    }
};

struct RecordCfg: public cmdlime::Config{
    CMDLIME_PARAM(file, std::string)() << "save result to file";
    CMDLIME_PARAM(db, std::string)()   << "save result to database";
    CMDLIME_FLAG(detailed)             << "hide search results" << cmdlime::WithoutShortName{};
};

struct HistoryCfg: public cmdlime::Config{
    CMDLIME_PARAM(surname, std::string)() << "filter search queries by surname" << EnsureAlpha{};
    CMDLIME_FLAG(noResults)               << "hide search results";
};

struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int)             << "zip code of the searched region";
    CMDLIME_PARAM(surname, std::string)   << "surname of the person to find" << EnsureAlpha{};
    CMDLIME_PARAM(name, std::string)()    << "name of the person to find" 	 << EnsureAlpha{};
    CMDLIME_FLAG(verbose)                 << "adds more information to the output";
    CMDLIME_SUBCOMMAND(record, RecordCfg) << "record search result"
                                          << [](auto& record){
                                              if (record && record->file.empty() && record->db.empty())
                                                  throw cmdlime::ValidationError{"file or db paremeter must be provided."};
                                              else
                                                  throw std::runtime_error{"ERROR"};
                                          };
    CMDLIME_COMMAND(history, HistoryCfg)  << "show search history";
};

int mainApp(const Cfg& cfg)
{
    if (cfg.history.has_value()){
        std::cout << "Preparing search history with surname filter:" << cfg.history->surname << std::endl;
        return 0;
    }

    std::cout << "Looking for person " << cfg.name << " " << cfg.surname << " in the region with zip code: " << cfg.zipCode << std::endl;
    if (cfg.record.has_value())
        std::cout << "Record settings: " << "file:" << cfg.record->file << " db:" << cfg.record->db << " detailed:" << cfg.record->detailed << std::endl;

    return 0;
}

int main(int argc, char** argv)
{
    return cmdlime::CommandLineReader{"person-finder"}.exec<Cfg>(argc, argv, mainApp);
}

Now you'll get the following error messages if you provide invalid parameters:

kamchatka-volcano@home:~$ ./person-finder --surname Deer 684007 record
Subcommand 'record' is invalid: file or db paremeter must be provided.
Usage: person-finder [commands] <zip-code> --surname <string> [--name <string>] [--verbose] [--help] 
kamchatka-volcano@home:~$ ./person-finder --surname Deer1 684007
Parameter 'surname' is invalid: value must contain alphabet characters only.
Usage: person-finder [commands] <zip-code> --surname <string> [--name <string>] [--verbose] [--help] 

Using post-processors

If you need to modify or validate the config object that is produced by cmdlime::CommandLineReader, you can register the necessary action by creating a specialization of the cmdlime::PostProcessor class template. For instance, let's capitalize a surname parameter only when the optional name parameter is not provided:

///examples/ex16.cpp
///
struct Cfg : public cmdlime::Config{
    CMDLIME_ARG(zipCode, int)             << "zip code of the searched region";
    CMDLIME_PARAM(surname, std::string)   << "surname of the person to find";
    CMDLIME_PARAM(name, std::string)()    << "name of the person to find";
    CMDLIME_FLAG(verbose)                 << "adds more information to the output";
};

namespace cmdlime{
template<>
struct PostProcessor<Cfg> {
    void operator()(Cfg& cfg)
    {
        if (cfg.name.empty())
            std::transform(
                    cfg.surname.begin(),
                    cfg.surname.end(),
                    cfg.surname.begin(),
                    [](const auto& ch)
                    {
                        return sfun::toupper(ch);
                    });
    }
};
}

Installation

Download and link the library from your project's CMakeLists.txt:

cmake_minimum_required(VERSION 3.14)

include(FetchContent)

FetchContent_Declare(cmdlime
    GIT_REPOSITORY "https://github.com/kamchatka-volcano/cmdlime.git"
    GIT_TAG "origin/master"
)
#uncomment if you need to install cmdlime with your target
#set(INSTALL_CMDLIME ON)
FetchContent_MakeAvailable(cmdlime)

add_executable(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PRIVATE cmdlime::cmdlime)

To install the library system-wide, use the following commands:

git clone https://github.com/kamchatka-volcano/cmdlime.git
cd cmdlime
cmake -S . -B build
cmake --build build
cmake --install build

After installation, you can use the find_package() command to make the installed library available inside your project:

find_package(cmdlime 0.10.0 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE cmdlime::cmdlime)   

Running tests

cd cmdlime
cmake -S . -B build -DENABLE_TESTS=ON
cmake --build build
cd build/tests && ctest

Building examples

cd cmdlime
cmake -S . -B build -DENABLE_EXAMPLES=ON
cmake --build build
cd build/examples

License

cmdlime is licensed under the MS-PL license

cmdlime's People

Contributors

kamchatka-volcano avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

cmdlime's Issues

2do: add support for subcommands

Possible interface:

struct CommandCfg: public cmdlime::Config{
    //FLAG, PARAM, ARG, etc
};

struct Cfg : public cmdlime::Config {
    //FLAG, PARAM, ARG, etc
    COMMAND(commandName, CommandCfg); //creates std::optional<CommandCfg> commandName;
};

Add unregistered options handler

Unknown parameters, flags and arguments errors should be emitted with a default unregistered options handler. The user should be able to override it by adding a custom handler to ignore or filter unregistered data. It should be possible to register a handler globally or for a specified Config type.

Building examples/ex01 fails

I tried to build the examples, but I got stuck at the first one.

The current build script does not build them, but I added the following to CMakeLists.txt:

add_executable(ex01 examples/ex01.cpp)
target_link_libraries(ex01 PRIVATE cmdlime)

Trying to build with g++ 9.3.0 on Ubuntu 20.04.2 LTS gives the following errors:

In file included from /src/cmdlime/include/cmdlime/configreader.h:5,
                 from /src/cmdlime/include/cmdlime/gnuconfig.h:3,
                 from /src/cmdlime/include/cmdlime/config.h:2,
                 from /src/cmdlime/examples/ex01.cpp:1:
/src/cmdlime/examples/ex01.cpp: In function ‘int main(int, char**)’:
/src/cmdlime/include/cmdlime/detail/configmacro.h:12:105: error: invalid use of ‘this’ in non-member function
   12 | #define ARG(name, type) type name = cmdlime::detail::ArgCreator<type, std::remove_reference_t<decltype(*this)>>{*this, #name, #type, [this]()->type&{return name;}}
      |                                                                                                         ^~~~
/src/cmdlime/examples/ex01.cpp:7:9: note: in expansion of macro ‘ARG’
    7 |         ARG(zipCode, int);
      |         ^~~
/src/cmdlime/include/cmdlime/detail/configmacro.h:12:95: error: template argument 1 is invalid
   12 | #define ARG(name, type) type name = cmdlime::detail::ArgCreator<type, std::remove_reference_t<decltype(*this)>>{*this, #name, #type, [this]()->type&{return name;}}
      |                                                                                               ^~~~~~~~
/src/cmdlime/examples/ex01.cpp:7:9: note: in expansion of macro ‘ARG’
    7 |         ARG(zipCode, int);
      |         ^~~
In file included from /src/cmdlime/include/cmdlime/configreader.h:5,
                 from /src/cmdlime/include/cmdlime/gnuconfig.h:3,
                 from /src/cmdlime/include/cmdlime/config.h:2,
                 from /src/cmdlime/examples/ex01.cpp:1:
/src/cmdlime/include/cmdlime/detail/configmacro.h:8:109: error: invalid use of ‘this’ in non-member function
    8 | #define PARAM(name, type) type name = cmdlime::detail::ParamCreator<type, std::remove_reference_t<decltype(*this)>>{*this, #name, #type, [this]()->type&{return name;}}
      |                                                                                                             ^~~~
/src/cmdlime/examples/ex01.cpp:8:9: note: in expansion of macro ‘PARAM’
    8 |         PARAM(name, std::string);
      |         ^~~~~
/src/cmdlime/include/cmdlime/detail/configmacro.h:8:99: error: template argument 1 is invalid
    8 | #define PARAM(name, type) type name = cmdlime::detail::ParamCreator<type, std::remove_reference_t<decltype(*this)>>{*this, #name, #type, [this]()->type&{return name;}}
      |                                                                                                   ^~~~~~~~
/src/cmdlime/examples/ex01.cpp:8:9: note: in expansion of macro ‘PARAM’
    8 |         PARAM(name, std::string);
      |         ^~~~~
In file included from /src/cmdlime/include/cmdlime/configreader.h:5,
                 from /src/cmdlime/include/cmdlime/gnuconfig.h:3,
                 from /src/cmdlime/include/cmdlime/config.h:2,
                 from /src/cmdlime/examples/ex01.cpp:1:
/src/cmdlime/include/cmdlime/detail/configmacro.h:12:110: error: template argument 2 is invalid
   12 | #define ARG(name, type) type name = cmdlime::detail::ArgCreator<type, std::remove_reference_t<decltype(*this)>>{*this, #name, #type, [this]()->type&{return name;}}
      |                                                                                                              ^~
/src/cmdlime/examples/ex01.cpp:7:9: note: in expansion of macro ‘ARG’
    7 |         ARG(zipCode, int);
      |         ^~~
In file included from /src/cmdlime/include/cmdlime/configreader.h:5,
                 from /src/cmdlime/include/cmdlime/gnuconfig.h:3,
                 from /src/cmdlime/include/cmdlime/config.h:2,
                 from /src/cmdlime/examples/ex01.cpp:1:
/src/cmdlime/include/cmdlime/detail/configmacro.h:8:114: error: template argument 2 is invalid
    8 | #define PARAM(name, type) type name = cmdlime::detail::ParamCreator<type, std::remove_reference_t<decltype(*this)>>{*this, #name, #type, [this]()->type&{return name;}}
      |                                                                                                                  ^~
/src/cmdlime/examples/ex01.cpp:8:9: note: in expansion of macro ‘PARAM’
    8 |         PARAM(name, std::string);
      |         ^~~~~
In file included from /src/cmdlime/include/cmdlime/detail/gnuformat.h:2,
                 from /src/cmdlime/include/cmdlime/gnuconfig.h:6,
                 from /src/cmdlime/include/cmdlime/config.h:2,
                 from /src/cmdlime/examples/ex01.cpp:1:
/src/cmdlime/include/cmdlime/detail/parser.h: In lambda function:
/src/cmdlime/include/cmdlime/detail/parser.h:68:13: warning: control reaches end of non-void function [-Wreturn-type]
   68 |             [&](auto param){
      |             ^~~~~~~~~~~~~~~~
   69 |                 switch(mode){
      |                 ~~~~~~~~~~~~~
   70 |                 case FindMode::Name:
      |                 ~~~~~~~~~~~~~~~~~~~~
   71 |                     return param->info().name() == name;
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   72 |                 case FindMode::ShortName:
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~
   73 |                     return param->info().shortName() == name;
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   74 |                 case FindMode::All:
      |                 ~~~~~~~~~~~~~~~~~~~
   75 |                     return param->info().name() == name || param->info().shortName() == name;
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   76 |                 }
      |                 ~
   77 |             });
      |             ~
/src/cmdlime/include/cmdlime/detail/parser.h: In lambda function:
/src/cmdlime/include/cmdlime/detail/parser.h:86:13: warning: control reaches end of non-void function [-Wreturn-type]
   86 |             [&](auto paramList){
      |             ^~~~~~~~~~~~~~~~~~~~
   87 |                 switch(mode){
      |                 ~~~~~~~~~~~~~
   88 |                 case FindMode::Name:
      |                 ~~~~~~~~~~~~~~~~~~~~
   89 |                     return paramList->info().name() == name;
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   90 |                 case FindMode::ShortName:
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~
   91 |                     return paramList->info().shortName() == name;
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   92 |                 case FindMode::All:
      |                 ~~~~~~~~~~~~~~~~~~~
   93 |                     return paramList->info().name() == name || paramList->info().shortName() == name;
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   94 |                 }
      |                 ~
   95 |             });
      |             ~
In file included from /src/cmdlime/include/cmdlime/detail/gnuformat.h:2,
                 from /src/cmdlime/include/cmdlime/gnuconfig.h:6,
                 from /src/cmdlime/include/cmdlime/config.h:2,
                 from /src/cmdlime/examples/ex01.cpp:1:
/src/cmdlime/include/cmdlime/detail/parser.h: In lambda function:
/src/cmdlime/include/cmdlime/detail/parser.h:123:13: warning: control reaches end of non-void function [-Wreturn-type]
  123 |             [&](auto flag){
      |             ^~~~~~~~~~~~~~~
  124 |             switch(mode){
      |             ~~~~~~~~~~~~~
  125 |                 case FindMode::Name:
      |                 ~~~~~~~~~~~~~~~~~~~~
  126 |                     return flag->info().name() == name;
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  127 |                 case FindMode::ShortName:
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~
  128 |                     return flag->info().shortName() == name;
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  129 |                 case FindMode::All:
      |                 ~~~~~~~~~~~~~~~~~~~
  130 |                     return flag->info().name() == name || flag->info().shortName() == name;
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  131 |                 }
      |                 ~
  132 |             });
      |             ~

Building with clang++ 10 on the same system seems to work. Building with MSVC (Visual Studio 2019) on Windows gives just a few warnings, so maybe I'm missing something in the g++ case:

c:\src\cmdlime\include\cmdlime\detail\parser.h(77) : warning C4715: '<lambda_d6bf33724169dfe5569812608f3db3ac>::operator()<gsl::not_null<cmdlime::detail::IParam *> >': not all control paths return a value
c:\src\cmdlime\include\cmdlime\detail\parser.h(132) : warning C4715: '<lambda_027da08a6184456737a8fd32a83f0ee3>::operator()<gsl::not_null<cmdlime::detail::IFlag *> >': not all control paths return a value
c:\src\cmdlime\include\cmdlime\detail\parser.h(95) : warning C4715: '<lambda_1cce2af26feeeaaa5a7e6c0694b00dac>::operator()<gsl::not_null<cmdlime::detail::IParamList *> >': not all control paths return a value

I haven't tried the other examples yet.

Improve validation of optional parameters

Validators for optional values should take the contained value as an argument and they shouldn't be invoked for empty values as there's nothing to validate in that case.

Add cmdlime::Format::PowerShell

All names are in PascalCase
Parameters and flags prefix: -
Short names aren't supported
Parameters usage: -Parameter value or -Parameter:value
Flags usage: -Flag
Flags can't be glued together like in POSIX format.
Default help flag: -?.

Reference

Build fails with Clang flags (-Werror and -Wshorten-64-to-32)

Thank you for the great CLI parser! When I include cmdlime in my project, the build fails. The flags that I have added for my project are:

  • -Wall
  • -Wcast-align
  • -Wconversion
  • -Wpedantic
  • -Wextra
  • -Werror
  • -Wnon-virtual-dtor
  • -Woverloaded-virtual
  • -Wsign-conversion

The error is:

In file included from main.cpp:2:
In file included from _deps/cmdlime-src/include/cmdlime/config.h:2:
In file included from _deps/cmdlime-src/include/cmdlime/gnuconfig.h:3:
In file included from _deps/cmdlime-src/include/cmdlime/configreader.h:4:
In file included from _deps/cmdlime-src/include/cmdlime/detail/config.h:3:
_deps/cmdlime-src/include/cmdlime/detail/usageinfocreator.h:321:16: error: implicit conversion loses integer precision: 'unsigned long' to 'int' [-Werror,-Wshorten-64-to-32]
        return size;
        ~~~~~~ ^~~~
_deps/cmdlime-src/include/cmdlime/detail/usageinfocreator.h:101:26: note: in instantiation of member function 'cmdlime::detail::UsageInfoCreator<cmdlime::detail::FormatType::GNU>::maxOptionNameSize' requested here
    , maxOptionNameSize_(maxOptionNameSize() + outputSettings.columnsSpacing)
                         ^
_deps/cmdlime-src/include/cmdlime/detail/config.h:69:16: note: in instantiation of member function 'cmdlime::detail::UsageInfoCreator<cmdlime::detail::FormatType::GNU>::UsageInfoCreator' requested here
        return UsageInfoCreator<formatType>{name, UsageInfoFormat{}, params, paramLists, flags, args, argList_.get()}.create();
               ^
main.cpp:18:22: note: in instantiation of member function 'cmdlime::detail::Config<cmdlime::detail::FormatType::GNU>::usageInfo' requested here
    std::cout << cfg.usageInfo("project");

Here's my main.cpp for reference:

#include <project/version.h>
#include <cmdlime/config.h>

#include <iostream>

auto main(int argc, char** argv) -> int {
  struct Cfg : public cmdlime::Config {
    PARAM(t1, std::string);
    PARAM(t2, int);
    EXITFLAG(help);
    EXITFLAG(version);
  } cfg;

  try {
    cfg.readCommandLine(argc, argv);
  } catch (const cmdlime::Error& e) {
    std::cerr << e.what();
    std::cout << cfg.usageInfo("project");
    return -1;
  }

  if (cfg.help) {
    std::cout << cfg.usageInfoDetailed("project");
    return 0;
  }

  if (cfg.version) {
    std::cout << PROJECT_VERSION;
    return 0;
  }

  return 0;
}

This was tested on macOS 11.3 (Intel architecture) with AppleClang 12.0.5 and cmake 3.20.2.

WithoutShortName{} only works with FLAGs, not with PARAMs

If you modify ex05.cpp, line 8 to be

PARAM(name, std::string) << cmdlime::WithoutShortName{};
You get this error with Clang:

$› clang++ -I ../include/ -std=c++17 ex05.cpp 
In file included from ex05.cpp:1:
In file included from ../include/cmdlime/config.h:2:
In file included from ../include/cmdlime/gnuconfig.h:3:
In file included from ../include/cmdlime/configreader.h:5:
In file included from ../include/cmdlime/detail/configmacro.h:2:
../include/cmdlime/detail/param.h:126:39: error: 'format' is a private member of 'cmdlime::detail::Config<cmdlime::detail::FormatType::GNU>'
        static_assert(Format<TConfig::format>::shortNamesEnabled, "Current command line format doesn't support short names");
                                      ^
ex05.cpp:8:37: note: in instantiation of member function 'cmdlime::detail::ParamCreator<std::__1::basic_string<char>, Cfg>::operator<<'
      requested here
        PARAM(name, std::string)    << cmdlime::WithoutShortName{};
                                    ^
../include/cmdlime/detail/config.h:142:33: note: declared private here
    constexpr static FormatType format = formatType;
                                ^
1 error generated.

$>

Changing the format static member in include/cmdlime/detail/config.h:142:33 from private to public fixes the issue.

Add cmdlime::Format::Windows

All names are in lowercase, by default short names are used, long name can be enabled with << UseLongName{} modifier.
Parameters and flags prefix: /
Parameters usage: /parameter value or /parameter:value
Flags usage: /flag
Flags can't be glued together like in POSIX format.
Default help flag: /?.

Reference

Add cmdlime::Format::GNUWindows

This is the GNU format with the ability to register alternative names starting with a forward slash.

All names are in kebab-case.
Parameters and flags prefix: --
Short names are supported. Short names prefix: -
Parameters usage: --parameter value--parameter=value-p value or -pvalue. /parameter:value and /parameter value are also supported, they have to be enabled with << WithSlashForm{} modifier or <<WithShortNameSlashForm to add /p:value or /p value, or << SlashName{"custom-name"} and << SlashShortName{"c"} to override an automatic name generation.
Flags usage: --flag-f. /flag or /f are also supported. They have to be enabled with << WithSlashForm{} or <<WithShortNameSlashForm modifiers, or << SlashName{"custom-name"} and << SlashShortName{"c"} to override an automatic name generation.
Flags in short form (except those that start with a slash) can be "glued" together: -abc or with one parameter: -fp value.
Default help flag: -h--help-?, or /?.

release version

Hi,

would it be possible to create release version or tag. I use this lib, but I would like to link my app to a specific release.

Thanks

Feature Request: Load configuration parameters from config file

A common use case with command line parsing libraries is to load startup configuration parameters that were not passed on the command line from a ".env" file (simple key- value pair file) or similar.

So my feature request would be to provide support for reading parameters that were not given as command line input from a .env file and if that also fails (file not found, cant be read or key doesn't exist in the file) then fall back to the default argument / behavior registered for the argument.

Example of .env file based on Readme parameters:

zipCode=684007
name=John
verbose=true

I like the library a lot and it perfectly embraces the DRY rule for command line parsing. However in my projects I always end up having to repeat myself anyway because I need to read my environment file for every single parameter that I registered.

Error in string_utils.h

std::string_view has no constructor from iterator and size.

Error in line 158 of string_utils.h

Fix: return std::string_view{ str.data(), res };

Error in line 166 of string_utils.h

Fix return std::string_view{ str.data() + static_cast(res + val.size()), str.size() - (res + val.size()) };

Still no support for MSVC

image

When trying to register a simple configuration like this:

struct IConfiguration
{
    virtual ~IConfiguration() noexcept = default;
};

struct ProgramConfig : public cmdlime::Config, public IConfiguration
{
    CMDLIME_PARAM(...);
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.