diff --git a/.travis.yml b/.travis.yml index b3956ed80..78e1f02f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -118,16 +118,6 @@ matrix: script: - .ci/make_and_test.sh 11 - # macOS and clang - - os: osx - compiler: clang - install: - - brew update - - echo 'brew "python"' > Brewfile - - echo 'brew "ccache"' >> Brewfile - - brew bundle - - python -m ensurepip --user - install: skip script: diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e3e9a70..bbdc1b39e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,30 @@ ## Version 1.8: Sets and Flags (IN PROGRESS) -Set handling has been completely replaced by a new backend that works as a Validator. This provides a single interface instead of the 16 different functions in App. It also allows ordered collections to be used, custom functions for filtering, and better help and error messages. You can also use a collection of pairs (like `std::map`) to transform the match into an output. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support general flag types. A new `add_option_fn` lets you more easily program CLI11 options with the types you choose. Vector options now support a custom separator. Apps can now be composed with unnamed subcommand support. +Set handling has been completely replaced by a new backend that works as a Validator. This provides a single interface instead of the 16 different functions in App. It also allows ordered collections to be used, custom functions for filtering, and better help and error messages. You can also use a collection of pairs (like `std::map`) to transform the match into an output. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support general flag types. A new `add_option_fn` lets you more easily program CLI11 options with the types you choose. Vector options now support a custom separator. Apps can now be composed with unnamed subcommand support. The final bool "defaults" flag when creating options has been replaced by `->capture_default_str()` (ending an old limitation in construction made this possible); the old method is still available but may be removed in future versions. +* Replaced default help capture: `.add_option("name", value, "", True)` becomes `.add_option("name", value)->capture_default_str()` [#242] +* Added `.always_capture_default()` [#242] * New `CLI::IsMember` validator replaces set validation [#222] * IsMember also supports container of pairs, transform allows modification of result [#228] * Much more powerful flags with different values [#211], general types [#235] * `add_option` now supports bool due to unified bool handling [#211] * Support for composable unnamed subcommands [#216] +* Reparsing is better supported with `.remaining_for_passthrough()` [#265] * Custom vector separator using `->delimiter(char)` [#209], [#221], [#240] -* Validators added for IP4 addresses and positive numbers [#210] +* Validators added for IP4 addresses and positive numbers [#210] and numbers [#262] * Minimum required Boost for optional Optionals has been corrected to 1.61 [#226] * Positionals can stop options from being parsed with `app.positionals_at_end()` [#223] +* Added `validate_positionals` [#262] +* Positional parsing is much more powerful [#251], duplicates supported []#247] * Validators can be negated with `!` [#230], and now handle tname functions [#228] * Better enum support and streaming helper [#233] and [#228] * Cleanup for shadow warnings [#232] +* Better alignment on multiline descriptions [#269] +* Better support for aarch64 [#266] > ### Converting from CLI11 1.7: > +> * `.add_option(..., true)` should be replaced by `.add_option(...)->capture_default_str()` or `app.option_defaults()->always_capture_default()` can be used > * `app.add_set("--name", value, {"choice1", "choice2"})` should become `app.add_option("--name", value)->check(CLI::IsMember({"choice1", "choice2"}))` > * The `_ignore_case` version of this can be replaced by adding `CLI::ignore_case` to the argument list in `IsMember` > * The `_ignore_underscore` version of this can be replaced by adding `CLI::ignore_underscore` to the argument list in `IsMember` @@ -39,6 +47,13 @@ Set handling has been completely replaced by a new backend that works as a Valid [#233]: https://github.com/CLIUtils/CLI11/pull/233 [#235]: https://github.com/CLIUtils/CLI11/pull/235 [#240]: https://github.com/CLIUtils/CLI11/pull/240 +[#242]: https://github.com/CLIUtils/CLI11/pull/242 +[#247]: https://github.com/CLIUtils/CLI11/pull/247 +[#251]: https://github.com/CLIUtils/CLI11/pull/251 +[#262]: https://github.com/CLIUtils/CLI11/pull/262 +[#265]: https://github.com/CLIUtils/CLI11/pull/265 +[#266]: https://github.com/CLIUtils/CLI11/pull/266 +[#269]: https://github.com/CLIUtils/CLI11/pull/269 ## Version 1.7.1: Quick patch diff --git a/README.md b/README.md index 7413d127e..7444fdf34 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,11 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature - [Option types](#option-types) - [Example](#example) - [Option options](#option-options) - - [Validators](#validators) 🚧 - - [Transforming Validators](#transforming-validators)🚧 - - [Validator operations](#validator-operations)🚧 - - [Custom Validators](#custom-validators)🚧 - - [Querying Validators](#querying-validators)🚧 + - [Validators](#validators) 🚧 + - [Transforming Validators](#transforming-validators) 🚧 + - [Validator operations](#validator-operations) 🚧 + - [Custom Validators](#custom-validators) 🚧 + - [Querying Validators](#querying-validators) 🚧 - [Getting Results](#getting-results) 🚧 - [Subcommands](#subcommands) - [Subcommand options](#subcommand-options) @@ -64,7 +64,7 @@ CLI11 provides all the features you expect in a powerful command line parser, wi It is tested on [Travis][], [AppVeyor][], and [Azure][], and is being included in 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 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 post][], or [Version 1.6 post][] for more information. -You can be notified when new releases are made by subscribing to on an RSS reader, like Feedly, or use the releases mode of the github watching tool. +You can be notified when new releases are made by subscribing to on an RSS reader, like Feedly, or use the releases mode of the github watching tool. ### Why write another CLI parser? @@ -74,7 +74,7 @@ An acceptable CLI parser library should be all of the following: - Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability. - C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang 3.5+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+. - Work on Linux, macOS, and Windows. -- Well tested using [Travis][] (Linux and macOS) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][]. +- Well tested using [Travis][] (Linux) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][]. - Clear help printing. - Nice error messages. - Standard shell idioms supported naturally, like grouping flags, a positional separator, etc. @@ -193,8 +193,7 @@ app.add_option(option_name, help_str="") // 🚧 app.add_option(option_name, variable_to_bind_to, // bool, int, float, vector, 🚧 enum, or string-like, or anything with a defined conversion from a string - help_string="", - default=false) + help_string="") app.add_option_function(option_name, function , // 🚧 int, bool, float, enum, vector, or string-like, or anything with a defined conversion from a string @@ -225,8 +224,7 @@ Option_group *app.add_option_group(name,description); // 🚧 -app.add_set(option_name, - variable_to_bind_to, // Same type as stored by set - set_of_possible_options, // Set will be copied, ignores changes -- help_string="", -- default=false) +- help_string="") -app.add_mutable_set(... // πŸ†• Set can change later, keeps reference -app.add_set_ignore_case(... // String only -app.add_mutable_set_ignore_case(... // πŸ†• String only @@ -236,9 +234,9 @@ Option_group *app.add_option_group(name,description); // 🚧 -app.add_mutable_set_ignore_case_underscore(... // πŸ†• String only ``` -An option name must start with a alphabetic character, underscore, a number 🚧, '?'🚧, or '@'🚧. For long options, after the first character '.', and '-' are also valid characters. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated 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. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`. +An option name must start with a alphabetic character, underscore, a number 🚧, '?' 🚧, or '@' 🚧. For long options, after the first character '.', and '-' are also valid characters. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated 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. -The `add_option_function(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid. +The `add_option_function(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid. 🚧 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 if some flags are passed. For example: @@ -285,16 +283,19 @@ Before parsing, you can set the following options: - `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden). - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). - `->ignore_underscore()`: πŸ†• Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character -- `->disable_flag_override()`: 🚧 from the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options. +- `->disable_flag_override()`: 🚧 From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options. - `->delimiter(char)`: 🚧 allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value. - `->description(str)`: πŸ†• Set/change the description. - `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). -- `->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 +- `->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 - `->check(Validator)`:🚧 Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones. - `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options. - `->transform(Validator)`: uses a Validator object to do the transformation see [Validators](#validators) for a description of available Validators and how to create new ones. - `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered. - `->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. + `->default_function(std::string())`: 🚧 Advanced: Change the function that `capture_default_str()` uses. + `->always_capture_default()`: 🚧 Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. 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 that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector` of results. @@ -324,7 +325,7 @@ On the command line, options can be given as: Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments. If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included). -If the remaining arguments are to processed by another `App` then the function `remaining_for_passthrough()`🚧 can be used to get the remaining arguments in reverse order such that `app.parse(vector)` works directly and could even be used inside a subcommand callback. +If the remaining arguments are to processed by another `App` then the function `remaining_for_passthrough()` 🚧 can be used to get the remaining arguments in reverse order such that `app.parse(vector)` works directly and could even be used inside a subcommand callback. You can access a vector of pointers to the parsed options in the original order using `parse_order()`. If `--` is present in the command line that does not end an unlimited option, then @@ -349,20 +350,25 @@ CLI11 has several Validators built-in that perform some common checks - `CLI::ValidIPV4`: 🚧 Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`. These Validators can be used by simply passing the name into the `check` or `transform` methods on an option + ```cpp ->check(CLI::ExistingFile); ->check(CLI::Range(0,10)); ``` -Validators can be merged using `&` and `|` and inverted using `!`🚧. For example +Validators can be merged using `&` and `|` and inverted using `!` 🚧. For example: + ```cpp ->check(CLI::Range(0,10)|CLI::Range(20,30)); ``` + will produce a check to ensure a value is between 0 and 10 or 20 and 30. + ```cpp ->check(!CLI::PositiveNumber); ``` -will produce a check for a number less than 0; + +will produce a check for a number less than 0. ##### Transforming Validators There are a few built in Validators that let you transform values if used with the `transform` function. If they also do some checks then they can be used `check` but some may do nothing in that case. @@ -377,7 +383,7 @@ of `IsMember`: * `CLI::IsMember(std::set({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`. * `CLI::IsMember(std::map({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key. The value member of the map is not used in `IsMember`, so it can be any type. * `auto p = std::make_shared>(std::initializer_list("one", "two")); CLI::IsMember(p)`: You can modify `p` later. -* 🚧 The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map. `Transformer` does not do any checking so values not in the map are ignored. `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`. A Transformer placed using `check` will not do anything. +* 🚧 The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map. `Transformer` does not do any checking so values not in the map are ignored. `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`. A Transformer placed using `check` will not do anything. After specifying a map of options, you can also specify "filter" just like in `CLI::IsMember`. Here are some examples (`Transformer` and `CheckedTransformer` are interchangeable in the examples) of `Transformer`: @@ -385,12 +391,12 @@ of `Transformer`: * `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values. * `CLI::Transformer(std::map({"two",2},{"three",3},{"four",4}}))`: most maplike containers work, the `::value_type` needs to produce a pair of some kind. - * `CLI::CheckedTransformer(std::map({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value. `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs. + * `CLI::CheckedTransformer(std::map({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value. `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs. * `auto p = std::make_shared>(std::initializer_list>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs` is an alias for `std::vector>` NOTES: If the container used in `IsMember`, `Transformer`, or `CheckedTransformer` has a `find` function like `std::unordered_map` or `std::map` then that function is used to do the searching. If it does not have a `find` function a linear search is performed. If there are filters present, the fast search is performed first, and if that fails a linear search with the filters on the key values is performed. -##### Validator operations🚧 +##### Validator operations 🚧 Validators are copyable and have a few operations that can be performed on them to alter settings. Most of the built in Validators have a default description that is displayed in the help. This can be altered via `.description(validator_description)`. The name of a Validator, which is useful for later reference from the `get_validator(name)` method of an `Option` can be set via `.name(validator_name)` The operation function of a Validator can be set via @@ -406,7 +412,7 @@ The check can later be activated through opt->get_validator("range")->active(); ``` -##### Custom Validators🚧 +##### Custom Validators 🚧 A validator object with a custom function can be created via ```cpp @@ -427,10 +433,10 @@ opt->get_validator(name); This will retrieve a Validator with the given name or throw a `CLI::OptionNotFound` error. If no name is given or name is empty the first unnamed Validator will be returned or the first Validator if there is only one. Validators have a few functions to query the current values - * `get_description()`:🚧 Will return a description string - * `get_name()`:🚧 Will return the Validator name - * `get_active()`:🚧 Will return the current active state, true if the Validator is active. - * `get_modifying()`: 🚧 Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()`🚧 method, though it is recommended to let `check` and `transform` option methods manipulate it if needed. + * `get_description()`: 🚧 Will return a description string + * `get_name()`: 🚧 Will return the Validator name + * `get_active()`: 🚧 Will return the current active state, true if the Validator is active. + * `get_modifying()`: 🚧 Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()` 🚧 method, though it is recommended to let `check` and `transform` option methods manipulate it if needed. #### Getting results In most cases, the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions. But there are situations where this is not possible or desired. For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code: @@ -456,7 +462,7 @@ You are allowed to throw `CLI::Success` in the callbacks. Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved). The same subcommand can be triggered multiple times but all positional arguments will take precedence over the second and future calls of the subcommand. `->count()` on the subcommand will return the number of times the subcommand was called. The subcommand callback will only be triggered once unless the `.immediate_callback()` flag is set. 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. 🚧 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 a similarly to groups in the main `App`. See [Option groups](#option-groups) to see how this might work. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed. Callbacks for nameless subcommands are only triggered if any options from the subcommand were parsed. +Nameless subcommands function a similarly to groups in the main `App`. See [Option groups](#option-groups) to see how this might work. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed. Callbacks for nameless subcommands are only triggered if any options from the subcommand were parsed. #### Subcommand options @@ -467,22 +473,22 @@ There are several options that are supported on the main app and subcommands and - `.allow_windows_style_options()`: πŸ†• Allow command line options to be parsed in 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`. - `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through. - `.disable()`: 🚧 Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group. -- `.disabled_by_default()`:🚧 Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others. +- `.disabled_by_default()`: 🚧 Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others. - `.enabled_by_default()`: 🚧 Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others. -- `.validate_positionals()`:🚧 Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments. -- `.excludes(option_or_subcommand)`: 🚧 If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error. +- `.validate_positionals()`: 🚧 Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments. +- `.excludes(option_or_subcommand)`: 🚧 If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error. - `.require_option()`: 🚧 Require 1 or more options or option groups be used. -- `.require_option(N)`: 🚧 Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. +- `.require_option(N)`: 🚧 Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. - `.require_option(min, max)`: 🚧 Explicitly set min and max allowed options or option groups. Setting `max` to 0 implies unlimited options. - `.require_subcommand()`: Require 1 or more subcommands. - `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. - `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited. - `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand. - `.add_subcommand(shared_ptr)`: 🚧 Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand. -- `.remove_subcommand(App)`:🚧 Remove a subcommand from the app or subcommand. +- `.remove_subcommand(App)`: 🚧 Remove a subcommand from the app or subcommand. - `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. - `.get_subcommands(filter)`: The list of subcommands that match a particular filter function. -- `.add_option_group(name="", description="")`: 🚧 Add an [option group](#option-groups) to an App, an option group is specialized subcommand intended for containing groups of options or other groups for controlling how options interact. +- `.add_option_group(name="", description="")`: 🚧 Add an [option group](#option-groups) to an App, an option group is specialized subcommand intended for containing groups of options or other groups for controlling how options interact. - `.get_parent()`: Get the parent App or `nullptr` if called on master 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 - `.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. @@ -514,7 +520,7 @@ There are several options that are supported on the main app and subcommands and #### Callbacks A subcommand has two optional callbacks that are executed at different stages of processing. The `preparse_callback` 🚧 is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process. For the main app the first argument is considered the program name, for subcommands the first argument is the subcommand name. For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group. -The second callback is executed after parsing. The behavior depends on the status of the `immediate_callback` flag 🚧. If true, this runs immediately after the parsing of the subcommand. Or if the flag is false, once after parsing of all arguments. If the `immediate_callback` is set then the callback can be executed multiple times if the subcommand list given multiple times. If the main app or subcommand has a config file, no data from the config file will be reflected in immediate_callback. `immediate_callback()` has no effect on the main app, though it can be inherited. For option_groups `immediate_callback` causes the callback to be run prior to other option groups and options in the main app, effectively giving the options in the group priority. +The second callback is executed after parsing. The behavior depends on the status of the `immediate_callback` flag 🚧. If true, this runs immediately after the parsing of the subcommand. Or if the flag is false, once after parsing of all arguments. If the `immediate_callback` is set then the callback can be executed multiple times if the subcommand list given multiple times. If the main app or subcommand has a config file, no data from the config file will be reflected in immediate_callback. `immediate_callback()` has no effect on the main app, though it can be inherited. For option_groups `immediate_callback` causes the callback to be run prior to other option groups and options in the main app, effectively giving the options in the group priority. For example say an application was set up like @@ -548,7 +554,7 @@ A subcommand is considered terminated when one of the following conditions are m 3. The positional_mark(`--`) is encountered and there are no available positional slots in the subcommand. 4. The subcommand_terminator mark(`++`) is encountered -If the `immediate_callback` flag is set then all contained options are processed and the callback is triggered. If a subcommand with an `immediate_callback` flag is called again, then the contained options are reset, and can be triggered again. +If the `immediate_callback` flag is set then all contained options are processed and the callback is triggered. If a subcommand with an `immediate_callback` flag is called again, then the contained options are reset, and can be triggered again. @@ -579,7 +585,7 @@ This results in the subcommand being moved from its parent into the option group Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first. So care must be taken to make sure of the order when using positional arguments and option groups. Option groups work well with `excludes` and `require_options` methods, as an Application will treat an option group as a single option for the purpose of counting and requirements, and an option group will be considered used if any of the options or subcommands contained in it are used. Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well. Disabling an option group will turn off all options within the group. -The `CLI::TriggerOn`🚧 and `CLI::TriggerOff`🚧 methods are helper methods to allow the use of options/subcommands from one group to trigger another group on or off. +The `CLI::TriggerOn` 🚧 and `CLI::TriggerOff` 🚧 methods are helper methods to allow the use of options/subcommands from one group to trigger another group on or off. ```cpp CLI::TriggerOn(group1_pointer, triggered_group); diff --git a/examples/json.cpp b/examples/json.cpp index bce5ecdab..b4c8101ae 100644 --- a/examples/json.cpp +++ b/examples/json.cpp @@ -28,8 +28,8 @@ class ConfigJSON : public CLI::Config { j[name] = opt->results(); // If the option has a default and is requested by optional argument - else if(default_also && !opt->get_defaultval().empty()) - j[name] = opt->get_defaultval(); + else if(default_also && !opt->get_default_str().empty()) + j[name] = opt->get_default_str(); // Flag, one passed } else if(opt->count() == 1) { diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index c08018d7c..98adf99f7 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -437,17 +437,33 @@ class App { Option *add_option(std::string option_name, callback_t option_callback, std::string option_description = "", - bool defaulted = false) { - Option myopt{option_name, option_description, option_callback, defaulted, this}; + bool defaulted = false, + std::function func = {}) { + Option myopt{option_name, option_description, option_callback, this}; if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { return *v == myopt; }) == std::end(options_)) { options_.emplace_back(); Option_p &option = options_.back(); - option.reset(new Option(option_name, option_description, option_callback, defaulted, this)); + option.reset(new Option(option_name, option_description, option_callback, this)); + + // Set the default string capture function + option->default_function(func); + + // For compatibility with CLI11 1.7 and before, capture the default string here + if(defaulted) + option->capture_default_str(); + + // Transfer defaults to the new option option_defaults_.copy_to(option.get()); + + // Don't bother to capture if we already did + if(!defaulted && option->get_always_capture_default()) + option->capture_default_str(); + return option.get(); + } else throw OptionAlreadyAdded(myopt.get_name()); } @@ -456,12 +472,16 @@ class App { template ::value & !std::is_const::value, detail::enabler> = detail::dummy> Option *add_option(std::string option_name, T &variable, ///< The variable to set - std::string option_description = "") { + std::string option_description = "", + bool defaulted = false) { - CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; + auto fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; - Option *opt = add_option(option_name, fun, option_description, false); + Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { + return std::string(CLI::detail::to_string(variable)); + }); opt->type_name(detail::type_name()); + return opt; } @@ -471,7 +491,7 @@ class App { const std::function &func, ///< the callback to execute std::string option_description = "") { - CLI::callback_t fun = [func](CLI::results_t res) { + auto fun = [func](CLI::results_t res) { T variable; bool result = detail::lexical_cast(res[0], variable); if(result) { @@ -484,6 +504,7 @@ class App { opt->type_name(detail::type_name()); return opt; } + /// Add option with no description or variable assignment Option *add_option(std::string option_name) { return add_option(option_name, CLI::callback_t(), std::string{}, false); @@ -497,34 +518,14 @@ class App { return add_option(option_name, CLI::callback_t(), option_description, false); } - /// Add option for non-vectors with a default print, allow template to specify conversion type - template ::value && !std::is_const::value, detail::enabler> = detail::dummy> - Option *add_option(std::string option_name, - T &variable, ///< The variable to set - std::string option_description, - bool defaulted) { - static_assert(std::is_constructible::value, "assign type must be assignable from conversion type"); - CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; - - Option *opt = add_option(option_name, fun, option_description, defaulted); - opt->type_name(detail::type_name()); - if(defaulted) { - std::stringstream out; - out << variable; - opt->default_str(out.str()); - } - return opt; - } - /// Add option for vectors template Option *add_option(std::string option_name, std::vector &variable, ///< The variable vector to set - std::string option_description = "") { + std::string option_description = "", + bool defaulted = false) { - CLI::callback_t fun = [&variable](CLI::results_t res) { + auto fun = [&variable](CLI::results_t res) { bool retval = true; variable.clear(); variable.reserve(res.size()); @@ -536,35 +537,18 @@ class App { return (!variable.empty()) && retval; }; - Option *opt = add_option(option_name, fun, option_description, false); - opt->type_name(detail::type_name())->type_size(-1); - return opt; - } - - /// Add option for vectors with defaulted argument - template - Option *add_option(std::string option_name, - std::vector &variable, ///< The variable vector to set - std::string option_description, - bool defaulted) { - - CLI::callback_t fun = [&variable](CLI::results_t res) { - bool retval = true; - variable.clear(); - variable.reserve(res.size()); - for(const auto &elem : res) { - - variable.emplace_back(); - retval &= detail::lexical_cast(elem, variable.back()); - } - return (!variable.empty()) && retval; + auto default_function = [&variable]() { + std::vector defaults; + defaults.resize(variable.size()); + std::transform(variable.begin(), variable.end(), defaults.begin(), [](T &val) { + return std::string(CLI::detail::to_string(val)); + }); + return std::string("[" + detail::join(defaults) + "]"); }; - Option *opt = add_option(option_name, fun, option_description, defaulted); + Option *opt = add_option(option_name, fun, option_description, defaulted, default_function); opt->type_name(detail::type_name())->type_size(-1); - if(defaulted) - opt->default_str("[" + detail::join(variable) + "]"); return opt; } @@ -995,13 +979,16 @@ class App { return worked; }; - CLI::Option *opt = add_option(option_name, std::move(fun), std::move(option_description), defaulted); - opt->type_name(label)->type_size(2); - if(defaulted) { + auto default_function = [&variable]() { std::stringstream out; out << variable; - opt->default_str(out.str()); - } + return out.str(); + }; + + CLI::Option *opt = + add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function); + + opt->type_name(label)->type_size(2); return opt; } diff --git a/include/CLI/Config.hpp b/include/CLI/Config.hpp index a48c20e10..d3e0f2b9e 100644 --- a/include/CLI/Config.hpp +++ b/include/CLI/Config.hpp @@ -32,8 +32,8 @@ ConfigINI::to_config(const App *app, bool default_also, bool write_description, value = detail::ini_join(opt->results()); // If the option has a default and is requested by optional argument - else if(default_also && !opt->get_defaultval().empty()) - value = opt->get_defaultval(); + else if(default_also && !opt->get_default_str().empty()) + value = opt->get_default_str(); // Flag, one passed } else if(opt->count() == 1) { value = "true"; diff --git a/include/CLI/Formatter.hpp b/include/CLI/Formatter.hpp index 78acbc611..9f99515e6 100644 --- a/include/CLI/Formatter.hpp +++ b/include/CLI/Formatter.hpp @@ -232,8 +232,8 @@ inline std::string Formatter::make_option_opts(const Option *opt) const { if(opt->get_type_size() != 0) { if(!opt->get_type_name().empty()) out << " " << get_label(opt->get_type_name()); - if(!opt->get_defaultval().empty()) - out << "=" << opt->get_defaultval(); + if(!opt->get_default_str().empty()) + out << "=" << opt->get_default_str(); if(opt->get_expected() > 1) out << " x " << opt->get_expected(); if(opt->get_expected() == -1) diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 563ae32dd..4eab08e78 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -50,10 +50,16 @@ template class OptionBase { /// Allow this option to be given in a configuration file bool configurable_{true}; + /// Disable overriding flag values with '=value' bool disable_flag_override_{false}; + /// Specify a delimiter character for vector arguments char delimiter_{'\0'}; + + /// Automatically capture default value + bool always_capture_default_{false}; + /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; @@ -66,6 +72,7 @@ template class OptionBase { other->configurable(configurable_); other->disable_flag_override(disable_flag_override_); other->delimiter(delimiter_); + other->always_capture_default(always_capture_default_); other->multi_option_policy(multi_option_policy_); } @@ -87,6 +94,11 @@ template class OptionBase { /// Support Plumbum term CRTP *mandatory(bool value = true) { return required(value); } + CRTP *always_capture_default(bool value = true) { + always_capture_default_ = value; + return static_cast(this); + } + // Getters /// Get the group of this option @@ -107,7 +119,12 @@ template class OptionBase { /// The status of configurable bool get_disable_flag_override() const { return disable_flag_override_; } + /// Get the current delimeter char char get_delimiter() const { return delimiter_; } + + /// Return true if this will automatically capture the default value for help printing + bool get_always_capture_default() const { return always_capture_default_; } + /// The status of the multi option policy MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } @@ -219,16 +236,16 @@ class Option : public OptionBase