File indexing completed on 2026-01-08 10:09:17
0001
0002
0003
0004
0005
0006
0007 #pragma once
0008
0009
0010
0011
0012 #include "../Formatter.hpp"
0013
0014
0015 #include <algorithm>
0016 #include <string>
0017 #include <utility>
0018 #include <vector>
0019
0020
0021 namespace CLI {
0022
0023 CLI11_INLINE std::string
0024 Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
0025 std::stringstream out;
0026
0027 out << "\n" << group << ":\n";
0028 for(const Option *opt : opts) {
0029 out << make_option(opt, is_positional);
0030 }
0031
0032 return out.str();
0033 }
0034
0035 CLI11_INLINE std::string Formatter::make_positionals(const App *app) const {
0036 std::vector<const Option *> opts =
0037 app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
0038
0039 if(opts.empty())
0040 return {};
0041
0042 return make_group(get_label("POSITIONALS"), true, opts);
0043 }
0044
0045 CLI11_INLINE std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
0046 std::stringstream out;
0047 std::vector<std::string> groups = app->get_groups();
0048
0049
0050 for(const std::string &group : groups) {
0051 std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) {
0052 return opt->get_group() == group
0053 && opt->nonpositional()
0054 && (mode != AppFormatMode::Sub
0055 || (app->get_help_ptr() != opt
0056 && app->get_help_all_ptr() != opt));
0057 });
0058 if(!group.empty() && !opts.empty()) {
0059 out << make_group(group, false, opts);
0060
0061
0062
0063
0064 }
0065 }
0066
0067 return out.str();
0068 }
0069
0070 CLI11_INLINE std::string Formatter::make_description(const App *app) const {
0071 std::string desc = app->get_description();
0072 auto min_options = app->get_require_option_min();
0073 auto max_options = app->get_require_option_max();
0074
0075 if(app->get_required()) {
0076 desc += " " + get_label("REQUIRED") + " ";
0077 }
0078
0079 if(min_options > 0) {
0080 if(max_options == min_options) {
0081 desc += " \n[Exactly " + std::to_string(min_options) + " of the following options are required]";
0082 } else if(max_options > 0) {
0083 desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
0084 " of the following options are required]";
0085 } else {
0086 desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
0087 }
0088 } else if(max_options > 0) {
0089 desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
0090 }
0091
0092 return (!desc.empty()) ? desc + "\n\n" : std::string{};
0093 }
0094
0095 CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const {
0096 std::string usage = app->get_usage();
0097 if(!usage.empty()) {
0098 return usage + "\n\n";
0099 }
0100
0101 std::stringstream out;
0102 out << '\n';
0103
0104 if(name.empty())
0105 out << get_label("Usage") << ':';
0106 else
0107 out << name;
0108
0109 std::vector<std::string> groups = app->get_groups();
0110
0111
0112 std::vector<const Option *> non_pos_options =
0113 app->get_options([](const Option *opt) { return opt->nonpositional(); });
0114 if(!non_pos_options.empty())
0115 out << " [" << get_label("OPTIONS") << "]";
0116
0117
0118 std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
0119
0120
0121 if(!positionals.empty()) {
0122
0123 std::vector<std::string> positional_names(positionals.size());
0124 std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) {
0125 return make_option_usage(opt);
0126 });
0127
0128 out << " " << detail::join(positional_names, " ");
0129 }
0130
0131
0132 if(!app->get_subcommands(
0133 [](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
0134 .empty()) {
0135 out << ' ' << (app->get_require_subcommand_min() == 0 ? "[" : "")
0136 << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
0137 : "SUBCOMMANDS")
0138 << (app->get_require_subcommand_min() == 0 ? "]" : "");
0139 }
0140
0141 out << "\n\n";
0142
0143 return out.str();
0144 }
0145
0146 CLI11_INLINE std::string Formatter::make_footer(const App *app) const {
0147 std::string footer = app->get_footer();
0148 if(footer.empty()) {
0149 return std::string{};
0150 }
0151 return '\n' + footer + "\n\n";
0152 }
0153
0154 CLI11_INLINE std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
0155
0156
0157 if(mode == AppFormatMode::Sub)
0158 return make_expanded(app, mode);
0159
0160 std::stringstream out;
0161 if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
0162 if(app->get_group() != "SUBCOMMANDS") {
0163 out << app->get_group() << ':';
0164 }
0165 }
0166
0167 detail::streamOutAsParagraph(
0168 out, make_description(app), description_paragraph_width_, "");
0169 out << make_usage(app, name);
0170 out << make_positionals(app);
0171 out << make_groups(app, mode);
0172 out << make_subcommands(app, mode);
0173 detail::streamOutAsParagraph(out, make_footer(app), footer_paragraph_width_);
0174
0175 return out.str();
0176 }
0177
0178 CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
0179 std::stringstream out;
0180
0181 std::vector<const App *> subcommands = app->get_subcommands({});
0182
0183
0184 std::vector<std::string> subcmd_groups_seen;
0185 for(const App *com : subcommands) {
0186 if(com->get_name().empty()) {
0187 if(!com->get_group().empty() && com->get_group().front() != '+') {
0188 out << make_expanded(com, mode);
0189 }
0190 continue;
0191 }
0192 std::string group_key = com->get_group();
0193 if(!group_key.empty() &&
0194 std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
0195 return detail::to_lower(a) == detail::to_lower(group_key);
0196 }) == subcmd_groups_seen.end())
0197 subcmd_groups_seen.push_back(group_key);
0198 }
0199
0200
0201 for(const std::string &group : subcmd_groups_seen) {
0202 out << '\n' << group << ":\n";
0203 std::vector<const App *> subcommands_group = app->get_subcommands(
0204 [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
0205 for(const App *new_com : subcommands_group) {
0206 if(new_com->get_name().empty())
0207 continue;
0208 if(mode != AppFormatMode::All) {
0209 out << make_subcommand(new_com);
0210 } else {
0211 out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
0212 out << '\n';
0213 }
0214 }
0215 }
0216
0217 return out.str();
0218 }
0219
0220 CLI11_INLINE std::string Formatter::make_subcommand(const App *sub) const {
0221 std::stringstream out;
0222 std::string name = " " + sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : "");
0223
0224 out << std::setw(static_cast<int>(column_width_)) << std::left << name;
0225 detail::streamOutAsParagraph(
0226 out, sub->get_description(), right_column_width_, std::string(column_width_, ' '), true);
0227 out << '\n';
0228 return out.str();
0229 }
0230
0231 CLI11_INLINE std::string Formatter::make_expanded(const App *sub, AppFormatMode mode) const {
0232 std::stringstream out;
0233 out << sub->get_display_name(true) << '\n';
0234
0235 detail::streamOutAsParagraph(
0236 out, make_description(sub), description_paragraph_width_, " ");
0237
0238 if(sub->get_name().empty() && !sub->get_aliases().empty()) {
0239 detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
0240 }
0241
0242 out << make_positionals(sub);
0243 out << make_groups(sub, mode);
0244 out << make_subcommands(sub, mode);
0245 detail::streamOutAsParagraph(out, make_footer(sub), footer_paragraph_width_);
0246
0247 out << '\n';
0248 return out.str();
0249 }
0250
0251 CLI11_INLINE std::string Formatter::make_option(const Option *opt, bool is_positional) const {
0252 std::stringstream out;
0253 if(is_positional) {
0254 const std::string left = " " + make_option_name(opt, true) + make_option_opts(opt);
0255 const std::string desc = make_option_desc(opt);
0256 out << std::setw(static_cast<int>(column_width_)) << std::left << left;
0257
0258 if(!desc.empty()) {
0259 bool skipFirstLinePrefix = true;
0260 if(left.length() >= column_width_) {
0261 out << '\n';
0262 skipFirstLinePrefix = false;
0263 }
0264 detail::streamOutAsParagraph(
0265 out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
0266 }
0267 } else {
0268 const std::string namesCombined = make_option_name(opt, false);
0269 const std::string opts = make_option_opts(opt);
0270 const std::string desc = make_option_desc(opt);
0271
0272
0273 const auto names = detail::split(namesCombined, ',');
0274 std::vector<std::string> vshortNames;
0275 std::vector<std::string> vlongNames;
0276 std::for_each(names.begin(), names.end(), [&vshortNames, &vlongNames](const std::string &name) {
0277 if(name.find("--", 0) != std::string::npos)
0278 vlongNames.push_back(name);
0279 else
0280 vshortNames.push_back(name);
0281 });
0282
0283
0284 std::string shortNames = detail::join(vshortNames, ", ");
0285 std::string longNames = detail::join(vlongNames, ", ");
0286
0287
0288 const auto shortNamesColumnWidth = static_cast<int>(column_width_ / 3);
0289 const auto longNamesColumnWidth = static_cast<int>(std::ceil(
0290 static_cast<float>(column_width_) / 3.0f * 2.0f));
0291 int shortNamesOverSize = 0;
0292
0293
0294 if(shortNames.length() > 0) {
0295 shortNames = " " + shortNames;
0296 if(longNames.length() == 0 && opts.length() > 0)
0297 shortNames += opts;
0298 if(longNames.length() > 0)
0299 shortNames += ",";
0300 if(static_cast<int>(shortNames.length()) >= shortNamesColumnWidth) {
0301 shortNames += " ";
0302 shortNamesOverSize = static_cast<int>(shortNames.length()) - shortNamesColumnWidth;
0303 }
0304 out << std::setw(shortNamesColumnWidth) << std::left << shortNames;
0305 } else {
0306 out << std::setw(shortNamesColumnWidth) << std::left << "";
0307 }
0308
0309
0310 shortNamesOverSize =
0311 (std::min)(shortNamesOverSize, longNamesColumnWidth);
0312 const auto adjustedLongNamesColumnWidth = longNamesColumnWidth - shortNamesOverSize;
0313
0314
0315 if(longNames.length() > 0) {
0316 if(opts.length() > 0)
0317 longNames += opts;
0318 if(static_cast<int>(longNames.length()) >= adjustedLongNamesColumnWidth)
0319 longNames += " ";
0320
0321 out << std::setw(adjustedLongNamesColumnWidth) << std::left << longNames;
0322 } else {
0323 out << std::setw(adjustedLongNamesColumnWidth) << std::left << "";
0324 }
0325
0326 if(!desc.empty()) {
0327 bool skipFirstLinePrefix = true;
0328 if(out.str().length() > column_width_) {
0329 out << '\n';
0330 skipFirstLinePrefix = false;
0331 }
0332 detail::streamOutAsParagraph(
0333 out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
0334 }
0335 }
0336
0337 out << '\n';
0338 return out.str();
0339 }
0340
0341 CLI11_INLINE std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
0342 if(is_positional)
0343 return opt->get_name(true, false);
0344
0345 return opt->get_name(false, true);
0346 }
0347
0348 CLI11_INLINE std::string Formatter::make_option_opts(const Option *opt) const {
0349 std::stringstream out;
0350
0351 if(!opt->get_option_text().empty()) {
0352 out << " " << opt->get_option_text();
0353 } else {
0354 if(opt->get_type_size() != 0) {
0355 if(!opt->get_type_name().empty())
0356 out << " " << get_label(opt->get_type_name());
0357 if(!opt->get_default_str().empty())
0358 out << " [" << opt->get_default_str() << "] ";
0359 if(opt->get_expected_max() == detail::expected_max_vector_size)
0360 out << " ...";
0361 else if(opt->get_expected_min() > 1)
0362 out << " x " << opt->get_expected();
0363
0364 if(opt->get_required())
0365 out << " " << get_label("REQUIRED");
0366 }
0367 if(!opt->get_envname().empty())
0368 out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
0369 if(!opt->get_needs().empty()) {
0370 out << " " << get_label("Needs") << ":";
0371 for(const Option *op : opt->get_needs())
0372 out << " " << op->get_name();
0373 }
0374 if(!opt->get_excludes().empty()) {
0375 out << " " << get_label("Excludes") << ":";
0376 for(const Option *op : opt->get_excludes())
0377 out << " " << op->get_name();
0378 }
0379 }
0380 return out.str();
0381 }
0382
0383 CLI11_INLINE std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
0384
0385 CLI11_INLINE std::string Formatter::make_option_usage(const Option *opt) const {
0386
0387 std::stringstream out;
0388 out << make_option_name(opt, true);
0389 if(opt->get_expected_max() >= detail::expected_max_vector_size)
0390 out << "...";
0391 else if(opt->get_expected_max() > 1)
0392 out << "(" << opt->get_expected() << "x)";
0393
0394 return opt->get_required() ? out.str() : "[" + out.str() + "]";
0395 }
0396
0397 }