diff --git a/core/src/lib/cli.cc b/core/src/lib/cli.cc index ce90bd7759b..0d891a3a432 100644 --- a/core/src/lib/cli.cc +++ b/core/src/lib/cli.cc @@ -24,6 +24,106 @@ #include "lib/bnet_network_dump.h" #include "lib/version.h" #include "lib/message.h" +#include + +class BareosCliFormatter : public CLI::Formatter { + public: + std::string make_option_opts(const CLI::Option* opt) const override + { + std::stringstream out; + + if (!opt->get_option_text().empty()) { + out << " " << opt->get_option_text(); + } else { + if (opt->get_type_size() != 0) { + if (!opt->get_type_name().empty()) { + out << " " << get_label(opt->get_type_name()); + } + if (opt->get_expected_max() == CLI::detail::expected_max_vector_size) { + out << " ..."; + } else if (opt->get_expected_min() > 1) { + out << " x " << opt->get_expected(); + } + if (!opt->get_default_str().empty()) { + out << "\n" << indent << indent; + out << "Default: " << opt->get_default_str(); + } + if (opt->get_required()) { + out << "\n" << indent << indent; + out << get_label("REQUIRED"); + } + } + if (!opt->get_envname().empty()) { + out << "\n" << indent << indent; + out << get_label("Env") << ": " << opt->get_envname(); + } + if (!opt->get_needs().empty()) { + out << "\n" << indent << indent; + out << get_label("Needs") << ":"; + for (const CLI::Option* op : opt->get_needs()) + out << " " << op->get_name(); + } + if (!opt->get_excludes().empty()) { + out << "\n" << indent << indent; + out << get_label("Excludes") << ":"; + for (const CLI::Option* op : opt->get_excludes()) + out << " " << op->get_name(); + } + } + return out.str(); + } + + std::string make_option(const CLI::Option* opt, + bool is_positional) const override + { + std::stringstream out; + + std::string name = make_option_name(opt, is_positional); + // remove option values from string, eg. + // -s{false},--no-signals{false} + // => -s,--no-signals + name = std::regex_replace(name, std::regex("\\{[^}]*\\}"), ""); + out << indent << name; + + out << make_option_opts(opt); + out << std::endl; + + std::string description = make_option_desc(opt); + if (!description.empty()) { + format_paragraph(out, description, indent + indent); + } + out << std::endl; + + return out.str(); + } + + protected: + std::string indent = std::string(" "); + std::size_t max_line_length = 79; + + std::ostream& format_paragraph(std::ostream& out, + const std::string& text, + const std::string& indent) const + { + std::istringstream text_iss(text); + + std::string word; + unsigned characters_written = indent.size(); + + out << indent; + while (text_iss >> word) { + if (word.size() + characters_written > max_line_length) { + out << "\n"; + out << indent; + characters_written = indent.size(); + } + out << word << " "; + characters_written += word.size() + 1; + } + out << std::endl; + return out; + } +}; void InitCLIApp(CLI::App& app, std::string description, int fsfyear) { @@ -38,7 +138,7 @@ void InitCLIApp(CLI::App& app, std::string description, int fsfyear) app.description(description); app.set_help_flag("-h,--help,-?", "Print this help message and exit."); app.set_version_flag("--version", kBareosVersionStrings.Full); - app.get_formatter()->column_width(40); + app.formatter(std::make_shared()); #ifdef HAVE_WIN32 app.allow_windows_style_options(); #endif diff --git a/core/src/tests/CMakeLists.txt b/core/src/tests/CMakeLists.txt index c2e15baf185..c4f8b751951 100644 --- a/core/src/tests/CMakeLists.txt +++ b/core/src/tests/CMakeLists.txt @@ -167,6 +167,9 @@ if(NOT client-only) $<$:${PAM_LIBRARIES}> GTest::gtest_main SKIP_GTEST ) + + bareos_add_test(cli_test LINK_LIBRARIES bareos CLI11::CLI11 GTest::gtest_main) + bareos_add_test( configure LINK_LIBRARIES testing_common dird_objects bareosfind bareossql GTest::gtest_main diff --git a/core/src/tests/cli_test.cc b/core/src/tests/cli_test.cc new file mode 100644 index 00000000000..b8e471f299b --- /dev/null +++ b/core/src/tests/cli_test.cc @@ -0,0 +1,71 @@ +/* + BAREOSĀ® - Backup Archiving REcovery Open Sourced + + Copyright (C) 2022-2022 Bareos GmbH & Co. KG + + This program is Free Software; you can redistribute it and/or + modify it under the terms of version three of the GNU Affero General Public + License as published by the Free Software Foundation and included + in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#if defined(HAVE_MINGW) +# include "include/bareos.h" +# include "gtest/gtest.h" +#else +# include "gtest/gtest.h" +# include "include/bareos.h" +#endif + +#include "lib/cli.h" + +TEST(CLI, HelpMessageDisplaysWithCorrectFormat) +{ + CLI::App app; + InitCLIApp(app, "test app"); + std::string random_default{"random default"}; + std::string random_option_text{"a random option."}; + bool random_flag; + auto xarg = app.add_flag("!-x", random_flag, random_option_text); + + app.add_option("-y", random_default, random_option_text) + ->required() + ->capture_default_str() + ->needs(xarg) + ->excludes(xarg) + ->expected(5); + + + /* clang-format off */ + std::string expected_help{ + "test app\n" + "Usage: [OPTIONS]\n\n" + "Options:\n" + " -h,-?,--help\n" + " Print this help message and exit. \n\n" + " --version\n" + " Display program version information and exit \n\n" + " -x\n" + " Excludes: -y\n" + " "+random_option_text+" \n\n" + " -y TEXT x 5\n" + " Default: "+random_default+"\n" + " REQUIRED\n" + " Needs: -x\n" + " Excludes: -x\n" + " "+random_option_text+" \n\n\n"}; + /* clang-format on */ + + EXPECT_STREQ(app.get_description().c_str(), "test app"); + EXPECT_STREQ(app.help().c_str(), expected_help.c_str()); +}