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