diff --git a/CHANGELOG.md b/CHANGELOG.md index da782137..5a60b69f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ -### Version 1.6: Formatters +### Version 1.6: Formatting -Added a new formatting system. You can now set the formatter on Apps and Options. +Added a new formatting system. You can now set the formatter on Apps. -* Added `AppFormatter` and `OptionFormatter`, and `formatter` slot +* Added `CLI::Formatter` and `formatter` slot for apps, inherited. * Added `help_all` support (not added by default) * Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering * Added `get_groups()` to get groups diff --git a/README.md b/README.md index 9ab8b7ae..d96ab976 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,6 @@ The add commands return a pointer to an internally stored `Option`. If you set t * `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. * `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options. * `->configurable(false)`: Disable this option from being in an ini configuration file. -* `->formatter(fmt)`: Set the formatter, with signature `std::string(const Option*, OptionFormatMode)`. See Formatting for more details. These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector` of results. @@ -306,15 +305,13 @@ The default settings for options are inherited to subcommands, as well. ## Formatting -The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App` or `Option`. -CLI11 comes with a default App formatter functional, `AppFormatter`, and a default `OptionFormatter` as well. They are both customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do -that too; you just need to implement the correct signature. The first argument is a const pointer to the App or Option in question. The App formatter will get a `std::string` usage name as the second option, and both end with with a mode. Both formatters return a `std::string`. +The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App`, where fmt is any copyable callable with the correct signature. +CLI11 comes with a default App formatter functional, `Formatter`. It is customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do +that too; you just need to implement the correct signature. The first argument is a const pointer to the in question. The formatter will get a `std::string` usage name as the second option, and a `AppFormatMode` mode for the final option. It should return a `std::string`. The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the situation the help was called in. `Sub` is optional, but the default formatter uses it to make sure expanded subcommands are called with their own formatter since you can't access anything but the call operator once a formatter has been set. -The `OptionFormatMode` also has three values, depending on the calling situation: `Positional`, `Optional`, and `Usage`. The default formatter uses these to print out the option in each of these situations. - ## Subclassing The App class was designed allow toolkits to subclass it, to provide preset default options (see above) and setup/teardown code. Subcommands remain an unsubclassed `App`, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`, than can removed/replaced using `.set_help_flag(name, help_string)`. You can also set a help-all flag with `.set_help_all_flag(name, help_string)`; this will expand the subcommands (one level only). You can remove options if you have pointers to them using `.remove_option(opt)`. You can add a `pre_callback` override to customize the after parse diff --git a/examples/formatter.cpp b/examples/formatter.cpp index 788c0b41..cc9b1f50 100644 --- a/examples/formatter.cpp +++ b/examples/formatter.cpp @@ -1,17 +1,15 @@ #include -class MyFormatter : public CLI::OptionFormatter { +class MyFormatter : public CLI::Formatter { public: - std::string make_opts(const CLI::Option *) const override { return " OPTION"; } + std::string make_option_opts(const CLI::Option *) const override { return " OPTION"; } }; int main(int argc, char **argv) { CLI::App app; app.set_help_all_flag("--help-all", "Show all help"); - app.option_defaults()->formatter(MyFormatter()); - - CLI::AppFormatter fmt; + MyFormatter fmt; fmt.column_width(15); app.formatter(fmt); diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 7e157b11..204f9c3a 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -24,7 +24,7 @@ #include "CLI/Split.hpp" #include "CLI/StringTools.hpp" #include "CLI/TypeTools.hpp" -#include "CLI/Formatter.hpp" +#include "CLI/FormatterFwd.hpp" namespace CLI { @@ -107,7 +107,7 @@ class App { Option *help_all_ptr_{nullptr}; /// This is the formatter for help printing. Default provided. INHERITABLE - std::function formatter_{AppFormatter()}; + std::function formatter_{Formatter()}; /// The error message printing function INHERITABLE std::function failure_message_ = FailureMessage::simple; diff --git a/include/CLI/AppFormatter.hpp b/include/CLI/AppFormatter.hpp deleted file mode 100644 index f463cd42..00000000 --- a/include/CLI/AppFormatter.hpp +++ /dev/null @@ -1,185 +0,0 @@ -#pragma once - -// Distributed under the 3-Clause BSD License. See accompanying -// file LICENSE or https://github.com/CLIUtils/CLI11 for details. - -#include - -#include "CLI/App.hpp" -#include "CLI/Formatter.hpp" - -namespace CLI { - -inline std::string -AppFormatter::make_group(std::string group, std::vector opts, OptionFormatMode mode) const { - std::stringstream out; - out << "\n" << group << ":\n"; - for(const Option *opt : opts) { - out << opt->help(mode); - } - - return out.str(); -} - -inline std::string AppFormatter::make_groups(const App *app, AppFormatMode mode) const { - std::stringstream out; - std::vector groups = app->get_groups(); - std::vector positionals = - app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); }); - - if(!positionals.empty()) - out << make_group(get_label("Positionals"), positionals, OptionFormatMode::Positional); - - // Options - for(const std::string &group : groups) { - std::vector grouped_items = - app->get_options([&group](const Option *opt) { return opt->nonpositional() && opt->get_group() == group; }); - - if(mode == AppFormatMode::Sub) { - grouped_items.erase(std::remove_if(grouped_items.begin(), - grouped_items.end(), - [app](const Option *opt) { - return app->get_help_ptr() == opt || app->get_help_all_ptr() == opt; - }), - grouped_items.end()); - } - - if(!group.empty() && !grouped_items.empty()) { - out << make_group(group, grouped_items, OptionFormatMode::Optional); - if(group != groups.back()) - out << "\n"; - } - } - - return out.str(); -} - -inline std::string AppFormatter::make_description(const App *app) const { - std::string desc = app->get_description(); - - if(!desc.empty()) - return desc + "\n"; - else - return ""; -} - -inline std::string AppFormatter::make_usage(const App *app, std::string name) const { - std::stringstream out; - - out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name; - - std::vector groups = app->get_groups(); - - // Print an Options badge if any options exist - std::vector non_pos_options = - app->get_options([](const Option *opt) { return opt->nonpositional(); }); - if(!non_pos_options.empty()) - out << " [" << get_label("OPTIONS") << "]"; - - // Positionals need to be listed here - std::vector positionals = app->get_options([](const Option *opt) { return opt->get_positional(); }); - - // Print out positionals if any are left - if(!positionals.empty()) { - // Convert to help names - std::vector positional_names(positionals.size()); - std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [](const Option *opt) { - return opt->help(OptionFormatMode::Usage); - }); - - out << " " << detail::join(positional_names, " "); - } - - // Add a marker if subcommands are expected or optional - if(!app->get_subcommands({}).empty()) { - out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "") - << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND" - : "SUBCOMMANDS") - << (app->get_require_subcommand_min() == 0 ? "]" : ""); - } - - out << std::endl; - - return out.str(); -} - -inline std::string AppFormatter::make_footer(const App *app) const { - std::string footer = app->get_footer(); - if(!footer.empty()) - return footer + "\n"; - else - return ""; -} - -inline std::string AppFormatter::operator()(const App *app, std::string name, AppFormatMode mode) const { - - std::stringstream out; - if(mode == AppFormatMode::Normal) { - out << make_description(app); - out << make_usage(app, name); - out << make_groups(app, mode); - out << make_subcommands(app, mode); - out << make_footer(app); - } else if(mode == AppFormatMode::Sub) { - out << make_expanded(app); - } else if(mode == AppFormatMode::All) { - out << make_description(app); - out << make_usage(app, name); - out << make_groups(app, mode); - out << make_subcommands(app, mode); - } - - return out.str(); -} - -inline std::string AppFormatter::make_subcommands(const App *app, AppFormatMode mode) const { - std::stringstream out; - - std::vector subcommands = app->get_subcommands({}); - - // Make a list in definition order of the groups seen - std::vector subcmd_groups_seen; - for(const App *com : subcommands) { - std::string group_key = com->get_group(); - if(!group_key.empty() && - std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) { - return detail::to_lower(a) == detail::to_lower(group_key); - }) == subcmd_groups_seen.end()) - subcmd_groups_seen.push_back(group_key); - } - - // For each group, filter out and print subcommands - for(const std::string &group : subcmd_groups_seen) { - out << "\n" << group << ":\n"; - if(mode == AppFormatMode::All) - out << "\n"; - std::vector subcommands_group = app->get_subcommands( - [&group](const App *app) { return detail::to_lower(app->get_group()) == detail::to_lower(group); }); - for(const App *new_com : subcommands_group) { - if(mode != AppFormatMode::All) { - out << make_subcommand(new_com); - } else { - out << new_com->help(new_com->get_name(), AppFormatMode::Sub); - if(new_com != subcommands_group.back()) - out << "\n"; - } - } - } - - return out.str(); -} - -inline std::string AppFormatter::make_subcommand(const App *sub) const { - std::stringstream out; - detail::format_help(out, sub->get_name(), sub->get_description(), column_width_); - return out.str(); -} - -inline std::string AppFormatter::make_expanded(const App *sub) const { - std::stringstream out; - out << sub->get_name() << "\n " << sub->get_description(); - out << make_groups(sub, AppFormatMode::Sub); - return out.str(); -} - -} // namespace CLI diff --git a/include/CLI/CLI.hpp b/include/CLI/CLI.hpp index 20dba54b..a06d3025 100644 --- a/include/CLI/CLI.hpp +++ b/include/CLI/CLI.hpp @@ -24,12 +24,10 @@ #include "CLI/Validators.hpp" -#include "CLI/Formatter.hpp" +#include "CLI/FormatterFwd.hpp" #include "CLI/Option.hpp" -#include "CLI/OptionFormatter.hpp" - #include "CLI/App.hpp" -#include "CLI/AppFormatter.hpp" +#include "CLI/Formatter.hpp" diff --git a/include/CLI/Formatter.hpp b/include/CLI/Formatter.hpp index 3c17014d..3c366f28 100644 --- a/include/CLI/Formatter.hpp +++ b/include/CLI/Formatter.hpp @@ -4,187 +4,234 @@ // file LICENSE or https://github.com/CLIUtils/CLI11 for details. #include -#include -#include "CLI/StringTools.hpp" +#include "CLI/App.hpp" +#include "CLI/Formatter.hpp" namespace CLI { -class Option; -class App; - -/// This enum signifies what situation the option is beig printed in. -/// -/// This is passed in as part of the built in formatter in App; it is -/// possible that a custom App formatter could avoid using it, however. -enum class OptionFormatMode { - Usage, //< In the program usage line - Positional, //< In the positionals - Optional //< In the normal optionals -}; - -/// This enum signifies the type of help requested -/// -/// This is passed in by App; all user classes must accept this as -/// the second argument. - -enum class AppFormatMode { - Normal, //< The normal, detailed help - All, //< A fully expanded help - Sub, //< Used when printed as part of expanded subcommand -}; - -/// This is an example formatter (and also the default formatter) -/// For option help. -class OptionFormatter { - protected: - /// @name Options - ///@{ - - /// @brief The required help printout labels (user changeable) - /// Values are REQUIRED, NEEDS, EXCLUDES - std::map labels_{{"REQUIRED", "(REQUIRED)"}}; - - /// The width of the first column - size_t column_width_{30}; - - ///@} - /// @name Basic - ///@{ - - public: - OptionFormatter() = default; - OptionFormatter(const OptionFormatter &) = default; - OptionFormatter(OptionFormatter &&) = default; - - ///@} - /// @name Setters - ///@{ - - /// Set the "REQUIRED" label - void label(std::string key, std::string val) { labels_[key] = val; } - - /// Set the column width - void column_width(size_t val) { column_width_ = val; } - - ///@} - /// @name Getters - ///@{ - - /// Get the current value of a name (REQUIRED, etc.) - std::string get_label(std::string key) const { - if(labels_.find(key) == labels_.end()) - return key; - else - return labels_.at(key); +inline std::string +Formatter::make_group(std::string group, std::vector opts, bool is_positional) const { + std::stringstream out; + out << "\n" << group << ":\n"; + for(const Option *opt : opts) { + out << make_option(opt, is_positional); } - /// Get the current column width - size_t get_column_width() const { return column_width_; } + return out.str(); +} - ///@} - /// @name Overridables - ///@{ +inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const { + std::stringstream out; + std::vector groups = app->get_groups(); + std::vector positionals = + app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); }); - /// @brief This is the name part of an option, Default: left column - virtual std::string make_name(const Option *, OptionFormatMode) const; + if(!positionals.empty()) + out << make_group(get_label("Positionals"), positionals, true); - /// @brief This is the options part of the name, Default: combined into left column - virtual std::string make_opts(const Option *) const; + // Options + for(const std::string &group : groups) { + std::vector grouped_items = + app->get_options([&group](const Option *opt) { return opt->nonpositional() && opt->get_group() == group; }); - /// @brief This is the description. Default: Right column, on new line if left column too large - virtual std::string make_desc(const Option *) const; + if(mode == AppFormatMode::Sub) { + grouped_items.erase(std::remove_if(grouped_items.begin(), + grouped_items.end(), + [app](const Option *opt) { + return app->get_help_ptr() == opt || app->get_help_all_ptr() == opt; + }), + grouped_items.end()); + } - /// @brief This is used to print the name on the USAGE line (by App formatter) - virtual std::string make_usage(const Option *opt) const; - - /// @brief This is the standard help combiner that does the "default" thing. - virtual std::string operator()(const Option *opt, OptionFormatMode mode) const { - std::stringstream out; - if(mode == OptionFormatMode::Usage) - out << make_usage(opt); - else - detail::format_help(out, make_name(opt, mode) + make_opts(opt), make_desc(opt), column_width_); - return out.str(); + if(!group.empty() && !grouped_items.empty()) { + out << make_group(group, grouped_items, false); + if(group != groups.back()) + out << "\n"; + } } - ///@} -}; + return out.str(); +} -class AppFormatter { - /// @name Options - ///@{ +inline std::string Formatter::make_description(const App *app) const { + std::string desc = app->get_description(); - /// The width of the first column - size_t column_width_{30}; + if(!desc.empty()) + return desc + "\n"; + else + return ""; +} - /// @brief The required help printout labels (user changeable) - /// Values are Needs, Excludes, etc. - std::map labels_; +inline std::string Formatter::make_usage(const App *app, std::string name) const { + std::stringstream out; - ///@} - /// @name Basic - ///@{ + out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name; - public: - AppFormatter() = default; - AppFormatter(const AppFormatter &) = default; - AppFormatter(AppFormatter &&) = default; - ///@} - /// @name Setters - ///@{ + std::vector groups = app->get_groups(); - /// Set the "REQUIRED" label - void label(std::string key, std::string val) { labels_[key] = val; } + // Print an Options badge if any options exist + std::vector non_pos_options = + app->get_options([](const Option *opt) { return opt->nonpositional(); }); + if(!non_pos_options.empty()) + out << " [" << get_label("OPTIONS") << "]"; - /// Set the column width - void column_width(size_t val) { column_width_ = val; } + // Positionals need to be listed here + std::vector positionals = app->get_options([](const Option *opt) { return opt->get_positional(); }); - ///@} - /// @name Getters - ///@{ + // Print out positionals if any are left + if(!positionals.empty()) { + // Convert to help names + std::vector positional_names(positionals.size()); + std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) { + return make_option_usage(opt); + }); - /// Get the current value of a name (REQUIRED, etc.) - std::string get_label(std::string key) const { - if(labels_.find(key) == labels_.end()) - return key; - else - return labels_.at(key); + out << " " << detail::join(positional_names, " "); } - /// Get the current column width - size_t get_column_width() const { return column_width_; } + // Add a marker if subcommands are expected or optional + if(!app->get_subcommands({}).empty()) { + out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "") + << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND" + : "SUBCOMMANDS") + << (app->get_require_subcommand_min() == 0 ? "]" : ""); + } - /// @name Overridables - ///@{ + out << std::endl; - /// This prints out a group of options - virtual std::string make_group(std::string group, std::vector opts, OptionFormatMode mode) const; + return out.str(); +} - /// This prints out all the groups of options - virtual std::string make_groups(const App *app, AppFormatMode mode) const; +inline std::string Formatter::make_footer(const App *app) const { + std::string footer = app->get_footer(); + if(!footer.empty()) + return footer + "\n"; + else + return ""; +} - /// This prints out all the subcommands - virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; +inline std::string Formatter::operator()(const App *app, std::string name, AppFormatMode mode) const { - /// This prints out a subcommand - virtual std::string make_subcommand(const App *sub) const; + std::stringstream out; + if(mode == AppFormatMode::Normal) { + out << make_description(app); + out << make_usage(app, name); + out << make_groups(app, mode); + out << make_subcommands(app, mode); + out << make_footer(app); + } else if(mode == AppFormatMode::Sub) { + out << make_expanded(app); + } else if(mode == AppFormatMode::All) { + out << make_description(app); + out << make_usage(app, name); + out << make_groups(app, mode); + out << make_subcommands(app, mode); + } - /// This prints out a subcommand in help-all - virtual std::string make_expanded(const App *sub) const; + return out.str(); +} - /// This prints out all the groups of options - virtual std::string make_footer(const App *app) const; +inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const { + std::stringstream out; - /// This displays the description line - virtual std::string make_description(const App *app) const; + std::vector subcommands = app->get_subcommands({}); - /// This displays the usage line - virtual std::string make_usage(const App *app, std::string name) const; + // Make a list in definition order of the groups seen + std::vector subcmd_groups_seen; + for(const App *com : subcommands) { + std::string group_key = com->get_group(); + if(!group_key.empty() && + std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) { + return detail::to_lower(a) == detail::to_lower(group_key); + }) == subcmd_groups_seen.end()) + subcmd_groups_seen.push_back(group_key); + } - /// This puts everything together - virtual std::string operator()(const App *, std::string, AppFormatMode) const; - ///@} -}; + // For each group, filter out and print subcommands + for(const std::string &group : subcmd_groups_seen) { + out << "\n" << group << ":\n"; + if(mode == AppFormatMode::All) + out << "\n"; + std::vector subcommands_group = app->get_subcommands( + [&group](const App *app) { return detail::to_lower(app->get_group()) == detail::to_lower(group); }); + for(const App *new_com : subcommands_group) { + if(mode != AppFormatMode::All) { + out << make_subcommand(new_com); + } else { + out << new_com->help(new_com->get_name(), AppFormatMode::Sub); + if(new_com != subcommands_group.back()) + out << "\n"; + } + } + } + + return out.str(); +} + +inline std::string Formatter::make_subcommand(const App *sub) const { + std::stringstream out; + detail::format_help(out, sub->get_name(), sub->get_description(), column_width_); + return out.str(); +} + +inline std::string Formatter::make_expanded(const App *sub) const { + std::stringstream out; + out << sub->get_name() << "\n " << sub->get_description(); + out << make_groups(sub, AppFormatMode::Sub); + return out.str(); +} + +inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const { + if(is_positional) + return opt->get_name(true, false); + else + return opt->get_name(false, true); +} + +inline std::string Formatter::make_option_opts(const Option *opt) const { + std::stringstream out; + + if(opt->get_type_size() != 0) { + if(!opt->get_typeval().empty()) + out << " " << get_label(opt->get_typeval()); + if(!opt->get_defaultval().empty()) + out << "=" << opt->get_defaultval(); + if(opt->get_expected() > 1) + out << " x " << opt->get_expected(); + if(opt->get_expected() == -1) + out << " ..."; + if(opt->get_required()) + out << " " << get_label("REQUIRED"); + } + if(!opt->get_envname().empty()) + out << " (" << get_label("Env") << ":" << opt->get_envname() << ")"; + if(!opt->get_needs().empty()) { + out << " " << get_label("Needs") << ":"; + for(const Option *op : opt->get_needs()) + out << " " << op->get_name(); + } + if(!opt->get_excludes().empty()) { + out << " " << get_label("Excludes") << ":"; + for(const Option *op : opt->get_excludes()) + out << " " << op->get_name(); + } + return out.str(); +} + +inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); } + +inline std::string Formatter::make_option_usage(const Option *opt) const { + // Note that these are positionals usages + std::stringstream out; + out << make_option_name(opt, true); + + if(opt->get_expected() > 1) + out << "(" << std::to_string(opt->get_expected()) << "x)"; + else if(opt->get_expected() < 0) + out << "..."; + + return opt->get_required() ? out.str() : "[" + out.str() + "]"; +} } // namespace CLI diff --git a/include/CLI/FormatterFwd.hpp b/include/CLI/FormatterFwd.hpp new file mode 100644 index 00000000..462539ae --- /dev/null +++ b/include/CLI/FormatterFwd.hpp @@ -0,0 +1,127 @@ +#pragma once + +// Distributed under the 3-Clause BSD License. See accompanying +// file LICENSE or https://github.com/CLIUtils/CLI11 for details. + +#include +#include + +#include "CLI/StringTools.hpp" + +namespace CLI { + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, //< The normal, detailed help + All, //< A fully expanded help + Sub, //< Used when printed as part of expanded subcommand +}; + +class Formatter { + /// @name Options + ///@{ + + /// The width of the first column + size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_; + + ///@} + /// @name Basic + ///@{ + + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + else + return labels_.at(key); + } + + /// Get the current column width + size_t get_column_width() const { return column_width_; } + + /// @name Overridables + ///@{ + + /// This prints out a group of options + virtual std::string make_group(std::string group, std::vector opts, bool is_positional) const; + + /// This prints out all the groups of options + virtual std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + virtual std::string operator()(const App *, std::string, AppFormatMode) const; + + ///@} + /// @name Options + ///@{ + + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + +} // namespace CLI diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 0c9a7651..c74caf81 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -16,7 +16,6 @@ #include "CLI/Macros.hpp" #include "CLI/Split.hpp" #include "CLI/StringTools.hpp" -#include "CLI/Formatter.hpp" namespace CLI { @@ -49,17 +48,12 @@ template class OptionBase { /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; - /// @brief A formatter to print out help - /// Used by the default App help formatter. - std::function formatter_{OptionFormatter()}; - template void copy_to(T *other) const { other->group(group_); other->required(required_); other->ignore_case(ignore_case_); other->configurable(configurable_); other->multi_option_policy(multi_option_policy_); - other->formatter(formatter_); } public: @@ -81,12 +75,6 @@ template class OptionBase { /// Support Plumbum term CRTP *mandatory(bool value = true) { return required(value); } - /// Set a formatter for this option - CRTP *formatter(std::function value) { - formatter_ = value; - return static_cast(this); - } - // Getters /// Get the group of this option @@ -491,17 +479,6 @@ class Option : public OptionBase