1 /*
2 Copyright (C) 2006 - 2018 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
3 wesnoth playturn Copyright (C) 2003 by David White <dave@whitevine.net>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12 
13 See the COPYING file for more details.
14 */
15 
16 #pragma once
17 #include "config.hpp"
18 #include "serialization/string_utils.hpp"
19 #include "formula/string_utils.hpp"
20 #include "gettext.hpp"
21 
22 #include <boost/algorithm/string.hpp>
23 
24 namespace events {
25 
26 //simple command args parser, separated from command_handler for clarity.
27 //a word begins with a nonspace
28 //n-th arg is n-th word up to the next space
29 //n-th data is n-th word up to the end
30 //cmd is 0-th arg, begins at 0 always.
31 class cmd_arg_parser
32 {
33 public:
cmd_arg_parser()34 	cmd_arg_parser() :
35 		str_(""),
36 		args(1, 0),
37 		args_end(false)
38 	{
39 	}
40 
cmd_arg_parser(const std::string & str)41 	explicit cmd_arg_parser(const std::string& str) :
42 		str_(str),
43 		args(1, 0),
44 		args_end(false)
45 	{
46 	}
47 
parse(const std::string & str)48 	void parse(const std::string& str)
49 	{
50 		str_ = str;
51 		args.clear();
52 		args.push_back(0);
53 		args_end = false;
54 	}
55 
get_str() const56 	const std::string& get_str() const
57 	{
58 		return str_;
59 	}
get_arg(unsigned n) const60 	std::string get_arg(unsigned n) const
61 	{
62 		advance_to_arg(n);
63 		if (n < args.size()) {
64 			return std::string(str_, args[n], str_.find(' ', args[n]) - args[n]);
65 		}
66 		else {
67 			return "";
68 		}
69 	}
get_data(unsigned n) const70 	std::string get_data(unsigned n) const
71 	{
72 		advance_to_arg(n);
73 		if (n < args.size()) {
74 			std::string data(str_, args[n]);
75 			boost::trim(data);
76 			return data;
77 		}
78 		else {
79 			return "";
80 		}
81 	}
get_cmd() const82 	std::string get_cmd() const
83 	{
84 		return get_arg(0);
85 	}
86 private:
87 	cmd_arg_parser& operator=(const cmd_arg_parser&);
88 	cmd_arg_parser(const cmd_arg_parser&);
advance_to_arg(unsigned n) const89 	void advance_to_arg(unsigned n) const
90 	{
91 		while (n < args.size() && !args_end) {
92 			size_t first_space = str_.find_first_of(' ', args.back());
93 			size_t next_arg_begin = str_.find_first_not_of(' ', first_space);
94 			if (next_arg_begin != std::string::npos) {
95 				args.push_back(next_arg_begin);
96 			}
97 			else {
98 				args_end = true;
99 			}
100 		}
101 	}
102 	std::string str_;
103 	mutable std::vector<size_t> args;
104 	mutable bool args_end;
105 };
106 
107 
108 //A helper class template with a slim public interface
109 //This represents a map of strings to void()-member-function-of-Worker-pointers
110 //with all the common functionality like general help, command help and aliases
111 //Usage (of a derived class): Derived(specific-arguments) d; d.dispatch(command);
112 //Derived classes should override virtual functions where noted.
113 //The template parameter currently must be the dervived class itself,
114 //i.e. class X : public map_command_handler<X>
115 //To add a new command in a derived class:
116 //  * add a new private void function() to the derived class
117 //  * add it to the function map in init_map there, setting flags like
118 //    "D" for debug only (checking the flag is also done in the derived class)
119 //  * remember to add some help and/or usage information in init_map()
120 template <class Worker>
121 class map_command_handler
122 {
123 public:
124 	typedef void (Worker::*command_handler)();
125 	struct command
126 	{
127 		command_handler handler;
128 		std::string help; //long help text
129 		std::string usage; //only args info
130 		std::string flags;
commandevents::map_command_handler::command131 		explicit command(command_handler h, const std::string help = "",
132 			const std::string& usage = "", const std::string flags = "")
133 			: handler(h), help(help), usage(usage), flags(flags)
134 		{
135 		}
has_flagevents::map_command_handler::command136 		bool has_flag(const char f) const
137 		{
138 			return flags.find(f) != flags.npos;
139 		}
add_flagevents::map_command_handler::command140 		command& add_flag(const char f)
141 		{
142 			flags += f;
143 			return *this;
144 		}
145 	};
146 	typedef std::map<std::string, command> command_map;
147 	typedef std::map<std::string, std::string> command_alias_map;
148 
map_command_handler()149 	map_command_handler() : cap_("")
150 	{
151 	}
152 
~map_command_handler()153 	virtual ~map_command_handler() {}
154 
empty() const155 	bool empty() const
156 	{
157 		return command_map_.empty();
158 	}
159 	//actual work function
dispatch(std::string cmd)160 	void dispatch(std::string cmd)
161 	{
162 		if (empty()) {
163 			init_map_default();
164 			init_map();
165 		}
166 
167 		// We recursively resolve alias (100 max to avoid infinite recursion)
168 		for (int i = 0; i < 100; ++i) {
169 			parse_cmd(cmd);
170 			std::string actual_cmd = get_actual_cmd(get_cmd());
171 			if (actual_cmd == get_cmd())
172 				break;
173 			std::string data = get_data(1);
174 			// translate the command and add space + data if any
175 			cmd = actual_cmd + (data.empty() ? "" : " ") + data;
176 		}
177 
178 		if (get_cmd().empty()) {
179 			return;
180 		}
181 
182 		if (const command* c = get_command(get_cmd())) {
183 			if (is_enabled(*c)) {
184 				(static_cast<Worker*>(this)->*(c->handler))();
185 			}
186 			else {
187 				print(get_cmd(), _("This command is currently unavailable."));
188 			}
189 		}
190 		else if (help_on_unknown_) {
191 			utils::string_map symbols;
192 			symbols["command"] = get_cmd();
193 			symbols["help_command"] = cmd_prefix_ + "help";
194 			print("help", VGETTEXT("Unknown command '$command', try $help_command "
195 				"for a list of available commands.", symbols));
196 		}
197 	}
198 
get_commands_list() const199 	std::vector<std::string> get_commands_list() const
200 	{
201 		std::vector<std::string> res;
202 		for (typename command_map::value_type i : command_map_) {
203 			res.push_back(i.first);
204 		}
205 		return res;
206 	}
207 	//command error reporting shorthands
command_failed(const std::string & message,bool=false)208 	void command_failed(const std::string& message, bool = false)
209 	{
210 		print(get_cmd(), _("Error:") + std::string(" ") + message);
211 	}
212 protected:
init_map_default()213 	void init_map_default()
214 	{
215 		register_command("help", &map_command_handler<Worker>::help,
216 			_("Available commands list and command-specific help. "
217 				"Use \"help all\" to include currently unavailable commands."),
218 			// TRANSLATORS: These are the arguments accepted by the "help" command,
219 			// which are either "all" or the name of another command.
220 			// As with the command's name, "all" is hardcoded, and shouldn't change in the translation.
221 			_("[all|<command>]\n“all” = overview of all commands, <command> = name of a specific command (provides more detail)"));
222 	}
223 	//derived classes initialize the map overriding this function
224 	virtual void init_map() = 0;
225 	//overridden in derived classes to actually print the messages somwehere
226 	virtual void print(const std::string& title, const std::string& message) = 0;
227 	//should be overridden in derived classes if the commands have flags
228 	//this should return a string describing what all the flags mean
get_flags_description() const229 	virtual std::string get_flags_description() const
230 	{
231 		return "";
232 	}
233 	//this should return a string describing the flags of the given command
get_command_flags_description(const command &) const234 	virtual std::string get_command_flags_description(const command& /*c*/) const
235 	{
236 		return "";
237 	}
238 	//this should be overridden if e.g. flags are used to control command
239 	//availability. Return false if the command should not be executed by dispatch()
is_enabled(const command &) const240 	virtual bool is_enabled(const command& /*c*/) const
241 	{
242 		return true;
243 	}
parse_cmd(const std::string & cmd_string)244 	virtual void parse_cmd(const std::string& cmd_string)
245 	{
246 		cap_.parse(cmd_string);
247 	}
248 	//safe n-th argunment getter
get_arg(unsigned argn) const249 	virtual std::string get_arg(unsigned argn) const
250 	{
251 		return cap_.get_arg(argn);
252 	}
253 	//"data" is n-th arg and everything after it
get_data(unsigned argn=1) const254 	virtual std::string get_data(unsigned argn = 1) const
255 	{
256 		return cap_.get_data(argn);
257 	}
get_cmd() const258 	virtual std::string get_cmd() const
259 	{
260 		return cap_.get_cmd();
261 	}
command_failed_need_arg(int argn)262 	void command_failed_need_arg(int argn)
263 	{
264 		utils::string_map symbols;
265 		symbols["arg_id"] = std::to_string(argn);
266 		command_failed(VGETTEXT("Missing argument $arg_id", symbols));
267 	}
print_usage()268 	void print_usage()
269 	{
270 		help_command(get_cmd());
271 	}
272 	//take aliases into account
get_actual_cmd(const std::string & cmd) const273 	std::string get_actual_cmd(const std::string& cmd) const
274 	{
275 		command_alias_map::const_iterator i = command_alias_map_.find(cmd);
276 		return i != command_alias_map_.end() ? i->second : cmd;
277 	}
get_command(const std::string & cmd) const278 	const command* get_command(const std::string& cmd) const
279 	{
280 		typename command_map::const_iterator i = command_map_.find(cmd);
281 		return i != command_map_.end() ? &i->second : 0;
282 	}
get_command(const std::string & cmd)283 	command* get_command(const std::string& cmd)
284 	{
285 		typename command_map::iterator i = command_map_.find(cmd);
286 		return i != command_map_.end() ? &i->second : 0;
287 	}
help()288 	void help()
289 	{
290 		//print command-specific help if available, otherwise list commands
291 		if (help_command(get_arg(1))) {
292 			return;
293 		}
294 		std::stringstream ss;
295 		bool show_unavail = show_unavailable_ || get_arg(1) == "all";
296 		for (typename command_map::value_type i : command_map_) {
297 			if (show_unavail || is_enabled(i.second)) {
298 				ss << i.first;
299 				//if (!i.second.usage.empty()) {
300 				//	ss << " " << i.second.usage;
301 				//}
302 				//uncomment the above to display usage information in command list
303 				//which might clutter it somewhat
304 				if (!i.second.flags.empty()) {
305 					ss << " (" << i.second.flags << ") ";
306 				}
307 				ss << "; ";
308 			}
309 		}
310 		utils::string_map symbols;
311 		symbols["flags_description"] = get_flags_description();
312 		symbols["list_of_commands"] = ss.str();
313 		symbols["help_command"] = cmd_prefix_ + "help";
314 		print(_("help"), VGETTEXT("Available commands $flags_description:\n$list_of_commands", symbols));
315 		print(_("help"), VGETTEXT("Type $help_command <command> for more info.", symbols));
316 	}
317 	//returns true if the command exists.
help_command(const std::string & acmd)318 	bool help_command(const std::string& acmd)
319 	{
320 		std::string cmd = get_actual_cmd(acmd);
321 		const command* c = get_command(cmd);
322 		if (c) {
323 			std::stringstream ss;
324 			ss << cmd_prefix_ << cmd;
325 			if (c->help.empty() && c->usage.empty()) {
326 				ss << _(" No help available.");
327 			}
328 			else {
329 				ss << " - " << c->help << "\n";
330 			}
331 			if (!c->usage.empty()) {
332 				ss << _("Usage:") << " " << cmd_prefix_ << cmd << " " << c->usage << "\n";
333 			}
334 			const auto flags_description = get_command_flags_description(*c);
335 			if (!flags_description.empty()) {
336 				// This shares the objectives dialog's translation of "Notes:"
337 				ss << _("Notes:") << " " << get_command_flags_description(*c) << "\n";
338 			}
339 			const std::vector<std::string> l = get_aliases(cmd);
340 			if (!l.empty()) {
341 				// TRANSLATORS: alternative names for command-line commands, only shown if
342 				// there is at least one of them.
343 				ss << _n("command^Alias:", "Aliases:", l.size()) << " " << utils::join(l, " ") << "\n";
344 			}
345 			print(_("help"), ss.str());
346 		}
347 		return c != 0;
348 	}
349 	cmd_arg_parser cap_;
350 protected:
351 	//show a "try help" message on unknown command?
set_help_on_unknown(bool value)352 	static void set_help_on_unknown(bool value)
353 	{
354 		help_on_unknown_ = value;
355 	}
356 	//this is display-only
set_cmd_prefix(std::string value)357 	static void set_cmd_prefix(std::string value)
358 	{
359 		cmd_prefix_ = value;
360 	}
register_command(const std::string & cmd,command_handler h,const std::string & help="",const std::string & usage="",const std::string & flags="")361 	virtual void register_command(const std::string& cmd,
362 		command_handler h, const std::string& help = "",
363 		const std::string& usage = "", const std::string& flags = "")
364 	{
365 		command c = command(h, help, usage, flags);
366 		std::pair<typename command_map::iterator, bool> r;
367 		r = command_map_.insert(typename command_map::value_type(cmd, c));
368 		if (!r.second) { //overwrite if exists
369 			r.first->second = c;
370 		}
371 	}
372 
register_alias(const std::string & to_cmd,const std::string & cmd)373 	virtual void register_alias(const std::string& to_cmd,
374 		const std::string& cmd)
375 	{
376 		command_alias_map_[cmd] = to_cmd;
377 	}
378 
379 	//get all aliases of a command.
get_aliases(const std::string & cmd)380 	static const std::vector<std::string> get_aliases(const std::string& cmd)
381 	{
382 		std::vector<std::string> aliases;
383 		typedef command_alias_map::value_type p;
384 		for (p i : command_alias_map_) {
385 			if (i.second == cmd) {
386 				aliases.push_back(i.first);
387 			}
388 		}
389 		return aliases;
390 	}
391 private:
392 	static command_map command_map_;
393 	static command_alias_map command_alias_map_;
394 	static bool help_on_unknown_;
395 	static bool show_unavailable_;
396 	static std::string cmd_prefix_;
397 };
398 
399 //static member definitions
400 template <class Worker>
401 typename map_command_handler<Worker>::command_map map_command_handler<Worker>::command_map_;
402 
403 template <class Worker>
404 typename map_command_handler<Worker>::command_alias_map map_command_handler<Worker>::command_alias_map_;
405 
406 template <class Worker>
407 bool map_command_handler<Worker>::help_on_unknown_ = true;
408 
409 template <class Worker>
410 bool map_command_handler<Worker>::show_unavailable_ = false;
411 
412 template <class Worker>
413 std::string map_command_handler<Worker>::cmd_prefix_;
414 
415 }
416