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