The Battle for Wesnoth  1.15.2+dev
map_command_handler.hpp
Go to the documentation of this file.
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"
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.
32 {
33 public:
35  str_(""),
36  args(1, 0),
37  args_end(false)
38  {
39  }
40 
41  explicit cmd_arg_parser(const std::string& str) :
42  str_(str),
43  args(1, 0),
44  args_end(false)
45  {
46  }
47 
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 
56  const std::string& get_str() const
57  {
58  return str_;
59  }
60  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  }
70  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  }
82  std::string get_cmd() const
83  {
84  return get_arg(0);
85  }
86 private:
89  void advance_to_arg(unsigned n) const
90  {
91  while (n < args.size() && !args_end) {
92  std::size_t first_space = str_.find_first_of(' ', args.back());
93  std::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<std::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>
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; //< see implementation of get_command_flags_description() for meaning of flags
131  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  }
136  bool has_flag(const char f) const
137  {
138  return flags.find(f) != flags.npos;
139  }
140  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 
149  map_command_handler() : cap_("")
150  {
151  }
152 
153  virtual ~map_command_handler() {}
154 
155  bool empty() const
156  {
157  return command_map_.empty();
158  }
159  //actual work function
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 
199  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
208  void command_failed(const std::string& message, bool = false)
209  {
210  print(get_cmd(), _("Error:") + std::string(" ") + message);
211  }
212 protected:
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
229  virtual std::string get_flags_description() const
230  {
231  return "";
232  }
233  //this should return a string describing the flags of the given command
234  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()
240  virtual bool is_enabled(const command& /*c*/) const
241  {
242  return true;
243  }
244  virtual void parse_cmd(const std::string& cmd_string)
245  {
246  cap_.parse(cmd_string);
247  }
248  //safe n-th argunment getter
249  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
254  virtual std::string get_data(unsigned argn = 1) const
255  {
256  return cap_.get_data(argn);
257  }
258  virtual std::string get_cmd() const
259  {
260  return cap_.get_cmd();
261  }
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  }
268  void print_usage()
269  {
270  help_command(get_cmd());
271  }
272  //take aliases into account
273  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  }
278  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  }
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  }
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.
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  }
350 protected:
351  //show a "try help" message on unknown command?
352  static void set_help_on_unknown(bool value)
353  {
354  help_on_unknown_ = value;
355  }
356  //this is display-only
357  static void set_cmd_prefix(const std::string& value)
358  {
359  cmd_prefix_ = value;
360  }
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 
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.
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>
402 
403 template <class Worker>
405 
406 template <class Worker>
408 
409 template <class Worker>
411 
412 template <class Worker>
414 
415 }
void command_failed(const std::string &message, bool=false)
std::string get_arg(unsigned n) const
void parse(const std::string &str)
std::map< std::string, t_string > string_map
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
virtual std::string get_data(unsigned argn=1) const
std::vector< std::string > get_commands_list() const
cmd_arg_parser & operator=(const cmd_arg_parser &)
static void set_cmd_prefix(const std::string &value)
std::string get_data(unsigned n) const
virtual std::string get_cmd() const
#define h
virtual std::string get_arg(unsigned argn) const
Definitions for the interface to Wesnoth Markup Language (WML).
const std::string & get_str() const
cmd_arg_parser(const std::string &str)
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:91
void advance_to_arg(unsigned n) const
virtual void register_alias(const std::string &to_cmd, const std::string &cmd)
virtual bool is_enabled(const command &) const
const command * get_command(const std::string &cmd) const
virtual std::string get_flags_description() const
static UNUSEDNOWARN std::string _n(const char *str1, const char *str2, int n)
Definition: gettext.hpp:95
std::vector< std::size_t > args
virtual std::string get_command_flags_description(const command &) const
std::map< std::string, std::string > command_alias_map
static void print(std::stringstream &sstr, const std::string &queue, const std::string &id)
Definition: fire_event.cpp:29
std::size_t i
Definition: function.cpp:933
std::map< std::string, command > command_map
command * get_command(const std::string &cmd)
mock_party p
static const std::vector< std::string > get_aliases(const std::string &cmd)
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
Handling of system events.
Definition: manager.hpp:41
static void set_help_on_unknown(bool value)
#define f
static command_alias_map command_alias_map_
Definition: help.cpp:55
bool help_command(const std::string &acmd)
virtual void parse_cmd(const std::string &cmd_string)
virtual void register_command(const std::string &cmd, command_handler h, const std::string &help="", const std::string &usage="", const std::string &flags="")
mock_char c
std::string get_cmd() const
static map_location::DIRECTION n
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
std::string get_actual_cmd(const std::string &cmd) const
command(command_handler h, const std::string &help="", const std::string &usage="", const std::string &flags="")