From ccd8b87ba062a99e91f58bbe2e7494bcf3301aee Mon Sep 17 00:00:00 2001 From: Markus Mittendrein Date: Thu, 1 Jun 2017 01:33:54 +0200 Subject: Correct/suggest command names with the help of damerau levenshtein distance --- src/crsm.cpp | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++------- src/crsm.hpp | 8 +++-- src/qt-config | 2 +- 3 files changed, 102 insertions(+), 16 deletions(-) diff --git a/src/crsm.cpp b/src/crsm.cpp index aa1ef13..e187123 100644 --- a/src/crsm.cpp +++ b/src/crsm.cpp @@ -245,6 +245,21 @@ bool CRSM::watchdog(const QString& id) return false; } +QString CRSM::cmdErrorText(const QString& command, const ClientInfo& client, bool& ret) +{ + QString retText; + QStringList corrections; + if(!(ret = cmd(command, client, corrections))) + { + retText = "Unbekannter Befehl: \"" + command + "\"!\n"; + if(!corrections.isEmpty()) + { + retText += "Meintest du: " + corrections.join(", ") + "?\n"; + } + } + return retText; +} + bool CRSM::clientMessage(ClientInfo& client, const QString& message, ClonkOutputInterface::MessageType type, const QTime& time) { bool isMeMessage = (type == Action); @@ -281,11 +296,16 @@ bool CRSM::clientMessage(ClientInfo& client, const QString& message, ClonkOutput QString command = getCommand(message); if(!command.isEmpty()) { - if(!cmd(command, client)) + bool ret; + const QString& txt = cmdErrorText(command, client, ret); + if(ret) + { + return true; + } + else { - respond(client, "Unbekannter Befehl: \"" + command + "\"!\n"); + respond(client, txt); } - return true; } } } @@ -699,7 +719,8 @@ void CRSM::newManagementData() if(data.at(0) == '/') { QString command = data.mid(1).trimmed(); - if(cmd(command, ClientInfo::managementClient(conn))) + QStringList corrections; + if(cmd(command, ClientInfo::managementClient(conn), corrections)) { continue; } @@ -1431,12 +1452,58 @@ bool CRSM::cmdExists(const QString &name, ClientInterface interface) return cmds.contains(name) && cmds.value(name).interfaces & interface; } -CmdFunctionRef* CRSM::findCommand(const QString &cmd, ClientInterface interface, QString &args) +QStringList CRSM::guessCmd(const QString& name, ClientInterface interface) +{ + int minDistance = -1; + QString minDistanceCmd; + QStringList minDistanceCmds; + + auto applyDistance = [&name, &minDistance, &minDistanceCmd, &minDistanceCmds](const QString& cmdName) + { + int distance = Util::damerauLevenshteinDistance(name, cmdName); + if(minDistance == -1 || distance < minDistance) + { + minDistance = distance; + minDistanceCmd = cmdName; + minDistanceCmds.clear(); + } + else if(minDistance == distance && !minDistanceCmds.contains(cmdName)) + { + minDistanceCmds.append(cmdName); + } + }; + + for(const auto& cmd : cmds) + { + if(cmd.interfaces & interface) + { + applyDistance(cmd.name); + } + } + + for(const auto& alias : Config.CRSM.CommandAlias.keys()) + { + applyDistance(alias); + } + + if(!minDistanceCmds.contains(minDistanceCmd)) + { + minDistanceCmds.append(minDistanceCmd); + } + + if(minDistanceCmds.length() == 1 && minDistance >= std::min(3, name.length() - 1)) + { + minDistanceCmds.clear(); + } + return minDistanceCmds; +} + +CmdFunctionRef* CRSM::findCommand(const QString &cmd, ClientInterface interface, QString &args, QStringList& corrections) { - return findCommand(cmd.split(QRegularExpression(R"(\s)"), QString::KeepEmptyParts), interface, args); + return findCommand(cmd.split(QRegularExpression(R"(\s)"), QString::KeepEmptyParts), interface, args, corrections); } -CmdFunctionRef* CRSM::findCommand(QStringList&& cmd, ClientInterface interface, QString &realCmd) +CmdFunctionRef* CRSM::findCommand(QStringList&& cmd, ClientInterface interface, QString &realCmd, QStringList& corrections) { if(cmd.length() > 0) { @@ -1457,8 +1524,23 @@ CmdFunctionRef* CRSM::findCommand(QStringList&& cmd, ClientInterface interface, } else { + const QStringList& guesses = guessCmd(cmdPart, interface); + if(guesses.length() == 1) + { + QString guess = guesses.first(); + substituteCommandAlias(guess); + if(cmdExists(guess, interface)) + { + return &cmds[guess]; + } + } + else if(guesses.length() > 1) + { + corrections.append(guesses); + } + cmd.removeLast(); - return findCommand(std::move(cmd), interface, realCmd); + return findCommand(std::move(cmd), interface, realCmd, corrections); } } } @@ -1468,12 +1550,12 @@ CmdFunctionRef* CRSM::findCommand(QStringList&& cmd, ClientInterface interface, } } -bool CRSM::cmd(const QString& cmd, const ClientInfo &client) +bool CRSM::cmd(const QString& cmd, const ClientInfo &client, QStringList& corrections) { Log.commandLog(cmd, client, false); CmdFunctionRef* cmdPtr; QString realCmd = cmd; - if((cmdPtr = findCommand(cmd, client.interface, realCmd)) != nullptr) + if((cmdPtr = findCommand(cmd, client.interface, realCmd, corrections)) != nullptr) { CmdFunctionRef cmdRef = *cmdPtr; QString args = cmd.mid(realCmd.length()).trimmed(); @@ -2327,9 +2409,11 @@ void CRSM::handleIrcMessage(const ClientInfo &client, QString message, const QSt if(!command.isEmpty()) { - if(!cmd(command, client)) + bool ret; + const QString& txt = cmdErrorText(command, client, ret); + if(!ret) { - respond(client, "Unbekannter Befehl: \"" + command + "\"!", (privateMessage ? RespondType::PrivateNotice : RespondType::Normal), true); + respond(client, txt, (privateMessage ? RespondType::PrivateNotice : RespondType::Normal), true); } } diff --git a/src/crsm.hpp b/src/crsm.hpp index 4e3aa34..c4c8482 100644 --- a/src/crsm.hpp +++ b/src/crsm.hpp @@ -239,9 +239,11 @@ private: void addCommand(const QString& name, CmdFunction func, int interfaces = Clonk | IRC, UserType userType = User, const QString& shortDescription = "", const QString &argList = "", const QString &longDescription = ""); inline void addCommandGroup(const QString& name, int interfaces = Clonk | IRC, UserType userType = User, const QString& shortDescription = "", const QString &longDescription = "", CmdFunction defaultFunc = &CRSM::groupinfo); bool cmdExists(const QString& name, ClientInterface interface); - CmdFunctionRef* findCommand(const QString& cmd, ClientInterface interface, QString& args); - CmdFunctionRef* findCommand(QStringList &&cmd, ClientInterface interface, QString& args); - bool cmd(const QString &cmd, const ClientInfo& client); + QStringList guessCmd(const QString& name, ClientInterface interface); + CmdFunctionRef* findCommand(const QString& cmd, ClientInterface interface, QString& args, QStringList& corrections); + CmdFunctionRef* findCommand(QStringList &&cmd, ClientInterface interface, QString& args, QStringList& corrections); + QString cmdErrorText(const QString& command, const ClientInfo& client, bool& ret); + bool cmd(const QString &cmd, const ClientInfo& client, QStringList& corrections); void rightsFailMessage(const ClientInfo& info, UserType minUserType); UserType clientUserType(const ClientInfo& client); diff --git a/src/qt-config b/src/qt-config index 0035a12..9e411a0 160000 --- a/src/qt-config +++ b/src/qt-config @@ -1 +1 @@ -Subproject commit 0035a128ba00259cf6a5ada68e05c0efaf9f95a4 +Subproject commit 9e411a03acc60aaff7244f26cc32597fd6452df3 -- cgit v1.2.3-54-g00ecf