Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

License

NotificationsYou must be signed in to change notification settings

kamchatka-volcano/cmdlime

Repository files navigation

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>structCfg :publiccmdlime::Config{CMDLIME_ARG(zipCode,int);CMDLIME_PARAM(name, std::string);CMDLIME_FLAG(verbose);};intmain(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;return0;}

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 --verboseLooking 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, and684007 is an argument. This naming convention is used throughout this document and in thecmdlime interface.

Table of Contents

Usage

Declaring the config structure

To usecmdlime, 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, subclasscmdlime::Config and declare fields using the following macros:

  • CMDLIME_ARG(name,type) - creates atype 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) - createslistType name; config field and registers it in the parser.listType can be any sequence container that supports theemplace_back operation; within the STL, this includesvector,deque, orlist. 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 formCMDLIME_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 atype name; config field and registers it in the parser.The declaration formCMDLIME_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 incmdlime::optional (astd::optional-like wrapper with a similar interface).

  • CMDLIME_PARAMLIST(name,listType) - createslistType name; config field and registers it in the parser.listType can be any sequence container that supports theemplace_back operation; within the STL, this includesvector,deque, orlist.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 formCMDLIME_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 abool name; config field and registers it in the parser.Flags are always optional and have a default value offalse.

  • CMDLIME_EXITFLAG(name) - creates abool 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 acmdlime::optional<type> name; config field for a nested configuration structure and registers it in the parser.type must be a subclass ofcmdlime::Config. Subcommands are always optional and have a default value ofcmdlime::optional<type>{}.

  • CMDLIME_COMMAND(name,type) - creates acmdlime::optional<type> name; config field for a nested configuration structure and registers it in the parser.type must be a subclass ofcmdlime::Config. Commands are always optional and have a default value ofcmdlime::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 andPascalCase names are supported and read from thekebab-case named parameters in the command line.

Let's alter the config for theperson-finder program by adding a required parametersurname and making thename parameter optional:

///examples/ex02.cpp///structCfg :publiccmdlime::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 DeerLooking 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 followingusing declaration to inheritcmdlime::Config's constructors:using Config::Config;

structCfg :publiccmdlime::Config{using Config::Config;CMDLIME_ARG(zipCode,int);CMDLIME_PARAM(name, std::string);CMDLIME_FLAG(verbose);virtualvoidwrite(){};//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 namedcmdlime::Config's methods:

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

Internally, these methods use thenameof library to get config fields' names andtypes as strings. Note that on the MSVC compiler, somenameof features used bycmdlime require the C++20standard. This is handled automatically by CMake configuration if MSVC is your default compiler, otherwise you will needto enable the C++20 standard manually.
nameof relies on non-standard functionality of C++ compilers, so if you don't like it, you can usecmdlime without it by providing the names yourself:

structCfg :publiccmdlime::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 allcmdlime'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 theCommandLineReader::usageInfoDetailed() method.

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

To useCommandLineReader::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 modifyperson-finder and see how it works.

///examples/ex03.cpp///#include<cmdlime/commandlinereader.h>#include<iostream>structCfg :publiccmdlime::Config{CMDLIME_ARG(zipCode,int);CMDLIME_PARAM(surname, std::string);CMDLIME_PARAM(name, std::string)();CMDLIME_FLAG(verbose);};intmainApp(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;return0;}intmain(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 --versionperson-finder 1.0kamchatka-volcano@home:~$./person-finder --helpUsage: person-finder <zip-code> --surname <string> [params] [flags]Arguments:    <zip-code> (int)Parameters:   -s, --surname <string>   -n, --name <string>        optionalFlags:   -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>structCfg :publiccmdlime::Config{CMDLIME_ARG(zipCode,int);CMDLIME_PARAM(name, std::string);CMDLIME_FLAG(verbose);CMDLIME_EXITFLAG(help);CMDLIME_EXITFLAG(version);};intmain(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>();return0;    }if (cfg.version){        std::cout <<"person-finder 1.0";return0;    }//At this point your config is ready to use    std::cout <<"Looking for person" << cfg.name <<" in the region with zip code:" << cfg.zipCode;return0;}

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--verboseand--version ended up having the same short name-v. Read the next section to learn how to fix it.

Custom names

///examples/ex05.cpp///structCfg :publiccmdlime::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 onCommandLineReader::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 ofperson-finder program that usesCommandLineReader::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 anotherperson-finder's rewrite:

///examples/ex06.cpp///structCfg :publiccmdlime::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 --helpUsage: person-finder <zip-code> --surname <A-Z...> [params] [flags]Arguments:    <zip-code> (int)Parameters:   -s, --surname <A-Z...>   -n, --first-name <string>     optionalFlags:   -v, --verbose       --help                    show usage info and exit       --version                 show version info and exit

Auto-generated usage info

cmdlime can generate help messages with theCommandLineReader::usageInfo() andCommandLineReader::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///structCfg :publiccmdlime::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 --helpUsage: person-finder <zip-code> --surname <A-Z...> [params] [flags]Arguments:    <zip-code> (int)             zip code of the searched regionParameters:   -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 withCommandLineReader::setUsageInfo() andCommandLineReader::setUsageInfoDetailed()

Unicode support

cmdlime stores strings instd::string, and all operations only require the used encoding to be compatible withASCII. This means that UTF-8 command line arguments are supported by default. On Windows Unicode command line argumentsencoded with UTF-16 can be automatically converted to UTF-8 with theCommandLineReader::exec() overload, which takes awide string array. In this case, thewmain 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 typesstd::wstring andstd::filesystem::path are stored withUTF-16 encoding by default. This allows using filesystem paths from your cmdlime config structure withstd::fstreamandstd::filesystem functions without any manual conversions. This functionality can be disabled by setting a CMakevariableCMDLIME_NO_WINDOWS_UNICODE, or by manually adding a compiler definitionCMDLIME_NO_WINDOWS_UNICODE_SUPPORTto your target.

Filesystem paths support

Thestd::filesystem::path parameters are automatically converted to the canonical form usingthestd::filesystem::weakly_canonical() function.

This functionality can be disabled by either setting a CMake variableCMDLIME_NO_CANONICAL_PATHS or manually adding acompiler definitionCMDLIME_NO_CANONICAL_PATHS.

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 inkebab-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 bycmdlime. You can choose this format explicitly by using theCommandLineReader<cmdlime::Format::GNU> specialization or its aliasGNUCommandLineReader.

///examples/ex08.cpp///intmain(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 --helpUsage: person-finder <zip-code> --surname <string> [params] [flags]Arguments:    <zip-code> (int)          zip code of the searched regionParameters:   -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 theCommandLineReader<cmdlime::Format::POSIX> specialization or its aliasPOSIXCommandLineReader.

///examples/ex09.cpp///intmain(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 -hUsage: person-finder <zip-code> -s <string> [params] [flags]Arguments:    <zip-code> (int)     zip code of the searched regionParameters:   -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 inlowercase.
Parameters and flags prefix:-
Short names aren't supported.
Parameters usage:-parameter value
Flags usage:-flag

You can choose this format by using theCommandLineReader<cmdlime::Format::X11> specialization or its aliasX11CommandLineReader.

///examples/ex10.cpp///intmain(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 -helpUsage: person-finder <zipcode> -surname <string> [params] [flags]Arguments:    <zipcode> (int)      zip code of the searched regionParameters:   -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 ofcmdlime, 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 incamelCase.
Parameters prefix:-
Flags prefix:--
Short names aren't supported.
Parameters usage:-parameter=value
Flags usage:--flag

You can choose this format by using theCommandLineReader<cmdlime::Format::Simple> specialization or its aliasSimpleCommandLineReader.

///examples/ex11.cpp///intmain(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 --helpUsage: person-finder <zipCode> -surname=<string> [params] [flags]Arguments:    <zipCode> (int)      zip code of the searched regionParameters:   -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 thecmdlime::StringConverter struct andimplement its static methodstoString and fromString.

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

///examples/ex12.cpp///#include<cmdlime/commandlinereader.h>#include<iostream>structCoord{double lat;double lon;};namespacecmdlime{template<>structStringConverter<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;    }};}structCfg :publiccmdlime::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";};intmainApp(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;return0;}intmain(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.25Looking for person  Deer in the region with zip code: 684007Possible location:53 157.25

To provide additional information in the error message of the StringConverter::fromString method, you can use thecmdlime::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

Withcmdlime, 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 thesubcommand's structure instead of the main one.
Let's enhanceperson-finder program by adding a result recording mode.

///examples/ex13.cpp///#include<cmdlime/commandlinereader.h>structRecordCfg:publiccmdlime::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{};};structCfg :publiccmdlime::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";};intmainApp(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;return0;}intmain(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 --detailedLooking for person  Deer in the region with zip code: 684007Record settings: file:res.txt db: detailed:1

Note that all required config fields, such as thezipCode positional argument and thesurname parameter, must still be specified. However, some subcommands don't need those parameters. For example, imagine that theperson-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>structRecordCfg:publiccmdlime::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{};};structHistoryCfg:publiccmdlime::Config{CMDLIME_PARAM(surname, std::string)() <<"filter search queries by surname";CMDLIME_FLAG(noResults)               <<"hide search results";};structCfg :publiccmdlime::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";};intmainApp(const Cfg& cfg){if (cfg.history.has_value()){        std::cout <<"Preparing search history with surname filter:" << cfg.history->surname << std::endl;return0;    }    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;return0;}intmain(int argc,char** argv){return cmdlime::CommandLineReader{"person-finder"}.exec<Cfg>(argc, argv, mainApp);}
kamchatka-volcano@home:~$./person-finderhistory --surname DoePreparing 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 withvoid (const T&) whereT 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 typecmdlime::ValidationError:

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

Let's improveperson-finder by checking that eitherfile ordb parameter of therecord subcommand is set and all names contain only alphabet characters:

///examples/ex15.cpp///#include<cmdlime/commandlinereader.h>#include<algorithm>structEnsureAlpha{voidoperator()(const std::string& name)    {if (!std::all_of(std::begin(name),std::end(name),                         [](auto ch){returnstd::isalpha(static_cast<int>(ch));                         }))throw cmdlime::ValidationError{"value must contain alphabet characters only."};    }};structRecordCfg:publiccmdlime::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{};};structHistoryCfg:publiccmdlime::Config{CMDLIME_PARAM(surname, std::string)() <<"filter search queries by surname" << EnsureAlpha{};CMDLIME_FLAG(noResults)               <<"hide search results";};structCfg :publiccmdlime::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."};elsethrow std::runtime_error{"ERROR"};                                          };CMDLIME_COMMAND(history, HistoryCfg)  <<"show search history";};intmainApp(const Cfg& cfg){if (cfg.history.has_value()){        std::cout <<"Preparing search history with surname filter:" << cfg.history->surname << std::endl;return0;    }    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;return0;}intmain(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 recordSubcommand '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 684007Parameter '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 bycmdlime::CommandLineReader, you can registerthe necessary action by creating a specialization of thecmdlime::PostProcessor class template. For instance, let'scapitalize a surname parameter only when the optional name parameter is not provided:

///examples/ex16.cpp///structCfg :publiccmdlime::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";};namespacecmdlime{template<>structPostProcessor<Cfg> {voidoperator()(Cfg& cfg)    {if (cfg.name.empty())std::transform(                    cfg.surname.begin(),                    cfg.surname.end(),                    cfg.surname.begin(),                    [](constauto& ch)                    {returnsfun::toupper(ch);                    });    }};}

Installation

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

cmake_minimum_required(VERSION 3.18)include(FetchContent)FetchContent_Declare(cmdlime    URL https://github.com/kamchatka-volcano/cmdlime/releases/download/v2.8.0/cmdlime-v2.8.0.zip)#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)

Prefer using the release ZIP archive with FetchContent, as it is fully self-contained and avoids spending additionaltime downloading the library dependencies during the CMake configuration step.

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

git clone https://github.com/kamchatka-volcano/cmdlime.gitcd cmdlimecmake -S . -B buildcmake --build buildcmake --install build

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

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

Running tests

cd cmdlimecmake -S . -B build -DENABLE_TESTS=ONcmake --build buildcd build/tests && ctest

Building examples

cd cmdlimecmake -S . -B build -DENABLE_EXAMPLES=ONcmake --build buildcd build/examples

License

cmdlime is licensed under theMS-PL license

About

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

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages


[8]ページ先頭

©2009-2026 Movatter.jp