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