{"id":15048039,"url":"https://github.com/kamchatka-volcano/cmdlime","last_synced_at":"2025-08-24T14:40:30.366Z","repository":{"id":43927467,"uuid":"363762365","full_name":"kamchatka-volcano/cmdlime","owner":"kamchatka-volcano","description":"Possibly the least verbose command line parsing library for C++","archived":false,"fork":false,"pushed_at":"2025-01-03T14:58:16.000Z","size":619,"stargazers_count":86,"open_issues_count":7,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-02T11:08:17.331Z","etag":null,"topics":["argument-parser","cli","command-line-parser","cpp17","header-only"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"ms-pl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kamchatka-volcano.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-02T22:22:51.000Z","updated_at":"2025-03-03T03:36:35.000Z","dependencies_parsed_at":"2025-02-07T03:02:59.300Z","dependency_job_id":"099f305f-a1a2-4494-8e3b-d4e832d1edd0","html_url":"https://github.com/kamchatka-volcano/cmdlime","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fcmdlime","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fcmdlime/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fcmdlime/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamchatka-volcano%2Fcmdlime/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kamchatka-volcano","download_url":"https://codeload.github.com/kamchatka-volcano/cmdlime/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248045233,"owners_count":21038553,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["argument-parser","cli","command-line-parser","cpp17","header-only"],"created_at":"2024-09-24T21:07:18.135Z","updated_at":"2025-08-24T14:40:30.322Z","avatar_url":"https://github.com/kamchatka-volcano.png","language":"C++","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg height=\"128\" src=\"doc/logo.jpg\"/\u003e  \n\u003c/p\u003e\n\n[![build \u0026 test (clang, gcc, MSVC)](https://github.com/kamchatka-volcano/cmdlime/actions/workflows/build_and_test.yml/badge.svg?branch=master)](https://github.com/kamchatka-volcano/cmdlime/actions/workflows/build_and_test.yml)\n\n**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:\n\n```C++\n///examples/ex01.cpp\n///\n#include \u003ccmdlime/commandlinereader.h\u003e\n#include \u003ciostream\u003e\n\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int);\n    CMDLIME_PARAM(name, std::string);\n    CMDLIME_FLAG(verbose);\n};\n\nint main(int argc, char** argv)\n{\n    auto reader = cmdlime::CommandLineReader{\"person-finder\"};\n    auto cfg = reader.read\u003cCfg\u003e(argc, argv);\n\n    //At this point your config is ready to use\n    std::cout \u003c\u003c \"Looking for person \" \u003c\u003c cfg.name \u003c\u003c \" in the region with zip code: \" \u003c\u003c cfg.zipCode;\n    return 0;\n}\n```\n\nThe default configuration follows the GNU command line options convention, so you can run the program like this:\n```console\nkamchatka-volcano@home:~$ ./person-finder 684007 --name John --verbose\nLooking for person John in the region with zip code: 684007\n```\n\nPlease 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.\n\n\n## Table of Contents\n*    [Usage](#usage)\n     * [Declaring the config structure](#declaring-the-config-structure)\n     * [Supporting non-aggregate config structures](#supporting-non-aggregate-config-structures)\n     * [Avoiding macros](#avoiding-macros)\n     * [Using CommandLineReader::exec()](#using-commandlinereaderexec)\n     * [Custom names](#custom-names)\n     * [Auto-generated usage info](#auto-generated-usage-info)\n     * [Unicode support](#unicode-support)\n     * [Filesystem paths support](#filesystem-paths-support)\n     * [Supported formats](#supported-formats)\n         * [GNU](#gnu)\n         * [POSIX](#posix)\n         * [X11](#x11)\n         * [Simple format](#simple-format)\n     * [User-defined types support](#user-defined-types-support)\n     * [Using subcommands](#using-subcommands)\n     * [Using validators](#using-validators)\n     * [Using post-processors](#using-post-processors)\n*    [Installation](#installation)\n*    [Running tests](#running-tests)\n*    [Building examples](#building-examples)\n*    [License](#license)\n\n## Usage\n\n\n### Declaring the config structure\n\nTo 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.\n\nTo do this, subclass `cmdlime::Config` and declare fields using the following macros:\n\n- **CMDLIME_ARG(`name`, `type`)** - creates a `type name;` config field and registers it in the parser.\nArguments are mapped to the config fields in the order of declaration. Arguments cannot have default values and must be specified in the command line.\n\n- **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.\n\n- **CMDLIME_PARAM(`name`, `type`)** - creates a `type name;` config field and registers it in the parser.\nThe 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).\n\n- **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`.\nA 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`).\nThe 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.\n- **CMDLIME_FLAG(`name`)** - creates a `bool name;` config field and registers it in the parser.\nFlags are always optional and have a default value of `false`.\n\n- **CMDLIME_EXITFLAG(`name`)** - creates a `bool name;` config field and registers it in the parser.\nIf 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.\n- **CMDLIME_SUBCOMMAND(`name`, `type`)** - creates a `cmdlime::optional\u003ctype\u003e 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\u003ctype\u003e{}`.\n\n- **CMDLIME_COMMAND(`name`, `type`)** - creates a `cmdlime::optional\u003ctype\u003e 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\u003ctype\u003e{}`. 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.\n\n*Note: Types used for config fields must be default constructable and copyable.*  \n\n*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.*   \n\nLet's alter the config for the `person-finder` program by adding a required parameter `surname` and making the `name` parameter optional:\n```C++\n///examples/ex02.cpp\n///\nstruct Cfg : public cmdlime::Config{ \n    CMDLIME_ARG(zipCode, int);\n    CMDLIME_PARAM(surname, std::string);\n    CMDLIME_PARAM(name, std::string)();\n    CMDLIME_FLAG(verbose);\n};\n```\nNow parameter `--name` can be skipped without raising an error:\n```console\nkamchatka-volcano@home:~$ ./person-finder 684007 --surname Deer\nLooking for person Deer in region with zip code: 684007\n```\n\n### Supporting non-aggregate config structures\n`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;`\n```cpp\nstruct Cfg : public cmdlime::Config{\n    using Config::Config;\n    CMDLIME_ARG(zipCode, int);\n    CMDLIME_PARAM(name, std::string);\n    CMDLIME_FLAG(verbose);\n    virtual void write(){}; //virtual method makes Cfg non-aggregate\n};\n```\n\n### Avoiding macros\nIf you have a low tolerance for macros, it's possible to register structure fields using the similarly named `cmdlime::Config`'s methods:\n```c++\n    struct Cfg : public cmdlime::Config{\n        int zipCode      = arg\u003c\u0026Cfg::zipCode\u003e();\n        std::string name = param\u003c\u0026Cfg::name\u003e();\n        bool verbose     = flag\u003c\u0026Cfg::verbose\u003e();\n    };\n```\n\nInternally, these methods use the [nameof](https://github.com/Neargye/nameof) library to get config fields' names and\ntypes as strings. Note that on the MSVC compiler, some **nameof** features used by **cmdlime** require the C++20\nstandard. This is handled automatically by CMake configuration if MSVC is your default compiler, otherwise you will need\nto enable the C++20 standard manually.  \n**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:\n\n```c++\n    struct Cfg : public cmdlime::Config{\n        int zipCode      = arg\u003c\u0026Cfg::zipCode\u003e(\"zipCode\", \"int\");\n        std::string name = param\u003c\u0026Cfg::name\u003e(\"name\", \"string\");\n        bool verbose     = flag\u003c\u0026Cfg::verbose\u003e(\"verbose\"); //flag are always booleans, so we don't need to specify a type's name here\n    };\n``` \n\nConfig 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.\n\n\n### Using CommandLineReader::exec()\n\n`CommandLineReader::exec()` is a helper method that hides the error handling boilerplate and adds `--help` and `--version` flags processing to your config.\n\nThe `--help` flag shows a detailed help message, which can otherwise be accessed through the `CommandLineReader::usageInfoDetailed()` method.\n\nThe `--version` flag is enabled only if version info is set in the config with the `CommandLineReader::setVersionInfo` method.\n\nTo 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.\n\n```C++\n///examples/ex03.cpp\n///\n#include \u003ccmdlime/commandlinereader.h\u003e\n#include \u003ciostream\u003e\n\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int);\n    CMDLIME_PARAM(surname, std::string);\n    CMDLIME_PARAM(name, std::string)();\n    CMDLIME_FLAG(verbose);\n};\n\nint mainApp(const Cfg\u0026 cfg)\n{\n    //Here your config is ready to use\n    std::cout \u003c\u003c \"Looking for person \" \u003c\u003c cfg.name \u003c\u003c \" \" \u003c\u003c cfg.surname \u003c\u003c \" in the region with zip code: \" \u003c\u003c cfg.zipCode;\n    return 0;\n}\n\nint main(int argc, char** argv)\n{\n    auto reader = cmdlime::CommandLineReader{\"person-finder\"};\n    reader.setVersionInfo(\"person-finder 1.0\");\n    return reader.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n```\n```console\nkamchatka-volcano@home:~$ ./person-finder --version\nperson-finder 1.0\nkamchatka-volcano@home:~$ ./person-finder --help\nUsage: person-finder \u003czip-code\u003e --surname \u003cstring\u003e [params] [flags] \nArguments:\n    \u003czip-code\u003e (int)          \nParameters:\n   -s, --surname \u003cstring\u003e     \n   -n, --name \u003cstring\u003e        optional\nFlags:\n   -v, --verbose              \n       --help                 show usage info and exit\n       --version              show version info and exit\n```\n\nAs 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:\n```C++\n///examples/ex04.cpp\n///\n#include \u003ccmdlime/commandlinereader.h\u003e\n#include \u003ciostream\u003e\n\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int);\n    CMDLIME_PARAM(name, std::string);\n    CMDLIME_FLAG(verbose);\n    CMDLIME_EXITFLAG(help);\n    CMDLIME_EXITFLAG(version);\n};\n\nint main(int argc, char** argv)\n{\n    auto reader = cmdlime::CommandLineReader{\"person-finder\"};\n    auto cfg = Cfg{};\n    try{\n        cfg = reader.read\u003cCfg\u003e(argc, argv);\n    }\n    catch(const cmdlime::Error\u0026 e){\n        std::cerr \u003c\u003c e.what();\n        std::cout \u003c\u003c reader.usageInfo\u003cCfg\u003e();\n        return -1;\n    }\n    if (cfg.help){\n        std::cout \u003c\u003c reader.usageInfoDetailed\u003cCfg\u003e();\n        return 0;\n    }\n    if (cfg.version){\n        std::cout \u003c\u003c \"person-finder 1.0\";\n        return 0;\n    }\n    //At this point your config is ready to use\n    std::cout \u003c\u003c \"Looking for person \" \u003c\u003c cfg.name \u003c\u003c \" in the region with zip code: \" \u003c\u003c cfg.zipCode;\n    return 0;\n}\n\n\n```\n\nTry to run it and...\n\n```console\nUsage: person-finder \u003czip-code\u003e --name \u003cstring\u003e [--verbose] [--help] [--version]\nFlag's short name 'v' is already used.\n```\n\nyou'll get this error. The thing is, the default command line format supports short names and our flags `--verbose`\nand `--version` ended up having the same short name `-v`. Read the next section to learn how to fix it.\n\n### Custom names\n\n```C++\n///examples/ex05.cpp\n///\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int);\n    CMDLIME_PARAM(name, std::string);\n    CMDLIME_FLAG(verbose);\n    CMDLIME_EXITFLAG(help)    \u003c\u003c cmdlime::WithoutShortName{};\n    CMDLIME_EXITFLAG(version) \u003c\u003c cmdlime::WithoutShortName{};\n};\n\n```\nHere'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()`.\n\n\nYou can use the following objects to customize names generation:  \n`cmdlime::Name{\"customName\"}` - overrides the command line option's name.  \n`cmdlime::ShortName{\"customShortName\"}` - overrides the command line option's short name.  \n`cmdlime::WithoutShortName{}` - removes the command line option's short name.  \n`cmdlime::ValueName{}` - overrides the parameter's value name in the usage info. \n\nAnd it's time for another `person-finder`'s rewrite:\n```C++\n///examples/ex06.cpp\n///\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int);\n    CMDLIME_PARAM(surname, std::string)  \u003c\u003c cmdlime::ValueName{\"A-Z...\"};\n    CMDLIME_PARAM(name, std::string)()   \u003c\u003c cmdlime::Name{\"first-name\"};\n    CMDLIME_FLAG(verbose);\n};\n```\n```console\nkamchatka-volcano@home:~$ ./person-finder --help\nUsage: person-finder \u003czip-code\u003e --surname \u003cA-Z...\u003e [params] [flags] \nArguments:\n    \u003czip-code\u003e (int)             \nParameters:\n   -s, --surname \u003cA-Z...\u003e     \n   -n, --first-name \u003cstring\u003e     optional\nFlags:\n   -v, --verbose                 \n       --help                    show usage info and exit\n       --version                 show version info and exit\n\n```\n### Auto-generated usage info\n\n**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.\n\nYou can add more information to the detailed usage info by setting parameter descriptions:\n```C++\n///examples/ex07.cpp\n///\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int)              \u003c\u003c \"zip code of the searched region\";\n    CMDLIME_PARAM(surname, std::string)    \u003c\u003c \"surname of the person to find\"       \u003c\u003c cmdlime::ValueName{\"A-Z...\"};\n    CMDLIME_PARAM(name, std::string)()     \u003c\u003c \"name of the person to find\"          \u003c\u003c cmdlime::Name{\"first-name\"};\n    CMDLIME_FLAG(verbose)                  \u003c\u003c \"adds more information to the output\";\n};\n\n```\n```console\nkamchatka-volcano@home:~$ ./person-finder --help\nUsage: person-finder \u003czip-code\u003e --surname \u003cA-Z...\u003e [params] [flags] \nArguments:\n    \u003czip-code\u003e (int)             zip code of the searched region\nParameters:\n   -s, --surname \u003cA-Z...\u003e     surname of the person to find\n   -n, --first-name \u003cstring\u003e     name of the person to find\n                                   (optional)\nFlags:\n   -v, --verbose                 adds more information to the output\n       --help                    show usage info and exit\n       --version                 show version info and exit\n```\n\nIf you don't like auto-generated usage info message you can set your own with `CommandLineReader::setUsageInfo()` and `CommandLineReader::setUsageInfoDetailed()`\n\n### Unicode support\n\n`cmdlime` stores strings in `std::string`, and all operations only require the used encoding to be compatible with\nASCII. This means that UTF-8 command line arguments are supported by default. On Windows Unicode command line arguments\nencoded with UTF-16 can be automatically converted to UTF-8 with the `CommandLineReader::exec()` overload, which takes a\nwide string array. In this case, the `wmain` entry point function should be used:\n\n```\nint wmain(int argc, wchar_t** argv)\n{\n    auto reader = cmdlime::CommandLineReader{\"person-finder\"};\n    reader.setVersionInfo(\"person-finder 1.0\");\n    return reader.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n```\n\nAdditionally, on Windows, `cmdlime` parameters of types `std::wstring` and `std::filesystem::path` are stored with\nUTF-16 encoding by default. This allows using filesystem paths from your cmdlime config structure with `std::fstream`\nand `std::filesystem` functions without any manual conversions. This functionality can be disabled by setting a CMake\nvariable `CMDLIME_NO_WINDOWS_UNICODE`, or by manually adding a compiler definition `CMDLIME_NO_WINDOWS_UNICODE_SUPPORT`\nto your target.\n\n### Filesystem paths support\n\nThe `std::filesystem::path` parameters are automatically converted to the canonical form using\nthe `std::filesystem::weakly_canonical()` function.\n\nThis functionality can be disabled by either setting a CMake variable `CMDLIME_NO_CANONICAL_PATHS` or manually adding a\ncompiler definition `CMDLIME_NO_CANONICAL_PATHS`.\n\n\n### Supported formats\n\n**cmdlime** supports several command line naming conventions and unlike other parsing libraries it enforces them strictly, so you can't mix different formats together.\n\nAll formats support the `--` argument delimiter. After encountering it, all command line options are treated as arguments, even if they start with hyphens.\n\n#### GNU\n\nAll names are in `kebab-case`.  \nParameters and flags prefix: `--`  \nShort names are supported. Short names prefix: `-`  \nParameters usage: `--parameter value`, `--parameter=value`, `-p value` or `-pvalue`  \nFlags usage: `--flag`, `-f`  \nFlags in short form can be \"glued\" together: `-abc` or with one parameter: `-fp value`\n\nThis is the default command line format used by **cmdlime**. You can choose this format explicitly by using the `CommandLineReader\u003ccmdlime::Format::GNU\u003e` specialization or its alias `GNUCommandLineReader`.\n\n```C++\n///examples/ex08.cpp\n///\nint main(int argc, char** argv)\n{\n    auto reader = cmdlime::GNUCommandLineReader{\"person-finder\"};\n    reader.setVersionInfo(\"person-finder 1.0\");\n    return reader.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n```\n\n```console\nkamchatka-volcano@home:~$ ./person-finder --help\nUsage: person-finder \u003czip-code\u003e --surname \u003cstring\u003e [params] [flags] \nArguments:\n    \u003czip-code\u003e (int)          zip code of the searched region\nParameters:\n   -s, --surname \u003cstring\u003e     surname of the person to find\n   -n, --name \u003cstring\u003e        name of the person to find\n                                (optional)\nFlags:\n   -v, --verbose              adds more information to the output\n       --help                 show usage info and exit\n       --version              show version info and exit\n```\n\n#### POSIX\n\nAll names consist of a single alphanumeric character.  \nParameters and flags prefix: `-`  \nShort names aren't supported (the default names are already short enough).  \nParameters usage: `-p value` or `-pvalue`  \nFlags usage: `-f`  \nFlags in short form can be \"glued\" together: `-abc` or with one parameter: `-fp value`\n\nParameters and flags must precede the arguments. Other than that, this format is a subset of the GNU format.\n\nYou can choose this format by using the `CommandLineReader\u003ccmdlime::Format::POSIX\u003e` specialization or its alias `POSIXCommandLineReader`.\n```C++\n///examples/ex09.cpp\n///\nint main(int argc, char** argv)\n{\n    auto reader = cmdlime::POSIXCommandLineReader{\"person-finder\"};\n    reader.setVersionInfo(\"person-finder 1.0\");\n    return reader.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n\n```\n```console\nkamchatka-volcano@home:~$ ./person-finder -h\nUsage: person-finder \u003czip-code\u003e -s \u003cstring\u003e [params] [flags] \nArguments:\n    \u003czip-code\u003e (int)     zip code of the searched region\nParameters:\n   -s \u003cstring\u003e           surname of the person to find\n   -n \u003cstring\u003e           name of the person to find\n                           (optional)\nFlags:\n   -V                    adds more information to the output\n   -h                    show usage info and exit\n   -v                    show version info and exit\n```\n\n#### X11\n\nAll names are in `lowercase`.  \nParameters and flags prefix: `-`  \nShort names aren't supported.  \nParameters usage: `-parameter value`  \nFlags usage: `-flag`\n\nYou can choose this format by using the `CommandLineReader\u003ccmdlime::Format::X11\u003e` specialization or its alias `X11CommandLineReader`.\n\n```C++\n///examples/ex10.cpp\n///\nint main(int argc, char** argv)\n{\n    auto reader = cmdlime::X11CommandLineReader{\"person-finder\"};\n    reader.setVersionInfo(\"person-finder 1.0\");\n    return reader.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n```\n```console\nkamchatka-volcano@home:~$ ./person-finder -help\nUsage: person-finder \u003czipcode\u003e -surname \u003cstring\u003e [params] [flags] \nArguments:\n    \u003czipcode\u003e (int)      zip code of the searched region\nParameters:\n   -surname \u003cstring\u003e     surname of the person to find\n   -name \u003cstring\u003e        name of the person to find\n                           (optional)\nFlags:\n   -verbose              adds more information to the output\n   -help                 show usage info and exit\n   -version              show version info and exit\n```\n\n#### Simple format\n\nThis 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.\n\nAll names are in `camelCase`.  \nParameters prefix: `-`  \nFlags prefix: `--`  \nShort names aren't supported.  \nParameters usage: `-parameter=value`   \nFlags usage: `--flag`\n\nYou can choose this format by using the `CommandLineReader\u003ccmdlime::Format::Simple\u003e` specialization or its alias `SimpleCommandLineReader`.\n\n\n```C++\n///examples/ex11.cpp\n///\nint main(int argc, char** argv)\n{\n    auto reader = cmdlime::SimpleCommandLineReader{\"person-finder\"};\n    reader.setVersionInfo(\"person-finder 1.0\");\n    return reader.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n```\n```console\nkamchatka-volcano@home:~$ ./person-finder --help\nUsage: person-finder \u003czipCode\u003e -surname=\u003cstring\u003e [params] [flags] \nArguments:\n    \u003czipCode\u003e (int)      zip code of the searched region\nParameters:\n   -surname=\u003cstring\u003e     surname of the person to find\n   -name=\u003cstring\u003e        name of the person to find\n                           (optional)\nFlags:\n  --verbose              adds more information to the output\n  --help                 show usage info and exit\n  --version              show version info and exit\n```\n\n### User-defined types support\n\nTo use user-defined types in the config, you need to add a specialization of the `cmdlime::StringConverter` struct and\nimplement its static methods `toString and fromString`.\n\nFor example, let's add a coordinate parameter `--coord` to the `person-finder` program.\n\n```C++\n///examples/ex12.cpp\n///\n#include \u003ccmdlime/commandlinereader.h\u003e\n#include \u003ciostream\u003e\n\nstruct Coord{\n    double lat;\n    double lon;\n};\n\nnamespace cmdlime{\ntemplate\u003c\u003e\nstruct StringConverter\u003cCoord\u003e{\n    static std::optional\u003cstd::string\u003e toString(const Coord\u0026 coord)\n    {\n        auto stream = std::stringstream{};\n        stream \u003c\u003c coord.lat \u003c\u003c \"-\" \u003c\u003c coord.lon;\n        return stream.str();\n    }\n\n    static std::optional\u003cCoord\u003e fromString(const std::string\u0026 data)\n    {\n        auto delimPos = data.find('-');\n        if (delimPos == std::string::npos)\n            return {};\n        auto coord = Coord{};\n        coord.lat = std::stod(data.substr(0, delimPos));\n        coord.lon = std::stod(data.substr(delimPos + 1, data.size() - delimPos - 1));\n        return coord;\n    }\n};\n}\n\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int)              \u003c\u003c \"zip code of the searched region\";\n    CMDLIME_PARAM(surname, std::string)    \u003c\u003c \"surname of the person to find\";\n    CMDLIME_PARAM(name, std::string)()     \u003c\u003c \"name of the person to find\";\n    CMDLIME_PARAM(coord, Coord)            \u003c\u003c \"possible location\";\n    CMDLIME_FLAG(verbose)                  \u003c\u003c \"adds more information to the output\";\n};\n\nint mainApp(const Cfg\u0026 cfg)\n{\n    std::cout \u003c\u003c \"Looking for person \" \u003c\u003c cfg.name \u003c\u003c \" \" \u003c\u003c cfg.surname \u003c\u003c \" in the region with zip code: \" \u003c\u003c cfg.zipCode \u003c\u003c std::endl;\n    std::cout \u003c\u003c \"Possible location:\" \u003c\u003c cfg.coord.lat \u003c\u003c \" \" \u003c\u003c cfg.coord.lon;\n    return 0;\n}\n\nint main(int argc, char** argv)\n{\n    auto reader = cmdlime::CommandLineReader{\"person-finder\"};\n    reader.setVersionInfo(\"person-finder 1.0\");\n    return reader.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n```\n\n```console\nkamchatka-volcano@home:~$ ./person-finder 684007 --surname Deer --coord 53.0-157.25\nLooking for person  Deer in the region with zip code: 684007\nPossible location:53 157.25\n```\n\nTo provide additional information in the error message of the StringConverter::fromString method, you can use the\ncmdlime::ValidationError exception:\n\n```cpp\n    static std::optional\u003cCoord\u003e fromString(const std::string\u0026 data)\n    {\n        auto delimPos = data.find('-');\n        if (delimPos == std::string::npos)\n            throw ValidationError{\"the coord parameter must be in the format 'lat-lon'\"};\n        auto coord = Coord{};\n        coord.lat = std::stod(data.substr(0, delimPos));\n        coord.lon = std::stod(data.substr(delimPos + 1, data.size() - delimPos - 1));\n        return coord;\n    }\n```\n\n### Using subcommands\n\nWith **cmdlime**, it's possible to place a config structure inside another config field by creating a subcommand.\nSubcommands are specified in the command line by their full name, and all following parameters are used to fill the\nsubcommand's structure instead of the main one.  \nLet's enhance `person-finder` program by adding a result recording mode.\n\n```C++\n///examples/ex13.cpp\n///\n#include \u003ccmdlime/commandlinereader.h\u003e\n\nstruct RecordCfg: public cmdlime::Config{\n    CMDLIME_PARAM(file, std::string)() \u003c\u003c \"save result to file\";\n    CMDLIME_PARAM(db, std::string)()   \u003c\u003c \"save result to database\";\n    CMDLIME_FLAG(detailed)             \u003c\u003c \"adds more information to the result\" \u003c\u003c cmdlime::WithoutShortName{};\n};\n\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int)               \u003c\u003c \"zip code of the searched region\";\n    CMDLIME_PARAM(surname, std::string)     \u003c\u003c \"surname of the person to find\";\n    CMDLIME_PARAM(name, std::string)()      \u003c\u003c \"name of the person to find\";\n    CMDLIME_FLAG(verbose)                   \u003c\u003c \"adds more information to the output\";\n    CMDLIME_SUBCOMMAND(record, RecordCfg)   \u003c\u003c \"record search result\";\n};\n\nint mainApp(const Cfg\u0026 cfg)\n{\n    std::cout \u003c\u003c \"Looking for person \" \u003c\u003c cfg.name \u003c\u003c \" \" \u003c\u003c cfg.surname \u003c\u003c \" in the region with zip code: \" \u003c\u003c cfg.zipCode \u003c\u003c std::endl;\n    if (cfg.record.has_value())\n        std::cout \u003c\u003c \"Record settings: \" \u003c\u003c \"file:\" \u003c\u003c cfg.record-\u003efile \u003c\u003c \" db:\" \u003c\u003c cfg.record-\u003edb \u003c\u003c \" detailed:\" \u003c\u003c cfg.record-\u003edetailed \u003c\u003c std::endl;\n    return 0;\n}\n\nint main(int argc, char** argv)\n{\n    return cmdlime::CommandLineReader{\"person-finder\"}.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n\n```\nNow, `person-finder` can be launched like this:\n\n```console\nkamchatka-volcano@home:~$ ./person-finder 684007 --surname Deer record --file res.txt --detailed\nLooking for person  Deer in the region with zip code: 684007\nRecord settings: file:res.txt db: detailed:1\n```\n\nNote 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.\n\nThis 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.\n\n\nLet's see how it works:\n\n```C++\n///examples/ex14.cpp\n///\n#include \u003ccmdlime/commandlinereader.h\u003e\n\nstruct RecordCfg: public cmdlime::Config{\n    CMDLIME_PARAM(file, std::string)() \u003c\u003c \"save result to file\";\n    CMDLIME_PARAM(db, std::string)()   \u003c\u003c \"save result to database\";\n    CMDLIME_FLAG(detailed)             \u003c\u003c \"hide search results\" \u003c\u003c cmdlime::WithoutShortName{};\n};\n\nstruct HistoryCfg: public cmdlime::Config{\n    CMDLIME_PARAM(surname, std::string)() \u003c\u003c \"filter search queries by surname\";\n    CMDLIME_FLAG(noResults)               \u003c\u003c \"hide search results\";\n};\n\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int)             \u003c\u003c \"zip code of the searched region\";\n    CMDLIME_PARAM(surname, std::string)   \u003c\u003c \"surname of the person to find\";\n    CMDLIME_PARAM(name, std::string)()    \u003c\u003c \"name of the person to find\";\n    CMDLIME_FLAG(verbose)                 \u003c\u003c \"adds more information to the output\";\n    CMDLIME_SUBCOMMAND(record, RecordCfg) \u003c\u003c \"record search result\";\n    CMDLIME_COMMAND(history, HistoryCfg)  \u003c\u003c \"show search history\";\n};\n\nint mainApp(const Cfg\u0026 cfg)\n{\n    if (cfg.history.has_value()){\n        std::cout \u003c\u003c \"Preparing search history with surname filter:\" \u003c\u003c cfg.history-\u003esurname \u003c\u003c std::endl;\n        return 0;\n    }\n\n    std::cout \u003c\u003c \"Looking for person \" \u003c\u003c cfg.name \u003c\u003c \" \" \u003c\u003c cfg.surname \u003c\u003c \" in the region with zip code: \" \u003c\u003c cfg.zipCode \u003c\u003c std::endl;\n    if (cfg.record.has_value())\n        std::cout \u003c\u003c \"Record settings: \" \u003c\u003c \"file:\" \u003c\u003c cfg.record-\u003efile \u003c\u003c \" db:\" \u003c\u003c cfg.record-\u003edb \u003c\u003c \" detailed:\" \u003c\u003c cfg.record-\u003edetailed \u003c\u003c std::endl;\n\n    return 0;\n}\n\nint main(int argc, char** argv)\n{\n    return cmdlime::CommandLineReader{\"person-finder\"}.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n\n```\n\n```console\nkamchatka-volcano@home:~$ ./person-finder history --surname Doe\nPreparing search history with surname filter:Doe\n```\n\nAs you can see, a config structure can have multiple commands, but only one can be specified for each config.\n\n### Using validators\nProcessed command line options can be validated by registering constraint checking functions or callable objects. The signature must be compatible with `void (const T\u0026)` 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`:\n\n```c++\nstruct Cfg : cmdlime::Config{\n    CMDLIME_PARAM(number, int) \n        \u003c\u003c [](int paramValue){\n            if (paramValue \u003c 0)\n                throw cmdlime::ValidationError{\"value can't be negative.\"};\n        };\n};\n```\n\nLet'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:\n```c++\n///examples/ex15.cpp\n///\n#include \u003ccmdlime/commandlinereader.h\u003e\n#include \u003calgorithm\u003e\n\nstruct EnsureAlpha{\n    void operator()(const std::string\u0026 name)\n    {\n        if (!std::all_of(std::begin(name), std::end(name),\n                         [](auto ch){\n                             return std::isalpha(static_cast\u003cint\u003e(ch));\n                         }))\n            throw cmdlime::ValidationError{\"value must contain alphabet characters only.\"};\n    }\n};\n\nstruct RecordCfg: public cmdlime::Config{\n    CMDLIME_PARAM(file, std::string)() \u003c\u003c \"save result to file\";\n    CMDLIME_PARAM(db, std::string)()   \u003c\u003c \"save result to database\";\n    CMDLIME_FLAG(detailed)             \u003c\u003c \"hide search results\" \u003c\u003c cmdlime::WithoutShortName{};\n};\n\nstruct HistoryCfg: public cmdlime::Config{\n    CMDLIME_PARAM(surname, std::string)() \u003c\u003c \"filter search queries by surname\" \u003c\u003c EnsureAlpha{};\n    CMDLIME_FLAG(noResults)               \u003c\u003c \"hide search results\";\n};\n\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int)             \u003c\u003c \"zip code of the searched region\";\n    CMDLIME_PARAM(surname, std::string)   \u003c\u003c \"surname of the person to find\" \u003c\u003c EnsureAlpha{};\n    CMDLIME_PARAM(name, std::string)()    \u003c\u003c \"name of the person to find\" \t \u003c\u003c EnsureAlpha{};\n    CMDLIME_FLAG(verbose)                 \u003c\u003c \"adds more information to the output\";\n    CMDLIME_SUBCOMMAND(record, RecordCfg) \u003c\u003c \"record search result\"\n                                          \u003c\u003c [](auto\u0026 record){\n                                              if (record \u0026\u0026 record-\u003efile.empty() \u0026\u0026 record-\u003edb.empty())\n                                                  throw cmdlime::ValidationError{\"file or db paremeter must be provided.\"};\n                                              else\n                                                  throw std::runtime_error{\"ERROR\"};\n                                          };\n    CMDLIME_COMMAND(history, HistoryCfg)  \u003c\u003c \"show search history\";\n};\n\nint mainApp(const Cfg\u0026 cfg)\n{\n    if (cfg.history.has_value()){\n        std::cout \u003c\u003c \"Preparing search history with surname filter:\" \u003c\u003c cfg.history-\u003esurname \u003c\u003c std::endl;\n        return 0;\n    }\n\n    std::cout \u003c\u003c \"Looking for person \" \u003c\u003c cfg.name \u003c\u003c \" \" \u003c\u003c cfg.surname \u003c\u003c \" in the region with zip code: \" \u003c\u003c cfg.zipCode \u003c\u003c std::endl;\n    if (cfg.record.has_value())\n        std::cout \u003c\u003c \"Record settings: \" \u003c\u003c \"file:\" \u003c\u003c cfg.record-\u003efile \u003c\u003c \" db:\" \u003c\u003c cfg.record-\u003edb \u003c\u003c \" detailed:\" \u003c\u003c cfg.record-\u003edetailed \u003c\u003c std::endl;\n\n    return 0;\n}\n\nint main(int argc, char** argv)\n{\n    return cmdlime::CommandLineReader{\"person-finder\"}.exec\u003cCfg\u003e(argc, argv, mainApp);\n}\n\n```\n\nNow you'll get the following error messages if you provide invalid parameters:\n\n```console\nkamchatka-volcano@home:~$ ./person-finder --surname Deer 684007 record\nSubcommand 'record' is invalid: file or db paremeter must be provided.\nUsage: person-finder [commands] \u003czip-code\u003e --surname \u003cstring\u003e [--name \u003cstring\u003e] [--verbose] [--help] \n```\n\n```console\nkamchatka-volcano@home:~$ ./person-finder --surname Deer1 684007\nParameter 'surname' is invalid: value must contain alphabet characters only.\nUsage: person-finder [commands] \u003czip-code\u003e --surname \u003cstring\u003e [--name \u003cstring\u003e] [--verbose] [--help] \n```\n\n### Using post-processors\n\nIf you need to modify or validate the config object that is produced by `cmdlime::CommandLineReader`, you can register\nthe necessary action by creating a specialization of the `cmdlime::PostProcessor` class template. For instance, let's\ncapitalize a surname parameter only when the optional name parameter is not provided:\n\n```cpp\n///examples/ex16.cpp\n///\nstruct Cfg : public cmdlime::Config{\n    CMDLIME_ARG(zipCode, int)             \u003c\u003c \"zip code of the searched region\";\n    CMDLIME_PARAM(surname, std::string)   \u003c\u003c \"surname of the person to find\";\n    CMDLIME_PARAM(name, std::string)()    \u003c\u003c \"name of the person to find\";\n    CMDLIME_FLAG(verbose)                 \u003c\u003c \"adds more information to the output\";\n};\n\nnamespace cmdlime{\ntemplate\u003c\u003e\nstruct PostProcessor\u003cCfg\u003e {\n    void operator()(Cfg\u0026 cfg)\n    {\n        if (cfg.name.empty())\n            std::transform(\n                    cfg.surname.begin(),\n                    cfg.surname.end(),\n                    cfg.surname.begin(),\n                    [](const auto\u0026 ch)\n                    {\n                        return sfun::toupper(ch);\n                    });\n    }\n};\n}\n```\n\n## Installation\nDownload and link the library from your project's CMakeLists.txt:\n```\ncmake_minimum_required(VERSION 3.18)\n\ninclude(FetchContent)\n\nFetchContent_Declare(cmdlime\n    URL https://github.com/kamchatka-volcano/cmdlime/releases/download/v2.8.0/cmdlime-v2.8.0.zip\n)\n#uncomment if you need to install cmdlime with your target\n#set(INSTALL_CMDLIME ON)\nFetchContent_MakeAvailable(cmdlime)\n\nadd_executable(${PROJECT_NAME})\ntarget_link_libraries(${PROJECT_NAME} PRIVATE cmdlime::cmdlime)\n```\n\nPrefer using the release ZIP archive with FetchContent, as it is fully self-contained and avoids spending additional\ntime downloading the library dependencies during the CMake configuration step.\n\nTo install the library system-wide, use the following commands:\n```\ngit clone https://github.com/kamchatka-volcano/cmdlime.git\ncd cmdlime\ncmake -S . -B build\ncmake --build build\ncmake --install build\n```\n\nAfter installation, you can use the `find_package()` command to make the installed library available inside your project:\n```\nfind_package(cmdlime 2.8.0 REQUIRED)\ntarget_link_libraries(${PROJECT_NAME} PRIVATE cmdlime::cmdlime)   \n```\n\n\n\n## Running tests\n```\ncd cmdlime\ncmake -S . -B build -DENABLE_TESTS=ON\ncmake --build build\ncd build/tests \u0026\u0026 ctest\n```\n\n## Building examples\n```\ncd cmdlime\ncmake -S . -B build -DENABLE_EXAMPLES=ON\ncmake --build build\ncd build/examples\n```\n\n## License\n**cmdlime** is licensed under the [MS-PL license](/LICENSE.md)  \n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamchatka-volcano%2Fcmdlime","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkamchatka-volcano%2Fcmdlime","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamchatka-volcano%2Fcmdlime/lists"}