1 /* libobby - Network text editing library
2  * Copyright (C) 2005 0x539 dev group
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (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 GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 
19 #include "command.hpp"
20 
21 namespace
22 {
unescape(std::string & result)23 	void unescape(std::string& result)
24 	{
25 		std::string::size_type pos = 0;
26 		while((pos = result.find('\\', pos)) != std::string::npos)
27 		{
28 			// get_text_param ensures that there is no backslash
29 			// at the end of the string
30 			switch(result[pos + 1])
31 			{
32 			case 'n':
33 				result.replace(pos, 2, 1, '\n');
34 				break;
35 			case '\'':
36 			case '\"':
37 			case '\\':
38 				result.erase(pos, 1);
39 				break;
40 			default:
41 				throw std::logic_error(
42 					"obby::command.cpp::unescape:\n"
43 					"Encountered invalid escape sequence"
44 				);
45 			}
46 
47 			++ pos;
48 		}
49 	}
50 
get_next_param(const std::string & list,std::string::size_type pos,std::string & result)51 	std::string::size_type get_next_param(const std::string& list,
52 	                                      std::string::size_type pos,
53 	                                      std::string& result)
54 	{
55 		std::string::size_type i = pos;
56 
57 		while(i < list.length() && isspace(list[i]) )
58 			++ i;
59 
60 		if(i == list.length() )
61 			return std::string::npos;
62 
63 		char str_char = '\0';
64 		if(list[i] == '\"' || list[i] == '\'')
65 		{
66 			str_char = list[i];
67 			++ i;
68 		}
69 
70 		pos = i;
71 		bool escape_flag = false;
72 
73 		for(; i < list.length(); ++ i)
74 		{
75 			if(escape_flag)
76 			{
77 				escape_flag = false;
78 			}
79 			else
80 			{
81 				if(list[i] == '\\')
82 					escape_flag = true;
83 
84 				if(str_char == '\0' && isspace(list[i]) )
85 					break;
86 
87 				if(str_char != '\0' && list[i] == str_char)
88 					break;
89 			}
90 		}
91 
92 		if(escape_flag)
93 		{
94 			throw std::logic_error(
95 				"Escaping backslash at end of line"
96 			);
97 		}
98 
99 		if(i == list.length() && str_char != '\0')
100 		{
101 			throw std::logic_error(
102 				"String not closed"
103 			);
104 		}
105 
106 		if(str_char != '\0')
107 		{
108 			result = list.substr(pos, i - pos);
109 			pos = i + 1;
110 		}
111 		else
112 		{
113 			result = list.substr(pos, i - pos);
114 			pos = i;
115 		}
116 
117 		unescape(result);
118 		return pos;
119 	}
120 }
121 
command_query(const std::string & command,const std::string & paramlist)122 obby::command_query::command_query(const std::string& command,
123                                    const std::string& paramlist):
124 	m_command(command), m_paramlist(paramlist)
125 {
126 }
127 
command_query(const net6::packet & pack,unsigned int & index)128 obby::command_query::command_query(const net6::packet& pack,
129                                    unsigned int& index):
130 	m_command(pack.get_param(index).as<std::string>() ),
131 	m_paramlist(pack.get_param(index + 1).as<std::string>() )
132 {
133 	index += 2;
134 }
135 
get_command() const136 const std::string& obby::command_query::get_command() const
137 {
138 	return m_command;
139 }
140 
get_paramlist() const141 const std::string& obby::command_query::get_paramlist() const
142 {
143 	return m_paramlist;
144 }
145 
append_packet(net6::packet & pack) const146 void obby::command_query::append_packet(net6::packet& pack) const
147 {
148 	pack << m_command << m_paramlist;
149 }
150 
command_result()151 obby::command_result::command_result():
152 	m_type(NO_REPLY)
153 {
154 }
155 
command_result(type type,const std::string & reply)156 obby::command_result::command_result(type type, const std::string& reply):
157 	m_type(type), m_reply(reply)
158 {
159 	if(type != REPLY && !reply.empty() )
160 	{
161 		throw std::logic_error(
162 			"obby::command_result::command_result:\n"
163 			"Result type is not reply, but reply string "
164 			"is nonempty"
165 		);
166 	}
167 }
168 
169 // Reply is only packed when type == REPLY
command_result(const net6::packet & pack,unsigned int & index)170 obby::command_result::command_result(const net6::packet& pack,
171                                      unsigned int& index):
172 	m_type(static_cast<type>(pack.get_param(index).as<unsigned int>()) ),
173 	m_reply(
174 		(m_type != REPLY) ?
175 		("") :
176 		pack.get_param(index + 1).as<std::string>()
177 	)
178 {
179 	++ index;
180 	if(m_type == REPLY) ++ index;
181 }
182 
get_type() const183 obby::command_result::type obby::command_result::get_type() const
184 {
185 	return m_type;
186 }
187 
get_reply() const188 const std::string& obby::command_result::get_reply() const
189 {
190 	return m_reply;
191 }
192 
append_packet(net6::packet & pack) const193 void obby::command_result::append_packet(net6::packet& pack) const
194 {
195 	pack << static_cast<unsigned int>(m_type);
196 	if(m_type == REPLY) pack << m_reply;
197 }
198 
command_paramlist(const std::string & list)199 obby::command_paramlist::command_paramlist(const std::string& list)
200 {
201 	std::string param;
202 	std::string::size_type pos = 0;
203 	while((pos = get_next_param(list, pos, param)) != std::string::npos)
204 		m_params.push_back(param);
205 }
206 
count() const207 obby::command_paramlist::size_type obby::command_paramlist::count() const
208 {
209 	return m_params.size();
210 }
211 
value(unsigned int index) const212 const std::string& obby::command_paramlist::value(unsigned int index) const
213 {
214 	return m_params.at(index);
215 }
216 
command_map()217 obby::command_map::command_map()
218 {
219 	add_command(
220 		"help",
221 		_("Shows all available commands"),
222 		sigc::mem_fun(*this, &command_map::on_help)
223 	);
224 }
225 
add_command(const std::string & name,const std::string & desc,const slot_type & func)226 void obby::command_map::add_command(const std::string& name,
227                                     const std::string& desc,
228                                     const slot_type& func)
229 {
230 	if(m_map.get() == NULL) m_map.reset(new map_type);
231 
232 	map_type::const_iterator iter = m_map->find(name);
233 	if(iter != m_map->end() )
234 	{
235 		throw std::logic_error(
236 			"obby::command_map::add_command:\n"
237 			"Command exists already"
238 		);
239 	}
240 
241 	command comm = { name, desc, func };
242 	(*m_map)[name] = comm;
243 }
244 
245 obby::command_result obby::command_map::
exec_command(const user & from,const command_query & query) const246 	exec_command(const user& from,
247                      const command_query& query) const
248 {
249 	if(m_map.get() == NULL) return command_result::NOT_FOUND;
250 	map_type::const_iterator iter = m_map->find(query.get_command() );
251 	if(iter == m_map->end() ) return command_result::NOT_FOUND;
252 	return iter->second.func(from, query.get_paramlist());
253 }
254 
on_help(const user & from,const std::string & paramlist)255 obby::command_result obby::command_map::on_help(const user& from,
256                                                 const std::string& paramlist)
257 {
258 	std::string reply;
259 
260 	for(map_type::const_iterator iter = m_map->begin();
261 	    iter != m_map->end();
262 	    ++ iter)
263 	{
264 		reply += iter->second.name;
265 		reply += ' ';
266 		reply += iter->second.desc;
267 		reply += '\n';
268 	}
269 
270 	return command_result(command_result::REPLY, reply);
271 }
272 
command_queue()273 obby::command_queue::command_queue():
274 	m_map(new map_type)
275 {
276 	result_event("help").connect(
277 		sigc::mem_fun(*this, &command_queue::on_help)
278 	);
279 }
280 
query(const command_query & query)281 void obby::command_queue::query(const command_query& query)
282 {
283 	m_commands.push(query);
284 }
285 
result(const command_result & result)286 void obby::command_queue::result(const command_result& result)
287 {
288 	if(m_commands.empty() )
289 	{
290 		throw std::logic_error(
291 			"obby::command_queue::reply:\n"
292 			"No query in command queue"
293 		);
294 	}
295 
296 	command_query query = m_commands.front();
297 	m_commands.pop();
298 
299 	if(result.get_type() == command_result::NOT_FOUND)
300 		m_signal_query_failed.emit(query);
301 	else
302 		(*m_map)[query.get_command()].emit(query, result);
303 }
304 
clear()305 void obby::command_queue::clear()
306 {
307 	while(!m_commands.empty() )
308 		m_commands.pop();
309 }
310 
311 obby::command_queue::signal_result_type obby::command_queue::
result_event(const std::string & command) const312 	result_event(const std::string& command) const
313 {
314 	return (*m_map)[command];
315 }
316 
317 obby::command_queue::signal_query_failed_type obby::command_queue::
query_failed_event() const318 	query_failed_event() const
319 {
320 	return m_signal_query_failed;
321 }
322 
help_event() const323 obby::command_queue::signal_help_type obby::command_queue::help_event() const
324 {
325 	return m_signal_help;
326 }
327 
on_help(const command_query & query,const command_result & result)328 void obby::command_queue::on_help(const command_query& query,
329                                   const command_result& result)
330 {
331 	const std::string& reply = result.get_reply();
332 	std::string::size_type pos = 0, prev = 0;
333 
334 	while( (pos = reply.find('\n', pos)) != std::string::npos)
335 	{
336 		std::string line = reply.substr(prev, pos - prev);
337 		std::string::size_type sep = line.find(' ');
338 		if(sep == std::string::npos) continue;
339 
340 		m_signal_help.emit(line.substr(0, sep), line.substr(sep + 1));
341 		prev = ++ pos;
342 	}
343 }
344