diff --git a/examples/formatter.cpp b/examples/formatter.cpp index cc9b1f50..4fa5fe1c 100644 --- a/examples/formatter.cpp +++ b/examples/formatter.cpp @@ -2,6 +2,7 @@ class MyFormatter : public CLI::Formatter { public: + using Formatter::Formatter; std::string make_option_opts(const CLI::Option *) const override { return " OPTION"; } }; @@ -9,8 +10,8 @@ int main(int argc, char **argv) { CLI::App app; app.set_help_all_flag("--help-all", "Show all help"); - MyFormatter fmt; - fmt.column_width(15); + auto fmt = std::make_shared(); + fmt->column_width(15); app.formatter(fmt); app.add_flag("--flag", "This is a flag"); diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 23311150..993f2781 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -106,8 +106,8 @@ class App { /// A pointer to the help all flag if there is one INHERITABLE Option *help_all_ptr_{nullptr}; - /// This is the formatter for help printing. Default provided. INHERITABLE - std::function formatter_{Formatter()}; + /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) + std::shared_ptr formatter_{new Formatter()}; /// The error message printing function INHERITABLE std::function failure_message_ = FailureMessage::simple; @@ -262,11 +262,17 @@ class App { } /// Set the help formatter - App *formatter(std::function fmt) { + App *formatter(std::shared_ptr fmt) { formatter_ = fmt; return this; } + /// Set the help formatter + App *formatter(std::function fmt) { + formatter_ = std::make_shared(fmt); + return this; + } + /// Check to see if this subcommand was parsed, true only if received on command line. bool parsed() const { return parsed_; } @@ -1071,13 +1077,16 @@ class App { if(!selected_subcommands.empty()) return selected_subcommands.at(0)->help(prev); else - return formatter_(this, prev, mode); + return formatter_->make_help(this, prev, mode); } ///@} /// @name Getters ///@{ + /// Access the formatter + std::shared_ptr get_formatter() const { return formatter_; } + /// Get the app or subcommand description std::string get_description() const { return description_; } diff --git a/include/CLI/Formatter.hpp b/include/CLI/Formatter.hpp index 5486fff3..1f0110b1 100644 --- a/include/CLI/Formatter.hpp +++ b/include/CLI/Formatter.hpp @@ -113,7 +113,7 @@ inline std::string Formatter::make_footer(const App *app) const { return ""; } -inline std::string Formatter::operator()(const App *app, std::string name, AppFormatMode mode) const { +inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const { // This immediatly forwards to the make_expanded method. This is done this way so that subcommands can // have overridden formatters diff --git a/include/CLI/FormatterFwd.hpp b/include/CLI/FormatterFwd.hpp index f4535e18..c2a27912 100644 --- a/include/CLI/FormatterFwd.hpp +++ b/include/CLI/FormatterFwd.hpp @@ -21,10 +21,15 @@ class App; enum class AppFormatMode { Normal, //< The normal, detailed help All, //< A fully expanded help - Sub, //< Used when printed as part of expanded subcommand + Sub, //< Used when printed as part of expanded subcommand }; -class Formatter { +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: /// @name Options ///@{ @@ -40,9 +45,14 @@ class Formatter { ///@{ public: - Formatter() = default; - Formatter(const Formatter &) = default; - Formatter(Formatter &&) = default; + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + virtual ~FormatterBase() = default; + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + ///@} /// @name Setters ///@{ @@ -68,6 +78,30 @@ class Formatter { /// Get the current column width size_t get_column_width() const { return column_width_; } + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + funct_t lambda_; + + public: + explicit FormatterLambda(funct_t funct) : lambda_(funct) {} + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + /// @name Overridables ///@{ @@ -102,7 +136,7 @@ class Formatter { 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; + std::string make_help(const App *, std::string, AppFormatMode) const override; ///@} /// @name Options diff --git a/tests/FormatterTest.cpp b/tests/FormatterTest.cpp index 0c538212..dafbaff5 100644 --- a/tests/FormatterTest.cpp +++ b/tests/FormatterTest.cpp @@ -11,9 +11,28 @@ using ::testing::HasSubstr; using ::testing::Not; +class SimpleFormatter : public CLI::FormatterBase { + public: + SimpleFormatter() : FormatterBase() {} + + std::string make_help(const CLI::App *, std::string, CLI::AppFormatMode) const override { + return "This is really simple"; + } +}; + TEST(Formatter, Nothing) { CLI::App app{"My prog"}; + app.formatter(std::make_shared()); + + std::string help = app.help(); + + EXPECT_EQ(help, "This is really simple"); +} + +TEST(Formatter, NothingLambda) { + CLI::App app{"My prog"}; + app.formatter( [](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); }); @@ -25,9 +44,9 @@ TEST(Formatter, Nothing) { TEST(Formatter, OptCustomize) { CLI::App app{"My prog"}; - CLI::Formatter optfmt; - optfmt.column_width(25); - optfmt.label("REQUIRED", "(MUST HAVE)"); + auto optfmt = std::make_shared(); + optfmt->column_width(25); + optfmt->label("REQUIRED", "(MUST HAVE)"); app.formatter(optfmt); int v; @@ -44,13 +63,33 @@ TEST(Formatter, OptCustomize) { " --opt INT (MUST HAVE) Something\n"); } -TEST(Formatter, AptCustomize) { +TEST(Formatter, OptCustomizeSimple) { + CLI::App app{"My prog"}; + + app.get_formatter()->column_width(25); + app.get_formatter()->label("REQUIRED", "(MUST HAVE)"); + + int v; + app.add_option("--opt", v, "Something")->required(); + + std::string help = app.help(); + + EXPECT_THAT(help, HasSubstr("(MUST HAVE)")); + EXPECT_EQ(help, + "My prog\n" + "Usage: [OPTIONS]\n\n" + "Options:\n" + " -h,--help Print this help message and exit\n" + " --opt INT (MUST HAVE) Something\n"); +} + +TEST(Formatter, AppCustomize) { CLI::App app{"My prog"}; app.add_subcommand("subcom1", "This"); - CLI::Formatter appfmt; - appfmt.column_width(20); - appfmt.label("Usage", "Run"); + auto appfmt = std::make_shared(); + appfmt->column_width(20); + appfmt->label("Usage", "Run"); app.formatter(appfmt); app.add_subcommand("subcom2", "This"); @@ -66,6 +105,26 @@ TEST(Formatter, AptCustomize) { " subcom2 This\n"); } +TEST(Formatter, AppCustomizeSimple) { + CLI::App app{"My prog"}; + app.add_subcommand("subcom1", "This"); + + app.get_formatter()->column_width(20); + app.get_formatter()->label("Usage", "Run"); + + app.add_subcommand("subcom2", "This"); + + std::string help = app.help(); + EXPECT_EQ(help, + "My prog\n" + "Run: [OPTIONS] [SUBCOMMAND]\n\n" + "Options:\n" + " -h,--help Print this help message and exit\n\n" + "Subcommands:\n" + " subcom1 This\n" + " subcom2 This\n"); +} + TEST(Formatter, AllSub) { CLI::App app{"My prog"}; CLI::App *sub = app.add_subcommand("subcom", "This");