CLI11
C++11 Command Line Interface Parser
Loading...
Searching...
No Matches
Formatter_inl.hpp
1// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
2// under NSF AWARD 1414736 and by the respective contributors.
3// All rights reserved.
4//
5// SPDX-License-Identifier: BSD-3-Clause
6
7#pragma once
8
9// IWYU pragma: private, include "CLI/CLI.hpp"
10
11// This include is only needed for IDEs to discover symbols
12#include "../Formatter.hpp"
13
14// [CLI11:public_includes:set]
15#include <algorithm>
16#include <set>
17#include <string>
18#include <utility>
19#include <vector>
20// [CLI11:public_includes:end]
21
22namespace CLI {
23// [CLI11:formatter_inl_hpp:verbatim]
24namespace detail {
25CLI11_INLINE std::string indent_block(const std::string &input, const std::string &indent) {
26 std::stringstream out;
27 bool line_start = true;
28
29 for(char ch : input) {
30 if(line_start && ch != '\n') {
31 out << indent;
32 }
33 out << ch;
34 line_start = (ch == '\n');
35 }
36
37 return out.str();
38}
39} // namespace detail
40
41CLI11_INLINE std::string
42Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
43 std::stringstream out;
44
45 out << "\n" << group << ":\n";
46 for(const Option *opt : opts) {
47 out << make_option(opt, is_positional);
48 }
49
50 return out.str();
51}
52
53CLI11_INLINE std::string Formatter::make_positionals(const App *app) const {
54 std::vector<const Option *> opts =
55 app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
56
57 if(opts.empty())
58 return {};
59
60 return make_group(get_label("POSITIONALS"), true, opts);
61}
62
63CLI11_INLINE std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
64 std::stringstream out;
65 std::vector<std::string> groups = app->get_groups();
66
67 // Options
68 for(const std::string &group : groups) {
69 std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) {
70 return opt->get_group() == group // Must be in the right group
71 && opt->nonpositional() // Must not be a positional
72 && (mode != AppFormatMode::Sub // If mode is Sub, then
73 || (app->get_help_ptr() != opt // Ignore help pointer
74 && app->get_help_all_ptr() != opt)); // Ignore help all pointer
75 });
76 if(!group.empty() && !opts.empty()) {
77 out << make_group(group, false, opts);
78
79 // Removed double newline between groups for consistency of help text
80 // if(group != groups.back())
81 // out << "\n";
82 }
83 }
84
85 return out.str();
86}
87
88CLI11_INLINE std::string Formatter::make_description(const App *app) const {
89 std::string desc = app->get_description();
90 auto min_options = app->get_require_option_min();
91 auto max_options = app->get_require_option_max();
92
93 if(app->get_required()) {
94 desc += " " + get_label("REQUIRED") + " ";
95 }
96
97 if(min_options > 0) {
98 if(max_options == min_options) {
99 desc += " \n[Exactly " + std::to_string(min_options) + " of the following options are required]";
100 } else if(max_options > 0) {
101 desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
102 " of the following options are required]";
103 } else {
104 desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
105 }
106 } else if(max_options > 0) {
107 desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
108 }
109
110 return (!desc.empty()) ? desc + "\n\n" : std::string{};
111}
112
113CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const {
114 std::string usage = app->get_usage();
115 if(!usage.empty()) {
116 return usage + "\n\n";
117 }
118
119 std::stringstream out;
120 out << '\n';
121
122 if(name.empty())
123 out << get_label("Usage") << ':';
124 else
125 out << name;
126
127 std::vector<std::string> groups = app->get_groups();
128
129 // Print an Options badge if any options exist
130 std::vector<const Option *> non_pos_options =
131 app->get_options([](const Option *opt) { return opt->nonpositional(); });
132 if(!non_pos_options.empty())
133 out << " [" << get_label("OPTIONS") << "]";
134
135 // Positionals need to be listed here
136 std::vector<const Option *> positionals =
137 app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
138
139 // Print out positionals if any are left
140 if(!positionals.empty()) {
141 // Convert to help names
142 std::vector<std::string> positional_names;
143 positional_names.reserve(positionals.size());
144 for(const auto *opt : positionals) {
145 positional_names.push_back(make_option_usage(opt));
146 }
147
148 out << " " << detail::join(positional_names, " ");
149 }
150
151 // Add a marker if subcommands are expected or optional
152 if(!app->get_subcommands(
153 [](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
154 .empty()) {
155 out << ' ' << (app->get_require_subcommand_min() == 0 ? "[" : "")
156 << get_label(app->get_require_subcommand_max() == 1 ? "SUBCOMMAND" : "SUBCOMMANDS")
157 << (app->get_require_subcommand_min() == 0 ? "]" : "");
158 }
159
160 out << "\n\n";
161
162 return out.str();
163}
164
165CLI11_INLINE std::string Formatter::make_footer(const App *app) const {
166 std::string footer = app->get_footer();
167 if(footer.empty()) {
168 return std::string{};
169 }
170 return '\n' + footer + '\n';
171}
172
173CLI11_INLINE std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
174 // This immediately forwards to the make_expanded method. This is done this way so that subcommands can
175 // have overridden formatters
176 if(mode == AppFormatMode::Sub)
177 return make_expanded(app, mode);
178
179 std::stringstream out;
180 if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
181 if(app->get_group() != "SUBCOMMANDS") {
182 out << app->get_group() << ':';
183 }
184 }
186 detail::streamOutAsParagraph(
187 out, make_description(app), description_paragraph_width_, ""); // Format description as paragraph
188 } else {
189 out << make_description(app);
190 }
191 out << make_usage(app, name);
192 out << make_positionals(app);
193 out << make_groups(app, mode);
194 out << make_subcommands(app, mode);
195 std::string footer_string = make_footer(app);
196
198 detail::streamOutAsParagraph(out, footer_string, footer_paragraph_width_); // Format footer as paragraph
199 } else {
200 out << footer_string;
201 }
202
203 return out.str();
204}
205
206CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
207 std::stringstream out;
208
209 std::vector<const App *> subcommands = app->get_subcommands({});
210
211 // Make a list in definition order of the groups seen
212 std::vector<std::string> subcmd_groups_seen;
213 for(const App *com : subcommands) {
214 if(com->get_name().empty()) {
215 if(!com->get_group().empty() && com->get_group().front() != '+') {
216 auto expanded = make_expanded(com, mode);
217 // add expansion in one place so each group has subgroups beneath it
218 out << expanded;
219 }
220 continue;
221 }
222 std::string group_key = com->get_group();
223 std::string group_key_lower = detail::to_lower(group_key);
224 if(!group_key.empty() &&
225 std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key_lower](const std::string &a) {
226 return detail::to_lower(a) == group_key_lower;
227 }) == subcmd_groups_seen.end())
228 subcmd_groups_seen.push_back(group_key);
229 }
230
231 // For each group, filter out and print subcommands
232 for(const std::string &group : subcmd_groups_seen) {
233 out << '\n' << group << ":\n";
234 std::vector<const App *> subcommands_group = app->get_subcommands(
235 [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
236 for(const App *new_com : subcommands_group) {
237 if(new_com->get_name().empty())
238 continue;
239 if(mode != AppFormatMode::All) {
240 out << make_subcommand(new_com);
241 } else {
242 out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
243 out << '\n';
244 }
245 }
246 }
247
248 return out.str();
249}
250
251CLI11_INLINE std::string Formatter::make_subcommand(const App *sub) const {
252 std::stringstream out;
253 std::string name = " " + sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : "");
254
255 out << std::setw(static_cast<int>(column_width_)) << std::left << name;
256
257 const std::string desc = sub->get_description();
258 if(!desc.empty()) {
259 bool skipFirstLinePrefix = true;
260 if(name.length() >= column_width_) {
261 out << '\n';
262 skipFirstLinePrefix = false;
263 }
264 detail::streamOutAsParagraph(
265 out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
266 }
267 out << '\n';
268 return out.str();
269}
270
271CLI11_INLINE std::string Formatter::make_expanded(const App *sub, AppFormatMode mode) const {
272 std::stringstream out;
273 const bool is_option_group = sub->get_name().empty();
274 const std::string body_indent = is_option_group ? " " : "";
275
276 out << sub->get_display_name(true) << '\n';
277
279 detail::streamOutAsParagraph(
280 out, make_description(sub), description_paragraph_width_, body_indent); // Format description as paragraph
281 } else {
282 out << detail::indent_block(make_description(sub), body_indent) << '\n';
283 }
284
285 if(sub->get_name().empty() && !sub->get_aliases().empty()) {
286 detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
287 }
288
289 out << detail::indent_block(make_positionals(sub), body_indent);
290 out << detail::indent_block(make_groups(sub, mode), body_indent);
291 out << detail::indent_block(make_subcommands(sub, mode), body_indent);
292 std::string footer_string = make_footer(sub);
293
294 if(mode == AppFormatMode::Sub && !footer_string.empty()) {
295 const auto *parent = sub->get_parent();
296 std::string parent_footer = (parent != nullptr) ? make_footer(sub->get_parent()) : std::string{};
297 if(footer_string == parent_footer) {
298 footer_string = "";
299 }
300 }
301 if(!footer_string.empty()) {
303 detail::streamOutAsParagraph(out, footer_string, footer_paragraph_width_); // Format footer as paragraph
304 } else {
305 out << footer_string;
306 }
307 }
308 return out.str();
309}
310
311CLI11_INLINE std::string Formatter::make_option(const Option *opt, bool is_positional) const {
312 std::stringstream out;
313 if(is_positional) {
314 const std::string left = " " + make_option_name(opt, true) + make_option_opts(opt);
315 const std::string desc = make_option_desc(opt);
316 out << std::setw(static_cast<int>(column_width_)) << std::left << left;
317
318 if(!desc.empty()) {
319 bool skipFirstLinePrefix = true;
320 if(left.length() >= column_width_) {
321 out << '\n';
322 skipFirstLinePrefix = false;
323 }
324 detail::streamOutAsParagraph(
325 out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
326 }
327 } else {
328 const std::string namesCombined = make_option_name(opt, false);
329 const std::string opts = make_option_opts(opt);
330 const std::string desc = make_option_desc(opt);
331
332 // Split all names at comma and sort them into short names and long names
333 const auto names = detail::split(namesCombined, ',');
334 std::vector<std::string> vshortNames;
335 std::vector<std::string> vlongNames;
336 std::for_each(names.begin(), names.end(), [&vshortNames, &vlongNames](const std::string &name) {
337 if(name.find("--", 0) != std::string::npos)
338 vlongNames.push_back(name);
339 else
340 vshortNames.push_back(name);
341 });
342
343 // Assemble short and long names
344 std::string shortNames = detail::join(vshortNames, ", ");
345 std::string longNames = detail::join(vlongNames, ", ");
346
347 // Calculate setw sizes
348 // Short names take enough width to align long names at the desired ratio
349 const auto shortNamesColumnWidth =
350 static_cast<int>(static_cast<float>(column_width_) * long_option_alignment_ratio_);
351 const auto longNamesColumnWidth = static_cast<int>(column_width_) - shortNamesColumnWidth;
352 int shortNamesOverSize = 0;
353
354 // Print short names
355 if(!shortNames.empty()) {
356 shortNames = " " + shortNames; // Indent
357 if(longNames.empty() && !opts.empty())
358 shortNames += opts; // Add opts if only short names and no long names
359 if(!longNames.empty())
360 shortNames += ",";
361 if(static_cast<int>(shortNames.length()) >= shortNamesColumnWidth) {
362 shortNames += " ";
363 shortNamesOverSize = static_cast<int>(shortNames.length()) - shortNamesColumnWidth;
364 }
365 out << std::setw(shortNamesColumnWidth) << std::left << shortNames;
366 } else {
367 out << std::setw(shortNamesColumnWidth) << std::left << "";
368 }
369
370 // Adjust long name column width in case of short names column reaching into long names column
371 shortNamesOverSize =
372 (std::min)(shortNamesOverSize, longNamesColumnWidth); // Prevent negative result with unsigned integers
373 const auto adjustedLongNamesColumnWidth = longNamesColumnWidth - shortNamesOverSize;
374
375 // Print long names
376 if(!longNames.empty()) {
377 if(!opts.empty())
378 longNames += opts;
379 if(static_cast<int>(longNames.length()) >= adjustedLongNamesColumnWidth)
380 longNames += " ";
381
382 out << std::setw(adjustedLongNamesColumnWidth) << std::left << longNames;
383 } else {
384 out << std::setw(adjustedLongNamesColumnWidth) << std::left << "";
385 }
386
387 if(!desc.empty()) {
388 bool skipFirstLinePrefix = true;
389 if(out.str().length() > column_width_) {
390 out << '\n';
391 skipFirstLinePrefix = false;
392 }
393 detail::streamOutAsParagraph(
394 out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
395 }
396 }
397
398 out << '\n';
399 return out.str();
400}
401
402CLI11_INLINE std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
403 if(is_positional)
404 return opt->get_name(true, false);
405
406 return opt->get_name(false, true, !enable_default_flag_values_);
407}
408
409CLI11_INLINE std::string Formatter::make_option_opts(const Option *opt) const {
410 std::stringstream out;
411 // Help output should be stable across runs, so sort pointer-based sets by option name before printing.
412 const auto print_option_set = [&out](const std::set<Option *> &options) {
413 std::vector<const Option *> sorted(options.begin(), options.end());
414 std::sort(sorted.begin(), sorted.end(), [](const Option *lhs, const Option *rhs) {
415 return lhs->get_name() < rhs->get_name();
416 });
417 for(const Option *op : sorted)
418 out << " " << op->get_name();
419 };
420
421 if(!opt->get_option_text().empty()) {
422 out << " " << opt->get_option_text();
423 } else {
424 if(opt->get_type_size() != 0) {
425 if(enable_option_type_names_) {
426 if(!opt->get_type_name().empty())
427 out << " " << get_label(opt->get_type_name());
428 }
430 if(!opt->get_default_str().empty())
431 out << " [" << opt->get_default_str() << "] ";
432 }
433 if(opt->get_expected_max() == detail::expected_max_vector_size)
434 out << " ...";
435 else if(opt->get_expected_min() > 1)
436 out << " x " << opt->get_expected();
437
438 if(opt->get_required())
439 out << " " << get_label("REQUIRED");
440 }
441 if(!opt->get_envname().empty())
442 out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
443 if(!opt->get_needs().empty()) {
444 out << " " << get_label("Needs") << ":";
445 print_option_set(opt->get_needs());
446 }
447 if(!opt->get_excludes().empty()) {
448 out << " " << get_label("Excludes") << ":";
449 print_option_set(opt->get_excludes());
450 }
451 }
452 return out.str();
453}
454
455CLI11_INLINE std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
456
457CLI11_INLINE std::string Formatter::make_option_usage(const Option *opt) const {
458 // Note that these are positionals usages
459 std::stringstream out;
460 out << make_option_name(opt, true);
461 if(opt->get_expected_max() >= detail::expected_max_vector_size)
462 out << "...";
463 else if(opt->get_expected_max() > 1)
464 out << "(" << opt->get_expected() << "x)";
465
466 return opt->get_required() ? out.str() : "[" + out.str() + "]";
467}
468// [CLI11:formatter_inl_hpp:end]
469} // namespace CLI
Creates a command line program, with very few defaults.
Definition App.hpp:114
CLI11_NODISCARD std::string get_usage() const
Generate and return the usage.
Definition App.hpp:1178
CLI11_NODISCARD std::size_t get_require_subcommand_max() const
Get the required max subcommand value.
Definition App.hpp:1191
Option * get_help_ptr()
Get a pointer to the help flag.
Definition App.hpp:1245
CLI11_NODISCARD std::string get_footer() const
Generate and return the footer.
Definition App.hpp:1183
CLI11_NODISCARD const Option * get_help_all_ptr() const
Get a pointer to the help all flag. (const)
Definition App.hpp:1251
App * get_parent()
Get the parent of this subcommand (or nullptr if main app)
Definition App.hpp:1266
CLI11_NODISCARD std::string get_display_name(bool with_aliases=false) const
Get a display name for an app.
Definition App_inl.hpp:960
CLI11_NODISCARD std::size_t get_require_option_max() const
Get the required max option value.
Definition App.hpp:1197
CLI11_NODISCARD bool get_required() const
Get the status of required.
Definition App.hpp:1212
CLI11_NODISCARD std::vector< std::string > get_groups() const
Get the groups available directly from this option (in order)
Definition App_inl.hpp:1019
CLI11_NODISCARD const std::vector< std::string > & get_aliases() const
Get the aliases of the current app.
Definition App.hpp:1275
CLI11_NODISCARD std::size_t get_require_option_min() const
Get the required min option value.
Definition App.hpp:1194
CLI11_NODISCARD const std::string & get_group() const
Get the group of this subcommand.
Definition App.hpp:1175
CLI11_NODISCARD std::vector< App * > get_subcommands() const
Definition App.hpp:976
std::vector< const Option * > get_options(const std::function< bool(const Option *)> filter={}) const
Get the list of options (user facing function, so returns raw pointers), has optional filter function...
Definition App_inl.hpp:822
CLI11_NODISCARD std::string get_description() const
Get the app or subcommand description.
Definition App.hpp:1121
CLI11_NODISCARD std::size_t get_require_subcommand_min() const
Get the required min subcommand value.
Definition App.hpp:1188
CLI11_NODISCARD const std::string & get_name() const
Get the name of the current app.
Definition App.hpp:1272
std::size_t column_width_
The width of the left column (options/flags/subcommands)
Definition FormatterFwd.hpp:48
std::size_t footer_paragraph_width_
The width of the footer paragraph.
Definition FormatterFwd.hpp:60
bool enable_option_defaults_
options controlling formatting of options
Definition FormatterFwd.hpp:67
CLI11_NODISCARD std::string get_label(std::string key) const
Get the current value of a name (REQUIRED, etc.)
Definition FormatterFwd.hpp:135
CLI11_NODISCARD bool is_description_paragraph_formatting_enabled() const
Get the current status of description paragraph formatting.
Definition FormatterFwd.hpp:157
std::size_t right_column_width_
The width of the right column (description of options/flags/subcommands)
Definition FormatterFwd.hpp:54
float long_option_alignment_ratio_
The alignment ratio for long options within the left column.
Definition FormatterFwd.hpp:51
std::size_t description_paragraph_width_
The width of the description paragraph at the top of help.
Definition FormatterFwd.hpp:57
CLI11_NODISCARD bool is_footer_paragraph_formatting_enabled() const
Get the current status of whether footer paragraph formatting is enabled.
Definition FormatterFwd.hpp:160
std::string make_groups(const App *app, AppFormatMode mode) const
This prints out all the groups of options.
Definition Formatter_inl.hpp:63
virtual std::string make_usage(const App *app, std::string name) const
This displays the usage line.
Definition Formatter_inl.hpp:113
virtual std::string make_option_name(const Option *, bool) const
This is the name part of an option, Default: left column.
Definition Formatter_inl.hpp:402
virtual std::string make_expanded(const App *sub, AppFormatMode mode) const
This prints out a subcommand in help-all.
Definition Formatter_inl.hpp:271
std::string make_help(const App *app, std::string, AppFormatMode mode) const override
This puts everything together.
Definition Formatter_inl.hpp:173
virtual CLI11_NODISCARD std::string make_group(std::string group, bool is_positional, std::vector< const Option * > opts) const
Definition Formatter_inl.hpp:42
virtual std::string make_option(const Option *, bool) const
This prints out an option help line, either positional or optional form.
Definition Formatter_inl.hpp:311
virtual std::string make_footer(const App *app) const
This prints out all the groups of options.
Definition Formatter_inl.hpp:165
virtual std::string make_option_usage(const Option *opt) const
This is used to print the name on the USAGE line.
Definition Formatter_inl.hpp:457
virtual std::string make_subcommand(const App *sub) const
This prints out a subcommand.
Definition Formatter_inl.hpp:251
virtual std::string make_positionals(const App *app) const
This prints out just the positionals "group".
Definition Formatter_inl.hpp:53
virtual std::string make_subcommands(const App *app, AppFormatMode mode) const
This prints out all the subcommands.
Definition Formatter_inl.hpp:206
virtual std::string make_description(const App *app) const
This displays the description line.
Definition Formatter_inl.hpp:88
virtual std::string make_option_desc(const Option *) const
This is the description. Default: Right column, on new line if left column too large.
Definition Formatter_inl.hpp:455
virtual std::string make_option_opts(const Option *) const
This is the options part of the name, Default: combined into left column.
Definition Formatter_inl.hpp:409
CLI11_NODISCARD bool get_required() const
True if this is a required option.
Definition Option.hpp:137
CLI11_NODISCARD const std::string & get_group() const
Get the group of this option.
Definition Option.hpp:134
Definition Option.hpp:259
CLI11_NODISCARD bool get_positional() const
True if the argument can be given directly.
Definition Option.hpp:632
CLI11_NODISCARD std::string get_envname() const
The environment variable associated to this value.
Definition Option.hpp:577
CLI11_NODISCARD std::string get_type_name() const
Get the full typename for this option.
Definition Option_inl.hpp:549
CLI11_NODISCARD std::string get_name(bool positional=false, bool all_options=false, bool disable_default_flag_values=false) const
Gets a comma separated list of names. Will include / prefer the positional name if positional is true...
Definition Option_inl.hpp:269
CLI11_NODISCARD std::string get_default_str() const
The default value (for help printing)
Definition Option.hpp:586
CLI11_NODISCARD bool nonpositional() const
True if option has at least one non-positional name.
Definition Option.hpp:635
CLI11_NODISCARD std::set< Option * > get_excludes() const
The set of options excluded.
Definition Option.hpp:583
CLI11_NODISCARD std::set< Option * > get_needs() const
The set of options needed.
Definition Option.hpp:580
CLI11_NODISCARD const std::string & get_description() const
Get the description.
Definition Option.hpp:641
CLI11_NODISCARD int get_expected() const
The number of times the option expects to be included.
Definition Option.hpp:613
CLI11_NODISCARD int get_expected_min() const
The number of times the option expects to be included.
Definition Option.hpp:616
CLI11_NODISCARD int get_expected_max() const
The max number of times the option expects to be included.
Definition Option.hpp:618
CLI11_NODISCARD int get_type_size() const
The number of arguments the option expects.
Definition Option.hpp:566