Skip to content

Commit

Permalink
qt: refactor console-only command parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
hernanmarino committed Jan 31, 2024
1 parent cad2df2 commit 1bf0c07
Showing 1 changed file with 86 additions and 28 deletions.
114 changes: 86 additions & 28 deletions src/qt/rpcconsole.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,33 @@ public Q_SLOTS:

private:
interfaces::Node& m_node;
bool executeConsoleHelpConsole(const std::vector<std::string>& 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<std::string, bool (RPCExecutor::*)(const std::vector<std::string>&, 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<std::string> parseHelper(const std::string& strCommand)
{
// Split while recognizing the several characters that can be used as separators in the GUI console
std::vector<std::string> 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)
*/
Expand Down Expand Up @@ -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)
{
Expand All @@ -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<std::string>& 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<std::string> 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),
Expand Down

0 comments on commit 1bf0c07

Please sign in to comment.