SNode.C
Loading...
Searching...
No Matches
Formatter.cpp
Go to the documentation of this file.
1/*
2 * SNode.C - A Slim Toolkit for Network Communication
3 * Copyright (C) Volker Christian <me@vchrist.at>
4 * 2020, 2021, 2022, 2023, 2024, 2025, 2026
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * MIT License
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * THE SOFTWARE.
40 */
41
42#include "Formatter.h"
43
44#ifndef DOXYGEN_SHOULD_SKIP_THIS
45
46#include "log/Logger.h"
47
48#include <algorithm>
49#include <functional>
50#include <iomanip>
51#include <set>
52#include <sstream>
53#include <vector>
54
55#endif // DOXYGEN_SHOULD_SKIP_THIS
56
57namespace CLI {
58
60 arrayDelimiter(' ');
61 }
62
65
66 CLI11_INLINE std::string
67 ConfigFormatter::to_config(const App* app, bool default_also, bool write_description, std::string prefix) const {
68 std::stringstream out;
69 std::string commentLead;
70 commentLead.push_back(commentChar);
71 commentLead.push_back(' ');
72
73 std::vector<std::string> groups = app->get_groups();
74 bool defaultUsed = false;
75 groups.insert(groups.begin(), std::string("Options"));
76 for (const auto& group : groups) {
77 if (group == "Options" || group.empty()) {
78 if (defaultUsed) {
79 continue;
80 }
81 defaultUsed = true;
82 }
83 if (write_description && group != "Options" && !group.empty() &&
84 !app->get_options([group](const Option* opt) -> bool {
85 return opt->get_group() == group && opt->get_configurable();
86 })
87 .empty()) {
88 out << '\n' << commentChar << commentLead << group << "\n";
89 }
90 for (const Option* opt : app->get_options({})) {
91 // Only process options that are configurable
92 if (opt->get_configurable()) {
93 if (opt->get_group() != group) {
94 if (!(group == "Options" && opt->get_group().empty())) {
95 continue;
96 }
97 }
98 const std::string name = prefix + opt->get_single_name();
99 std::string value;
100 try {
101 value = detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
102 } catch (CLI::ParseError& e) {
103 value = std::string{"<["} + Color::Code::FG_RED + e.get_name() + Color::Code::FG_DEFAULT + "] " + e.what() + ">";
104 }
105
106 std::string defaultValue{};
107 if (default_also) {
108 static_assert(std::string::npos + static_cast<std::string::size_type>(1) == 0,
109 "std::string::npos + static_cast<std::string::size_type>(1) != 0");
110 if (!value.empty() &&
111 detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, literalQuote, true) == "\"" + value + "\"") {
112 value.clear();
113 }
114 if (!opt->get_default_str().empty()) {
115 defaultValue = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, literalQuote, true);
116 if (defaultValue == "'\"\"'") {
117 defaultValue = "";
118 }
119 } else if (opt->get_run_callback_for_default()) {
120 defaultValue = "\"\""; // empty string default value
121 } else if (opt->get_required()) {
122 defaultValue = "\"<REQUIRED>\"";
123 } else if (opt->get_expected_min() == 0) {
124 defaultValue = "false";
125 } else {
126 defaultValue = "\"\"";
127 }
128 }
129 if (write_description && opt->has_description() && (default_also || !value.empty())) {
130 out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
131 }
132 if (default_also && !defaultValue.empty()) {
133 if (defaultValue == value) {
134 value.clear();
135 }
136 out << commentChar << name << valueDelimiter << defaultValue << "\n";
137 }
138 if (!value.empty()) {
139 if (!opt->get_fnames().empty()) {
140 value = opt->get_flag_value(name, value);
141 }
142 if (value == "\"default\"") {
143 value = "default";
144 }
145 out << name << valueDelimiter << value << "\n\n";
146 } else {
147 out << '\n';
148 }
149 }
150 }
151 }
152 auto subcommands = app->get_subcommands({});
153 for (const App* subcom : subcommands) {
154 if (subcom->get_name().empty()) {
155 if (write_description && !subcom->get_group().empty()) {
156 out << '\n' << commentLead << subcom->get_group() << " Options\n";
157 }
158 out << to_config(subcom, default_also, write_description, prefix);
159 }
160 }
161
162 for (const App* subcom : subcommands) {
163 if (!subcom->get_name().empty()) {
164 if (subcom->get_configurable() && app->got_subcommand(subcom)) {
165 if (!prefix.empty() || app->get_parent() == nullptr) {
166 out << '[' << prefix << subcom->get_name() << "]\n";
167 } else {
168 std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name();
169 const auto* p = app->get_parent();
170 while (p->get_parent() != nullptr) {
171 subname = p->get_name() + parentSeparatorChar + subname;
172 p = p->get_parent();
173 }
174 out << '[' << subname << "]\n";
175 }
176 out << to_config(subcom, default_also, write_description, "");
177 } else {
178 out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar);
179 }
180 }
181 }
182
183 std::string outString;
184 if (write_description && !out.str().empty()) {
185 outString =
186 commentChar + std::string("#") + commentLead + detail::fix_newlines(commentChar + commentLead, app->get_description());
187 }
188
189 return outString + out.str();
190 }
191
192#ifndef CLI11_ORIGINAL_FORMATTER
195
196 CLI11_INLINE std::string HelpFormatter::make_group(std::string group, bool is_positional, std::vector<const Option*> opts) const {
197 std::stringstream out;
198
199 // ############## Next line changed
200 out << "\n" << group << ":\n";
201 for (const Option* opt : opts) {
202 out << make_option(opt, is_positional);
203 }
204
205 return out.str();
206 }
207
208 CLI11_INLINE std::string HelpFormatter::make_description(const App* app) const {
209 std::string desc = app->get_description();
210 auto min_options = app->get_require_option_min();
211 auto max_options = app->get_require_option_max();
212 // ############## Next line changed (if statement removed)
213 if ((max_options == min_options) && (min_options > 0)) {
214 if (min_options == 1) {
215 desc += " \n[Exactly 1 of the following options is required]";
216 } else {
217 desc += " \n[Exactly " + std::to_string(min_options) + " options from the following list are required]";
218 }
219 } else if (max_options > 0) {
220 if (min_options > 0) {
221 desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
222 " of the follow options are required]";
223 } else {
224 desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
225 }
226 } else if (min_options > 0) {
227 desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
228 }
229 return (!desc.empty()) ? desc + "\n" : std::string{};
230 }
231
232 CLI11_INLINE std::string HelpFormatter::make_usage(const App* app, std::string name) const {
233 const std::string usage = app->get_usage();
234 if (!usage.empty()) {
235 return usage + "\n";
236 }
237
238 std::stringstream out;
239
240 out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
241
242 // Print an Options badge if any options exist
243 const std::vector<const Option*> non_pos_options = app->get_options([](const Option* opt) {
244 return opt->nonpositional();
245 });
246 if (!non_pos_options.empty()) {
247 out << " [" << get_label("OPTIONS") << "]";
248 }
249
250 // Positionals need to be listed here
251 std::vector<const Option*> positionals = app->get_options([](const Option* opt) {
252 return opt->get_positional();
253 });
254
255 // Print out positionals if any are left
256 if (!positionals.empty()) {
257 // Convert to help names
258 std::vector<std::string> positional_names(positionals.size());
259 std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option* opt) {
260 return make_option_usage(opt);
261 });
262
263 out << " " << detail::join(positional_names, " ");
264 }
265
266 // Add a marker if subcommands are expected or optional
267 if (!app->get_subcommands([](const App* subc) {
268 return !subc->get_disabled() && !subc->get_name().empty();
269 })
270 .empty()) {
271 // ############## Next line changed
272 out << " ["
273 << get_label(app->get_subcommands([](const CLI::App* subc) {
274 return ((!subc->get_disabled()) && (!subc->get_name().empty()) /*&& subc->get_required()*/);
275 }).size() <= 1
276 ? "SUBCOMMAND"
277 : "SUBCOMMANDS")
278 << " [--help]"
279 << "]";
280 }
281
282 const Option* disabledOpt = app->get_option_no_throw("--disabled");
283 if (disabledOpt != nullptr ? disabledOpt->as<bool>() : false) {
284 out << " " << get_label("DISABLED");
285 } else if (app->get_required()) {
286 out << " " << get_label("REQUIRED");
287 }
288
289 out << std::endl;
290
291 return out.str();
292 }
293
294 CLI11_INLINE std::string HelpFormatter::make_footer(const App* app) const {
295 const std::string footer = app->get_footer();
296 if (footer.empty()) {
297 return std::string{"\n"};
298 }
299 return '\n' + footer + "\n\n";
300 }
301
302 CLI11_INLINE std::string HelpFormatter::make_subcommands(const App* app, AppFormatMode mode) const {
303 std::stringstream out;
304
305 const std::vector<const App*> subcommands = app->get_subcommands([](const App* subc) {
306 if (subc->get_group() == "Instances") {
307 if (subc->get_option("--disabled")->as<bool>()) {
308 const_cast<CLI::App*>(subc)->group(subc->get_group() + " (disabled)");
309 }
310 }
311 return !subc->get_disabled() && !subc->get_name().empty();
312 });
313
314 // Make a list in alphabetic order of the groups seen
315 std::set<std::string> subcmd_groups_seen;
316 for (const App* com : subcommands) {
317 if (com->get_name().empty()) {
318 if (!com->get_group().empty()) {
319 out << make_expanded(com, mode);
320 }
321 continue;
322 }
323 std::string group_key = com->get_group();
324 if (!group_key.empty() &&
325 std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](const std::string& a) {
326 return detail::to_lower(a) == detail::to_lower(group_key);
327 }) == subcmd_groups_seen.end()) {
328 subcmd_groups_seen.insert(group_key);
329 }
330 }
331
332 // For each group, filter out and print subcommands
333 const Option* disabledOpt = app->get_option_no_throw("--disabled");
334 bool disabled = false;
335 if (disabledOpt != nullptr) {
336 disabled = disabledOpt->as<bool>();
337 }
338
339 if (!disabled) {
340 for (const std::string& group : subcmd_groups_seen) {
341 out << "\n" << group << ":\n";
342 const std::vector<const App*> subcommands_group = app->get_subcommands([&group](const App* sub_app) {
343 return detail::to_lower(sub_app->get_group()) == detail::to_lower(group) && !sub_app->get_disabled() &&
344 !sub_app->get_name().empty();
345 });
346 for (const App* new_com : subcommands_group) {
347 if (new_com->get_name().empty()) {
348 continue;
349 }
350 if (mode != AppFormatMode::All) {
351 out << make_subcommand(new_com);
352 } else {
353 out << new_com->help(disabledOpt != nullptr && (app->get_help_ptr()->as<std::string>() == "exact" ||
354 app->get_help_ptr()->as<std::string>() == "expanded")
355 ? new_com
356 : nullptr,
357 new_com->get_name(),
358 AppFormatMode::Sub);
359 out << "\n";
360 }
361 }
362 }
363 }
364
365 // ########## Next line(s) changed
366
367 std::string tmp = out.str();
368 if (mode == AppFormatMode::All && !tmp.empty()) {
369 tmp.pop_back();
370 }
371
372 out.str(tmp);
373 out.clear();
374
375 return out.str();
376 }
377
378 CLI11_INLINE std::string HelpFormatter::make_subcommand(const App* sub) const {
379 std::stringstream out;
380 const std::string name = " " + sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : "");
381
382 out << std::setw(static_cast<int>(column_width_)) << std::left << name;
383 const std::string desc = sub->get_description();
384 if (!desc.empty()) {
385 bool skipFirstLinePrefix = true;
386 if (out.str().length() >= column_width_) {
387 out << '\n';
388 skipFirstLinePrefix = false;
389 }
390 detail::streamOutAsParagraph(out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
391 }
392
393 out << '\n';
394
395 return out.str();
396 }
397
398 CLI11_INLINE std::string HelpFormatter::make_expanded(const App* sub, AppFormatMode mode) const {
399 std::stringstream out;
400 // ########## Next lines changed
401 const Option* disabledOpt = sub->get_option_no_throw("--disabled");
402 out << sub->get_display_name(true) + " [OPTIONS]" + (!sub->get_subcommands({}).empty() ? " [SECTIONS]" : "") +
403 ((disabledOpt != nullptr ? disabledOpt->as<bool>() : false) ? " " + get_label("DISABLED")
404 : (sub->get_required() ? " " + get_label("REQUIRED") : ""))
405 << "\n";
406
407 detail::streamOutAsParagraph(out, make_description(sub), description_paragraph_width_, ""); // Format description as paragraph
408
409 if (sub->get_name().empty() && !sub->get_aliases().empty()) {
410 detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
411 }
412 out << make_positionals(sub);
413 out << make_groups(sub, mode);
414 out << make_subcommands(sub, mode);
415
416 // Drop blank spaces
417 std::string tmp = out.str();
418 tmp.pop_back();
419
420 // Indent all but the first line (the name)
421 return detail::find_and_replace(tmp, "\n", "\n ") + "\n";
422 }
423
424 CLI11_INLINE std::string HelpFormatter::make_option_opts(const Option* opt) const {
425 std::stringstream out;
426
427 if (!opt->get_option_text().empty()) {
428 out << " " << opt->get_option_text();
429 } else {
430 if (opt->get_type_size() != 0) {
431 if (!opt->get_type_name().empty()) {
432 // ########## Next line changed
433 out << ((opt->get_items_expected_max() == 0) ? "=" : " ") << get_label(opt->get_type_name());
434 }
435 if (!opt->get_default_str().empty()) {
436 out << " [" << opt->get_default_str() << "]";
437 }
438 try {
439 if (opt->count() > 0 && opt->get_default_str() != opt->as<std::string>()) {
440 out << " {";
441 std::string completeResult;
442 for (const auto& result : opt->reduced_results()) {
443 completeResult += (!result.empty() ? result : "\"\"") + " ";
444 }
445 completeResult.pop_back();
446 out << completeResult << "}";
447 }
448 } catch (CLI::ParseError& e) {
449 out << " <[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << ">";
450 }
451 if (opt->get_expected_max() == detail::expected_max_vector_size) {
452 out << " ... ";
453 } else if (opt->get_expected_min() > 1) {
454 out << " x " << opt->get_expected();
455 }
456 if (opt->get_required() && !get_label("REQUIRED").empty()) {
457 out << " " << get_label("REQUIRED");
458 }
459 if (opt->get_configurable() && !get_label("PERSISTENT").empty()) {
460 out << " " << get_label("PERSISTENT");
461 }
462 }
463 if (!opt->get_envname().empty()) {
464 out << " (" << get_label("Env") << ":" << opt->get_envname() << ") ";
465 }
466 if (!opt->get_needs().empty()) {
467 out << " " << get_label("Needs") << ":";
468 for (const Option* op : opt->get_needs()) {
469 out << " " << op->get_name();
470 }
471 }
472 if (!opt->get_excludes().empty()) {
473 out << " " << get_label("Excludes") << ":";
474 for (const Option* op : opt->get_excludes()) {
475 out << " " << op->get_name();
476 }
477 }
478 }
479 return out.str();
480 }
481
482#endif // CLI11_ORIGINAL_FORMATTER
483
484} // namespace CLI
~ConfigFormatter() override
Definition Formatter.cpp:63
std::string to_config(const App *app, bool default_also, bool write_description, std::string prefix) const override
Definition Formatter.cpp:67
std::string make_option_opts(const Option *opt) const override
std::string make_footer(const App *app) const override
std::string make_subcommand(const App *sub) const override
~HelpFormatter() override
std::string make_description(const App *app) const override
std::string make_usage(const App *app, std::string name) const override
std::string make_subcommands(const App *app, AppFormatMode mode) const override
std::string make_group(std::string group, bool is_positional, std::vector< const Option * > opts) const override
std::string make_expanded(const App *sub, AppFormatMode mode) const override
Definition Logger.h:55
Code
Definition Logger.h:57
@ FG_DEFAULT
Definition Logger.h:58
std::string operator+(const std::string &string, const Code &code)
Definition Logger.cpp:304