Skip to content

Commit 3a69ed5

Browse files
option name formatting in help (#1247)
Add some controls to manipulate option string formatting, including disabling the default values, disabling default flag values, disabling type names. Fixes #857 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 3a1946e commit 3a69ed5

File tree

9 files changed

+241
-48
lines changed

9 files changed

+241
-48
lines changed

.github/workflows/pr_merged.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55

66
permissions:
77
contents: write
8-
pull-request: write
8+
pull-requests: write
99

1010
jobs:
1111
label-merged:

README.md

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ set with a simple and intuitive interface.
3838
- [Option options](#option-options)
3939
- [Validators](#validators)
4040
- [Default Validators](#default-validators)
41-
- [Validators that may be disabled 🚧](#validators-that-may-be-disabled-)
42-
- [Extra Validators 🚧](#extra-validators-)
41+
- [Validators that may be disabled 🆕](#validators-that-may-be-disabled-)
42+
- [Extra Validators 🆕](#extra-validators-)
4343
- [Validator Usage](#validator-usage)
4444
- [Transforming Validators](#transforming-validators)
4545
- [Validator operations](#validator-operations)
@@ -166,8 +166,8 @@ this library:
166166
incomplete arguments. It's better not to guess. Most third party command line
167167
parsers for python actually reimplement command line parsing rather than using
168168
argparse because of this perceived design flaw (recent versions do have an
169-
option to disable it). 🆕 The latest version of CLI11 does include partial
170-
option matching for option prefixes. This is enabled by
169+
option to disable it). Recent releases of CLI11 do include partial option
170+
matching for option prefixes 🆕. This is enabled by
171171
`.allow_subcommand_prefix_matching()`, along with an example that generates
172172
suggested close matches.
173173
- 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
599599
and are executed before any Validators added through `check`.
600600

601601
CLI11 has several Validators included that perform some common checks. By
602-
default the most commonly used ones are available. 🚧 If some are not needed
602+
default the most commonly used ones are available. 🆕 If some are not needed
603603
they can be disabled by using
604604

605605
```c++
@@ -625,7 +625,7 @@ of flags.
625625
- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
626626
- `CLI::Number`: Requires the input be a number.
627627
628-
#### Validators that may be disabled 🚧
628+
#### Validators that may be disabled 🆕
629629
630630
Validators that may be disabled by setting `CLI11_DISABLE_EXTRA_VALIDATORS` to 1
631631
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.
660660
the input be convertible to an `unsigned int` regardless of the end
661661
conversion.
662662
663-
#### Extra Validators 🚧
663+
#### Extra Validators 🆕
664664
665665
New validators will go into code sections that must be explicitly enabled by
666666
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
842842
can also set a custom description function, and operation function. One example
843843
of this is in the
844844
[custom validator example](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_validator.cpp).
845-
example. The `check` and `transform` operations can also take a shared_ptr 🚧 to
845+
example. The `check` and `transform` operations can also take a shared_ptr 🆕 to
846846
a validator if you wish to reuse the validator in multiple locations or it is
847847
mutating and the check is dependent on other operations or is variable. Note
848848
that in this case it is not recommended to use the same object for both check
@@ -1076,9 +1076,14 @@ option_groups. These are:
10761076
for processing the app for custom output formats).
10771077
- `.parse_order()`: Get the list of option pointers in the order they were
10781078
parsed (including duplicates).
1079-
- `.formatter(fmt)`: Set a formatter, with signature
1080-
`std::string(const App*, std::string, AppFormatMode)`. See Formatting for more
1081-
details.
1079+
- `.formatter(std::shared_ptr<formatterBase> fmt)`: Set a custom formatter for
1080+
help.
1081+
- `.formatter_fn(fmt)`, with signature
1082+
`std::string(const App*, std::string, AppFormatMode)`. See [formatting][] for
1083+
more details.
1084+
- `.config_formatter(std::shared_ptr<Config> fmt)`: set a custom config
1085+
formatter for generating config files, more details available at [Config
1086+
files][config]
10821087
- `.description(str)`: Set/change the description.
10831088
- `.get_description()`: Access the description.
10841089
- `.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.
14511456

14521457
### Formatting
14531458

1454-
The job of formatting help printouts is delegated to a formatter callable object
1455-
on Apps and Options. You are free to replace either formatter by calling
1456-
`formatter(fmt)` on an `App`, where fmt is any copyable callable with the
1457-
correct signature. CLI11 comes with a default App formatter functional,
1458-
`Formatter`. It is customizable; you can set `label(key, value)` to replace the
1459-
default labels like `REQUIRED`, and `column_width(n)` to set the width of the
1460-
columns before you add the functional to the app or option. You can also
1461-
override almost any stage of the formatting process in a subclass of either
1462-
formatter. If you want to make a new formatter from scratch, you can do that
1463-
too; you just need to implement the correct signature. The first argument is a
1464-
const pointer to the in question. The formatter will get a `std::string` usage
1465-
name as the second option, and a `AppFormatMode` mode for the final option. It
1466-
should return a `std::string`.
1467-
1468-
The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the
1469-
situation the help was called in. `Sub` is optional, but the default formatter
1470-
uses it to make sure expanded subcommands are called with their own formatter
1471-
since you can't access anything but the call operator once a formatter has been
1472-
set.
1459+
The job of formatting help printouts is delegated to a formatter object. You are
1460+
free to replace the formatter with a custom one by calling `formatter(fmt)` on
1461+
an `App`. CLI11 comes with a default App formatter, `Formatter`. You can
1462+
retrieve the formatter via `.get_formatter()` this will return a pointer to the
1463+
current `Formatter`. It is customizable; you can set `label(key, value)` to
1464+
replace the default labels like `REQUIRED`, and `OPTIONS`. You can also set
1465+
`column_width(n)` to set the width of the columns before you add the functional
1466+
to the app or option. Several other configuration options are also available in
1467+
the `Formatter`. You can also override almost any stage of the formatting
1468+
process in a subclass of either formatter. If you want to make a new formatter
1469+
from scratch, you can do that too; you just need to implement the correct
1470+
signature. see [formatting][] for more details.
14731471

14741472
### Subclassing
14751473

@@ -1997,3 +1995,5 @@ try! Feedback is always welcome.
19971995
[toml]: https://toml.io
19981996
[lyra]: https://github.com/bfgroup/Lyra
19991997
[installation]: https://cliutils.github.io/CLI11/book/chapters/installation.html
1998+
[formatting]: https://cliutils.github.io/CLI11/book/chapters/formatting.html
1999+
[config]: https://cliutils.github.io/CLI11/book/chapters/config.html

book/chapters/formatting.md

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# Formatting help output
22

3-
{% hint style='info' %} New in CLI11 1.6 {% endhint %}
4-
53
## Customizing an existing formatter
64

75
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.
119

1210
There are several configuration options that you can set:
1311

14-
| Set method | Description | Availability |
15-
| --------------------- | -------------------------------- | ------------ |
16-
| `column_width(width)` | The width of the columns | Both |
17-
| `label(key, value)` | Set a label to a different value | Both |
12+
| Set method | Description | Availability |
13+
| ------------------------------------------ | -------------------------------------------------------------------- | ------------ |
14+
| `column_width(width)` | The width of the columns (30) | Both |
15+
| `label(key, value)` | Set a label to a different value | Both |
16+
| `long_option_alignment_ratio(float)` | Set the alignment ratio for long options within the left column(1/3) | Both |
17+
| `right_column_width(std::size_t)` | Set the right column width(65) | Both |
18+
| `description_paragraph_width(std::size_t)` | Set the description paragraph width at the top of help(88) | Both |
19+
| `footer_paragraph_width(std::size_t)` | Set the footer paragraph width (88) | Both |
20+
| `enable_description_formatting(bool)` | enable/disable description paragraph formatting (true) | Both |
21+
| `enable_footer_formatting(bool)` | enable/disable footer paragraph formatting (true) | Both |
22+
| `enable_option_defaults(bool)` | enable/disable printing of option defaults (true) | Both |
23+
| `enable_option_type_names(bool)` | enable/disable printing of option types (true) | Both |
24+
| `enable_default_flag_values(bool)` | enable/disable printing of default flag values (true) | Both |
1825

1926
Labels will map the built in names and type names from key to value if present.
2027
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);
2532
app.get_formatter()->label("REQUIRED", "(MUST HAVE)");
2633
```
2734
35+
Used labels are `REQUIRED`, `POSITIONALS`, `Usage`, `OPTIONS`, `SUBCOMMAND`,
36+
`SUBCOMMANDS`, `Env`, `Needs`,`Excludes`, and any type name such as `TEXT`,
37+
`INT`,`FLOAT` and others. Replacing these labels with new ones will use the
38+
specified words in place of the label.
39+
40+
### Customization Option Descriptions
41+
42+
Some of the control parameters are visualized in Figure 1. They manage the
43+
column widths and ratios of the different sections of the help
44+
45+
![example help output](../images/help_output1.png)
46+
47+
### long option alignment ratio
48+
49+
The long option alignment ratio controls the relative proportion of short to
50+
long option names. It must be a number between 0 and 1. values entered outside
51+
this range are converted into the range by absolute value or inversion. It
52+
defines where in the left column long optiosn are aligned. It is a ratio of the
53+
column width property.
54+
55+
### formatting options
56+
57+
There are occasions where it is necessary to disable the formatting for headers
58+
and footers the two options `enable_description_formatting(false)` and
59+
`enable_footer_formatting(false)` turn off any formatting on the description and
60+
footer. This allows things like word art or external management of alignment and
61+
width. With formatting enabled the width is enforced and the paragraphs
62+
reflowed.
63+
64+
### Option output control
65+
66+
Additional control options manage printing of specific aspects of an option
67+
68+
```text
69+
OPTIONS:
70+
-h, --help Print this help message and exit
71+
--opt TEXT [DEFFFF]
72+
-o, --opt2 INT this is a description for opt2
73+
-f, -n, --opt3, --option-double FLOAT
74+
this is a description for option3
75+
--flag, --no_flag{false}
76+
a flag option with a negative flag as well
77+
```
78+
79+
The `[DEFFFF]` portion, which is the default value for options if specified can
80+
be turned off in the help output through `enable_option_defaults(false)`. The
81+
`TEXT`, `INT`, `FLOAT` or other type names can be turned off via
82+
`enable_option_type_names(false)`. and the `{false}` or flag default values can
83+
be turned off using `enable_default_flag_values(false)`.
84+
2885
## Subclassing
2986

3087
You can further configure pieces of the code while still keeping most of the
@@ -84,3 +141,24 @@ Notes:
84141
- `*1`: This signature depends on whether the call is from a positional or
85142
optional.
86143
- `o` is opt pointer, `p` is true if positional.
144+
145+
## formatting callback
146+
147+
For certain cases it is useful to use a callback for the help formatting
148+
149+
```c++
150+
app.formatter_fn(
151+
[](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); });
152+
```
153+
154+
This callback replaces the make_help call in the formatter with the callback.
155+
This is a wrapper around a custom formatter that just needs the main call. All
156+
configuration options are available but are ignored as the output is purely
157+
driven by the callback. The first argument is a const pointer to the App in
158+
question. The formatter will get a std::string usage name as the second option,
159+
and a AppFormatMode mode for the final option. It should return a std::string.
160+
The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the
161+
situation the help was called in. `Sub` is optional, but the default formatter
162+
uses it to make sure expanded subcommands are called with their own formatter
163+
since you can't access anything but the call operator once a formatter has been
164+
set.

book/images/help_output1.png

982 KB
Loading

include/CLI/FormatterFwd.hpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ class FormatterBase {
6363
bool enable_description_formatting_{true};
6464
bool enable_footer_formatting_{true};
6565

66+
/// options controlling formatting of options
67+
bool enable_option_defaults_{true};
68+
bool enable_option_type_names_{true};
69+
bool enable_default_flag_values_{true};
6670
/// @brief The required help printout labels (user changeable)
6771
/// Values are Needs, Excludes, etc.
6872
std::map<std::string, std::string> labels_{};
@@ -96,7 +100,10 @@ class FormatterBase {
96100

97101
/// Set the alignment ratio for long options within the left column
98102
/// The ratio is in [0;1] range (e.g. 0.2 = 20% of column width, 6.f/column_width = 6th character)
99-
void long_option_alignment_ratio(float ratio) { long_option_alignment_ratio_ = ratio; }
103+
void long_option_alignment_ratio(float ratio) {
104+
long_option_alignment_ratio_ =
105+
(ratio >= 0.0f) ? ((ratio <= 1.0f) ? ratio : 1.0f / ratio) : ((ratio < -1.0f) ? 1.0f / (-ratio) : -ratio);
106+
}
100107

101108
/// Set the right column width (description of options/flags/subcommands)
102109
void right_column_width(std::size_t val) { right_column_width_ = val; }
@@ -110,6 +117,13 @@ class FormatterBase {
110117
void enable_description_formatting(bool value = true) { enable_description_formatting_ = value; }
111118
/// disable formatting for footer paragraph
112119
void enable_footer_formatting(bool value = true) { enable_footer_formatting_ = value; }
120+
121+
/// enable option defaults to be printed
122+
void enable_option_defaults(bool value = true) { enable_option_defaults_ = value; }
123+
/// enable option type names to be printed
124+
void enable_option_type_names(bool value = true) { enable_option_type_names_ = value; }
125+
/// enable default flag values to be printed
126+
void enable_default_flag_values(bool value = true) { enable_default_flag_values_ = value; }
113127
///@}
114128
/// @name Getters
115129
///@{
@@ -133,12 +147,25 @@ class FormatterBase {
133147
/// Get the current footer paragraph width
134148
CLI11_NODISCARD std::size_t get_footer_paragraph_width() const { return footer_paragraph_width_; }
135149

150+
/// @brief Get the current alignment ratio for long options within the left column
151+
/// @return
152+
CLI11_NODISCARD float get_long_option_alignment_ratio() const { return long_option_alignment_ratio_; }
153+
136154
/// Get the current status of description paragraph formatting
137155
CLI11_NODISCARD bool is_description_paragraph_formatting_enabled() const { return enable_description_formatting_; }
138156

139157
/// Get the current status of whether footer paragraph formatting is enabled
140158
CLI11_NODISCARD bool is_footer_paragraph_formatting_enabled() const { return enable_footer_formatting_; }
141159

160+
/// Get the current status of whether option defaults are printed
161+
CLI11_NODISCARD bool is_option_defaults_enabled() const { return enable_option_defaults_; }
162+
163+
/// Get the current status of whether option type names are printed
164+
CLI11_NODISCARD bool is_option_type_names_enabled() const { return enable_option_type_names_; }
165+
166+
/// Get the current status of whether default flag values are printed
167+
CLI11_NODISCARD bool is_default_flag_values_enabled() const { return enable_default_flag_values_; }
168+
142169
///@}
143170
};
144171

include/CLI/Option.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,8 +657,10 @@ class Option : public OptionBase<Option> {
657657
/// Will include / prefer the positional name if positional is true.
658658
/// If all_options is false, pick just the most descriptive name to show.
659659
/// Use `get_name(true)` to get the positional name (replaces `get_pname`)
660-
CLI11_NODISCARD std::string get_name(bool positional = false, ///< Show the positional name
661-
bool all_options = false ///< Show every option
660+
/// if disable_default_flag_values is true, do not include the default values for flags such as `--no-flag{false}`
661+
CLI11_NODISCARD std::string get_name(bool positional = false, ///< Show the positional name
662+
bool all_options = false, ///< Show every option
663+
bool disable_default_flag_values = false ///< Disable default values in name
662664
) const;
663665

664666
///@}

include/CLI/impl/Formatter_inl.hpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ CLI11_INLINE std::string Formatter::make_option_name(const Option *opt, bool is_
368368
if(is_positional)
369369
return opt->get_name(true, false);
370370

371-
return opt->get_name(false, true);
371+
return opt->get_name(false, true, !enable_default_flag_values_);
372372
}
373373

374374
CLI11_INLINE std::string Formatter::make_option_opts(const Option *opt) const {
@@ -378,10 +378,14 @@ CLI11_INLINE std::string Formatter::make_option_opts(const Option *opt) const {
378378
out << " " << opt->get_option_text();
379379
} else {
380380
if(opt->get_type_size() != 0) {
381-
if(!opt->get_type_name().empty())
382-
out << " " << get_label(opt->get_type_name());
383-
if(!opt->get_default_str().empty())
384-
out << " [" << opt->get_default_str() << "] ";
381+
if(enable_option_type_names_) {
382+
if(!opt->get_type_name().empty())
383+
out << " " << get_label(opt->get_type_name());
384+
}
385+
if(enable_option_defaults_) {
386+
if(!opt->get_default_str().empty())
387+
out << " [" << opt->get_default_str() << "] ";
388+
}
385389
if(opt->get_expected_max() == detail::expected_max_vector_size)
386390
out << " ...";
387391
else if(opt->get_expected_min() > 1)

include/CLI/impl/Option_inl.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ CLI11_INLINE Option *Option::multi_option_policy(MultiOptionPolicy value) {
254254
return this;
255255
}
256256

257-
CLI11_NODISCARD CLI11_INLINE std::string Option::get_name(bool positional, bool all_options) const {
257+
CLI11_NODISCARD CLI11_INLINE std::string
258+
Option::get_name(bool positional, bool all_options, bool disable_default_flag_values) const {
258259
if(get_group().empty())
259260
return {}; // Hidden
260261

@@ -269,14 +270,14 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_name(bool positional, bool
269270
if((get_items_expected() == 0) && (!fnames_.empty())) {
270271
for(const std::string &sname : snames_) {
271272
name_list.push_back("-" + sname);
272-
if(check_fname(sname)) {
273+
if(!disable_default_flag_values && check_fname(sname)) {
273274
name_list.back() += "{" + get_flag_value(sname, "") + "}";
274275
}
275276
}
276277

277278
for(const std::string &lname : lnames_) {
278279
name_list.push_back("--" + lname);
279-
if(check_fname(lname)) {
280+
if(!disable_default_flag_values && check_fname(lname)) {
280281
name_list.back() += "{" + get_flag_value(lname, "") + "}";
281282
}
282283
}

0 commit comments

Comments
 (0)