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
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 <algorithm>
47#include <functional>
48#include <iomanip>
49#include <set>
50#include <sstream>
51#include <vector>
52
53#endif // DOXYGEN_SHOULD_SKIP_THIS
54
55namespace CLI {
56
58 }
59
60 CLI11_INLINE std::string
61 ConfigFormatter::to_config(const App* app, bool default_also, bool write_description, std::string prefix) const {
62 std::stringstream out;
63 std::string commentLead;
64 commentLead.push_back(commentChar);
65 commentLead.push_back(' ');
66
67 std::vector<std::string> groups = app->get_groups();
68 bool defaultUsed = false;
69 groups.insert(groups.begin(), std::string("Options"));
70 for (const auto& group : groups) {
71 if (group == "Options" || group.empty()) {
72 if (defaultUsed) {
73 continue;
74 }
75 defaultUsed = true;
76 }
77 if (write_description && group != "Options" && !group.empty() &&
78 !app->get_options([group](const Option* opt) -> bool {
79 return opt->get_group() == group && opt->get_configurable();
80 })
81 .empty()) {
82 out << '\n' << commentChar << commentLead << group << "\n";
83 }
84 for (const Option* opt : app->get_options({})) {
85 // Only process options that are configurable
86 if (opt->get_configurable()) {
87 if (opt->get_group() != group) {
88 if (!(group == "Options" && opt->get_group().empty())) {
89 continue;
90 }
91 }
92 const std::string name = prefix + opt->get_single_name();
93 std::string value =
94 detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
95
96 std::string defaultValue{};
97 if (default_also) {
98 static_assert(std::string::npos + static_cast<std::string::size_type>(1) == 0,
99 "std::string::npos + static_cast<std::string::size_type>(1) != 0");
100 if (!value.empty() && detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, literalQuote) == value) {
101 value.clear();
102 }
103 if (!opt->get_default_str().empty()) {
104 defaultValue = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, literalQuote);
105 if (defaultValue == "'\"\"'") {
106 defaultValue = "";
107 }
108 } else if (opt->get_run_callback_for_default()) {
109 defaultValue = "\"\""; // empty string default value
110 } else if (opt->get_required()) {
111 defaultValue = "\"<REQUIRED>\"";
112 } else if (opt->get_expected_min() == 0) {
113 defaultValue = "false";
114 } else {
115 defaultValue = "\"\"";
116 }
117 }
118 if (write_description && opt->has_description() && (default_also || !value.empty())) {
119 out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
120 }
121 if (default_also && !defaultValue.empty()) {
122 out << commentChar << name << valueDelimiter << defaultValue << "\n";
123 }
124 if (!value.empty()) {
125 if (!opt->get_fnames().empty()) {
126 value = opt->get_flag_value(name, value);
127 }
128 if (value == "\"default\"") {
129 value = "default";
130 }
131 out << name << valueDelimiter << value << "\n\n";
132 } else {
133 out << '\n';
134 }
135 }
136 }
137 }
138 auto subcommands = app->get_subcommands({});
139 for (const App* subcom : subcommands) {
140 if (subcom->get_name().empty()) {
141 if (write_description && !subcom->get_group().empty()) {
142 out << '\n' << commentLead << subcom->get_group() << " Options\n";
143 }
144 out << to_config(subcom, default_also, write_description, prefix);
145 }
146 }
147
148 for (const App* subcom : subcommands) {
149 if (!subcom->get_name().empty()) {
150 if (subcom->get_configurable() && app->got_subcommand(subcom)) {
151 if (!prefix.empty() || app->get_parent() == nullptr) {
152 out << '[' << prefix << subcom->get_name() << "]\n";
153 } else {
154 std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name();
155 const auto* p = app->get_parent();
156 while (p->get_parent() != nullptr) {
157 subname = p->get_name() + parentSeparatorChar + subname;
158 p = p->get_parent();
159 }
160 out << '[' << subname << "]\n";
161 }
162 out << to_config(subcom, default_also, write_description, "");
163 } else {
164 out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar);
165 }
166 }
167 }
168
169 std::string outString;
170 if (write_description && !out.str().empty()) {
171 outString =
172 commentChar + std::string("#") + commentLead + detail::fix_newlines(commentChar + commentLead, app->get_description());
173 }
174
175 return outString + out.str();
176 }
177
178#ifndef CLI11_ORIGINAL_FORMATTER
180 }
181
182 CLI11_INLINE std::string HelpFormatter::make_group(std::string group, bool is_positional, std::vector<const Option*> opts) const {
183 std::stringstream out;
184
185 // ############## Next line changed
186 out << "\n" << group << ":\n";
187 for (const Option* opt : opts) {
188 out << make_option(opt, is_positional);
189 }
190
191 return out.str();
192 }
193
194 CLI11_INLINE std::string HelpFormatter::make_description(const App* app) const {
195 std::string desc = app->get_description();
196 auto min_options = app->get_require_option_min();
197 auto max_options = app->get_require_option_max();
198 // ############## Next line changed (if statement removed)
199 if ((max_options == min_options) && (min_options > 0)) {
200 if (min_options == 1) {
201 desc += " \n[Exactly 1 of the following options is required]";
202 } else {
203 desc += " \n[Exactly " + std::to_string(min_options) + " options from the following list are required]";
204 }
205 } else if (max_options > 0) {
206 if (min_options > 0) {
207 desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
208 " of the follow options are required]";
209 } else {
210 desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
211 }
212 } else if (min_options > 0) {
213 desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
214 }
215 return (!desc.empty()) ? desc + "\n" : std::string{};
216 }
217
218 CLI11_INLINE std::string HelpFormatter::make_usage(const App* app, std::string name) const {
219 const std::string usage = app->get_usage();
220 if (!usage.empty()) {
221 return usage + "\n";
222 }
223
224 std::stringstream out;
225
226 out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
227
228 // Print an Options badge if any options exist
229 const std::vector<const Option*> non_pos_options = app->get_options([](const Option* opt) {
230 return opt->nonpositional();
231 });
232 if (!non_pos_options.empty()) {
233 out << " [" << get_label("OPTIONS") << "]";
234 }
235
236 // Positionals need to be listed here
237 std::vector<const Option*> positionals = app->get_options([](const Option* opt) {
238 return opt->get_positional();
239 });
240
241 // Print out positionals if any are left
242 if (!positionals.empty()) {
243 // Convert to help names
244 std::vector<std::string> positional_names(positionals.size());
245 std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option* opt) {
246 return make_option_usage(opt);
247 });
248
249 out << " " << detail::join(positional_names, " ");
250 }
251
252 // Add a marker if subcommands are expected or optional
253 if (!app->get_subcommands([](const App* subc) {
254 return !subc->get_disabled() && !subc->get_name().empty();
255 })
256 .empty()) {
257 // ############## Next line changed
258 out << " ["
259 << get_label(app->get_subcommands([](const CLI::App* subc) {
260 return ((!subc->get_disabled()) && (!subc->get_name().empty()) /*&& subc->get_required()*/);
261 }).size() <= 1
262 ? "SUBCOMMAND"
263 : "SUBCOMMANDS")
264 << " [--help]"
265 << "]";
266 }
267
268 const Option* disabledOpt = app->get_option_no_throw("--disabled");
269 if (disabledOpt != nullptr ? disabledOpt->as<bool>() : false) {
270 out << " " << get_label("DISABLED");
271 } else if (app->get_required()) {
272 out << " " << get_label("REQUIRED");
273 }
274
275 out << std::endl << std::endl;
276
277 return out.str();
278 }
279
280 CLI11_INLINE std::string HelpFormatter::make_subcommands(const App* app, AppFormatMode mode) const {
281 std::stringstream out;
282
283 const std::vector<const App*> subcommands = app->get_subcommands([](const App* subc) {
284 if (subc->get_group() == "Instances") {
285 if (subc->get_option("--disabled")->as<bool>()) {
286 const_cast<CLI::App*>(subc)->group(subc->get_group() + " (disabled)");
287 }
288 }
289 return !subc->get_disabled() && !subc->get_name().empty();
290 });
291
292 // Make a list in alphabetic order of the groups seen
293 std::set<std::string> subcmd_groups_seen;
294 for (const App* com : subcommands) {
295 if (com->get_name().empty()) {
296 if (!com->get_group().empty()) {
297 out << make_expanded(com, mode);
298 }
299 continue;
300 }
301 std::string group_key = com->get_group();
302 if (!group_key.empty() &&
303 std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](const std::string& a) {
304 return detail::to_lower(a) == detail::to_lower(group_key);
305 }) == subcmd_groups_seen.end()) {
306 subcmd_groups_seen.insert(group_key);
307 }
308 }
309
310 // For each group, filter out and print subcommands
311 const Option* disabledOpt = app->get_option_no_throw("--disabled");
312 bool disabled = false;
313 if (disabledOpt != nullptr) {
314 disabled = disabledOpt->as<bool>();
315 }
316
317 if (!disabled) {
318 for (const std::string& group : subcmd_groups_seen) {
319 out << "\n" << group << ":\n";
320 const std::vector<const App*> subcommands_group = app->get_subcommands([&group](const App* sub_app) {
321 return detail::to_lower(sub_app->get_group()) == detail::to_lower(group) && !sub_app->get_disabled() &&
322 !sub_app->get_name().empty();
323 });
324 for (const App* new_com : subcommands_group) {
325 if (new_com->get_name().empty()) {
326 continue;
327 }
328 if (mode != AppFormatMode::All) {
329 out << make_subcommand(new_com);
330 } else {
331 out << new_com->help(disabledOpt != nullptr && (app->get_help_ptr()->as<std::string>() == "exact" ||
332 app->get_help_ptr()->as<std::string>() == "expanded")
333 ? new_com
334 : nullptr,
335 new_com->get_name(),
336 AppFormatMode::Sub);
337 out << "\n";
338 }
339 }
340 }
341 }
342
343 // ########## Next line(s) changed
344
345 std::string tmp = out.str();
346 if (mode == AppFormatMode::All && !tmp.empty()) {
347 tmp.pop_back();
348 }
349
350 out.str(tmp);
351 out.clear();
352
353 return out.str();
354 }
355
356 CLI11_INLINE std::string HelpFormatter::make_subcommand(const App* sub) const {
357 std::stringstream out;
358 std::string name = " " + sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : "");
359
360 out << std::setw(static_cast<int>(column_width_)) << std::left << name;
361 std::string desc = sub->get_description();
362 if (!desc.empty()) {
363 bool skipFirstLinePrefix = true;
364 if (out.str().length() >= column_width_) {
365 out << '\n';
366 skipFirstLinePrefix = false;
367 }
368 detail::streamOutAsParagraph(out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
369 }
370
371 out << '\n';
372
373 return out.str();
374 }
375
376 CLI11_INLINE std::string HelpFormatter::make_expanded(const App* sub, AppFormatMode mode) const {
377 std::stringstream out;
378 // ########## Next lines changed
379 const Option* disabledOpt = sub->get_option_no_throw("--disabled");
380 out << sub->get_display_name(true) + " [OPTIONS]" + (!sub->get_subcommands({}).empty() ? " [SECTIONS]" : "") +
381 ((disabledOpt != nullptr ? disabledOpt->as<bool>() : false) ? " " + get_label("DISABLED")
382 : (sub->get_required() ? " " + get_label("REQUIRED") : ""))
383 << "\n";
384
385 detail::streamOutAsParagraph(out, make_description(sub), description_paragraph_width_, ""); // Format description as paragraph
386
387 if (sub->get_name().empty() && !sub->get_aliases().empty()) {
388 detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
389 }
390 out << make_positionals(sub);
391 out << make_groups(sub, mode);
392 out << make_subcommands(sub, mode);
393
394 // Drop blank spaces
395 std::string tmp = out.str();
396 tmp.pop_back();
397
398 // Indent all but the first line (the name)
399 return detail::find_and_replace(tmp, "\n", "\n ") + "\n";
400 }
401
402 CLI11_INLINE std::string HelpFormatter::make_option_opts(const Option* opt) const {
403 std::stringstream out;
404
405 if (!opt->get_option_text().empty()) {
406 out << " " << opt->get_option_text();
407 } else {
408 if (opt->get_type_size() != 0) {
409 if (!opt->get_type_name().empty()) {
410 // ########## Next line changed
411 out << ((opt->get_items_expected_max() == 0) ? "=" : " ") << get_label(opt->get_type_name());
412 }
413 if (!opt->get_default_str().empty()) {
414 out << " [" << opt->get_default_str() << "]";
415 }
416 if (opt->count() > 0 && opt->get_default_str() != opt->as<std::string>()) {
417 out << " {" << opt->as<std::string>() << "}";
418 }
419 if (opt->get_expected_max() == detail::expected_max_vector_size) {
420 out << " ... ";
421 } else if (opt->get_expected_min() > 1) {
422 out << " x " << opt->get_expected();
423 }
424 if (opt->get_required() && !get_label("REQUIRED").empty()) {
425 out << " " << get_label("REQUIRED");
426 }
427 if (opt->get_configurable() && !get_label("PERSISTENT").empty()) {
428 out << " " << get_label("PERSISTENT");
429 }
430 }
431 if (!opt->get_envname().empty()) {
432 out << " (" << get_label("Env") << ":" << opt->get_envname() << ") ";
433 }
434 if (!opt->get_needs().empty()) {
435 out << " " << get_label("Needs") << ":";
436 for (const Option* op : opt->get_needs()) {
437 out << " " << op->get_name();
438 }
439 }
440 if (!opt->get_excludes().empty()) {
441 out << " " << get_label("Excludes") << ":";
442 for (const Option* op : opt->get_excludes()) {
443 out << " " << op->get_name();
444 }
445 }
446 }
447 return out.str();
448 }
449
450#endif // CLI11_ORIGINAL_FORMATTER
451
452} // namespace CLI
~ConfigFormatter() override
Definition Formatter.cpp:57
std::string to_config(const App *app, bool default_also, bool write_description, std::string prefix) const override
Definition Formatter.cpp:61
std::string make_option_opts(const Option *opt) 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