CLI11
C++11 Command Line Interface Parser
Loading...
Searching...
No Matches
Formatter_inl.hpp
1// Copyright (c) 2017-2024, 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 <string>
17#include <utility>
18#include <vector>
19// [CLI11:public_includes:end]
20
21namespace CLI {
22// [CLI11:formatter_inl_hpp:verbatim]
23CLI11_INLINE std::string
24Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
25 std::stringstream out;
26
27 out << "\n" << group << ":\n";
28 for(const Option *opt : opts) {
29 out << make_option(opt, is_positional);
30 }
31
32 return out.str();
33}
34
35CLI11_INLINE std::string Formatter::make_positionals(const App *app) const {
36 std::vector<const Option *> opts =
37 app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
38
39 if(opts.empty())
40 return {};
41
42 return make_group(get_label("POSITIONALS"), true, opts);
43}
44
45CLI11_INLINE std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
46 std::stringstream out;
47 std::vector<std::string> groups = app->get_groups();
48
49 // Options
50 for(const std::string &group : groups) {
51 std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) {
52 return opt->get_group() == group // Must be in the right group
53 && opt->nonpositional() // Must not be a positional
54 && (mode != AppFormatMode::Sub // If mode is Sub, then
55 || (app->get_help_ptr() != opt // Ignore help pointer
56 && app->get_help_all_ptr() != opt)); // Ignore help all pointer
57 });
58 if(!group.empty() && !opts.empty()) {
59 out << make_group(group, false, opts);
60
61 // Removed double newline between groups for consistency of help text
62 // if(group != groups.back())
63 // out << "\n";
64 }
65 }
66
67 return out.str();
68}
69
70CLI11_INLINE std::string Formatter::make_description(const App *app) const {
71 std::string desc = app->get_description();
72 auto min_options = app->get_require_option_min();
73 auto max_options = app->get_require_option_max();
74
75 if(app->get_required()) {
76 desc += " " + get_label("REQUIRED") + " ";
77 }
78
79 if(min_options > 0) {
80 if(max_options == min_options) {
81 desc += " \n[Exactly " + std::to_string(min_options) + " of the following options are required]";
82 } else if(max_options > 0) {
83 desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
84 " of the following options are required]";
85 } else {
86 desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
87 }
88 } else if(max_options > 0) {
89 desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
90 }
91
92 return (!desc.empty()) ? desc + "\n" : std::string{};
93}
94
95CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const {
96 std::string usage = app->get_usage();
97 if(!usage.empty()) {
98 return usage + "\n\n";
99 }
100
101 std::stringstream out;
102 out << '\n';
103
104 if(name.empty())
105 out << get_label("Usage") << ':';
106 else
107 out << name;
108
109 std::vector<std::string> groups = app->get_groups();
110
111 // Print an Options badge if any options exist
112 std::vector<const Option *> non_pos_options =
113 app->get_options([](const Option *opt) { return opt->nonpositional(); });
114 if(!non_pos_options.empty())
115 out << " [" << get_label("OPTIONS") << "]";
116
117 // Positionals need to be listed here
118 std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
119
120 // Print out positionals if any are left
121 if(!positionals.empty()) {
122 // Convert to help names
123 std::vector<std::string> positional_names(positionals.size());
124 std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) {
125 return make_option_usage(opt);
126 });
127
128 out << " " << detail::join(positional_names, " ");
129 }
130
131 // Add a marker if subcommands are expected or optional
132 if(!app->get_subcommands(
133 [](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
134 .empty()) {
135 out << ' ' << (app->get_require_subcommand_min() == 0 ? "[" : "")
136 << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
137 : "SUBCOMMANDS")
138 << (app->get_require_subcommand_min() == 0 ? "]" : "");
139 }
140
141 out << "\n\n";
142
143 return out.str();
144}
145
146CLI11_INLINE std::string Formatter::make_footer(const App *app) const {
147 std::string footer = app->get_footer();
148 if(footer.empty()) {
149 return std::string{};
150 }
151 return '\n' + footer + "\n\n";
152}
153
154CLI11_INLINE std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
155 // This immediately forwards to the make_expanded method. This is done this way so that subcommands can
156 // have overridden formatters
157 if(mode == AppFormatMode::Sub)
158 return make_expanded(app, mode);
159
160 std::stringstream out;
161 if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
162 if(app->get_group() != "SUBCOMMANDS") {
163 out << app->get_group() << ':';
164 }
165 }
166
167 out << make_usage(app, name);
168 detail::streamOutAsParagraph(
169 out, make_description(app), description_paragraph_width_, " "); // Format description as paragraph
170 out << make_positionals(app);
171 out << make_groups(app, mode);
172 out << make_subcommands(app, mode);
173 detail::streamOutAsParagraph(out, make_footer(app), footer_paragraph_width_); // Format footer as paragraph
174
175 return out.str();
176}
177
178CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
179 std::stringstream out;
180
181 std::vector<const App *> subcommands = app->get_subcommands({});
182
183 // Make a list in definition order of the groups seen
184 std::vector<std::string> subcmd_groups_seen;
185 for(const App *com : subcommands) {
186 if(com->get_name().empty()) {
187 if(!com->get_group().empty() && com->get_group().front() != '+') {
188 out << make_expanded(com, mode);
189 }
190 continue;
191 }
192 std::string group_key = com->get_group();
193 if(!group_key.empty() &&
194 std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
195 return detail::to_lower(a) == detail::to_lower(group_key);
196 }) == subcmd_groups_seen.end())
197 subcmd_groups_seen.push_back(group_key);
198 }
199
200 // For each group, filter out and print subcommands
201 for(const std::string &group : subcmd_groups_seen) {
202 out << '\n' << group << ":\n";
203 std::vector<const App *> subcommands_group = app->get_subcommands(
204 [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
205 for(const App *new_com : subcommands_group) {
206 if(new_com->get_name().empty())
207 continue;
208 if(mode != AppFormatMode::All) {
209 out << make_subcommand(new_com);
210 } else {
211 out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
212 out << '\n';
213 }
214 }
215 }
216
217 return out.str();
218}
219
220CLI11_INLINE std::string Formatter::make_subcommand(const App *sub) const {
221 std::stringstream out;
222 std::string name = " " + sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : "");
223
224 out << std::setw(static_cast<int>(column_width_)) << std::left << name;
225 detail::streamOutAsParagraph(
226 out, sub->get_description(), right_column_width_, std::string(column_width_, ' '), true);
227 out << '\n';
228 return out.str();
229}
230
231CLI11_INLINE std::string Formatter::make_expanded(const App *sub, AppFormatMode mode) const {
232 std::stringstream out;
233 out << sub->get_display_name(true) << '\n';
234
235 detail::streamOutAsParagraph(
236 out, make_description(sub), description_paragraph_width_, " "); // Format description as paragraph
237
238 if(sub->get_name().empty() && !sub->get_aliases().empty()) {
239 detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
240 }
241
242 out << make_positionals(sub);
243 out << make_groups(sub, mode);
244 out << make_subcommands(sub, mode);
245 detail::streamOutAsParagraph(out, make_footer(sub), footer_paragraph_width_); // Format footer as paragraph
246
247 out << '\n';
248 return out.str();
249}
250
251CLI11_INLINE std::string Formatter::make_option(const Option *opt, bool is_positional) const {
252 std::stringstream out;
253 if(is_positional) {
254 const std::string left = " " + make_option_name(opt, true) + make_option_opts(opt);
255 const std::string desc = make_option_desc(opt);
256 out << std::setw(static_cast<int>(column_width_)) << std::left << left;
257
258 if(!desc.empty()) {
259 bool skipFirstLinePrefix = true;
260 if(left.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 } else {
268 const std::string namesCombined = make_option_name(opt, false);
269 const std::string opts = make_option_opts(opt);
270 const std::string desc = make_option_desc(opt);
271
272 // Split all names at comma and sort them into short names and long names
273 const auto names = detail::split(namesCombined, ',');
274 std::vector<std::string> vshortNames;
275 std::vector<std::string> vlongNames;
276 std::for_each(names.begin(), names.end(), [&vshortNames, &vlongNames](const std::string &name) {
277 if(name.find("--", 0) != std::string::npos)
278 vlongNames.push_back(name);
279 else
280 vshortNames.push_back(name);
281 });
282
283 // Assemble short and long names
284 std::string shortNames = detail::join(vshortNames, ", ");
285 std::string longNames = detail::join(vlongNames, ", ");
286
287 // Calculate setw sizes
288 const auto shortNamesColumnWidth = static_cast<int>(column_width_ / 3); // 33% left for short names
289 const auto longNamesColumnWidth = static_cast<int>(std::ceil(
290 static_cast<float>(column_width_) / 3.0f * 2.0f)); // 66% right for long names and options, ceil result
291 int shortNamesOverSize = 0;
292
293 // Print short names
294 if(shortNames.length() > 0) {
295 shortNames = " " + shortNames; // Indent
296 if(longNames.length() == 0 && opts.length() > 0)
297 shortNames += opts; // Add opts if only short names and no long names
298 if(longNames.length() > 0)
299 shortNames += ",";
300 if(static_cast<int>(shortNames.length()) >= shortNamesColumnWidth) {
301 shortNames += " ";
302 shortNamesOverSize = static_cast<int>(shortNames.length()) - shortNamesColumnWidth;
303 }
304 out << std::setw(shortNamesColumnWidth) << std::left << shortNames;
305 } else {
306 out << std::setw(shortNamesColumnWidth) << std::left << "";
307 }
308
309 // Adjust long name column width in case of short names column reaching into long names column
310 shortNamesOverSize =
311 (std::min)(shortNamesOverSize, longNamesColumnWidth); // Prevent negative result with unsigned integers
312 const auto adjustedLongNamesColumnWidth = longNamesColumnWidth - shortNamesOverSize;
313
314 // Print long names
315 if(longNames.length() > 0) {
316 if(opts.length() > 0)
317 longNames += opts;
318 if(static_cast<int>(longNames.length()) >= adjustedLongNamesColumnWidth)
319 longNames += " ";
320
321 out << std::setw(adjustedLongNamesColumnWidth) << std::left << longNames;
322 } else {
323 out << std::setw(adjustedLongNamesColumnWidth) << std::left << "";
324 }
325
326 if(!desc.empty()) {
327 bool skipFirstLinePrefix = true;
328 if(out.str().length() > column_width_) {
329 out << '\n';
330 skipFirstLinePrefix = false;
331 }
332 detail::streamOutAsParagraph(
333 out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
334 }
335 }
336
337 out << '\n';
338 return out.str();
339}
340
341CLI11_INLINE std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
342 if(is_positional)
343 return opt->get_name(true, false);
344
345 return opt->get_name(false, true);
346}
347
348CLI11_INLINE std::string Formatter::make_option_opts(const Option *opt) const {
349 std::stringstream out;
350
351 if(!opt->get_option_text().empty()) {
352 out << " " << opt->get_option_text();
353 } else {
354 if(opt->get_type_size() != 0) {
355 if(!opt->get_type_name().empty())
356 out << " " << get_label(opt->get_type_name());
357 if(!opt->get_default_str().empty())
358 out << " [" << opt->get_default_str() << "] ";
359 if(opt->get_expected_max() == detail::expected_max_vector_size)
360 out << " ...";
361 else if(opt->get_expected_min() > 1)
362 out << " x " << opt->get_expected();
363
364 if(opt->get_required())
365 out << " " << get_label("REQUIRED");
366 }
367 if(!opt->get_envname().empty())
368 out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
369 if(!opt->get_needs().empty()) {
370 out << " " << get_label("Needs") << ":";
371 for(const Option *op : opt->get_needs())
372 out << " " << op->get_name();
373 }
374 if(!opt->get_excludes().empty()) {
375 out << " " << get_label("Excludes") << ":";
376 for(const Option *op : opt->get_excludes())
377 out << " " << op->get_name();
378 }
379 }
380 return out.str();
381}
382
383CLI11_INLINE std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
384
385CLI11_INLINE std::string Formatter::make_option_usage(const Option *opt) const {
386 // Note that these are positionals usages
387 std::stringstream out;
388 out << make_option_name(opt, true);
389 if(opt->get_expected_max() >= detail::expected_max_vector_size)
390 out << "...";
391 else if(opt->get_expected_max() > 1)
392 out << "(" << opt->get_expected() << "x)";
393
394 return opt->get_required() ? out.str() : "[" + out.str() + "]";
395}
396// [CLI11:formatter_inl_hpp:end]
397} // namespace CLI
Creates a command line program, with very few defaults.
Definition App.hpp:90
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:57
CLI11_NODISCARD std::string get_label(std::string key) const
Get the current value of a name (REQUIRED, etc.)
Definition FormatterFwd.hpp:104
std::size_t right_column_width_
The width of the right column (description of options/flags/subcommands)
Definition FormatterFwd.hpp:51
std::size_t description_paragraph_width_
The width of the description paragraph at the top of help.
Definition FormatterFwd.hpp:54
std::string make_groups(const App *app, AppFormatMode mode) const
This prints out all the groups of options.
Definition Formatter_inl.hpp:45
virtual std::string make_usage(const App *app, std::string name) const
This displays the usage line.
Definition Formatter_inl.hpp:95
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:341
virtual std::string make_expanded(const App *sub, AppFormatMode mode) const
This prints out a subcommand in help-all.
Definition Formatter_inl.hpp:231
std::string make_help(const App *app, std::string, AppFormatMode mode) const override
This puts everything together.
Definition Formatter_inl.hpp:154
virtual CLI11_NODISCARD std::string make_group(std::string group, bool is_positional, std::vector< const Option * > opts) const
Definition Formatter_inl.hpp:24
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:251
virtual std::string make_footer(const App *app) const
This prints out all the groups of options.
Definition Formatter_inl.hpp:146
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:385
virtual std::string make_subcommand(const App *sub) const
This prints out a subcommand.
Definition Formatter_inl.hpp:220
virtual std::string make_positionals(const App *app) const
This prints out just the positionals "group".
Definition Formatter_inl.hpp:35
virtual std::string make_subcommands(const App *app, AppFormatMode mode) const
This prints out all the subcommands.
Definition Formatter_inl.hpp:178
virtual std::string make_description(const App *app) const
This displays the description line.
Definition Formatter_inl.hpp:70
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:383
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:348
Definition Option.hpp:231