1 /*
2 * Copyright (C) 2011-2016 OpenDungeons Team
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "ConsoleInterface.h"
19
20 #include "network/ClientNotification.h"
21 #include "network/ODClient.h"
22
23 #include <regex>
24
ConsoleInterface(PrintFunction_t printFunction)25 ConsoleInterface::ConsoleInterface(PrintFunction_t printFunction)
26 :mPrintFunction(printFunction), mCommandHistoryBuffer(), mHistoryPos(mCommandHistoryBuffer.begin())
27 {
28 addCommand("help",
29 ">help Lists available commands\n>help <command> displays description for <command>",
30 ConsoleInterface::helpCommand,
31 Command::cStubServer,
32 {AbstractModeManager::ModeType::GAME, AbstractModeManager::ModeType::EDITOR},
33 {});
34 }
35
addCommand(String_t name,String_t description,CommandClientFunction_t commandClient,CommandServerFunction_t commandServer,std::initializer_list<ModeType> allowedModes,std::initializer_list<String_t> aliases)36 bool ConsoleInterface::addCommand(String_t name, String_t description,
37 CommandClientFunction_t commandClient,
38 CommandServerFunction_t commandServer,
39 std::initializer_list<ModeType> allowedModes,
40 std::initializer_list<String_t> aliases)
41 {
42 CommandPtr_t commandPtr = std::make_shared<Command>(commandClient, commandServer, description, allowedModes);
43 mCommandMap.emplace(name, commandPtr);
44 for(auto& alias : aliases)
45 {
46 mCommandMap.emplace(alias, commandPtr);
47 }
48 return true;
49 }
50
tryExecuteClientCommand(String_t commandString,ModeType modeType,AbstractModeManager & modeManager)51 Command::Result ConsoleInterface::tryExecuteClientCommand(String_t commandString,
52 ModeType modeType,
53 AbstractModeManager& modeManager)
54 {
55 mCommandHistoryBuffer.emplace_back(commandString);
56 print(">> " + commandString);
57 Command::ArgumentList_t tokenList;
58 boost::algorithm::split(tokenList,
59 commandString, boost::algorithm::is_space(),
60 boost::algorithm::token_compress_on);
61
62 const String_t& commandName = tokenList.front();
63 auto it = mCommandMap.find(commandName);
64 if(it != mCommandMap.end())
65 {
66 // Check if there is something to do on client side
67 try
68 {
69 Command::Result result = Command::Result::SUCCESS;
70 result = it->second->executeClient(tokenList, modeType, *this, modeManager);
71
72 return result;
73 }
74 catch(const std::invalid_argument& e)
75 {
76 print(String_t("Invalid argument: ") + e.what());
77 return Command::Result::INVALID_ARGUMENT;
78 }
79 catch(const std::out_of_range& e)
80 {
81 print(String_t("Argument out of range") + e.what());
82 return Command::Result::INVALID_ARGUMENT;
83 }
84 }
85 else
86 {
87 print("Unknown command: \"" + commandString + "\"");
88 return Command::Result::NOT_FOUND;
89 }
90 }
91
tryExecuteServerCommand(const std::vector<std::string> & args,GameMap & gameMap)92 Command::Result ConsoleInterface::tryExecuteServerCommand(const std::vector<std::string>& args, GameMap& gameMap)
93 {
94 if(args.empty())
95 {
96 print("Invalid empty command");
97 return Command::Result::INVALID_ARGUMENT;
98 }
99 const std::string& commandName = args[0];
100 auto it = mCommandMap.find(commandName);
101 if(it != mCommandMap.end())
102 {
103 // Check if there is something to do on client side
104 try
105 {
106 Command::Result result = Command::Result::SUCCESS;
107 result = it->second->executeServer(args, *this, gameMap);
108
109 return result;
110 }
111 catch(const std::invalid_argument& e)
112 {
113 print(std::string("Invalid argument: ") + e.what());
114 return Command::Result::INVALID_ARGUMENT;
115 }
116 catch(const std::out_of_range& e)
117 {
118 print(std::string("Argument out of range") + e.what());
119 return Command::Result::INVALID_ARGUMENT;
120 }
121 }
122 else
123 {
124 print("Unknown command: \"" + commandName + "\"");
125 return Command::Result::NOT_FOUND;
126 }
127 }
128
tryCompleteCommand(const String_t & prefix,String_t & completedCmd)129 bool ConsoleInterface::tryCompleteCommand(const String_t& prefix, String_t& completedCmd)
130 {
131 std::vector<const String_t*> matches;
132 for(auto& element : mCommandMap)
133 {
134 if(boost::algorithm::starts_with(element.first, prefix))
135 {
136 matches.push_back(&element.first);
137 }
138 }
139
140 if(matches.empty())
141 return false;
142
143 if(matches.size() == 1)
144 {
145 completedCmd = *(*matches.begin());
146 return true;
147 }
148
149 // There are several matches. We display them
150 for(auto match : matches)
151 {
152 const String_t& str = *match;
153 print(str);
154 }
155 print("\n");
156
157 // We try to complete until there is a difference between the matches in
158 // a way like Linux does
159 completedCmd = prefix;
160 std::size_t index = completedCmd.length();
161 // We take the first entry as reference
162 const String_t& refStr = *(*matches.begin());
163 while(index < refStr.length())
164 {
165 bool isDif = false;
166 for(auto match : matches)
167 {
168 const String_t& str = *match;
169
170 if(str.length() <= index)
171 {
172 isDif = true;
173 break;
174 }
175
176 if(str[index] != refStr[index])
177 {
178 isDif = true;
179 break;
180 }
181 }
182
183 if(isDif)
184 break;
185
186 completedCmd += refStr[index];
187 ++index;
188 }
189 return true;
190 }
191
scrollCommandHistoryPositionUp(const ConsoleInterface::String_t & currentPrompt)192 boost::optional<const ConsoleInterface::String_t&> ConsoleInterface::scrollCommandHistoryPositionUp(const ConsoleInterface::String_t& currentPrompt)
193 {
194 //If the list is empty, or we are at the top, return none
195 if(mCommandHistoryBuffer.empty() || mHistoryPos == mCommandHistoryBuffer.begin())
196 {
197 return boost::none;
198 }
199 else
200 {
201 if(!isInHistory())
202 {
203 mTemporaryCommandString = currentPrompt;
204 }
205 --mHistoryPos;
206 return boost::optional<const String_t&>(*mHistoryPos);
207 }
208 }
209
scrollCommandHistoryPositionDown()210 boost::optional<const ConsoleInterface::String_t&> ConsoleInterface::scrollCommandHistoryPositionDown()
211 {
212 //If we are at the start, don't scroll
213 if(!isInHistory())
214 {
215 return boost::none;
216 }
217 else
218 {
219 ++mHistoryPos;
220 if(mHistoryPos == mCommandHistoryBuffer.end())
221 {
222 return boost::optional<const String_t&>(mTemporaryCommandString);
223 }
224 return boost::optional<const String_t&>(*mHistoryPos);
225 }
226 }
227
getCommandDescription(const String_t & command)228 boost::optional<const ConsoleInterface::String_t&> ConsoleInterface::getCommandDescription(const String_t& command)
229 {
230 auto it = mCommandMap.find(command);
231 if(it != mCommandMap.end())
232 {
233 return boost::optional<const String_t&>(it->second->getDescription());
234 }
235 else
236 {
237 return boost::none;
238 }
239 }
240
helpCommand(const Command::ArgumentList_t & args,ConsoleInterface & console,AbstractModeManager &)241 Command::Result ConsoleInterface::helpCommand(const Command::ArgumentList_t& args, ConsoleInterface& console, AbstractModeManager&)
242 {
243 if(args.size() > 1)
244 {
245 auto result = console.getCommandDescription(args[1]);
246 if(result)
247 {
248 console.print("Help for command \"" + args[1] + "\":\n");
249 console.print(*result + "\n");
250 }
251 else
252 {
253 console.print("Help for command \"" + args[1] + "\" not found!\n");
254 }
255 }
256 else
257 {
258 console.print("Commands:\n");
259 for(auto& it : console.mCommandMap)
260 {
261 console.print(it.first);
262 }
263 console.print("\n");
264 }
265 return Command::Result::SUCCESS;
266 }
267