diff --git a/include/cli/cli.h b/include/cli/cli.h index 95b3aa4..a8b3931 100644 --- a/include/cli/cli.h +++ b/include/cli/cli.h @@ -37,6 +37,7 @@ #include #include // std::isspace #include +#include #include "colorprofile.h" #include "detail/history.h" #include "detail/split.h" @@ -295,7 +296,14 @@ namespace cli class CliSession { + static void DefaultNoMatchHandler(std::ostream& out, const std::string& cmd) + { + out << "wrong command: " << cmd << '\n'; + } + public: + using NoMatchHandler_t = std::function; + CliSession(Cli& _cli, std::ostream& _out, std::size_t historySize = 100); virtual ~CliSession() noexcept { Cli::UnRegister(out); } @@ -330,6 +338,13 @@ namespace cli exitAction = action; } + NoMatchHandler_t NoMatchHandler(NoMatchHandler_t handler) + { + using std::swap; + swap(handler, noMatchHandler); + return std::move(handler); + } + void ShowHistory() const { history.Show(out); } std::string PreviousCmd(const std::string& line) @@ -352,6 +367,7 @@ namespace cli std::ostream& out; std::function< void(std::ostream&)> exitAction = []( std::ostream& ){}; detail::History history; + NoMatchHandler_t noMatchHandler = &CliSession::DefaultNoMatchHandler; }; // ******************************************************************** @@ -485,7 +501,7 @@ namespace cli return (parent && parent->Exec(cmdLine, session)); } - std::string Prompt() const + virtual std::string Prompt() const { return Name(); } @@ -775,7 +791,7 @@ namespace cli if (!found) found = current->ScanCmds(strs, *this); if (!found) // error msg if not found - out << "wrong command: " << cmd << '\n'; + noMatchHandler(out, cmd); } catch(const std::exception& e) { diff --git a/test/test_cli.cpp b/test/test_cli.cpp index ae395a6..c399882 100644 --- a/test/test_cli.cpp +++ b/test/test_cli.cpp @@ -410,4 +410,48 @@ BOOST_AUTO_TEST_CASE(Exceptions) BOOST_CHECK_NO_THROW( UserInput(cli, oss, "customexception") ); } +BOOST_AUTO_TEST_CASE(NoMatch) +{ + Cli cli{make_unique("cli")}; + + stringstream iss, oss; + auto reset = [&iss, &oss](const char* iss_content) { + oss.str({}); + oss.clear(); + + iss.clear(); + iss.str(iss_content); + }; + + reset("asdf\n"); + CliFileSession sess{cli, iss, oss}; + sess.Start(); + + // Check default handler works + BOOST_CHECK_EQUAL(ExtractContent(oss), "wrong command: asdf"); + + reset("bsdf\n"); + CliFileSession sess2{cli, iss, oss}; + auto orig = sess2.NoMatchHandler([](std::ostream& out, const std::string& cmd) { out << "custom handler " << cmd << '\n'; }); + + sess2.Start(); + BOOST_CHECK_EQUAL(ExtractContent(oss), "custom handler bsdf"); + + // Verify chaining + reset("csdf\n"); + CliFileSession sess3{cli, iss, oss}; + sess3.NoMatchHandler([&orig](std::ostream& out, const std::string& cmd) { out << "intercept "; orig(out, "prefix-" + cmd); }); + + sess3.Start(); + BOOST_CHECK_EQUAL(ExtractContent(oss), "intercept wrong command: prefix-csdf"); + + // Returned handler should be last installed handler (i.e. custom handler for sess2) + reset("dsdf\n"); + CliFileSession sess4{cli, iss, oss}; + sess4.NoMatchHandler(sess2.NoMatchHandler(nullptr)); + + sess4.Start(); + BOOST_CHECK_EQUAL(ExtractContent(oss), "custom handler dsdf"); +} + BOOST_AUTO_TEST_SUITE_END()