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