The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
map_command_handler.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2006 - 2025
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"
21#include "gettext.hpp"
22
23#include <boost/algorithm/string.hpp>
24
25namespace 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{
34public:
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 {
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 {
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 }
87private:
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()
121template <class Worker>
123{
124public:
125 typedef void (Worker::*command_handler)();
126 struct command
127 {
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 = "")
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
151 {
152 }
153
155
156 bool empty() const
157 {
158 return command_map_.empty();
159 }
160 //actual work function
161 bool dispatch(std::string cmd)
162 {
163 if (empty()) {
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 false;
181 }
182
183 if (const command* c = get_command(get_cmd())) {
184 if (is_enabled(*c)) {
185 (static_cast<Worker*>(this)->*(c->handler))();
186 return true;
187 }
188 else {
189 print(get_cmd(), _("This command is currently unavailable."));
190 return false;
191 }
192 }
193
194 utils::string_map symbols;
195 if(!cmd_flag_) {
196 symbols["help_command"] = cmd_prefix_ + "help";
197 symbols["command"] = cmd_prefix_ + get_cmd();
198 }
199 else {
200 symbols["help_command"] = "help";
201 symbols["command"] = get_cmd();
202 }
203 std::string string_user = get_cmd();
204 int distance = 0;
205 // Minimum length of the two compared strings.
206 int len_min = 0;
207 bool has_command_proposal = false;
208 // Compare the input with every command (excluding alias).
209 for(const auto& [key, index] : command_map_) {
210 // No need to test commands that are not enabled.
211 if(is_enabled(index)) {
212 distance = edit_distance_approx(string_user, key);
213 len_min = std::min(string_user.length(), key.length());
214 // Maximum of a third of the letters are wrong. The ratio
215 // between the edit distance and the minimum length, multiplied
216 // by a hundred gives us the percentage of errors.
217 if(distance * 100 / len_min < 34) {
218 symbols["command_proposal"] = key;
219 has_command_proposal = true;
220 // If a good enough candidate is found, exit the loop.
221 break;
222 }
223 }
224 }
225 // If a proposal for a command is found, print it
226 if(has_command_proposal) {
227 print("help", VGETTEXT("Unknown command ‘$command’, did you mean ‘$command_proposal’? try $help_command "
228 "for a list of available commands.", symbols));
229 }
230 else {
231 print("help", VGETTEXT("Unknown command ‘$command’, try $help_command "
232 "for a list of available commands.", symbols));
233 }
234 return false;
235 }
236
237 std::vector<std::string> get_commands_list() const
238 {
239 std::vector<std::string> res;
240 for (typename command_map::value_type i : command_map_) {
241 res.push_back(i.first);
242 }
243 return res;
244 }
245 //command error reporting shorthands
246 void command_failed(const std::string& message, bool = false)
247 {
248 print(get_cmd(), _("Error:") + std::string(" ") + message);
249 }
250protected:
252 {
254 _("Available commands list and command-specific help. "
255 "Use \"help all\" to include currently unavailable commands."),
256 // TRANSLATORS: These are the arguments accepted by the "help" command,
257 // which are either "all" or the name of another command.
258 // As with the command's name, "all" is hardcoded, and shouldn't change in the translation.
259 _("[all|<command>]\n“all” = overview of all commands, <command> = name of a specific command (provides more detail)"));
260 }
261 //derived classes initialize the map overriding this function
262 virtual void init_map() = 0;
263 //overridden in derived classes to actually print the messages somwehere
264 virtual void print(const std::string& title, const std::string& message) = 0;
265 //should be overridden in derived classes if the commands have flags
266 //this should return a string describing what all the flags mean
267 virtual std::string get_flags_description() const
268 {
269 return "";
270 }
271 //this should return a string describing the flags of the given command
272 virtual std::string get_command_flags_description(const command& /*c*/) const
273 {
274 return "";
275 }
276 //this should be overridden if e.g. flags are used to control command
277 //availability. Return false if the command should not be executed by dispatch()
278 virtual bool is_enabled(const command& /*c*/) const
279 {
280 return true;
281 }
282 virtual void parse_cmd(const std::string& cmd_string)
283 {
284 cap_.parse(cmd_string);
285 }
286 //safe n-th argunment getter
287 virtual std::string get_arg(unsigned argn) const
288 {
289 return cap_.get_arg(argn);
290 }
291 //"data" is n-th arg and everything after it
292 virtual std::string get_data(unsigned argn = 1) const
293 {
294 return cap_.get_data(argn);
295 }
296 virtual std::string get_cmd() const
297 {
298 return cap_.get_cmd();
299 }
301 {
302 utils::string_map symbols;
303 symbols["arg_id"] = std::to_string(argn);
304 command_failed(VGETTEXT("Missing argument $arg_id", symbols));
305 }
307 {
309 }
310 //take aliases into account
311 std::string get_actual_cmd(const std::string& cmd) const
312 {
313 command_alias_map::const_iterator i = command_alias_map_.find(cmd);
314 return i != command_alias_map_.end() ? i->second : cmd;
315 }
316 const command* get_command(const std::string& cmd) const
317 {
318 typename command_map::const_iterator i = command_map_.find(cmd);
319 return i != command_map_.end() ? &i->second : 0;
320 }
321 command* get_command(const std::string& cmd)
322 {
323 typename command_map::iterator i = command_map_.find(cmd);
324 return i != command_map_.end() ? &i->second : 0;
325 }
326 void help()
327 {
328 //print command-specific help if available, otherwise list commands
329 if (help_command(get_arg(1))) {
330 return;
331 }
332 std::stringstream ss;
333 bool show_unavail = show_unavailable_ || get_arg(1) == "all";
334 for (typename command_map::value_type i : command_map_) {
335 if (show_unavail || is_enabled(i.second)) {
336 ss << i.first;
337 //if (!i.second.usage.empty()) {
338 // ss << " " << i.second.usage;
339 //}
340 //uncomment the above to display usage information in command list
341 //which might clutter it somewhat
342 if (!i.second.flags.empty()) {
343 ss << " (" << i.second.flags << ") ";
344 }
345 ss << "; ";
346 }
347 }
348 utils::string_map symbols;
349 symbols["flags_description"] = get_flags_description();
350 symbols["list_of_commands"] = ss.str();
351 if(!cmd_flag_) {
352 symbols["help_command"] = cmd_prefix_ + "help";
353 }
354 else {
355 symbols["help_command"] = "help";
356 }
357 print(_("help"), VGETTEXT("Available commands $flags_description:\n$list_of_commands", symbols));
358 print(_("help"), VGETTEXT("Type $help_command <command> for more info.", symbols));
359 }
360 //returns true if the command exists.
361 bool help_command(const std::string& acmd)
362 {
363 std::string cmd = get_actual_cmd(acmd);
364 const command* c = get_command(cmd);
365 if (c) {
366 std::stringstream ss;
367 if(!cmd_flag_) {
368 ss << cmd_prefix_ << cmd;
369 }
370 else {
371 ss << cmd;
372 }
373 if (c->help.empty() && c->usage.empty()) {
374 ss << _(" No help available.");
375 }
376 else {
377 ss << " - " << c->help << "\n";
378 }
379 if (!c->usage.empty()) {
380 if(!cmd_flag_) {
381 ss << _("Usage:") << " " << cmd_prefix_ << cmd << " " << c->usage << "\n";
382 }
383 else {
384 ss << _("Usage:") << " " << cmd << " " << c->usage << "\n";
385 }
386 }
387 const auto flags_description = get_command_flags_description(*c);
388 if (!flags_description.empty()) {
389 // This shares the objectives dialog's translation of "Notes:"
390 ss << _("Notes:") << " " << get_command_flags_description(*c) << "\n";
391 }
392 const std::vector<std::string> l = get_aliases(cmd);
393 if (!l.empty()) {
394 // TRANSLATORS: alternative names for command-line commands, only shown if
395 // there is at least one of them.
396 ss << _n("command^Alias:", "Aliases:", l.size()) << " " << utils::join(l, " ") << "\n";
397 }
398 print(_("help"), ss.str());
399 }
400 return c != 0;
401 }
403protected:
404 //this is display-only
405 static void set_cmd_prefix(const std::string& value)
406 {
408 }
409 //sets the "cmd_flag_" flag as "true" when "cmd_prefix_" is in command
410 //line mode and to "false" when "cmd_prefix_" is in message line mode.
411 //The "help" message's symbols depend on the flag's value.
412 static void set_cmd_flag(bool value)
413 {
415 }
416 virtual void register_command(const std::string& cmd,
417 command_handler h, const std::string& help = "",
418 const std::string& usage = "", const std::string& flags = "")
419 {
420 command c = command(h, help, usage, flags);
421 std::pair<typename command_map::iterator, bool> r;
422 r = command_map_.insert(typename command_map::value_type(cmd, c));
423 if (!r.second) { //overwrite if exists
424 r.first->second = c;
425 }
426 }
427
428 virtual void register_alias(const std::string& to_cmd,
429 const std::string& cmd)
430 {
431 command_alias_map_[cmd] = to_cmd;
432 }
433
434 //get all aliases of a command.
435 static const std::vector<std::string> get_aliases(const std::string& cmd)
436 {
437 std::vector<std::string> aliases;
438 typedef command_alias_map::value_type p;
439 for (p i : command_alias_map_) {
440 if (i.second == cmd) {
441 aliases.push_back(i.first);
442 }
443 }
444 return aliases;
445 }
446private:
447 static inline command_map command_map_ {};
449 static inline bool show_unavailable_ = false;
450 static inline std::string cmd_prefix_ {};
451 static inline bool cmd_flag_ = false;
452};
453
454}
void parse(const std::string &str)
std::string get_arg(unsigned n) const
cmd_arg_parser(const cmd_arg_parser &)
void advance_to_arg(unsigned n) const
std::vector< std::size_t > args
std::string get_data(unsigned n) const
std::string get_cmd() const
cmd_arg_parser(const std::string &str)
cmd_arg_parser & operator=(const cmd_arg_parser &)
const std::string & get_str() const
std::map< std::string, std::string > command_alias_map
virtual std::string get_flags_description() const
static const std::vector< std::string > get_aliases(const std::string &cmd)
virtual void register_command(const std::string &cmd, command_handler h, const std::string &help="", const std::string &usage="", const std::string &flags="")
std::map< std::string, command > command_map
std::vector< std::string > get_commands_list() const
virtual std::string get_command_flags_description(const command &) const
virtual std::string get_arg(unsigned argn) const
virtual std::string get_cmd() const
static void set_cmd_prefix(const std::string &value)
virtual bool is_enabled(const command &) const
virtual std::string get_data(unsigned argn=1) const
static void set_cmd_flag(bool value)
command * get_command(const std::string &cmd)
virtual void init_map()=0
virtual void parse_cmd(const std::string &cmd_string)
void command_failed(const std::string &message, bool=false)
bool help_command(const std::string &acmd)
static command_alias_map command_alias_map_
std::string get_actual_cmd(const std::string &cmd) const
const command * get_command(const std::string &cmd) const
virtual void register_alias(const std::string &to_cmd, const std::string &cmd)
virtual void print(const std::string &title, const std::string &message)=0
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t edit_distance_approx(std::string_view str_1, std::string_view str_2) noexcept
Calculate the approximate edit distance of two strings.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _n(const char *str1, const char *str2, int n)
Definition gettext.hpp:104
static std::string _(const char *str)
Definition gettext.hpp:100
Handling of system events.
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::map< std::string, t_string > string_map
std::string_view data
Definition picture.cpp:188
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
command(command_handler h, const std::string &help="", const std::string &usage="", const std::string &flags="")
mock_party p
static map_location::direction n
#define i
#define h
#define f
#define c