From 1bf0c075c7b8a88c8977b49b72a61faf53003040 Mon Sep 17 00:00:00 2001 From: Hernan Marino Date: Sun, 25 Dec 2022 22:51:50 -0300 Subject: [PATCH] qt: refactor console-only command parsing --- src/qt/rpcconsole.cpp | 114 +++++++++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 28 deletions(-) diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index ceaa3ac46be..14dab62b205 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -96,8 +96,33 @@ public Q_SLOTS: private: interfaces::Node& m_node; + bool executeConsoleHelpConsole(const std::vector& parsed_command, const WalletModel* wallet_model, const bool exec_help = false); + bool executeConsoleOnlyCommand(const std::string& command, const WalletModel* wallet_model); + // std::map mapping strings to methods member of RPCExecutor class + // Keys must be strings with commands and (optionally) parameters in "canonical" form (separated by single space) + // Keys should match the beggining of user input commands (user commands can have more parameters than the key) + std::map&, const WalletModel*, const bool)> m_method_map{ + {"help-console", &RPCExecutor::executeConsoleHelpConsole}}; }; +/** + * Small and fast parser supporting console command syntax, with limited functionality. + * Splits a command line string into a vector with command at position 0, and parameters after + * + * @param[in] strCommand Command line to parse + * + * @return a vector of strings with command and parameters + */ +std::vector parseHelper(const std::string& strCommand) +{ + // Split while recognizing the several characters that can be used as separators in the GUI console + std::vector vec{SplitString(strCommand, " (),")}; + // Remove empty strings produced by consecutive separators + auto should_remove{[](const std::string& str) { return str.empty(); }}; + vec.erase(std::remove_if(vec.begin(), vec.end(), should_remove), vec.end()); + return vec; +} + /** Class for handling RPC timers * (used for e.g. re-locking the wallet after a timeout) */ @@ -415,35 +440,15 @@ void RPCExecutor::request(const QString &command, const WalletModel* wallet_mode std::string result; std::string executableCommand = command.toStdString() + "\n"; - // Catch the console-only-help command before RPC call is executed and reply with help text as-if a RPC reply. - if(executableCommand == "help-console\n") { - Q_EMIT reply(RPCConsole::CMD_REPLY, QString(("\n" - "This console accepts RPC commands using the standard syntax.\n" - " example: getblockhash 0\n\n" - - "This console can also accept RPC commands using the parenthesized syntax.\n" - " example: getblockhash(0)\n\n" - - "Commands may be nested when specified with the parenthesized syntax.\n" - " example: getblock(getblockhash(0) 1)\n\n" - - "A space or a comma can be used to delimit arguments for either syntax.\n" - " example: getblockhash 0\n" - " getblockhash,0\n\n" - - "Named results can be queried with a non-quoted key string in brackets using the parenthesized syntax.\n" - " example: getblock(getblockhash(0) 1)[tx]\n\n" - - "Results without keys can be queried with an integer in brackets using the parenthesized syntax.\n" - " example: getblock(getblockhash(0),1)[tx][0]\n\n"))); - return; - } - if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, wallet_model)) { - Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); - return; + // Attempt to execute console-only commands + if (!RPCExecutor::executeConsoleOnlyCommand(command.toStdString(), wallet_model)) { + // Send to the RPC command parser if not console-only + if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, wallet_model)) { + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); + return; + } + Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result)); } - - Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result)); } catch (UniValue& objError) { @@ -464,6 +469,59 @@ void RPCExecutor::request(const QString &command, const WalletModel* wallet_mode } } +/** + * @brief Executes the console-only command "help-console". + * @param parsed_command A vector of strings with command and parameters, usually generated by RPCExecutor::parseHelper + * @param wallet_model WalletModel to use for the command + * @return True if the command was executed, false otherwise. + */ +bool RPCExecutor::executeConsoleHelpConsole(const std::vector& parsed_command, const WalletModel* wallet_model, const bool exec_help) +{ + // Reply with help text as-if a RPC reply. + Q_EMIT reply(RPCConsole::CMD_REPLY, + QString("\n" + "This console accepts RPC commands using the standard syntax.\n" + " example: getblockhash 0\n\n" + "This console can also accept RPC commands using the parenthesized syntax.\n" + " example: getblockhash(0)\n\n" + "Commands may be nested when specified with the parenthesized syntax.\n" + " example: getblock(getblockhash(0) 1)\n\n" + "A space or a comma can be used to delimit arguments for either syntax.\n" + " example: getblockhash 0\n" + " getblockhash,0\n\n" + "Named results can be queried with a non-quoted key string in brackets using the parenthesized syntax.\n" + " example: getblock(getblockhash(0) 1)[tx]\n\n" + "Results without keys can be queried with an integer in brackets using the parenthesized syntax.\n" + " example: getblock(getblockhash(0),1)[tx][0]\n\n")); + return true; +} + +/** + * Catches console-only command before a RPC call is executed + * + * @param[in] command Command line to execute + * @param[in] wallet_model Wallet model to use + * @return true if command was handled by this method (even on errors), false otherwise + * + */ +bool RPCExecutor::executeConsoleOnlyCommand(const std::string& command, const WalletModel* wallet_model) +{ + // Parse command line into a vector of strings + const std::vector parsed_command{parseHelper(command)}; + + if (parsed_command.empty()) return false; + + std::string method = parsed_command[0]; + bool exec_help = false; + if (method == "help" && parsed_command.size() > 1) { + exec_help = true; + method = parsed_command[1]; + } + auto it_method = m_method_map.find(method); + if (it_method == m_method_map.end()) return false; // method not found + return (this->*(it_method->second))(parsed_command, wallet_model, exec_help); +} + RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformStyle, QWidget *parent) : QWidget(parent), m_node(node),