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" 18 #include "serialization/string_utils.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. 31 class cmd_arg_parser 32 { 33 public: cmd_arg_parser()34 cmd_arg_parser() : 35 str_(""), 36 args(1, 0), 37 args_end(false) 38 { 39 } 40 cmd_arg_parser(const std::string & str)41 explicit cmd_arg_parser(const std::string& str) : 42 str_(str), 43 args(1, 0), 44 args_end(false) 45 { 46 } 47 parse(const std::string & str)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 get_str() const56 const std::string& get_str() const 57 { 58 return str_; 59 } get_arg(unsigned n) const60 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 } get_data(unsigned n) const70 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 } get_cmd() const82 std::string get_cmd() const 83 { 84 return get_arg(0); 85 } 86 private: 87 cmd_arg_parser& operator=(const cmd_arg_parser&); 88 cmd_arg_parser(const cmd_arg_parser&); advance_to_arg(unsigned n) const89 void advance_to_arg(unsigned n) const 90 { 91 while (n < args.size() && !args_end) { 92 size_t first_space = str_.find_first_of(' ', args.back()); 93 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<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> 121 class map_command_handler 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; commandevents::map_command_handler::command131 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 } has_flagevents::map_command_handler::command136 bool has_flag(const char f) const 137 { 138 return flags.find(f) != flags.npos; 139 } add_flagevents::map_command_handler::command140 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 map_command_handler()149 map_command_handler() : cap_("") 150 { 151 } 152 ~map_command_handler()153 virtual ~map_command_handler() {} 154 empty() const155 bool empty() const 156 { 157 return command_map_.empty(); 158 } 159 //actual work function dispatch(std::string cmd)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 get_commands_list() const199 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 command_failed(const std::string & message,bool=false)208 void command_failed(const std::string& message, bool = false) 209 { 210 print(get_cmd(), _("Error:") + std::string(" ") + message); 211 } 212 protected: init_map_default()213 void init_map_default() 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 get_flags_description() const229 virtual std::string get_flags_description() const 230 { 231 return ""; 232 } 233 //this should return a string describing the flags of the given command get_command_flags_description(const command &) const234 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() is_enabled(const command &) const240 virtual bool is_enabled(const command& /*c*/) const 241 { 242 return true; 243 } parse_cmd(const std::string & cmd_string)244 virtual void parse_cmd(const std::string& cmd_string) 245 { 246 cap_.parse(cmd_string); 247 } 248 //safe n-th argunment getter get_arg(unsigned argn) const249 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 get_data(unsigned argn=1) const254 virtual std::string get_data(unsigned argn = 1) const 255 { 256 return cap_.get_data(argn); 257 } get_cmd() const258 virtual std::string get_cmd() const 259 { 260 return cap_.get_cmd(); 261 } command_failed_need_arg(int argn)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 } print_usage()268 void print_usage() 269 { 270 help_command(get_cmd()); 271 } 272 //take aliases into account get_actual_cmd(const std::string & cmd) const273 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 } get_command(const std::string & cmd) const278 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 } get_command(const std::string & cmd)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 } help()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. help_command(const std::string & acmd)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 } 349 cmd_arg_parser cap_; 350 protected: 351 //show a "try help" message on unknown command? set_help_on_unknown(bool value)352 static void set_help_on_unknown(bool value) 353 { 354 help_on_unknown_ = value; 355 } 356 //this is display-only set_cmd_prefix(std::string value)357 static void set_cmd_prefix(std::string value) 358 { 359 cmd_prefix_ = value; 360 } register_command(const std::string & cmd,command_handler h,const std::string & help="",const std::string & usage="",const std::string & flags="")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 register_alias(const std::string & to_cmd,const std::string & cmd)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. get_aliases(const std::string & cmd)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> 401 typename map_command_handler<Worker>::command_map map_command_handler<Worker>::command_map_; 402 403 template <class Worker> 404 typename map_command_handler<Worker>::command_alias_map map_command_handler<Worker>::command_alias_map_; 405 406 template <class Worker> 407 bool map_command_handler<Worker>::help_on_unknown_ = true; 408 409 template <class Worker> 410 bool map_command_handler<Worker>::show_unavailable_ = false; 411 412 template <class Worker> 413 std::string map_command_handler<Worker>::cmd_prefix_; 414 415 } 416