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