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