Add first version
[ric-plt/sdl.git] / src / cli / commandmap.cpp
1 #include "private/cli/commandmap.hpp"
2 #include <sstream>
3 #include <iomanip>
4 #include <memory>
5 #include <cstdlib>
6 #include <boost/io/ios_state.hpp>
7
8 using namespace shareddatalayer::cli;
9
10 namespace
11 {
12     std::string buildCommandNameAlreadyRegisteredError(const std::string& commandName)
13     {
14         std::ostringstream os;
15         os << "command name \"" << commandName << "\" already registered";
16         return os.str();
17     }
18
19     std::string buildUnknownCommandNameError(const std::string& commandName)
20     {
21         std::ostringstream os;
22         os << "unknown command: \"" << commandName << '\"';
23         return os.str();
24     }
25
26     std::string buildCategoryOffsetAlreadyRegisteredError(const std::string& commandName, int offset)
27     {
28         std::ostringstream os;
29         os << commandName << ": Offset " << offset << " already registered";
30         return os.str();
31     }
32
33     std::string getCategoryName(CommandMap::Category category)
34     {
35         switch (category)
36         {
37             case CommandMap::Category::UTIL:
38                 return "Utility";
39             default:
40                 return "Unknown";
41         }
42     }
43 }
44
45 CommandMap::CommandNameAlreadyRegistered::CommandNameAlreadyRegistered(const std::string& commandName):
46     Exception(buildCommandNameAlreadyRegisteredError(commandName))
47 {
48 }
49
50 CommandMap::UnknownCommandName::UnknownCommandName(const std::string& commandName):
51     Exception(buildUnknownCommandNameError(commandName))
52 {
53 }
54
55 CommandMap::CategoryOffsetAlreadyRegistered::CategoryOffsetAlreadyRegistered(const std::string& commandName, int offset):
56     Exception(buildCategoryOffsetAlreadyRegisteredError(commandName, offset))
57 {
58 }
59
60 struct CommandMap::Info
61 {
62     CommandFunction function;
63     std::string shortHelp;
64     std::string longHelp;
65     boost::program_options::options_description options;
66
67     Info(const CommandFunction& function,
68          const std::string& shortHelp,
69          const std::string& longHelp);
70 };
71
72 CommandMap::Info::Info(const CommandFunction& function,
73                        const std::string& shortHelp,
74                        const std::string& longHelp):
75     function(function),
76     shortHelp(shortHelp),
77     longHelp(longHelp)
78 {
79 }
80
81 CommandMap::CommandMap()
82 {
83 }
84
85 CommandMap::~CommandMap()
86 {
87 }
88
89 boost::program_options::options_description&
90 CommandMap::registerCommand(const std::string& commandName,
91                             const std::string& shortHelp,
92                             const std::string& longHelp,
93                             const CommandFunction& commandFunction,
94                             Category category,
95                             int categoryOffset)
96 {
97     const auto ret(map.insert(std::make_pair(commandName, Info(commandFunction, shortHelp, longHelp))));
98     if (!ret.second)
99         throw CommandNameAlreadyRegistered(commandName);
100     const auto retCat(categoryMap.insert(std::make_pair(CategoryKey(category, categoryOffset), commandName)));
101     if (!retCat.second)
102         throw CategoryOffsetAlreadyRegistered(commandName, categoryOffset);
103     return ret.first->second.options;
104 }
105
106 std::vector<std::string> CommandMap::getCommandNames() const
107 {
108     std::vector<std::string> ret;
109     for (const auto& i : map)
110         ret.push_back(i.first);
111     return ret;
112 }
113
114 const boost::program_options::options_description&
115 CommandMap::getCommandOptions(const std::string& commandName) const
116 {
117     const auto i(map.find(commandName));
118     if (i == map.end())
119         throw UnknownCommandName(commandName);
120     return i->second.options;
121 }
122
123 void CommandMap::shortHelp(std::ostream& out) const
124 {
125     boost::io::ios_all_saver guard(out);
126     size_t maxWidth(0U);
127     Category currentCategory(Category::UNDEFINED);
128     for (const auto& i : map)
129         if (maxWidth < i.first.size())
130             maxWidth = i.first.size();
131     for (const auto& i : categoryMap)
132     {
133         if (currentCategory != i.first.first)
134         {
135             currentCategory = i.first.first;
136             out << std::endl;
137             out << getCategoryName(currentCategory) << " commands:" << std::endl;
138         }
139         out << std::left << std::setw(maxWidth + 2U) << i.second << map.at(i.second).shortHelp << '\n';
140     }
141 }
142
143 void CommandMap::longHelp(std::ostream& out, const std::string& commandName) const
144 {
145     const auto i(map.find(commandName));
146     if (i == map.end())
147         throw UnknownCommandName(commandName);
148     out << i->first;
149     if (!i->second.options.options().empty())
150     {
151         out << " OPTIONS\n\n" << i->second.longHelp << "\n\nOptions:\n";
152         i->second.options.print(out);
153     }
154     else
155     {
156         out << "\n\n" << i->second.longHelp << '\n';
157     }
158 }
159
160 int CommandMap::execute(const std::string& commandName,
161                         std::ostream& out,
162                         std::ostream& err,
163                         const boost::program_options::variables_map& params,
164                         size_t count)
165 {
166     const auto i(map.find(commandName));
167     if (i == map.end())
168     {
169         err << "unknown command: \"" << commandName << '\"' << std::endl;
170         return EXIT_FAILURE;
171     }
172     const auto& function(i->second.function);
173     int ret(EXIT_SUCCESS);
174     for (size_t i = 0U; i < count; ++i)
175         if ((ret = function(out, err, params)) != EXIT_SUCCESS)
176             break;
177     return ret;
178 }
179
180 CommandMap& CommandMap::getCommandMap() noexcept
181 {
182     static CommandMap instance;
183     return instance;
184 }
185