Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-02 10:13:38

0001 // Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
0002 // under NSF AWARD 1414736 and by the respective contributors.
0003 // All rights reserved.
0004 //
0005 // SPDX-License-Identifier: BSD-3-Clause
0006 
0007 #pragma once
0008 
0009 // IWYU pragma: private, include "CLI/CLI.hpp"
0010 
0011 // This include is only needed for IDEs to discover symbols
0012 #include "../Formatter.hpp"
0013 
0014 // [CLI11:public_includes:set]
0015 #include <algorithm>
0016 #include <set>
0017 #include <string>
0018 #include <utility>
0019 #include <vector>
0020 // [CLI11:public_includes:end]
0021 
0022 namespace CLI {
0023 // [CLI11:formatter_inl_hpp:verbatim]
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 }  // namespace detail
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     // Options
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                     // Must be in the right group
0071                    && opt->nonpositional()                       // Must not be a positional
0072                    && (mode != AppFormatMode::Sub                // If mode is Sub, then
0073                        || (app->get_help_ptr() != opt            // Ignore help pointer
0074                            && app->get_help_all_ptr() != opt));  // Ignore help all pointer
0075         });
0076         if(!group.empty() && !opts.empty()) {
0077             out << make_group(group, false, opts);
0078 
0079             // Removed double newline between groups for consistency of help text
0080             // if(group != groups.back())
0081             //    out << "\n";
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     // Print an Options badge if any options exist
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     // Positionals need to be listed here
0136     std::vector<const Option *> positionals =
0137         app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
0138 
0139     // Print out positionals if any are left
0140     if(!positionals.empty()) {
0141         // Convert to help names
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     // Add a marker if subcommands are expected or optional
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     // This immediately forwards to the make_expanded method. This is done this way so that subcommands can
0175     // have overridden formatters
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_, "");  // Format description as paragraph
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_);  // Format footer as paragraph
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     // Make a list in definition order of the groups seen
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                 // add expansion in one place so each group has subgroups beneath it
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     // For each group, filter out and print subcommands
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);  // Format description as paragraph
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_);  // Format footer as paragraph
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         // Split all names at comma and sort them into short names and long names
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         // Assemble short and long names
0344         std::string shortNames = detail::join(vshortNames, ", ");
0345         std::string longNames = detail::join(vlongNames, ", ");
0346 
0347         // Calculate setw sizes
0348         // Short names take enough width to align long names at the desired ratio
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         // Print short names
0355         if(!shortNames.empty()) {
0356             shortNames = "  " + shortNames;  // Indent
0357             if(longNames.empty() && !opts.empty())
0358                 shortNames += opts;  // Add opts if only short names and no long names
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         // Adjust long name column width in case of short names column reaching into long names column
0371         shortNamesOverSize =
0372             (std::min)(shortNamesOverSize, longNamesColumnWidth);  // Prevent negative result with unsigned integers
0373         const auto adjustedLongNamesColumnWidth = longNamesColumnWidth - shortNamesOverSize;
0374 
0375         // Print long names
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     // Help output should be stable across runs, so sort pointer-based sets by option name before printing.
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     // Note that these are positionals usages
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 // [CLI11:formatter_inl_hpp:end]
0469 }  // namespace CLI