<commit_before>/*
* Copyright (C) 2008-2013 Communi authors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*/

#include "commandparser.h"
#include <IrcCommand>
#include <QDateTime>
#include <QHash>
#include <QMap>

CommandParser::Private CommandParser::d;

static QMap<QString, QString>& command_syntaxes()
{
    static QMap<QString, QString> syntaxes;
    if (syntaxes.isEmpty()) {
        syntaxes.insert("ADMIN", "(<server>)");
        syntaxes.insert("AWAY", "(<reason...>)");
        syntaxes.insert("INFO", "(<server>)");
        syntaxes.insert("INVITE", "<user>");
        syntaxes.insert("JOIN", "<channel> (<key>)");
        syntaxes.insert("KICK", "<user> (<reason...>)");
        syntaxes.insert("KNOCK", "<channel> (<message...>)");
        syntaxes.insert("LIST", "(<channels>) (<server>)");
        syntaxes.insert("ME", "<message...>");
        syntaxes.insert("MODE", "<channel/user> (<mode>) (<arg>)");
        syntaxes.insert("MOTD", "(<server>)");
        syntaxes.insert("NAMES", "(<channel>)");
        syntaxes.insert("NICK", "<nick>");
        syntaxes.insert("NOTICE", "<channel/user> <message...>");
        syntaxes.insert("PART", "(<reason...>)");
        syntaxes.insert("PING", "(<user>)");
        syntaxes.insert("QUIT" , "(<message...>)");
        syntaxes.insert("QUOTE" , "<command> (<parameters...>)");
        syntaxes.insert("STATS", "<query> (<server>)");
        syntaxes.insert("TIME", "(<user>)");
        syntaxes.insert("TOPIC", "(<topic...>)");
        syntaxes.insert("TRACE", "(<target>)");
        syntaxes.insert("USERS", "(<server>)");
        syntaxes.insert("VERSION", "(<user>)");
        syntaxes.insert("WHO", "<user>");
        syntaxes.insert("WHOIS", "<user>");
        syntaxes.insert("WHOWAS", "<user>");
    }
    return syntaxes;
}

static QString expandAlias(const QString& receiver, const QString& message)
{
    QStringList words = message.split(QRegExp("\\s+"), QString::SkipEmptyParts);
    QString alias = words.takeFirst().toUpper();
    QMap<QString, QString> aliases = CommandParser::aliases();
    if (aliases.contains(alias)) {
        int pos = 0;
        QRegExp regExp("\\$(\\d+)");
        QString command = aliases.value(alias);
        while ((pos = regExp.indexIn(command)) != -1)
        {
            int index = regExp.cap(1).toInt();
            command.replace(pos, regExp.matchedLength(), words.value(index - 1));
        }
        command.replace("$*", words.join(" "));
        command.replace("$?", receiver);
        return command;
    }
    return message;
}

QStringList CommandParser::availableCommands()
{
    QStringList commands = command_syntaxes().keys() + d.aliases.keys();
    qSort(commands);
    return commands;
}

QStringList CommandParser::suggestedCommands(const QString& command, const QStringList& params)
{
    QStringList suggestions;
    foreach (const QString& available, availableCommands()) {
        if (!command.compare(available, Qt::CaseInsensitive) || (params.isEmpty() && available.startsWith(command, Qt::CaseInsensitive)))
            suggestions += available;
    }
    return suggestions;
}

QString CommandParser::syntax(const QString& command)
{
    QString cmd = command.toUpper();
    if (command_syntaxes().contains(cmd))
        return cmd + " " + command_syntaxes().value(cmd);
    if (d.aliases.contains(cmd))
        return d.aliases.value(cmd);
    return QString();
}

void CommandParser::addCustomCommand(const QString& command, const QString& syntax)
{
    command_syntaxes().insert(command.toUpper(), syntax);
}

void CommandParser::removeCustomCommand(const QString& command)
{
    command_syntaxes().remove(command.toUpper());
}

QMap<QString, QString> CommandParser::aliases()
{
    return d.aliases;
}

void CommandParser::setAliases(const QMap<QString, QString>& aliases)
{
    d.aliases.clear();
    QMapIterator<QString, QString> it(aliases);
    while (it.hasNext()) {
        it.next();
        d.aliases[it.key().toUpper()] = it.value();
    }
}

IrcCommand* CommandParser::parseCommand(const QString& receiver, const QString& text)
{
    if (text.startsWith("/")) {
        typedef IrcCommand*(*ParseFunc)(const QString&, const QStringList&);

        static QHash<QString, ParseFunc> parseFunctions;
        if (parseFunctions.isEmpty()) {
            parseFunctions.insert("ADMIN", &CommandParser::parseAdmin);
            parseFunctions.insert("AWAY", &CommandParser::parseAway);
            parseFunctions.insert("INFO", &CommandParser::parseInfo);
            parseFunctions.insert("INVITE", &CommandParser::parseInvite);
            parseFunctions.insert("JOIN", &CommandParser::parseJoin);
            parseFunctions.insert("KICK", &CommandParser::parseKick);
            parseFunctions.insert("KNOCK", &CommandParser::parseKnock);
            parseFunctions.insert("LIST", &CommandParser::parseList);
            parseFunctions.insert("ME", &CommandParser::parseMe);
            parseFunctions.insert("MODE", &CommandParser::parseMode);
            parseFunctions.insert("MOTD", &CommandParser::parseMotd);
            parseFunctions.insert("NAMES", &CommandParser::parseNames);
            parseFunctions.insert("NICK", &CommandParser::parseNick);
            parseFunctions.insert("NOTICE", &CommandParser::parseNotice);
            parseFunctions.insert("PART", &CommandParser::parsePart);
            parseFunctions.insert("PING", &CommandParser::parsePing);
            parseFunctions.insert("QUIT", &CommandParser::parseQuit);
            parseFunctions.insert("QUOTE", &CommandParser::parseQuote);
            parseFunctions.insert("STATS", &CommandParser::parseStats);
            parseFunctions.insert("TIME", &CommandParser::parseTime);
            parseFunctions.insert("TOPIC", &CommandParser::parseTopic);
            parseFunctions.insert("TRACE", &CommandParser::parseTrace);
            parseFunctions.insert("USERS", &CommandParser::parseUsers);
            parseFunctions.insert("VERSION", &CommandParser::parseVersion);
            parseFunctions.insert("WHO", &CommandParser::parseWho);
            parseFunctions.insert("WHOIS", &CommandParser::parseWhois);
            parseFunctions.insert("WHOWAS", &CommandParser::parseWhowas);
        }

        const QString expanded = expandAlias(receiver, text.mid(1));
        const QStringList words = expanded.split(" ", QString::SkipEmptyParts);
        const QString command = words.value(0).toUpper();
        ParseFunc parseFunc = parseFunctions.value(command);
        if (parseFunc) {
            IrcCommand* cmd = parseFunc(receiver, words.mid(1));
            if (cmd)
                return cmd;
        } else if (command_syntaxes().contains(command.toUpper())) {
            return parseCustomCommand(command, words.mid(1), command_syntaxes().value(command.toUpper()));
        }
    } else {
        return IrcCommand::createMessage(receiver, text);
    }

    // unknown command
    return 0;
}

IrcCommand* CommandParser::parseCustomCommand(const QString& command, const QStringList& params, const QString& syntax)
{
    QStringList tokens = syntax.split(" ", QString::SkipEmptyParts);
    int min = 0;
    int max = tokens.count();
    while (!tokens.isEmpty())
    {
        QString p = tokens.takeFirst();
        if (!p.startsWith("(<"))<commit_msg>Fix #16: Provide a way to use a slash at the start of a message<commit_after>/*
* Copyright (C) 2008-2013 Communi authors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*/

#include "commandparser.h"
#include <IrcCommand>
#include <QDateTime>
#include <QHash>
#include <QMap>

CommandParser::Private CommandParser::d;

static QMap<QString, QString>& command_syntaxes()
{
    static QMap<QString, QString> syntaxes;
    if (syntaxes.isEmpty()) {
        syntaxes.insert("ADMIN", "(<server>)");
        syntaxes.insert("AWAY", "(<reason...>)");
        syntaxes.insert("INFO", "(<server>)");
        syntaxes.insert("INVITE", "<user>");
        syntaxes.insert("JOIN", "<channel> (<key>)");
        syntaxes.insert("KICK", "<user> (<reason...>)");
        syntaxes.insert("KNOCK", "<channel> (<message...>)");
        syntaxes.insert("LIST", "(<channels>) (<server>)");
        syntaxes.insert("ME", "<message...>");
        syntaxes.insert("MODE", "<channel/user> (<mode>) (<arg>)");
        syntaxes.insert("MOTD", "(<server>)");
        syntaxes.insert("NAMES", "(<channel>)");
        syntaxes.insert("NICK", "<nick>");
        syntaxes.insert("NOTICE", "<channel/user> <message...>");
        syntaxes.insert("PART", "(<reason...>)");
        syntaxes.insert("PING", "(<user>)");
        syntaxes.insert("QUIT" , "(<message...>)");
        syntaxes.insert("QUOTE" , "<command> (<parameters...>)");
        syntaxes.insert("STATS", "<query> (<server>)");
        syntaxes.insert("TIME", "(<user>)");
        syntaxes.insert("TOPIC", "(<topic...>)");
        syntaxes.insert("TRACE", "(<target>)");
        syntaxes.insert("USERS", "(<server>)");
        syntaxes.insert("VERSION", "(<user>)");
        syntaxes.insert("WHO", "<user>");
        syntaxes.insert("WHOIS", "<user>");
        syntaxes.insert("WHOWAS", "<user>");
    }
    return syntaxes;
}

static QString expandAlias(const QString& receiver, const QString& message)
{
    QStringList words = message.split(QRegExp("\\s+"), QString::SkipEmptyParts);
    QString alias = words.takeFirst().toUpper();
    QMap<QString, QString> aliases = CommandParser::aliases();
    if (aliases.contains(alias)) {
        int pos = 0;
        QRegExp regExp("\\$(\\d+)");
        QString command = aliases.value(alias);
        while ((pos = regExp.indexIn(command)) != -1)
        {
            int index = regExp.cap(1).toInt();
            command.replace(pos, regExp.matchedLength(), words.value(index - 1));
        }
        command.replace("$*", words.join(" "));
        command.replace("$?", receiver);
        return command;
    }
    return message;
}

QStringList CommandParser::availableCommands()
{
    QStringList commands = command_syntaxes().keys() + d.aliases.keys();
    qSort(commands);
    return commands;
}

QStringList CommandParser::suggestedCommands(const QString& command, const QStringList& params)
{
    QStringList suggestions;
    foreach (const QString& available, availableCommands()) {
        if (!command.compare(available, Qt::CaseInsensitive) || (params.isEmpty() && available.startsWith(command, Qt::CaseInsensitive)))
            suggestions += available;
    }
    return suggestions;
}

QString CommandParser::syntax(const QString& command)
{
    QString cmd = command.toUpper();
    if (command_syntaxes().contains(cmd))
        return cmd + " " + command_syntaxes().value(cmd);
    if (d.aliases.contains(cmd))
        return d.aliases.value(cmd);
    return QString();
}

void CommandParser::addCustomCommand(const QString& command, const QString& syntax)
{
    command_syntaxes().insert(command.toUpper(), syntax);
}

void CommandParser::removeCustomCommand(const QString& command)
{
    command_syntaxes().remove(command.toUpper());
}

QMap<QString, QString> CommandParser::aliases()
{
    return d.aliases;
}

void CommandParser::setAliases(const QMap<QString, QString>& aliases)
{
    d.aliases.clear();
    QMapIterator<QString, QString> it(aliases);
    while (it.hasNext()) {
        it.next();
        d.aliases[it.key().toUpper()] = it.value();
    }
}

IrcCommand* CommandParser::parseCommand(const QString& receiver, const QString& text)
{
    if (text.startsWith("//") || text.startsWith("/ ") || !text.startsWith('/')) {
        QString message = text;
        if (message.startsWith('/'))
            message.remove(0, 1);
        return IrcCommand::createMessage(receiver, message.trimmed());
    } else {
        typedef IrcCommand*(*ParseFunc)(const QString&, const QStringList&);

        static QHash<QString, ParseFunc> parseFunctions;
        if (parseFunctions.isEmpty()) {
            parseFunctions.insert("ADMIN", &CommandParser::parseAdmin);
            parseFunctions.insert("AWAY", &CommandParser::parseAway);
            parseFunctions.insert("INFO", &CommandParser::parseInfo);
            parseFunctions.insert("INVITE", &CommandParser::parseInvite);
            parseFunctions.insert("JOIN", &CommandParser::parseJoin);
            parseFunctions.insert("KICK", &CommandParser::parseKick);
            parseFunctions.insert("KNOCK", &CommandParser::parseKnock);
            parseFunctions.insert("LIST", &CommandParser::parseList);
            parseFunctions.insert("ME", &CommandParser::parseMe);
            parseFunctions.insert("MODE", &CommandParser::parseMode);
            parseFunctions.insert("MOTD", &CommandParser::parseMotd);
            parseFunctions.insert("NAMES", &CommandParser::parseNames);
            parseFunctions.insert("NICK", &CommandParser::parseNick);
            parseFunctions.insert("NOTICE", &CommandParser::parseNotice);
            parseFunctions.insert("PART", &CommandParser::parsePart);
            parseFunctions.insert("PING", &CommandParser::parsePing);
            parseFunctions.insert("QUIT", &CommandParser::parseQuit);
            parseFunctions.insert("QUOTE", &CommandParser::parseQuote);
            parseFunctions.insert("STATS", &CommandParser::parseStats);
            parseFunctions.insert("TIME", &CommandParser::parseTime);
            parseFunctions.insert("TOPIC", &CommandParser::parseTopic);
            parseFunctions.insert("TRACE", &CommandParser::parseTrace);
            parseFunctions.insert("USERS", &CommandParser::parseUsers);
            parseFunctions.insert("VERSION", &CommandParser::parseVersion);
            parseFunctions.insert("WHO", &CommandParser::parseWho);
            parseFunctions.insert("WHOIS", &CommandParser::parseWhois);
            parseFunctions.insert("WHOWAS", &CommandParser::parseWhowas);
        }

        const QString expanded = expandAlias(receiver, text.mid(1));
        const QStringList words = expanded.split(" ", QString::SkipEmptyParts);
        const QString command = words.value(0).toUpper();
        ParseFunc parseFunc = parseFunctions.value(command);
        if (parseFunc) {
            IrcCommand* cmd = parseFunc(receiver, words.mid(1));
            if (cmd)
                return cmd;
        } else if (command_syntaxes().contains(command.toUpper())) {
            return parseCustomCommand(command, words.mid(1), command_syntaxes().value(command.toUpper()));
        }
    }

    // unknown command
    return 0;
}

IrcCommand* CommandParser::parseCustomCommand(const QString& command, const QStringList& params, const QString& syntax)
{
    QStringList tokens = syntax.split(" ", QString::SkipEmptyParts);
    int min = 0;
    int max = tokens.count();
    while (!tokens.isEmpty())
    {
        QString p = tokens.takeFirst();
        if (!p.startsWith("(<"))
{'fraction_non_alphanumeric': 0.15994392404256674, 'fraction_numerical': 0.0026763525138596826, 'mean_word_length': 3.485281509002572, 'pattern_counts': {'":': 0, '<': 99, '<?xml version=': 0, '>': 97, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6691920', 'n_tokens_mistral': 4447, 'n_tokens_neox': 4373, 'n_words': 916}