Add first version
[ric-plt/sdl.git] / src / cli / commandmap.cpp
diff --git a/src/cli/commandmap.cpp b/src/cli/commandmap.cpp
new file mode 100644 (file)
index 0000000..e0e8304
--- /dev/null
@@ -0,0 +1,185 @@
+#include "private/cli/commandmap.hpp"
+#include <sstream>
+#include <iomanip>
+#include <memory>
+#include <cstdlib>
+#include <boost/io/ios_state.hpp>
+
+using namespace shareddatalayer::cli;
+
+namespace
+{
+    std::string buildCommandNameAlreadyRegisteredError(const std::string& commandName)
+    {
+        std::ostringstream os;
+        os << "command name \"" << commandName << "\" already registered";
+        return os.str();
+    }
+
+    std::string buildUnknownCommandNameError(const std::string& commandName)
+    {
+        std::ostringstream os;
+        os << "unknown command: \"" << commandName << '\"';
+        return os.str();
+    }
+
+    std::string buildCategoryOffsetAlreadyRegisteredError(const std::string& commandName, int offset)
+    {
+        std::ostringstream os;
+        os << commandName << ": Offset " << offset << " already registered";
+        return os.str();
+    }
+
+    std::string getCategoryName(CommandMap::Category category)
+    {
+        switch (category)
+        {
+            case CommandMap::Category::UTIL:
+                return "Utility";
+            default:
+                return "Unknown";
+        }
+    }
+}
+
+CommandMap::CommandNameAlreadyRegistered::CommandNameAlreadyRegistered(const std::string& commandName):
+    Exception(buildCommandNameAlreadyRegisteredError(commandName))
+{
+}
+
+CommandMap::UnknownCommandName::UnknownCommandName(const std::string& commandName):
+    Exception(buildUnknownCommandNameError(commandName))
+{
+}
+
+CommandMap::CategoryOffsetAlreadyRegistered::CategoryOffsetAlreadyRegistered(const std::string& commandName, int offset):
+    Exception(buildCategoryOffsetAlreadyRegisteredError(commandName, offset))
+{
+}
+
+struct CommandMap::Info
+{
+    CommandFunction function;
+    std::string shortHelp;
+    std::string longHelp;
+    boost::program_options::options_description options;
+
+    Info(const CommandFunction& function,
+         const std::string& shortHelp,
+         const std::string& longHelp);
+};
+
+CommandMap::Info::Info(const CommandFunction& function,
+                       const std::string& shortHelp,
+                       const std::string& longHelp):
+    function(function),
+    shortHelp(shortHelp),
+    longHelp(longHelp)
+{
+}
+
+CommandMap::CommandMap()
+{
+}
+
+CommandMap::~CommandMap()
+{
+}
+
+boost::program_options::options_description&
+CommandMap::registerCommand(const std::string& commandName,
+                            const std::string& shortHelp,
+                            const std::string& longHelp,
+                            const CommandFunction& commandFunction,
+                            Category category,
+                            int categoryOffset)
+{
+    const auto ret(map.insert(std::make_pair(commandName, Info(commandFunction, shortHelp, longHelp))));
+    if (!ret.second)
+        throw CommandNameAlreadyRegistered(commandName);
+    const auto retCat(categoryMap.insert(std::make_pair(CategoryKey(category, categoryOffset), commandName)));
+    if (!retCat.second)
+        throw CategoryOffsetAlreadyRegistered(commandName, categoryOffset);
+    return ret.first->second.options;
+}
+
+std::vector<std::string> CommandMap::getCommandNames() const
+{
+    std::vector<std::string> ret;
+    for (const auto& i : map)
+        ret.push_back(i.first);
+    return ret;
+}
+
+const boost::program_options::options_description&
+CommandMap::getCommandOptions(const std::string& commandName) const
+{
+    const auto i(map.find(commandName));
+    if (i == map.end())
+        throw UnknownCommandName(commandName);
+    return i->second.options;
+}
+
+void CommandMap::shortHelp(std::ostream& out) const
+{
+    boost::io::ios_all_saver guard(out);
+    size_t maxWidth(0U);
+    Category currentCategory(Category::UNDEFINED);
+    for (const auto& i : map)
+        if (maxWidth < i.first.size())
+            maxWidth = i.first.size();
+    for (const auto& i : categoryMap)
+    {
+        if (currentCategory != i.first.first)
+        {
+            currentCategory = i.first.first;
+            out << std::endl;
+            out << getCategoryName(currentCategory) << " commands:" << std::endl;
+        }
+        out << std::left << std::setw(maxWidth + 2U) << i.second << map.at(i.second).shortHelp << '\n';
+    }
+}
+
+void CommandMap::longHelp(std::ostream& out, const std::string& commandName) const
+{
+    const auto i(map.find(commandName));
+    if (i == map.end())
+        throw UnknownCommandName(commandName);
+    out << i->first;
+    if (!i->second.options.options().empty())
+    {
+        out << " OPTIONS\n\n" << i->second.longHelp << "\n\nOptions:\n";
+        i->second.options.print(out);
+    }
+    else
+    {
+        out << "\n\n" << i->second.longHelp << '\n';
+    }
+}
+
+int CommandMap::execute(const std::string& commandName,
+                        std::ostream& out,
+                        std::ostream& err,
+                        const boost::program_options::variables_map& params,
+                        size_t count)
+{
+    const auto i(map.find(commandName));
+    if (i == map.end())
+    {
+        err << "unknown command: \"" << commandName << '\"' << std::endl;
+        return EXIT_FAILURE;
+    }
+    const auto& function(i->second.function);
+    int ret(EXIT_SUCCESS);
+    for (size_t i = 0U; i < count; ++i)
+        if ((ret = function(out, err, params)) != EXIT_SUCCESS)
+            break;
+    return ret;
+}
+
+CommandMap& CommandMap::getCommandMap() noexcept
+{
+    static CommandMap instance;
+    return instance;
+}
+