changeset 524:ba81aeb514b8

Irccd: change loading order in js_plugin, #678 To open a Javascript plugin, several steps are required in this order: 1. create Duktape heap, 2. load Javascript modules, 3. open the file 4. apply user configuration This order is important as it let the plugin setting defaults values that can be overriden by the user.
author David Demelier <markand@malikania.fr>
date Tue, 14 Nov 2017 19:54:32 +0100
parents 542c54f76488
children d070d33bf4a0
files libirccd-js/irccd/js_plugin.cpp libirccd-js/irccd/js_plugin.hpp tests/js-plugin/config-assign.js tests/js-plugin/config-fill.js tests/js-plugin/irccd.conf tests/js-plugin/main.cpp
diffstat 6 files changed, 142 insertions(+), 87 deletions(-) [+]
line wrap: on
line diff
--- a/libirccd-js/irccd/js_plugin.cpp	Mon Nov 13 15:49:47 2017 +0100
+++ b/libirccd-js/irccd/js_plugin.cpp	Tue Nov 14 19:54:32 2017 +0100
@@ -16,15 +16,11 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <boost/filesystem.hpp>
-
-#include "sysconfig.hpp"
-
-#if defined(HAVE_STAT)
-#  include <sys/stat.h>
-#  include <cerrno>
-#  include <cstring>
-#endif
+#include <cstring>
+#include <cerrno>
+#include <fstream>
+#include <iterator>
+#include <stdexcept>
 
 #include "irccd.hpp"
 #include "logger.hpp"
@@ -86,21 +82,11 @@
     }
 }
 
-void js_plugin::put_vars()
+js_plugin::js_plugin(std::string name, std::string path)
+    : plugin(name, path)
 {
     StackAssert sa(context_);
 
-    duk_push_pointer(context_, this);
-    duk_put_global_string(context_, "\xff""\xff""plugin");
-    dukx_push_std_string(context_, name());
-    duk_put_global_string(context_, "\xff""\xff""name");
-    dukx_push_std_string(context_, path());
-    duk_put_global_string(context_, "\xff""\xff""path");
-}
-
-js_plugin::js_plugin(std::string name, std::string path)
-    : plugin(name, path)
-{
     /*
      * Create two special tables for configuration and formats, they are
      * referenced later as
@@ -117,6 +103,46 @@
     duk_put_global_string(context_, format_property.c_str());
     duk_push_object(context_);
     duk_put_global_string(context_, paths_property.c_str());
+
+    duk_push_pointer(context_, this);
+    duk_put_global_string(context_, "\xff""\xff""plugin");
+    dukx_push_std_string(context_, name);
+    duk_put_global_string(context_, "\xff""\xff""name");
+    dukx_push_std_string(context_, path);
+    duk_put_global_string(context_, "\xff""\xff""path");
+}
+
+void js_plugin::open()
+{
+    std::ifstream input(path());
+
+    if (!input)
+        throw std::runtime_error(std::strerror(errno));
+
+    std::string data(
+        std::istreambuf_iterator<char>(input.rdbuf()),
+        std::istreambuf_iterator<char>()
+    );
+
+    if (duk_peval_string(context_, data.c_str()))
+        throw dukx_exception(context_, -1);
+
+    // Read metadata.
+    duk_get_global_string(context_, "info");
+
+    if (duk_get_type(context_, -1) == DUK_TYPE_OBJECT) {
+        duk_get_prop_string(context_, -1, "author");
+        set_author(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : author());
+        duk_get_prop_string(context_, -2, "license");
+        set_license(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : license());
+        duk_get_prop_string(context_, -3, "summary");
+        set_summary(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : summary());
+        duk_get_prop_string(context_, -4, "version");
+        set_version(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : version());
+        duk_pop_n(context_, 4);
+    }
+
+    duk_pop(context_);
 }
 
 void js_plugin::on_channel_mode(irccd& , const channel_mode_event &event)
@@ -197,55 +223,6 @@
 {
     StackAssert sa(context_);
 
-    /*
-     * Duktape currently emit useless warnings when a file do
-     * not exists so we do a homemade access.
-     */
-#if defined(HAVE_STAT)
-    struct stat st;
-
-    if (::stat(path().c_str(), &st) < 0)
-        throw std::runtime_error(std::strerror(errno));
-#endif
-
-    // Try to load the file (does not call onLoad yet).
-    dukx_peval_file(context_, path());
-    duk_pop(context_);
-
-
-    /*
-     * We put configuration and formats after loading the file and before
-     * calling onLoad to allow the plugin adding configuration to
-     * irccd.Plugin.(config|format) before the user.
-     */
-    put_vars();
-
-    // Read metadata .
-    duk_get_global_string(context_, "info");
-
-    if (duk_get_type(context_, -1) == DUK_TYPE_OBJECT) {
-        // 'author'
-        duk_get_prop_string(context_, -1, "author");
-        set_author(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : author());
-        duk_pop(context_);
-
-        // 'license'
-        duk_get_prop_string(context_, -1, "license");
-        set_license(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : license());
-        duk_pop(context_);
-
-        // 'summary'
-        duk_get_prop_string(context_, -1, "summary");
-        set_summary(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : summary());
-        duk_pop(context_);
-
-        // 'version'
-        duk_get_prop_string(context_, -1, "version");
-        set_version(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : version());
-        duk_pop(context_);
-    }
-
-    duk_pop(context_);
     call("onLoad", 0);
 }
 
@@ -413,6 +390,8 @@
         for (const auto& mod : modules_)
             mod->load(irccd_, plugin);
 
+        plugin->open();
+
         return plugin;
     } catch (const std::exception &ex) {
         log::warning() << "plugin " << id << ": " << ex.what() << std::endl;
--- a/libirccd-js/irccd/js_plugin.hpp	Mon Nov 13 15:49:47 2017 +0100
+++ b/libirccd-js/irccd/js_plugin.hpp	Tue Nov 14 19:54:32 2017 +0100
@@ -40,6 +40,11 @@
 class js_plugin : public plugin {
 public:
     /**
+     * List of modules to enable.
+     */
+    using modules_t = std::vector<std::unique_ptr<module>>;
+
+    /**
      * Global property where to read/write plugin configuration (object).
      */
     static const std::string config_property;
@@ -62,7 +67,6 @@
     std::unordered_map<std::string, std::string> get_table(const std::string&) const;
     void put_table(const std::string&, const std::unordered_map<std::string, std::string>&);
     void call(const std::string&, unsigned = 0);
-    void put_vars();
 
 public:
     /**
@@ -84,6 +88,11 @@
     }
 
     /**
+     * Open the script file associated.
+     */
+    void open();
+
+    /**
      * \copydoc Plugin::config
      */
     plugin_config config() override
--- a/tests/js-plugin/config-assign.js	Mon Nov 13 15:49:47 2017 +0100
+++ b/tests/js-plugin/config-assign.js	Tue Nov 14 19:54:32 2017 +0100
@@ -1,3 +1,3 @@
 Irccd.Plugin.config = {
-    "key1": "value1"
+    "hard": "true"
 };
--- a/tests/js-plugin/config-fill.js	Mon Nov 13 15:49:47 2017 +0100
+++ b/tests/js-plugin/config-fill.js	Tue Nov 14 19:54:32 2017 +0100
@@ -1,1 +1,1 @@
-Irccd.Plugin.config["key1"] = "value1";
+Irccd.Plugin.config["hard"] = "true";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/js-plugin/irccd.conf	Tue Nov 14 19:54:32 2017 +0100
@@ -0,0 +1,3 @@
+[plugin.test]
+path = "none"
+verbose = "false"
--- a/tests/js-plugin/main.cpp	Mon Nov 13 15:49:47 2017 +0100
+++ b/tests/js-plugin/main.cpp	Tue Nov 14 19:54:32 2017 +0100
@@ -20,13 +20,14 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/irccd.hpp>
+#include <irccd/service.hpp>
 #include <irccd/js_plugin.hpp>
 #include <irccd/js_irccd_module.hpp>
 #include <irccd/js_plugin_module.hpp>
 
 namespace irccd {
 
-class test {
+class js_plugin_test {
 protected:
     irccd irccd_;
     std::shared_ptr<js_plugin> plugin_;
@@ -37,48 +38,111 @@
 
         js_irccd_module().load(irccd_, plugin_);
         js_plugin_module().load(irccd_, plugin_);
+
+        plugin_->open();
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(test_suite, test)
+BOOST_FIXTURE_TEST_SUITE(js_plugin_test_suite, js_plugin_test)
 
 BOOST_AUTO_TEST_CASE(assign)
 {
-    load("assign", CMAKE_CURRENT_SOURCE_DIR "/config-assign.js");
+    load("test", CMAKE_CURRENT_SOURCE_DIR "/config-assign.js");
 
     plugin_->set_config({
-        { "key2", "value2" }
+        { "path",       "none"  },
+        { "verbose",    "false" }
     });
     plugin_->on_load(irccd_);
 
-    BOOST_TEST(plugin_->config().at("key1") == "value1");
-    BOOST_TEST(plugin_->config().at("key2") == "value2");
+    BOOST_TEST(plugin_->config().at("path") == "none");
+    BOOST_TEST(plugin_->config().at("verbose") == "false");
+    BOOST_TEST(plugin_->config().at("hard") == "true");
 }
 
 BOOST_AUTO_TEST_CASE(fill)
 {
-    load("assign", CMAKE_CURRENT_SOURCE_DIR "/config-fill.js");
+    load("test", CMAKE_CURRENT_SOURCE_DIR "/config-fill.js");
 
     plugin_->set_config({
-        { "key2", "value2" }
+        { "path",       "none"  },
+        { "verbose",    "false" }
     });
     plugin_->on_load(irccd_);
 
-    BOOST_TEST(plugin_->config().at("key1") == "value1");
-    BOOST_TEST(plugin_->config().at("key2") == "value2");
+    BOOST_TEST(plugin_->config().at("path") == "none");
+    BOOST_TEST(plugin_->config().at("verbose") == "false");
+    BOOST_TEST(plugin_->config().at("hard") == "true");
 }
 
 BOOST_AUTO_TEST_CASE(merge_after)
 {
-    load("assign", CMAKE_CURRENT_SOURCE_DIR "/config-fill.js");
+    load("test", CMAKE_CURRENT_SOURCE_DIR "/config-fill.js");
 
     plugin_->on_load(irccd_);
     plugin_->set_config({
-        { "key2", "value2" }
+        { "path",       "none"  },
+        { "verbose",    "false" }
     });
 
-    BOOST_TEST(plugin_->config().at("key1") == "value1");
-    BOOST_TEST(plugin_->config().at("key2") == "value2");
+    BOOST_TEST(plugin_->config().at("path") == "none");
+    BOOST_TEST(plugin_->config().at("verbose") == "false");
+    BOOST_TEST(plugin_->config().at("hard") == "true");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+class js_plugin_loader_test {
+protected:
+    irccd irccd_;
+    std::shared_ptr<plugin> plugin_;
+
+    js_plugin_loader_test()
+    {
+        irccd_.set_config(CMAKE_CURRENT_SOURCE_DIR "/irccd.conf");
+
+        auto loader = std::make_unique<js_plugin_loader>(irccd_);
+
+        loader->add_module(std::make_unique<js_irccd_module>());
+        loader->add_module(std::make_unique<js_plugin_module>());
+
+        irccd_.plugins().add_loader(std::move(loader));
+    }
+
+    void load(std::string name, std::string path)
+    {
+        irccd_.plugins().load(name, path);
+        plugin_ = irccd_.plugins().require(name);
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(js_plugin_loader_test_suite, js_plugin_loader_test)
+
+BOOST_AUTO_TEST_CASE(assign)
+{
+    load("test", CMAKE_CURRENT_SOURCE_DIR "/config-assign.js");
+
+    BOOST_TEST(plugin_->config().at("path") == "none");
+    BOOST_TEST(plugin_->config().at("verbose") == "false");
+    BOOST_TEST(plugin_->config().at("hard") == "true");
+}
+
+BOOST_AUTO_TEST_CASE(fill)
+{
+    load("test", CMAKE_CURRENT_SOURCE_DIR "/config-fill.js");
+
+    BOOST_TEST(plugin_->config().at("path") == "none");
+    BOOST_TEST(plugin_->config().at("verbose") == "false");
+    BOOST_TEST(plugin_->config().at("hard") == "true");
+}
+
+BOOST_AUTO_TEST_CASE(merge_after)
+{
+    load("test", CMAKE_CURRENT_SOURCE_DIR "/config-fill.js");
+
+    BOOST_TEST(plugin_->config().at("path") == "none");
+    BOOST_TEST(plugin_->config().at("verbose") == "false");
+    BOOST_TEST(plugin_->config().at("hard") == "true");
 }
 
 BOOST_AUTO_TEST_SUITE_END()