// // Author Wild Coast Solutions // David Hamilton // // This file is distributed under the MIT License. See the accompanying file // LICENSE.txt or http://www.opensource.org/licenses/mit-license.php for terms // and conditions. // // This file contains an implementation of a simple console command libary. // // Project url: https://github.com/WildCoastSolutions/Console #ifndef WILD_CONSOLE_CONSOLE_H #define WILD_CONSOLE_CONSOLE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../CommandLine/CommandLine.h" namespace Wild { namespace Console { typedef std::function CommandHandler; void Split(std::list &tokens, const std::string &s, const std::string &separator); // Represents a single command // e.g. call -a
-b -d class Command { private: CommandLine::Args args; CommandHandler handler; public: std::string name; std::string letter; std::string description; Command( const std::string &name, const std::string &letter, const std::string &description, CommandHandler commandHandler = nullptr) : name(name), letter(letter), description(description), args({}), handler(commandHandler) { } Command( const std::string &name, const std::string &letter, const std::string &description, const std::initializer_list &args, CommandHandler commandHandler = nullptr) : name(name), letter(letter), description(description), args(args), handler(commandHandler) { } bool Handle(const std::string &command, std::list &arguments) { bool result = args.Parse(arguments); if (result == true && handler != nullptr) handler(args); return result; } }; class Console { private: typedef std::function CommandHandler; std::string consoleName; std::string version; std::map> commandMap; std::map letterLookup; std::list commands; std::string currentCommand; CommandHandler commandHandler; public: Console( const std::string &consoleName, const std::string &version, const std::list &commands, CommandHandler commandHandler = nullptr) : consoleName(consoleName), version(version), commands(commands), commandHandler(commandHandler) { if (consoleName.size() == 0) throw std::invalid_argument("console name required"); if (version.size() == 0) throw std::invalid_argument("version required"); if (commands.size() == 0) throw std::invalid_argument("console commands required"); commandMap["help"] = std::make_unique( "help", "h", "display help", [this](CommandLine::Args args) { Help(args); }); letterLookup["h"] = "help"; letterLookup["?"] = "help"; commandMap["quit"] = std::make_unique( "quit", "q", "quit console"); letterLookup["q"] = "quit"; for (auto command : commands) { if (commandMap.count(command.name) > 0) throw std::runtime_error("Cannot have two arguments with the same name"); commandMap[command.name] = std::make_unique(command); if (letterLookup.count(command.letter) > 0) throw std::runtime_error("Cannot have two arguments with the same name"); letterLookup[command.letter] = command.name; } } void Help(CommandLine::Args args) { std::cout << consoleName << " " << version << std::endl << std::endl; std::cout << "\"help \" for more info" << std::endl << std::endl; for (auto command : commands) { std::cout << std::left << std::setw(20) << command.name << command.description << std::endl; } std::cout << std::endl; } std::string CurrentCommand() { return currentCommand; } bool Handle(const std::string &command) { std::list argumentList; Split(argumentList, command, " "); // Just hitting enter is ok, do nothing if (argumentList.size() == 0) return true; std::string commandString = argumentList.front(); argumentList.pop_front(); if (letterLookup.find(commandString) != letterLookup.end()) currentCommand = letterLookup[commandString]; else currentCommand = commandString; if (commandMap.find(currentCommand) == commandMap.end()) { std::cerr << "Could not find command: " << commandString << std::endl; return false; } return commandMap[currentCommand]->Handle(currentCommand, argumentList); } void Run() { std::string commandString; while (CurrentCommand() != "quit") { std::cout << consoleName << ">"; std::getline(std::cin, commandString); try { Handle(commandString); } catch (std::runtime_error &e) { std::cerr << "Could not process command: " << e.what() << std::endl; } } } }; // Splits a string based on a separator string (not the characters inside the separator string) // separators at the start or end of the string are ignored void Split(std::list &tokens, const std::string &s, const std::string &separator) { if (s.size() == 0) throw std::runtime_error("no string passed in"); if (separator.size() == 0) throw std::runtime_error("no separator passed in"); tokens.clear(); size_t prev = 0; size_t pos = s.find(separator); while (pos != std::string::npos) { if (pos != 0) // if we found a separator at the beginning, ignore it tokens.push_back(s.substr(prev, pos - prev)); prev = pos + separator.size(); pos = s.find(separator, prev); } std::string last = s.substr(prev); if (last.size()) tokens.push_back(last); } } } #endif // #ifndef WILD_CONSOLE_CONSOLE_H