File indexing completed on 2026-05-06 08:37:15
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() == 1 ? "SUBCOMMAND" : "SUBCOMMANDS")
0137 << (app->get_require_subcommand_min() == 0 ? "]" : "");
0138 }
0139
0140 out << "\n\n";
0141
0142 return out.str();
0143 }
0144
0145 CLI11_INLINE std::string Formatter::make_footer(const App *app) const {
0146 std::string footer = app->get_footer();
0147 if(footer.empty()) {
0148 return std::string{};
0149 }
0150 return '\n' + footer + '\n';
0151 }
0152
0153 CLI11_INLINE std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
0154
0155
0156 if(mode == AppFormatMode::Sub)
0157 return make_expanded(app, mode);
0158
0159 std::stringstream out;
0160 if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
0161 if(app->get_group() != "SUBCOMMANDS") {
0162 out << app->get_group() << ':';
0163 }
0164 }
0165 if(is_description_paragraph_formatting_enabled()) {
0166 detail::streamOutAsParagraph(
0167 out, make_description(app), description_paragraph_width_, "");
0168 } else {
0169 out << make_description(app) << '\n';
0170 }
0171 out << make_usage(app, name);
0172 out << make_positionals(app);
0173 out << make_groups(app, mode);
0174 out << make_subcommands(app, mode);
0175 std::string footer_string = make_footer(app);
0176
0177 if(is_footer_paragraph_formatting_enabled()) {
0178 detail::streamOutAsParagraph(out, footer_string, footer_paragraph_width_);
0179 } else {
0180 out << footer_string;
0181 }
0182
0183 return out.str();
0184 }
0185
0186 CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
0187 std::stringstream out;
0188
0189 std::vector<const App *> subcommands = app->get_subcommands({});
0190
0191
0192 std::vector<std::string> subcmd_groups_seen;
0193 for(const App *com : subcommands) {
0194 if(com->get_name().empty()) {
0195 if(!com->get_group().empty() && com->get_group().front() != '+') {
0196 out << make_expanded(com, mode);
0197 }
0198 continue;
0199 }
0200 std::string group_key = com->get_group();
0201 if(!group_key.empty() &&
0202 std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
0203 return detail::to_lower(a) == detail::to_lower(group_key);
0204 }) == subcmd_groups_seen.end())
0205 subcmd_groups_seen.push_back(group_key);
0206 }
0207
0208
0209 for(const std::string &group : subcmd_groups_seen) {
0210 out << '\n' << group << ":\n";
0211 std::vector<const App *> subcommands_group = app->get_subcommands(
0212 [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
0213 for(const App *new_com : subcommands_group) {
0214 if(new_com->get_name().empty())
0215 continue;
0216 if(mode != AppFormatMode::All) {
0217 out << make_subcommand(new_com);
0218 } else {
0219 out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
0220 out << '\n';
0221 }
0222 }
0223 }
0224
0225 return out.str();
0226 }
0227
0228 CLI11_INLINE std::string Formatter::make_subcommand(const App *sub) const {
0229 std::stringstream out;
0230 std::string name = " " + sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : "");
0231
0232 out << std::setw(static_cast<int>(column_width_)) << std::left << name;
0233 detail::streamOutAsParagraph(
0234 out, sub->get_description(), right_column_width_, std::string(column_width_, ' '), true);
0235 out << '\n';
0236 return out.str();
0237 }
0238
0239 CLI11_INLINE std::string Formatter::make_expanded(const App *sub, AppFormatMode mode) const {
0240 std::stringstream out;
0241 out << sub->get_display_name(true) << '\n';
0242
0243 if(is_description_paragraph_formatting_enabled()) {
0244 detail::streamOutAsParagraph(
0245 out, make_description(sub), description_paragraph_width_, " ");
0246 } else {
0247 out << make_description(sub) << '\n';
0248 }
0249
0250 if(sub->get_name().empty() && !sub->get_aliases().empty()) {
0251 detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
0252 }
0253
0254 out << make_positionals(sub);
0255 out << make_groups(sub, mode);
0256 out << make_subcommands(sub, mode);
0257 std::string footer_string = make_footer(sub);
0258
0259 if(mode == AppFormatMode::Sub && !footer_string.empty()) {
0260 const auto *parent = sub->get_parent();
0261 std::string parent_footer = (parent != nullptr) ? make_footer(sub->get_parent()) : std::string{};
0262 if(footer_string == parent_footer) {
0263 footer_string = "";
0264 }
0265 }
0266 if(!footer_string.empty()) {
0267 if(is_footer_paragraph_formatting_enabled()) {
0268 detail::streamOutAsParagraph(out, footer_string, footer_paragraph_width_);
0269 } else {
0270 out << footer_string;
0271 }
0272 }
0273 return out.str();
0274 }
0275
0276 CLI11_INLINE std::string Formatter::make_option(const Option *opt, bool is_positional) const {
0277 std::stringstream out;
0278 if(is_positional) {
0279 const std::string left = " " + make_option_name(opt, true) + make_option_opts(opt);
0280 const std::string desc = make_option_desc(opt);
0281 out << std::setw(static_cast<int>(column_width_)) << std::left << left;
0282
0283 if(!desc.empty()) {
0284 bool skipFirstLinePrefix = true;
0285 if(left.length() >= column_width_) {
0286 out << '\n';
0287 skipFirstLinePrefix = false;
0288 }
0289 detail::streamOutAsParagraph(
0290 out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
0291 }
0292 } else {
0293 const std::string namesCombined = make_option_name(opt, false);
0294 const std::string opts = make_option_opts(opt);
0295 const std::string desc = make_option_desc(opt);
0296
0297
0298 const auto names = detail::split(namesCombined, ',');
0299 std::vector<std::string> vshortNames;
0300 std::vector<std::string> vlongNames;
0301 std::for_each(names.begin(), names.end(), [&vshortNames, &vlongNames](const std::string &name) {
0302 if(name.find("--", 0) != std::string::npos)
0303 vlongNames.push_back(name);
0304 else
0305 vshortNames.push_back(name);
0306 });
0307
0308
0309 std::string shortNames = detail::join(vshortNames, ", ");
0310 std::string longNames = detail::join(vlongNames, ", ");
0311
0312
0313
0314 const auto shortNamesColumnWidth =
0315 static_cast<int>(static_cast<float>(column_width_) * long_option_alignment_ratio_);
0316 const auto longNamesColumnWidth = static_cast<int>(column_width_) - shortNamesColumnWidth;
0317 int shortNamesOverSize = 0;
0318
0319
0320 if(!shortNames.empty()) {
0321 shortNames = " " + shortNames;
0322 if(longNames.empty() && !opts.empty())
0323 shortNames += opts;
0324 if(!longNames.empty())
0325 shortNames += ",";
0326 if(static_cast<int>(shortNames.length()) >= shortNamesColumnWidth) {
0327 shortNames += " ";
0328 shortNamesOverSize = static_cast<int>(shortNames.length()) - shortNamesColumnWidth;
0329 }
0330 out << std::setw(shortNamesColumnWidth) << std::left << shortNames;
0331 } else {
0332 out << std::setw(shortNamesColumnWidth) << std::left << "";
0333 }
0334
0335
0336 shortNamesOverSize =
0337 (std::min)(shortNamesOverSize, longNamesColumnWidth);
0338 const auto adjustedLongNamesColumnWidth = longNamesColumnWidth - shortNamesOverSize;
0339
0340
0341 if(!longNames.empty()) {
0342 if(!opts.empty())
0343 longNames += opts;
0344 if(static_cast<int>(longNames.length()) >= adjustedLongNamesColumnWidth)
0345 longNames += " ";
0346
0347 out << std::setw(adjustedLongNamesColumnWidth) << std::left << longNames;
0348 } else {
0349 out << std::setw(adjustedLongNamesColumnWidth) << std::left << "";
0350 }
0351
0352 if(!desc.empty()) {
0353 bool skipFirstLinePrefix = true;
0354 if(out.str().length() > column_width_) {
0355 out << '\n';
0356 skipFirstLinePrefix = false;
0357 }
0358 detail::streamOutAsParagraph(
0359 out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix);
0360 }
0361 }
0362
0363 out << '\n';
0364 return out.str();
0365 }
0366
0367 CLI11_INLINE std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
0368 if(is_positional)
0369 return opt->get_name(true, false);
0370
0371 return opt->get_name(false, true);
0372 }
0373
0374 CLI11_INLINE std::string Formatter::make_option_opts(const Option *opt) const {
0375 std::stringstream out;
0376
0377 if(!opt->get_option_text().empty()) {
0378 out << " " << opt->get_option_text();
0379 } else {
0380 if(opt->get_type_size() != 0) {
0381 if(!opt->get_type_name().empty())
0382 out << " " << get_label(opt->get_type_name());
0383 if(!opt->get_default_str().empty())
0384 out << " [" << opt->get_default_str() << "] ";
0385 if(opt->get_expected_max() == detail::expected_max_vector_size)
0386 out << " ...";
0387 else if(opt->get_expected_min() > 1)
0388 out << " x " << opt->get_expected();
0389
0390 if(opt->get_required())
0391 out << " " << get_label("REQUIRED");
0392 }
0393 if(!opt->get_envname().empty())
0394 out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
0395 if(!opt->get_needs().empty()) {
0396 out << " " << get_label("Needs") << ":";
0397 for(const Option *op : opt->get_needs())
0398 out << " " << op->get_name();
0399 }
0400 if(!opt->get_excludes().empty()) {
0401 out << " " << get_label("Excludes") << ":";
0402 for(const Option *op : opt->get_excludes())
0403 out << " " << op->get_name();
0404 }
0405 }
0406 return out.str();
0407 }
0408
0409 CLI11_INLINE std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
0410
0411 CLI11_INLINE std::string Formatter::make_option_usage(const Option *opt) const {
0412
0413 std::stringstream out;
0414 out << make_option_name(opt, true);
0415 if(opt->get_expected_max() >= detail::expected_max_vector_size)
0416 out << "...";
0417 else if(opt->get_expected_max() > 1)
0418 out << "(" << opt->get_expected() << "x)";
0419
0420 return opt->get_required() ? out.str() : "[" + out.str() + "]";
0421 }
0422
0423 }