diff --git a/.github/workflows/pr_merged.yml b/.github/workflows/pr_merged.yml index 8709fbab..76b1be3b 100644 --- a/.github/workflows/pr_merged.yml +++ b/.github/workflows/pr_merged.yml @@ -5,7 +5,7 @@ on: permissions: contents: write - pull-request: write + pull-requests: write jobs: label-merged: diff --git a/README.md b/README.md index 99b4c5b1..87f9d613 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ set with a simple and intuitive interface. - [Option options](#option-options) - [Validators](#validators) - [Default Validators](#default-validators) - - [Validators that may be disabled 🚧](#validators-that-may-be-disabled-) - - [Extra Validators 🚧](#extra-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) @@ -166,8 +166,8 @@ this library: 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). 🆕 The latest version of CLI11 does include partial - option matching for option prefixes. This is enabled by + 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 @@ -599,7 +599,7 @@ 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 included that perform some common checks. By -default the most commonly used ones are available. 🚧 If some are not needed +default the most commonly used ones are available. 🆕 If some are not needed they can be disabled by using ```c++ @@ -625,7 +625,7 @@ of flags. - `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 🆕 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 @@ -660,7 +660,7 @@ computation time that may not be valuable for some use cases. the input be convertible to an `unsigned int` regardless of the end conversion. -#### Extra Validators 🚧 +#### Extra Validators 🆕 New validators will go into code sections that must be explicitly enabled by setting `CLI11_ENABLE_EXTRA_VALIDATORS` to 1 @@ -842,7 +842,7 @@ 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. 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 +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 @@ -1076,9 +1076,14 @@ option_groups. These are: for processing the app for custom output formats). - `.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 @@ -1451,25 +1456,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 @@ -1997,3 +1995,5 @@ try! Feedback is always welcome. [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/book/chapters/formatting.md b/book/chapters/formatting.md index 66f0765b..abd03501 100644 --- a/book/chapters/formatting.md +++ b/book/chapters/formatting.md @@ -1,7 +1,5 @@ # Formatting help output -{% hint style='info' %} New in CLI11 1.6 {% endhint %} - ## Customizing an existing formatter In CLI11, you can control the output of the help printout in full or in part. @@ -11,10 +9,19 @@ will be inherited by subcommands that are created after you set the formatter. There are several configuration options that you can set: -| Set method | Description | Availability | -| --------------------- | -------------------------------- | ------------ | -| `column_width(width)` | The width of the columns | Both | -| `label(key, value)` | Set a label to a different value | Both | +| Set method | Description | Availability | +| ------------------------------------------ | -------------------------------------------------------------------- | ------------ | +| `column_width(width)` | The width of the columns (30) | Both | +| `label(key, value)` | Set a label to a different value | Both | +| `long_option_alignment_ratio(float)` | Set the alignment ratio for long options within the left column(1/3) | Both | +| `right_column_width(std::size_t)` | Set the right column width(65) | Both | +| `description_paragraph_width(std::size_t)` | Set the description paragraph width at the top of help(88) | Both | +| `footer_paragraph_width(std::size_t)` | Set the footer paragraph width (88) | Both | +| `enable_description_formatting(bool)` | enable/disable description paragraph formatting (true) | Both | +| `enable_footer_formatting(bool)` | enable/disable footer paragraph formatting (true) | Both | +| `enable_option_defaults(bool)` | enable/disable printing of option defaults (true) | Both | +| `enable_option_type_names(bool)` | enable/disable printing of option types (true) | Both | +| `enable_default_flag_values(bool)` | enable/disable printing of default flag values (true) | Both | Labels will map the built in names and type names from key to value if present. For example, if you wanted to change the width of the columns to 40 and the @@ -25,6 +32,56 @@ app.get_formatter()->column_width(40); app.get_formatter()->label("REQUIRED", "(MUST HAVE)"); ``` +Used labels are `REQUIRED`, `POSITIONALS`, `Usage`, `OPTIONS`, `SUBCOMMAND`, +`SUBCOMMANDS`, `Env`, `Needs`,`Excludes`, and any type name such as `TEXT`, +`INT`,`FLOAT` and others. Replacing these labels with new ones will use the +specified words in place of the label. + +### Customization Option Descriptions + +Some of the control parameters are visualized in Figure 1. They manage the +column widths and ratios of the different sections of the help + +![example help output](../images/help_output1.png) + +### long option alignment ratio + +The long option alignment ratio controls the relative proportion of short to +long option names. It must be a number between 0 and 1. values entered outside +this range are converted into the range by absolute value or inversion. It +defines where in the left column long optiosn are aligned. It is a ratio of the +column width property. + +### formatting options + +There are occasions where it is necessary to disable the formatting for headers +and footers the two options `enable_description_formatting(false)` and +`enable_footer_formatting(false)` turn off any formatting on the description and +footer. This allows things like word art or external management of alignment and +width. With formatting enabled the width is enforced and the paragraphs +reflowed. + +### Option output control + +Additional control options manage printing of specific aspects of an option + +```text +OPTIONS: + -h, --help Print this help message and exit + --opt TEXT [DEFFFF] + -o, --opt2 INT this is a description for opt2 + -f, -n, --opt3, --option-double FLOAT + this is a description for option3 + --flag, --no_flag{false} + a flag option with a negative flag as well +``` + +The `[DEFFFF]` portion, which is the default value for options if specified can +be turned off in the help output through `enable_option_defaults(false)`. The +`TEXT`, `INT`, `FLOAT` or other type names can be turned off via +`enable_option_type_names(false)`. and the `{false}` or flag default values can +be turned off using `enable_default_flag_values(false)`. + ## Subclassing You can further configure pieces of the code while still keeping most of the @@ -84,3 +141,24 @@ Notes: - `*1`: This signature depends on whether the call is from a positional or optional. - `o` is opt pointer, `p` is true if positional. + +## formatting callback + +For certain cases it is useful to use a callback for the help formatting + +```c++ +app.formatter_fn( + [](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); }); +``` + +This callback replaces the make_help call in the formatter with the callback. +This is a wrapper around a custom formatter that just needs the main call. All +configuration options are available but are ignored as the output is purely +driven by the callback. The first argument is a const pointer to the App 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. diff --git a/book/images/help_output1.png b/book/images/help_output1.png new file mode 100644 index 00000000..edaedb28 Binary files /dev/null and b/book/images/help_output1.png differ diff --git a/include/CLI/FormatterFwd.hpp b/include/CLI/FormatterFwd.hpp index 54d08c53..7245cc43 100644 --- a/include/CLI/FormatterFwd.hpp +++ b/include/CLI/FormatterFwd.hpp @@ -63,6 +63,10 @@ class FormatterBase { 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_{}; @@ -96,7 +100,10 @@ class FormatterBase { /// 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; } + 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; } @@ -110,6 +117,13 @@ class FormatterBase { 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 ///@{ @@ -133,12 +147,25 @@ class FormatterBase { /// 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_; } + ///@} }; diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 3522df20..e9b7714a 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -657,8 +657,10 @@ class Option : public OptionBase