The Battle for Wesnoth  1.17.4+dev
map_command_handler.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2022
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  std::string string_user = get_cmd();
194  int distance = 0;
195  // Minimum length of the two compared strings.
196  int len_min = 0;
197  bool has_command_proposal = false;
198  // Compare the input with every command (excluding alias).
199  for(const auto& [key, index] : command_map_) {
200  // No need to test commands that are not enabled.
201  if(is_enabled(index)) {
202  distance = edit_distance_approx(string_user, key);
203  len_min = std::min(string_user.length(), key.length());
204  // Maximum of a third of the letters are wrong. The ratio
205  // between the edit distance and the minimum length, multiplied
206  // by a hundred gives us the percentage of errors.
207  if(distance * 100 / len_min < 34) {
208  symbols["command_proposal"] = key;
209  has_command_proposal = true;
210  // If a good enough candidate is found, exit the loop.
211  break;
212  }
213  }
214  }
215  symbols["command"] = get_cmd();
216  symbols["help_command"] = cmd_prefix_ + "help";
217  // If a proposal for a command is found, print it
218  if(has_command_proposal) {
219  print("help", VGETTEXT("Unknown command '$command', did you mean '$command_proposal'? try $help_command "
220  "for a list of available commands.", symbols));
221  }
222  else {
223  print("help", VGETTEXT("Unknown command '$command', try $help_command "
224  "for a list of available commands.", symbols));
225  }
226  }
227  }
228 
229  std::vector<std::string> get_commands_list() const
230  {
231  std::vector<std::string> res;
232  for (typename command_map::value_type i : command_map_) {
233  res.push_back(i.first);
234  }
235  return res;
236  }
237  //command error reporting shorthands
238  void command_failed(const std::string& message, bool = false)
239  {
240  print(get_cmd(), _("Error:") + std::string(" ") + message);
241  }
242 protected:
244  {
245  register_command("help", &map_command_handler<Worker>::help,
246  _("Available commands list and command-specific help. "
247  "Use \"help all\" to include currently unavailable commands."),
248  // TRANSLATORS: These are the arguments accepted by the "help" command,
249  // which are either "all" or the name of another command.
250  // As with the command's name, "all" is hardcoded, and shouldn't change in the translation.
251  _("[all|<command>]\n“all” = overview of all commands, <command> = name of a specific command (provides more detail)"));
252  }
253  //derived classes initialize the map overriding this function
254  virtual void init_map() = 0;
255  //overridden in derived classes to actually print the messages somwehere
256  virtual void print(const std::string& title, const std::string& message) = 0;
257  //should be overridden in derived classes if the commands have flags
258  //this should return a string describing what all the flags mean
259  virtual std::string get_flags_description() const
260  {
261  return "";
262  }
263  //this should return a string describing the flags of the given command
264  virtual std::string get_command_flags_description(const command& /*c*/) const
265  {
266  return "";
267  }
268  //this should be overridden if e.g. flags are used to control command
269  //availability. Return false if the command should not be executed by dispatch()
270  virtual bool is_enabled(const command& /*c*/) const
271  {
272  return true;
273  }
274  virtual void parse_cmd(const std::string& cmd_string)
275  {
276  cap_.parse(cmd_string);
277  }
278  //safe n-th argunment getter
279  virtual std::string get_arg(unsigned argn) const
280  {
281  return cap_.get_arg(argn);
282  }
283  //"data" is n-th arg and everything after it
284  virtual std::string get_data(unsigned argn = 1) const
285  {
286  return cap_.get_data(argn);
287  }
288  virtual std::string get_cmd() const
289  {
290  return cap_.get_cmd();
291  }
292  void command_failed_need_arg(int argn)
293  {
294  utils::string_map symbols;
295  symbols["arg_id"] = std::to_string(argn);
296  command_failed(VGETTEXT("Missing argument $arg_id", symbols));
297  }
298  void print_usage()
299  {
300  help_command(get_cmd());
301  }
302  //take aliases into account
303  std::string get_actual_cmd(const std::string& cmd) const
304  {
305  command_alias_map::const_iterator i = command_alias_map_.find(cmd);
306  return i != command_alias_map_.end() ? i->second : cmd;
307  }
308  const command* get_command(const std::string& cmd) const
309  {
310  typename command_map::const_iterator i = command_map_.find(cmd);
311  return i != command_map_.end() ? &i->second : 0;
312  }
313  command* get_command(const std::string& cmd)
314  {
315  typename command_map::iterator i = command_map_.find(cmd);
316  return i != command_map_.end() ? &i->second : 0;
317  }
318  void help()
319  {
320  //print command-specific help if available, otherwise list commands
321  if (help_command(get_arg(1))) {
322  return;
323  }
324  std::stringstream ss;
325  bool show_unavail = show_unavailable_ || get_arg(1) == "all";
326  for (typename command_map::value_type i : command_map_) {
327  if (show_unavail || is_enabled(i.second)) {
328  ss << i.first;
329  //if (!i.second.usage.empty()) {
330  // ss << " " << i.second.usage;
331  //}
332  //uncomment the above to display usage information in command list
333  //which might clutter it somewhat
334  if (!i.second.flags.empty()) {
335  ss << " (" << i.second.flags << ") ";
336  }
337  ss << "; ";
338  }
339  }
340  utils::string_map symbols;
341  symbols["flags_description"] = get_flags_description();
342  symbols["list_of_commands"] = ss.str();
343  symbols["help_command"] = cmd_prefix_ + "help";
344  print(_("help"), VGETTEXT("Available commands $flags_description:\n$list_of_commands", symbols));
345  print(_("help"), VGETTEXT("Type $help_command <command> for more info.", symbols));
346  }
347  //returns true if the command exists.
348  bool help_command(const std::string& acmd)
349  {
350  std::string cmd = get_actual_cmd(acmd);
351  const command* c = get_command(cmd);
352  if (c) {
353  std::stringstream ss;
354  ss << cmd_prefix_ << cmd;
355  if (c->help.empty() && c->usage.empty()) {
356  ss << _(" No help available.");
357  }
358  else {
359  ss << " - " << c->help << "\n";
360  }
361  if (!c->usage.empty()) {
362  ss << _("Usage:") << " " << cmd_prefix_ << cmd << " " << c->usage << "\n";
363  }
364  const auto flags_description = get_command_flags_description(*c);
365  if (!flags_description.empty()) {
366  // This shares the objectives dialog's translation of "Notes:"
367  ss << _("Notes:") << " " << get_command_flags_description(*c) << "\n";
368  }
369  const std::vector<std::string> l = get_aliases(cmd);
370  if (!l.empty()) {
371  // TRANSLATORS: alternative names for command-line commands, only shown if
372  // there is at least one of them.
373  ss << _n("command^Alias:", "Aliases:", l.size()) << " " << utils::join(l, " ") << "\n";
374  }
375  print(_("help"), ss.str());
376  }
377  return c != 0;
378  }
380 protected:
381  //show a "try help" message on unknown command?
382  static void set_help_on_unknown(bool value)
383  {
384  help_on_unknown_ = value;
385  }
386  //this is display-only
387  static void set_cmd_prefix(const std::string& value)
388  {
389  cmd_prefix_ = value;
390  }
391  virtual void register_command(const std::string& cmd,
392  command_handler h, const std::string& help = "",
393  const std::string& usage = "", const std::string& flags = "")
394  {
395  command c = command(h, help, usage, flags);
396  std::pair<typename command_map::iterator, bool> r;
397  r = command_map_.insert(typename command_map::value_type(cmd, c));
398  if (!r.second) { //overwrite if exists
399  r.first->second = c;
400  }
401  }
402 
403  virtual void register_alias(const std::string& to_cmd,
404  const std::string& cmd)
405  {
406  command_alias_map_[cmd] = to_cmd;
407  }
408 
409  //get all aliases of a command.
410  static const std::vector<std::string> get_aliases(const std::string& cmd)
411  {
412  std::vector<std::string> aliases;
413  typedef command_alias_map::value_type p;
414  for (p i : command_alias_map_) {
415  if (i.second == cmd) {
416  aliases.push_back(i.first);
417  }
418  }
419  return aliases;
420  }
421 private:
422  static inline command_map command_map_ {};
423  static inline command_alias_map command_alias_map_ {};
424  static inline bool help_on_unknown_ = true;
425  static inline bool show_unavailable_ = false;
426  static inline std::string cmd_prefix_ {};
427 };
428 
429 }
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
int edit_distance_approx(const std::string &str_1, const std::string &str_2)
Approximately calculates the distance between two strings.
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)
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
#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="")