formatter: add long_option_alignment_ratio (#1185)

The default formatter has hardcoded ratio at which the long options are
aligned. It's currently 1/3 of the column, which makes the default look
awkward:
```
  -h,     --help              Print
          --option            Something
```
A ->long_option_alignment_ratio(6/30.f) allows output to look like this:
```
  -h, --help                  Print
      --option                Something
```
The 1/3 ratio is also bad if you want to print "descriptive" long
options on a single line, because then you might want to increase the
column width, but that means you waste more space on short options.

e.g. ->column_width(46)
```
  -l,          --very-descriptive-long-option  Something
```
vs. ->column_width(38)
```
  -l,       --very-descriptive-long-option
                                      Something
```
vs. ->column_width(38) ->long_option_alignment_ratio(6/38.f)
```
  -l, --very-descriptive-long-option  Something
```
Any absolute offset `X` can be set as `X/column_width`, so provide a
ratio-based interface.

I would have prefered to give an absolute integer offset, but we still
have to preserve the functionality that does 1/3 if user changed
nothing, which means that ratio-based interface is simpler.

I don't have a good idea for the name, "short_option_ratio" might work
as well.
The setter does not sanity check that the value is in [0;1] range.

---------

Signed-off-by: Radim Krčmář <radim@krcmar.dev>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Radim Krčmář
2025-08-27 14:17:20 +02:00
committed by GitHub
parent 6e9b07734c
commit e7e8de0346
3 changed files with 25 additions and 3 deletions

View File

@@ -47,6 +47,9 @@ class FormatterBase {
/// 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};
@@ -91,6 +94,10 @@ class FormatterBase {
/// 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; }
/// Set the right column width (description of options/flags/subcommands)
void right_column_width(std::size_t val) { right_column_width_ = val; }

View File

@@ -298,9 +298,10 @@ CLI11_INLINE std::string Formatter::make_option(const Option *opt, bool is_posit
std::string longNames = detail::join(vlongNames, ", ");
// Calculate setw sizes
const auto shortNamesColumnWidth = static_cast<int>(column_width_ / 3); // 33% left for short names
const auto longNamesColumnWidth = static_cast<int>(std::ceil(
static_cast<float>(column_width_) / 3.0f * 2.0f)); // 66% right for long names and options, ceil result
// Short names take enough width to align long names at the desired ratio
const auto shortNamesColumnWidth =
static_cast<int>(static_cast<float>(column_width_) * long_option_alignment_ratio_);
const auto longNamesColumnWidth = static_cast<int>(column_width_) - shortNamesColumnWidth;
int shortNamesOverSize = 0;
// Print short names

View File

@@ -261,3 +261,17 @@ TEST_CASE("Formatter: Description", "[formatter]") {
CHECK_THAT(help, !Contains("to Pr e s e r v e SPA C ES"));
CHECK_THAT(help, !Contains(desc_string));
}
TEST_CASE("Formatter: LongOptionAlignment", "[formatter]") {
CLI::App app{"My prog"};
app.get_formatter()->long_option_alignment_ratio(6 / 30.f);
int v{0};
app.add_option("--opt", v, "Something");
std::string help = app.help();
CHECK_THAT(help, Contains("\n -h, --help Print"));
CHECK_THAT(help, Contains("\n --opt INT Something"));
}