changeset 452:2170aa0e38aa

Irccdctl: implement rule-add
author David Demelier <markand@malikania.fr>
date Thu, 20 Jul 2017 22:56:34 +0200
parents 1fdedd2977d2
children acb2d4990249
files doc/html/CMakeLists.txt doc/html/irccdctl/command/index.md doc/html/irccdctl/command/rule-add.md irccd/main.cpp irccdctl/cli.cpp irccdctl/cli.hpp irccdctl/main.cpp libirccd/irccd/command.cpp libirccd/irccd/command.hpp tests/CMakeLists.txt tests/cmd-rule-add/CMakeLists.txt tests/cmd-rule-add/main.cpp
diffstat 12 files changed, 421 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/doc/html/CMakeLists.txt	Fri Jul 07 18:03:18 2017 +0200
+++ b/doc/html/CMakeLists.txt	Thu Jul 20 22:56:34 2017 +0200
@@ -152,6 +152,7 @@
     ${html_SOURCE_DIR}/irccdctl/command/plugin-load.md
     ${html_SOURCE_DIR}/irccdctl/command/plugin-reload.md
     ${html_SOURCE_DIR}/irccdctl/command/plugin-unload.md
+    ${html_SOURCE_DIR}/irccdctl/command/rule-add.md
     ${html_SOURCE_DIR}/irccdctl/command/rule-info.md
     ${html_SOURCE_DIR}/irccdctl/command/rule-list.md
     ${html_SOURCE_DIR}/irccdctl/command/rule-move.md
--- a/doc/html/irccdctl/command/index.md	Fri Jul 07 18:03:18 2017 +0200
+++ b/doc/html/irccdctl/command/index.md	Thu Jul 20 22:56:34 2017 +0200
@@ -12,6 +12,7 @@
   - [plugin-load](plugin-load.html)
   - [plugin-reload](plugin-reload.html)
   - [plugin-unload](plugin-unload.html)
+  - [rule-add](rule-add.html)
   - [rule-info](rule-info.html)
   - [rule-list](rule-list.html)
   - [rule-move](rule-move.html)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/html/irccdctl/command/rule-add.md	Thu Jul 20 22:56:34 2017 +0200
@@ -0,0 +1,31 @@
+---
+title: rule-add
+guide: yes
+---
+
+# rule-add
+
+Add a new rule to irccd.
+
+If no index is specified, the rule is added to the end.
+
+## Usage
+
+````nohighlight
+$ irccdctl rule-add [options] accept|drop
+````
+
+Available options:
+
+  - **-c, --add-channel**: match a channel
+  - **-e, --add-event**: match an event
+  - **-i, --index**: rule position
+  - **-p, --add-plugin**: match a plugin
+  - **-s, --add-server**: match a server
+
+## Example
+
+````nohighlight
+$ irccdctl rule-add -p hangman drop
+$ irccdctl rule-add -s localhost -c #games -p hangman accept
+````
--- a/irccd/main.cpp	Fri Jul 07 18:03:18 2017 +0200
+++ b/irccd/main.cpp	Thu Jul 20 22:56:34 2017 +0200
@@ -320,6 +320,7 @@
     instance->commands().add(std::make_unique<command::ServerPartCommand>());
     instance->commands().add(std::make_unique<command::ServerReconnectCommand>());
     instance->commands().add(std::make_unique<command::ServerTopicCommand>());
+    instance->commands().add(std::make_unique<command::RuleAddCommand>());
     instance->commands().add(std::make_unique<command::RuleInfoCommand>());
     instance->commands().add(std::make_unique<command::RuleListCommand>());
     instance->commands().add(std::make_unique<command::RuleMoveCommand>());
--- a/irccdctl/cli.cpp	Fri Jul 07 18:03:18 2017 +0200
+++ b/irccdctl/cli.cpp	Thu Jul 20 22:56:34 2017 +0200
@@ -29,6 +29,8 @@
 #include "options.hpp"
 #include "util.hpp"
 
+using namespace std::string_literals;
+
 namespace irccd {
 
 /*
@@ -842,6 +844,85 @@
 }
 
 /*
+ * RuleAddCli.
+ * ------------------------------------------------------------------
+ */
+
+RuleAddCli::RuleAddCli()
+    : Cli("rule-add",
+          "add a new rule",
+          "rule-add [options] accept|drop",
+          "Add a new rule to irccd.\n\n"
+          "If no index is specified, the rule is added to the end.\n\n"
+          "Available options:\n"
+          "  -c, --add-channel\t\tmatch a channel\n"
+          "  -e, --add-event\t\tmatch an event\n"
+          "  -i, --index\t\t\trule position\n"
+          "  -p, --add-plugin\t\tmatch a plugin\n"
+          "  -s, --add-server\t\tmatch a server\n\n"
+          "Example:\n"
+          "\tirccdctl rule-add -p hangman drop\n"
+          "\tirccdctl rule-add -s localhost -c #games -p hangman accept")
+{
+}
+
+void RuleAddCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+{
+    static const option::Options options{
+        { "-c",             true },
+        { "--add-channel",  true },
+        { "-e",             true },
+        { "--add-event",    true },
+        { "-i",             true },
+        { "--index",        true },
+        { "-p",             true },
+        { "--add-plugin",   true },
+        { "-s",             true },
+        { "--add-server",   true }
+    };
+
+    auto copy = args;
+    auto result = option::read(copy, options);
+
+    if (copy.size() < 1)
+        throw std::invalid_argument("rule-add requires at least 1 argument");
+
+    auto json = nlohmann::json::object({
+        { "command",    "rule-add"              },
+        { "channels",   nlohmann::json::array() },
+        { "events",     nlohmann::json::array() },
+        { "plugins",    nlohmann::json::array() },
+        { "servers",    nlohmann::json::array() }
+    });
+
+    // All sets.
+    for (const auto& pair : result) {
+        if (pair.first == "-c" || pair.first == "--add-channel")
+            json["channels"].push_back(pair.second);
+        if (pair.first == "-e" || pair.first == "--add-event")
+            json["events"].push_back(pair.second);
+        if (pair.first == "-p" || pair.first == "--add-plugin")
+            json["plugins"].push_back(pair.second);
+        if (pair.first == "-s" || pair.first == "--add-server")
+            json["servers"].push_back(pair.second);
+    }
+
+    // Index.
+    if (result.count("-i") > 0)
+        json["index"] = util::toNumber<unsigned>(result.find("-i")->second);
+    if (result.count("--index") > 0)
+        json["index"] = util::toNumber<unsigned>(result.find("--index")->second);
+
+    // And action.
+    if (copy[0] != "accept" && copy[0] != "drop")
+        throw std::runtime_error("invalid action '"s + copy[0] + "'");
+
+    json["action"] = copy[0];
+
+    check(request(irccdctl, json));
+}
+
+/*
  * RuleListCli.
  * ------------------------------------------------------------------
  */
--- a/irccdctl/cli.hpp	Fri Jul 07 18:03:18 2017 +0200
+++ b/irccdctl/cli.hpp	Thu Jul 20 22:56:34 2017 +0200
@@ -586,6 +586,27 @@
 };
 
 /*
+ * RuleAddCli.
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Implementation of irccdctl rule-add.
+ */
+class RuleAddCli : public Cli {
+public:
+    /**
+     * Default constructor.
+     */
+    RuleAddCli();
+
+    /**
+     * \copydoc Cli::exec
+     */
+    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+};
+
+/*
  * RuleListCli.
  * ------------------------------------------------------------------
  */
--- a/irccdctl/main.cpp	Fri Jul 07 18:03:18 2017 +0200
+++ b/irccdctl/main.cpp	Thu Jul 20 22:56:34 2017 +0200
@@ -522,6 +522,7 @@
     commands.push_back(std::make_unique<cli::ServerPartCli>());
     commands.push_back(std::make_unique<cli::ServerReconnectCli>());
     commands.push_back(std::make_unique<cli::ServerTopicCli>());
+    commands.push_back(std::make_unique<cli::RuleAddCli>());
     commands.push_back(std::make_unique<cli::RuleListCli>());
     commands.push_back(std::make_unique<cli::RuleInfoCli>());
     commands.push_back(std::make_unique<cli::RuleMoveCli>());
--- a/libirccd/irccd/command.cpp	Fri Jul 07 18:03:18 2017 +0200
+++ b/libirccd/irccd/command.cpp	Thu Jul 20 22:56:34 2017 +0200
@@ -22,6 +22,8 @@
 #include "transport.hpp"
 #include "util.hpp"
 
+using namespace std::string_literals;
+
 namespace irccd {
 
 namespace command {
@@ -98,6 +100,42 @@
     };
 }
 
+Rule fromJson(const nlohmann::json &json)
+{
+    auto toset = [] (auto object, auto name) -> RuleSet {
+        RuleSet result;
+
+        for (const auto &s : object[name])
+            if (s.is_string())
+                result.insert(s.template get<std::string>());
+
+        return result;
+    };
+    auto toaction = [] (auto object, auto name) -> RuleAction {
+        auto v = object[name];
+
+        if (!v.is_string())
+            throw std::runtime_error("no action given");
+
+        auto s = v.template get<std::string>();
+        if (s == "accept")
+            return RuleAction::Accept;
+        if (s == "drop")
+            return RuleAction::Drop;
+
+        throw std::runtime_error("unknown action '"s + s + "' given");
+    };
+
+    return {
+        toset(json, "servers"),
+        toset(json, "channels"),
+        toset(json, "origins"),
+        toset(json, "plugins"),
+        toset(json, "events"),
+        toaction(json, "action")
+    };
+}
+
 } // !namespace
 
 PluginConfigCommand::PluginConfigCommand()
@@ -547,6 +585,24 @@
     }
 }
 
+RuleAddCommand::RuleAddCommand()
+    : Command("rule-add")
+{
+}
+
+void RuleAddCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args)
+{
+    auto index = util::json::getUint(args, "index", irccd.rules().length());
+    auto rule = fromJson(args);
+
+    if (index > irccd.rules().length())
+        client.error("rule-add", "index is out of range");
+    else {
+        irccd.rules().insert(rule, index);
+        client.success("rule-add");
+    }
+}
+
 } // !command
 
 } // !irccd
--- a/libirccd/irccd/command.hpp	Fri Jul 07 18:03:18 2017 +0200
+++ b/libirccd/irccd/command.hpp	Thu Jul 20 22:56:34 2017 +0200
@@ -522,6 +522,22 @@
     void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
 };
 
+/**
+ * \brief Implementation of rule-add transport command.
+ */
+class IRCCD_EXPORT RuleAddCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    RuleAddCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
+};
+
 } // !command
 
 } // !irccd
--- a/tests/CMakeLists.txt	Fri Jul 07 18:03:18 2017 +0200
+++ b/tests/CMakeLists.txt	Thu Jul 20 22:56:34 2017 +0200
@@ -26,6 +26,7 @@
     add_subdirectory(cmd-plugin-load)
     add_subdirectory(cmd-plugin-reload)
     add_subdirectory(cmd-plugin-unload)
+    add_subdirectory(cmd-rule-add)
     add_subdirectory(cmd-rule-info)
     add_subdirectory(cmd-rule-list)
     add_subdirectory(cmd-rule-move)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-add/CMakeLists.txt	Thu Jul 20 22:56:34 2017 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME cmd-rule-add
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-add/main.cpp	Thu Jul 20 22:56:34 2017 +0200
@@ -0,0 +1,188 @@
+/*
+ * main.cpp -- test rule-add remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <command.hpp>
+#include <command-tester.hpp>
+#include <service.hpp>
+
+using namespace irccd;
+using namespace irccd::command;
+
+class RuleAddCommandTest : public CommandTester {
+protected:
+    nlohmann::json m_result;
+
+    /*
+     * Rule sets are unordered so use this function to search a string in
+     * the JSON array.
+     */
+    inline bool contains(const nlohmann::json &array, const std::string &str)
+    {
+        for (const auto &v : array)
+            if (v.is_string() && v == str)
+                return true;
+
+        return false;
+    }
+
+public:
+    RuleAddCommandTest()
+        : CommandTester(std::make_unique<RuleAddCommand>())
+    {
+        m_irccd.commands().add(std::make_unique<RuleListCommand>());
+        m_irccdctl.client().onMessage.connect([&] (auto result) {
+            m_result = result;
+        });
+    }
+};
+
+TEST_F(RuleAddCommandTest, basic)
+{
+    m_irccdctl.client().request({
+        { "command",    "rule-add"          },
+        { "servers",    { "s1", "s2" }      },
+        { "channels",   { "c1", "c2" }      },
+        { "plugins",    { "p1", "p2" }      },
+        { "events",     { "onMessage" }     },
+        { "action",     "accept"            },
+        { "index",      0                   }
+    });
+
+    try {
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        auto servers = m_result["list"][0]["servers"];
+        auto channels = m_result["list"][0]["channels"];
+        auto plugins = m_result["list"][0]["plugins"];
+        auto events = m_result["list"][0]["events"];
+
+        ASSERT_TRUE(contains(servers, "s1"));
+        ASSERT_TRUE(contains(servers, "s2"));
+        ASSERT_TRUE(contains(channels, "c1"));
+        ASSERT_TRUE(contains(channels, "c2"));
+        ASSERT_TRUE(contains(plugins, "p1"));
+        ASSERT_TRUE(contains(plugins, "p2"));
+        ASSERT_TRUE(contains(events, "onMessage"));
+        ASSERT_EQ("accept", m_result["list"][0]["action"]);
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleAddCommandTest, append)
+{
+    try {
+        m_irccdctl.client().request({
+            { "command",    "rule-add"          },
+            { "servers",    { "s1" }            },
+            { "channels",   { "c1" }            },
+            { "plugins",    { "p1" }            },
+            { "events",     { "onMessage" }     },
+            { "action",     "accept"            },
+            { "index",      0                   }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({
+            { "command",    "rule-add"          },
+            { "servers",    { "s2" }            },
+            { "channels",   { "c2" }            },
+            { "plugins",    { "p2" }            },
+            { "events",     { "onMessage" }     },
+            { "action",     "drop"              },
+            { "index",      1                   }
+        });
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+
+        m_result = nullptr;
+        m_irccdctl.client().request({{ "command", "rule-list" }});
+
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_TRUE(m_result["status"].get<bool>());
+        ASSERT_EQ(2U, m_result["list"].size());
+
+        // Rule 0.
+        {
+            auto servers = m_result["list"][0]["servers"];
+            auto channels = m_result["list"][0]["channels"];
+            auto plugins = m_result["list"][0]["plugins"];
+            auto events = m_result["list"][0]["events"];
+
+            ASSERT_TRUE(contains(servers, "s1"));
+            ASSERT_TRUE(contains(channels, "c1"));
+            ASSERT_TRUE(contains(plugins, "p1"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("accept", m_result["list"][0]["action"]);
+        }
+
+        // Rule 1.
+        {
+            auto servers = m_result["list"][1]["servers"];
+            auto channels = m_result["list"][1]["channels"];
+            auto plugins = m_result["list"][1]["plugins"];
+            auto events = m_result["list"][1]["events"];
+
+            ASSERT_TRUE(contains(servers, "s2"));
+            ASSERT_TRUE(contains(channels, "c2"));
+            ASSERT_TRUE(contains(plugins, "p2"));
+            ASSERT_TRUE(contains(events, "onMessage"));
+            ASSERT_EQ("drop", m_result["list"][1]["action"]);
+        }
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}