diff --git a/contrib/CLI11/LICENSE b/contrib/CLI11/LICENSE index 715be0bb4..dc6d29f41 100644 --- a/contrib/CLI11/LICENSE +++ b/contrib/CLI11/LICENSE @@ -1,4 +1,4 @@ -CLI11 2.2 Copyright (c) 2017-2024 University of Cincinnati, developed by Henry +CLI11 2.6.2 Copyright (c) 2017-2026 University of Cincinnati, developed by Henry Schreiner under NSF AWARD 1414736. All rights reserved. Redistribution and use in source and binary forms of CLI11, with or without diff --git a/contrib/CLI11/README.contrib b/contrib/CLI11/README.contrib index 304172b0d..ac657f28c 100644 --- a/contrib/CLI11/README.contrib +++ b/contrib/CLI11/README.contrib @@ -1,2 +1,2 @@ Source: https://github.com/CLIUtils/CLI11 -Revision: v2.4.2 +Revision: v2.6.2 diff --git a/contrib/CLI11/README.md b/contrib/CLI11/README.md index 0f3229913..823402c2b 100644 --- a/contrib/CLI11/README.md +++ b/contrib/CLI11/README.md @@ -37,6 +37,10 @@ set with a simple and intuitive interface. - [Example](#example) - [Option options](#option-options) - [Validators](#validators) + - [Default Validators](#default-validators) + - [Validators that may be disabled πŸ†•](#validators-that-may-be-disabled-) + - [Extra Validators πŸ†•](#extra-validators-) + - [Validator Usage](#validator-usage) - [Transforming Validators](#transforming-validators) - [Validator operations](#validator-operations) - [Custom Validators](#custom-validators) @@ -68,16 +72,16 @@ Features that were added in the last released minor version are marked with ### Introduction CLI11 provides all the features you expect in a powerful command line parser, -with a beautiful, minimal syntax and no dependencies beyond C++11. It is header -only, and comes in a single file form for easy inclusion in projects. It is easy -to use for small projects, but powerful enough for complex command line +with a beautiful, minimal syntax and no dependencies beyond C++11. It is +header-only, and comes in a single file form for easy inclusion in projects. It +is easy to use for small projects, but powerful enough for complex command line projects, and can be customized for frameworks. It is tested on [Azure][] and [GitHub Actions][actions-link], and was originally used by the [GooFit GPU fitting framework][goofit]. It was inspired by [`plumbum.cli`][plumbum] for -Python. CLI11 has a user friendly introduction in this README, a more in-depth +Python. CLI11 has a user-friendly introduction in this README, a more in-depth tutorial [GitBook][], as well as [API documentation][api-docs] generated by Travis. See the [changelog](./CHANGELOG.md) or [GitHub Releases][] for details -for current and past releases. Also see the [Version 1.0 post][], [Version 1.3 +on current and past releases. Also see the [Version 1.0 post][], [Version 1.3 post][], [Version 1.6 post][], or [Version 2.0 post][] for more information. You can be notified when new releases are made by subscribing to @@ -111,7 +115,7 @@ An acceptable CLI parser library should be all of the following: - Ability to add a configuration file (`TOML`, `INI`, or custom format), and produce it as well. - Produce real values that can be used directly in code, not something you have - pay compute time to look up, for HPC applications. + to pay compute time to look up, for HPC applications. - Work with common types, simple custom types, and extensible to exotic types. - Permissively licensed. @@ -158,31 +162,35 @@ installation fuss. There are some other possible "features" that are intentionally not supported by this library: -- Non-standard variations on syntax, like `-long` options. This is non-standard - and should be avoided, so that is enforced by this library. - Completion of partial options, such as Python's `argparse` supplies for incomplete arguments. It's better not to guess. Most third party command line parsers for python actually reimplement command line parsing rather than using argparse because of this perceived design flaw (recent versions do have an - option to disable it). + option to disable it). Recent releases of CLI11 do include partial option + matching for option prefixes πŸ†•. This is enabled by + `.allow_subcommand_prefix_matching()`, along with an example that generates + suggested close matches. - Autocomplete: This might eventually be added to both Plumbum and CLI11, but it is not supported yet. +- While not recommended, CLI11 does now support non-standard option names such + as `-option`. This is enabled through `allow_non_standard_option_names()` + modifier on an app. ## Install -To use, the most common methods are described here additional methods and +To use, the most common methods are described here, additional methods and details are available at [installation][]: - All-in-one local header: Copy `CLI11.hpp` from the [most recent release][github releases] into your include directory, and you are set. This is combined from the source files for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, - `AutoTimer`). The utilities are completely self contained and can be copied + `AutoTimer`). The utilities are completely self-contained and can be copied separately. - All-in-one global header: Like above, but copying the file to a shared folder location like `/opt/CLI11`. Then, the C++ include path has to be extended to - point at this folder. With CMake 3.5+, use `include_directories(/opt/CLI11)` -- For other methods including using CMake or vcpkg and some specific + point at this folder. With CMake 3.10+, use `include_directories(/opt/CLI11)` +- For other methods including using CMake, conan or vcpkg and some specific instructions for GCC 8 or WASI see [installation][]. ## Usage @@ -205,9 +213,11 @@ int main(int argc, char** argv) { } ``` -For more information about πŸ†•`ensure_utf8` the section on -[Unicode support](#unicode-support) below. The πŸ†•`ensure_utf8` function is only -available in main currently and not in a release. +When adding options the names should not conflict with each other, if an option +is added, or a modifier changed that would cause naming conflicts a run time +error will be thrown in the add_option method. This includes default options for +help `-h, --help`. For more information about `ensure_utf8` the section on +[Unicode support](#unicode-support) below.
Note: If you don't like macros, this is what that macro expands to: (click to expand)

@@ -287,7 +297,9 @@ string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name -will be used on the help line for its positional form. +will be used on the help line for its positional form. The string `++` is also +not allowed as option name due to its use as an array separator and marker on +config files. The `add_option_function(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is @@ -309,7 +321,7 @@ assigning it. Or using some variant type ```cpp using vtype=std::variant; vtype v1; -app.add_option("--vs",v1); +app.add_option("--vs",v1); app.add_option("--vi",v1); app.add_option("--vf",v1); ``` @@ -338,10 +350,10 @@ app.add_option,int>("--vs",v1); would load a vector of doubles but ensure all values can be represented as integers. -Automatic direct capture of the default string is disabled when using the two -parameter template. Use `set_default_str(...)` or -`->default_function(std::string())` to set the default string or capture -function directly for these cases. +Use `default_str(...)` or `default_val(...)` to set the default string or value +for the option or flag. Use `->default_function(std::string())` to customize the +default capture function directly. The default value is then captured by calling +`->capture_default_str()`. Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value @@ -415,7 +427,7 @@ Before parsing, you can set the following options: option. Options can be removed from the excludes list with `->remove_excludes(opt)` - `->envname(name)`: Gets the value from the environment if present and not - passed on the command line. πŸ†• The value must also pass any validators to be + passed on the command line. The value must also pass any validators to be used. - `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. Options given an empty string will not show @@ -451,7 +463,7 @@ Before parsing, you can set the following options: are `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::TakeLast`, `CLI::MultiOptionPolicy::TakeFirst`, `CLI::MultiOptionPolicy::Join`, `CLI::MultiOptionPolicy::TakeAll`, - `CLI::MultiOptionPolicy::Sum`, and `CLI::MultiOptionPolicy::Reverse` πŸ†•. + `CLI::MultiOptionPolicy::Sum`, and `CLI::MultiOptionPolicy::Reverse`. - `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails @@ -470,7 +482,8 @@ Before parsing, you can set the following options: - `->configurable(false)`: Disable this option from being in a configuration file. - `->capture_default_str()`: Store the current value attached and display it in - the help string. + the help string. This should work with almost any type that `add_option` can + accept. - `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses. - `->always_capture_default()`: Always run `capture_default_str()` when creating @@ -493,6 +506,33 @@ Before parsing, you can set the following options: validation checks for the option to be executed when the option value is parsed vs. at the end of all parsing. This could cause the callback to be executed multiple times. Also works with positional options. +- `->callback_priority(CallbackPriority priority)`: πŸ†• changes the order in + which the option callback is executed. Four principal callback call-points are + available. `CallbackPriority::First` executes at the very beginning of + processing, before configuration files are read and environment variables are + interpreted. `CallbackPriority::PreRequirementsCheck` executes after + configuration and environment processing but before requirements checking. + `CallbackPriority::Normal` executes after the requirements check but before + any previously potentially raised exceptions are re-thrown. + `CallbackPriority::Last` executes after exception handling is completed. For + each position, both ordinary option callbacks and help callbacks are invoked. + The relative order between them can be controlled using the corresponding + `PreHelp` variants. `CallbackPriority::FirstPreHelp` executes ordinary option + callbacks before help callbacks at the very beginning of processing. + `CallbackPriority::PreRequirementsCheckPreHelp` executes ordinary option + callbacks before help callbacks after configuration and environment processing + but before requirements checking. `CallbackPriority::NormalPreHelp` executes + ordinary option callbacks before help callbacks after the requirements check + but before exception re-throwing. `CallbackPriority::LastPreHelp` executes + ordinary option callbacks before help callbacks after exception handling has + completed. When using the standard priorities (`CallbackPriority::First`, + `CallbackPriority::PreRequirementsCheck`, `CallbackPriority::Normal`, + `CallbackPriority::Last`), help callbacks are executed before ordinary option + callbacks. By default, help callbacks use `CallbackPriority::First`, and + ordinary option callbacks use `CallbackPriority::Normal`. This mechanism + provides fine-grained control over when option values are set and when help or + requirement checks occur, enabling precise customization of the processing + sequence. These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function @@ -514,7 +554,7 @@ On the command line, options can be given as: - `-ffilename` (no space required) - `-abcf filename` (flags and option can be combined) - `--long` (long flag) -- `--long_flag=true` (long flag with equals to override default value) +- `--long_flag=true` (long flag with equals -- to override default value) - `--file filename` (space) - `--file=filename` (equals) @@ -559,7 +599,41 @@ are added through the `check` or `transform` functions. The differences between the two function are that checks do not modify the input whereas transforms can and are executed before any Validators added through `check`. -CLI11 has several Validators built-in that perform some common checks +CLI11 has several Validators included that perform some common checks. By +default the most commonly used ones are available. πŸ†• If some are not needed +they can be disabled by using + +```c++ +#define CLI11_DISABLE_EXTRA_VALIDATORS 1 +``` + +#### Default Validators + +These validators are always available regardless of definitions. These are used +internally or are very commonly used, so will always remain available regardless +of flags. + +- `CLI::ExistingFile`: Requires that the file exists if given. +- `CLI::ExistingDirectory`: Requires that the directory exists. +- `CLI::ExistingPath`: Requires that the path (file or directory) exists. +- `CLI::NonexistentPath`: Requires that the path does not exist. +- `CLI::FileOnDefaultPath`: Best used as a transform, Will check that a file + exists either directly or in a default path and update the path appropriately. + See [Transforming Validators](#transforming-validators) for more details +- `CLI::Range(min,max)`: Requires that the option be between min and max (make + sure to use floating point if needed). Min defaults to 0. +- `CLI::PositiveNumber`: Requires the number be greater than 0 +- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0 +- `CLI::Number`: Requires the input be a number. + +#### Validators that may be disabled πŸ†• + +Validators that may be disabled by setting `CLI11_DISABLE_EXTRA_VALIDATORS` to 1 +or enabled by setting `CLI11_ENABLE_EXTRA_VALIDATORS` to 1. By default they are +enabled. In version 3.0 these will likely move to be disabled by default and be +controlled solely by the `CLI11_ENABLE_EXTRA_VALIDATORS` option. These +validators are less commonly used or are template heavy and require additional +computation time that may not be valuable for some use cases. - `CLI::IsMember(...)`: Require an option be a member of a given set. See [Transforming Validators](#transforming-validators) for more details. @@ -575,21 +649,11 @@ CLI11 has several Validators built-in that perform some common checks - `CLI::AsSizeValue(...)`: Convert inputs like `100b`, `42 KB`, `101 Mb`, `11 Mib` to absolute values. `KB` can be configured to be interpreted as 10^3 or 2^10. -- `CLI::ExistingFile`: Requires that the file exists if given. -- `CLI::ExistingDirectory`: Requires that the directory exists. -- `CLI::ExistingPath`: Requires that the path (file or directory) exists. -- `CLI::NonexistentPath`: Requires that the path does not exist. -- `CLI::FileOnDefaultPath`: Best used as a transform, Will check that a file - exists either directly or in a default path and update the path appropriately. - See [Transforming Validators](#transforming-validators) for more details -- `CLI::Range(min,max)`: Requires that the option be between min and max (make - sure to use floating point if needed). Min defaults to 0. + - `CLI::Bounded(min,max)`: Modify the input such that it is always between min and max (make sure to use floating point if needed). Min defaults to 0. Will produce an error if conversion is not possible. -- `CLI::PositiveNumber`: Requires the number be greater than 0 -- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0 -- `CLI::Number`: Requires the input be a number. + - `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`. - `CLI::TypeValidator`:Requires that the option be convertible to the @@ -597,8 +661,22 @@ CLI11 has several Validators built-in that perform some common checks the input be convertible to an `unsigned int` regardless of the end conversion. -These Validators can be used by simply passing the name into the `check` or -`transform` methods on an option +#### Extra Validators πŸ†• + +New validators will go into code sections that must be explicitly enabled by +setting `CLI11_ENABLE_EXTRA_VALIDATORS` to 1 + +- `CLI::ReadPermission`: Requires that the file or folder given exist and have + read permission. Requires C++17. +- `CLI::WritePermission`: Requires that the file or folder given exist and have + write permission. Requires C++17. +- `CLI::ExecPermission`: Requires that the file given exist and have execution + permission. Requires C++17. + +#### Validator Usage + +These Validators once enabled can be used by simply passing the name into the +`check` or `transform` methods on an option ```cpp ->check(CLI::ExistingFile); @@ -701,8 +779,8 @@ filters on the key values is performed. `CLI::FileOnDefaultPath(default_path, false)`. This allows multiple paths to be chained using multiple transform calls. -- `CLI::EscapedString`: πŸ†• can be used to process an escaped string. The - processing is equivalent to that used for TOML config files, see +- `CLI::EscapedString`: can be used to process an escaped string. The processing + is equivalent to that used for TOML config files, see [TOML strings](https://toml.io/en/v1.0.0#string). With 2 notable exceptions. \` can also be used as a literal string notation, and it also allows binary string notation see @@ -762,14 +840,22 @@ CLI::Validator(validator_description); ``` It is also possible to create a subclass of `CLI::Validator`, in which case it -can also set a custom description function, and operation function. +can also set a custom description function, and operation function. One example +of this is in the +[custom validator example](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_validator.cpp). +example. The `check` and `transform` operations can also take a shared_ptr πŸ†• to +a validator if you wish to reuse the validator in multiple locations or it is +mutating and the check is dependent on other operations or is variable. Note +that in this case it is not recommended to use the same object for both check +and transform operations, the check will modify some internal flags on the +object so it will not be usable for transform operations. ##### Querying Validators Once loaded into an Option, a pointer to a named Validator can be retrieved via ```cpp -opt->get_validator(name); +auto *validator = opt->get_validator(name); ``` This will retrieve a Validator with the given name or throw a @@ -779,7 +865,7 @@ unnamed Validator will be returned or the first Validator if there is only one. or ```cpp -opt->get_validator(index); +auto *validator = opt->get_validator(index); ``` Which will return a validator in the index it is applied which isn't necessarily @@ -812,7 +898,11 @@ not used in performance critical code: variable. - `Value=opt->as()`: Returns the result or default value directly as the specified type if possible, can be vector to return all results, and a - non-vector to get the result according to the MultiOptionPolicy in place. + non-vector to get the result according to the MultiOptionPolicy in place. If + it is expected that the results will be needed as a vector, it is suggested + that `->expected(CLI::details::expected_max_vector_size)` or + `allow_extra_args()` be used on the option to inform CLI11 that vector args + are expected and allowed. ### Subcommands @@ -859,7 +949,9 @@ triggered once unless the `.immediate_callback()` flag is set or the callback is specified through the `parse_complete_callback()` function. The `final_callback()` is triggered only once. In which case the callback executes on completion of the subcommand arguments but after the arguments for that -subcommand have been parsed, and can be triggered multiple times. +subcommand have been parsed, and can be triggered multiple times. Note that the +`parse_complete_callback()` is executed prior to processing any config files. +The `final_callback()` is executed after config file processing. Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments. Nameless subcommands function @@ -873,7 +965,7 @@ nameless subcommands are allowed. Callbacks for nameless subcommands are only triggered if any options from the subcommand were parsed. Subcommand names given through the `add_subcommand` method have the same restrictions as option names. -πŸ†• Options or flags in a subcommand may be directly specified using dot notation +Options or flags in a subcommand may be directly specified using dot notation - `--subcommand.long=val` (long subcommand option) - `--subcommand.long val` (long subcommand option) @@ -885,8 +977,8 @@ through the `add_subcommand` method have the same restrictions as option names. The use of dot notation in this form is equivalent `--subcommand.long ` => `subcommand --long ++`. Nested subcommands also work `sub1.subsub` would trigger the subsub subcommand in `sub1`. This is equivalent to "sub1 subsub". -Quotes around the subcommand names are permitted πŸ†• following the TOML standard -for such specification. This includes allowing escape sequences. For example +Quotes around the subcommand names are permitted following the TOML standard for +such specification. This includes allowing escape sequences. For example `"subcommand".'f'` or `"subcommand.with.dots".arg1 = value`. #### Subcommand options @@ -902,11 +994,29 @@ option_groups. These are: the form of `/s /long /file:file_name.ext` This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext`. +- `.allow_non_standard_option_names()`: Allow specification of single `-` long + form option names. This is not recommended but is available to enable + reworking of existing interfaces. If this modifier is enabled on an app or + subcommand, options or flags can be specified like normal but instead of + throwing an exception, long form single dash option names will be allowed. It + is not allowed to have a single character short option starting with the same + character as a single dash long form name; for example, `-s` and `-single` are + not allowed in the same application. +- `.allow_subcommand_prefix_matching()`:πŸ†• If this modifier is enabled, + unambiguious prefix portions of a subcommand will match. For example + `upgrade_package` would match on `upgrade_`, `upg`, `u` as long as no other + subcommand would also match. It also disallows subcommand names that are full + prefixes of another subcommand. - `.fallthrough()`: Allow extra unmatched options and positionals to "fall - through" and be matched on a parent option. Subcommands always are allowed to - "fall through" as in they will first attempt to match on the current + through" and be matched on a parent option. Subcommands by default are allowed + to "fall through" as in they will first attempt to match on the current subcommand and if they fail will progressively check parents for matching - subcommands. + subcommands. This can be disabled through `subcommand_fallthrough(false)`. +- `.subcommand_fallthrough()`: Allow subcommands to "fall through" and be + matched on a parent option. Disabling this prevents additional subcommands at + the same level from being matched. It can be useful in certain circumstances + where there might be ambiguity between subcommands and positionals. The + default is true. - `.configurable()`: Allow the subcommand to be triggered from a configuration file. By default subcommand options in a configuration file do not trigger a subcommand but will just update default values. @@ -964,16 +1074,24 @@ option_groups. These are: - `.get_parent()`: Get the parent App or `nullptr` if called on main App. - `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched + along with the parent for subcommands with fallthrough. - `.get_option_no_throw(name)`: Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available. - `.get_options(filter)`: Get the list of all defined option pointers (useful - for processing the app for custom output formats). + for processing the app for custom output formats). If used on a subcommand + will also get options that are in the parent app if the subcommand has + fallthrough. - `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates). -- `.formatter(fmt)`: Set a formatter, with signature - `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more - details. +- `.formatter(std::shared_ptr fmt)`: Set a custom formatter for + help. +- `.formatter_fn(fmt)`, with signature + `std::string(const App*, std::string, AppFormatMode)`. See [formatting][] for + more details. +- `.config_formatter(std::shared_ptr fmt)`: set a custom config + formatter for generating config files, more details available at [Config + files][config] - `.description(str)`: Set/change the description. - `.get_description()`: Access the description. - `.alias(str)`: set an alias for the subcommand, this allows subcommands to be @@ -1008,15 +1126,41 @@ option_groups. These are: executes after the first argument of an application is processed. See [Subcommand callbacks](#callbacks) for some additional details. - `.allow_extras()`: Do not throw an error if extra arguments are left over. +- `.allow_extras(CLI::ExtrasMode)`: πŸ†• Specify the method of handling + unrecognized arguments. + - `CLI::ExtrasMode::Error`: generate an error on unrecognized argument. Same + as `.allow_extras(false)`. + - `CLI::ExtrasMode::ErrorImmediately`: generate an error immediately on + parsing an unrecognized option`. + - `CLI::ExtrasMode::Ignore`: ignore any unrecognized argument, do not generate + an error. + - `CLI::ExtrasMode::AssumeSingleArgument`: After an unrecognized flag or + option argument, if the following argument is not a flag or option argument + assume it an argument and treat it as also unrecognized even if it would + otherwise go to a positional argument + - `CLI::ExtrasMode::AssumeMultipleArguments`:After an unrecognized flag or + option argument, if the following arguments are not a flag or option + argument assume they are arguments and treat them as also unrecognized even + if it would otherwise go to a positional argument + - `CLI::ExtrasMode::Capture`: capture all unrecognized arguments, same as + `true` for `.allow_extras`: - `.positionals_at_end()`: Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered. -- `.prefix_command()`: Like `allow_extras`, but stop immediately on the first - unrecognized item. It is ideal for allowing your app or subcommand to be a - "prefix" to calling another app. -- `.usage(message)`: πŸ†• Replace text to appear at the start of the help string +- `.prefix_command()`: Like `allow_extras`, but stop processing immediately on + the first unrecognized item. All subsequent arguments are placed in the + remaining_arg list. It is ideal for allowing your app or subcommand to be a + "prefix" to calling another app. Can be called with a `bool` value to turn on + or off +- `.prefix_command(CLI::PrefixCommandMode)`: πŸ†• specify the prefix_command mode + to use. `PrefixCommandMode::on` and `PrefixCommandMode::off` are the same as + `prefix_command(true)` or `prefix_command(false)`. Calling with + `PrefixCommandMode::separator_only` will only trigger prefix command mode by + the use of the subcommand separator `--` other unrecognized arguments would be + considered an error depending on whether `allow_extras` was set or not. +- `.usage(message)`: Replace text to appear at the start of the help string after description. -- `.usage(std::string())`: πŸ†• Set a callback to generate a string that will - appear at the start of the help string after description. +- `.usage(std::string())`: Set a callback to generate a string that will appear + at the start of the help string after description. - `.footer(message)`: Set text to appear at the bottom of the help string. - `.footer(std::string())`: Set a callback to generate a string that will appear at the end of the help string. @@ -1099,8 +1243,8 @@ A subcommand is considered terminated when one of the following conditions are met. 1. There are no more arguments to process -2. Another subcommand is encountered that would not fit in an optional slot of - the subcommand +2. Another subcommand is encountered that would not fit in an optional + positional slot of the subcommand 3. The `positional_mark` (`--`) is encountered and there are no available positional slots in the subcommand. 4. The `subcommand_terminator` mark (`++`) is encountered @@ -1205,7 +1349,17 @@ auto hidden_group=app.add_option_group(""); ``` will create a group such that no options in that group are displayed in the help -string. +string. For the purposes of help display, if the option group name starts with a +'+' it is treated as if it were not in a group for help and get_options. For +example: + +```cpp +auto added_group=app.add_option_group("+sub"); +``` + +In this case the help output will not reference the option group and options +inside of it will be treated for most purposes as if they were part of the +parent. ### Configuration file @@ -1222,15 +1376,15 @@ is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in [TOML][] format by default, though the default reader can also accept files in INI format as well. The config reader -can read most aspects of TOML files including strings both literal πŸ†• and with -potential escape sequences πŸ†•, digit separators πŸ†•, and multi-line strings πŸ†•, -and run them through the CLI11 parser. Other formats can be added by an adept -user, some variations are available through customization points in the default -formatter. An example of a TOML file: +can read most aspects of TOML files including strings both literal and with +potential escape sequences, digit separators, and multi-line strings, and run +them through the CLI11 parser. Other formats can be added by an adept user, some +variations are available through customization points in the default formatter. +An example of a TOML file: ```toml # Comments are supported, using a # -# The default section is [default], case insensitive +# The default section is [default], case-insensitive value = 1 value2 = 123_456 # a string with separators @@ -1251,7 +1405,7 @@ or equivalently in INI format ```ini ; Comments are supported, using a ; -; The default section is [default], case insensitive +; The default section is [default], case-insensitive value = 1 str = "A string" @@ -1267,7 +1421,7 @@ sub.subcommand = true Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, -`yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case insensitive). +`yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case-insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults"). You cannot set positional-only arguments. Subcommands can be triggered from @@ -1335,25 +1489,18 @@ The default settings for options are inherited to subcommands, as well. ### Formatting -The job of formatting help printouts is delegated to a formatter callable object -on Apps and Options. You are free to replace either formatter by calling -`formatter(fmt)` on an `App`, where fmt is any copyable callable with the -correct signature. CLI11 comes with a default App formatter functional, -`Formatter`. It is customizable; you can set `label(key, value)` to replace the -default labels like `REQUIRED`, and `column_width(n)` to set the width of the -columns before you add the functional to the app or option. You can also -override almost any stage of the formatting process in a subclass of either -formatter. If you want to make a new formatter from scratch, you can do that -too; you just need to implement the correct signature. The first argument is a -const pointer to the in question. The formatter will get a `std::string` usage -name as the second option, and a `AppFormatMode` mode for the final option. It -should return a `std::string`. - -The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the -situation the help was called in. `Sub` is optional, but the default formatter -uses it to make sure expanded subcommands are called with their own formatter -since you can't access anything but the call operator once a formatter has been -set. +The job of formatting help printouts is delegated to a formatter object. You are +free to replace the formatter with a custom one by calling `formatter(fmt)` on +an `App`. CLI11 comes with a default App formatter, `Formatter`. You can +retrieve the formatter via `.get_formatter()` this will return a pointer to the +current `Formatter`. It is customizable; you can set `label(key, value)` to +replace the default labels like `REQUIRED`, and `OPTIONS`. You can also set +`column_width(n)` to set the width of the columns before you add the functional +to the app or option. Several other configuration options are also available in +the `Formatter`. You can also override almost any stage of the formatting +process in a subclass of either formatter. If you want to make a new formatter +from scratch, you can do that too; you just need to implement the correct +signature. see [formatting][] for more details. ### Subclassing @@ -1463,7 +1610,7 @@ int wmain(int argc, wchar_t *argv[]) { ``` 3\. Retrieve arguments yourself by using Windows APIs like -[`CommandLineToArgvW`](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw) +[CommandLineToArgvW](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw) and pass them to CLI. This is what the library is doing under the hood in `ensure_utf8`. @@ -1594,7 +1741,7 @@ brief description of each is included here - [formatter](https://github.com/CLIUtils/CLI11/blob/main/examples/formatter.cpp): Illustrating usage of a custom formatter - [groups](https://github.com/CLIUtils/CLI11/blob/main/examples/groups.cpp): - Example using groups of options for help grouping and a the timer helper class + Example using groups of options for help grouping and a timer helper class - [inter_argument_order](https://github.com/CLIUtils/CLI11/blob/main/examples/inter_argument_order.cpp): An app to practice mixing unlimited arguments, but still recover the original order. @@ -1636,15 +1783,19 @@ brief description of each is included here Short example of subcommands - [validators](https://github.com/CLIUtils/CLI11/blob/main/examples/validators.cpp): Example illustrating use of validators +- [custom validators](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_validator.cpp): + Example illustrating use of validators +- [date validators](https://github.com/CLIUtils/CLI11/blob/main/examples/date_validator.cpp): + Example illustrating use of validators ## Contribute To contribute, open an [issue][github issues] or [pull request][github pull requests] on GitHub, or ask a question on [gitter][]. There -is also a short note to contributors [here](./.github/CONTRIBUTING.md). This -readme roughly follows the [Standard Readme Style][] and includes a mention of -almost every feature of the library. More complex features are documented in -more detail in the [CLI11 tutorial GitBook][gitbook]. +is also a [short note to contributors](./.github/CONTRIBUTING.md). This readme +roughly follows the [Standard Readme Style][] and includes a mention of almost +every feature of the library. More complex features are documented in more +detail in the [CLI11 tutorial GitBook][gitbook]. This project was created by [Henry Schreiner](https://github.com/henryiii) and major features were added by [Philip Top](https://github.com/phlptp). Special @@ -1658,71 +1809,89 @@ thanks to all the contributors Alex Dewar
Alex Dewar

πŸ’» + Alexander Galanin
Alexander Galanin

πŸ’» Andrew Hardin
Andrew Hardin

πŸ’» Andrey Zhukov
Andrey Zhukov

πŸ’» + Anna Zhukova
Anna Zhukova

πŸ’» Anton
Anton

πŸ’» Artem Trokhymchuk
Artem Trokhymchuk

πŸ’» - Benjamin Beichler
Benjamin Beichler

πŸ’» - Caleb Zulawski
Caleb Zulawski

πŸ“¦ - Christian Asmussen
Christian Asmussen

πŸ“– + Benjamin Beichler
Benjamin Beichler

πŸ’» + Caleb Zulawski
Caleb Zulawski

πŸ“¦ + Christian Asmussen
Christian Asmussen

πŸ“– πŸ’» Christoph Bachhuber
Christoph Bachhuber

πŸ’‘ πŸ’» + Comix
Comix

πŸ’» D. Fleury
D. Fleury

πŸ’» Dan Barowy
Dan Barowy

πŸ“– + + Daniel Mensinger
Daniel Mensinger

πŸ“¦ DarkWingMcQuack
DarkWingMcQuack

πŸ’» + Dominik Nussbaumer
Dominik Nussbaumer

πŸ’» Dominik Steinberger
Dominik Steinberger

πŸ’» - - Doug Johnston
Doug Johnston

πŸ› πŸ’» Dylan Baker
Dylan Baker

πŸ“¦ Eli Schwartz
Eli Schwartz

πŸ’» + + Ethan Sifferman
Ethan Sifferman

πŸ’» Fred HelmesjΓΆ
Fred HelmesjΓΆ

πŸ› πŸ’» - Henry Schreiner
Henry Schreiner

πŸ› πŸ“– πŸ’» + Guillem Blanco
Guillem Blanco

πŸ’» + Henry Schreiner
Henry Schreiner

πŸ› πŸ“– πŸ’» πŸ’‘ πŸ“¦ Isabella Muerte
Isabella Muerte

πŸ“¦ - - Izzy Muerte
Izzy Muerte

πŸ’» Jakob Lover
Jakob Lover

πŸ’» + + James Gerity
James Gerity

πŸ“– Jesus Briales
Jesus Briales

πŸ’» πŸ› + Jonark
Jonark

πŸ’» Jonas Nilsson
Jonas Nilsson

πŸ› πŸ’» - Jose Luis Rivero
Jose Luis Rivero

πŸ’» - Josh Soref
Josh Soref

πŸ”§ + Jose Luis Rivero
Jose Luis Rivero

πŸ’» πŸ“¦ + Josh Soref
Josh Soref

πŸ”§ πŸ“– + Julien Marrec
Julien Marrec

πŸ’» KOLANICH
KOLANICH

πŸ“¦ Kannan
Kannan

πŸ› πŸ’» Khem Raj
Khem Raj

πŸ’» Lars Nielsen
Lars Nielsen

πŸ’» + Lonely-Dream
Lonely-Dream

πŸ’» Lucas Czech
Lucas Czech

πŸ› πŸ’» Mak Kolybabi
Mak Kolybabi

πŸ“– - Marcin Ropa
Marcin Ropa

πŸ’» + Marc
Marc

πŸ’» + Marcin Ropa
Marcin Ropa

πŸ’» Marcus Brinkmann
Marcus Brinkmann

πŸ› πŸ’» Mathias Soeken
Mathias Soeken

πŸ“– Matt McCormick
Matt McCormick

πŸ’» Max
Max

πŸ’» Michael Hall
Michael Hall

πŸ“– - Nathan Hourt
Nathan Hourt

πŸ› πŸ’» - Nathaniel Hourt
Nathaniel Hourt

πŸ’» + Mikael Persson
Mikael Persson

πŸ“¦ πŸš‡ + Miko
Miko

πŸ’» + Nathan Hourt
Nathan Hourt

πŸ› πŸ’» + Nathaniel Hourt
Nathaniel Hourt

πŸ’» Olaf Meeuwissen
Olaf Meeuwissen

πŸ’» + Olivia (Zoe)
Olivia (Zoe)

πŸ“– OndΕ™ej ČertΓ­k
OndΕ™ej ČertΓ­k

πŸ› + + Paul le Roux
Paul le Roux

πŸ’» πŸ“¦ PaweΕ‚ Bylica
PaweΕ‚ Bylica

πŸ“¦ PeteAudinate
PeteAudinate

πŸ’» Peter Azmanov
Peter Azmanov

πŸ’» Peter Harris
Peter Harris

πŸ’» + Peter Heywood
Peter Heywood

πŸ’» + Philip Top
Philip Top

πŸ› πŸ“– πŸ’» πŸ’‘ πŸ“¦ - Peter Heywood
Peter Heywood

πŸ’» - Philip Top
Philip Top

πŸ› πŸ“– πŸ’» - Rafi Wiener
Rafi Wiener

πŸ› πŸ’» + Prashanth Mundkur
Prashanth Mundkur

πŸ’» + Radim KrčmΓ‘Ε™
Radim KrčmΓ‘Ε™

πŸ’» + Rafi Wiener
Rafi Wiener

πŸ’» RangeMachine
RangeMachine

πŸ’» Robert Adam
Robert Adam

πŸ’» Ryan Curtin
Ryan Curtin

πŸ“– @@ -1732,32 +1901,45 @@ thanks to all the contributors Sam Hocevar
Sam Hocevar

πŸ’» Sean Fisk
Sean Fisk

πŸ› πŸ’» StΓ©phane Del Pino
StΓ©phane Del Pino

πŸ’» + The0Dev
The0Dev

πŸ’» + Theo Paris
Theo Paris

πŸ’» + Theodor Nesfeldt EngΓΈy
Theodor Nesfeldt EngΓΈy

πŸš‡ + Uilian Ries
Uilian Ries

πŸ’» + + Viacheslav Kroilov
Viacheslav Kroilov

πŸ’» Volker Christian
Volker Christian

πŸ’» + Zephyr Lykos
Zephyr Lykos

πŸ“¦ almikhayl
almikhayl

πŸ’» πŸ“¦ ayum
ayum

πŸ’» - - captainurist
captainurist

πŸ’» christos
christos

πŸ’» + + deining
deining

πŸ“– dherrera-fb
dherrera-fb

πŸ’» + dherrera-meta
dherrera-meta

πŸ’» djerius
djerius

πŸ’» dryleev
dryleev

πŸ’» elszon
elszon

πŸ’» + ferdymercury
ferdymercury

πŸ“– - ferdymercury
ferdymercury

πŸ“– fpeng1985
fpeng1985

πŸ’» geir-t
geir-t

πŸ“¦ gostefan
gostefan

πŸ’» + huangqinjin
huangqinjin

πŸ’» + jaefunk
jaefunk

πŸ“– ncihnegn
ncihnegn

πŸ’» - nurelin
nurelin

πŸ’» - polistern
polistern

πŸ’» + nshaheed
nshaheed

πŸ“¦ + nurelin
nurelin

πŸ’» + polistern
polistern

πŸ’» + romanholidaypancakes
romanholidaypancakes

πŸ’» ryan4729
ryan4729

⚠️ shameekganguly
shameekganguly

πŸ’» + tansy
tansy

πŸ’» @@ -1849,9 +2031,10 @@ try! Feedback is always welcome. https://app.codacy.com/project/badge/Grade/2796b969c1b54321a02ad08affec0800 [codacy-link]: https://www.codacy.com/gh/CLIUtils/CLI11/dashboard?utm_source=github.com&utm_medium=referral&utm_content=CLIUtils/CLI11&utm_campaign=Badge_Grade -[hunter]: https://docs.hunter.sh/en/latest/packages/pkg/CLI11.html [standard readme style]: https://github.com/RichardLitt/standard-readme [argparse]: https://github.com/p-ranav/argparse [toml]: https://toml.io [lyra]: https://github.com/bfgroup/Lyra [installation]: https://cliutils.github.io/CLI11/book/chapters/installation.html +[formatting]: https://cliutils.github.io/CLI11/book/chapters/formatting.html +[config]: https://cliutils.github.io/CLI11/book/chapters/config.html diff --git a/contrib/CLI11/include/CLI/App.hpp b/contrib/CLI11/include/CLI/App.hpp index 940b858bd..277e6fda9 100644 --- a/contrib/CLI11/include/CLI/App.hpp +++ b/contrib/CLI11/include/CLI/App.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -46,7 +46,15 @@ namespace CLI { #endif namespace detail { -enum class Classifier { NONE, POSITIONAL_MARK, SHORT, LONG, WINDOWS_STYLE, SUBCOMMAND, SUBCOMMAND_TERMINATOR }; +enum class Classifier : std::uint8_t { + NONE, + POSITIONAL_MARK, + SHORT, + LONG, + WINDOWS_STYLE, + SUBCOMMAND, + SUBCOMMAND_TERMINATOR +}; struct AppFriend; } // namespace detail @@ -58,9 +66,26 @@ CLI11_INLINE std::string simple(const App *app, const Error &e); CLI11_INLINE std::string help(const App *app, const Error &e); } // namespace FailureMessage +/// enumeration of modes of how to deal with command line extras +enum class ExtrasMode : std::uint8_t { + Error = 0, + ErrorImmediately, + Ignore, + AssumeSingleArgument, + AssumeMultipleArguments, + Capture +}; + /// enumeration of modes of how to deal with extras in config files +enum class ConfigExtrasMode : std::uint8_t { Error = 0, Ignore, IgnoreAll, Capture }; -enum class config_extras_mode : char { error = 0, ignore, ignore_all, capture }; +/// @brief enumeration of modes of how to deal with extras in config files +enum class config_extras_mode : std::uint8_t { error = 0, ignore, ignore_all, capture }; + +/// @brief enumeration of prefix command modes, separator requires that the first extra argument be a "--", other +/// unrecognized arguments will cause an error. on allows the first extra to trigger prefix mode regardless of other +/// recognized options +enum class PrefixCommandMode : std::uint8_t { Off = 0, SeparatorOnly = 1, On = 2 }; class App; @@ -104,14 +129,14 @@ class App { std::string description_{}; /// If true, allow extra arguments (ie, don't throw an error). INHERITABLE - bool allow_extras_{false}; + ExtrasMode allow_extras_{ExtrasMode::Error}; /// If ignore, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE - /// if error error on an extra argument, and if capture feed it to the app - config_extras_mode allow_config_extras_{config_extras_mode::ignore}; + /// if error, error on an extra argument, and if capture feed it to the app + ConfigExtrasMode allow_config_extras_{ConfigExtrasMode::Ignore}; - /// If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE - bool prefix_command_{false}; + /// If true, cease processing on an unrecognized option (implies allow_extras) INHERITABLE + PrefixCommandMode prefix_command_{PrefixCommandMode::Off}; /// If set to true the name was automatically generated from the command line vs a user set name bool has_automatic_name_{false}; @@ -218,15 +243,19 @@ class App { /// Storage for subcommand list std::vector subcommands_{}; - /// If true, the program name is not case sensitive INHERITABLE + /// If true, the program name is not case-sensitive INHERITABLE bool ignore_case_{false}; /// If true, the program should ignore underscores INHERITABLE bool ignore_underscore_{false}; - /// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand. INHERITABLE + /// Allow options or other arguments to fallthrough, so that parent commands can collect options after subcommand. + /// INHERITABLE bool fallthrough_{false}; + /// Allow subcommands to fallthrough, so that parent commands can trigger other subcommands after subcommand. + bool subcommand_fallthrough_{true}; + /// Allow '/' for options for Windows like options. Defaults to true on Windows, false otherwise. INHERITABLE bool allow_windows_style_options_{ #ifdef _WIN32 @@ -238,7 +267,7 @@ class App { /// specify that positional arguments come at the end of the argument sequence not inheritable bool positionals_at_end_{false}; - enum class startup_mode : char { stable, enabled, disabled }; + enum class startup_mode : std::uint8_t { stable, enabled, disabled }; /// specify the startup mode for the app /// stable=no change, enabled= startup enabled, disabled=startup disabled startup_mode default_startup{startup_mode::stable}; @@ -256,6 +285,12 @@ class App { /// This is potentially useful as a modifier subcommand bool silent_{false}; + /// indicator that the subcommand should allow non-standard option arguments, such as -single_dash_flag + bool allow_non_standard_options_{false}; + + /// indicator to allow subcommands to match with prefix matching + bool allow_prefix_matching_{false}; + /// Counts the number of times this command/subcommand was parsed std::uint32_t parsed_{0U}; @@ -275,7 +310,7 @@ class App { App *parent_{nullptr}; /// The group membership INHERITABLE - std::string group_{"Subcommands"}; + std::string group_{"SUBCOMMANDS"}; /// Alias names for the subcommand std::vector aliases_{}; @@ -366,6 +401,12 @@ class App { /// Remove the error when extras are left over on the command line. App *allow_extras(bool allow = true) { + allow_extras_ = allow ? ExtrasMode::Capture : ExtrasMode::Error; + return this; + } + + /// Remove the error when extras are left over on the command line. + App *allow_extras(ExtrasMode allow) { allow_extras_ = allow; return this; } @@ -388,6 +429,17 @@ class App { return this; } + /// allow non standard option names + App *allow_non_standard_option_names(bool allowed = true) { + allow_non_standard_options_ = allowed; + return this; + } + + /// allow prefix matching for subcommands + App *allow_subcommand_prefix_matching(bool allowed = true) { + allow_prefix_matching_ = allowed; + return this; + } /// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled App *disabled_by_default(bool disable = true) { if(disable) { @@ -428,23 +480,37 @@ class App { /// ignore extras in config files App *allow_config_extras(bool allow = true) { if(allow) { - allow_config_extras_ = config_extras_mode::capture; - allow_extras_ = true; + allow_config_extras_ = ConfigExtrasMode::Capture; + allow_extras_ = ExtrasMode::Capture; } else { - allow_config_extras_ = config_extras_mode::error; + allow_config_extras_ = ConfigExtrasMode::Error; } return this; } /// ignore extras in config files App *allow_config_extras(config_extras_mode mode) { + allow_config_extras_ = static_cast(mode); + return this; + } + + /// ignore extras in config files + App *allow_config_extras(ConfigExtrasMode mode) { allow_config_extras_ = mode; return this; } - /// Do not parse anything after the first unrecognized option and return - App *prefix_command(bool allow = true) { - prefix_command_ = allow; + /// Do not parse anything after the first unrecognized option (if true) all remaining arguments are stored in + /// remaining args + App *prefix_command(bool is_prefix = true) { + prefix_command_ = is_prefix ? PrefixCommandMode::On : PrefixCommandMode::Off; + return this; + } + + /// Do not parse anything after the first unrecognized option (if true) all remaining arguments are stored in + /// remaining args + App *prefix_command(PrefixCommandMode mode) { + prefix_command_ = mode; return this; } @@ -625,13 +691,15 @@ class App { Option *add_flag(std::string flag_name) { return _add_flag_internal(flag_name, CLI::callback_t(), std::string{}); } /// Add flag with description but with no variable assignment or callback - /// takes a constant string, if a variable string is passed that variable will be assigned the results from the - /// flag + /// takes a constant string or a rvalue reference to a string, if a variable string is passed that variable will be + /// assigned the results from the flag template ::value && std::is_constructible::value, detail::enabler> = - detail::dummy> - Option *add_flag(std::string flag_name, T &flag_description) { - return _add_flag_internal(flag_name, CLI::callback_t(), flag_description); + enable_if_t<(std::is_const::type>::value || + std::is_rvalue_reference::value) && + std::is_constructible::type>::value, + detail::enabler> = detail::dummy> + Option *add_flag(std::string flag_name, T &&flag_description) { + return _add_flag_internal(flag_name, CLI::callback_t(), std::forward(flag_description)); } /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes @@ -710,7 +778,10 @@ class App { auto option_group = std::make_shared(std::move(group_description), group_name, this); auto *ptr = option_group.get(); // move to App_p for overload resolution on older gcc versions - App_p app_ptr = std::dynamic_pointer_cast(option_group); + App_p app_ptr = std::static_pointer_cast(option_group); + // don't inherit the footer in option groups and clear the help flag by default + app_ptr->footer_ = ""; + app_ptr->set_help_flag(); add_subcommand(std::move(app_ptr)); return ptr; } @@ -827,13 +898,19 @@ class App { return this; } - /// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand. + /// Set fallthrough, set to true so that options will fallthrough to parent if not recognized in a subcommand /// Default from parent, usually set on parent. App *fallthrough(bool value = true) { fallthrough_ = value; return this; } + /// Set subcommand fallthrough, set to true so that subcommands on parents are recognized + App *subcommand_fallthrough(bool value = true) { + subcommand_fallthrough_ = value; + return this; + } + /// Check to see if this subcommand was parsed, true only if received on command line. /// This allows the subcommand to be directly checked. explicit operator bool() const { return parsed_ > 0; } @@ -1060,7 +1137,7 @@ class App { } /// Get an option by name (non-const version) - Option *get_option(std::string option_name) { + CLI11_NODISCARD Option *get_option(std::string option_name) { auto *opt = get_option_no_throw(option_name); if(opt == nullptr) { throw OptionNotFound(option_name); @@ -1083,6 +1160,9 @@ class App { /// Check the status of fallthrough CLI11_NODISCARD bool get_fallthrough() const { return fallthrough_; } + /// Check the status of subcommand fallthrough + CLI11_NODISCARD bool get_subcommand_fallthrough() const { return subcommand_fallthrough_; } + /// Check the status of the allow windows style options CLI11_NODISCARD bool get_allow_windows_style_options() const { return allow_windows_style_options_; } @@ -1118,10 +1198,16 @@ class App { CLI11_NODISCARD std::size_t get_require_option_max() const { return require_option_max_; } /// Get the prefix command status - CLI11_NODISCARD bool get_prefix_command() const { return prefix_command_; } + CLI11_NODISCARD bool get_prefix_command() const { return static_cast(prefix_command_); } + + /// Get the prefix command status + CLI11_NODISCARD PrefixCommandMode get_prefix_command_mode() const { return prefix_command_; } /// Get the status of allow extras - CLI11_NODISCARD bool get_allow_extras() const { return allow_extras_; } + CLI11_NODISCARD bool get_allow_extras() const { return allow_extras_ > ExtrasMode::Ignore; } + + /// Get the mode of allow_extras + CLI11_NODISCARD ExtrasMode get_allow_extras_mode() const { return allow_extras_; } /// Get the status of required CLI11_NODISCARD bool get_required() const { return required_; } @@ -1132,6 +1218,12 @@ class App { /// Get the status of silence CLI11_NODISCARD bool get_silent() const { return silent_; } + /// Get the status of allowing non standard option names + CLI11_NODISCARD bool get_allow_non_standard_option_names() const { return allow_non_standard_options_; } + + /// Get the status of allowing prefix matching for subcommands + CLI11_NODISCARD bool get_allow_subcommand_prefix_matching() const { return allow_prefix_matching_; } + /// Get the status of disabled CLI11_NODISCARD bool get_immediate_callback() const { return immediate_callback_; } @@ -1146,7 +1238,9 @@ class App { CLI11_NODISCARD bool get_validate_optional_arguments() const { return validate_optional_arguments_; } /// Get the status of allow extras - CLI11_NODISCARD config_extras_mode get_allow_config_extras() const { return allow_config_extras_; } + CLI11_NODISCARD config_extras_mode get_allow_config_extras() const { + return static_cast(allow_config_extras_); + } /// Get a pointer to the help flag. Option *get_help_ptr() { return help_ptr_; } @@ -1190,9 +1284,18 @@ class App { /// Get a display name for an app CLI11_NODISCARD std::string get_display_name(bool with_aliases = false) const; - /// Check the name, case insensitive and underscore insensitive if set + /// Check the name, case-insensitive and underscore insensitive, and prefix matching if set + /// @return true if matched CLI11_NODISCARD bool check_name(std::string name_to_check) const; + /// @brief enumeration of matching possibilities + enum class NameMatch : std::uint8_t { none = 0, exact = 1, prefix = 2 }; + + /// Check the name, case-insensitive and underscore insensitive if set + /// @return NameMatch::none if no match, NameMatch::exact if the match is exact NameMatch::prefix if prefix is + /// enabled and a prefix matches + CLI11_NODISCARD NameMatch check_name_detail(std::string name_to_check) const; + /// Get the groups available directly from this option (in order) CLI11_NODISCARD std::vector get_groups() const; @@ -1244,12 +1347,12 @@ class App { void _process_env(); /// Process callbacks. Runs on *all* subcommands. - void _process_callbacks(); + void _process_callbacks(CallbackPriority priority); /// Run help flag processing if any are found. /// /// The flags allow recursive calls to remember if there was a help flag on a parent. - void _process_help_flags(bool trigger_help = false, bool trigger_all_help = false) const; + void _process_help_flags(CallbackPriority priority, bool trigger_help = false, bool trigger_all_help = false) const; /// Verify required options and cross requirements. Subcommands too (only if selected). void _process_requirements(); @@ -1260,10 +1363,6 @@ class App { /// Throw an error if anything is left over and should not be. void _process_extras(); - /// Throw an error if anything is left over and should not be. - /// Modifies the args to fill in the missing items before throwing. - void _process_extras(std::vector &args); - /// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands void increment_parsed(); @@ -1285,6 +1384,9 @@ class App { /// Fill in a single config option bool _parse_single_config(const ConfigItem &item, std::size_t level = 0); + /// @brief store the results for a flag like option + bool _add_flag_like_result(Option *op, const ConfigItem &item, const std::vector &inputs); + /// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing /// from main return false if the parse has failed and needs to return to parent bool _parse_single(std::vector &args, bool &positional_only); @@ -1320,7 +1422,10 @@ class App { void _trigger_pre_parse(std::size_t remaining_args); /// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app - App *_get_fallthrough_parent(); + CLI11_NODISCARD App *_get_fallthrough_parent() noexcept; + + /// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app + CLI11_NODISCARD const App *_get_fallthrough_parent() const noexcept; /// Helper function to run through all possible comparisons of subcommand names to check there is no overlap CLI11_NODISCARD const std::string &_compare_subcommand_names(const App &subcom, const App &base) const; @@ -1340,6 +1445,11 @@ class Option_group : public App { : App(std::move(group_description), "", parent) { group(group_name); // option groups should have automatic fallthrough + if(group_name.empty() || group_name.front() == '+') { + // help will not be used by default in these contexts + set_help_flag(""); + set_help_all_flag(""); + } } using App::add_option; /// Add an existing option to the Option_group @@ -1437,6 +1547,9 @@ struct AppFriend { #endif /// Wrap the fallthrough parent function to make sure that is working correctly static App *get_fallthrough_parent(App *app) { return app->_get_fallthrough_parent(); } + + /// Wrap the const fallthrough parent function to make sure that is working correctly + static const App *get_fallthrough_parent(const App *app) { return app->_get_fallthrough_parent(); } }; } // namespace detail diff --git a/contrib/CLI11/include/CLI/Argv.hpp b/contrib/CLI11/include/CLI/Argv.hpp index 16aa88488..b9c7b0b04 100644 --- a/contrib/CLI11/include/CLI/Argv.hpp +++ b/contrib/CLI11/include/CLI/Argv.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/contrib/CLI11/include/CLI/CLI.hpp b/contrib/CLI11/include/CLI/CLI.hpp index f8607ea4d..3f489c075 100644 --- a/contrib/CLI11/include/CLI/CLI.hpp +++ b/contrib/CLI11/include/CLI/CLI.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -41,4 +41,6 @@ #include "Formatter.hpp" +#include "ExtraValidators.hpp" + // IWYU pragma: end_exports diff --git a/contrib/CLI11/include/CLI/Config.hpp b/contrib/CLI11/include/CLI/Config.hpp index 8384e977a..bf2ce97f9 100644 --- a/contrib/CLI11/include/CLI/Config.hpp +++ b/contrib/CLI11/include/CLI/Config.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/contrib/CLI11/include/CLI/ConfigFwd.hpp b/contrib/CLI11/include/CLI/ConfigFwd.hpp index 3db801027..5b4a1fab7 100644 --- a/contrib/CLI11/include/CLI/ConfigFwd.hpp +++ b/contrib/CLI11/include/CLI/ConfigFwd.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -16,7 +16,7 @@ #include #include // [CLI11:public_includes:end] - +#include "Encoding.hpp" #include "Error.hpp" #include "StringTools.hpp" @@ -34,12 +34,14 @@ struct ConfigItem { std::string name{}; /// Listing of inputs std::vector inputs{}; - + /// @brief indicator if a multiline vector separator was inserted + bool multiline{false}; /// The list of parents and name joined by "." CLI11_NODISCARD std::string fullname() const { std::vector tmp = parents; tmp.emplace_back(name); return detail::join(tmp, "."); + (void)multiline; // suppression for cppcheck false positive } }; @@ -68,7 +70,12 @@ class Config { /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure CLI11_NODISCARD std::vector from_file(const std::string &name) const { +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 + std::ifstream input{to_path(name)}; +#else std::ifstream input{name}; +#endif + if(!input.good()) throw FileError::Missing(name); @@ -100,6 +107,10 @@ class ConfigBase : public Config { uint8_t maximumLayers{255}; /// the separator used to separator parent layers char parentSeparatorChar{'.'}; + /// comment default values + bool commentDefaultsBool = false; + /// specify the config reader should collapse repeated field names to a single vector + bool allowMultipleDuplicateFields{false}; /// Specify the configuration index to use for arrayed sections int16_t configIndex{-1}; /// Specify the configuration section that should be used @@ -147,6 +158,11 @@ class ConfigBase : public Config { parentSeparatorChar = sep; return this; } + /// comment default value options + ConfigBase *commentDefaults(bool comDef = true) { + commentDefaultsBool = comDef; + return this; + } /// get a reference to the configuration section std::string §ionRef() { return configSection; } /// get the section @@ -166,6 +182,11 @@ class ConfigBase : public Config { configIndex = sectionIndex; return this; } + /// specify that multiple duplicate arguments should be merged even if not sequential + ConfigBase *allowDuplicateFields(bool value = true) { + allowMultipleDuplicateFields = value; + return this; + } }; /// the default Config is the TOML file format diff --git a/contrib/CLI11/include/CLI/Encoding.hpp b/contrib/CLI11/include/CLI/Encoding.hpp index 42e24909e..e0f5a24d4 100644 --- a/contrib/CLI11/include/CLI/Encoding.hpp +++ b/contrib/CLI11/include/CLI/Encoding.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/contrib/CLI11/include/CLI/Error.hpp b/contrib/CLI11/include/CLI/Error.hpp index 84dcef5e8..63f4eb845 100644 --- a/contrib/CLI11/include/CLI/Error.hpp +++ b/contrib/CLI11/include/CLI/Error.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -41,7 +41,7 @@ namespace CLI { /// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, /// int values from e.get_error_code(). -enum class ExitCodes { +enum class ExitCodes : int { Success = 0, IncorrectConstruction = 100, BadNameString, @@ -132,8 +132,8 @@ class BadNameString : public ConstructionError { static BadNameString BadPositionalName(std::string name) { return BadNameString("Invalid positional Name: " + name); } - static BadNameString DashesOnly(std::string name) { - return BadNameString("Must have a name, not just dashes: " + name); + static BadNameString ReservedName(std::string name) { + return BadNameString("Names '-','--','++' are reserved and not allowed as option names " + name); } static BadNameString MultiPositionalNames(std::string name) { return BadNameString("Only one positional name allowed, remove: " + name); @@ -275,7 +275,7 @@ class ArgumentMismatch : public ParseError { std::to_string(received)); } static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { - return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + return ArgumentMismatch(name + ": At most " + std::to_string(num) + " required but received " + std::to_string(received)); } static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { @@ -310,13 +310,13 @@ class ExtrasError : public ParseError { explicit ExtrasError(std::vector args) : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + - detail::rjoin(args, " "), + detail::join(args, " "), ExitCodes::ExtrasError) {} ExtrasError(const std::string &name, std::vector args) : ExtrasError(name, (args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + - detail::rjoin(args, " "), + detail::join(args, " "), ExitCodes::ExtrasError) {} }; @@ -347,7 +347,7 @@ class HorribleError : public ParseError { // After parsing -/// Thrown when counting a non-existent option +/// Thrown when counting a nonexistent option class OptionNotFound : public Error { CLI11_ERROR_DEF(Error, OptionNotFound) explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} diff --git a/contrib/CLI11/include/CLI/ExtraValidators.hpp b/contrib/CLI11/include/CLI/ExtraValidators.hpp new file mode 100644 index 000000000..faf2e440b --- /dev/null +++ b/contrib/CLI11/include/CLI/ExtraValidators.hpp @@ -0,0 +1,623 @@ +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once +#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \ + (!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0) +// IWYU pragma: private, include "CLI/CLI.hpp" + +#include "Error.hpp" +#include "Macros.hpp" +#include "StringTools.hpp" +#include "Validators.hpp" + +// [CLI11:public_includes:set] +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// [CLI11:public_includes:end] + +namespace CLI { +// [CLI11:extra_validators_hpp:verbatim] +// The implementation of the extra validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator(); +}; + +} // namespace detail + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) + : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; + auto val = DesiredType(); + if(!lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string{}; + }) {} + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min_val, T max_val) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; + description(out.str()); + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} +}; + +// Static is not needed here, because global const implies static. + +/// Check for an IP4 address +CLI11_MODULE_INLINE const detail::IPV4Validator ValidIPV4; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + // NOLINTNEXTLINE + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +} // namespace detail + /// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&...args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&...args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&...args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { + return detail::value_string(detail::pair_adaptor::second(v)); + }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + bool converted = lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options : std::uint8_t { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num{}; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000); + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000); + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000); +}; + +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 +// new extra validators +#if CLI11_HAS_FILESYSTEM +namespace detail { +enum class Permission : std::uint8_t { none = 0, read = 1, write = 2, exec = 4 }; +class PermissionValidator : public Validator { + public: + explicit PermissionValidator(Permission permission); +}; +} // namespace detail + +/// Check that the file exist and available for read +const detail::PermissionValidator ReadPermissions(detail::Permission::read); + +/// Check that the file exist and available for write +const detail::PermissionValidator WritePermissions(detail::Permission::write); + +/// Check that the file exist and available for write +const detail::PermissionValidator ExecPermissions(detail::Permission::exec); +#endif + +#endif +// [CLI11:extra_validators_hpp:end] +} // namespace CLI + +#ifndef CLI11_COMPILE +#include "impl/ExtraValidators_inl.hpp" // IWYU pragma: export +#endif + +#endif diff --git a/contrib/CLI11/include/CLI/Formatter.hpp b/contrib/CLI11/include/CLI/Formatter.hpp index c5da1ef9d..111fae424 100644 --- a/contrib/CLI11/include/CLI/Formatter.hpp +++ b/contrib/CLI11/include/CLI/Formatter.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/contrib/CLI11/include/CLI/FormatterFwd.hpp b/contrib/CLI11/include/CLI/FormatterFwd.hpp index ad3f49c30..d0a134927 100644 --- a/contrib/CLI11/include/CLI/FormatterFwd.hpp +++ b/contrib/CLI11/include/CLI/FormatterFwd.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -29,7 +29,7 @@ class App; /// This is passed in by App; all user classes must accept this as /// the second argument. -enum class AppFormatMode { +enum class AppFormatMode : std::uint8_t { Normal, ///< The normal, detailed help All, ///< A fully expanded help Sub, ///< Used when printed as part of expanded subcommand @@ -44,9 +44,29 @@ class FormatterBase { /// @name Options ///@{ - /// The width of the first column + /// The width of the left column (options/flags/subcommands) std::size_t column_width_{30}; + /// The alignment ratio for long options within the left column + float long_option_alignment_ratio_{1 / 3.f}; + + /// The width of the right column (description of options/flags/subcommands) + std::size_t right_column_width_{65}; + + /// The width of the description paragraph at the top of help + std::size_t description_paragraph_width_{80}; + + /// The width of the footer paragraph + std::size_t footer_paragraph_width_{80}; + + /// options controlling formatting for footer and descriptions + bool enable_description_formatting_{true}; + bool enable_footer_formatting_{true}; + + /// options controlling formatting of options + bool enable_option_defaults_{true}; + bool enable_option_type_names_{true}; + bool enable_default_flag_values_{true}; /// @brief The required help printout labels (user changeable) /// Values are Needs, Excludes, etc. std::map labels_{}; @@ -72,12 +92,38 @@ class FormatterBase { /// @name Setters ///@{ - /// Set the "REQUIRED" label + /// Set the "REQUIRED" or other labels void label(std::string key, std::string val) { labels_[key] = val; } - /// Set the column width + /// Set the left column width (options/flags/subcommands) void column_width(std::size_t val) { column_width_ = val; } + /// Set the alignment ratio for long options within the left column + /// The ratio is in [0;1] range (e.g. 0.2 = 20% of column width, 6.f/column_width = 6th character) + void long_option_alignment_ratio(float ratio) { + long_option_alignment_ratio_ = + (ratio >= 0.0f) ? ((ratio <= 1.0f) ? ratio : 1.0f / ratio) : ((ratio < -1.0f) ? 1.0f / (-ratio) : -ratio); + } + + /// Set the right column width (description of options/flags/subcommands) + void right_column_width(std::size_t val) { right_column_width_ = val; } + + /// Set the description paragraph width at the top of help + void description_paragraph_width(std::size_t val) { description_paragraph_width_ = val; } + + /// Set the footer paragraph width + void footer_paragraph_width(std::size_t val) { footer_paragraph_width_ = val; } + /// enable formatting for description paragraph + void enable_description_formatting(bool value = true) { enable_description_formatting_ = value; } + /// disable formatting for footer paragraph + void enable_footer_formatting(bool value = true) { enable_footer_formatting_ = value; } + + /// enable option defaults to be printed + void enable_option_defaults(bool value = true) { enable_option_defaults_ = value; } + /// enable option type names to be printed + void enable_option_type_names(bool value = true) { enable_option_type_names_ = value; } + /// enable default flag values to be printed + void enable_default_flag_values(bool value = true) { enable_default_flag_values_ = value; } ///@} /// @name Getters ///@{ @@ -89,9 +135,37 @@ class FormatterBase { return labels_.at(key); } - /// Get the current column width + /// Get the current left column width (options/flags/subcommands) CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; } + /// Get the current right column width (description of options/flags/subcommands) + CLI11_NODISCARD std::size_t get_right_column_width() const { return right_column_width_; } + + /// Get the current description paragraph width at the top of help + CLI11_NODISCARD std::size_t get_description_paragraph_width() const { return description_paragraph_width_; } + + /// Get the current footer paragraph width + CLI11_NODISCARD std::size_t get_footer_paragraph_width() const { return footer_paragraph_width_; } + + /// @brief Get the current alignment ratio for long options within the left column + /// @return + CLI11_NODISCARD float get_long_option_alignment_ratio() const { return long_option_alignment_ratio_; } + + /// Get the current status of description paragraph formatting + CLI11_NODISCARD bool is_description_paragraph_formatting_enabled() const { return enable_description_formatting_; } + + /// Get the current status of whether footer paragraph formatting is enabled + CLI11_NODISCARD bool is_footer_paragraph_formatting_enabled() const { return enable_footer_formatting_; } + + /// Get the current status of whether option defaults are printed + CLI11_NODISCARD bool is_option_defaults_enabled() const { return enable_option_defaults_; } + + /// Get the current status of whether option type names are printed + CLI11_NODISCARD bool is_option_type_names_enabled() const { return enable_option_type_names_; } + + /// Get the current status of whether default flag values are printed + CLI11_NODISCARD bool is_default_flag_values_enabled() const { return enable_default_flag_values_; } + ///@} }; @@ -146,7 +220,7 @@ class Formatter : public FormatterBase { virtual std::string make_subcommand(const App *sub) const; /// This prints out a subcommand in help-all - virtual std::string make_expanded(const App *sub) const; + virtual std::string make_expanded(const App *sub, AppFormatMode mode) const; /// This prints out all the groups of options virtual std::string make_footer(const App *app) const; @@ -158,19 +232,14 @@ class Formatter : public FormatterBase { virtual std::string make_usage(const App *app, std::string name) const; /// This puts everything together - std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override; + std::string make_help(const App *app, std::string, AppFormatMode mode) const override; ///@} /// @name Options ///@{ /// This prints out an option help line, either positional or optional form - virtual std::string make_option(const Option *opt, bool is_positional) const { - std::stringstream out; - detail::format_help( - out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); - return out.str(); - } + virtual std::string make_option(const Option *, bool) const; /// @brief This is the name part of an option, Default: left column virtual std::string make_option_name(const Option *, bool) const; diff --git a/contrib/CLI11/include/CLI/Macros.hpp b/contrib/CLI11/include/CLI/Macros.hpp index 73fbf87d7..d1c0c9f54 100644 --- a/contrib/CLI11/include/CLI/Macros.hpp +++ b/contrib/CLI11/include/CLI/Macros.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -18,11 +18,17 @@ #define CLI11_CPP17 #if __cplusplus > 201703L #define CLI11_CPP20 +#if __cplusplus > 202002L +#define CLI11_CPP23 +#if __cplusplus > 202302L +#define CLI11_CPP26 +#endif +#endif #endif #endif #endif #elif defined(_MSC_VER) && __cplusplus == 199711L -// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard was fully implemented) // Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer #if _MSVC_LANG >= 201402L #define CLI11_CPP14 @@ -30,6 +36,9 @@ #define CLI11_CPP17 #if _MSVC_LANG > 201703L && _MSC_VER >= 1910 #define CLI11_CPP20 +#if _MSVC_LANG > 202002L && _MSC_VER >= 1922 +#define CLI11_CPP23 +#endif #endif #endif #endif @@ -53,15 +62,15 @@ /** detection of rtti */ #ifndef CLI11_USE_STATIC_RTTI -#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#if (defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) #define CLI11_USE_STATIC_RTTI 1 #elif defined(__cpp_rtti) -#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#if (defined(_CPPRTTI) && _CPPRTTI == 0) #define CLI11_USE_STATIC_RTTI 1 #else #define CLI11_USE_STATIC_RTTI 0 #endif -#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#elif (defined(__GCC_RTTI) && __GXX_RTTI) #define CLI11_USE_STATIC_RTTI 0 #else #define CLI11_USE_STATIC_RTTI 1 @@ -96,12 +105,48 @@ #endif /** availability */ +#if !defined(CLI11_CPP26) && !defined(CLI11_HAS_CODECVT) #if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 #define CLI11_HAS_CODECVT 0 #else #define CLI11_HAS_CODECVT 1 #include #endif +#else +#if defined(CLI11_HAS_CODECVT) +#if CLI11_HAS_CODECVT > 0 +#include +#endif +#else +#define CLI11_HAS_CODECVT 0 +#endif +#endif + +/** rtti enabled */ +#ifndef CLI11_HAS_RTTI +#if defined(__GXX_RTTI) && __GXX_RTTI == 1 +// gcc +#define CLI11_HAS_RTTI 1 +#elif defined(_CPPRTTI) && _CPPRTTI == 1 +// msvc +#define CLI11_HAS_RTTI 1 +#elif defined(__NO_RTTI__) && __NO_RTTI__ == 1 +// intel +#define CLI11_HAS_RTTI 0 +#elif defined(__has_feature) +// clang and other newer compilers +#if __has_feature(cxx_rtti) +#define CLI11_HAS_RTTI 1 +#else +#define CLI11_HAS_RTTI 0 +#endif +#elif defined(__RTTI) || defined(__INTEL_RTTI__) +// more intel and some other compilers +#define CLI11_HAS_RTTI 1 +#else +#define CLI11_HAS_RTTI 0 +#endif +#endif /** disable deprecations */ #if defined(__GNUC__) // GCC or clang @@ -130,4 +175,11 @@ #else #define CLI11_INLINE inline #endif + +/** Module inline to support module operations**/ +#if defined CLI11_CPP17 +#define CLI11_MODULE_INLINE inline +#else +#define CLI11_MODULE_INLINE static +#endif // [CLI11:macros_hpp:end] diff --git a/contrib/CLI11/include/CLI/Option.hpp b/contrib/CLI11/include/CLI/Option.hpp index 40b7f6f63..7eae42498 100644 --- a/contrib/CLI11/include/CLI/Option.hpp +++ b/contrib/CLI11/include/CLI/Option.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -34,8 +34,11 @@ using callback_t = std::function; class Option; class App; +class ConfigBase; using Option_p = std::unique_ptr