changeset 758:445c071e8efb

Irccd: Javascript API cleanup - Place all related code to `js` namespace, - Import duk.hpp and duk.cpp from libduk.
author David Demelier <markand@malikania.fr>
date Thu, 09 Aug 2018 13:07:19 +0200
parents 97b356010785
children 791d7591eca7
files irccdctl/cli.cpp irccdctl/cli.hpp irccdctl/main.cpp libirccd-js/CMakeLists.txt libirccd-js/irccd/js/directory_js_api.cpp libirccd-js/irccd/js/directory_js_api.hpp libirccd-js/irccd/js/directory_jsapi.cpp libirccd-js/irccd/js/directory_jsapi.hpp libirccd-js/irccd/js/duk.cpp libirccd-js/irccd/js/duk.hpp libirccd-js/irccd/js/duktape.hpp libirccd-js/irccd/js/duktape_vector.hpp libirccd-js/irccd/js/elapsed_timer_js_api.cpp libirccd-js/irccd/js/elapsed_timer_js_api.hpp libirccd-js/irccd/js/elapsed_timer_jsapi.cpp libirccd-js/irccd/js/elapsed_timer_jsapi.hpp libirccd-js/irccd/js/file_js_api.cpp libirccd-js/irccd/js/file_js_api.hpp libirccd-js/irccd/js/file_jsapi.cpp libirccd-js/irccd/js/file_jsapi.hpp libirccd-js/irccd/js/irccd_js_api.cpp libirccd-js/irccd/js/irccd_js_api.hpp libirccd-js/irccd/js/irccd_jsapi.cpp libirccd-js/irccd/js/irccd_jsapi.hpp libirccd-js/irccd/js/js_api.cpp libirccd-js/irccd/js/js_api.hpp libirccd-js/irccd/js/js_plugin.cpp libirccd-js/irccd/js/js_plugin.hpp libirccd-js/irccd/js/jsapi.cpp libirccd-js/irccd/js/jsapi.hpp libirccd-js/irccd/js/logger_js_api.cpp libirccd-js/irccd/js/logger_js_api.hpp libirccd-js/irccd/js/logger_jsapi.cpp libirccd-js/irccd/js/logger_jsapi.hpp libirccd-js/irccd/js/plugin_js_api.cpp libirccd-js/irccd/js/plugin_js_api.hpp libirccd-js/irccd/js/plugin_jsapi.cpp libirccd-js/irccd/js/plugin_jsapi.hpp libirccd-js/irccd/js/server_js_api.cpp libirccd-js/irccd/js/server_js_api.hpp libirccd-js/irccd/js/server_jsapi.cpp libirccd-js/irccd/js/server_jsapi.hpp libirccd-js/irccd/js/system_js_api.cpp libirccd-js/irccd/js/system_js_api.hpp libirccd-js/irccd/js/system_jsapi.cpp libirccd-js/irccd/js/system_jsapi.hpp libirccd-js/irccd/js/timer_js_api.cpp libirccd-js/irccd/js/timer_js_api.hpp libirccd-js/irccd/js/timer_jsapi.cpp libirccd-js/irccd/js/timer_jsapi.hpp libirccd-js/irccd/js/unicode_js_api.cpp libirccd-js/irccd/js/unicode_js_api.hpp libirccd-js/irccd/js/unicode_jsapi.cpp libirccd-js/irccd/js/unicode_jsapi.hpp libirccd-js/irccd/js/util_js_api.cpp libirccd-js/irccd/js/util_js_api.hpp libirccd-js/irccd/js/util_jsapi.cpp libirccd-js/irccd/js/util_jsapi.hpp libirccd-test/CMakeLists.txt libirccd-test/irccd/test/cli_fixture.cpp libirccd-test/irccd/test/cli_fixture.hpp libirccd-test/irccd/test/cli_test.cpp libirccd-test/irccd/test/cli_test.hpp libirccd-test/irccd/test/javascript_fixture.cpp libirccd-test/irccd/test/javascript_fixture.hpp libirccd-test/irccd/test/js_fixture.cpp libirccd-test/irccd/test/js_fixture.hpp libirccd-test/irccd/test/plugin_cli_test.cpp libirccd-test/irccd/test/plugin_cli_test.hpp libirccd-test/irccd/test/plugin_test.cpp libirccd-test/irccd/test/plugin_test.hpp libirccd-test/irccd/test/rule_cli_test.cpp libirccd-test/irccd/test/rule_cli_test.hpp libirccd-test/irccd/test/server_cli_test.cpp libirccd-test/irccd/test/server_cli_test.hpp tests/src/irccdctl/cli-plugin-config/main.cpp tests/src/irccdctl/cli-plugin-info/main.cpp tests/src/irccdctl/cli-plugin-list/main.cpp tests/src/irccdctl/cli-plugin-load/main.cpp tests/src/irccdctl/cli-plugin-reload/main.cpp tests/src/irccdctl/cli-plugin-unload/main.cpp tests/src/irccdctl/cli-rule-add/main.cpp tests/src/irccdctl/cli-rule-edit/main.cpp tests/src/irccdctl/cli-rule-info/main.cpp tests/src/irccdctl/cli-rule-list/main.cpp tests/src/irccdctl/cli-rule-move/main.cpp tests/src/irccdctl/cli-rule-remove/main.cpp tests/src/irccdctl/cli-server-disconnect/main.cpp tests/src/irccdctl/cli-server-info/main.cpp tests/src/irccdctl/cli-server-invite/main.cpp tests/src/irccdctl/cli-server-join/main.cpp tests/src/irccdctl/cli-server-kick/main.cpp tests/src/irccdctl/cli-server-list/main.cpp tests/src/irccdctl/cli-server-me/main.cpp tests/src/irccdctl/cli-server-message/main.cpp tests/src/irccdctl/cli-server-mode/main.cpp tests/src/irccdctl/cli-server-nick/main.cpp tests/src/irccdctl/cli-server-notice/main.cpp tests/src/irccdctl/cli-server-part/main.cpp tests/src/irccdctl/cli-server-reconnect/main.cpp tests/src/irccdctl/cli-server-topic/main.cpp tests/src/libirccd-js/CMakeLists.txt tests/src/libirccd-js/js-api-directory/CMakeLists.txt tests/src/libirccd-js/js-api-directory/main.cpp tests/src/libirccd-js/js-api-elapsedtimer/CMakeLists.txt tests/src/libirccd-js/js-api-elapsedtimer/main.cpp tests/src/libirccd-js/js-api-file/CMakeLists.txt tests/src/libirccd-js/js-api-file/main.cpp tests/src/libirccd-js/js-api-irccd/CMakeLists.txt tests/src/libirccd-js/js-api-irccd/main.cpp tests/src/libirccd-js/js-api-logger/CMakeLists.txt tests/src/libirccd-js/js-api-logger/main.cpp tests/src/libirccd-js/js-api-system/CMakeLists.txt tests/src/libirccd-js/js-api-system/main.cpp tests/src/libirccd-js/js-api-timer/CMakeLists.txt tests/src/libirccd-js/js-api-timer/main.cpp tests/src/libirccd-js/js-api-timer/timer.js tests/src/libirccd-js/js-api-unicode/CMakeLists.txt tests/src/libirccd-js/js-api-unicode/main.cpp tests/src/libirccd-js/js-api-util/CMakeLists.txt tests/src/libirccd-js/js-api-util/main.cpp tests/src/libirccd-js/js-plugin/main.cpp tests/src/libirccd-js/jsapi-directory/CMakeLists.txt tests/src/libirccd-js/jsapi-directory/main.cpp tests/src/libirccd-js/jsapi-elapsedtimer/CMakeLists.txt tests/src/libirccd-js/jsapi-elapsedtimer/main.cpp tests/src/libirccd-js/jsapi-file/CMakeLists.txt tests/src/libirccd-js/jsapi-file/main.cpp tests/src/libirccd-js/jsapi-irccd/CMakeLists.txt tests/src/libirccd-js/jsapi-irccd/main.cpp tests/src/libirccd-js/jsapi-logger/CMakeLists.txt tests/src/libirccd-js/jsapi-logger/main.cpp tests/src/libirccd-js/jsapi-system/CMakeLists.txt tests/src/libirccd-js/jsapi-system/main.cpp tests/src/libirccd-js/jsapi-timer/CMakeLists.txt tests/src/libirccd-js/jsapi-timer/main.cpp tests/src/libirccd-js/jsapi-timer/timer.js tests/src/libirccd-js/jsapi-unicode/CMakeLists.txt tests/src/libirccd-js/jsapi-unicode/main.cpp tests/src/libirccd-js/jsapi-util/CMakeLists.txt tests/src/libirccd-js/jsapi-util/main.cpp
diffstat 141 files changed, 8520 insertions(+), 8537 deletions(-) [+]
line wrap: on
line diff
--- a/irccdctl/cli.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/irccdctl/cli.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -34,6 +34,14 @@
 
 namespace {
 
+template <typename T>
+auto bind() noexcept -> cli::constructor
+{
+    return [] () noexcept {
+        return std::make_unique<T>();
+    };
+}
+
 auto format(std::vector<std::string> args) -> std::string
 {
     auto result = option::read(args, {
@@ -223,6 +231,38 @@
 
 // {{{ cli
 
+const std::vector<cli::constructor> cli::registry{
+    bind<plugin_config_cli>(),
+    bind<plugin_info_cli>(),
+    bind<plugin_list_cli>(),
+    bind<plugin_load_cli>(),
+    bind<plugin_reload_cli>(),
+    bind<plugin_unload_cli>(),
+    bind<rule_add_cli>(),
+    bind<rule_edit_cli>(),
+    bind<rule_info_cli>(),
+    bind<rule_info_cli>(),
+    bind<rule_list_cli>(),
+    bind<rule_move_cli>(),
+    bind<rule_remove_cli>(),
+    bind<server_connect_cli>(),
+    bind<server_disconnect_cli>(),
+    bind<server_info_cli>(),
+    bind<server_invite_cli>(),
+    bind<server_join_cli>(),
+    bind<server_kick_cli>(),
+    bind<server_list_cli>(),
+    bind<server_me_cli>(),
+    bind<server_message_cli>(),
+    bind<server_mode_cli>(),
+    bind<server_nick_cli>(),
+    bind<server_notice_cli>(),
+    bind<server_part_cli>(),
+    bind<server_reconnect_cli>(),
+    bind<server_topic_cli>(),
+    bind<watch_cli>()
+};
+
 void cli::recv_response(ctl::controller& ctl, nlohmann::json req, handler_t handler)
 {
     ctl.read([&ctl, req, handler, this] (auto code, auto message) {
--- a/irccdctl/cli.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/irccdctl/cli.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,6 +19,7 @@
 #ifndef IRCCD_CTL_CLI_HPP
 #define IRCCD_CTL_CLI_HPP
 
+#include <functional>
 #include <stdexcept>
 #include <string>
 #include <string_view>
@@ -38,10 +39,20 @@
 class cli {
 public:
     /**
-     * Convenient handler for request function.
+     * \brief Convenient handler for request function.
      */
     using handler_t = std::function<void (nlohmann::json)>;
 
+    /**
+     * \brief Command constructor factory.
+     */
+    using constructor = std::function<auto () -> std::unique_ptr<cli>>;
+
+    /**
+     * \brief Registry of all commands.
+     */
+    static const std::vector<constructor> registry;
+
 private:
     void recv_response(ctl::controller&, nlohmann::json, handler_t);
 
--- a/irccdctl/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/irccdctl/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -466,38 +466,11 @@
     -- argc;
     ++ argv;
 
-    const auto add = [] (auto c) {
-        commands.emplace(c->get_name(), std::move(c));
-    };
+    for (const auto& f : cli::registry) {
+        auto c = f();
 
-    add(std::make_unique<plugin_config_cli>());
-    add(std::make_unique<plugin_info_cli>());
-    add(std::make_unique<plugin_list_cli>());
-    add(std::make_unique<plugin_load_cli>());
-    add(std::make_unique<plugin_reload_cli>());
-    add(std::make_unique<plugin_unload_cli>());
-    add(std::make_unique<server_connect_cli>());
-    add(std::make_unique<server_disconnect_cli>());
-    add(std::make_unique<server_info_cli>());
-    add(std::make_unique<server_invite_cli>());
-    add(std::make_unique<server_join_cli>());
-    add(std::make_unique<server_kick_cli>());
-    add(std::make_unique<server_list_cli>());
-    add(std::make_unique<server_me_cli>());
-    add(std::make_unique<server_message_cli>());
-    add(std::make_unique<server_mode_cli>());
-    add(std::make_unique<server_nick_cli>());
-    add(std::make_unique<server_notice_cli>());
-    add(std::make_unique<server_part_cli>());
-    add(std::make_unique<server_reconnect_cli>());
-    add(std::make_unique<server_topic_cli>());
-    add(std::make_unique<rule_add_cli>());
-    add(std::make_unique<rule_edit_cli>());
-    add(std::make_unique<rule_list_cli>());
-    add(std::make_unique<rule_info_cli>());
-    add(std::make_unique<rule_move_cli>());
-    add(std::make_unique<rule_remove_cli>());
-    add(std::make_unique<watch_cli>());
+        commands.emplace(c->get_name(), std::move(c));
+    }
 }
 
 void do_connect()
--- a/libirccd-js/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ b/libirccd-js/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -22,40 +22,40 @@
 
 set(
     HEADERS
-    ${libirccd-js_SOURCE_DIR}/irccd/js/directory_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/duktape.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/duktape_vector.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/file_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/irccd_jsapi.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/directory_js_api.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/duk.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_js_api.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/file_js_api.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/irccd_js_api.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/logger_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/plugin_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/server_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/system_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/timer_jsapi.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/js_api.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/logger_js_api.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/plugin_js_api.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/server_js_api.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/system_js_api.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/timer_js_api.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/util_jsapi.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode_js_api.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/util_js_api.hpp
 )
 
 set(
     SOURCES
-    ${libirccd-js_SOURCE_DIR}/irccd/js/directory_jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/file_jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/irccd_jsapi.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/duk.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/directory_js_api.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_js_api.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/file_js_api.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/irccd_js_api.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/logger_jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/plugin_jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/server_jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/system_jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/timer_jsapi.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/js_api.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/logger_js_api.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/plugin_js_api.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/server_js_api.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/system_js_api.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/timer_js_api.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode_jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/util_jsapi.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode_js_api.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/util_js_api.cpp
 )
 
 irccd_define_library(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/directory_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,406 @@
+/*
+ * directory_js_api.cpp -- Irccd.Directory API
+ *
+ * Copyright (c) 2013-2018 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 <boost/predef/os.h>
+
+#include <irccd/sysconfig.hpp>
+
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <fstream>
+#include <regex>
+#include <stdexcept>
+#include <string>
+
+#include <irccd/fs_util.hpp>
+
+#include "directory_js_api.hpp"
+#include "irccd_js_api.hpp"
+#include "js_plugin.hpp"
+
+namespace fs = boost::filesystem;
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ wrap
+
+/*
+ * Wrap the function and raise appropriate error in case of need.
+ */
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+    try {
+        return handler();
+    } catch (const boost::system::system_error& ex) {
+        duk::raise(ctx, ex);
+    } catch (const std::system_error& ex) {
+        duk::raise(ctx, ex);
+    } catch (const std::exception& ex) {
+        duk::raise(ctx, ex);
+    }
+
+    return 0;
+}
+
+// }}}
+
+// {{{ path
+
+/*
+ * Get the path associated with the directory object as this.
+ */
+auto path(duk_context* ctx) -> std::string
+{
+    duk_push_this(ctx);
+    duk_get_prop_string(ctx, -1, "path");
+
+    if (duk_get_type(ctx, -1) != DUK_TYPE_STRING)
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Directory object");
+
+    const auto ret = duk::get<std::string>(ctx, -1);
+
+    if (ret.empty())
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "directory object has empty path");
+
+    duk_pop_n(ctx, 2);
+
+    return ret;
+}
+
+// }}}
+
+// {{{ find
+
+/*
+ * Generic find function for:
+ *
+ * - Irccd.Directory.find
+ * - Irccd.Directory.prototype.find
+ *
+ * The pattern_index is the argument where to test if the argument is a regex or
+ * a string.
+ */
+auto find(duk_context* ctx, std::string base, bool recursive, int pattern_index) -> duk_ret_t
+{
+    /*
+     * Helper for checking if it's a valid RegExp object.
+     */
+    const auto is_regex = [&] {
+        duk_get_global_string(ctx, "RegExp");
+        const auto result = duk_instanceof(ctx, pattern_index, -1);
+        duk_pop(ctx);
+
+        return result;
+    };
+
+    /*
+     * Helper for getting regex source.
+     */
+    const auto pattern = [&] {
+        duk_get_prop_string(ctx, pattern_index, "source");
+        const auto pattern = duk_to_string(ctx, -1);
+        duk_pop(ctx);
+
+        return pattern;
+    };
+
+    std::string path;
+
+    if (duk_is_string(ctx, pattern_index))
+        path = fs_util::find(base, duk::get<std::string>(ctx, pattern_index), recursive);
+    else if (is_regex())
+        path = fs_util::find(base, pattern(), recursive);
+    else
+        throw duk::type_error("pattern must be a string or a regex expression");
+
+    if (path.empty())
+        return 0;
+
+    return duk::push(ctx, path);
+}
+
+// }}}
+
+// {{{ remove
+
+/*
+ * Generic remove function for:
+ *
+ * - Irccd.Directory.remove
+ * - Irccd.Directory.prototype.remove
+ */
+auto remove(const std::string& path, bool recursive) -> duk_ret_t
+{
+    if (!boost::filesystem::is_directory(path))
+        throw std::system_error(make_error_code(std::errc::invalid_argument));
+
+    if (!recursive)
+        boost::filesystem::remove(path);
+    else
+        boost::filesystem::remove_all(path);
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Directory.prototype.find
+
+/*
+ * Method: Irccd.Directory.prototype.find(pattern, recursive)
+ * --------------------------------------------------------
+ *
+ * Synonym of Irccd.Directory.find(path, pattern, recursive) but the path is
+ * taken from the directory object.
+ *
+ * Arguments:
+ *   - pattern, the regular expression or file name,
+ *   - recursive, set to true to search recursively (default: false).
+ * Returns:
+ *   The path to the file or undefined if not found.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_prototype_find(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return find(ctx, path(ctx), duk::get<bool>(ctx, 1), 0);
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Directory.prototype.remove
+
+/*
+ * Method: Irccd.Directory.prototype.remove(recursive)
+ * --------------------------------------------------------
+ *
+ * Synonym of Directory.remove(recursive) but the path is taken from the
+ * directory object.
+ *
+ * Arguments:
+ *   - recursive, recursively or not (default: false).
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_prototype_remove(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return remove(path(ctx), duk::get<bool>(ctx, 0));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Directory [constructor]
+
+/*
+ * Function: Irccd.Directory(path) [constructor]
+ * --------------------------------------------------------
+ *
+ * Opens and read the directory at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the directory,
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_constructor(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        if (!duk_is_constructor_call(ctx))
+            return 0;
+
+        const auto path = duk::require<std::string>(ctx, 0);
+
+        if (!boost::filesystem::is_directory(path))
+            throw std::system_error(make_error_code(std::errc::invalid_argument));
+
+        duk_push_this(ctx);
+
+        // 'entries' property.
+        duk_push_string(ctx, "entries");
+        duk_push_array(ctx);
+
+        unsigned i = 0;
+        for (const auto& entry : boost::filesystem::directory_iterator(path)) {
+            duk_push_object(ctx);
+            duk::push(ctx, entry.path().filename().string());
+            duk_put_prop_string(ctx, -2, "name");
+            duk_push_int(ctx, entry.status().type());
+            duk_put_prop_string(ctx, -2, "type");
+            duk_put_prop_index(ctx, -2, i++);
+        }
+
+        duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+
+        // 'path' property.
+        duk::push(ctx, "path");
+        duk::push(ctx, path);
+        duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Directory.find
+
+/*
+ * Function: Irccd.Directory.find(path, pattern, recursive)
+ * --------------------------------------------------------
+ *
+ * Find an entry by a pattern or a regular expression.
+ *
+ * Arguments:
+ *   - path, the base path,
+ *   - pattern, the regular expression or file name,
+ *   - recursive, set to true to search recursively (default: false).
+ * Returns:
+ *   The path to the file or undefined on errors or not found.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_find(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return find(ctx, duk::require<std::string>(ctx, 0), duk::get<bool>(ctx, 2), 1);
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Directory.remove
+
+/*
+ * Function: Irccd.Directory.remove(path, recursive)
+ * --------------------------------------------------------
+ *
+ * Remove the directory optionally recursively.
+ *
+ * Arguments:
+ *   - path, the path to the directory,
+ *   - recursive, recursively or not (default: false).
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_remove(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return remove(duk::require<std::string>(ctx, 0), duk::get<bool>(ctx, 1));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Directory.mkdir
+
+/*
+ * Function: Irccd.Directory.mkdir(path, mode = 0700)
+ * --------------------------------------------------------
+ *
+ * Create a directory specified by path. It will create needed subdirectories
+ * just like you have invoked mkdir -p.
+ *
+ * Arguments:
+ *   - path, the path to the directory,
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_mkdir(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        boost::filesystem::create_directories(duk::require<std::string>(ctx, 0));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ definitions
+
+const duk_function_list_entry methods[] = {
+    { "find",           Directory_prototype_find,   DUK_VARARGS },
+    { "remove",         Directory_prototype_remove, 1           },
+    { nullptr,          nullptr,                    0           }
+};
+
+const duk_function_list_entry functions[] = {
+    { "find",           Directory_find,             DUK_VARARGS },
+    { "mkdir",          Directory_mkdir,            DUK_VARARGS },
+    { "remove",         Directory_remove,           DUK_VARARGS },
+    { nullptr,          nullptr,                    0           }
+};
+
+const duk_number_list_entry constants[] = {
+    { "TypeFile",       static_cast<int>(fs::regular_file)      },
+    { "TypeDir",        static_cast<int>(fs::directory_file)    },
+    { "TypeLink",       static_cast<int>(fs::symlink_file)      },
+    { "TypeBlock",      static_cast<int>(fs::block_file)        },
+    { "TypeCharacter",  static_cast<int>(fs::character_file)    },
+    { "TypeFifo",       static_cast<int>(fs::fifo_file)         },
+    { "TypeSocket",     static_cast<int>(fs::socket_file)       },
+    { "TypeUnknown",    static_cast<int>(fs::type_unknown)      },
+    { nullptr,          0                                       }
+};
+
+// }}}
+
+} // !namespace
+
+// {{{ directory_js_api
+
+auto directory_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.Directory";
+}
+
+void directory_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+    duk_push_c_function(plugin->get_context(), Directory_constructor, 2);
+    duk_put_number_list(plugin->get_context(), -1, constants);
+    duk_put_function_list(plugin->get_context(), -1, functions);
+
+#if BOOST_OS_WINDOWS
+    duk_push_string(plugin->get_context(), "\\");
+#else
+    duk_push_string(plugin->get_context(), "/");
+#endif
+
+    duk_put_prop_string(plugin->get_context(), -2, "separator");
+
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, methods);
+    duk_put_prop_string(plugin->get_context(), -2, "prototype");
+    duk_put_prop_string(plugin->get_context(), -2, "Directory");
+    duk_pop(plugin->get_context());
+}
+
+// }}}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/directory_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,50 @@
+/*
+ * directory_js_api.hpp -- Irccd.Directory API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_DIRECTORY_JS_API_HPP
+#define IRCCD_JS_DIRECTORY_JS_API_HPP
+
+/**
+ * \file directory_js_api.hpp
+ * \brief Irccd.Directory Javascript API.
+ */
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Irccd.Directory Javascript API.
+ * \ingroup js_api
+ */
+class directory_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc js_api::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_DIRECTORY_JS_API_HPP
--- a/libirccd-js/irccd/js/directory_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,376 +0,0 @@
-/*
- * directory_jsapi.cpp -- Irccd.Directory API
- *
- * Copyright (c) 2013-2018 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 <boost/predef/os.h>
-
-#include <irccd/sysconfig.hpp>
-
-#include <cerrno>
-#include <cstdio>
-#include <cstring>
-#include <fstream>
-#include <regex>
-#include <stdexcept>
-#include <string>
-
-#include <irccd/fs_util.hpp>
-
-#include "directory_jsapi.hpp"
-#include "irccd_jsapi.hpp"
-#include "js_plugin.hpp"
-
-namespace fs = boost::filesystem;
-
-namespace irccd {
-
-namespace {
-
-template <typename Handler>
-duk_ret_t wrap(duk_context* ctx, Handler handler)
-{
-    try {
-        return handler();
-    } catch (const boost::system::system_error& ex) {
-        dukx_throw(ctx, ex);
-    } catch (const std::system_error& ex) {
-        dukx_throw(ctx, ex);
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, ex);
-    }
-
-    return 0;
-}
-
-std::string path(duk_context *ctx)
-{
-    duk_push_this(ctx);
-    duk_get_prop_string(ctx, -1, "path");
-
-    if (duk_get_type(ctx, -1) != DUK_TYPE_STRING)
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Directory object");
-
-    const auto ret = dukx_get<std::string>(ctx, -1);
-
-    if (ret.empty())
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "directory object has empty path");
-
-    duk_pop_n(ctx, 2);
-
-    return ret;
-}
-
-/*
- * Generic find function for:
- *
- * - Irccd.Directory.find
- * - Irccd.Directory.prototype.find
- *
- * The pattern_index is the argument where to test if the argument is a regex or
- * a string.
- */
-duk_ret_t find(duk_context* ctx, std::string base, bool recursive, int pattern_index)
-{
-    /*
-     * Helper for checking if it's a valid RegExp object.
-     */
-    const auto is_regex = [&] {
-        duk_get_global_string(ctx, "RegExp");
-        const auto result = duk_instanceof(ctx, pattern_index, -1);
-        duk_pop(ctx);
-
-        return result;
-    };
-
-    /**
-     * Helper for getting regex source.
-     */
-    const auto pattern = [&] {
-        duk_get_prop_string(ctx, pattern_index, "source");
-        const auto pattern = duk_to_string(ctx, -1);
-        duk_pop(ctx);
-
-        return pattern;
-    };
-
-    std::string path;
-
-    if (duk_is_string(ctx, pattern_index))
-        path = fs_util::find(base, dukx_get<std::string>(ctx, pattern_index), recursive);
-    else if (is_regex())
-        path = fs_util::find(base, pattern(), recursive);
-    else
-        throw dukx_type_error("pattern must be a string or a regex expression");
-
-    if (path.empty())
-        return 0;
-
-    return dukx_push(ctx, path);
-}
-
-/*
- * Generic remove function for:
- *
- * - Irccd.Directory.remove
- * - Irccd.Directory.prototype.remove
- */
-duk_ret_t remove(const std::string& path, bool recursive)
-{
-    if (!boost::filesystem::is_directory(path))
-        throw std::system_error(make_error_code(std::errc::invalid_argument));
-
-    if (!recursive)
-        boost::filesystem::remove(path);
-    else
-        boost::filesystem::remove_all(path);
-
-    return 0;
-}
-
-// {{{ Irccd.Directory.prototype.find
-
-/*
- * Method: Irccd.Directory.prototype.find(pattern, recursive)
- * --------------------------------------------------------
- *
- * Synonym of Irccd.Directory.find(path, pattern, recursive) but the path is
- * taken from the directory object.
- *
- * Arguments:
- *   - pattern, the regular expression or file name,
- *   - recursive, set to true to search recursively (default: false).
- * Returns:
- *   The path to the file or undefined if not found.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t Directory_prototype_find(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return find(ctx, path(ctx), dukx_get<bool>(ctx, 1), 0);
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Directory.prototype.remove
-
-/*
- * Method: Irccd.Directory.prototype.remove(recursive)
- * --------------------------------------------------------
- *
- * Synonym of Directory.remove(recursive) but the path is taken from the
- * directory object.
- *
- * Arguments:
- *   - recursive, recursively or not (default: false).
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t Directory_prototype_remove(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return remove(path(ctx), dukx_get<bool>(ctx, 0));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Directory [constructor]
-
-/*
- * Function: Irccd.Directory(path) [constructor]
- * --------------------------------------------------------
- *
- * Opens and read the directory at the specified path.
- *
- * Arguments:
- *   - path, the path to the directory,
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t Directory_constructor(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        if (!duk_is_constructor_call(ctx))
-            return 0;
-
-        const auto path = dukx_require<std::string>(ctx, 0);
-
-        if (!boost::filesystem::is_directory(path))
-            throw std::system_error(make_error_code(std::errc::invalid_argument));
-
-        duk_push_this(ctx);
-
-        // 'entries' property.
-        duk_push_string(ctx, "entries");
-        duk_push_array(ctx);
-
-        unsigned i = 0;
-        for (const auto& entry : boost::filesystem::directory_iterator(path)) {
-            duk_push_object(ctx);
-            dukx_push(ctx, entry.path().filename().string());
-            duk_put_prop_string(ctx, -2, "name");
-            duk_push_int(ctx, entry.status().type());
-            duk_put_prop_string(ctx, -2, "type");
-            duk_put_prop_index(ctx, -2, i++);
-        }
-
-        duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-
-        // 'path' property.
-        dukx_push(ctx, "path");
-        dukx_push(ctx, path);
-        duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Directory.find
-
-/*
- * Function: Irccd.Directory.find(path, pattern, recursive)
- * --------------------------------------------------------
- *
- * Find an entry by a pattern or a regular expression.
- *
- * Arguments:
- *   - path, the base path,
- *   - pattern, the regular expression or file name,
- *   - recursive, set to true to search recursively (default: false).
- * Returns:
- *   The path to the file or undefined on errors or not found.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t Directory_find(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return find(ctx, dukx_require<std::string>(ctx, 0), dukx_get<bool>(ctx, 2), 1);
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Directory.remove
-
-/*
- * Function: Irccd.Directory.remove(path, recursive)
- * --------------------------------------------------------
- *
- * Remove the directory optionally recursively.
- *
- * Arguments:
- *   - path, the path to the directory,
- *   - recursive, recursively or not (default: false).
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t Directory_remove(duk_context *ctx)
-{
-    return wrap(ctx, [&] {
-        return remove(dukx_require<std::string>(ctx, 0), dukx_get<bool>(ctx, 1));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Directory.mkdir
-
-/*
- * Function: Irccd.Directory.mkdir(path, mode = 0700)
- * --------------------------------------------------------
- *
- * Create a directory specified by path. It will create needed subdirectories
- * just like you have invoked mkdir -p.
- *
- * Arguments:
- *   - path, the path to the directory,
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t Directory_mkdir(duk_context *ctx)
-{
-    return wrap(ctx, [&] {
-        boost::filesystem::create_directories(dukx_require<std::string>(ctx, 0));
-
-        return 0;
-    });
-}
-
-// }}}
-
-const duk_function_list_entry methods[] = {
-    { "find",           Directory_prototype_find,   DUK_VARARGS },
-    { "remove",         Directory_prototype_remove, 1           },
-    { nullptr,          nullptr,                    0           }
-};
-
-const duk_function_list_entry functions[] = {
-    { "find",           Directory_find,             DUK_VARARGS },
-    { "mkdir",          Directory_mkdir,            DUK_VARARGS },
-    { "remove",         Directory_remove,           DUK_VARARGS },
-    { nullptr,          nullptr,                    0           }
-};
-
-const duk_number_list_entry constants[] = {
-    { "TypeFile",       static_cast<int>(fs::regular_file)      },
-    { "TypeDir",        static_cast<int>(fs::directory_file)    },
-    { "TypeLink",       static_cast<int>(fs::symlink_file)      },
-    { "TypeBlock",      static_cast<int>(fs::block_file)        },
-    { "TypeCharacter",  static_cast<int>(fs::character_file)    },
-    { "TypeFifo",       static_cast<int>(fs::fifo_file)         },
-    { "TypeSocket",     static_cast<int>(fs::socket_file)       },
-    { "TypeUnknown",    static_cast<int>(fs::type_unknown)      },
-    { nullptr,          0                                       }
-};
-
-} // !namespace
-
-std::string directory_jsapi::get_name() const
-{
-    return "Irccd.Directory";
-}
-
-void directory_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_get_global_string(plugin->get_context(), "Irccd");
-    duk_push_c_function(plugin->get_context(), Directory_constructor, 2);
-    duk_put_number_list(plugin->get_context(), -1, constants);
-    duk_put_function_list(plugin->get_context(), -1, functions);
-
-#if BOOST_OS_WINDOWS
-    duk_push_string(plugin->get_context(), "\\");
-#else
-    duk_push_string(plugin->get_context(), "/");
-#endif
-
-    duk_put_prop_string(plugin->get_context(), -2, "separator");
-
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, methods);
-    duk_put_prop_string(plugin->get_context(), -2, "prototype");
-    duk_put_prop_string(plugin->get_context(), -2, "Directory");
-    duk_pop(plugin->get_context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/directory_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * directory_jsapi.hpp -- Irccd.Directory API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_DIRECTORY_JSAPI_HPP
-#define IRCCD_JS_DIRECTORY_JSAPI_HPP
-
-/**
- * \file directory_jsapi.hpp
- * \brief Irccd.Directory Javascript API.
- */
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.Directory Javascript API.
- * \ingroup jsapi
- */
-class directory_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc jsapi::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_DIRECTORY_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/duk.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,396 @@
+/*
+ * duk.cpp -- miscellaneous Duktape extras
+ *
+ * Copyright (c) 2017-2018 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 <cassert>
+#include <cstdio>
+
+#include "duk.hpp"
+
+namespace irccd::js::duk {
+
+// {{{ stack_guard
+
+stack_guard::stack_guard(duk_context* ctx, unsigned expected) noexcept
+#if !defined(NDEBUG)
+    : context_(ctx)
+    , expected_(expected)
+    , at_start_(duk_get_top(ctx))
+#endif
+{
+#if defined(NDEBUG)
+    (void)ctx;
+    (void)expected;
+#endif
+}
+
+stack_guard::~stack_guard() noexcept
+{
+#if !defined(NDEBUG)
+    auto result = duk_get_top(context_) - at_start_;
+
+    if (result != static_cast<int>(expected_)) {
+        std::fprintf(stderr, "Corrupt stack detection in stack_guard:\n");
+        std::fprintf(stderr, "  Size at start:           %d\n", at_start_);
+        std::fprintf(stderr, "  Size at end:             %d\n", duk_get_top(context_));
+        std::fprintf(stderr, "  Expected (user):         %u\n", expected_);
+        std::fprintf(stderr, "  Expected (adjusted):     %u\n", expected_ + at_start_);
+        std::fprintf(stderr, "  Difference count:       %+d\n", result - expected_);
+        std::abort();
+    }
+#endif
+}
+
+// }}}
+
+// {{{ context
+
+context::context() noexcept
+    : handle_(duk_create_heap_default(), duk_destroy_heap)
+{
+}
+
+context::operator duk_context*() noexcept
+{
+    return handle_.get();
+}
+
+context::operator duk_context*() const noexcept
+{
+    return handle_.get();
+}
+
+// }}}
+
+// {{{ stack_info
+
+stack_info::stack_info(std::string name,
+                       std::string message,
+                       std::string stack,
+                       std::string file_name,
+                       unsigned line_number) noexcept
+    : name_(std::move(name))
+    , message_(std::move(message))
+    , stack_(std::move(stack))
+    , file_name_(std::move(file_name))
+    , line_number_(line_number)
+{
+}
+
+auto stack_info::get_name() const noexcept -> const std::string&
+{
+    return name_;
+}
+
+auto stack_info::get_message() const noexcept -> const std::string&
+{
+    return message_;
+}
+
+auto stack_info::get_stack() const noexcept -> const std::string&
+{
+    return stack_;
+}
+
+auto stack_info::get_file_name() const noexcept -> const std::string&
+{
+    return file_name_;
+}
+
+auto stack_info::get_line_number() const noexcept -> unsigned
+{
+    return line_number_;
+}
+
+auto stack_info::what() const noexcept -> const char*
+{
+    return message_.c_str();
+}
+
+// }}}
+
+// {{{ error
+
+error::error(int type, std::string message) noexcept
+    : type_(type)
+    , message_(std::move(message))
+{
+}
+
+error::error(std::string message) noexcept
+    : message_(std::move(message))
+{
+}
+
+void error::create(duk_context* ctx) const
+{
+    duk_push_error_object(ctx, type_, "%s", message_.c_str());
+}
+
+// }}}
+
+// {{{ eval_error
+
+eval_error::eval_error(std::string message) noexcept
+    : error(DUK_ERR_EVAL_ERROR, std::move(message))
+{
+}
+
+// }}}
+
+// {{{ range_error
+
+range_error::range_error(std::string message) noexcept
+    : error(DUK_ERR_RANGE_ERROR, std::move(message))
+{
+}
+
+// }}}
+
+// {{{ reference_error
+
+reference_error::reference_error(std::string message) noexcept
+    : error(DUK_ERR_REFERENCE_ERROR, std::move(message))
+{
+}
+
+// }}}
+
+// {{{ syntax_error
+
+syntax_error::syntax_error(std::string message) noexcept
+    : error(DUK_ERR_SYNTAX_ERROR, std::move(message))
+{
+}
+
+// }}}
+
+// {{{ type_error
+
+type_error::type_error(std::string message) noexcept
+    : error(DUK_ERR_TYPE_ERROR, std::move(message))
+{
+}
+
+// }}}
+
+// {{{ uri_error
+
+uri_error::uri_error(std::string message) noexcept
+    : error(DUK_ERR_URI_ERROR, std::move(message))
+{
+}
+
+// }}}
+
+// {{{ get_stack
+
+auto get_stack(duk_context* ctx, int index, bool pop) -> stack_info
+{
+    index = duk_normalize_index(ctx, index);
+
+    duk_get_prop_string(ctx, index, "name");
+    auto name = duk_to_string(ctx, -1);
+    duk_get_prop_string(ctx, index, "message");
+    auto message = duk_to_string(ctx, -1);
+    duk_get_prop_string(ctx, index, "fileName");
+    auto file_name = duk_to_string(ctx, -1);
+    duk_get_prop_string(ctx, index, "lineNumber");
+    auto line_number = duk_to_uint(ctx, -1);
+    duk_get_prop_string(ctx, index, "stack");
+    auto stack = duk_to_string(ctx, -1);
+    duk_pop_n(ctx, 5);
+
+    if (pop)
+        duk_remove(ctx, index);
+
+    return {
+        std::move(name),
+        std::move(message),
+        std::move(stack),
+        std::move(file_name),
+        line_number
+    };
+}
+
+// }}}
+
+// {{{ type_traits<std::exception>
+
+void type_traits<std::exception>::raise(duk_context* ctx, const std::exception& ex)
+{
+    duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+}
+
+// }}}
+
+// {{{ type_traits<error>
+
+void type_traits<error>::raise(duk_context* ctx, const error& ex)
+{
+    ex.create(ctx);
+    duk_throw(ctx);
+}
+
+// }}}
+
+// {{{ type_traits<bool>
+
+void type_traits<bool>::push(duk_context* ctx, bool value)
+{
+    duk_push_boolean(ctx, value);
+}
+
+auto type_traits<bool>::get(duk_context* ctx, duk_idx_t index) -> bool
+{
+    return duk_get_boolean(ctx, index);
+}
+
+auto type_traits<bool>::require(duk_context* ctx, duk_idx_t index) -> bool
+{
+    return duk_require_boolean(ctx, index);
+}
+
+// }}}
+
+// {{{ type_traits<duk_double_t>
+
+void type_traits<duk_double_t>::push(duk_context* ctx, duk_double_t value)
+{
+    duk_push_number(ctx, value);
+}
+
+auto type_traits<duk_double_t>::get(duk_context* ctx, duk_idx_t index) -> duk_double_t
+{
+    return duk_get_number(ctx, index);
+}
+
+auto type_traits<duk_double_t>::require(duk_context* ctx, duk_idx_t index) -> duk_double_t
+{
+    return duk_require_number(ctx, index);
+}
+
+// }}}
+
+// {{{ type_traits<duk_int_t>
+
+void type_traits<duk_int_t>::push(duk_context* ctx, duk_int_t value)
+{
+    duk_push_int(ctx, value);
+}
+
+auto type_traits<duk_int_t>::get(duk_context* ctx, duk_idx_t index) -> duk_int_t
+{
+    return duk_get_int(ctx, index);
+}
+
+auto type_traits<duk_int_t>::require(duk_context* ctx, duk_idx_t index) -> duk_int_t
+{
+    return duk_require_int(ctx, index);
+}
+
+// }}}
+
+// {{{ type_traits<duk_uint_t>
+
+void type_traits<duk_uint_t>::push(duk_context* ctx, duk_uint_t value)
+{
+    duk_push_uint(ctx, value);
+}
+
+auto type_traits<duk_uint_t>::get(duk_context* ctx, duk_idx_t index) -> duk_uint_t
+{
+    return duk_get_uint(ctx, index);
+}
+
+auto type_traits<duk_uint_t>::require(duk_context* ctx, duk_idx_t index) -> duk_uint_t
+{
+    return duk_require_uint(ctx, index);
+}
+
+// }}}
+
+// {{{ type_traits<const char*>
+
+void type_traits<const char*>::push(duk_context* ctx, const char* value)
+{
+    duk_push_string(ctx, value);
+}
+
+auto type_traits<const char*>::get(duk_context* ctx, duk_idx_t index) -> const char*
+{
+    return duk_get_string(ctx, index);
+}
+
+auto type_traits<const char*>::require(duk_context* ctx, duk_idx_t index) -> const char*
+{
+    return duk_require_string(ctx, index);
+}
+
+// }}}
+
+// {{{ type_traits<std::string>
+
+void type_traits<std::string>::push(duk_context* ctx, const std::string& value)
+{
+    duk_push_lstring(ctx, value.data(), value.size());
+}
+
+auto type_traits<std::string>::get(duk_context* ctx, duk_idx_t index) -> std::string
+{
+    duk_size_t length;
+    const char* str = duk_get_lstring(ctx, index, &length);
+
+    return { str, length };
+}
+
+auto type_traits<std::string>::require(duk_context* ctx, duk_idx_t index) -> std::string
+{
+    duk_size_t length;
+    const char* str = duk_require_lstring(ctx, index, &length);
+
+    return { str, length };
+}
+
+// }}}
+
+// {{{ type_traits<std::string_view>
+
+void type_traits<std::string_view>::push(duk_context* ctx, std::string_view value)
+{
+    duk_push_lstring(ctx, value.data(), value.size());
+}
+
+auto type_traits<std::string_view>::get(duk_context* ctx, duk_idx_t index) -> std::string_view
+{
+    duk_size_t length;
+    const char* str = duk_get_lstring(ctx, index, &length);
+
+    return { str, length };
+}
+
+auto type_traits<std::string_view>::require(duk_context* ctx, duk_idx_t index) -> std::string_view
+{
+    duk_size_t length;
+    const char* str = duk_require_lstring(ctx, index, &length);
+
+    return { str, length };
+}
+
+// }}}
+
+} // !irccd::js::duk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/duk.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,885 @@
+/*
+ * duk.hpp -- miscellaneous Duktape extras
+ *
+ * Copyright (c) 2017-2018 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.
+ */
+
+#ifndef DUKTAPE_HPP
+#define DUKTAPE_HPP
+
+/**
+ * \file duk.hpp
+ * \brief Miscellaneous Duktape extras
+ * \author David Demelier <markand@malikania.fr>
+ * \version 0.2.0
+ */
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "duktape.h"
+
+/**
+ * \brief Miscellaneous Duktape extras.
+ */
+namespace irccd::js::duk {
+
+// {{{ stack_guard
+
+/**
+ * \brief Stack sanity checker.
+ *
+ * Instanciate this class where you need to manipulate the Duktape stack outside
+ * a Duktape/C function, its destructor will examinate if the stack size matches
+ * the user expected size.
+ *
+ * When compiled with NDEBUG, this class does nothing.
+ *
+ * To use it, just declare an lvalue at the beginning of your function.
+ */
+class stack_guard {
+#if !defined(NDEBUG)
+private:
+    duk_context* context_;
+    unsigned expected_;
+    int at_start_;
+#endif
+
+public:
+    /**
+     * Create the stack checker.
+     *
+     * No-op if NDEBUG is set.
+     *
+     * \param ctx the context
+     * \param expected the size expected relative to the already existing values
+     */
+    stack_guard(duk_context* ctx, unsigned expected = 0) noexcept;
+
+    /**
+     * Verify the expected size.
+     *
+     * No-op if NDEBUG is set.
+     */
+    ~stack_guard() noexcept;
+};
+
+// }}}
+
+// {{{ context
+
+/**
+ * \brief RAII based Duktape handler.
+ *
+ * This class is implicitly convertible to duk_context for convenience.
+ */
+class context {
+private:
+    std::unique_ptr<duk_context, void (*)(duk_context*)> handle_;
+
+    context(const context&) = delete;
+    void operator=(const context&) = delete;
+
+public:
+    /**
+     * Create default context.
+     */
+    context() noexcept;
+
+    /**
+     * Default move constructor.
+     */
+    context(context&&) noexcept = default;
+
+    /**
+     * Convert the context to the native Duktape/C type.
+     *
+     * \return the duk_context
+     */
+    operator duk_context*() noexcept;
+
+    /**
+     * Convert the context to the native Duktape/C type.
+     *
+     * \return the duk_context
+     */
+    operator duk_context*() const noexcept;
+
+    /**
+     * Default move assignment operator.
+     *
+     * \return this
+     */
+    auto operator=(context&&) noexcept -> context& = default;
+};
+
+// }}}
+
+// {{{ stack_info
+
+/**
+ * \brief Error description.
+ *
+ * This class fills the fields got in an Error object.
+ */
+class stack_info : public std::exception {
+private:
+    std::string name_;
+    std::string message_;
+    std::string stack_;
+    std::string file_name_;
+    unsigned line_number_;
+
+public:
+    /**
+     * Construct the stack information.
+     *
+     * \param name the exception name (e.g. ReferenceError)
+     * \param message the error message
+     * \param stack the stack trace
+     * \param file_name the optional filename
+     * \param line_number the optional line number
+     */
+    stack_info(std::string name,
+               std::string message,
+               std::string stack,
+               std::string file_name,
+               unsigned line_number = 0) noexcept;
+
+    /**
+     * Get the exception name.
+     *
+     * \return the exception name (e.g. ReferenceError)
+     */
+    auto get_name() const noexcept -> const std::string&;
+
+    /**
+     * Get the error message.
+     *
+     * \return the message
+     */
+    auto get_message() const noexcept -> const std::string&;
+
+    /**
+     * Get the stack trace.
+     *
+     * \return the stack
+     */
+    auto get_stack() const noexcept -> const std::string&;
+
+    /**
+     * Get the optional file name.
+     *
+     * \return the file name
+     */
+    auto get_file_name() const noexcept -> const std::string&;
+
+    /**
+     * Get the line number.
+     *
+     * \return the line number
+     */
+    auto get_line_number() const noexcept -> unsigned;
+
+    /**
+     * Get the error message. This effectively returns message field.
+     *
+     * \return the message
+     */
+    auto what() const noexcept -> const char* override;
+};
+
+// }}}
+
+// {{{ type_traits
+
+/**
+ * \brief Operations on different types.
+ *
+ * This class provides some functions for the given type, depending on the
+ * nature of the function.
+ *
+ * For example, push will call type_traits<T>::push static function
+ * if the type_traits is implemented for that given T type.
+ *
+ * This helps passing/getting function between Javascript and C++ code.
+ *
+ * Example:
+ *
+ * ```cpp
+ * push(ctx, 123);     // Uses type_traits<int>
+ * push(ctx, true);    // Uses type_traits<bool>
+ * ```
+ *
+ * This class is specialized for the following types:
+ *
+ *   - `bool`,
+ *   - `duk_int_t`,
+ *   - `duk_uint_t`,
+ *   - `duk_double_t`,
+ *   - `const char*`,
+ *   - `std::string`,
+ *   - `std::string_view`.
+ *
+ * Regarding exceptions, this class is specialized for the following types:
+ *
+ *   - error,
+ *   - std::exception,
+ *
+ * \see push
+ * \see get
+ * \see require
+ * \see raise
+ */
+template <typename T>
+struct type_traits;
+
+// }}}
+
+// {{{ push
+
+/**
+ * Generic push function.
+ *
+ * This function calls type_traits<T>::push if specialized.
+ *
+ * \param ctx the Duktape context
+ * \param value the forwarded value
+ * \return 1 for convenience
+ */
+template <typename T>
+auto push(duk_context* ctx, T&& value) -> int
+{
+    using Type = typename std::decay<T>::type;
+
+    type_traits<Type>::push(ctx, std::forward<T>(value));
+
+    return 1;
+}
+
+// }}}
+
+// {{{ get
+
+/**
+ * Generic get function.
+ *
+ * This functions calls type_traits<T>::get if specialized.
+ *
+ * \param ctx the Duktape context
+ * \param index the value index
+ * \return the converted value
+ */
+template <typename T>
+auto get(duk_context* ctx, duk_idx_t index)
+{
+    using Type = typename std::decay<T>::type;
+
+    return type_traits<Type>::get(ctx, index);
+}
+
+// }}}
+
+// {{{ require
+
+/**
+ * Generic require function.
+ *
+ * This functions calls type_traits<T>::require if specialized.
+ *
+ * \param ctx the Duktape context
+ * \param index the value index
+ * \return the converted value
+ */
+template <typename T>
+auto require(duk_context* ctx, duk_idx_t index)
+{
+    using Type = typename std::decay<T>::type;
+
+    return type_traits<Type>::require(ctx, index);
+}
+
+// }}}
+
+// {{{ error
+
+/**
+ * \brief Base ECMAScript error class.
+ * \warning Override the function create for your own exceptions
+ */
+class error {
+private:
+    int type_{DUK_ERR_ERROR};
+    std::string message_;
+
+protected:
+    /**
+     * Constructor with a type of error specified, specially designed for
+     * derived errors.
+     *
+     * \param type of error (e.g. DUK_ERR_ERROR)
+     * \param message the message
+     */
+    error(int type, std::string message) noexcept;
+
+public:
+    /**
+     * Constructor with a message.
+     *
+     * \param message the message
+     */
+    error(std::string message) noexcept;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    ~error() = default;
+
+    /**
+     * Create the exception on the stack.
+     *
+     * \note the default implementation search for the global variables
+     * \param ctx the context
+     */
+    void create(duk_context* ctx) const;
+};
+
+// }}}
+
+// {{{ eval_error
+
+/**
+ * \brief Error in eval() function.
+ */
+class eval_error : public error {
+public:
+    /**
+     * Construct an EvalError.
+     *
+     * \param message the message
+     */
+    eval_error(std::string message) noexcept;
+};
+
+// }}}
+
+// {{{ range_error
+
+/**
+ * \brief Value is out of range.
+ */
+class range_error : public error {
+public:
+    /**
+     * Construct an RangeError.
+     *
+     * \param message the message
+     */
+    range_error(std::string message) noexcept;
+};
+
+// }}}
+
+// {{{ reference_error
+
+/**
+ * \brief Trying to use a variable that does not exist.
+ */
+class reference_error : public error {
+public:
+    /**
+     * Construct an ReferenceError.
+     *
+     * \param message the message
+     */
+    reference_error(std::string message) noexcept;
+};
+
+// }}}
+
+// {{{ syntax_error
+
+/**
+ * \brief Syntax error in the script.
+ */
+class syntax_error : public error {
+public:
+    /**
+     * Construct an SyntaxError.
+     *
+     * \param message the message
+     */
+    syntax_error(std::string message) noexcept;
+};
+
+// }}}
+
+// {{{ type_error
+
+/**
+ * \brief Invalid type given.
+ */
+class type_error : public error {
+public:
+    /**
+     * Construct an TypeError.
+     *
+     * \param message the message
+     */
+    type_error(std::string message) noexcept;
+};
+
+// }}}
+
+// {{{ uri_error
+
+/**
+ * \brief URI manipulation failure.
+ */
+class uri_error : public error {
+public:
+    /**
+     * Construct an URIError.
+     *
+     * \param message the message
+     */
+    uri_error(std::string message) noexcept;
+};
+
+// }}}
+
+// {{{ raise
+
+/**
+ * Create an exception into the stack and throws it.
+ *
+ * This function needs the following requirements in type_traits
+ *
+ * ```cpp
+ * static void raise(duk_context*, Error);
+ * ```
+ *
+ * Error can be any kind of value, it is forwarded.
+ *
+ * \param ctx the Duktape context
+ * \param error the error object
+ */
+template <typename Error>
+void raise(duk_context* ctx, Error&& error)
+{
+    using type = std::decay_t<Error>;
+
+    type_traits<type>::raise(ctx, std::forward<Error>(error));
+}
+
+// }}}
+
+// {{{ get_stack
+
+/**
+ * Get the error object when a JavaScript error has been thrown (e.g. eval
+ * failure).
+ *
+ * \param ctx the context
+ * \param index the index
+ * \param pop if true, also remove the exception from the stack
+ * \return the information
+ */
+auto get_stack(duk_context* ctx, int index, bool pop = true) -> stack_info;
+
+// }}}
+
+// {{{ type_traits<std::exception>
+
+/**
+ * \brief Specialization for std::exception.
+ */
+template <>
+struct type_traits<std::exception> {
+    /**
+     * Raise a Error object.
+     *
+     * \param ctx the Duktape context
+     * \param ex the exception
+     */
+    static void raise(duk_context* ctx, const std::exception& ex);
+};
+
+// }}}
+
+// {{{ type_traits<error>
+
+/**
+ * \brief Specialization for error.
+ */
+template <>
+struct type_traits<error> {
+    /**
+     * Raise a error.
+     *
+     * \param ctx the Duktape context
+     * \param ex the exception
+     */
+    static void raise(duk_context* ctx, const error& ex);
+};
+
+// }}}
+
+// {{{ type_traits<bool>
+
+/**
+ * \brief Specialization for bool.
+ */
+template <>
+struct type_traits<bool> {
+    /**
+     * Push a boolean.
+     *
+     * Uses duk_push_boolean
+     *
+     * \param ctx the Duktape context
+     * \param value the value
+     */
+    static void push(duk_context* ctx, bool value);
+
+    /**
+     * Get a boolean.
+     *
+     * Uses duk_get_boolean.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto get(duk_context* ctx, duk_idx_t index) -> bool;
+
+    /**
+     * Require a boolean.
+     *
+     * Uses duk_require_boolean.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto require(duk_context* ctx, duk_idx_t index) -> bool;
+};
+
+// }}}
+
+// {{{ type_traits<duk_double_t>
+
+/**
+ * \brief Specialization for duk_double_t.
+ */
+template <>
+struct type_traits<duk_double_t> {
+    /**
+     * Push a double.
+     *
+     * Uses duk_push_number
+     *
+     * \param ctx the Duktape context
+     * \param value the value
+     */
+    static void push(duk_context* ctx, duk_double_t value);
+
+    /**
+     * Get a double.
+     *
+     * Uses duk_get_number.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto get(duk_context* ctx, duk_idx_t index) -> duk_double_t;
+
+    /**
+     * Require a double.
+     *
+     * Uses duk_require_double.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto require(duk_context* ctx, duk_idx_t index) -> duk_double_t;
+};
+
+// }}}
+
+// {{{ type_traits<duk_int_t>
+
+/**
+ * \brief Specialization for duk_int_t.
+ */
+template <>
+struct type_traits<duk_int_t> {
+    /**
+     * Push an int.
+     *
+     * Uses duk_push_int
+     *
+     * \param ctx the Duktape context
+     * \param value the value
+     */
+    static void push(duk_context* ctx, duk_int_t value);
+
+    /**
+     * Get an int.
+     *
+     * Uses duk_get_number.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto get(duk_context* ctx, duk_idx_t index) -> duk_int_t;
+
+    /**
+     * Require an int.
+     *
+     * Uses duk_require_int.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto require(duk_context* ctx, duk_idx_t index) -> duk_int_t;
+};
+
+// }}}
+
+// {{{ type_traits<duk_uint_t>
+
+/**
+ * \brief Specialization for duk_uint_t.
+ */
+template <>
+struct type_traits<duk_uint_t> {
+    /**
+     * Push an unsigned int.
+     *
+     * Uses duk_push_uint
+     *
+     * \param ctx the Duktape context
+     * \param value the value
+     */
+    static void push(duk_context* ctx, duk_uint_t value);
+
+    /**
+     * Get an unsigned int.
+     *
+     * Uses duk_get_uint.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto get(duk_context* ctx, duk_idx_t index) -> duk_uint_t;
+
+    /**
+     * Require an unsigned int.
+     *
+     * Uses duk_require_uint.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto require(duk_context* ctx, duk_idx_t index) -> duk_uint_t;
+};
+
+// }}}
+
+// {{{ type_traits<const char*>
+
+/**
+ * \brief Specialization for C strings.
+ */
+template <>
+struct type_traits<const char*> {
+    /**
+     * Push a C string.
+     *
+     * Uses duk_push_string
+     *
+     * \param ctx the Duktape context
+     * \param value the value
+     */
+    static void push(duk_context* ctx, const char* value);
+
+    /**
+     * Get a C string.
+     *
+     * Uses duk_get_string.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto get(duk_context* ctx, duk_idx_t index) -> const char*;
+
+    /**
+     * Require a C string.
+     *
+     * Uses duk_require_string.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto require(duk_context* ctx, duk_idx_t index) -> const char*;
+};
+
+// }}}
+
+// {{{ type_traits<std::string>
+
+/**
+ * \brief Specialization for C++ std::strings.
+ */
+template <>
+struct type_traits<std::string> {
+    /**
+     * Push a C++ std::string.
+     *
+     * Uses duk_push_lstring
+     *
+     * \param ctx the Duktape context
+     * \param value the value
+     */
+    static void push(duk_context* ctx, const std::string& value);
+
+    /**
+     * Get a C++ std::string.
+     *
+     * Uses duk_get_lstring.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto get(duk_context* ctx, duk_idx_t index) -> std::string;
+
+    /**
+     * Require a C++ std::string.
+     *
+     * Uses duk_require_lstring.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto require(duk_context* ctx, duk_idx_t index) -> std::string;
+};
+
+// }}}
+
+// {{{ type_traits<std::string_view>
+
+/**
+ * \brief Specialization for C++ std::string_views.
+ */
+template <>
+struct type_traits<std::string_view> : public std::true_type {
+    /**
+     * Push a C++ std::string_view.
+     *
+     * Uses duk_push_lstring
+     *
+     * \param ctx the Duktape context
+     * \param value the value
+     */
+    static void push(duk_context* ctx, std::string_view value);
+
+    /**
+     * Get a C++ std::string_view.
+     *
+     * Uses duk_get_lstring.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto get(duk_context* ctx, duk_idx_t index) -> std::string_view;
+
+    /**
+     * Require a C++ std::string_view.
+     *
+     * Uses duk_require_lstring.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the converted value
+     */
+    static auto require(duk_context* ctx, duk_idx_t index) -> std::string_view;
+};
+
+// }}}
+
+// {{{ type_traits<std::vector<T>>
+
+/**
+ * \brief Specialization for std::vector.
+ */
+template <typename T>
+struct type_traits<std::vector<T>> {
+    /**
+     * Push a vector.
+     *
+     * Uses duk_push_lstring
+     *
+     * \param ctx the Duktape context
+     * \param value the value
+     */
+    static void push(duk_context* ctx, const std::vector<T>& values)
+    {
+        using size_type = typename std::vector<T>::size_type;
+
+        duk_push_array(ctx);
+
+        for (size_type i = 0; i < values.size(); ++i) {
+            type_traits<T>::push(ctx, values[i]);
+            duk_put_prop_index(ctx, -2, i);
+        }
+    }
+
+    static auto get(duk_context* ctx, duk_idx_t index) -> std::vector<T>
+    {
+        using size_type = typename std::vector<T>::size_type;
+
+        std::vector<T> result;
+        size_type length = duk_get_length(ctx, index);
+
+        for (size_type i = 0; i < length; ++i) {
+            duk_get_prop_index(ctx, index, i);
+            result.push_back(type_traits<T>::get(ctx, -1));
+            duk_pop(ctx);
+        }
+
+        return result;
+    }
+};
+
+// }}}
+
+} // !irccd::js::duk
+
+#endif // !DUKTAPE_HPP
--- a/libirccd-js/irccd/js/duktape.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1147 +0,0 @@
-/*
- * duktape.hpp -- miscellaneous Duktape extras
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_DUKTAPE_HPP
-#define IRCCD_JS_DUKTAPE_HPP
-
-/**
- * \file duktape.hpp
- * \brief Miscellaneous Duktape extras
- * \author David Demelier <markand@malikania.fr>
- * \version 0.2.0
- */
-
-#include <cassert>
-#include <cstdio>
-#include <initializer_list>
-#include <iterator>
-#include <memory>
-#include <string>
-#include <string_view>
-#include <type_traits>
-#include <utility>
-
-#include "duktape.h"
-
-namespace irccd {
-
-/**
- * \brief Stack sanity checker.
- *
- * Instanciate this class where you need to manipulate the Duktape stack outside
- * a Duktape/C function, its destructor will examinate if the stack size matches
- * the user expected size.
- *
- * When compiled with NDEBUG, this class does nothing.
- *
- * To use it, just declare an lvalue at the beginning of your function.
- */
-class dukx_stack_assert {
-#if !defined(NDEBUG)
-private:
-    duk_context* context_;
-    unsigned expected_;
-    int at_start_;
-#endif
-
-public:
-    /**
-     * Create the stack checker.
-     *
-     * No-op if NDEBUG is set.
-     *
-     * \param ctx the context
-     * \param expected the size expected relative to the already existing values
-     */
-    inline dukx_stack_assert(duk_context* ctx, unsigned expected = 0) noexcept
-#if !defined(NDEBUG)
-        : context_(ctx)
-        , expected_(expected)
-        , at_start_(duk_get_top(ctx))
-#endif
-    {
-#if defined(NDEBUG)
-        (void)ctx;
-        (void)expected;
-#endif
-    }
-
-    /**
-     * Verify the expected size.
-     *
-     * No-op if NDEBUG is set.
-     */
-    inline ~dukx_stack_assert() noexcept
-    {
-#if !defined(NDEBUG)
-        auto result = duk_get_top(context_) - at_start_;
-
-        if (result != static_cast<int>(expected_)) {
-            std::fprintf(stderr, "Corrupt stack detection in dukx_stack_assert:\n");
-            std::fprintf(stderr, "  Size at start:           %d\n", at_start_);
-            std::fprintf(stderr, "  Size at end:             %d\n", duk_get_top(context_));
-            std::fprintf(stderr, "  Expected (user):         %u\n", expected_);
-            std::fprintf(stderr, "  Expected (adjusted):     %u\n", expected_ + at_start_);
-            std::fprintf(stderr, "  Difference count:       %+d\n", result - expected_);
-            std::abort();
-        }
-#endif
-    }
-};
-
-/**
- * \brief RAII based Duktape handler.
- *
- * This class is implicitly convertible to duk_context for convenience.
- */
-class dukx_context {
-private:
-    std::unique_ptr<duk_context, void (*)(duk_context*)> handle_;
-
-    dukx_context(const dukx_context&) = delete;
-    dukx_context &operator=(const dukx_context&) = delete;
-
-public:
-    /**
-     * Create default context.
-     */
-    inline dukx_context() noexcept
-        : handle_(duk_create_heap_default(), duk_destroy_heap)
-    {
-    }
-
-    /**
-     * Default move constructor.
-     */
-    dukx_context(dukx_context&&) noexcept = default;
-
-    /**
-     * Convert the context to the native Duktape/C type.
-     *
-     * \return the duk_context
-     */
-    inline operator duk_context*() noexcept
-    {
-        return handle_.get();
-    }
-
-    /**
-     * Convert the context to the native Duktape/C type.
-     *
-     * \return the duk_context
-     */
-    inline operator duk_context*() const noexcept
-    {
-        return handle_.get();
-    }
-
-    /**
-     * Default move assignment operator.
-     *
-     * \return this
-     */
-    dukx_context& operator=(dukx_context&&) noexcept = delete;
-};
-
-/**
- * \brief Error description.
- *
- * This class fills the fields got in an Error object.
- */
-class dukx_stack_info : public std::exception {
-private:
-    std::string name_;
-    std::string message_;
-    std::string stack_;
-    std::string file_name_;
-    int line_number_;
-
-public:
-    /**
-     * Construct the stack information.
-     *
-     * \param name the exception name (e.g. ReferenceError)
-     * \param message the error message
-     * \param stack the stack trace
-     * \param file_name the optional filename
-     * \param line_number the optional line number
-     */
-    inline dukx_stack_info(std::string name,
-                           std::string message,
-                           std::string stack,
-                           std::string file_name,
-                           int line_number = 0) noexcept
-        : name_(std::move(name))
-        , message_(std::move(message))
-        , stack_(std::move(stack))
-        , file_name_(std::move(file_name))
-        , line_number_(line_number)
-    {
-    }
-
-    /**
-     * Get the exception name.
-     *
-     * \return the exception name (e.g. ReferenceError)
-     */
-    inline const std::string& name() const noexcept
-    {
-        return name_;
-    }
-
-    /**
-     * Get the error message.
-     *
-     * \return the message
-     */
-    inline const std::string& message() const noexcept
-    {
-        return message_;
-    }
-
-    /**
-     * Get the stack trace.
-     *
-     * \return the stack
-     */
-    inline const std::string& stack() const noexcept
-    {
-        return stack_;
-    }
-
-    /**
-     * Get the optional file name.
-     *
-     * \return the file name
-     */
-    inline const std::string& file_name() const noexcept
-    {
-        return file_name_;
-    }
-
-    /**
-     * Get the line number.
-     *
-     * \return the line number
-     */
-    inline int line_number() const noexcept
-    {
-        return line_number_;
-    }
-
-    /**
-     * Get the error message. This effectively returns message field.
-     *
-     * \return the message
-     */
-    const char* what() const noexcept override
-    {
-        return message_.c_str();
-    }
-};
-
-/**
- * \brief Base ECMAScript error class.
- * \warning Override the function create for your own exceptions
- */
-class dukx_error {
-private:
-    int type_{DUK_ERR_ERROR};
-    std::string message_;
-
-protected:
-    /**
-     * Constructor with a type of error specified, specially designed for
-     * derived errors.
-     *
-     * \param type of error (e.g. DUK_ERR_ERROR)
-     * \param message the message
-     */
-    inline dukx_error(int type, std::string message) noexcept
-        : type_(type)
-        , message_(std::move(message))
-    {
-    }
-
-public:
-    /**
-     * Constructor with a message.
-     *
-     * \param message the message
-     */
-    inline dukx_error(std::string message) noexcept
-        : message_(std::move(message))
-    {
-    }
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~dukx_error() = default;
-
-    /**
-     * Get internal Duktape error (e.g. DUK_ERR_TYPE_ERROR)
-     *
-     * \return the type
-     */
-    inline int get_type() const noexcept
-    {
-        return type_;
-    }
-
-    /**
-     * Get the error message.
-     *
-     * \return the message
-     */
-    inline const std::string& get_message() const noexcept
-    {
-        return message_;
-    }
-};
-
-/**
- * \brief Error in eval() function.
- */
-class dukx_eval_error : public dukx_error {
-public:
-    /**
-     * Construct an EvalError.
-     *
-     * \param message the message
-     */
-    inline dukx_eval_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_EVAL_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Value is out of range.
- */
-class dukx_range_error : public dukx_error {
-public:
-    /**
-     * Construct an RangeError.
-     *
-     * \param message the message
-     */
-    inline dukx_range_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_RANGE_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Trying to use a variable that does not exist.
- */
-class dukx_reference_error : public dukx_error {
-public:
-    /**
-     * Construct an ReferenceError.
-     *
-     * \param message the message
-     */
-    inline dukx_reference_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_REFERENCE_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Syntax error in the script.
- */
-class dukx_syntax_error : public dukx_error {
-public:
-    /**
-     * Construct an SyntaxError.
-     *
-     * \param message the message
-     */
-    inline dukx_syntax_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_SYNTAX_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Invalid type given.
- */
-class dukx_type_error : public dukx_error {
-public:
-    /**
-     * Construct an TypeError.
-     *
-     * \param message the message
-     */
-    inline dukx_type_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_TYPE_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief URI manipulation failure.
- */
-class dukx_uri_error : public dukx_error {
-public:
-    /**
-     * Construct an URIError.
-     *
-     * \param message the message
-     */
-    inline dukx_uri_error(std::string message) noexcept
-        : dukx_error(DUK_ERR_URI_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Operations on different types.
- *
- * This class provides some functions for the given type, depending on the
- * nature of the function.
- *
- * For example, dukx_push will call dukx_type_traits<T>::push static function
- * if the dukx_type_traits is implemented for that given T type.
- *
- * This helps passing/getting function between Javascript and C++ code.
- *
- * Example:
- *
- * ```cpp
- * dukx_push(ctx, 123);     // Uses dukx_type_traits<int>
- * dukx_push(ctx, true);    // Uses dukx_type_traits<bool>
- * ```
- *
- * This class is specialized for the following types:
- *
- *   - `bool`,
- *   - `duk_int_t`,
- *   - `duk_uint_t`,
- *   - `duk_double_t`,
- *   - `const char*`,
- *   - `std::string`
- *
- * It is also specialized for all exceptions types:
- *
- *   - `dukx_error`,
- *   - `dukx_eval_error`,
- *   - `dukx_range_error`,
- *   - `dukx_reference_error`,
- *   - `dukx_syntax_error`,
- *   - `dukx_type_error`,
- *   - `dukx_uri_error`.
- *
- * And more general std::exception:
- *
- *   - `std::exception`.
- */
-template <typename T>
-class dukx_type_traits : public std::false_type {
-};
-
-/**
- * \brief Specialization for bool.
- */
-template <>
-class dukx_type_traits<bool> : public std::true_type {
-public:
-    /**
-     * Push a boolean.
-     *
-     * Uses duk_push_boolean
-     *
-     * \param ctx the Duktape context
-     * \param value the value
-     */
-    static void push(duk_context* ctx, bool value)
-    {
-        duk_push_boolean(ctx, value);
-    }
-
-    /**
-     * Get a boolean.
-     *
-     * Uses duk_get_boolean.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static bool get(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_get_boolean(ctx, index);
-    }
-
-    /**
-     * Require a boolean.
-     *
-     * Uses duk_require_boolean.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static bool require(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_require_boolean(ctx, index);
-    }
-};
-
-/**
- * \brief Specialization for duk_double_t.
- */
-template <>
-class dukx_type_traits<duk_double_t> : public std::true_type {
-public:
-    /**
-     * Push a double.
-     *
-     * Uses duk_push_number
-     *
-     * \param ctx the Duktape context
-     * \param value the value
-     */
-    static void push(duk_context* ctx, duk_double_t value)
-    {
-        duk_push_number(ctx, value);
-    }
-
-    /**
-     * Get a double.
-     *
-     * Uses duk_get_number.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static duk_double_t get(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_get_number(ctx, index);
-    }
-
-    /**
-     * Require a double.
-     *
-     * Uses duk_require_double.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static duk_double_t require(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_require_number(ctx, index);
-    }
-};
-
-/**
- * \brief Specialization for duk_int_t.
- */
-template <>
-class dukx_type_traits<duk_int_t> : public std::true_type {
-public:
-    /**
-     * Push an int.
-     *
-     * Uses duk_push_int
-     *
-     * \param ctx the Duktape context
-     * \param value the value
-     */
-    static void push(duk_context* ctx, duk_int_t value)
-    {
-        duk_push_int(ctx, value);
-    }
-
-    /**
-     * Get an int.
-     *
-     * Uses duk_get_number.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static duk_int_t get(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_get_int(ctx, index);
-    }
-
-    /**
-     * Require an int.
-     *
-     * Uses duk_require_int.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static duk_int_t require(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_require_int(ctx, index);
-    }
-};
-
-/**
- * \brief Specialization for duk_uint_t.
- */
-template <>
-class dukx_type_traits<duk_uint_t> : public std::true_type {
-public:
-    /**
-     * Push an unsigned int.
-     *
-     * Uses duk_push_uint
-     *
-     * \param ctx the Duktape context
-     * \param value the value
-     */
-    static void push(duk_context* ctx, duk_uint_t value)
-    {
-        duk_push_uint(ctx, value);
-    }
-
-    /**
-     * Get an unsigned int.
-     *
-     * Uses duk_get_uint.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static duk_uint_t get(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_get_uint(ctx, index);
-    }
-
-    /**
-     * Require an unsigned int.
-     *
-     * Uses duk_require_uint.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static duk_uint_t require(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_require_uint(ctx, index);
-    }
-};
-
-/**
- * \brief Specialization for C strings.
- */
-template <>
-class dukx_type_traits<const char*> : public std::true_type {
-public:
-    /**
-     * Push a C string.
-     *
-     * Uses duk_push_string
-     *
-     * \param ctx the Duktape context
-     * \param value the value
-     */
-    static void push(duk_context* ctx, const char* value)
-    {
-        duk_push_string(ctx, value);
-    }
-
-    /**
-     * Get a C string.
-     *
-     * Uses duk_get_string.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static const char* get(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_get_string(ctx, index);
-    }
-
-    /**
-     * Require a C string.
-     *
-     * Uses duk_require_string.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static const char* require(duk_context* ctx, duk_idx_t index)
-    {
-        return duk_require_string(ctx, index);
-    }
-};
-
-/**
- * \brief Specialization for C++ std::strings.
- */
-template <>
-class dukx_type_traits<std::string> : public std::true_type {
-public:
-    /**
-     * Push a C++ std::string.
-     *
-     * Uses duk_push_lstring
-     *
-     * \param ctx the Duktape context
-     * \param value the value
-     */
-    static void push(duk_context* ctx, const std::string& value)
-    {
-        duk_push_lstring(ctx, value.data(), value.size());
-    }
-
-    /**
-     * Get a C++ std::string.
-     *
-     * Uses duk_get_lstring.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static std::string get(duk_context* ctx, duk_idx_t index)
-    {
-        duk_size_t length;
-        const char* str = duk_get_lstring(ctx, index, &length);
-
-        return {str, length};
-    }
-
-    /**
-     * Require a C++ std::string.
-     *
-     * Uses duk_require_lstring.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static std::string require(duk_context* ctx, duk_idx_t index)
-    {
-        duk_size_t length;
-        const char* str = duk_require_lstring(ctx, index, &length);
-
-        return {str, length};
-    }
-};
-
-/**
- * \brief Specialization for C++ std::string_view.
- */
-template <>
-class dukx_type_traits<std::string_view> : public std::true_type {
-public:
-    /**
-     * Push a C++ std::string_view.
-     *
-     * Uses duk_push_lstring
-     *
-     * \param ctx the Duktape context
-     * \param value the value
-     */
-    static void push(duk_context* ctx, std::string_view value)
-    {
-        duk_push_lstring(ctx, value.data(), value.size());
-    }
-
-    /**
-     * Get a C++ std::string_view.
-     *
-     * Uses duk_get_lstring.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static std::string_view get(duk_context* ctx, duk_idx_t index)
-    {
-        duk_size_t length;
-        const char* str = duk_get_lstring(ctx, index, &length);
-
-        return {str, length};
-    }
-
-    /**
-     * Require a C++ std::string_view.
-     *
-     * Uses duk_require_lstring.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the converted value
-     */
-    static std::string_view require(duk_context* ctx, duk_idx_t index)
-    {
-        duk_size_t length;
-        const char* str = duk_require_lstring(ctx, index, &length);
-
-        return {str, length};
-    }
-};
-
-/**
- * \brief Specialization for dukx_error.
- */
-template <>
-class dukx_type_traits<dukx_error> : public std::true_type {
-public:
-    /**
-     * Create the exception on the stack.
-     *
-     * \param ctx the context
-     * \param ex the error
-     */
-    static void raise(duk_context* ctx, const dukx_error& ex)
-    {
-        duk_error(ctx, ex.get_type(), "%s", ex.get_message().c_str());
-    }
-};
-
-/**
- * \brief Specialization for dukx_eval_error.
- */
-template <>
-class dukx_type_traits<dukx_eval_error> : public dukx_type_traits<dukx_error> {
-};
-
-/**
- * \brief Specialization for dukx_range_error.
- */
-template <>
-class dukx_type_traits<dukx_range_error> : public dukx_type_traits<dukx_error> {
-};
-
-/**
- * \brief Specialization for dukx_reference_error.
- */
-template <>
-class dukx_type_traits<dukx_reference_error> : public dukx_type_traits<dukx_error> {
-};
-
-/**
- * \brief Specialization for dukx_syntax_error.
- */
-template <>
-class dukx_type_traits<dukx_syntax_error> : public dukx_type_traits<dukx_error> {
-};
-
-/**
- * \brief Specialization for dukx_type_error.
- */
-template <>
-class dukx_type_traits<dukx_type_error> : public dukx_type_traits<dukx_error> {
-};
-
-/**
- * \brief Specialization for dukx_uri_error.
- */
-template <>
-class dukx_type_traits<dukx_uri_error> : public dukx_type_traits<dukx_error> {
-};
-
-/**
- * \brief Specialization for std::exception.
- */
-template <>
-class dukx_type_traits<std::exception> : public std::true_type {
-public:
-    /**
-     * Raise std::exception as general DUK_ERR_ERROR.
-     *
-     * \param ctx the context
-     * \param ex the exception
-     */
-    static void raise(duk_context* ctx, const std::exception& ex) {
-        duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-    }
-};
-
-/**
- * \brief Partial specialization for collections.
- *
- * Derive from this class to implement type traits for collections.
- *
- * \see duktape_vector.hpp
- */
-template <typename Container>
-class dukx_array_type_traits : public std::true_type {
-public:
-    /**
-     * Push a Javascript array by copying values.
-     *
-     * Uses dukx_push for each T values in the collection.
-     *
-     * \param ctx the Duktape context
-     * \param c the container
-     */
-    static void push(duk_context* ctx, const Container& c)
-    {
-        using Type = typename Container::value_type;
-
-        duk_push_array(ctx);
-
-        int i = 0;
-
-        for (auto v : c) {
-            dukx_type_traits<Type>::push(ctx, v);
-            duk_put_prop_index(ctx, -2, i++);
-        }
-    }
-
-    /**
-     * Get an array from the Javascript array.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the container
-     */
-    static Container get(duk_context* ctx, duk_idx_t index)
-    {
-        using Type = typename Container::value_type;
-        using Size = typename Container::size_type;
-
-        Container result;
-        Size length = duk_get_length(ctx, index);
-
-        for (Size i = 0; i < length; ++i) {
-            duk_get_prop_index(ctx, index, i);
-            result.push_back(dukx_type_traits<Type>::get(ctx, -1));
-            duk_pop(ctx);
-        }
-
-        return result;
-    }
-
-    /**
-     * Require an array from the Javascript array.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the container
-     */
-    static Container require(duk_context* ctx, duk_idx_t index)
-    {
-        duk_check_type(ctx, index, DUK_TYPE_OBJECT);
-
-        return get(ctx, index);
-    }
-};
-
-/**
- * \brief Partial specialization for maps.
- *
- * Derive from this class to implement type traits for maps.
- *
- * \see duktape_vector.hpp
- */
-template <typename Container>
-class dukx_object_type_traits : public std::true_type {
-public:
-    /**
-     * Push a container by copying values.
-     *
-     * Uses dukx_push for each key/value pair in the container.
-     *
-     * \param ctx the Duktape context
-     * \param c the container
-     */
-    static void push(duk_context* ctx, const Container& c)
-    {
-        using Type = typename Container::mapped_type;
-
-        duk_push_object(ctx);
-
-        for (const auto& pair : c) {
-            dukx_type_traits<std::string>::push(ctx, pair.first);
-            dukx_type_traits<Type>::push(ctx, pair.second);
-            duk_put_prop(ctx, -3);
-        }
-    }
-
-    /**
-     * Get a object from the Javascript array.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the container
-     */
-    static Container get(duk_context* ctx, duk_idx_t index)
-    {
-        using Type = typename Container::mapped_type;
-
-        Container result;
-
-        duk_enum(ctx, index, 0);
-
-        while (duk_next(ctx, -1, true)) {
-            result.emplace(
-                dukx_type_traits<std::string>::get(ctx, -2),
-                dukx_type_traits<Type>::get(ctx, -1)
-            );
-            duk_pop_n(ctx, 2);
-        }
-
-        duk_pop(ctx);
-
-        return result;
-    }
-
-    /**
-     * Require a object from the Javascript array.
-     *
-     * \param ctx the Duktape context
-     * \param index the value index
-     * \return the container
-     */
-    static Container require(duk_context* ctx, duk_idx_t index)
-    {
-        duk_check_type(ctx, index, DUK_TYPE_OBJECT);
-
-        return get(ctx, index);
-    }
-};
-
-/**
- * Generic push function.
- *
- * This function calls dukx_type_traits<T>::push if specialized.
- *
- * \param ctx the Duktape context
- * \param value the forwarded value
- * \return 1 for convenience
- */
-template <typename T>
-int dukx_push(duk_context* ctx, T&& value)
-{
-    using Type = typename std::decay<T>::type;
-
-    static_assert(dukx_type_traits<Type>::value, "type T not supported");
-
-    dukx_type_traits<Type>::push(ctx, std::forward<T>(value));
-
-    return 1;
-}
-
-/**
- * Generic get function.
- *
- * This functions calls dukx_type_traits<T>::get if specialized.
- *
- * \param ctx the Duktape context
- * \param index the value index
- * \return the converted value
- */
-template <typename T>
-T dukx_get(duk_context* ctx, duk_idx_t index)
-{
-    using Type = typename std::decay<T>::type;
-
-    static_assert(dukx_type_traits<Type>::value, "type T not supported");
-
-    return dukx_type_traits<Type>::get(ctx, index);
-}
-
-/**
- * Generic require function.
- *
- * This functions calls dukx_type_traits<T>::require if specialized.
- *
- * \param ctx the Duktape context
- * \param index the value index
- * \return the converted value
- */
-template <typename T>
-T dukx_require(duk_context* ctx, duk_idx_t index)
-{
-    using Type = typename std::decay<T>::type;
-
-    static_assert(dukx_type_traits<Type>::value, "type T not supported");
-
-    return dukx_type_traits<Type>::require(ctx, index);
-}
-
-/**
- * Create an exception into the stack and throws it.
- *
- * The dukx_type_traits<Error> must have the following function:
- *
- * ```
- * static void raise(const Error&);
- * ```
- *
- * \param ctx the Duktape context
- * \param error the error object
- */
-template <typename Error>
-void dukx_throw(duk_context* ctx, const Error& error)
-{
-    static_assert(dukx_type_traits<Error>::value, "type T not supported");
-
-    dukx_type_traits<Error>::raise(ctx, error);
-}
-
-/**
- * Get the error object when a JavaScript error has been thrown (e.g. eval
- * failure).
- *
- * \param ctx the context
- * \param index the index
- * \param pop if true, also remove the exception from the stack
- * \return the information
- */
-inline dukx_stack_info dukx_stack(duk_context* ctx, int index, bool pop = true)
-{
-    index = duk_normalize_index(ctx, index);
-
-    duk_get_prop_string(ctx, index, "name");
-    auto name = duk_to_string(ctx, -1);
-    duk_get_prop_string(ctx, index, "message");
-    auto message = duk_to_string(ctx, -1);
-    duk_get_prop_string(ctx, index, "fileName");
-    auto file_name = duk_to_string(ctx, -1);
-    duk_get_prop_string(ctx, index, "lineNumber");
-    auto line_number = duk_to_int(ctx, -1);
-    duk_get_prop_string(ctx, index, "stack");
-    auto stack = duk_to_string(ctx, -1);
-    duk_pop_n(ctx, 5);
-
-    if (pop)
-        duk_remove(ctx, index);
-
-    return {
-        std::move(name),
-        std::move(message),
-        std::move(stack),
-        std::move(file_name),
-        line_number
-    };
-}
-
-} // !irccd
-
-#endif // !IRCCD_JS_DUKTAPE_HPP
--- a/libirccd-js/irccd/js/duktape_vector.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/*
- * duktape_vector.hpp -- miscellaneous Duktape extras (std::vector support)
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_DUKTAPE_VECTOR_HPP
-#define IRCCD_JS_DUKTAPE_VECTOR_HPP
-
-/**
- * \file duktape_vector.hpp
- * \brief Miscellaneous Duktape extras (std::vector support).
- */
-
-#include <vector>
-
-#include "duktape.hpp"
-
-namespace irccd {
-
-/**
- * \brief Specialization for std::vector<T>.
- */
-template <typename T>
-class dukx_type_traits<std::vector<T>> : public dukx_array_type_traits<std::vector<T>> {
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_DUKTAPE_VECTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/elapsed_timer_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,180 @@
+/*
+ * elapsed_timer_js_api.cpp -- Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2018 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 <boost/timer/timer.hpp>
+
+#include "elapsed_timer_js_api.hpp"
+#include "js_plugin.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature("\xff""\xff""Irccd.ElapsedTimer");
+
+// {{{ self
+
+auto self(duk_context* ctx) -> boost::timer::cpu_timer*
+{
+    duk::stack_guard sa(ctx);
+
+    duk_push_this(ctx);
+    duk_get_prop_string(ctx, -1, signature.data());
+    const auto ptr = static_cast<boost::timer::cpu_timer*>(duk_to_pointer(ctx, -1));
+    duk_pop_2(ctx);
+
+    if (!ptr)
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not an ElapsedTimer object");
+
+    return ptr;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer.prototype.pause
+
+/*
+ * Method: ElapsedTimer.prototype.pause
+ * ------------------------------------------------------------------
+ *
+ * Pause the timer, without resetting the current elapsed time stored.
+ */
+auto ElapsedTimer_prototype_pause(duk_context* ctx) -> duk_ret_t
+{
+    self(ctx)->stop();
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer.prototype.restart
+
+/*
+ * Method: Irccd.ElapsedTimer.prototype.restart
+ * ------------------------------------------------------------------
+ *
+ * Restart the timer without resetting the current elapsed time.
+ */
+auto ElapsedTimer_prototype_restart(duk_context* ctx) -> duk_ret_t
+{
+    self(ctx)->resume();
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer.prototype.elapsed
+
+/*
+ * Method: ElapsedTimer.prototype.elapsed
+ * ------------------------------------------------------------------
+ *
+ * Get the number of elapsed milliseconds.
+ *
+ * Returns:
+ *   The time elapsed.
+ */
+auto ElapsedTimer_prototype_elapsed(duk_context* ctx) -> duk_ret_t
+{
+    duk_push_uint(ctx, self(ctx)->elapsed().wall / 1000000LL);
+
+    return 1;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer [constructor]
+
+/*
+ * Function: Irccd.ElapsedTimer [constructor]
+ * ------------------------------------------------------------------
+ *
+ * Construct a new ElapsedTimer object.
+ */
+auto ElapsedTimer_constructor(duk_context* ctx) -> duk_ret_t
+{
+    duk_push_this(ctx);
+    duk_push_pointer(ctx, new boost::timer::cpu_timer);
+    duk_put_prop_string(ctx, -2, signature.data());
+    duk_pop(ctx);
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer [destructor]
+
+/*
+ * Function: Irccd.ElapsedTimer [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Delete the property.
+ */
+auto ElapsedTimer_destructor(duk_context* ctx) -> duk_ret_t
+{
+    duk_get_prop_string(ctx, 0, signature.data());
+    delete static_cast<boost::timer::cpu_timer*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+    duk_del_prop_string(ctx, 0, signature.data());
+
+    return 0;
+}
+
+// }}}
+
+// {{{ definitions
+
+const duk_function_list_entry methods[] = {
+    { "elapsed",    ElapsedTimer_prototype_elapsed, 0 },
+    { "pause",      ElapsedTimer_prototype_pause,   0 },
+    { "restart",    ElapsedTimer_prototype_restart, 0 },
+    { nullptr,      nullptr,                        0 }
+};
+
+// }}}
+
+} // !namespace
+
+// {{{ elapsed_timer_js_api
+
+auto elapsed_timer_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.ElapsedTimer";
+}
+
+void elapsed_timer_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+    duk_push_c_function(plugin->get_context(), ElapsedTimer_constructor, 0);
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, methods);
+    duk_push_c_function(plugin->get_context(), ElapsedTimer_destructor, 1);
+    duk_set_finalizer(plugin->get_context(), -2);
+    duk_put_prop_string(plugin->get_context(), -2, "prototype");
+    duk_put_prop_string(plugin->get_context(), -2, "ElapsedTimer");
+    duk_pop(plugin->get_context());
+}
+
+// }}}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/elapsed_timer_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,50 @@
+/*
+ * elapsed_timer_js_api.hpp -- Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_ELAPSED_TIMER_JS_API_HPP
+#define IRCCD_JS_ELAPSED_TIMER_JS_API_HPP
+
+/**
+ * \file elapsed_timer_js_api.hpp
+ * \brief Irccd.ElapsedTimer Javascript API.
+ */
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Irccd.ElapsedTimer Javascript API.
+ * \ingroup Javascript js_api
+ */
+class elapsed_timer_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc js_api::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_ELAPSED_TIMER_JS_API_HPP
--- a/libirccd-js/irccd/js/elapsed_timer_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
-/*
- * elapsed_timer_jsapi.cpp -- Irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2018 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 <boost/timer/timer.hpp>
-
-#include "elapsed_timer_jsapi.hpp"
-#include "js_plugin.hpp"
-
-namespace irccd {
-
-namespace {
-
-const char* signature("\xff""\xff""irccd-elapsed-timer-ptr");
-
-// {{{ self
-
-boost::timer::cpu_timer* self(duk_context* ctx)
-{
-    dukx_stack_assert sa(ctx);
-
-    duk_push_this(ctx);
-    duk_get_prop_string(ctx, -1, signature);
-    const auto ptr = static_cast<boost::timer::cpu_timer*>(duk_to_pointer(ctx, -1));
-    duk_pop_2(ctx);
-
-    if (!ptr)
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not an ElapsedTimer object");
-
-    return ptr;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer.prototype.pause
-
-/*
- * Method: ElapsedTimer.prototype.pause
- * ------------------------------------------------------------------
- *
- * Pause the timer, without resetting the current elapsed time stored.
- */
-duk_ret_t ElapsedTimer_prototype_pause(duk_context* ctx)
-{
-    self(ctx)->stop();
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer.prototype.restart
-
-/*
- * Method: Irccd.ElapsedTimer.prototype.restart
- * ------------------------------------------------------------------
- *
- * Restart the timer without resetting the current elapsed time.
- */
-duk_ret_t ElapsedTimer_prototype_restart(duk_context* ctx)
-{
-    self(ctx)->resume();
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer.prototype.elapsed
-
-/*
- * Method: ElapsedTimer.prototype.elapsed
- * ------------------------------------------------------------------
- *
- * Get the number of elapsed milliseconds.
- *
- * Returns:
- *   The time elapsed.
- */
-duk_ret_t ElapsedTimer_prototype_elapsed(duk_context* ctx)
-{
-    duk_push_uint(ctx, self(ctx)->elapsed().wall / 1000000LL);
-
-    return 1;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer [constructor]
-
-/*
- * Function: Irccd.ElapsedTimer [constructor]
- * ------------------------------------------------------------------
- *
- * Construct a new ElapsedTimer object.
- */
-duk_ret_t ElapsedTimer_constructor(duk_context* ctx)
-{
-    duk_push_this(ctx);
-    duk_push_pointer(ctx, new boost::timer::cpu_timer);
-    duk_put_prop_string(ctx, -2, signature);
-    duk_pop(ctx);
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer [destructor]
-
-/*
- * Function: Irccd.ElapsedTimer [destructor]
- * ------------------------------------------------------------------
- *
- * Delete the property.
- */
-duk_ret_t ElapsedTimer_destructor(duk_context* ctx)
-{
-    duk_get_prop_string(ctx, 0, signature);
-    delete static_cast<boost::timer::cpu_timer*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-    duk_del_prop_string(ctx, 0, signature);
-
-    return 0;
-}
-
-// }}}
-
-const duk_function_list_entry methods[] = {
-    { "elapsed",    ElapsedTimer_prototype_elapsed, 0 },
-    { "pause",      ElapsedTimer_prototype_pause,   0 },
-    { "restart",    ElapsedTimer_prototype_restart, 0 },
-    { nullptr,      nullptr,                        0 }
-};
-
-} // !namespace
-
-std::string elapsed_timer_jsapi::get_name() const
-{
-    return "Irccd.ElapsedTimer";
-}
-
-void elapsed_timer_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_get_global_string(plugin->get_context(), "Irccd");
-    duk_push_c_function(plugin->get_context(), ElapsedTimer_constructor, 0);
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, methods);
-    duk_push_c_function(plugin->get_context(), ElapsedTimer_destructor, 1);
-    duk_set_finalizer(plugin->get_context(), -2);
-    duk_put_prop_string(plugin->get_context(), -2, "prototype");
-    duk_put_prop_string(plugin->get_context(), -2, "ElapsedTimer");
-    duk_pop(plugin->get_context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/elapsed_timer_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * elapsed_timer_jsapi.hpp -- Irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_ELAPSED_TIMER_JSAPI_HPP
-#define IRCCD_JS_ELAPSED_TIMER_JSAPI_HPP
-
-/**
- * \file elapsed_timer_jsapi.hpp
- * \brief Irccd.ElapsedTimer Javascript API.
- */
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.ElapsedTimer Javascript API.
- * \ingroup Javascript jsapi
- */
-class elapsed_timer_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc jsapi::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_ELAPSED_TIMER_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/file_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,805 @@
+/*
+ * file_js_api.cpp -- Irccd.File API
+ *
+ * Copyright (c) 2013-2018 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 <algorithm>
+#include <array>
+#include <cassert>
+#include <iterator>
+#include <vector>
+
+#include <boost/filesystem.hpp>
+
+#include <irccd/fs_util.hpp>
+
+#include "file_js_api.hpp"
+#include "irccd_js_api.hpp"
+#include "js_plugin.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature("\xff""\xff""Irccd.File");
+const std::string_view prototype("\xff""\xff""Irccd.File.prototype");
+
+// {{{ clear_crlf
+
+auto clear_crlf(std::string input) noexcept -> std::string
+{
+    if (input.length() > 0 && input.back() == '\r')
+        input.pop_back();
+
+    return input;
+}
+
+// }}}
+
+// {{{ from_errno
+
+auto from_errno() noexcept -> std::system_error
+{
+    return std::system_error(make_error_code(static_cast<std::errc>(errno)));
+}
+
+// }}}
+
+// {{{ self
+
+auto self(duk_context* ctx) -> std::shared_ptr<file>
+{
+    duk::stack_guard sa(ctx);
+
+    duk_push_this(ctx);
+    duk_get_prop_string(ctx, -1, signature.data());
+    auto ptr = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
+    duk_pop_2(ctx);
+
+    if (!ptr)
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object");
+
+    return *ptr;
+}
+
+// }}}
+
+// {{{ wrap
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+    try {
+        return handler();
+    } catch (const boost::system::system_error& ex) {
+        duk::raise(ctx, ex);
+    } catch (const std::system_error& ex) {
+        duk::raise(ctx, ex);
+    } catch (const std::exception& ex) {
+        duk::raise(ctx, ex);
+    }
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.basename
+
+/*
+ * Method: Irccd.File.prototype.basename()
+ * --------------------------------------------------------
+ *
+ * Synonym of `Irccd.File.basename(path)` but with the path from the file.
+ *
+ * Returns:
+ *   The base name.
+ */
+auto File_prototype_basename(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, fs_util::base_name(self(ctx)->get_path()));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.close
+
+/*
+ * Method: Irccd.File.prototype.close()
+ * --------------------------------------------------------
+ *
+ * Force close of the file, automatically called when object is collected.
+ */
+auto File_prototype_close(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        self(ctx)->close();
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.dirname
+
+/*
+ * Method: Irccd.File.prototype.dirname()
+ * --------------------------------------------------------
+ *
+ * Synonym of `Irccd.File.dirname(path)` but with the path from the file.
+ *
+ * Returns:
+ *   The directory name.
+ */
+auto File_prototype_dirname(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, fs_util::dir_name(self(ctx)->get_path()));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.lines
+
+/*
+ * Method: Irccd.File.prototype.lines()
+ * --------------------------------------------------------
+ *
+ * Read all lines and return an array.
+ *
+ * Returns:
+ *   An array with all lines.
+ * Throws
+ *   - Any exception on error.
+ */
+auto File_prototype_lines(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        duk_push_array(ctx);
+
+        std::FILE* fp = self(ctx)->get_handle();
+        std::string buffer;
+        std::array<char, 128> data;
+        std::int32_t i = 0;
+
+        while (std::fgets(&data[0], data.size(), fp) != nullptr) {
+            buffer += data.data();
+
+            const auto pos = buffer.find('\n');
+
+            if (pos != std::string::npos) {
+                duk::push(ctx, clear_crlf(buffer.substr(0, pos)));
+                duk_put_prop_index(ctx, -2, i++);
+
+                buffer.erase(0, pos + 1);
+            }
+        }
+
+        // Maybe an error in the stream.
+        if (std::ferror(fp))
+            throw from_errno();
+
+        // Missing '\n' in end of file.
+        if (!buffer.empty()) {
+            duk::push(ctx, clear_crlf(buffer));
+            duk_put_prop_index(ctx, -2, i++);
+        }
+
+        return 1;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.read
+
+/*
+ * Method: Irccd.File.prototype.read(amount)
+ * --------------------------------------------------------
+ *
+ * Read the specified amount of characters or the whole file.
+ *
+ * Arguments:
+ *   - amount, the amount of characters or -1 to read all (Optional, default: -1).
+ * Returns:
+ *   The string.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_read(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        const auto fp = self(ctx)->get_handle();
+        const auto amount = duk_is_number(ctx, 0) ? duk_get_int(ctx, 0) : -1;
+
+        if (amount == 0 || !fp)
+            return 0;
+
+        std::string data;
+        std::size_t total = 0;
+
+        if (amount < 0) {
+            std::array<char, 128> buffer;
+            std::size_t nread;
+
+            while ((nread = std::fread(&buffer[0], sizeof (buffer[0]), buffer.size(), fp)) > 0) {
+                if (std::ferror(fp))
+                    throw from_errno();
+
+                std::copy(buffer.begin(), buffer.begin() + nread, std::back_inserter(data));
+                total += nread;
+            }
+        } else {
+            data.resize(static_cast<std::size_t>(amount));
+            total = std::fread(&data[0], sizeof (data[0]), static_cast<std::size_t>(amount), fp);
+
+            if (std::ferror(fp))
+                throw from_errno();
+
+            data.resize(total);
+        }
+
+        return duk::push(ctx, data);
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.readline
+
+/*
+ * Method: Irccd.File.prototype.readline()
+ * --------------------------------------------------------
+ *
+ * Read the next line available.
+ *
+ * Returns:
+ *   The next line or undefined if eof.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_readline(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        auto fp = self(ctx)->get_handle();
+
+        if (fp == nullptr || std::feof(fp))
+            return 0;
+
+        std::string result;
+
+        for (int ch; (ch = std::fgetc(fp)) != EOF && ch != '\n'; )
+            result += (char)ch;
+        if (std::ferror(fp))
+            throw from_errno();
+
+        return duk::push(ctx, clear_crlf(result));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.remove
+
+/*
+ * Method: Irccd.File.prototype.remove()
+ * --------------------------------------------------------
+ *
+ * Synonym of Irccd.File.prototype.remove(path) but with the path from the file.
+ *
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_remove(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        boost::filesystem::remove(self(ctx)->get_path());
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.seek
+
+/*
+ * Method: Irccd.File.prototype.seek(type, amount)
+ * --------------------------------------------------------
+ *
+ * Sets the position in the file.
+ *
+ * Arguments:
+ *   - type, the type of setting (File.SeekSet, File.SeekCur, File.SeekSet),
+ *   - amount, the new offset.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_seek(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        auto fp = self(ctx)->get_handle();
+        auto type = duk_require_int(ctx, 0);
+        auto amount = duk_require_int(ctx, 1);
+
+        if (fp != nullptr && std::fseek(fp, amount, type) != 0)
+            throw from_errno();
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.stat
+
+#if defined(HAVE_STAT)
+
+/*
+ * Method: Irccd.File.prototype.stat() [optional]
+ * --------------------------------------------------------
+ *
+ * Synonym of File.stat(path) but with the path from the file.
+ *
+ * Returns:
+ *   The stat information.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_stat(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        auto file = self(ctx);
+        struct stat st;
+
+        if (file->get_handle() == nullptr && ::stat(file->get_path().c_str(), &st) < 0)
+            throw from_errno();
+
+        duk::push(ctx, st);
+
+        return 1;
+    });
+}
+
+#endif // !HAVE_STAT
+
+// }}}
+
+// {{{ Irccd.File.prototype.tell
+
+/*
+ * Method: Irccd.File.prototype.tell()
+ * --------------------------------------------------------
+ *
+ * Get the actual position in the file.
+ *
+ * Returns:
+ *   The position.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_tell(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        auto fp = self(ctx)->get_handle();
+        long pos;
+
+        if (fp == nullptr)
+            return 0;
+
+        if ((pos = std::ftell(fp)) == -1L)
+            throw from_errno();
+
+        duk_push_int(ctx, pos);
+
+        return 1;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.write
+
+/*
+ * Method: Irccd.File.prototype.write(data)
+ * --------------------------------------------------------
+ *
+ * Write some characters to the file.
+ *
+ * Arguments:
+ *   - data, the character to write.
+ * Returns:
+ *   The number of bytes written.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_write(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        auto fp = self(ctx)->get_handle();
+        auto data = duk::require<std::string>(ctx, 0);
+
+        if (fp == nullptr)
+            return 0;
+
+        const auto nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
+
+        if (std::ferror(fp))
+            throw from_errno();
+
+        duk_push_uint(ctx, nwritten);
+
+        return 1;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File [constructor]
+
+/*
+ * Function: Irccd.File(path, mode) [constructor]
+ * --------------------------------------------------------
+ *
+ * Open a file specified by path with the specified mode.
+ *
+ * Arguments:
+ *   - path, the path to the file,
+ *   - mode, the mode string.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_constructor(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        if (!duk_is_constructor_call(ctx))
+            return 0;
+
+        const auto path = duk::require<std::string>(ctx, 0);
+        const auto mode = duk::require<std::string>(ctx, 1);
+
+        duk_push_this(ctx);
+        duk_push_pointer(ctx, new std::shared_ptr<file>(new file(path, mode)));
+        duk_put_prop_string(ctx, -2, signature.data());
+        duk_pop(ctx);
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File [destructor]
+
+/*
+ * Function: Irccd.File() [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Delete the property.
+ */
+auto File_destructor(duk_context* ctx) -> duk_ret_t
+{
+    duk_get_prop_string(ctx, 0, signature.data());
+    delete static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+    duk_del_prop_string(ctx, 0, signature.data());
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.File.basename
+
+/*
+ * Function: Irccd.File.basename(path)
+ * --------------------------------------------------------
+ *
+ * duk_ret_turn the file basename as specified in `basename(3)` C function.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Returns:
+ *   The base name.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_basename(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, fs_util::base_name(duk_require_string(ctx, 0)));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.dirname
+
+/*
+ * Function: Irccd.File.dirname(path)
+ * --------------------------------------------------------
+ *
+ * duk_ret_turn the file directory name as specified in `dirname(3)` C function.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_dirname(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, fs_util::dir_name(duk_require_string(ctx, 0)));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.exists
+
+/*
+ * Function: Irccd.File.exists(path)
+ * --------------------------------------------------------
+ *
+ * Check if the file exists.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Returns:
+ *   True if exists.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_exists(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, boost::filesystem::exists(duk_require_string(ctx, 0)));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.remove
+
+/*
+ * Function Irccd.File.remove(path)
+ * --------------------------------------------------------
+ *
+ * Remove the file at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_remove(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        boost::filesystem::remove(duk::require<std::string>(ctx, 0));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.File.stat
+
+#if defined(HAVE_STAT)
+
+/*
+ * Function Irccd.File.stat(path) [optional]
+ * --------------------------------------------------------
+ *
+ * Get file information at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Returns:
+ *   The stat information.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_stat(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        struct stat st;
+
+        if (::stat(duk_require_string(ctx, 0), &st) < 0)
+            throw from_errno();
+
+        return duk::push(ctx, st);
+    });
+}
+
+#endif // !HAVE_STAT
+
+// }}}
+
+// {{{ definitions
+
+const duk_function_list_entry methods[] = {
+    { "basename",   File_prototype_basename,    0 },
+    { "close",      File_prototype_close,       0 },
+    { "dirname",    File_prototype_dirname,     0 },
+    { "lines",      File_prototype_lines,       0 },
+    { "read",       File_prototype_read,        1 },
+    { "readline",   File_prototype_readline,    0 },
+    { "remove",     File_prototype_remove,      0 },
+    { "seek",       File_prototype_seek,        2 },
+#if defined(HAVE_STAT)
+    { "stat",       File_prototype_stat,        0 },
+#endif
+    { "tell",       File_prototype_tell,        0 },
+    { "write",      File_prototype_write,       1 },
+    { nullptr,      nullptr,                    0 }
+};
+
+const duk_function_list_entry functions[] = {
+    { "basename",   File_basename,              1 },
+    { "dirname",    File_dirname,               1 },
+    { "exists",     File_exists,                1 },
+    { "remove",     File_remove,                1 },
+#if defined(HAVE_STAT)
+    { "stat",       File_stat,                  1 },
+#endif
+    { nullptr,      nullptr,                    0 }
+};
+
+const duk_number_list_entry constants[] = {
+    { "SeekCur",    SEEK_CUR },
+    { "SeekEnd",    SEEK_END },
+    { "SeekSet",    SEEK_SET },
+    { nullptr,      0        }
+};
+
+// }}}
+
+} // !namespace
+
+// {{{ file_js_api
+
+auto file_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.File";
+}
+
+void file_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+    duk_push_c_function(plugin->get_context(), File_constructor, 2);
+    duk_put_number_list(plugin->get_context(), -1, constants);
+    duk_put_function_list(plugin->get_context(), -1, functions);
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, methods);
+    duk_push_c_function(plugin->get_context(), File_destructor, 1);
+    duk_set_finalizer(plugin->get_context(), -2);
+    duk_dup(plugin->get_context(), -1);
+    duk_put_global_string(plugin->get_context(), prototype.data());
+    duk_put_prop_string(plugin->get_context(), -2, "prototype");
+    duk_put_prop_string(plugin->get_context(), -2, "File");
+    duk_pop(plugin->get_context());
+}
+
+// }}}
+
+// {{{ duk::type_traits<std::shared_ptr<file>>
+
+using file_traits = duk::type_traits<std::shared_ptr<file>>;
+
+void file_traits::push(duk_context* ctx, std::shared_ptr<file> fp)
+{
+    assert(ctx);
+    assert(fp);
+
+    duk::stack_guard sa(ctx, 1);
+
+    duk_push_object(ctx);
+    duk_push_pointer(ctx, new std::shared_ptr<file>(std::move(fp)));
+    duk_put_prop_string(ctx, -2, signature.data());
+    duk_get_global_string(ctx, prototype.data());
+    duk_set_prototype(ctx, -2);
+}
+
+auto file_traits::require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<file>
+{
+    if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature.data()))
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object");
+
+    duk_get_prop_string(ctx, index, signature.data());
+    const auto fp = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+
+    return *fp;
+}
+
+// }}}
+
+// {{{ duk::type_traits<struct stat>
+
+#if defined(HAVE_STAT)
+
+void duk::type_traits<struct stat>::push(duk_context* ctx, const struct stat& st)
+{
+    duk::stack_guard sa(ctx, 1);
+
+    duk_push_object(ctx);
+
+#if defined(HAVE_STAT_ST_ATIME)
+    duk_push_int(ctx, st.st_atime);
+    duk_put_prop_string(ctx, -2, "atime");
+#endif
+#if defined(HAVE_STAT_ST_BLKSIZE)
+    duk_push_int(ctx, st.st_blksize);
+    duk_put_prop_string(ctx, -2, "blksize");
+#endif
+#if defined(HAVE_STAT_ST_BLOCKS)
+    duk_push_int(ctx, st.st_blocks);
+    duk_put_prop_string(ctx, -2, "blocks");
+#endif
+#if defined(HAVE_STAT_ST_CTIME)
+    duk_push_int(ctx, st.st_ctime);
+    duk_put_prop_string(ctx, -2, "ctime");
+#endif
+#if defined(HAVE_STAT_ST_DEV)
+    duk_push_int(ctx, st.st_dev);
+    duk_put_prop_string(ctx, -2, "dev");
+#endif
+#if defined(HAVE_STAT_ST_GID)
+    duk_push_int(ctx, st.st_gid);
+    duk_put_prop_string(ctx, -2, "gid");
+#endif
+#if defined(HAVE_STAT_ST_INO)
+    duk_push_int(ctx, st.st_ino);
+    duk_put_prop_string(ctx, -2, "ino");
+#endif
+#if defined(HAVE_STAT_ST_MODE)
+    duk_push_int(ctx, st.st_mode);
+    duk_put_prop_string(ctx, -2, "mode");
+#endif
+#if defined(HAVE_STAT_ST_MTIME)
+    duk_push_int(ctx, st.st_mtime);
+    duk_put_prop_string(ctx, -2, "mtime");
+#endif
+#if defined(HAVE_STAT_ST_NLINK)
+    duk_push_int(ctx, st.st_nlink);
+    duk_put_prop_string(ctx, -2, "nlink");
+#endif
+#if defined(HAVE_STAT_ST_RDEV)
+    duk_push_int(ctx, st.st_rdev);
+    duk_put_prop_string(ctx, -2, "rdev");
+#endif
+#if defined(HAVE_STAT_ST_SIZE)
+    duk_push_int(ctx, st.st_size);
+    duk_put_prop_string(ctx, -2, "size");
+#endif
+#if defined(HAVE_STAT_ST_UID)
+    duk_push_int(ctx, st.st_uid);
+    duk_put_prop_string(ctx, -2, "uid");
+#endif
+}
+
+#endif // !HAVE_STAT
+
+// }}}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/file_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,211 @@
+/*
+ * file_js_api.hpp -- Irccd.File API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_FILE_JS_API_HPP
+#define IRCCD_JS_FILE_JS_API_HPP
+
+/**
+ * \file file_js_api.hpp
+ * \brief Irccd.File Javascript API.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#if defined(HAVE_STAT)
+#   include <sys/types.h>
+#   include <sys/stat.h>
+#endif
+
+#include <cassert>
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <stdexcept>
+#include <string>
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Object for Javascript to perform I/O.
+ *
+ * This class can be constructed to Javascript.
+ *
+ * It is used in:
+ *
+ * - Irccd.File [constructor]
+ * - Irccd.System.popen (optional)
+ */
+class file {
+private:
+    file(const file&) = delete;
+    file& operator=(const file&) = delete;
+
+    file(file&&) = delete;
+    file& operator=(file&&) = delete;
+
+private:
+    std::string path_;
+    std::FILE* stream_;
+    std::function<void (std::FILE*)> destructor_;
+
+public:
+    /**
+     * Construct a file specified by path
+     *
+     * \param path the path
+     * \param mode the mode string (for std::fopen)
+     * \throw std::runtime_error on failures
+     */
+    inline file(std::string path, const std::string& mode)
+        : path_(std::move(path))
+        , destructor_([] (std::FILE* fp) { std::fclose(fp); })
+    {
+        if ((stream_ = std::fopen(path_.c_str(), mode.c_str())) == nullptr)
+            throw std::runtime_error(std::strerror(errno));
+    }
+
+    /**
+     * Construct a file from a already created FILE pointer (e.g. popen).
+     *
+     * The class takes ownership of fp and will close it.
+     *
+     * \pre destructor must not be null
+     * \param fp the file pointer
+     * \param destructor the function to close fp (e.g. std::fclose)
+     */
+    inline file(std::FILE* fp, std::function<void (std::FILE*)> destructor) noexcept
+        : stream_(fp)
+        , destructor_(std::move(destructor))
+    {
+        assert(destructor_ != nullptr);
+    }
+
+    /**
+     * Closes the file.
+     */
+    virtual ~file() noexcept
+    {
+        close();
+    }
+
+    /**
+     * Get the path.
+     *
+     * \return the path
+     * \warning empty when constructed from the FILE constructor
+     */
+    inline const std::string& get_path() const noexcept
+    {
+        return path_;
+    }
+
+    /**
+     * Get the handle.
+     *
+     * \return the handle or nullptr if the stream was closed
+     */
+    inline std::FILE* get_handle() noexcept
+    {
+        return stream_;
+    }
+
+    /**
+     * Force close, can be safely called multiple times.
+     */
+    inline void close() noexcept
+    {
+        if (stream_) {
+            destructor_(stream_);
+            stream_ = nullptr;
+        }
+    }
+};
+
+/**
+ * \brief Irccd.File Javascript API.
+ * \ingroup js_api
+ */
+class file_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc js_api::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+namespace duk {
+
+/**
+ * \brief Specialization for generic file type as shared_ptr.
+ *
+ * Supports push, require.
+ */
+template <>
+struct type_traits<std::shared_ptr<file>> {
+    /**
+     * Push a file.
+     *
+     * \pre fp != nullptr
+     * \param ctx the the context
+     * \param fp the file
+     */
+    static void push(duk_context* ctx, std::shared_ptr<file> fp);
+
+    /**
+     * Require a file. Raises a JavaScript error if not a File.
+     *
+     * \param ctx the context
+     * \param index the index
+     * \return the file pointer
+     */
+    static auto require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<file>;
+};
+
+#if defined(HAVE_STAT)
+
+/**
+ * \brief Specialization for struct stat.
+ *
+ * Supports push.
+ */
+template <>
+struct type_traits<struct stat> {
+    /**
+     * Push the stat information to the stack as Javascript object.
+     *
+     * \param ctx the context
+     * \param st the stat structure
+     */
+    static void push(duk_context* ctx, const struct stat& st);
+};
+
+#endif // !HAVE_STAT
+
+} // !duk
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_FILE_JS_API_HPP
--- a/libirccd-js/irccd/js/file_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,774 +0,0 @@
-/*
- * file_jsapi.cpp -- Irccd.File API
- *
- * Copyright (c) 2013-2018 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 <algorithm>
-#include <array>
-#include <cassert>
-#include <iterator>
-#include <vector>
-
-#include <boost/filesystem.hpp>
-
-#include <irccd/fs_util.hpp>
-
-#include "file_jsapi.hpp"
-#include "irccd_jsapi.hpp"
-#include "js_plugin.hpp"
-
-namespace irccd {
-
-namespace {
-
-const char *signature("\xff""\xff""irccd-file-ptr");
-const char *prototype("\xff""\xff""irccd-file-prototype");
-
-// Remove trailing \r for CRLF line style.
-std::string clear_crlf(std::string input) noexcept
-{
-    if (input.length() > 0 && input.back() == '\r')
-        input.pop_back();
-
-    return input;
-}
-
-std::system_error from_errno() noexcept
-{
-    return std::system_error(make_error_code(static_cast<std::errc>(errno)));
-}
-
-std::shared_ptr<file> self(duk_context* ctx)
-{
-    dukx_stack_assert sa(ctx);
-
-    duk_push_this(ctx);
-    duk_get_prop_string(ctx, -1, signature);
-    auto ptr = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
-    duk_pop_2(ctx);
-
-    if (!ptr)
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object");
-
-    return *ptr;
-}
-
-template <typename Handler>
-duk_ret_t wrap(duk_context* ctx, Handler handler)
-{
-    try {
-        return handler();
-    } catch (const boost::system::system_error& ex) {
-        dukx_throw(ctx, ex);
-    } catch (const std::system_error& ex) {
-        dukx_throw(ctx, ex);
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, ex);
-    }
-
-    return 0;
-}
-
-// {{{ Irccd.File.prototype.basename
-
-/*
- * Method: Irccd.File.prototype.basename()
- * --------------------------------------------------------
- *
- * Synonym of `Irccd.File.basename(path)` but with the path from the file.
- *
- * Returns:
- *   The base name.
- */
-duk_ret_t File_prototype_basename(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, fs_util::base_name(self(ctx)->get_path()));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.close
-
-/*
- * Method: Irccd.File.prototype.close()
- * --------------------------------------------------------
- *
- * Force close of the file, automatically called when object is collected.
- */
-duk_ret_t File_prototype_close(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        self(ctx)->close();
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.dirname
-
-/*
- * Method: Irccd.File.prototype.dirname()
- * --------------------------------------------------------
- *
- * Synonym of `Irccd.File.dirname(path)` but with the path from the file.
- *
- * Returns:
- *   The directory name.
- */
-duk_ret_t File_prototype_dirname(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, fs_util::dir_name(self(ctx)->get_path()));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.lines
-
-/*
- * Method: Irccd.File.prototype.lines()
- * --------------------------------------------------------
- *
- * Read all lines and return an array.
- *
- * Returns:
- *   An array with all lines.
- * Throws
- *   - Any exception on error.
- */
-duk_ret_t File_prototype_lines(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        duk_push_array(ctx);
-
-        std::FILE* fp = self(ctx)->get_handle();
-        std::string buffer;
-        std::array<char, 128> data;
-        std::int32_t i = 0;
-
-        while (std::fgets(&data[0], data.size(), fp) != nullptr) {
-            buffer += data.data();
-
-            const auto pos = buffer.find('\n');
-
-            if (pos != std::string::npos) {
-                dukx_push(ctx, clear_crlf(buffer.substr(0, pos)));
-                duk_put_prop_index(ctx, -2, i++);
-
-                buffer.erase(0, pos + 1);
-            }
-        }
-
-        // Maybe an error in the stream.
-        if (std::ferror(fp))
-            throw from_errno();
-
-        // Missing '\n' in end of file.
-        if (!buffer.empty()) {
-            dukx_push(ctx, clear_crlf(buffer));
-            duk_put_prop_index(ctx, -2, i++);
-        }
-
-        return 1;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.read
-
-/*
- * Method: Irccd.File.prototype.read(amount)
- * --------------------------------------------------------
- *
- * Read the specified amount of characters or the whole file.
- *
- * Arguments:
- *   - amount, the amount of characters or -1 to read all (Optional, default: -1).
- * Returns:
- *   The string.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_prototype_read(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        const auto fp = self(ctx)->get_handle();
-        const auto amount = duk_is_number(ctx, 0) ? duk_get_int(ctx, 0) : -1;
-
-        if (amount == 0 || !fp)
-            return 0;
-
-        std::string data;
-        std::size_t total = 0;
-
-        if (amount < 0) {
-            std::array<char, 128> buffer;
-            std::size_t nread;
-
-            while ((nread = std::fread(&buffer[0], sizeof (buffer[0]), buffer.size(), fp)) > 0) {
-                if (std::ferror(fp))
-                    throw from_errno();
-
-                std::copy(buffer.begin(), buffer.begin() + nread, std::back_inserter(data));
-                total += nread;
-            }
-        } else {
-            data.resize(static_cast<std::size_t>(amount));
-            total = std::fread(&data[0], sizeof (data[0]), static_cast<std::size_t>(amount), fp);
-
-            if (std::ferror(fp))
-                throw from_errno();
-
-            data.resize(total);
-        }
-
-        return dukx_push(ctx, data);
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.readline
-
-/*
- * Method: Irccd.File.prototype.readline()
- * --------------------------------------------------------
- *
- * Read the next line available.
- *
- * Returns:
- *   The next line or undefined if eof.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_prototype_readline(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        auto fp = self(ctx)->get_handle();
-
-        if (fp == nullptr || std::feof(fp))
-            return 0;
-
-        std::string result;
-
-        for (int ch; (ch = std::fgetc(fp)) != EOF && ch != '\n'; )
-            result += (char)ch;
-        if (std::ferror(fp))
-            throw from_errno();
-
-        return dukx_push(ctx, clear_crlf(result));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.remove
-
-/*
- * Method: Irccd.File.prototype.remove()
- * --------------------------------------------------------
- *
- * Synonym of Irccd.File.prototype.remove(path) but with the path from the file.
- *
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_prototype_remove(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        boost::filesystem::remove(self(ctx)->get_path());
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.seek
-
-/*
- * Method: Irccd.File.prototype.seek(type, amount)
- * --------------------------------------------------------
- *
- * Sets the position in the file.
- *
- * Arguments:
- *   - type, the type of setting (File.SeekSet, File.SeekCur, File.SeekSet),
- *   - amount, the new offset.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_prototype_seek(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        auto fp = self(ctx)->get_handle();
-        auto type = duk_require_int(ctx, 0);
-        auto amount = duk_require_int(ctx, 1);
-
-        if (fp != nullptr && std::fseek(fp, amount, type) != 0)
-            throw from_errno();
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.stat
-
-#if defined(HAVE_STAT)
-
-/*
- * Method: Irccd.File.prototype.stat() [optional]
- * --------------------------------------------------------
- *
- * Synonym of File.stat(path) but with the path from the file.
- *
- * Returns:
- *   The stat information.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_prototype_stat(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        auto file = self(ctx);
-        struct stat st;
-
-        if (file->get_handle() == nullptr && ::stat(file->get_path().c_str(), &st) < 0)
-            throw from_errno();
-
-        dukx_push(ctx, st);
-
-        return 1;
-    });
-}
-
-#endif // !HAVE_STAT
-
-// }}}
-
-// {{{ Irccd.File.prototype.tell
-
-/*
- * Method: Irccd.File.prototype.tell()
- * --------------------------------------------------------
- *
- * Get the actual position in the file.
- *
- * Returns:
- *   The position.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_prototype_tell(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        auto fp = self(ctx)->get_handle();
-        long pos;
-
-        if (fp == nullptr)
-            return 0;
-
-        if ((pos = std::ftell(fp)) == -1L)
-            throw from_errno();
-
-        duk_push_int(ctx, pos);
-
-        return 1;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.write
-
-/*
- * Method: Irccd.File.prototype.write(data)
- * --------------------------------------------------------
- *
- * Write some characters to the file.
- *
- * Arguments:
- *   - data, the character to write.
- * Returns:
- *   The number of bytes written.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_prototype_write(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        auto fp = self(ctx)->get_handle();
-        auto data = dukx_require<std::string>(ctx, 0);
-
-        if (fp == nullptr)
-            return 0;
-
-        const auto nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
-
-        if (std::ferror(fp))
-            throw from_errno();
-
-        duk_push_uint(ctx, nwritten);
-
-        return 1;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File [constructor]
-
-/*
- * Function: Irccd.File(path, mode) [constructor]
- * --------------------------------------------------------
- *
- * Open a file specified by path with the specified mode.
- *
- * Arguments:
- *   - path, the path to the file,
- *   - mode, the mode string.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_constructor(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        if (!duk_is_constructor_call(ctx))
-            return 0;
-
-        const auto path = dukx_require<std::string>(ctx, 0);
-        const auto mode = dukx_require<std::string>(ctx, 1);
-
-        duk_push_this(ctx);
-        duk_push_pointer(ctx, new std::shared_ptr<file>(new file(path, mode)));
-        duk_put_prop_string(ctx, -2, signature);
-        duk_pop(ctx);
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File [destructor]
-
-/*
- * Function: Irccd.File() [destructor]
- * ------------------------------------------------------------------
- *
- * Delete the property.
- */
-duk_ret_t File_destructor(duk_context* ctx)
-{
-    duk_get_prop_string(ctx, 0, signature);
-    delete static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-    duk_del_prop_string(ctx, 0, signature);
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.File.basename
-
-/*
- * Function: Irccd.File.basename(path)
- * --------------------------------------------------------
- *
- * duk_ret_turn the file basename as specified in `basename(3)` C function.
- *
- * Arguments:
- *   - path, the path to the file.
- * Returns:
- *   The base name.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_basename(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, fs_util::base_name(duk_require_string(ctx, 0)));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.dirname
-
-/*
- * Function: Irccd.File.dirname(path)
- * --------------------------------------------------------
- *
- * duk_ret_turn the file directory name as specified in `dirname(3)` C function.
- *
- * Arguments:
- *   - path, the path to the file.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_dirname(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, fs_util::dir_name(duk_require_string(ctx, 0)));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.exists
-
-/*
- * Function: Irccd.File.exists(path)
- * --------------------------------------------------------
- *
- * Check if the file exists.
- *
- * Arguments:
- *   - path, the path to the file.
- * Returns:
- *   True if exists.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_exists(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, boost::filesystem::exists(duk_require_string(ctx, 0)));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.remove
-
-/*
- * function Irccd.File.remove(path)
- * --------------------------------------------------------
- *
- * Remove the file at the specified path.
- *
- * Arguments:
- *   - path, the path to the file.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_remove(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        boost::filesystem::remove(dukx_require<std::string>(ctx, 0));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.File.stat
-
-#if defined(HAVE_STAT)
-
-/*
- * function Irccd.File.stat(path) [optional]
- * --------------------------------------------------------
- *
- * Get file information at the specified path.
- *
- * Arguments:
- *   - path, the path to the file.
- * Returns:
- *   The stat information.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t File_stat(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        struct stat st;
-
-        if (::stat(duk_require_string(ctx, 0), &st) < 0)
-            throw from_errno();
-
-        return dukx_push(ctx, st);
-    });
-}
-
-#endif // !HAVE_STAT
-
-// }}}
-
-const duk_function_list_entry methods[] = {
-    { "basename",   File_prototype_basename,    0 },
-    { "close",      File_prototype_close,       0 },
-    { "dirname",    File_prototype_dirname,     0 },
-    { "lines",      File_prototype_lines,       0 },
-    { "read",       File_prototype_read,        1 },
-    { "readline",   File_prototype_readline,    0 },
-    { "remove",     File_prototype_remove,      0 },
-    { "seek",       File_prototype_seek,        2 },
-#if defined(HAVE_STAT)
-    { "stat",       File_prototype_stat,        0 },
-#endif
-    { "tell",       File_prototype_tell,        0 },
-    { "write",      File_prototype_write,       1 },
-    { nullptr,      nullptr,                    0 }
-};
-
-const duk_function_list_entry functions[] = {
-    { "basename",   File_basename,              1 },
-    { "dirname",    File_dirname,               1 },
-    { "exists",     File_exists,                1 },
-    { "remove",     File_remove,                1 },
-#if defined(HAVE_STAT)
-    { "stat",       File_stat,                  1 },
-#endif
-    { nullptr,      nullptr,                    0 }
-};
-
-const duk_number_list_entry constants[] = {
-    { "SeekCur",    SEEK_CUR },
-    { "SeekEnd",    SEEK_END },
-    { "SeekSet",    SEEK_SET },
-    { nullptr,      0        }
-};
-
-} // !namespace
-
-std::string file_jsapi::get_name() const
-{
-    return "Irccd.File";
-}
-
-void file_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_get_global_string(plugin->get_context(), "Irccd");
-    duk_push_c_function(plugin->get_context(), File_constructor, 2);
-    duk_put_number_list(plugin->get_context(), -1, constants);
-    duk_put_function_list(plugin->get_context(), -1, functions);
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, methods);
-    duk_push_c_function(plugin->get_context(), File_destructor, 1);
-    duk_set_finalizer(plugin->get_context(), -2);
-    duk_dup(plugin->get_context(), -1);
-    duk_put_global_string(plugin->get_context(), prototype);
-    duk_put_prop_string(plugin->get_context(), -2, "prototype");
-    duk_put_prop_string(plugin->get_context(), -2, "File");
-    duk_pop(plugin->get_context());
-}
-
-using file_traits = dukx_type_traits<std::shared_ptr<file>>;
-
-void file_traits::push(duk_context* ctx, std::shared_ptr<file> fp)
-{
-    assert(ctx);
-    assert(fp);
-
-    dukx_stack_assert sa(ctx, 1);
-
-    duk_push_object(ctx);
-    duk_push_pointer(ctx, new std::shared_ptr<file>(std::move(fp)));
-    duk_put_prop_string(ctx, -2, signature);
-    duk_get_global_string(ctx, prototype);
-    duk_set_prototype(ctx, -2);
-}
-
-std::shared_ptr<file> file_traits::require(duk_context* ctx, duk_idx_t index)
-{
-    if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature))
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object");
-
-    duk_get_prop_string(ctx, index, signature);
-    const auto fp = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-
-    return *fp;
-}
-
-#if defined(HAVE_STAT)
-
-void dukx_type_traits<struct stat>::push(duk_context* ctx, const struct stat& st)
-{
-    dukx_stack_assert sa(ctx, 1);
-
-    duk_push_object(ctx);
-
-#if defined(HAVE_STAT_ST_ATIME)
-    duk_push_int(ctx, st.st_atime);
-    duk_put_prop_string(ctx, -2, "atime");
-#endif
-#if defined(HAVE_STAT_ST_BLKSIZE)
-    duk_push_int(ctx, st.st_blksize);
-    duk_put_prop_string(ctx, -2, "blksize");
-#endif
-#if defined(HAVE_STAT_ST_BLOCKS)
-    duk_push_int(ctx, st.st_blocks);
-    duk_put_prop_string(ctx, -2, "blocks");
-#endif
-#if defined(HAVE_STAT_ST_CTIME)
-    duk_push_int(ctx, st.st_ctime);
-    duk_put_prop_string(ctx, -2, "ctime");
-#endif
-#if defined(HAVE_STAT_ST_DEV)
-    duk_push_int(ctx, st.st_dev);
-    duk_put_prop_string(ctx, -2, "dev");
-#endif
-#if defined(HAVE_STAT_ST_GID)
-    duk_push_int(ctx, st.st_gid);
-    duk_put_prop_string(ctx, -2, "gid");
-#endif
-#if defined(HAVE_STAT_ST_INO)
-    duk_push_int(ctx, st.st_ino);
-    duk_put_prop_string(ctx, -2, "ino");
-#endif
-#if defined(HAVE_STAT_ST_MODE)
-    duk_push_int(ctx, st.st_mode);
-    duk_put_prop_string(ctx, -2, "mode");
-#endif
-#if defined(HAVE_STAT_ST_MTIME)
-    duk_push_int(ctx, st.st_mtime);
-    duk_put_prop_string(ctx, -2, "mtime");
-#endif
-#if defined(HAVE_STAT_ST_NLINK)
-    duk_push_int(ctx, st.st_nlink);
-    duk_put_prop_string(ctx, -2, "nlink");
-#endif
-#if defined(HAVE_STAT_ST_RDEV)
-    duk_push_int(ctx, st.st_rdev);
-    duk_put_prop_string(ctx, -2, "rdev");
-#endif
-#if defined(HAVE_STAT_ST_SIZE)
-    duk_push_int(ctx, st.st_size);
-    duk_put_prop_string(ctx, -2, "size");
-#endif
-#if defined(HAVE_STAT_ST_UID)
-    duk_push_int(ctx, st.st_uid);
-    duk_put_prop_string(ctx, -2, "uid");
-#endif
-}
-
-#endif // !HAVE_STAT
-
-} // !irccd
--- a/libirccd-js/irccd/js/file_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,209 +0,0 @@
-/*
- * file_jsapi.hpp -- Irccd.File API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_FILE_JSAPI_HPP
-#define IRCCD_JS_FILE_JSAPI_HPP
-
-/**
- * \file file_jsapi.hpp
- * \brief Irccd.File Javascript API.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#if defined(HAVE_STAT)
-#   include <sys/types.h>
-#   include <sys/stat.h>
-#endif
-
-#include <cassert>
-#include <cerrno>
-#include <cstdio>
-#include <cstring>
-#include <functional>
-#include <stdexcept>
-#include <string>
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief Object for Javascript to perform I/O.
- *
- * This class can be constructed to Javascript.
- *
- * It is used in:
- *
- * - Irccd.File [constructor]
- * - Irccd.System.popen (optional)
- */
-class file {
-private:
-    file(const file&) = delete;
-    file& operator=(const file&) = delete;
-
-    file(file&&) = delete;
-    file& operator=(file&&) = delete;
-
-private:
-    std::string path_;
-    std::FILE* stream_;
-    std::function<void (std::FILE*)> destructor_;
-
-public:
-    /**
-     * Construct a file specified by path
-     *
-     * \param path the path
-     * \param mode the mode string (for std::fopen)
-     * \throw std::runtime_error on failures
-     */
-    inline file(std::string path, const std::string& mode)
-        : path_(std::move(path))
-        , destructor_([] (std::FILE* fp) { std::fclose(fp); })
-    {
-        if ((stream_ = std::fopen(path_.c_str(), mode.c_str())) == nullptr)
-            throw std::runtime_error(std::strerror(errno));
-    }
-
-    /**
-     * Construct a file from a already created FILE pointer (e.g. popen).
-     *
-     * The class takes ownership of fp and will close it.
-     *
-     * \pre destructor must not be null
-     * \param fp the file pointer
-     * \param destructor the function to close fp (e.g. std::fclose)
-     */
-    inline file(std::FILE* fp, std::function<void (std::FILE*)> destructor) noexcept
-        : stream_(fp)
-        , destructor_(std::move(destructor))
-    {
-        assert(destructor_ != nullptr);
-    }
-
-    /**
-     * Closes the file.
-     */
-    virtual ~file() noexcept
-    {
-        close();
-    }
-
-    /**
-     * Get the path.
-     *
-     * \return the path
-     * \warning empty when constructed from the FILE constructor
-     */
-    inline const std::string& get_path() const noexcept
-    {
-        return path_;
-    }
-
-    /**
-     * Get the handle.
-     *
-     * \return the handle or nullptr if the stream was closed
-     */
-    inline std::FILE* get_handle() noexcept
-    {
-        return stream_;
-    }
-
-    /**
-     * Force close, can be safely called multiple times.
-     */
-    inline void close() noexcept
-    {
-        if (stream_) {
-            destructor_(stream_);
-            stream_ = nullptr;
-        }
-    }
-};
-
-/**
- * \brief Irccd.File Javascript API.
- * \ingroup jsapi
- */
-class file_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc jsapi::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-/**
- * \brief Specialization for generic file type as shared_ptr.
- *
- * Supports push, require.
- */
-template <>
-class dukx_type_traits<std::shared_ptr<file>> : public std::true_type {
-public:
-    /**
-     * Push a file.
-     *
-     * \pre fp != nullptr
-     * \param ctx the the context
-     * \param fp the file
-     */
-    static void push(duk_context* ctx, std::shared_ptr<file> fp);
-
-    /**
-     * Require a file. Raises a JavaScript error if not a File.
-     *
-     * \param ctx the context
-     * \param index the index
-     * \return the file pointer
-     */
-    static std::shared_ptr<file> require(duk_context* ctx, duk_idx_t index);
-};
-
-#if defined(HAVE_STAT)
-
-/**
- * \brief Specialization for struct stat.
- *
- * Supports push.
- */
-template <>
-class dukx_type_traits<struct stat> : public std::true_type {
-public:
-    /**
-     * Push the stat information to the stack as Javascript object.
-     *
-     * \param ctx the context
-     * \param st the stat structure
-     */
-    static void push(duk_context* ctx, const struct stat& st);
-};
-
-#endif // !HAVE_STAT
-
-} // !irccd
-
-#endif // !IRCCD_JS_FILE_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/irccd_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,244 @@
+/*
+ * irccd_js_api.cpp -- Irccd API
+ *
+ * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
+
+#include <cerrno>
+#include <string>
+#include <unordered_map>
+
+#include "irccd_js_api.hpp"
+#include "js_plugin.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ do_raise
+
+template <typename Error>
+void do_raise(duk_context* ctx, const Error& ex)
+{
+    duk::stack_guard sa(ctx, 1);
+
+    duk_get_global_string(ctx, "Irccd");
+    duk_get_prop_string(ctx, -1, "SystemError");
+    duk_remove(ctx, -2);
+    duk::push(ctx, ex.code().value());
+    duk::push(ctx, ex.code().message());
+    duk_new(ctx, 2);
+
+    (void)duk_throw(ctx);
+}
+
+// }}}
+
+// {{{ Irccd.SystemError [constructor]
+
+auto constructor(duk_context* ctx) -> duk_ret_t
+{
+    duk_push_this(ctx);
+    duk_push_int(ctx, duk_require_int(ctx, 0));
+    duk_put_prop_string(ctx, -2, "errno");
+    duk_push_string(ctx, duk_require_string(ctx, 1));
+    duk_put_prop_string(ctx, -2, "message");
+    duk_push_string(ctx, "SystemError");
+    duk_put_prop_string(ctx, -2, "name");
+    duk_pop(ctx);
+
+    return 0;
+}
+
+// }}}
+
+// {{{ definitions
+
+const std::unordered_map<std::string, int> errors{
+    { "E2BIG",              E2BIG           },
+    { "EACCES",             EACCES          },
+    { "EADDRINUSE",         EADDRINUSE      },
+    { "EADDRNOTAVAIL",      EADDRNOTAVAIL   },
+    { "EAFNOSUPPORT",       EAFNOSUPPORT    },
+    { "EAGAIN",             EAGAIN          },
+    { "EALREADY",           EALREADY        },
+    { "EBADF",              EBADF           },
+#if defined(EBADMSG)
+    { "EBADMSG",            EBADMSG         },
+#endif
+    { "EBUSY",              EBUSY           },
+    { "ECANCELED",          ECANCELED       },
+    { "ECHILD",             ECHILD          },
+    { "ECONNABORTED",       ECONNABORTED    },
+    { "ECONNREFUSED",       ECONNREFUSED    },
+    { "ECONNRESET",         ECONNRESET      },
+    { "EDEADLK",            EDEADLK         },
+    { "EDESTADDRREQ",       EDESTADDRREQ    },
+    { "EDOM",               EDOM            },
+    { "EEXIST",             EEXIST          },
+    { "EFAULT",             EFAULT          },
+    { "EFBIG",              EFBIG           },
+    { "EHOSTUNREACH",       EHOSTUNREACH    },
+#if defined(EIDRM)
+    { "EIDRM",              EIDRM           },
+#endif
+    { "EILSEQ",             EILSEQ          },
+    { "EINPROGRESS",        EINPROGRESS     },
+    { "EINTR",              EINTR           },
+    { "EINVAL",             EINVAL          },
+    { "EIO",                EIO             },
+    { "EISCONN",            EISCONN         },
+    { "EISDIR",             EISDIR          },
+    { "ELOOP",              ELOOP           },
+    { "EMFILE",             EMFILE          },
+    { "EMLINK",             EMLINK          },
+    { "EMSGSIZE",           EMSGSIZE        },
+    { "ENAMETOOLONG",       ENAMETOOLONG    },
+    { "ENETDOWN",           ENETDOWN        },
+    { "ENETRESET",          ENETRESET       },
+    { "ENETUNREACH",        ENETUNREACH     },
+    { "ENFILE",             ENFILE          },
+    { "ENOBUFS",            ENOBUFS         },
+#if defined(ENODATA)
+    { "ENODATA",            ENODATA         },
+#endif
+    { "ENODEV",             ENODEV          },
+    { "ENOENT",             ENOENT          },
+    { "ENOEXEC",            ENOEXEC         },
+    { "ENOLCK",             ENOLCK          },
+#if defined(ENOLINK)
+    { "ENOLINK",            ENOLINK         },
+#endif
+    { "ENOMEM",             ENOMEM          },
+#if defined(ENOMSG)
+    { "ENOMSG",             ENOMSG          },
+#endif
+    { "ENOPROTOOPT",        ENOPROTOOPT     },
+    { "ENOSPC",             ENOSPC          },
+#if defined(ENOSR)
+    { "ENOSR",              ENOSR           },
+#endif
+#if defined(ENOSTR)
+    { "ENOSTR",             ENOSTR          },
+#endif
+    { "ENOSYS",             ENOSYS          },
+    { "ENOTCONN",           ENOTCONN        },
+    { "ENOTDIR",            ENOTDIR         },
+    { "ENOTEMPTY",          ENOTEMPTY       },
+#if defined(ENOTRECOVERABLE)
+    { "ENOTRECOVERABLE",    ENOTRECOVERABLE },
+#endif
+    { "ENOTSOCK",           ENOTSOCK        },
+    { "ENOTSUP",            ENOTSUP         },
+    { "ENOTTY",             ENOTTY          },
+    { "ENXIO",              ENXIO           },
+    { "EOPNOTSUPP",         EOPNOTSUPP      },
+    { "EOVERFLOW",          EOVERFLOW       },
+    { "EOWNERDEAD",         EOWNERDEAD      },
+    { "EPERM",              EPERM           },
+    { "EPIPE",              EPIPE           },
+    { "EPROTO",             EPROTO          },
+    { "EPROTONOSUPPORT",    EPROTONOSUPPORT },
+    { "EPROTOTYPE",         EPROTOTYPE      },
+    { "ERANGE",             ERANGE          },
+    { "EROFS",              EROFS           },
+    { "ESPIPE",             ESPIPE          },
+    { "ESRCH",              ESRCH           },
+#if defined(ETIME)
+    { "ETIME",              ETIME           },
+#endif
+    { "ETIMEDOUT",          ETIMEDOUT       },
+#if defined(ETXTBSY)
+    { "ETXTBSY",            ETXTBSY         },
+#endif
+    { "EWOULDBLOCK",        EWOULDBLOCK     },
+    { "EXDEV",              EXDEV           }
+};
+
+// }}}
+
+} // !namespace
+
+void duk::type_traits<std::system_error>::raise(duk_context* ctx, const std::system_error& ex)
+{
+    do_raise(ctx, ex);
+}
+
+void duk::type_traits<boost::system::system_error>::raise(duk_context* ctx, const boost::system::system_error& ex)
+{
+    do_raise(ctx, ex);
+}
+
+auto irccd_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd";
+}
+
+void irccd_js_api::load(irccd& irccd, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    // irccd.
+    duk_push_object(plugin->get_context());
+
+    // Version.
+    duk_push_object(plugin->get_context());
+    duk::push(plugin->get_context(), IRCCD_VERSION_MAJOR);
+    duk_put_prop_string(plugin->get_context(), -2, "major");
+    duk::push(plugin->get_context(), IRCCD_VERSION_MINOR);
+    duk_put_prop_string(plugin->get_context(), -2, "minor");
+    duk::push(plugin->get_context(), IRCCD_VERSION_PATCH);
+    duk_put_prop_string(plugin->get_context(), -2, "patch");
+    duk_put_prop_string(plugin->get_context(), -2, "version");
+
+    // Create the system_error that inherits from Error.
+    duk_push_c_function(plugin->get_context(), constructor, 2);
+
+    // Put errno codes into the irccd.system_error object.
+    for (const auto& [k, v] : errors) {
+        duk_push_int(plugin->get_context(), v);
+        duk_put_prop_string(plugin->get_context(), -2, k.c_str());
+    }
+
+    duk_push_object(plugin->get_context());
+    duk_get_global_string(plugin->get_context(), "Error");
+    duk_get_prop_string(plugin->get_context(), -1, "prototype");
+    duk_remove(plugin->get_context(), -2);
+    duk_set_prototype(plugin->get_context(), -2);
+    duk_put_prop_string(plugin->get_context(), -2, "prototype");
+    duk_put_prop_string(plugin->get_context(), -2, "SystemError");
+
+    // Set irccd as global.
+    duk_put_global_string(plugin->get_context(), "Irccd");
+
+    // Store global instance.
+    duk_push_pointer(plugin->get_context(), &irccd);
+    duk_put_global_string(plugin->get_context(), "\xff""\xff""irccd-ref");
+}
+
+auto duk::type_traits<irccd>::self(duk_context *ctx) -> irccd&
+{
+    duk::stack_guard sa(ctx);
+
+    duk_get_global_string(ctx, "\xff""\xff""irccd-ref");
+    const auto ptr = static_cast<irccd*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+
+    return *ptr;
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/irccd_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,103 @@
+/*
+ * irccd_js_api.hpp -- Irccd API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_IRCCD_JS_API_HPP
+#define IRCCD_JS_IRCCD_JS_API_HPP
+
+/**
+ * \file irccd_js_api.hpp
+ * \brief irccd Javascript API.
+ */
+
+#include <cerrno>
+#include <cstring>
+#include <string>
+#include <system_error>
+
+#include <boost/system/system_error.hpp>
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Irccd Javascript API.
+ * \ingroup js_api
+ */
+class irccd_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc js_api::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+namespace duk {
+
+/**
+ * \brief Specialize dukx_type_traits for irccd.
+ */
+template <>
+struct type_traits<irccd> {
+    /**
+     * Get irccd instance stored in this context.
+     *
+     * \param ctx the context
+     * \return the irccd reference
+     */
+    static auto self(duk_context* ctx) -> irccd&;
+};
+
+/**
+ * \brief Specialize dukx_type_traits for std::system_error.
+ */
+template <>
+struct type_traits<std::system_error> {
+    /**
+     * Raise an Irccd.SystemError.
+     *
+     * \param ctx the context
+     * param ex the exception
+     */
+    static void raise(duk_context* ctx, const std::system_error& ex);
+};
+
+/**
+ * \brief Specialize dukx_type_traits for boost::system::system_error.
+ */
+template <>
+struct type_traits<boost::system::system_error> {
+    /**
+     * Raise an Irccd.SystemError.
+     *
+     * \param ctx the context
+     * param ex the exception
+     */
+    static void raise(duk_context* ctx, const boost::system::system_error& ex);
+};
+
+} // !duk
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_IRCCD_JS_API_HPP
--- a/libirccd-js/irccd/js/irccd_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-/*
- * irccd_jsapi.cpp -- Irccd API
- *
- * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
-
-#include <cerrno>
-#include <string>
-#include <unordered_map>
-
-#include "irccd_jsapi.hpp"
-#include "js_plugin.hpp"
-
-namespace irccd {
-
-namespace {
-
-template <typename Error>
-void do_raise(duk_context* ctx, const Error& ex)
-{
-    dukx_stack_assert sa(ctx, 1);
-
-    duk_get_global_string(ctx, "Irccd");
-    duk_get_prop_string(ctx, -1, "SystemError");
-    duk_remove(ctx, -2);
-    dukx_push(ctx, ex.code().value());
-    dukx_push(ctx, ex.code().message());
-    duk_new(ctx, 2);
-
-    (void)duk_throw(ctx);
-}
-
-const std::unordered_map<std::string, int> errors{
-    { "E2BIG",              E2BIG           },
-    { "EACCES",             EACCES          },
-    { "EADDRINUSE",         EADDRINUSE      },
-    { "EADDRNOTAVAIL",      EADDRNOTAVAIL   },
-    { "EAFNOSUPPORT",       EAFNOSUPPORT    },
-    { "EAGAIN",             EAGAIN          },
-    { "EALREADY",           EALREADY        },
-    { "EBADF",              EBADF           },
-#if defined(EBADMSG)
-    { "EBADMSG",            EBADMSG         },
-#endif
-    { "EBUSY",              EBUSY           },
-    { "ECANCELED",          ECANCELED       },
-    { "ECHILD",             ECHILD          },
-    { "ECONNABORTED",       ECONNABORTED    },
-    { "ECONNREFUSED",       ECONNREFUSED    },
-    { "ECONNRESET",         ECONNRESET      },
-    { "EDEADLK",            EDEADLK         },
-    { "EDESTADDRREQ",       EDESTADDRREQ    },
-    { "EDOM",               EDOM            },
-    { "EEXIST",             EEXIST          },
-    { "EFAULT",             EFAULT          },
-    { "EFBIG",              EFBIG           },
-    { "EHOSTUNREACH",       EHOSTUNREACH    },
-#if defined(EIDRM)
-    { "EIDRM",              EIDRM           },
-#endif
-    { "EILSEQ",             EILSEQ          },
-    { "EINPROGRESS",        EINPROGRESS     },
-    { "EINTR",              EINTR           },
-    { "EINVAL",             EINVAL          },
-    { "EIO",                EIO             },
-    { "EISCONN",            EISCONN         },
-    { "EISDIR",             EISDIR          },
-    { "ELOOP",              ELOOP           },
-    { "EMFILE",             EMFILE          },
-    { "EMLINK",             EMLINK          },
-    { "EMSGSIZE",           EMSGSIZE        },
-    { "ENAMETOOLONG",       ENAMETOOLONG    },
-    { "ENETDOWN",           ENETDOWN        },
-    { "ENETRESET",          ENETRESET       },
-    { "ENETUNREACH",        ENETUNREACH     },
-    { "ENFILE",             ENFILE          },
-    { "ENOBUFS",            ENOBUFS         },
-#if defined(ENODATA)
-    { "ENODATA",            ENODATA         },
-#endif
-    { "ENODEV",             ENODEV          },
-    { "ENOENT",             ENOENT          },
-    { "ENOEXEC",            ENOEXEC         },
-    { "ENOLCK",             ENOLCK          },
-#if defined(ENOLINK)
-    { "ENOLINK",            ENOLINK         },
-#endif
-    { "ENOMEM",             ENOMEM          },
-#if defined(ENOMSG)
-    { "ENOMSG",             ENOMSG          },
-#endif
-    { "ENOPROTOOPT",        ENOPROTOOPT     },
-    { "ENOSPC",             ENOSPC          },
-#if defined(ENOSR)
-    { "ENOSR",              ENOSR           },
-#endif
-#if defined(ENOSTR)
-    { "ENOSTR",             ENOSTR          },
-#endif
-    { "ENOSYS",             ENOSYS          },
-    { "ENOTCONN",           ENOTCONN        },
-    { "ENOTDIR",            ENOTDIR         },
-    { "ENOTEMPTY",          ENOTEMPTY       },
-#if defined(ENOTRECOVERABLE)
-    { "ENOTRECOVERABLE",    ENOTRECOVERABLE },
-#endif
-    { "ENOTSOCK",           ENOTSOCK        },
-    { "ENOTSUP",            ENOTSUP         },
-    { "ENOTTY",             ENOTTY          },
-    { "ENXIO",              ENXIO           },
-    { "EOPNOTSUPP",         EOPNOTSUPP      },
-    { "EOVERFLOW",          EOVERFLOW       },
-    { "EOWNERDEAD",         EOWNERDEAD      },
-    { "EPERM",              EPERM           },
-    { "EPIPE",              EPIPE           },
-    { "EPROTO",             EPROTO          },
-    { "EPROTONOSUPPORT",    EPROTONOSUPPORT },
-    { "EPROTOTYPE",         EPROTOTYPE      },
-    { "ERANGE",             ERANGE          },
-    { "EROFS",              EROFS           },
-    { "ESPIPE",             ESPIPE          },
-    { "ESRCH",              ESRCH           },
-#if defined(ETIME)
-    { "ETIME",              ETIME           },
-#endif
-    { "ETIMEDOUT",          ETIMEDOUT       },
-#if defined(ETXTBSY)
-    { "ETXTBSY",            ETXTBSY         },
-#endif
-    { "EWOULDBLOCK",        EWOULDBLOCK     },
-    { "EXDEV",              EXDEV           }
-};
-
-duk_ret_t constructor(duk_context* ctx)
-{
-    duk_push_this(ctx);
-    duk_push_int(ctx, duk_require_int(ctx, 0));
-    duk_put_prop_string(ctx, -2, "errno");
-    duk_push_string(ctx, duk_require_string(ctx, 1));
-    duk_put_prop_string(ctx, -2, "message");
-    duk_push_string(ctx, "SystemError");
-    duk_put_prop_string(ctx, -2, "name");
-    duk_pop(ctx);
-
-    return 0;
-}
-
-} // !namespace
-
-void dukx_type_traits<std::system_error>::raise(duk_context* ctx, const std::system_error& ex)
-{
-    do_raise(ctx, ex);
-}
-
-void dukx_type_traits<boost::system::system_error>::raise(duk_context* ctx, const boost::system::system_error& ex)
-{
-    do_raise(ctx, ex);
-}
-
-std::string irccd_jsapi::get_name() const
-{
-    return "Irccd";
-}
-
-void irccd_jsapi::load(irccd& irccd, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    // irccd.
-    duk_push_object(plugin->get_context());
-
-    // Version.
-    duk_push_object(plugin->get_context());
-    dukx_push(plugin->get_context(), IRCCD_VERSION_MAJOR);
-    duk_put_prop_string(plugin->get_context(), -2, "major");
-    dukx_push(plugin->get_context(), IRCCD_VERSION_MINOR);
-    duk_put_prop_string(plugin->get_context(), -2, "minor");
-    dukx_push(plugin->get_context(), IRCCD_VERSION_PATCH);
-    duk_put_prop_string(plugin->get_context(), -2, "patch");
-    duk_put_prop_string(plugin->get_context(), -2, "version");
-
-    // Create the system_error that inherits from Error.
-    duk_push_c_function(plugin->get_context(), constructor, 2);
-
-    // Put errno codes into the irccd.system_error object.
-    for (const auto& pair : errors) {
-        duk_push_int(plugin->get_context(), pair.second);
-        duk_put_prop_string(plugin->get_context(), -2, pair.first.c_str());
-    }
-
-    duk_push_object(plugin->get_context());
-    duk_get_global_string(plugin->get_context(), "Error");
-    duk_get_prop_string(plugin->get_context(), -1, "prototype");
-    duk_remove(plugin->get_context(), -2);
-    duk_set_prototype(plugin->get_context(), -2);
-    duk_put_prop_string(plugin->get_context(), -2, "prototype");
-    duk_put_prop_string(plugin->get_context(), -2, "SystemError");
-
-    // Set irccd as global.
-    duk_put_global_string(plugin->get_context(), "Irccd");
-
-    // Store global instance.
-    duk_push_pointer(plugin->get_context(), &irccd);
-    duk_put_global_string(plugin->get_context(), "\xff""\xff""irccd-ref");
-}
-
-irccd& dukx_type_traits<irccd>::self(duk_context *ctx)
-{
-    dukx_stack_assert sa(ctx);
-
-    duk_get_global_string(ctx, "\xff""\xff""irccd-ref");
-    const auto ptr = static_cast<irccd*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-
-    return *ptr;
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/irccd_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/*
- * irccd_jsapi.hpp -- Irccd API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_IRCCD_JSAPI_HPP
-#define IRCCD_JS_IRCCD_JSAPI_HPP
-
-/**
- * \file irccd_jsapi.hpp
- * \brief irccd Javascript API.
- */
-
-#include <cerrno>
-#include <cstring>
-#include <string>
-#include <system_error>
-
-#include <boost/system/system_error.hpp>
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd Javascript API.
- * \ingroup jsapi
- */
-class irccd_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc jsapi::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-/**
- * \brief Specialize dukx_type_traits for irccd.
- */
-template <>
-class dukx_type_traits<irccd> : public std::true_type {
-public:
-    /**
-     * Get irccd instance stored in this context.
-     *
-     * \param ctx the context
-     * \return the irccd reference
-     */
-    static irccd& self(duk_context* ctx);
-};
-
-/**
- * \brief Specialize dukx_type_traits for std::system_error.
- */
-template <>
-class dukx_type_traits<std::system_error> : public std::true_type {
-public:
-    /**
-     * Raise an Irccd.SystemError.
-     *
-     * \param ctx the context
-     * param ex the exception
-     */
-    static void raise(duk_context* ctx, const std::system_error& ex);
-};
-
-/**
- * \brief Specialize dukx_type_traits for boost::system::system_error.
- */
-template <>
-class dukx_type_traits<boost::system::system_error> : public std::true_type {
-public:
-    /**
-     * Raise an Irccd.SystemError.
-     *
-     * \param ctx the context
-     * param ex the exception
-     */
-    static void raise(duk_context* ctx, const boost::system::system_error& ex);
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_IRCCD_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,60 @@
+/*
+ * js_api.cpp -- Javascript API module
+ *
+ * Copyright (c) 2013-2018 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 "directory_js_api.hpp"
+#include "elapsed_timer_js_api.hpp"
+#include "file_js_api.hpp"
+#include "irccd_js_api.hpp"
+#include "logger_js_api.hpp"
+#include "plugin_js_api.hpp"
+#include "server_js_api.hpp"
+#include "system_js_api.hpp"
+#include "timer_js_api.hpp"
+#include "unicode_js_api.hpp"
+#include "util_js_api.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+template <typename T>
+auto bind() noexcept -> js_api::factory
+{
+    return [] () noexcept {
+        return std::make_unique<T>();
+    };
+}
+
+} // !namespace
+
+const std::vector<js_api::factory> js_api::registry{
+    // Irccd API must be loaded first.
+    bind<irccd_js_api>(),
+    bind<directory_js_api>(),
+    bind<elapsed_timer_js_api>(),
+    bind<file_js_api>(),
+    bind<logger_js_api>(),
+    bind<plugin_js_api>(),
+    bind<server_js_api>(),
+    bind<system_js_api>(),
+    bind<timer_js_api>(),
+    bind<unicode_js_api>(),
+    bind<util_js_api>()
+};
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,92 @@
+/*
+ * js_api.hpp -- Javascript API module
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_JS_API_HPP
+#define IRCCD_JS_JS_API_HPP
+
+/**
+ * \file js_api.hpp
+ * \brief Javascript API module.
+ */
+
+/**
+ * \defgroup Javascript modules.
+ * \brief Modules for the Javascript API.
+ */
+
+#include <functional>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "duk.hpp"
+
+namespace irccd {
+
+class irccd;
+
+namespace js {
+
+class js_plugin;
+
+/**
+ * \brief Javascript API module.
+ */
+class js_api {
+public:
+    /**
+     * \brief Command constructor factory.
+     */
+    using factory = std::function<auto () -> std::unique_ptr<js_api>>;
+
+    /**
+     * \brief Registry of all commands.
+     */
+    static const std::vector<factory> registry;
+
+    /**
+     * Default constructor.
+     */
+    js_api() noexcept = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~js_api() noexcept = default;
+
+    /**
+     * Get the module name.
+     *
+     * \return the name
+     */
+    virtual auto get_name() const noexcept -> std::string_view = 0;
+
+    /**
+     * Load the module into the Javascript plugin.
+     *
+     * \param irccd the irccd instance
+     * \param plugin the plugin
+     */
+    virtual void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) = 0;
+};
+
+} // !js
+
+} // !irccd
+
+#endif // !IRCCD_JS_JS_API_HPP
--- a/libirccd-js/irccd/js/js_plugin.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/libirccd-js/irccd/js/js_plugin.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -23,31 +23,20 @@
 #include <stdexcept>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/logger.hpp>
 
-#include "directory_jsapi.hpp"
-#include "duktape_vector.hpp"
-#include "elapsed_timer_jsapi.hpp"
-#include "file_jsapi.hpp"
-#include "irccd_jsapi.hpp"
+#include "js_api.hpp"
 #include "js_plugin.hpp"
-#include "logger_jsapi.hpp"
-#include "plugin_jsapi.hpp"
-#include "server_jsapi.hpp"
-#include "system_jsapi.hpp"
-#include "timer_jsapi.hpp"
-#include "unicode_jsapi.hpp"
-#include "util_jsapi.hpp"
+#include "server_js_api.hpp"
 
-namespace irccd {
+namespace irccd::js {
 
 namespace {
 
-auto get_metadata(dukx_context& ctx, std::string_view name) -> std::string_view
+auto get_metadata(duk::context& ctx, std::string_view name) -> std::string_view
 {
     std::string_view ret("unknown");
 
-    dukx_stack_assert guard(ctx);
+    duk::stack_guard guard(ctx);
     duk_get_global_string(ctx, "info");
 
     if (duk_get_type(ctx, -1) == DUK_TYPE_OBJECT) {
@@ -64,11 +53,11 @@
     return ret;
 }
 
-auto get_table(dukx_context& ctx, std::string_view name) -> plugin::map
+auto get_table(duk::context& ctx, std::string_view name) -> plugin::map
 {
     plugin::map result;
 
-    dukx_stack_assert sa(ctx);
+    duk::stack_guard sa(ctx);
     duk_get_global_string(ctx, name.data());
     duk_enum(ctx, -1, 0);
 
@@ -82,13 +71,13 @@
     return result;
 }
 
-void set_table(dukx_context& ctx, std::string_view name, const plugin::map& vars)
+void set_table(duk::context& ctx, std::string_view name, const plugin::map& vars)
 {
-    dukx_stack_assert sa(ctx);
+    duk::stack_guard sa(ctx);
     duk_get_global_string(ctx, name.data());
 
     for (const auto& pair : vars) {
-        dukx_push(ctx, pair.second);
+        duk::push(ctx, pair.second);
         duk_put_prop_string(ctx, -2, pair.first.c_str());
     }
 
@@ -104,14 +93,14 @@
 template <typename Value, typename... Args>
 void js_plugin::push(Value&& value, Args&&... args)
 {
-    dukx_push(context_, std::forward<Value>(value));
+    duk::push(context_, std::forward<Value>(value));
     push(std::forward<Args>(args)...);
 }
 
 template <typename... Args>
 void js_plugin::call(const std::string& func, Args&&... args)
 {
-    dukx_stack_assert sa(context_);
+    duk::stack_guard sa(context_);
 
     duk_get_global_string(context_, func.c_str());
 
@@ -123,7 +112,7 @@
     push(std::forward<Args>(args)...);
 
     if (duk_pcall(context_, sizeof... (Args)) != 0)
-        throw plugin_error(plugin_error::exec_error, get_name(), dukx_stack(context_, -1).stack());
+        throw plugin_error(plugin_error::exec_error, get_name(), duk::get_stack(context_, -1).get_stack());
 
     duk_pop(context_);
 }
@@ -132,7 +121,7 @@
     : plugin(std::move(id))
     , path_(path)
 {
-    dukx_stack_assert sa(context_);
+    duk::stack_guard sa(context_);
 
     /*
      * Create two special tables for configuration and formats, they are
@@ -153,11 +142,11 @@
 
     duk_push_pointer(context_, this);
     duk_put_global_string(context_, "\xff""\xff""plugin");
-    dukx_push(context_, path);
+    duk::push(context_, path);
     duk_put_global_string(context_, "\xff""\xff""path");
 }
 
-auto js_plugin::get_context() noexcept -> dukx_context&
+auto js_plugin::get_context() noexcept -> duk::context&
 {
     return context_;
 }
@@ -230,7 +219,7 @@
     );
 
     if (duk_peval_string(context_, data.c_str()))
-        throw plugin_error(plugin_error::exec_error, get_name(), dukx_stack(context_, -1).stack());
+        throw plugin_error(plugin_error::exec_error, get_name(), duk::get_stack(context_, -1).get_stack());
 }
 
 void js_plugin::handle_command(irccd&, const message_event& event)
@@ -357,19 +346,19 @@
     return plugin;
 }
 
-void dukx_type_traits<whois_info>::push(duk_context* ctx, const whois_info& whois)
+void duk::type_traits<whois_info>::push(duk_context* ctx, const whois_info& whois)
 {
     duk_push_object(ctx);
-    dukx_push(ctx, whois.nick);
+    duk::push(ctx, whois.nick);
     duk_put_prop_string(ctx, -2, "nickname");
-    dukx_push(ctx, whois.user);
+    duk::push(ctx, whois.user);
     duk_put_prop_string(ctx, -2, "username");
-    dukx_push(ctx, whois.realname);
+    duk::push(ctx, whois.realname);
     duk_put_prop_string(ctx, -2, "realname");
-    dukx_push(ctx, whois.host);
+    duk::push(ctx, whois.host);
     duk_put_prop_string(ctx, -2, "host");
-    dukx_push(ctx, whois.channels);
+    duk::push(ctx, whois.channels);
     duk_put_prop_string(ctx, -2, "channels");
 }
 
-} // !irccd
+} // !irccd::js
--- a/libirccd-js/irccd/js/js_plugin.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/libirccd-js/irccd/js/js_plugin.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -27,11 +27,11 @@
 #include <irccd/daemon/plugin.hpp>
 #include <irccd/daemon/server.hpp>
 
-#include "duktape.hpp"
+#include "duk.hpp"
 
-namespace irccd {
+namespace irccd::js {
 
-class jsapi;
+class js_api;
 
 /**
  * \brief JavaScript plugins for irccd.
@@ -56,14 +56,11 @@
 
 private:
     // JavaScript context.
-    mutable dukx_context context_;
+    mutable duk::context context_;
 
     // Path to Javascript script file.
     std::string path_;
 
-    /*
-     * Helpers to call a Javascript function.
-     */
     void push() noexcept;
 
     template <typename Value, typename... Args>
@@ -86,7 +83,7 @@
      *
      * \return the context
      */
-    auto get_context() noexcept -> dukx_context&;
+    auto get_context() noexcept -> duk::context&;
 
     /**
      * Open the script file associated.
@@ -244,7 +241,7 @@
  */
 class js_plugin_loader : public plugin_loader {
 public:
-    using modules = std::vector<std::unique_ptr<jsapi>>;
+    using modules = std::vector<std::unique_ptr<js_api>>;
 
 private:
     irccd& irccd_;
@@ -283,12 +280,24 @@
     auto open(std::string_view id, std::string_view path) -> std::shared_ptr<plugin>;
 };
 
+namespace duk {
+
+/**
+ * \brief Specialization for type_traits<whois_info>
+ */
 template <>
-class dukx_type_traits<whois_info> : public std::true_type {
-public:
+struct type_traits<whois_info> : public std::true_type {
+    /**
+     * Push a whois_info.
+     *
+     * \param ctx the Duktape context
+     * \param who the information
+     */
     static void push(duk_context* ctx, const whois_info& who);
 };
 
-} // !irccd
+} // !duk
+
+} // !irccd::js
 
 #endif // !IRCCD_PLUGIN_JS_HPP
--- a/libirccd-js/irccd/js/jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * jsapi.cpp -- Javascript API module
- *
- * Copyright (c) 2013-2018 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 "directory_jsapi.hpp"
-#include "elapsed_timer_jsapi.hpp"
-#include "file_jsapi.hpp"
-#include "irccd_jsapi.hpp"
-#include "logger_jsapi.hpp"
-#include "plugin_jsapi.hpp"
-#include "server_jsapi.hpp"
-#include "system_jsapi.hpp"
-#include "timer_jsapi.hpp"
-#include "unicode_jsapi.hpp"
-#include "util_jsapi.hpp"
-
-namespace irccd {
-
-namespace {
-
-template <typename T>
-auto bind() noexcept -> jsapi::factory
-{
-    return [] () noexcept {
-        return std::make_unique<T>();
-    };
-}
-
-} // !namespace
-
-const std::vector<jsapi::factory> jsapi::registry{
-    // Irccd API must be loaded first.
-    bind<irccd_jsapi>(),
-    bind<directory_jsapi>(),
-    bind<elapsed_timer_jsapi>(),
-    bind<file_jsapi>(),
-    bind<logger_jsapi>(),
-    bind<plugin_jsapi>(),
-    bind<server_jsapi>(),
-    bind<system_jsapi>(),
-    bind<timer_jsapi>(),
-    bind<unicode_jsapi>(),
-    bind<util_jsapi>()
-};
-
-} // !irccd
--- a/libirccd-js/irccd/js/jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/*
- * jsapi.hpp -- Javascript API module
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_JSAPI_HPP
-#define IRCCD_JS_JSAPI_HPP
-
-/**
- * \file jsapi.hpp
- * \brief Javascript API module.
- */
-
-/**
- * \defgroup Javascript modules.
- * \brief Modules for the Javascript API.
- */
-
-#include <functional>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "duktape.hpp"
-
-namespace irccd {
-
-class irccd;
-class js_plugin;
-
-/**
- * \brief Javascript API module.
- */
-class jsapi {
-public:
-    /**
-     * \brief Command constructor factory.
-     */
-    using factory = std::function<auto () -> std::unique_ptr<jsapi>>;
-
-    /**
-     * \brief Registry of all commands.
-     */
-    static const std::vector<factory> registry;
-
-    /**
-     * Default constructor.
-     */
-    jsapi() noexcept = default;
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~jsapi() noexcept = default;
-
-    /**
-     * Get the module name.
-     *
-     * \return the name
-     */
-    virtual std::string get_name() const = 0;
-
-    /**
-     * Load the module into the Javascript plugin.
-     *
-     * \param irccd the irccd instance
-     * \param plugin the plugin
-     */
-    virtual void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) = 0;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/logger_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,147 @@
+/*
+ * logger_js_api.cpp -- Irccd.Logger API
+ *
+ * Copyright (c) 2013-2018 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 <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/logger.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+
+#include "irccd_js_api.hpp"
+#include "js_plugin.hpp"
+#include "logger_js_api.hpp"
+#include "plugin_js_api.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ print
+
+auto print(duk_context* ctx, unsigned level) -> duk_ret_t
+{
+    assert(level <= 2);
+
+    try {
+        auto& sink = duk::type_traits<irccd>::self(ctx).get_log();
+        auto& self = duk::type_traits<js_plugin>::self(ctx);
+
+        switch (level) {
+        case 0:
+            sink.debug<plugin>(self) << duk_require_string(ctx, 0) << std::endl;
+            break;
+        case 1:
+            sink.info<plugin>(self) << duk_require_string(ctx, 0) << std::endl;
+            break;
+        default:
+            sink.warning<plugin>(self) << duk_require_string(ctx, 0) << std::endl;
+            break;
+        }
+    } catch (const std::exception& ex) {
+        duk::raise(ctx, ex);
+    }
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Logger.info
+
+/*
+ * Function: Irccd.Logger.info(message)
+ * --------------------------------------------------------
+ *
+ * Write a verbose message.
+ *
+ * Arguments:
+ *   - message, the message.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Logger_info(duk_context* ctx) -> duk_ret_t
+{
+    return print(ctx, 1);
+}
+
+// }}}
+
+// {{{ Irccd.Logger.warning
+
+/*
+ * Function: Irccd.Logger.warning(message)
+ * --------------------------------------------------------
+ *
+ * Write a warning message.
+ *
+ * Arguments:
+ *   - message, the warning.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Logger_warning(duk_context* ctx) -> duk_ret_t
+{
+    return print(ctx, 2);
+}
+
+// }}}
+
+// {{{ Irccd.Logger.debug
+
+/*
+ * Function: Irccd.Logger.debug(message)
+ * --------------------------------------------------------
+ *
+ * Write a debug message, only shown if irccd is compiled in debug.
+ *
+ * Arguments:
+ *   - message, the message.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Logger_debug(duk_context* ctx) -> duk_ret_t
+{
+    return print(ctx, 0);
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+    { "info",       Logger_info,    1 },
+    { "warning",    Logger_warning, 1 },
+    { "debug",      Logger_debug,   1 },
+    { nullptr,      nullptr,        0 }
+};
+
+} // !namespace
+
+auto logger_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.Logger";
+}
+
+void logger_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, functions);
+    duk_put_prop_string(plugin->get_context(), -2, "Logger");
+    duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/logger_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,50 @@
+/*
+ * logger_js_api.hpp -- Irccd.Logger API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_LOGGER_JS_API_HPP
+#define IRCCD_JS_LOGGER_JS_API_HPP
+
+/**
+ * \file logger_js_api.hpp
+ * \brief Irccd.Logger Javascript API.
+ */
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief irccd.Logger Javascript API.
+ * \ingroup js_api
+ */
+class logger_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc Module::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_LOGGER_JS_API_HPP
--- a/libirccd-js/irccd/js/logger_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-/*
- * logger_jsapi.cpp -- Irccd.Logger API
- *
- * Copyright (c) 2013-2018 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 <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/plugin_service.hpp>
-
-#include "irccd_jsapi.hpp"
-#include "js_plugin.hpp"
-#include "logger_jsapi.hpp"
-#include "plugin_jsapi.hpp"
-
-namespace irccd {
-
-namespace {
-
-// {{{ print
-
-duk_ret_t print(duk_context* ctx, unsigned level)
-{
-    assert(level <= 2);
-
-    try {
-        auto& sink = dukx_type_traits<irccd>::self(ctx).get_log();
-        auto self = dukx_type_traits<js_plugin>::self(ctx);
-
-        switch (level) {
-        case 0:
-            sink.debug<plugin>(*self) << duk_require_string(ctx, 0) << std::endl;
-            break;
-        case 1:
-            sink.info<plugin>(*self) << duk_require_string(ctx, 0) << std::endl;
-            break;
-        default:
-            sink.warning<plugin>(*self) << duk_require_string(ctx, 0) << std::endl;
-            break;
-        }
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, ex);
-    }
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Logger.info
-
-/*
- * Function: Irccd.Logger.info(message)
- * --------------------------------------------------------
- *
- * Write a verbose message.
- *
- * Arguments:
- *   - message, the message.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t Logger_info(duk_context* ctx)
-{
-    return print(ctx, 1);
-}
-
-// }}}
-
-// {{{ Irccd.Logger.warning
-
-/*
- * Function: Irccd.Logger.warning(message)
- * --------------------------------------------------------
- *
- * Write a warning message.
- *
- * Arguments:
- *   - message, the warning.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t Logger_warning(duk_context* ctx)
-{
-    return print(ctx, 2);
-}
-
-// }}}
-
-// {{{ Irccd.Logger.debug
-
-/*
- * Function: Irccd.Logger.debug(message)
- * --------------------------------------------------------
- *
- * Write a debug message, only shown if irccd is compiled in debug.
- *
- * Arguments:
- *   - message, the message.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-duk_ret_t Logger_debug(duk_context* ctx)
-{
-    return print(ctx, 0);
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-    { "info",       Logger_info,    1 },
-    { "warning",    Logger_warning, 1 },
-    { "debug",      Logger_debug,   1 },
-    { nullptr,      nullptr,        0 }
-};
-
-} // !namespace
-
-std::string logger_jsapi::get_name() const
-{
-    return "Irccd.Logger";
-}
-
-void logger_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_get_global_string(plugin->get_context(), "Irccd");
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, functions);
-    duk_put_prop_string(plugin->get_context(), -2, "Logger");
-    duk_pop(plugin->get_context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/logger_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * logger_jsapi.hpp -- Irccd.Logger API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_LOGGER_JSAPI_HPP
-#define IRCCD_JS_LOGGER_JSAPI_HPP
-
-/**
- * \file logger_jsapi.hpp
- * \brief Irccd.Logger Javascript API.
- */
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief irccd.Logger Javascript API.
- * \ingroup jsapi
- */
-class logger_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc Module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_LOGGER_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/plugin_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,458 @@
+/*
+ * plugin_js_api.cpp -- Irccd.Plugin API
+ *
+ * Copyright (c) 2013-2018 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 <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+
+#include "irccd_js_api.hpp"
+#include "js_plugin.hpp"
+#include "plugin_js_api.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature("\xff""\xff""Irccd.Plugin");
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_idx_t
+{
+    try {
+        return handler();
+    } catch (const plugin_error& ex) {
+        duk::raise(ctx, ex);
+    } catch (const std::system_error& ex) {
+        duk::raise(ctx, ex);
+    } catch (const std::exception& ex) {
+        duk::raise(ctx, ex);
+    }
+
+    return 0;
+}
+
+/*
+ * set
+ * ------------------------------------------------------------------
+ *
+ * This setter is used to replace the Irccd.Plugin.(config|format|paths)
+ * property when the plugin assign a new one.
+ *
+ * Because the plugin configuration always has higher priority, when a new
+ * object is assigned to 'config' or to the 'format' property, the plugin
+ * configuration is merged to the assigned one, adding or replacing any values.
+ *
+ * Example:
+ *
+ * Plugin 'xyz' does:
+ *
+ * Irccd.Plugin.config = {
+ *      mode: "simple",
+ *      level: "123"
+ * };
+ *
+ * The user configuration is:
+ *
+ * [plugin.xyz]
+ * mode = "hard"
+ * path = "/var"
+ *
+ * The final user table looks like this:
+ *
+ * Irccd.Plugin.Config = {
+ *      mode: "hard",
+ *      level: "123",
+ *      path: "/var"
+ * };
+ */
+auto set(duk_context* ctx, std::string_view name) -> duk_ret_t
+{
+    if (!duk_is_object(ctx, 0))
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "'%s' property must be object", name.data());
+
+    // Merge old table with new one.
+    duk_get_global_string(ctx, name.data());
+    duk_enum(ctx, -1, 0);
+
+    while (duk_next(ctx, -1, true))
+        duk_put_prop(ctx, 0);
+
+    // Pop enum and old table.
+    duk_pop_2(ctx);
+
+    // Replace the old table with the new assigned one.
+    duk_put_global_string(ctx, name.data());
+
+    return 0;
+}
+
+/*
+ * get
+ * ------------------------------------------------------------------
+ *
+ * Get the Irccd.Plugin.(config|format|paths) property.
+ */
+auto get(duk_context* ctx, std::string_view name) -> duk_ret_t
+{
+    duk_get_global_string(ctx, name.data());
+
+    return 1;
+}
+
+/*
+ * set_config
+ * ------------------------------------------------------------------
+ *
+ * Wrap setter for Irccd.Plugin.config property.
+ */
+auto set_config(duk_context* ctx) -> duk_ret_t
+{
+    return set(ctx, js_plugin::config_property);
+}
+
+/*
+ * get_config
+ * ------------------------------------------------------------------
+ *
+ * Wrap getter for Irccd.Plugin.config property.
+ */
+auto get_config(duk_context* ctx) -> duk_ret_t
+{
+    return get(ctx, js_plugin::config_property);
+}
+
+/*
+ * set_format
+ * ------------------------------------------------------------------
+ *
+ * Wrap setter for Irccd.Plugin.format property.
+ */
+auto set_format(duk_context* ctx) -> duk_ret_t
+{
+    return set(ctx, js_plugin::format_property);
+}
+
+/*
+ * get_format
+ * ------------------------------------------------------------------
+ *
+ * Wrap getter for Irccd.Plugin.format property.
+ */
+auto get_format(duk_context* ctx) -> duk_ret_t
+{
+    return get(ctx, js_plugin::format_property);
+}
+
+/*
+ * set_paths
+ * ------------------------------------------------------------------
+ *
+ * Wrap setter for Irccd.Plugin.format property.
+ */
+auto set_paths(duk_context* ctx) -> duk_ret_t
+{
+    return set(ctx, js_plugin::paths_property);
+}
+
+/*
+ * get_paths
+ * ------------------------------------------------------------------
+ *
+ * Wrap getter for Irccd.Plugin.format property.
+ */
+auto get_paths(duk_context* ctx) -> duk_ret_t
+{
+    return get(ctx, js_plugin::paths_property);
+}
+
+// {{{ Irccd.Plugin.info
+
+/*
+ * Function: Irccd.Plugin.info([name])
+ * ------------------------------------------------------------------
+ *
+ * Get information about a plugin.
+ *
+ * The returned object as the following properties:
+ *
+ * - name: (string) the plugin identifier,
+ * - author: (string) the author,
+ * - license: (string) the license,
+ * - summary: (string) a short description,
+ * - version: (string) the version
+ *
+ * Arguments:
+ *   - name, the plugin identifier, if not specified the current plugin is
+ *     selected.
+ * Returns:
+ *   The plugin information or undefined if the plugin was not found.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Plugin_info(duk_context* ctx) -> duk_idx_t
+{
+    return wrap(ctx, [&] {
+        plugin* plugin;
+
+        if (duk_get_top(ctx) >= 1)
+            plugin = duk::type_traits<irccd>::self(ctx).plugins().get(duk_require_string(ctx, 0)).get();
+        else
+            plugin = std::addressof(duk::type_traits<js_plugin>::self(ctx));
+
+        if (!plugin)
+            return 0;
+
+        duk_push_object(ctx);
+        duk::push(ctx, plugin->get_name());
+        duk_put_prop_string(ctx, -2, "name");
+        duk::push(ctx, plugin->get_author());
+        duk_put_prop_string(ctx, -2, "author");
+        duk::push(ctx, plugin->get_license());
+        duk_put_prop_string(ctx, -2, "license");
+        duk::push(ctx, plugin->get_summary());
+        duk_put_prop_string(ctx, -2, "summary");
+        duk::push(ctx, plugin->get_version());
+        duk_put_prop_string(ctx, -2, "version");
+
+        return 1;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Plugin.list
+
+/*
+ * Function: Irccd.Plugin.list()
+ * ------------------------------------------------------------------
+ *
+ * Get the list of plugins, the array returned contains all plugin names.
+ *
+ * Returns:
+ *   The list of all plugin names.
+ */
+auto Plugin_list(duk_context* ctx) -> duk_idx_t
+{
+    int i = 0;
+
+    duk_push_array(ctx);
+
+    for (const auto& plg : duk::type_traits<irccd>::self(ctx).plugins().all()) {
+        duk::push(ctx, plg->get_id());
+        duk_put_prop_index(ctx, -2, i++);
+    }
+
+    return 1;
+}
+
+// }}}
+
+// {{{ Irccd.Plugin.load
+
+/*
+ * Function: Irccd.Plugin.load(name)
+ * ------------------------------------------------------------------
+ *
+ * Load a plugin by name. This function will search through the standard
+ * directories.
+ *
+ * Arguments:
+ *   - name, the plugin identifier.
+ * Throws:
+ *   - Irccd.PluginError on plugin related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Plugin_load(duk_context* ctx) -> duk_idx_t
+{
+    return wrap(ctx, [&] {
+        duk::type_traits<irccd>::self(ctx).plugins().load(
+            duk::require<std::string_view>(ctx, 0), "");
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Plugin.reload
+
+/*
+ * Function: Irccd.Plugin.reload(name)
+ * ------------------------------------------------------------------
+ *
+ * Reload a plugin by name.
+ *
+ * Arguments:
+ *   - name, the plugin identifier.
+ * Throws:
+ *   - Irccd.PluginError on plugin related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Plugin_reload(duk_context* ctx) -> duk_idx_t
+{
+    return wrap(ctx, [&] {
+        duk::type_traits<irccd>::self(ctx).plugins().reload(duk::require<std::string>(ctx, 0));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Plugin.unload
+
+/*
+ * Function: Irccd.Plugin.unload(name)
+ * ------------------------------------------------------------------
+ *
+ * Unload a plugin by name.
+ *
+ * Arguments:
+ *   - name, the plugin identifier.
+ * Throws:
+ *   - Irccd.PluginError on plugin related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Plugin_unload(duk_context* ctx) -> duk_idx_t
+{
+    return wrap(ctx, [&] {
+        duk::type_traits<irccd>::self(ctx).plugins().unload(duk::require<std::string>(ctx, 0));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.PluginError [constructor]
+
+/*
+ * Function: Irccd.PluginError(code, message)
+ * ------------------------------------------------------------------
+ *
+ * Create an Irccd.PluginError object.
+ *
+ * Arguments:
+ *   - code, the error code,
+ *   - message, the error message.
+ */
+auto PluginError_constructor(duk_context* ctx) -> duk_ret_t
+{
+    duk_push_this(ctx);
+    duk_push_int(ctx, duk_require_int(ctx, 0));
+    duk_put_prop_string(ctx, -2, "code");
+    duk_push_string(ctx, duk_require_string(ctx, 1));
+    duk_put_prop_string(ctx, -2, "message");
+    duk_push_string(ctx, "PluginError");
+    duk_put_prop_string(ctx, -2, "name");
+    duk_pop(ctx);
+
+    return 0;
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+    { "info",   Plugin_info,    DUK_VARARGS },
+    { "list",   Plugin_list,    0           },
+    { "load",   Plugin_load,    1           },
+    { "reload", Plugin_reload,  1           },
+    { "unload", Plugin_unload,  1           },
+    { nullptr,  nullptr,        0           }
+};
+
+} // !namespace
+
+auto plugin_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.Plugin";
+}
+
+void plugin_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    // Store plugin.
+    duk_push_pointer(plugin->get_context(), plugin.get());
+    duk_put_global_string(plugin->get_context(), signature.data());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, functions);
+
+    // 'config' property.
+    duk_push_string(plugin->get_context(), "config");
+    duk_push_c_function(plugin->get_context(), get_config, 0);
+    duk_push_c_function(plugin->get_context(), set_config, 1);
+    duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
+    // 'format' property.
+    duk_push_string(plugin->get_context(), "format");
+    duk_push_c_function(plugin->get_context(), get_format, 0);
+    duk_push_c_function(plugin->get_context(), set_format, 1);
+    duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
+    // 'format' property.
+    duk_push_string(plugin->get_context(), "paths");
+    duk_push_c_function(plugin->get_context(), get_paths, 0);
+    duk_push_c_function(plugin->get_context(), set_paths, 1);
+    duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
+    // PluginError function.
+    duk_push_c_function(plugin->get_context(), PluginError_constructor, 2);
+    duk_push_object(plugin->get_context());
+    duk_get_global_string(plugin->get_context(), "Error");
+    duk_get_prop_string(plugin->get_context(), -1, "prototype");
+    duk_remove(plugin->get_context(), -2);
+    duk_set_prototype(plugin->get_context(), -2);
+    duk_put_prop_string(plugin->get_context(), -2, "prototype");
+    duk_put_prop_string(plugin->get_context(), -2, "PluginError");
+
+    duk_put_prop_string(plugin->get_context(), -2, "Plugin");
+    duk_pop(plugin->get_context());
+}
+
+namespace duk {
+
+auto type_traits<js_plugin>::self(duk_context* ctx) -> js_plugin&
+{
+    duk::stack_guard sa(ctx);
+
+    duk_get_global_string(ctx, signature.data());
+    auto plugin = static_cast<js_plugin*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+
+    return *plugin;
+}
+
+void type_traits<plugin_error>::raise(duk_context* ctx, const plugin_error& ex)
+{
+    duk::stack_guard sa(ctx, 1);
+
+    duk_get_global_string(ctx, "Irccd");
+    duk_get_prop_string(ctx, -1, "PluginError");
+    duk_remove(ctx, -2);
+    duk::push(ctx, ex.code().value());
+    duk::push(ctx, ex.code().message());
+    duk_new(ctx, 2);
+
+    (void)duk_throw(ctx);
+}
+
+} // !duk
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/plugin_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,83 @@
+/*
+ * plugin_js_api.hpp -- Irccd.Plugin API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_PLUGIN_JS_API_HPP
+#define IRCCD_JS_PLUGIN_JS_API_HPP
+
+/**
+ * \file plugin_js_api.hpp
+ * \brief Irccd.Plugin Javascript API.
+ */
+
+#include "js_api.hpp"
+#include "js_plugin.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Irccd.Plugin Javascript API.
+ * \ingroup js_api
+ */
+class plugin_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc Module::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+namespace duk {
+
+/**
+ * \brief Specialize dukx_type_traits for plugin.
+ */
+template <>
+struct type_traits<js_plugin> {
+    /**
+     * Access the plugin stored in this context.
+     *
+     * \param ctx the context
+     * \return the plugin
+     */
+    static auto self(duk_context* ctx) -> js_plugin&;
+};
+
+/**
+ * \brief Specialization for plugin_error.
+ */
+template <>
+struct type_traits<plugin_error> {
+    /**
+     * Raise a plugin_error.
+     *
+     * \param ctx the context
+     * \param error the error
+     */
+    static void raise(duk_context* ctx, const plugin_error& error);
+};
+
+} // !duk
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_PLUGIN_JS_API_HPP
--- a/libirccd-js/irccd/js/plugin_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,466 +0,0 @@
-/*
- * plugin_jsapi.cpp -- Irccd.Plugin API
- *
- * Copyright (c) 2013-2018 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 <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/plugin_service.hpp>
-
-#include "irccd_jsapi.hpp"
-#include "js_plugin.hpp"
-#include "plugin_jsapi.hpp"
-
-namespace irccd {
-
-namespace {
-
-const char plugin_ref[] = "\xff""\xff""irccd-plugin-ptr";
-
-template <typename Handler>
-duk_idx_t wrap(duk_context* ctx, Handler handler)
-{
-    try {
-        return handler();
-    } catch (const plugin_error& ex) {
-        dukx_throw(ctx, ex);
-    } catch (const std::system_error& ex) {
-        dukx_throw(ctx, ex);
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, ex);
-    }
-
-    return 0;
-}
-
-/*
- * set
- * ------------------------------------------------------------------
- *
- * This setter is used to replace the Irccd.Plugin.(config|format|paths)
- * property when the plugin assign a new one.
- *
- * Because the plugin configuration always has higher priority, when a new
- * object is assigned to 'config' or to the 'format' property, the plugin
- * configuration is merged to the assigned one, adding or replacing any values.
- *
- * Example:
- *
- * Plugin 'xyz' does:
- *
- * Irccd.Plugin.config = {
- *      mode: "simple",
- *      level: "123"
- * };
- *
- * The user configuration is:
- *
- * [plugin.xyz]
- * mode = "hard"
- * path = "/var"
- *
- * The final user table looks like this:
- *
- * Irccd.Plugin.Config = {
- *      mode: "hard",
- *      level: "123",
- *      path: "/var"
- * };
- */
-duk_ret_t set(duk_context* ctx, std::string_view name)
-{
-    if (!duk_is_object(ctx, 0))
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "'%s' property must be object", name.data());
-
-    // Merge old table with new one.
-    duk_get_global_string(ctx, name.data());
-    duk_enum(ctx, -1, 0);
-
-    while (duk_next(ctx, -1, true))
-        duk_put_prop(ctx, 0);
-
-    // Pop enum and old table.
-    duk_pop_2(ctx);
-
-    // Replace the old table with the new assigned one.
-    duk_put_global_string(ctx, name.data());
-
-    return 0;
-}
-
-/*
- * get
- * ------------------------------------------------------------------
- *
- * Get the Irccd.Plugin.(config|format|paths) property.
- */
-duk_ret_t get(duk_context* ctx, std::string_view name)
-{
-    duk_get_global_string(ctx, name.data());
-
-    return 1;
-}
-
-/*
- * set_config
- * ------------------------------------------------------------------
- *
- * Wrap setter for Irccd.Plugin.config property.
- */
-duk_ret_t set_config(duk_context* ctx)
-{
-    return set(ctx, js_plugin::config_property);
-}
-
-/*
- * get_config
- * ------------------------------------------------------------------
- *
- * Wrap getter for Irccd.Plugin.config property.
- */
-duk_ret_t get_config(duk_context* ctx)
-{
-    return get(ctx, js_plugin::config_property);
-}
-
-/*
- * set_format
- * ------------------------------------------------------------------
- *
- * Wrap setter for Irccd.Plugin.format property.
- */
-duk_ret_t set_format(duk_context* ctx)
-{
-    return set(ctx, js_plugin::format_property);
-}
-
-/*
- * get_format
- * ------------------------------------------------------------------
- *
- * Wrap getter for Irccd.Plugin.format property.
- */
-duk_ret_t get_format(duk_context* ctx)
-{
-    return get(ctx, js_plugin::format_property);
-}
-
-/*
- * set_paths
- * ------------------------------------------------------------------
- *
- * Wrap setter for Irccd.Plugin.format property.
- */
-duk_ret_t set_paths(duk_context* ctx)
-{
-    return set(ctx, js_plugin::paths_property);
-}
-
-/*
- * get_paths
- * ------------------------------------------------------------------
- *
- * Wrap getter for Irccd.Plugin.format property.
- */
-duk_ret_t get_paths(duk_context* ctx)
-{
-    return get(ctx, js_plugin::paths_property);
-}
-
-// {{{ Irccd.Plugin.info
-
-/*
- * Function: Irccd.Plugin.info([name])
- * ------------------------------------------------------------------
- *
- * Get information about a plugin.
- *
- * The returned object as the following properties:
- *
- * - name: (string) the plugin identifier,
- * - author: (string) the author,
- * - license: (string) the license,
- * - summary: (string) a short description,
- * - version: (string) the version
- *
- * Arguments:
- *   - name, the plugin identifier, if not specified the current plugin is
- *     selected.
- * Returns:
- *   The plugin information or undefined if the plugin was not found.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_idx_t Plugin_info(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        std::shared_ptr<plugin> plugin;
-
-        if (duk_get_top(ctx) >= 1)
-            plugin = dukx_type_traits<irccd>::self(ctx).plugins().get(duk_require_string(ctx, 0));
-        else
-            plugin = dukx_type_traits<js_plugin>::self(ctx);
-
-        if (!plugin)
-            return 0;
-
-        duk_push_object(ctx);
-        dukx_push(ctx, plugin->get_name());
-        duk_put_prop_string(ctx, -2, "name");
-        dukx_push(ctx, plugin->get_author());
-        duk_put_prop_string(ctx, -2, "author");
-        dukx_push(ctx, plugin->get_license());
-        duk_put_prop_string(ctx, -2, "license");
-        dukx_push(ctx, plugin->get_summary());
-        duk_put_prop_string(ctx, -2, "summary");
-        dukx_push(ctx, plugin->get_version());
-        duk_put_prop_string(ctx, -2, "version");
-
-        return 1;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Plugin.list
-
-/*
- * Function: Irccd.Plugin.list()
- * ------------------------------------------------------------------
- *
- * Get the list of plugins, the array returned contains all plugin names.
- *
- * Returns:
- *   The list of all plugin names.
- */
-duk_idx_t Plugin_list(duk_context* ctx)
-{
-    int i = 0;
-
-    duk_push_array(ctx);
-
-    for (const auto& plg : dukx_type_traits<irccd>::self(ctx).plugins().all()) {
-        dukx_push(ctx, plg->get_id());
-        duk_put_prop_index(ctx, -2, i++);
-    }
-
-    return 1;
-}
-
-// }}}
-
-// {{{ Irccd.Plugin.load
-
-/*
- * Function: Irccd.Plugin.load(name)
- * ------------------------------------------------------------------
- *
- * Load a plugin by name. This function will search through the standard
- * directories.
- *
- * Arguments:
- *   - name, the plugin identifier.
- * Throws:
- *   - Irccd.PluginError on plugin related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_idx_t Plugin_load(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        dukx_type_traits<irccd>::self(ctx).plugins().load(
-            dukx_require<std::string_view>(ctx, 0), "");
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Plugin.reload
-
-/*
- * Function: Irccd.Plugin.reload(name)
- * ------------------------------------------------------------------
- *
- * Reload a plugin by name.
- *
- * Arguments:
- *   - name, the plugin identifier.
- * Throws:
- *   - Irccd.PluginError on plugin related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_idx_t Plugin_reload(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        dukx_type_traits<irccd>::self(ctx).plugins().reload(dukx_require<std::string>(ctx, 0));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Plugin.unload
-
-/*
- * Function: Irccd.Plugin.unload(name)
- * ------------------------------------------------------------------
- *
- * Unload a plugin by name.
- *
- * Arguments:
- *   - name, the plugin identifier.
- * Throws:
- *   - Irccd.PluginError on plugin related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_idx_t Plugin_unload(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        dukx_type_traits<irccd>::self(ctx).plugins().unload(dukx_require<std::string>(ctx, 0));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.PluginError [constructor]
-
-/*
- * Function: Irccd.PluginError(code, message)
- * ------------------------------------------------------------------
- *
- * Create an Irccd.PluginError object.
- *
- * Arguments:
- *   - code, the error code,
- *   - message, the error message.
- */
-duk_ret_t PluginError_constructor(duk_context* ctx)
-{
-    duk_push_this(ctx);
-    duk_push_int(ctx, duk_require_int(ctx, 0));
-    duk_put_prop_string(ctx, -2, "code");
-    duk_push_string(ctx, duk_require_string(ctx, 1));
-    duk_put_prop_string(ctx, -2, "message");
-    duk_push_string(ctx, "PluginError");
-    duk_put_prop_string(ctx, -2, "name");
-    duk_pop(ctx);
-
-    return 0;
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-    { "info",   Plugin_info,    DUK_VARARGS },
-    { "list",   Plugin_list,    0           },
-    { "load",   Plugin_load,    1           },
-    { "reload", Plugin_reload,  1           },
-    { "unload", Plugin_unload,  1           },
-    { nullptr,  nullptr,        0           }
-};
-
-} // !namespace
-
-std::string plugin_jsapi::get_name() const
-{
-    return "Irccd.Plugin";
-}
-
-void plugin_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_push_pointer(plugin->get_context(), new std::weak_ptr<js_plugin>(plugin));
-    duk_push_object(plugin->get_context());
-    duk_push_c_function(plugin->get_context(), [] (auto ctx) -> duk_ret_t {
-        duk_get_global_string(ctx, plugin_ref);
-        delete static_cast<std::shared_ptr<js_plugin>*>(duk_to_pointer(ctx, -1));
-        duk_pop(ctx);
-        duk_push_null(ctx);
-        duk_put_global_string(ctx, plugin_ref);
-        return 0;
-    }, 1);
-    duk_set_finalizer(plugin->get_context(), -2);
-    duk_put_global_string(plugin->get_context(), "\xff""\xff""dummy-shared-ptr");
-    duk_put_global_string(plugin->get_context(), plugin_ref);
-    duk_get_global_string(plugin->get_context(), "Irccd");
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, functions);
-
-    // 'config' property.
-    duk_push_string(plugin->get_context(), "config");
-    duk_push_c_function(plugin->get_context(), get_config, 0);
-    duk_push_c_function(plugin->get_context(), set_config, 1);
-    duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
-
-    // 'format' property.
-    duk_push_string(plugin->get_context(), "format");
-    duk_push_c_function(plugin->get_context(), get_format, 0);
-    duk_push_c_function(plugin->get_context(), set_format, 1);
-    duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
-
-    // 'format' property.
-    duk_push_string(plugin->get_context(), "paths");
-    duk_push_c_function(plugin->get_context(), get_paths, 0);
-    duk_push_c_function(plugin->get_context(), set_paths, 1);
-    duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
-
-    // PluginError function.
-    duk_push_c_function(plugin->get_context(), PluginError_constructor, 2);
-    duk_push_object(plugin->get_context());
-    duk_get_global_string(plugin->get_context(), "Error");
-    duk_get_prop_string(plugin->get_context(), -1, "prototype");
-    duk_remove(plugin->get_context(), -2);
-    duk_set_prototype(plugin->get_context(), -2);
-    duk_put_prop_string(plugin->get_context(), -2, "prototype");
-    duk_put_prop_string(plugin->get_context(), -2, "PluginError");
-
-    duk_put_prop_string(plugin->get_context(), -2, "Plugin");
-    duk_pop(plugin->get_context());
-}
-
-using plugin_traits = dukx_type_traits<js_plugin>;
-using plugin_error_traits = dukx_type_traits<plugin_error>;
-
-std::shared_ptr<js_plugin> dukx_type_traits<js_plugin>::self(duk_context* ctx)
-{
-    dukx_stack_assert sa(ctx);
-
-    duk_get_global_string(ctx, plugin_ref);
-    auto plugin = static_cast<std::weak_ptr<js_plugin>*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-
-    return plugin->lock();
-}
-
-void plugin_error_traits::raise(duk_context* ctx, const plugin_error& ex)
-{
-    dukx_stack_assert sa(ctx, 1);
-
-    duk_get_global_string(ctx, "Irccd");
-    duk_get_prop_string(ctx, -1, "PluginError");
-    duk_remove(ctx, -2);
-    dukx_push(ctx, ex.code().value());
-    dukx_push(ctx, ex.code().message());
-    duk_new(ctx, 2);
-
-    (void)duk_throw(ctx);
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/plugin_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/*
- * plugin_jsapi.hpp -- Irccd.Plugin API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_PLUGIN_JSAPI_HPP
-#define IRCCD_JS_PLUGIN_JSAPI_HPP
-
-/**
- * \file plugin_jsapi.hpp
- * \brief Irccd.Plugin Javascript API.
- */
-
-#include "jsapi.hpp"
-#include "js_plugin.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.Plugin Javascript API.
- * \ingroup jsapi
- */
-class plugin_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc Module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-/**
- * \brief Specialize dukx_type_traits for plugin.
- */
-template <>
-class dukx_type_traits<js_plugin> : public std::true_type {
-public:
-    /**
-     * Access the plugin stored in this context.
-     *
-     * \param ctx the context
-     * \return the plugin
-     */
-    static std::shared_ptr<js_plugin> self(duk_context* ctx);
-};
-
-/**
- * \brief Specialization for plugin_error.
- */
-template <>
-class dukx_type_traits<plugin_error> : public std::true_type {
-public:
-    /**
-     * Raise a plugin_error.
-     *
-     * \param ctx the context
-     * \param error the error
-     */
-    static void raise(duk_context* ctx, const plugin_error& error);
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_PLUGIN_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/server_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,898 @@
+/*
+ * server_js_api.cpp -- Irccd.Server API
+ *
+ * Copyright (c) 2013-2018 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 <cassert>
+#include <sstream>
+#include <unordered_map>
+
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_util.hpp>
+
+#include "irccd_js_api.hpp"
+#include "js_plugin.hpp"
+#include "server_js_api.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature("\xff""\xff""Irccd.Server");
+const std::string_view prototype("\xff""\xff""Irccd.Server.prototype");
+
+auto self(duk_context* ctx) -> std::shared_ptr<server>
+{
+    duk::stack_guard sa(ctx);
+
+    duk_push_this(ctx);
+    duk_get_prop_string(ctx, -1, signature.data());
+    auto ptr = duk_to_pointer(ctx, -1);
+    duk_pop_2(ctx);
+
+    if (!ptr)
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object");
+
+    return *static_cast<std::shared_ptr<server>*>(ptr);
+}
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+    try {
+        return handler(ctx);
+    } catch (const server_error& ex) {
+        duk::raise(ctx, ex);
+    } catch (const std::exception& ex) {
+        duk::raise(ctx, ex);
+    }
+
+    return 0;
+}
+
+// {{{ Irccd.Server.prototype.info
+
+/*
+ * Method: Irccd.Server.prototype.info()
+ * ------------------------------------------------------------------
+ *
+ * Get the server information as an object containing the following properties:
+ *
+ * name: the server unique name
+ * host: the host name
+ * port: the port number
+ * ssl: true if using ssl
+ * sslVerify: true if ssl was verified
+ * channels: an array of all channels
+ */
+auto Server_prototype_info(duk_context* ctx) -> duk_ret_t
+{
+    const auto server = self(ctx);
+    const auto& channels = server->get_channels();
+
+    duk_push_object(ctx);
+    duk::push(ctx, server->get_id());
+    duk_put_prop_string(ctx, -2, "name");
+    duk::push(ctx, server->get_host());
+    duk_put_prop_string(ctx, -2, "host");
+    duk_push_int(ctx, server->get_port());
+    duk_put_prop_string(ctx, -2, "port");
+    duk_push_boolean(ctx,
+        (server->get_options() & server::options::ssl) == server::options::ssl);
+    duk_put_prop_string(ctx, -2, "ssl");
+    duk_push_boolean(ctx,
+        (server->get_options() & server::options::ssl_verify) == server::options::ssl_verify);
+    duk_put_prop_string(ctx, -2, "sslVerify");
+    duk::push(ctx, server->get_command_char());
+    duk_put_prop_string(ctx, -2, "commandChar");
+    duk::push(ctx, server->get_realname());
+    duk_put_prop_string(ctx, -2, "realname");
+    duk::push(ctx, server->get_nickname());
+    duk_put_prop_string(ctx, -2, "nickname");
+    duk::push(ctx, server->get_username());
+    duk_put_prop_string(ctx, -2, "username");
+    duk::push(ctx, std::vector<std::string>(channels.begin(), channels.end()));
+    duk_put_prop_string(ctx, -2, "channels");
+
+    return 1;
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.invite
+
+/*
+ * Method: Irccd.Server.prototype.invite(target, channel)
+ * ------------------------------------------------------------------
+ *
+ * Invite someone to a channel.
+ *
+ * Arguments:
+ *   - target, the target to invite,
+ *   - channel, the channel.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_invite(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto target = duk::require<std::string>(ctx, 0);
+        auto channel = duk::require<std::string>(ctx, 1);
+
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->invite(std::move(target), std::move(channel));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.isSelf
+
+/*
+ * Method: Irccd.Server.prototype.isSelf(nickname)
+ * ------------------------------------------------------------------
+ *
+ * Arguments:
+ *   - nickname, the nickname to check.
+ * Returns:
+ *   True if the nickname targets this server.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Server_prototype_isSelf(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        return duk::push(ctx, self(ctx)->is_self(duk::require<std::string>(ctx, 0)));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.join
+
+/*
+ * Method: Irccd.Server.prototype.join(channel, password = undefined)
+ * ------------------------------------------------------------------
+ *
+ * Join a channel with an optional password.
+ *
+ * Arguments:
+ *   - channel, the channel to join,
+ *   - password, the password or undefined to not use.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_join(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = duk::require<std::string>(ctx, 0);
+        auto password = duk::get<std::string>(ctx, 1);
+
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->join(std::move(channel), std::move(password));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.kick
+
+/*
+ * Method: Irccd.Server.prototype.kick(target, channel, reason = undefined)
+ * ------------------------------------------------------------------
+ *
+ * Kick someone from a channel.
+ *
+ * Arguments:
+ *   - target, the target to kick,
+ *   - channel, the channel,
+ *   - reason, the optional reason or undefined to not set.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_kick(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto target = duk::require<std::string>(ctx, 0);
+        auto channel = duk::require<std::string>(ctx, 1);
+        auto reason = duk::get<std::string>(ctx, 2);
+
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->kick(std::move(target), std::move(channel), std::move(reason));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.me
+
+/*
+ * Method: Irccd.Server.prototype.me(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a CTCP Action.
+ *
+ * Arguments:
+ *   - target, the target or a channel,
+ *   - message, the message.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_me(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto target = duk::require<std::string>(ctx, 0);
+        auto message = duk::get<std::string>(ctx, 1);
+
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->me(std::move(target), std::move(message));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.message
+
+/*
+ * Method: Irccd.Server.prototype.message(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a message.
+ *
+ * Arguments:
+ *   - target, the target or a channel,
+ *   - message, the message.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_message(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto target = duk::require<std::string>(ctx, 0);
+        auto message = duk::get<std::string>(ctx, 1);
+
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->message(std::move(target), std::move(message));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.mode
+
+/*
+ * Method: Irccd.Server.prototype.mode(channel, mode, limit, user, mask)
+ * ------------------------------------------------------------------
+ *
+ * Change your mode.
+ *
+ * Arguments:
+ *   - mode, the new mode.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_mode(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = duk::require<std::string>(ctx, 0);
+        auto mode = duk::require<std::string>(ctx, 1);
+        auto limit = duk::get<std::string>(ctx, 2);
+        auto user = duk::get<std::string>(ctx, 3);
+        auto mask = duk::get<std::string>(ctx, 4);
+
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+        if (mode.empty())
+            throw server_error(server_error::invalid_mode);
+
+        self(ctx)->mode(
+            std::move(channel),
+            std::move(mode),
+            std::move(limit),
+            std::move(user),
+            std::move(mask)
+        );
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.names
+
+/*
+ * Method: Irccd.Server.prototype.names(channel)
+ * ------------------------------------------------------------------
+ *
+ * Get the list of names from a channel.
+ *
+ * Arguments:
+ *   - channel, the channel.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_names(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = duk::require<std::string>(ctx, 0);
+
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->names(std::move(channel));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.nick
+
+/*
+ * Method: Irccd.Server.prototype.nick(nickname)
+ * ------------------------------------------------------------------
+ *
+ * Change the nickname.
+ *
+ * Arguments:
+ *   - nickname, the nickname.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_nick(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto nickname = duk::require<std::string>(ctx, 0);
+
+        if (nickname.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->set_nickname(std::move(nickname));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.notice
+
+/*
+ * Method: Irccd.Server.prototype.notice(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a private notice.
+ *
+ * Arguments:
+ *   - target, the target,
+ *   - message, the notice message.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_notice(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto target = duk::require<std::string>(ctx, 0);
+        auto message = duk::get<std::string>(ctx, 1);
+
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->notice(std::move(target), std::move(message));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.part
+
+/*
+ * Method: Irccd.Server.prototype.part(channel, reason = undefined)
+ * ------------------------------------------------------------------
+ *
+ * Leave a channel.
+ *
+ * Arguments:
+ *   - channel, the channel to leave,
+ *   - reason, the optional reason, keep undefined for portability.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_part(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = duk::require<std::string>(ctx, 0);
+        auto reason = duk::get<std::string>(ctx, 1);
+
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->part(std::move(channel), std::move(reason));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.send
+
+/*
+ * Method: Irccd.Server.prototype.send(raw)
+ * ------------------------------------------------------------------
+ *
+ * Send a raw message to the IRC server.
+ *
+ * Arguments:
+ *   - raw, the raw message (without terminators).
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_send(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto raw = duk::require<std::string>(ctx, 0);
+
+        if (raw.empty())
+            throw server_error(server_error::invalid_message);
+
+        self(ctx)->send(std::move(raw));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.topic
+
+/*
+ * Method: Server.prototype.topic(channel, topic)
+ * ------------------------------------------------------------------
+ *
+ * Change a channel topic.
+ *
+ * Arguments:
+ *   - channel, the channel,
+ *   - topic, the new topic.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_topic(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto channel = duk::require<std::string>(ctx, 0);
+        auto topic = duk::get<std::string>(ctx, 1);
+
+        if (channel.empty())
+            throw server_error(server_error::invalid_channel);
+
+        self(ctx)->topic(std::move(channel), std::move(topic));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.whois
+
+/*
+ * Method: Irccd.Server.prototype.whois(target)
+ * ------------------------------------------------------------------
+ *
+ * Get whois information.
+ *
+ * Arguments:
+ *   - target, the target.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_whois(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto target = duk::require<std::string>(ctx, 0);
+
+        if (target.empty())
+            throw server_error(server_error::invalid_nickname);
+
+        self(ctx)->whois(std::move(target));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.toString
+
+/*
+ * Method: Irccd.Server.prototype.toString()
+ * ------------------------------------------------------------------
+ *
+ * Convert the object to std::string, convenience for adding the object
+ * as property key.
+ *
+ * Returns:
+ *   The server name (unique).
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Server_prototype_toString(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        duk::push(ctx, self(ctx)->get_id());
+
+        return 1;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server [constructor]
+
+/*
+ * Function: Irccd.Server(params) [constructor]
+ * ------------------------------------------------------------------
+ *
+ * Construct a new server.
+ *
+ * Params must be filled with the following properties:
+ *
+ * name: the name,
+ * host: the host,
+ * ipv6: true to use ipv6,      (Optional: default false)
+ * port: the port number,       (Optional: default 6667)
+ * password: the password,      (Optional: default none)
+ * channels: array of channels  (Optiona: default empty)
+ * ssl: true to use ssl,        (Optional: default false)
+ * sslVerify: true to verify    (Optional: default true)
+ * nickname: "nickname",        (Optional, default: irccd)
+ * username: "user name",       (Optional, default: irccd)
+ * realname: "real name",       (Optional, default: IRC Client Daemon)
+ * commandChar: "!",            (Optional, the command char, default: "!")
+ *
+ * Arguments:
+ *   - params, the server properties
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_constructor(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        if (!duk_is_constructor_call(ctx))
+            return 0;
+
+        duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
+
+        auto json = nlohmann::json::parse(duk_json_encode(ctx, 0));
+        auto s = server_util::from_json(duk::type_traits<irccd>::self(ctx).get_service(), json);
+
+        duk_push_this(ctx);
+        duk_push_pointer(ctx, new std::shared_ptr<server>(std::move(s)));
+        duk_put_prop_string(ctx, -2, signature.data());
+        duk_pop(ctx);
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server [destructor]
+
+/*
+ * Function: Irccd.Server() [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Delete the property.
+ */
+auto Server_destructor(duk_context* ctx) -> duk_ret_t
+{
+    duk_get_prop_string(ctx, 0, signature.data());
+    delete static_cast<std::shared_ptr<server>*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+    duk_del_prop_string(ctx, 0, signature.data());
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Server.add
+
+/*
+ * Function: Irccd.Server.add(s)
+ * ------------------------------------------------------------------
+ *
+ * Register a new server to the irccd instance.
+ *
+ * Arguments:
+ *   - s, the server to add.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Server_add(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        duk::type_traits<irccd>::self(ctx).servers().add(
+            duk::require<std::shared_ptr<server>>(ctx, 0));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.find
+
+/*
+ * Function: Irccd.Server.find(name)
+ * ------------------------------------------------------------------
+ *
+ * Find a server by name.
+ *
+ * Arguments:
+ *   - name, the server name
+ * Returns:
+ *   The server object or undefined if not found.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Server_find(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [] (auto ctx) {
+        auto id = duk::require<std::string>(ctx, 0);
+        auto server = duk::type_traits<irccd>::self(ctx).servers().get(id);
+
+        if (!server)
+            return 0;
+
+        duk::push(ctx, server);
+
+        return 1;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Server.list
+
+/*
+ * Function: Irccd.Server.list()
+ * ------------------------------------------------------------------
+ *
+ * Get the map of all loaded servers.
+ *
+ * Returns:
+ *   An object with string-to-servers pairs.
+ */
+auto Server_list(duk_context* ctx) -> duk_ret_t
+{
+    duk_push_object(ctx);
+
+    for (const auto& server : duk::type_traits<irccd>::self(ctx).servers().all()) {
+        duk::push(ctx, server);
+        duk_put_prop_string(ctx, -2, server->get_id().c_str());
+    }
+
+    return 1;
+}
+
+// }}}
+
+// {{{ Irccd.Server.remove
+
+/*
+ * Function: Irccd.Server.remove(name)
+ * ------------------------------------------------------------------
+ *
+ * Remove a server from the irccd instance. You can pass the server object since
+ * it's coercible to a string.
+ *
+ * Arguments:
+ *   - name the server name.
+ */
+auto Server_remove(duk_context* ctx) -> duk_ret_t
+{
+    duk::type_traits<irccd>::self(ctx).servers().remove(duk_require_string(ctx, 0));
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.ServerError
+
+/*
+ * Function: Irccd.ServerError(code, message)
+ * ------------------------------------------------------------------
+ *
+ * Create an Irccd.ServerError object.
+ *
+ * Arguments:
+ *   - code, the error code,
+ *   - message, the error message.
+ */
+auto ServerError_constructor(duk_context* ctx) -> duk_ret_t
+{
+    duk_push_this(ctx);
+    duk_push_int(ctx, duk_require_int(ctx, 0));
+    duk_put_prop_string(ctx, -2, "code");
+    duk_push_string(ctx, duk_require_string(ctx, 1));
+    duk_put_prop_string(ctx, -2, "message");
+    duk_push_string(ctx, "ServerError");
+    duk_put_prop_string(ctx, -2, "name");
+    duk_pop(ctx);
+
+    return 0;
+}
+
+// }}}
+
+const duk_function_list_entry methods[] = {
+    { "info",       Server_prototype_info,      0           },
+    { "invite",     Server_prototype_invite,    2           },
+    { "isSelf",     Server_prototype_isSelf,    1           },
+    { "join",       Server_prototype_join,      DUK_VARARGS },
+    { "kick",       Server_prototype_kick,      DUK_VARARGS },
+    { "me",         Server_prototype_me,        2           },
+    { "message",    Server_prototype_message,   2           },
+    { "mode",       Server_prototype_mode,      1           },
+    { "names",      Server_prototype_names,     1           },
+    { "nick",       Server_prototype_nick,      1           },
+    { "notice",     Server_prototype_notice,    2           },
+    { "part",       Server_prototype_part,      DUK_VARARGS },
+    { "send",       Server_prototype_send,      1           },
+    { "topic",      Server_prototype_topic,     2           },
+    { "toString",   Server_prototype_toString,  0           },
+    { "whois",      Server_prototype_whois,     1           },
+    { nullptr,      nullptr,                    0           }
+};
+
+const duk_function_list_entry functions[] = {
+    { "add",        Server_add,                 1           },
+    { "find",       Server_find,                1           },
+    { "list",       Server_list,                0           },
+    { "remove",     Server_remove,              1           },
+    { nullptr,      nullptr,                    0           }
+};
+
+} // !namespace
+
+auto server_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.Server";
+}
+
+void server_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+
+    // ServerError function.
+    duk_push_c_function(plugin->get_context(), ServerError_constructor, 2);
+    duk_push_object(plugin->get_context());
+    duk_get_global_string(plugin->get_context(), "Error");
+    duk_get_prop_string(plugin->get_context(), -1, "prototype");
+    duk_remove(plugin->get_context(), -2);
+    duk_set_prototype(plugin->get_context(), -2);
+    duk_put_prop_string(plugin->get_context(), -2, "prototype");
+    duk_put_prop_string(plugin->get_context(), -2, "ServerError");
+
+    // Server constructor.
+    duk_push_c_function(plugin->get_context(), Server_constructor, 1);
+    duk_put_function_list(plugin->get_context(), -1, functions);
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, methods);
+    duk_push_c_function(plugin->get_context(), Server_destructor, 1);
+    duk_set_finalizer(plugin->get_context(), -2);
+    duk_dup_top(plugin->get_context());
+    duk_put_global_string(plugin->get_context(), prototype.data());
+    duk_put_prop_string(plugin->get_context(), -2, "prototype");
+    duk_put_prop_string(plugin->get_context(), -2, "Server");
+    duk_pop(plugin->get_context());
+}
+
+namespace duk {
+
+void type_traits<std::shared_ptr<server>>::push(duk_context* ctx, std::shared_ptr<server> server)
+{
+    assert(ctx);
+    assert(server);
+
+    duk::stack_guard sa(ctx, 1);
+
+    duk_push_object(ctx);
+    duk_push_pointer(ctx, new std::shared_ptr<class server>(std::move(server)));
+    duk_put_prop_string(ctx, -2, signature.data());
+    duk_get_global_string(ctx, prototype.data());
+    duk_set_prototype(ctx, -2);
+}
+
+auto type_traits<std::shared_ptr<server>>::require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<server>
+{
+    if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature.data()))
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object");
+
+    duk_get_prop_string(ctx, index, signature.data());
+    auto file = *static_cast<std::shared_ptr<server>*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+
+    return file;
+}
+
+void type_traits<server_error>::raise(duk_context* ctx, const server_error& ex)
+{
+    duk::stack_guard sa(ctx, 1);
+
+    duk_get_global_string(ctx, "Irccd");
+    duk_get_prop_string(ctx, -1, "ServerError");
+    duk_remove(ctx, -2);
+    duk::push(ctx, ex.code().value());
+    duk::push(ctx, ex.code().message());
+    duk_new(ctx, 2);
+
+    (void)duk_throw(ctx);
+}
+
+} // !duk
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/server_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,96 @@
+/*
+ * server_js_api.hpp -- Irccd.Server API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_SERVER_JS_API_HPP
+#define IRCCD_JS_SERVER_JS_API_HPP
+
+/**
+ * \file mod-server.hpp
+ * \brief irccd.Server Javascript API.
+ */
+
+#include <irccd/daemon/server.hpp>
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief irccd.Server Javascript API.
+ * \ingroup js_api
+ */
+class server_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc js_api::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+namespace duk {
+
+/**
+ * \brief Specialization for servers as shared_ptr.
+ *
+ * Supports push, require.
+ */
+template <>
+struct type_traits<std::shared_ptr<server>> {
+    /**
+     * Push a server.
+     *
+     * \pre server != nullptr
+     * \param ctx the context
+     * \param server the server
+     */
+    static void push(duk_context* ctx, std::shared_ptr<server> server);
+
+    /**
+     * Require a server. Raise a Javascript error if not a Server.
+     *
+     * \param ctx the context
+     * \param index the index
+     * \return the server
+     */
+    static auto require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<server>;
+};
+
+/**
+ * \brief Specialization for server_error.
+ */
+template <>
+struct type_traits<server_error> {
+    /**
+     * Raise a server_error.
+     *
+     * \param ctx the context
+     * \param error the error
+     */
+    static void raise(duk_context* ctx, const server_error& error);
+};
+
+} // !duk
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_SERVER_JS_API_HPP
--- a/libirccd-js/irccd/js/server_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,898 +0,0 @@
-/*
- * server_jsapi.cpp -- Irccd.Server API
- *
- * Copyright (c) 2013-2018 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 <cassert>
-#include <sstream>
-#include <unordered_map>
-
-#include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server_service.hpp>
-#include <irccd/daemon/server_util.hpp>
-
-#include "duktape_vector.hpp"
-#include "irccd_jsapi.hpp"
-#include "js_plugin.hpp"
-#include "server_jsapi.hpp"
-
-namespace irccd {
-
-namespace {
-
-const char *signature("\xff""\xff""irccd-server-ptr");
-const char *prototype("\xff""\xff""irccd-server-prototype");
-
-std::shared_ptr<server> self(duk_context* ctx)
-{
-    dukx_stack_assert sa(ctx);
-
-    duk_push_this(ctx);
-    duk_get_prop_string(ctx, -1, signature);
-    auto ptr = duk_to_pointer(ctx, -1);
-    duk_pop_2(ctx);
-
-    if (!ptr)
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object");
-
-    return *static_cast<std::shared_ptr<server>*>(ptr);
-}
-
-template <typename Handler>
-duk_ret_t wrap(duk_context* ctx, Handler handler)
-{
-    try {
-        return handler(ctx);
-    } catch (const server_error& ex) {
-        dukx_throw(ctx, ex);
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, ex);
-    }
-
-    return 0;
-}
-
-// {{{ Irccd.Server.prototype.info
-
-/*
- * Method: Irccd.Server.prototype.info()
- * ------------------------------------------------------------------
- *
- * Get the server information as an object containing the following properties:
- *
- * name: the server unique name
- * host: the host name
- * port: the port number
- * ssl: true if using ssl
- * sslVerify: true if ssl was verified
- * channels: an array of all channels
- */
-duk_ret_t Server_prototype_info(duk_context* ctx)
-{
-    const auto server = self(ctx);
-    const auto& channels = server->get_channels();
-
-    duk_push_object(ctx);
-    dukx_push(ctx, server->get_id());
-    duk_put_prop_string(ctx, -2, "name");
-    dukx_push(ctx, server->get_host());
-    duk_put_prop_string(ctx, -2, "host");
-    duk_push_int(ctx, server->get_port());
-    duk_put_prop_string(ctx, -2, "port");
-    duk_push_boolean(ctx,
-        (server->get_options() & server::options::ssl) == server::options::ssl);
-    duk_put_prop_string(ctx, -2, "ssl");
-    duk_push_boolean(ctx,
-        (server->get_options() & server::options::ssl_verify) == server::options::ssl_verify);
-    duk_put_prop_string(ctx, -2, "sslVerify");
-    dukx_push(ctx, server->get_command_char());
-    duk_put_prop_string(ctx, -2, "commandChar");
-    dukx_push(ctx, server->get_realname());
-    duk_put_prop_string(ctx, -2, "realname");
-    dukx_push(ctx, server->get_nickname());
-    duk_put_prop_string(ctx, -2, "nickname");
-    dukx_push(ctx, server->get_username());
-    duk_put_prop_string(ctx, -2, "username");
-    dukx_push(ctx, std::vector<std::string>(channels.begin(), channels.end()));
-    duk_put_prop_string(ctx, -2, "channels");
-
-    return 1;
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.invite
-
-/*
- * Method: Irccd.Server.prototype.invite(target, channel)
- * ------------------------------------------------------------------
- *
- * Invite someone to a channel.
- *
- * Arguments:
- *   - target, the target to invite,
- *   - channel, the channel.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_invite(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto target = dukx_require<std::string>(ctx, 0);
-        auto channel = dukx_require<std::string>(ctx, 1);
-
-        if (target.empty())
-            throw server_error(server_error::invalid_nickname);
-        if (channel.empty())
-            throw server_error(server_error::invalid_channel);
-
-        self(ctx)->invite(std::move(target), std::move(channel));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.isSelf
-
-/*
- * Method: Irccd.Server.prototype.isSelf(nickname)
- * ------------------------------------------------------------------
- *
- * Arguments:
- *   - nickname, the nickname to check.
- * Returns:
- *   True if the nickname targets this server.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t Server_prototype_isSelf(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        return dukx_push(ctx, self(ctx)->is_self(dukx_require<std::string>(ctx, 0)));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.join
-
-/*
- * Method: Irccd.Server.prototype.join(channel, password = undefined)
- * ------------------------------------------------------------------
- *
- * Join a channel with an optional password.
- *
- * Arguments:
- *   - channel, the channel to join,
- *   - password, the password or undefined to not use.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_join(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto channel = dukx_require<std::string>(ctx, 0);
-        auto password = dukx_get<std::string>(ctx, 1);
-
-        if (channel.empty())
-            throw server_error(server_error::invalid_channel);
-
-        self(ctx)->join(std::move(channel), std::move(password));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.kick
-
-/*
- * Method: Irccd.Server.prototype.kick(target, channel, reason = undefined)
- * ------------------------------------------------------------------
- *
- * Kick someone from a channel.
- *
- * Arguments:
- *   - target, the target to kick,
- *   - channel, the channel,
- *   - reason, the optional reason or undefined to not set.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_kick(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto target = dukx_require<std::string>(ctx, 0);
-        auto channel = dukx_require<std::string>(ctx, 1);
-        auto reason = dukx_get<std::string>(ctx, 2);
-
-        if (target.empty())
-            throw server_error(server_error::invalid_nickname);
-        if (channel.empty())
-            throw server_error(server_error::invalid_channel);
-
-        self(ctx)->kick(std::move(target), std::move(channel), std::move(reason));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.me
-
-/*
- * Method: Irccd.Server.prototype.me(target, message)
- * ------------------------------------------------------------------
- *
- * Send a CTCP Action.
- *
- * Arguments:
- *   - target, the target or a channel,
- *   - message, the message.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_me(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto target = dukx_require<std::string>(ctx, 0);
-        auto message = dukx_get<std::string>(ctx, 1);
-
-        if (target.empty())
-            throw server_error(server_error::invalid_nickname);
-
-        self(ctx)->me(std::move(target), std::move(message));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.message
-
-/*
- * Method: Irccd.Server.prototype.message(target, message)
- * ------------------------------------------------------------------
- *
- * Send a message.
- *
- * Arguments:
- *   - target, the target or a channel,
- *   - message, the message.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_message(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto target = dukx_require<std::string>(ctx, 0);
-        auto message = dukx_get<std::string>(ctx, 1);
-
-        if (target.empty())
-            throw server_error(server_error::invalid_nickname);
-
-        self(ctx)->message(std::move(target), std::move(message));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.mode
-
-/*
- * Method: Irccd.Server.prototype.mode(channel, mode, limit, user, mask)
- * ------------------------------------------------------------------
- *
- * Change your mode.
- *
- * Arguments:
- *   - mode, the new mode.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_mode(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto channel = dukx_require<std::string>(ctx, 0);
-        auto mode = dukx_require<std::string>(ctx, 1);
-        auto limit = dukx_get<std::string>(ctx, 2);
-        auto user = dukx_get<std::string>(ctx, 3);
-        auto mask = dukx_get<std::string>(ctx, 4);
-
-        if (channel.empty())
-            throw server_error(server_error::invalid_channel);
-        if (mode.empty())
-            throw server_error(server_error::invalid_mode);
-
-        self(ctx)->mode(
-            std::move(channel),
-            std::move(mode),
-            std::move(limit),
-            std::move(user),
-            std::move(mask)
-        );
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.names
-
-/*
- * Method: Irccd.Server.prototype.names(channel)
- * ------------------------------------------------------------------
- *
- * Get the list of names from a channel.
- *
- * Arguments:
- *   - channel, the channel.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_names(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto channel = dukx_require<std::string>(ctx, 0);
-
-        if (channel.empty())
-            throw server_error(server_error::invalid_channel);
-
-        self(ctx)->names(std::move(channel));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.nick
-
-/*
- * Method: Irccd.Server.prototype.nick(nickname)
- * ------------------------------------------------------------------
- *
- * Change the nickname.
- *
- * Arguments:
- *   - nickname, the nickname.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_nick(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto nickname = dukx_require<std::string>(ctx, 0);
-
-        if (nickname.empty())
-            throw server_error(server_error::invalid_nickname);
-
-        self(ctx)->set_nickname(std::move(nickname));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.notice
-
-/*
- * Method: Irccd.Server.prototype.notice(target, message)
- * ------------------------------------------------------------------
- *
- * Send a private notice.
- *
- * Arguments:
- *   - target, the target,
- *   - message, the notice message.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_notice(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto target = dukx_require<std::string>(ctx, 0);
-        auto message = dukx_get<std::string>(ctx, 1);
-
-        if (target.empty())
-            throw server_error(server_error::invalid_nickname);
-
-        self(ctx)->notice(std::move(target), std::move(message));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.part
-
-/*
- * Method: Irccd.Server.prototype.part(channel, reason = undefined)
- * ------------------------------------------------------------------
- *
- * Leave a channel.
- *
- * Arguments:
- *   - channel, the channel to leave,
- *   - reason, the optional reason, keep undefined for portability.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_part(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto channel = dukx_require<std::string>(ctx, 0);
-        auto reason = dukx_get<std::string>(ctx, 1);
-
-        if (channel.empty())
-            throw server_error(server_error::invalid_channel);
-
-        self(ctx)->part(std::move(channel), std::move(reason));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.send
-
-/*
- * Method: Irccd.Server.prototype.send(raw)
- * ------------------------------------------------------------------
- *
- * Send a raw message to the IRC server.
- *
- * Arguments:
- *   - raw, the raw message (without terminators).
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_send(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto raw = dukx_require<std::string>(ctx, 0);
-
-        if (raw.empty())
-            throw server_error(server_error::invalid_message);
-
-        self(ctx)->send(std::move(raw));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.topic
-
-/*
- * Method: Server.prototype.topic(channel, topic)
- * ------------------------------------------------------------------
- *
- * Change a channel topic.
- *
- * Arguments:
- *   - channel, the channel,
- *   - topic, the new topic.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_topic(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto channel = dukx_require<std::string>(ctx, 0);
-        auto topic = dukx_get<std::string>(ctx, 1);
-
-        if (channel.empty())
-            throw server_error(server_error::invalid_channel);
-
-        self(ctx)->topic(std::move(channel), std::move(topic));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.whois
-
-/*
- * Method: Irccd.Server.prototype.whois(target)
- * ------------------------------------------------------------------
- *
- * Get whois information.
- *
- * Arguments:
- *   - target, the target.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_prototype_whois(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto target = dukx_require<std::string>(ctx, 0);
-
-        if (target.empty())
-            throw server_error(server_error::invalid_nickname);
-
-        self(ctx)->whois(std::move(target));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.toString
-
-/*
- * Method: Irccd.Server.prototype.toString()
- * ------------------------------------------------------------------
- *
- * Convert the object to std::string, convenience for adding the object
- * as property key.
- *
- * Returns:
- *   The server name (unique).
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t Server_prototype_toString(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        dukx_push(ctx, self(ctx)->get_id());
-
-        return 1;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server [constructor]
-
-/*
- * Function: Irccd.Server(params) [constructor]
- * ------------------------------------------------------------------
- *
- * Construct a new server.
- *
- * Params must be filled with the following properties:
- *
- * name: the name,
- * host: the host,
- * ipv6: true to use ipv6,      (Optional: default false)
- * port: the port number,       (Optional: default 6667)
- * password: the password,      (Optional: default none)
- * channels: array of channels  (Optiona: default empty)
- * ssl: true to use ssl,        (Optional: default false)
- * sslVerify: true to verify    (Optional: default true)
- * nickname: "nickname",        (Optional, default: irccd)
- * username: "user name",       (Optional, default: irccd)
- * realname: "real name",       (Optional, default: IRC Client Daemon)
- * commandChar: "!",            (Optional, the command char, default: "!")
- *
- * Arguments:
- *   - params, the server properties
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Server_constructor(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        if (!duk_is_constructor_call(ctx))
-            return 0;
-
-        duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
-
-        auto json = nlohmann::json::parse(duk_json_encode(ctx, 0));
-        auto s = server_util::from_json(dukx_type_traits<irccd>::self(ctx).get_service(), json);
-
-        duk_push_this(ctx);
-        duk_push_pointer(ctx, new std::shared_ptr<server>(std::move(s)));
-        duk_put_prop_string(ctx, -2, signature);
-        duk_pop(ctx);
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server [destructor]
-
-/*
- * Function: Irccd.Server() [destructor]
- * ------------------------------------------------------------------
- *
- * Delete the property.
- */
-duk_ret_t Server_destructor(duk_context* ctx)
-{
-    duk_get_prop_string(ctx, 0, signature);
-    delete static_cast<std::shared_ptr<server>*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-    duk_del_prop_string(ctx, 0, signature);
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Server.add
-
-/*
- * Function: Irccd.Server.add(s)
- * ------------------------------------------------------------------
- *
- * Register a new server to the irccd instance.
- *
- * Arguments:
- *   - s, the server to add.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t Server_add(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        dukx_type_traits<irccd>::self(ctx).servers().add(
-            dukx_require<std::shared_ptr<server>>(ctx, 0));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.find
-
-/*
- * Function: Irccd.Server.find(name)
- * ------------------------------------------------------------------
- *
- * Find a server by name.
- *
- * Arguments:
- *   - name, the server name
- * Returns:
- *   The server object or undefined if not found.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t Server_find(duk_context* ctx)
-{
-    return wrap(ctx, [] (auto ctx) {
-        auto id = dukx_require<std::string>(ctx, 0);
-        auto server = dukx_type_traits<irccd>::self(ctx).servers().get(id);
-
-        if (!server)
-            return 0;
-
-        dukx_push(ctx, server);
-
-        return 1;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Server.list
-
-/*
- * Function: Irccd.Server.list()
- * ------------------------------------------------------------------
- *
- * Get the map of all loaded servers.
- *
- * Returns:
- *   An object with string-to-servers pairs.
- */
-duk_ret_t Server_list(duk_context* ctx)
-{
-    duk_push_object(ctx);
-
-    for (const auto& server : dukx_type_traits<irccd>::self(ctx).servers().all()) {
-        dukx_push(ctx, server);
-        duk_put_prop_string(ctx, -2, server->get_id().c_str());
-    }
-
-    return 1;
-}
-
-// }}}
-
-// {{{ Irccd.Server.remove
-
-/*
- * Function: Irccd.Server.remove(name)
- * ------------------------------------------------------------------
- *
- * Remove a server from the irccd instance. You can pass the server object since
- * it's coercible to a string.
- *
- * Arguments:
- *   - name the server name.
- */
-duk_ret_t Server_remove(duk_context* ctx)
-{
-    dukx_type_traits<irccd>::self(ctx).servers().remove(duk_require_string(ctx, 0));
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.ServerError
-
-/*
- * Function: Irccd.ServerError(code, message)
- * ------------------------------------------------------------------
- *
- * Create an Irccd.ServerError object.
- *
- * Arguments:
- *   - code, the error code,
- *   - message, the error message.
- */
-duk_ret_t ServerError_constructor(duk_context* ctx)
-{
-    duk_push_this(ctx);
-    duk_push_int(ctx, duk_require_int(ctx, 0));
-    duk_put_prop_string(ctx, -2, "code");
-    duk_push_string(ctx, duk_require_string(ctx, 1));
-    duk_put_prop_string(ctx, -2, "message");
-    duk_push_string(ctx, "ServerError");
-    duk_put_prop_string(ctx, -2, "name");
-    duk_pop(ctx);
-
-    return 0;
-}
-
-// }}}
-
-const duk_function_list_entry methods[] = {
-    { "info",       Server_prototype_info,      0           },
-    { "invite",     Server_prototype_invite,    2           },
-    { "isSelf",     Server_prototype_isSelf,    1           },
-    { "join",       Server_prototype_join,      DUK_VARARGS },
-    { "kick",       Server_prototype_kick,      DUK_VARARGS },
-    { "me",         Server_prototype_me,        2           },
-    { "message",    Server_prototype_message,   2           },
-    { "mode",       Server_prototype_mode,      1           },
-    { "names",      Server_prototype_names,     1           },
-    { "nick",       Server_prototype_nick,      1           },
-    { "notice",     Server_prototype_notice,    2           },
-    { "part",       Server_prototype_part,      DUK_VARARGS },
-    { "send",       Server_prototype_send,      1           },
-    { "topic",      Server_prototype_topic,     2           },
-    { "toString",   Server_prototype_toString,  0           },
-    { "whois",      Server_prototype_whois,     1           },
-    { nullptr,      nullptr,                    0           }
-};
-
-const duk_function_list_entry functions[] = {
-    { "add",        Server_add,                 1           },
-    { "find",       Server_find,                1           },
-    { "list",       Server_list,                0           },
-    { "remove",     Server_remove,              1           },
-    { nullptr,      nullptr,                    0           }
-};
-
-} // !namespace
-
-std::string server_jsapi::get_name() const
-{
-    return "Irccd.Server";
-}
-
-void server_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_get_global_string(plugin->get_context(), "Irccd");
-
-    // ServerError function.
-    duk_push_c_function(plugin->get_context(), ServerError_constructor, 2);
-    duk_push_object(plugin->get_context());
-    duk_get_global_string(plugin->get_context(), "Error");
-    duk_get_prop_string(plugin->get_context(), -1, "prototype");
-    duk_remove(plugin->get_context(), -2);
-    duk_set_prototype(plugin->get_context(), -2);
-    duk_put_prop_string(plugin->get_context(), -2, "prototype");
-    duk_put_prop_string(plugin->get_context(), -2, "ServerError");
-
-    // Server constructor.
-    duk_push_c_function(plugin->get_context(), Server_constructor, 1);
-    duk_put_function_list(plugin->get_context(), -1, functions);
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, methods);
-    duk_push_c_function(plugin->get_context(), Server_destructor, 1);
-    duk_set_finalizer(plugin->get_context(), -2);
-    duk_dup_top(plugin->get_context());
-    duk_put_global_string(plugin->get_context(), prototype);
-    duk_put_prop_string(plugin->get_context(), -2, "prototype");
-    duk_put_prop_string(plugin->get_context(), -2, "Server");
-    duk_pop(plugin->get_context());
-}
-
-using server_traits = dukx_type_traits<std::shared_ptr<server>>;
-using server_error_traits = dukx_type_traits<server_error>;
-
-void server_traits::push(duk_context* ctx, std::shared_ptr<server> server)
-{
-    assert(ctx);
-    assert(server);
-
-    dukx_stack_assert sa(ctx, 1);
-
-    duk_push_object(ctx);
-    duk_push_pointer(ctx, new std::shared_ptr<class server>(std::move(server)));
-    duk_put_prop_string(ctx, -2, signature);
-    duk_get_global_string(ctx, prototype);
-    duk_set_prototype(ctx, -2);
-}
-
-std::shared_ptr<server> server_traits::require(duk_context* ctx, duk_idx_t index)
-{
-    if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature))
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object");
-
-    duk_get_prop_string(ctx, index, signature);
-    auto file = *static_cast<std::shared_ptr<server> *>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-
-    return file;
-}
-
-void server_error_traits::raise(duk_context* ctx, const server_error& ex)
-{
-    dukx_stack_assert sa(ctx, 1);
-
-    duk_get_global_string(ctx, "Irccd");
-    duk_get_prop_string(ctx, -1, "ServerError");
-    duk_remove(ctx, -2);
-    dukx_push(ctx, ex.code().value());
-    dukx_push(ctx, ex.code().message());
-    duk_new(ctx, 2);
-
-    (void)duk_throw(ctx);
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/server_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/*
- * server_jsapi.hpp -- Irccd.Server API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_SERVER_JSAPI_HPP
-#define IRCCD_JS_SERVER_JSAPI_HPP
-
-/**
- * \file mod-server.hpp
- * \brief irccd.Server Javascript API.
- */
-
-#include <irccd/daemon/server.hpp>
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief irccd.Server Javascript API.
- * \ingroup jsapi
- */
-class server_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc jsapi::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-/**
- * \brief Specialization for servers as shared_ptr.
- *
- * Supports push, require.
- */
-template <>
-class dukx_type_traits<std::shared_ptr<server>> : public std::true_type {
-public:
-    /**
-     * Push a server.
-     *
-     * \pre server != nullptr
-     * \param ctx the context
-     * \param server the server
-     */
-    static void push(duk_context* ctx, std::shared_ptr<server> server);
-
-    /**
-     * Require a server. Raise a Javascript error if not a Server.
-     *
-     * \param ctx the context
-     * \param index the index
-     * \return the server
-     */
-    static std::shared_ptr<server> require(duk_context* ctx, duk_idx_t index);
-};
-
-/**
- * \brief Specialization for server_error.
- */
-template <>
-class dukx_type_traits<server_error> : public std::true_type {
-public:
-    /**
-     * Raise a server_error.
-     *
-     * \param ctx the context
-     * \param error the error
-     */
-    static void raise(duk_context* ctx, const server_error& error);
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_SERVER_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/system_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,330 @@
+/*
+ * system_js_api.cpp -- Irccd.System API
+ *
+ * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
+
+#include <chrono>
+#include <cstdlib>
+#include <thread>
+
+#if defined(HAVE_POPEN)
+#  include <cstdio>
+#endif
+
+#include <irccd/system.hpp>
+
+#include "file_js_api.hpp"
+#include "irccd_js_api.hpp"
+#include "js_plugin.hpp"
+#include "system_js_api.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ wrap
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+    try {
+        return handler();
+    } catch (const std::system_error& ex) {
+        duk::raise(ctx, ex);
+    } catch (const std::exception& ex) {
+        duk::raise(ctx, ex);
+    }
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.System.env
+
+/*
+ * Function: Irccd.System.env(key)
+ * ------------------------------------------------------------------
+ *
+ * Get an environment system variable.
+ *
+ * Arguments:
+ *   - key, the environment variable.
+ * Returns:
+ *   The value.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_env(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, sys::env(duk::get<std::string>(ctx, 0)));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.System.exec
+
+/*
+ * Function: Irccd.System.exec(cmd)
+ * ------------------------------------------------------------------
+ *
+ * Execute a system command.
+ *
+ * Arguments:
+ *   - cmd, the command to execute.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_exec(duk_context* ctx) -> duk_ret_t
+{
+    std::system(duk_require_string(ctx, 0));
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.System.home
+
+/*
+ * Function: Irccd.System.home()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system user's home.
+ *
+ * Returns:
+ *   The user home directory.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_home(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, sys::home());
+    });
+}
+
+// }}}
+
+// {{{ Irccd.System.name
+
+/*
+ * Function: Irccd.System.name()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system name.
+ *
+ * Returns:
+ *   The system name.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_name(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, sys::name());
+    });
+}
+
+// }}}
+
+// {{{ Irccd.System.popen
+
+#if defined(HAVE_POPEN)
+
+/*
+ * Function: Irccd.System.popen(cmd, mode) [optional]
+ * ------------------------------------------------------------------
+ *
+ * Wrapper for popen(3) if the function is available.
+ *
+ * Arguments:
+ *   - cmd, the command to execute,
+ *   - mode, the mode (e.g. "r").
+ * Returns:
+ *   A irccd.File object.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_popen(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        auto fp = ::popen(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+        if (fp == nullptr)
+            throw std::system_error(make_error_code(static_cast<std::errc>(errno)));
+
+        return duk::push(ctx, std::make_shared<file>(fp, [] (auto fp) { ::pclose(fp); }));
+    });
+}
+
+#endif // !HAVE_POPEN
+
+// }}}
+
+// {{{ Icrcd.System.sleep
+
+/*
+ * Function: Irccd.System.sleep(delay)
+ * ------------------------------------------------------------------
+ *
+ * Sleep the main loop for the specific delay in seconds.
+ *
+ * Arguments:
+ *   - delay, the delay in seconds.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_sleep(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        std::this_thread::sleep_for(std::chrono::seconds(duk_get_int(ctx, 0)));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.System.ticks
+
+/*
+ * Function: Irccd.System.ticks()
+ * ------------------------------------------------------------------
+ *
+ * Get the number of milliseconds since irccd was started.
+ *
+ * Returns:
+ *   The number of milliseconds.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_ticks(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push<unsigned>(ctx, sys::ticks());
+    });
+}
+
+// }}}
+
+// {{{ Irccd.System.usleep
+
+/*
+ * Function: Irccd.System.usleep(delay)
+ * ------------------------------------------------------------------
+ *
+ * Sleep the main loop for the specific delay in microseconds.
+ *
+ * Arguments:
+ *   - delay, the delay in microseconds.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_usleep(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        std::this_thread::sleep_for(std::chrono::microseconds(duk_get_int(ctx, 0)));
+
+        return 0;
+    });
+}
+
+// }}}
+
+// {{{ Irccd.System.uptime
+
+/*
+ * Function: Irccd.System.uptime()
+ * ------------------------------------------------------------------
+ *
+ * Get the system uptime.
+ *
+ * Returns:
+ *   The system uptime.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_uptime(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push<unsigned>(ctx, sys::uptime());
+    });
+}
+
+// }}}
+
+// {{{ Irccd.System.version
+
+/*
+ * Function: Irccd.System.version()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system version.
+ *
+ * Returns:
+ *   The system version.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_version(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, sys::version());
+    });
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+    { "env",        System_env,     1 },
+    { "exec",       System_exec,    1 },
+    { "home",       System_home,    0 },
+    { "name",       System_name,    0 },
+#if defined(HAVE_POPEN)
+    { "popen",      System_popen,   2 },
+#endif
+    { "sleep",      System_sleep,   1 },
+    { "ticks",      System_ticks,   0 },
+    { "uptime",     System_uptime,  0 },
+    { "usleep",     System_usleep,  1 },
+    { "version",    System_version, 0 },
+    { nullptr,      nullptr,        0 }
+};
+
+} // !namespace
+
+auto system_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.System";
+}
+
+void system_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, functions);
+    duk_put_prop_string(plugin->get_context(), -2, "System");
+    duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/system_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,50 @@
+/*
+ * system_js_api.hpp -- Irccd.System API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_SYSTEM_JS_API_HPP
+#define IRCCD_JS_SYSTEM_JS_API_HPP
+
+/**
+ * \file system_js_api.hpp
+ * \brief Irccd.System Javascript API.
+ */
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Irccd.System Javascript API.
+ * \ingroup js_api
+ */
+class system_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc js_api::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_SYSTEM_JS_API_HPP
--- a/libirccd-js/irccd/js/system_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,330 +0,0 @@
-/*
- * system_jsapi.cpp -- Irccd.System API
- *
- * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
-
-#include <chrono>
-#include <cstdlib>
-#include <thread>
-
-#if defined(HAVE_POPEN)
-#  include <cstdio>
-#endif
-
-#include <irccd/system.hpp>
-
-#include "file_jsapi.hpp"
-#include "irccd_jsapi.hpp"
-#include "js_plugin.hpp"
-#include "system_jsapi.hpp"
-
-namespace irccd {
-
-namespace {
-
-// {{{ wrap
-
-template <typename Handler>
-duk_ret_t wrap(duk_context* ctx, Handler handler)
-{
-    try {
-        return handler();
-    } catch (const std::system_error& ex) {
-        dukx_throw(ctx, ex);
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, ex);
-    }
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.System.env
-
-/*
- * Function: Irccd.System.env(key)
- * ------------------------------------------------------------------
- *
- * Get an environment system variable.
- *
- * Arguments:
- *   - key, the environment variable.
- * Returns:
- *   The value.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_env(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, sys::env(dukx_get<std::string>(ctx, 0)));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.System.exec
-
-/*
- * Function: Irccd.System.exec(cmd)
- * ------------------------------------------------------------------
- *
- * Execute a system command.
- *
- * Arguments:
- *   - cmd, the command to execute.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_exec(duk_context* ctx)
-{
-    std::system(duk_require_string(ctx, 0));
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.System.home
-
-/*
- * Function: Irccd.System.home()
- * ------------------------------------------------------------------
- *
- * Get the operating system user's home.
- *
- * Returns:
- *   The user home directory.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_home(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, sys::home());
-    });
-}
-
-// }}}
-
-// {{{ Irccd.System.name
-
-/*
- * Function: Irccd.System.name()
- * ------------------------------------------------------------------
- *
- * Get the operating system name.
- *
- * Returns:
- *   The system name.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_name(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, sys::name());
-    });
-}
-
-// }}}
-
-// {{{ Irccd.System.popen
-
-#if defined(HAVE_POPEN)
-
-/*
- * Function: Irccd.System.popen(cmd, mode) [optional]
- * ------------------------------------------------------------------
- *
- * Wrapper for popen(3) if the function is available.
- *
- * Arguments:
- *   - cmd, the command to execute,
- *   - mode, the mode (e.g. "r").
- * Returns:
- *   A irccd.File object.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_popen(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        auto fp = ::popen(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-        if (fp == nullptr)
-            throw std::system_error(make_error_code(static_cast<std::errc>(errno)));
-
-        return dukx_push(ctx, std::make_shared<file>(fp, [] (auto fp) { ::pclose(fp); }));
-    });
-}
-
-#endif // !HAVE_POPEN
-
-// }}}
-
-// {{{ Icrcd.System.sleep
-
-/*
- * Function: Irccd.System.sleep(delay)
- * ------------------------------------------------------------------
- *
- * Sleep the main loop for the specific delay in seconds.
- *
- * Arguments:
- *   - delay, the delay in seconds.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_sleep(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        std::this_thread::sleep_for(std::chrono::seconds(duk_get_int(ctx, 0)));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.System.ticks
-
-/*
- * Function: Irccd.System.ticks()
- * ------------------------------------------------------------------
- *
- * Get the number of milliseconds since irccd was started.
- *
- * Returns:
- *   The number of milliseconds.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_ticks(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push<unsigned>(ctx, sys::ticks());
-    });
-}
-
-// }}}
-
-// {{{ Irccd.System.usleep
-
-/*
- * Function: Irccd.System.usleep(delay)
- * ------------------------------------------------------------------
- *
- * Sleep the main loop for the specific delay in microseconds.
- *
- * Arguments:
- *   - delay, the delay in microseconds.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_usleep(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        std::this_thread::sleep_for(std::chrono::microseconds(duk_get_int(ctx, 0)));
-
-        return 0;
-    });
-}
-
-// }}}
-
-// {{{ Irccd.System.uptime
-
-/*
- * Function: Irccd.System.uptime()
- * ------------------------------------------------------------------
- *
- * Get the system uptime.
- *
- * Returns:
- *   The system uptime.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_uptime(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push<unsigned>(ctx, sys::uptime());
-    });
-}
-
-// }}}
-
-// {{{ Irccd.System.version
-
-/*
- * Function: Irccd.System.version()
- * ------------------------------------------------------------------
- *
- * Get the operating system version.
- *
- * Returns:
- *   The system version.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t System_version(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, sys::version());
-    });
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-    { "env",        System_env,     1 },
-    { "exec",       System_exec,    1 },
-    { "home",       System_home,    0 },
-    { "name",       System_name,    0 },
-#if defined(HAVE_POPEN)
-    { "popen",      System_popen,   2 },
-#endif
-    { "sleep",      System_sleep,   1 },
-    { "ticks",      System_ticks,   0 },
-    { "uptime",     System_uptime,  0 },
-    { "usleep",     System_usleep,  1 },
-    { "version",    System_version, 0 },
-    { nullptr,      nullptr,        0 }
-};
-
-} // !namespace
-
-std::string system_jsapi::get_name() const
-{
-    return "Irccd.System";
-}
-
-void system_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_get_global_string(plugin->get_context(), "Irccd");
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, functions);
-    duk_put_prop_string(plugin->get_context(), -2, "System");
-    duk_pop(plugin->get_context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/system_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * system_jsapi.hpp -- Irccd.System API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_SYSTEM_JSAPI_HPP
-#define IRCCD_JS_SYSTEM_JSAPI_HPP
-
-/**
- * \file system_jsapi.hpp
- * \brief Irccd.System Javascript API.
- */
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.System Javascript API.
- * \ingroup jsapi
- */
-class system_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc jsapi::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_SYSTEM_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/timer_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,314 @@
+/*
+ * timer_js_api.cpp -- Irccd.timer API
+ *
+ * Copyright (c) 2013-2018 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 <boost/asio.hpp>
+
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/logger.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+
+#include "irccd_js_api.hpp"
+#include "js_plugin.hpp"
+#include "plugin_js_api.hpp"
+#include "timer_js_api.hpp"
+
+namespace asio = boost::asio;
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature("\xff""\xff""Irccd.Timer");
+const std::string_view table("\xff""\xff""Irccd.Timer.callbacks");
+
+// {{{ timer
+
+class timer : public std::enable_shared_from_this<timer> {
+public:
+    enum class type {
+        single,
+        repeat
+    };
+
+private:
+    boost::asio::deadline_timer handle_;
+    js_plugin& plugin_;
+
+    std::string key_;
+    type type_;
+    int delay_;
+
+    bool is_running_{false};
+    bool is_waiting_{false};
+
+    void handle();
+
+public:
+    timer(boost::asio::io_service&, js_plugin&, type, int);
+
+    auto key() const noexcept -> const std::string&;
+
+    void start();
+
+    void stop();
+};
+
+void timer::handle()
+{
+    duk::stack_guard sa(plugin_.get_context());
+
+    duk_push_global_stash(plugin_.get_context());
+    duk_get_prop_string(plugin_.get_context(), -1, table.data());
+    duk_remove(plugin_.get_context(), -2);
+    duk_get_prop_string(plugin_.get_context(), -1, key_.c_str());
+    duk_remove(plugin_.get_context(), -2);
+
+    if (duk_pcall(plugin_.get_context(), 0)) {
+        auto& log = duk::type_traits<irccd>::self(plugin_.get_context()).get_log();
+
+        log.warning(static_cast<const plugin&>(plugin_)) << "timer error:" << std::endl;
+        log.warning(static_cast<const plugin&>(plugin_)) << "  " << duk::get_stack(plugin_.get_context(), -1).what() << std::endl;
+    } else
+        duk_pop(plugin_.get_context());
+}
+
+timer::timer(boost::asio::io_service& service, js_plugin& plugin, type type, int delay)
+    : handle_(service)
+    , plugin_(plugin)
+    , type_(type)
+    , delay_(delay)
+{
+}
+
+auto timer::key() const noexcept -> const std::string&
+{
+    return key_;
+}
+
+void timer::start()
+{
+    if (is_waiting_)
+        return;
+
+    is_running_ = is_waiting_ = true;
+
+    handle_.expires_from_now(boost::posix_time::milliseconds(delay_));
+    handle_.async_wait([this] (auto code) {
+        is_waiting_ = false;
+
+        if (code) {
+            is_running_ = false;
+            return;
+        }
+
+        handle();
+
+        if (is_running_ && type_ == type::repeat)
+            start();
+    });
+}
+
+void timer::stop()
+{
+    if (is_running_) {
+        handle_.cancel();
+        is_running_ = false;
+    }
+}
+
+// }}}
+
+// {{{ self
+
+auto self(duk_context* ctx) -> timer*
+{
+    duk::stack_guard sa(ctx);
+
+    duk_push_this(ctx);
+    duk_get_prop_string(ctx, -1, signature.data());
+    auto ptr = duk_to_pointer(ctx, -1);
+    duk_pop_2(ctx);
+
+    if (!ptr)
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Timer object");
+
+    return static_cast<timer*>(ptr);
+}
+
+// }}}
+
+// {{{ Irccd.Timer.prototype.start
+
+/*
+ * Method: Irccd.Timer.prototype.start()
+ * --------------------------------------------------------
+ *
+ * Start the timer. If the timer is already started the method is a no-op.
+ */
+auto Timer_prototype_start(duk_context* ctx) -> duk_ret_t
+{
+    self(ctx)->start();
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Timer.prototype.stop
+
+/*
+ * Method: Irccd.Timer.prototype.stop()
+ * --------------------------------------------------------
+ *
+ * Stop the timer.
+ */
+auto Timer_prototype_stop(duk_context* ctx) -> duk_ret_t
+{
+    self(ctx)->stop();
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Timer [destructor]
+
+/*
+ * Function: Irccd.Timer() [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Deleter the timer.
+ */
+auto Timer_destructor(duk_context* ctx) -> duk_ret_t
+{
+    duk::stack_guard sa(ctx);
+
+    // Get timer from this.
+    duk_get_prop_string(ctx, 0, signature.data());
+    auto ptr = static_cast<timer*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+
+    // Remove callback from timer table.
+    duk_push_global_stash(ctx);
+    duk_get_prop_string(ctx, -1, table.data());
+    duk_remove(ctx, -2);
+    duk_del_prop_string(ctx, -1, ptr->key().c_str());
+    duk_pop(ctx);
+
+    delete ptr;
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Timer [constructor]
+
+/*
+ * Function: Irccd.Timer(type, delay, callback) [constructor]
+ * --------------------------------------------------------
+ *
+ * Create a new timer object.
+ *
+ * Arguments:
+ *   - type, the type of timer (Irccd.Timer.Single or Irccd.Timer.Repeat),
+ *   - delay, the interval in milliseconds,
+ *   - callback, the function to call.
+ */
+auto Timer_constructor(duk_context* ctx) -> duk_ret_t
+{
+    if (!duk_is_constructor_call(ctx))
+        return 0;
+
+    try {
+        // Check parameters.
+        const auto type = duk_require_int(ctx, 0);
+        const auto delay = duk_require_int(ctx, 1);
+
+        if (type < static_cast<int>(timer::type::single) || type > static_cast<int>(timer::type::repeat))
+            duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid timer type");
+        if (delay < 0)
+            duk_error(ctx, DUK_ERR_TYPE_ERROR, "negative delay given");
+        if (!duk_is_callable(ctx, 2))
+            duk_error(ctx, DUK_ERR_TYPE_ERROR, "missing callback function");
+
+        auto& plugin = duk::type_traits<js_plugin>::self(ctx);
+        auto& daemon = duk::type_traits<irccd>::self(ctx);
+        auto object = new timer(daemon.get_service(), plugin, static_cast<timer::type>(type), delay);
+
+        duk_push_this(ctx);
+        duk_push_pointer(ctx, object);
+        duk_put_prop_string(ctx, -2, signature.data());
+        duk_push_c_function(ctx, Timer_destructor, 1);
+        duk_set_finalizer(ctx, -2);
+        duk_pop(ctx);
+
+        // Store the function in a table to be called later.
+        duk_push_global_stash(ctx);
+        duk_get_prop_string(ctx, -1, table.data());
+        duk_remove(ctx, -2);
+        duk_dup(ctx, 2);
+        duk_put_prop_string(ctx, -2, object->key().c_str());
+        duk_pop(ctx);
+    } catch (const std::exception& ex) {
+        duk::raise(ctx, ex);
+    }
+
+    return 0;
+}
+
+// }}}
+
+const duk_function_list_entry methods[] = {
+    { "start",  Timer_prototype_start,  0                   },
+    { "stop",   Timer_prototype_stop,   0                   },
+    { nullptr,  nullptr,                0                   }
+};
+
+const duk_number_list_entry constants[] = {
+    { "Single",     static_cast<int>(timer::type::single)   },
+    { "Repeat",     static_cast<int>(timer::type::repeat)   },
+    { nullptr,      0                                       }
+};
+
+} // !namespace
+
+auto timer_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.Timer";
+}
+
+void timer_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+    duk_push_c_function(plugin->get_context(), Timer_constructor, 3);
+    duk_put_number_list(plugin->get_context(), -1, constants);
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, methods);
+    duk_put_prop_string(plugin->get_context(), -2, "prototype");
+    duk_put_prop_string(plugin->get_context(), -2, "Timer");
+    duk_pop(plugin->get_context());
+    duk_push_global_stash(plugin->get_context());
+    duk_push_object(plugin->get_context());
+    duk_put_prop_string(plugin->get_context(), -2, table.data());
+    duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/timer_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,50 @@
+/*
+ * timer_js_api.hpp -- Irccd.Timer API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_TIMER_JS_API_HPP
+#define IRCCD_JS_TIMER_JS_API_HPP
+
+/**
+ * \file timer_js_api
+ * \brief Irccd.Timer Javascript API.
+ */
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Irccd.Timer Javascript API.
+ * \ingroup js_api
+ */
+class timer_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc js_api::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_TIMER_JS_API_HPP
--- a/libirccd-js/irccd/js/timer_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,301 +0,0 @@
-/*
- * timer_jsapi.cpp -- Irccd.timer API
- *
- * Copyright (c) 2013-2018 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 <boost/asio.hpp>
-
-#include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/plugin_service.hpp>
-
-#include "irccd_jsapi.hpp"
-#include "js_plugin.hpp"
-#include "plugin_jsapi.hpp"
-#include "timer_jsapi.hpp"
-
-namespace irccd {
-
-namespace {
-
-const char* signature("\xff""\xff""irccd-timer-ptr");
-const char* table("\xff""\xff""irccd-timer-callbacks");
-
-// {{{ timer
-
-class timer {
-public:
-    enum class type_t {
-        single,
-        repeat
-    };
-
-private:
-    boost::asio::deadline_timer handle_;
-    std::weak_ptr<js_plugin> plugin_;
-    std::uintmax_t delay_;
-    type_t type_;
-    bool is_running_{false};
-    bool is_waiting_{false};
-
-    void handle();
-
-public:
-    inline timer(boost::asio::io_service& service,
-                 std::weak_ptr<js_plugin> plugin,
-                 std::uintmax_t delay,
-                 type_t type) noexcept
-        : handle_(service)
-        , plugin_(plugin)
-        , delay_(delay)
-        , type_(type)
-    {
-    }
-
-    inline std::string key() const
-    {
-        return std::to_string(reinterpret_cast<std::uintptr_t>(this));
-    }
-
-    void start();
-    void stop();
-};
-
-void timer::handle()
-{
-    auto plg = plugin_.lock();
-
-    if (!plg)
-        return;
-
-    auto& ctx = plg->get_context();
-
-    duk_get_global_string(ctx, table);
-    duk_get_prop_string(ctx, -1, key().c_str());
-    duk_remove(ctx, -2);
-
-    if (duk_pcall(ctx, 0)) {
-        auto& log = dukx_type_traits<irccd>::self(ctx).get_log();
-
-        log.warning(static_cast<const plugin&>(*plg)) << "timer error:" << std::endl;
-        log.warning(static_cast<const plugin&>(*plg)) << "  " << dukx_stack(ctx, -1).what() << std::endl;
-    } else
-        duk_pop(ctx);
-}
-
-void timer::start()
-{
-    if (is_waiting_)
-        return;
-
-    is_running_ = is_waiting_ = true;
-
-    handle_.expires_from_now(boost::posix_time::milliseconds(delay_));
-    handle_.async_wait([this] (auto code) {
-        is_waiting_ = false;
-
-        if (code)
-            return;
-
-        handle();
-
-        if (is_running_ && type_ == type_t::repeat)
-            start();
-    });
-}
-
-void timer::stop()
-{
-    if (is_running_) {
-        handle_.cancel();
-        is_running_ = false;
-    }
-}
-
-// }}}
-
-// {{{ self
-
-timer* self(duk_context* ctx)
-{
-    dukx_stack_assert sa(ctx);
-
-    duk_push_this(ctx);
-    duk_get_prop_string(ctx, -1, signature);
-    auto ptr = duk_to_pointer(ctx, -1);
-    duk_pop_2(ctx);
-
-    if (!ptr)
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Timer object");
-
-    return static_cast<timer*>(ptr);
-}
-
-// }}}
-
-// {{{ Irccd.Timer.prototype.start
-
-/*
- * Method: Irccd.Timer.prototype.start()
- * --------------------------------------------------------
- *
- * Start the timer. If the timer is already started the method is a no-op.
- */
-duk_ret_t Timer_prototype_start(duk_context* ctx)
-{
-    self(ctx)->start();
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Timer.prototype.stop
-
-/*
- * Method: Irccd.Timer.prototype.stop()
- * --------------------------------------------------------
- *
- * Stop the timer.
- */
-duk_ret_t Timer_prototype_stop(duk_context* ctx)
-{
-    self(ctx)->stop();
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Timer [destructor]
-
-/*
- * Function: Irccd.Timer() [destructor]
- * ------------------------------------------------------------------
- *
- * Deleter the timer.
- */
-duk_ret_t Timer_destructor(duk_context* ctx)
-{
-    dukx_stack_assert sa(ctx);
-
-    // Get timer from this.
-    duk_get_prop_string(ctx, 0, signature);
-    auto ptr = static_cast<timer*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-
-    // Remove callback from timer table.
-    duk_get_global_string(ctx, table);
-    duk_del_prop_string(ctx, -1, ptr->key().c_str());
-    duk_pop(ctx);
-
-    delete ptr;
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Timer [constructor]
-
-/*
- * Function: Irccd.Timer(type, delay, callback) [constructor]
- * --------------------------------------------------------
- *
- * Create a new timer object.
- *
- * Arguments:
- *   - type, the type of timer (Irccd.Timer.Single or Irccd.Timer.Repeat),
- *   - delay, the interval in milliseconds,
- *   - callback, the function to call.
- */
-duk_ret_t Timer_constructor(duk_context* ctx)
-{
-    if (!duk_is_constructor_call(ctx))
-        return 0;
-
-    try {
-        // Check parameters.
-        const auto type = duk_require_int(ctx, 0);
-        const auto delay = duk_require_int(ctx, 1);
-
-        if (type < static_cast<int>(timer::type_t::single) || type > static_cast<int>(timer::type_t::repeat))
-            duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid timer type");
-        if (delay < 0)
-            duk_error(ctx, DUK_ERR_TYPE_ERROR, "negative delay given");
-        if (!duk_is_callable(ctx, 2))
-            duk_error(ctx, DUK_ERR_TYPE_ERROR, "missing callback function");
-
-        auto plugin = dukx_type_traits<js_plugin>::self(ctx);
-        auto& daemon = dukx_type_traits<irccd>::self(ctx);
-        auto object = new timer(daemon.get_service(), plugin, delay, static_cast<timer::type_t>(type));
-
-        duk_push_this(ctx);
-        duk_push_pointer(ctx, object);
-        duk_put_prop_string(ctx, -2, signature);
-        duk_push_c_function(ctx, Timer_destructor, 1);
-        duk_set_finalizer(ctx, -2);
-        duk_pop(ctx);
-
-        // Store the function in a table to be called later.
-        duk_get_global_string(ctx, table);
-        duk_dup(ctx, 2);
-        duk_put_prop_string(ctx, -2, object->key().c_str());
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, ex);
-    }
-
-    return 0;
-}
-
-// }}}
-
-const duk_function_list_entry methods[] = {
-    { "start",  Timer_prototype_start,  0                   },
-    { "stop",   Timer_prototype_stop,   0                   },
-    { nullptr,  nullptr,                0                   }
-};
-
-const duk_number_list_entry constants[] = {
-    { "Single",     static_cast<int>(timer::type_t::single) },
-    { "Repeat",     static_cast<int>(timer::type_t::repeat) },
-    { nullptr,      0                                       }
-};
-
-} // !namespace
-
-std::string timer_jsapi::get_name() const
-{
-    return "Irccd.Timer";
-}
-
-void timer_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_get_global_string(plugin->get_context(), "Irccd");
-    duk_push_c_function(plugin->get_context(), Timer_constructor, 3);
-    duk_put_number_list(plugin->get_context(), -1, constants);
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, methods);
-    duk_put_prop_string(plugin->get_context(), -2, "prototype");
-    duk_put_prop_string(plugin->get_context(), -2, "Timer");
-    duk_pop(plugin->get_context());
-    duk_push_object(plugin->get_context());
-    duk_put_global_string(plugin->get_context(), table);
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/timer_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * timer_jsapi.hpp -- Irccd.Timer API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_TIMER_JSAPI_HPP
-#define IRCCD_JS_TIMER_JSAPI_HPP
-
-/**
- * \file timer_jsapi
- * \brief Irccd.Timer Javascript API.
- */
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.Timer Javascript API.
- * \ingroup jsapi
- */
-class timer_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc jsapi::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_TIMER_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/unicode_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,163 @@
+/*
+ * unicode_js_api.cpp -- Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2018 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 "js_plugin.hpp"
+#include "unicode.hpp"
+#include "unicode_js_api.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ Irccd.Unicode.isDigit
+
+/*
+ * Function: Irccd.Unicode.isDigit(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is in the digit category.
+ */
+auto Unicode_isDigit(duk_context* ctx) noexcept -> duk_ret_t
+{
+    return duk::push(ctx, unicode::isdigit(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isLetter
+
+/*
+ * Function: Irccd.Unicode.isLetter(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is in the letter category.
+ */
+auto Unicode_isLetter(duk_context* ctx) noexcept -> duk_ret_t
+{
+    return duk::push(ctx, unicode::isalpha(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isLower
+
+/*
+ * Function: Irccd.Unicode.isLower(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is lower case.
+ */
+auto Unicode_isLower(duk_context* ctx) noexcept -> duk_ret_t
+{
+    return duk::push(ctx, unicode::islower(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isSpace
+
+/*
+ * Function: Irccd.Unicode.isSpace(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is in the space category.
+ */
+auto Unicode_isSpace(duk_context* ctx) noexcept -> duk_ret_t
+{
+    return duk::push(ctx, unicode::isspace(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isTitle
+
+/*
+ * Function: Irccd.Unicode.isTitle(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is title case.
+ */
+auto Unicode_isTitle(duk_context* ctx) noexcept -> duk_ret_t
+{
+    return duk::push(ctx, unicode::istitle(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isUpper
+
+/*
+ * Function: Irccd.Unicode.isUpper(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is upper case.
+ */
+auto Unicode_isUpper(duk_context* ctx) noexcept -> duk_ret_t
+{
+    return duk::push(ctx, unicode::isupper(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+    { "isDigit",        Unicode_isDigit,    1 },
+    { "isLetter",       Unicode_isLetter,   1 },
+    { "isLower",        Unicode_isLower,    1 },
+    { "isSpace",        Unicode_isSpace,    1 },
+    { "isTitle",        Unicode_isTitle,    1 },
+    { "isUpper",        Unicode_isUpper,    1 },
+    { nullptr,          nullptr,            0 }
+};
+
+} // !namespace
+
+auto unicode_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.Unicode";
+}
+
+void unicode_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, functions);
+    duk_put_prop_string(plugin->get_context(), -2, "Unicode");
+    duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/unicode_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,50 @@
+/*
+ * unicode_js_api.hpp -- Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_UNICODE_JS_API_HPP
+#define IRCCD_JS_UNICODE_JS_API_HPP
+
+/**
+ * \file unicode_js_api.hpp
+ * \brief Irccd.Unicode Javascript API.
+ */
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Irccd.Unicode Javascript API.
+ * \ingroup js_api
+ */
+class unicode_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc js_api::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_UNICODE_JS_API_HPP
--- a/libirccd-js/irccd/js/unicode_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-/*
- * unicode_jsapi.cpp -- Irccd.Unicode API
- *
- * Copyright (c) 2013-2018 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 "js_plugin.hpp"
-#include "unicode.hpp"
-#include "unicode_jsapi.hpp"
-
-namespace irccd {
-
-namespace {
-
-// {{{ Irccd.Unicode.isDigit
-
-/*
- * Function: Irccd.Unicode.isDigit(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is in the digit category.
- */
-duk_ret_t Unicode_isDigit(duk_context* ctx) noexcept
-{
-    return dukx_push(ctx, unicode::isdigit(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isLetter
-
-/*
- * Function: Irccd.Unicode.isLetter(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is in the letter category.
- */
-duk_ret_t Unicode_isLetter(duk_context* ctx) noexcept
-{
-    return dukx_push(ctx, unicode::isalpha(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isLower
-
-/*
- * Function: Irccd.Unicode.isLower(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is lower case.
- */
-duk_ret_t Unicode_isLower(duk_context* ctx) noexcept
-{
-    return dukx_push(ctx, unicode::islower(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isSpace
-
-/*
- * Function: Irccd.Unicode.isSpace(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is in the space category.
- */
-duk_ret_t Unicode_isSpace(duk_context* ctx) noexcept
-{
-    return dukx_push(ctx, unicode::isspace(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isTitle
-
-/*
- * Function: Irccd.Unicode.isTitle(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is title case.
- */
-duk_ret_t Unicode_isTitle(duk_context* ctx) noexcept
-{
-    return dukx_push(ctx, unicode::istitle(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isUpper
-
-/*
- * Function: Irccd.Unicode.isUpper(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is upper case.
- */
-duk_ret_t Unicode_isUpper(duk_context* ctx) noexcept
-{
-    return dukx_push(ctx, unicode::isupper(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-    { "isDigit",        Unicode_isDigit,    1 },
-    { "isLetter",       Unicode_isLetter,   1 },
-    { "isLower",        Unicode_isLower,    1 },
-    { "isSpace",        Unicode_isSpace,    1 },
-    { "isTitle",        Unicode_isTitle,    1 },
-    { "isUpper",        Unicode_isUpper,    1 },
-    { nullptr,          nullptr,            0 }
-};
-
-} // !namespace
-
-std::string unicode_jsapi::get_name() const
-{
-    return "Irccd.Unicode";
-}
-
-void unicode_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_get_global_string(plugin->get_context(), "Irccd");
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, functions);
-    duk_put_prop_string(plugin->get_context(), -2, "Unicode");
-    duk_pop(plugin->get_context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/unicode_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * unicode_jsapi.hpp -- Irccd.Unicode API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_UNICODE_JSAPI_HPP
-#define IRCCD_JS_UNICODE_JSAPI_HPP
-
-/**
- * \file unicode_jsapi.hpp
- * \brief Irccd.Unicode Javascript API.
- */
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.Unicode Javascript API.
- * \ingroup jsapi
- */
-class unicode_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc jsapi::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_UNICODE_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/util_js_api.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,351 @@
+/*
+ * util_js_api.cpp -- Irccd.Util API
+ *
+ * Copyright (c) 2013-2018 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 <climits>
+
+#include <irccd/string_util.hpp>
+
+#include "irccd_js_api.hpp"
+#include "js_plugin.hpp"
+#include "util_js_api.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ subst
+
+/*
+ * Read parameters for Irccd.Util.format function, the object is defined as
+ * following:
+ *
+ * {
+ *   date: the date object
+ *   flags: the flags (not implemented yet)
+ *   field1: a field to substitute in #{} pattern
+ *   field2: a field to substitute in #{} pattern
+ *   fieldn: ...
+ * }
+ */
+auto subst(duk_context* ctx, int index) -> string_util::subst
+{
+    string_util::subst params;
+
+    if (!duk_is_object(ctx, index))
+        return params;
+
+    duk_enum(ctx, index, 0);
+
+    while (duk_next(ctx, -1, true)) {
+        if (duk::get<std::string>(ctx, -2) == "date")
+            params.time = static_cast<time_t>(duk_get_number(ctx, -1) / 1000);
+        else
+            params.keywords.insert({
+                duk::get<std::string>(ctx, -2),
+                duk::get<std::string>(ctx, -1)
+            });
+
+        duk_pop_n(ctx, 2);
+    }
+
+    return params;
+}
+
+// }}}
+
+// {{{ split
+
+/*
+ * split (for Irccd.Util.cut)
+ * ------------------------------------------------------------------
+ *
+ * Extract individual tokens in array or a whole string as a std:::vector.
+ */
+auto split(duk_context* ctx) -> std::vector<std::string>
+{
+    duk_require_type_mask(ctx, 0, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_STRING);
+
+    std::vector<std::string> result;
+    std::string pattern = " \t\n";
+
+    if (duk_is_string(ctx, 0))
+        result = string_util::split(duk::get<std::string>(ctx, 0), pattern);
+    else if (duk_is_array(ctx, 0)) {
+        duk_enum(ctx, 0, DUK_ENUM_ARRAY_INDICES_ONLY);
+
+        while (duk_next(ctx, -1, 1)) {
+            // Split individual tokens as array if spaces are found.
+            const auto tmp = string_util::split(duk_to_string(ctx, -1), pattern);
+
+            result.insert(result.end(), tmp.begin(), tmp.end());
+            duk_pop_2(ctx);
+        }
+    }
+
+    return result;
+}
+
+// }}}
+
+// {{{ limit
+
+/*
+ * limit (for Irccd.Util.cut)
+ * ------------------------------------------------------------------
+ *
+ * Get the maxl/maxc argument.
+ *
+ * The argument value is the default and also used as the result returned.
+ */
+auto limit(duk_context* ctx, int index, const char* name, int value) -> int
+{
+    if (duk_get_top(ctx) < index || !duk_is_number(ctx, index))
+        return value;
+
+    value = duk_to_int(ctx, index);
+
+    if (value <= 0)
+        duk_error(ctx, DUK_ERR_RANGE_ERROR, "argument %d (%s) must be positive", index, name);
+
+    return value;
+}
+
+// }}}
+
+// {{{ lines
+
+/*
+ * lines (for Irccd.Util.cut)
+ * ------------------------------------------------------------------
+ *
+ * Build a list of lines.
+ *
+ * Several cases possible:
+ *
+ *   - s is the current line
+ *   - abc is the token to add
+ *
+ * s   = ""                 (new line)
+ * s  -> "abc"
+ *
+ * s   = "hello world"      (enough room)
+ * s  -> "hello world abc"
+ *
+ * s   = "hello world"      (not enough room: maxc is smaller)
+ * s+1 = "abc"
+ */
+auto lines(duk_context* ctx, const std::vector<std::string>& tokens, int maxc) -> std::vector<std::string>
+{
+    std::vector<std::string> result{""};
+
+    for (const auto& s : tokens) {
+        if (s.length() > static_cast<std::size_t>(maxc))
+            duk_error(ctx, DUK_ERR_RANGE_ERROR, "word '%s' could not fit in maxc limit (%d)", s.c_str(), maxc);
+
+        // Compute the length required (prepend a space if needed)
+        auto required = s.length() + (result.back().empty() ? 0 : 1);
+
+        if (result.back().length() + required > static_cast<std::size_t>(maxc))
+            result.push_back(s);
+        else {
+            if (!result.back().empty())
+                result.back() += ' ';
+
+            result.back() += s;
+        }
+    }
+
+    return result;
+}
+
+// }}}
+
+// {{{ wrap
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+    try {
+        return handler();
+    } catch (const std::system_error& ex) {
+        duk::raise(ctx, ex);
+    } catch (const std::exception& ex) {
+        duk::raise(ctx, ex);
+    }
+
+    return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Util.cut
+
+/*
+ * Function: Irccd.Util.cut(data, maxc, maxl)
+ * --------------------------------------------------------
+ *
+ * Cut a piece of data into several lines.
+ *
+ * The argument data is a string or a list of strings. In any case, all strings
+ * are first splitted by spaces and trimmed. This ensure that useless
+ * whitespaces are discarded.
+ *
+ * The argument maxc controls the maximum of characters allowed per line, it can
+ * be a positive integer. If undefined is given, a default of 72 is used.
+ *
+ * The argument maxl controls the maximum of lines allowed. It can be a positive
+ * integer or undefined for an infinite list.
+ *
+ * If maxl is used as a limit and the data can not fit within the bounds,
+ * undefined is returned.
+ *
+ * An empty list may be returned if empty strings were found.
+ *
+ * Arguments:
+ *   - data, a string or an array of strings,
+ *   - maxc, max number of colums (Optional, default: 72),
+ *   - maxl, max number of lines (Optional, default: undefined).
+ * Returns:
+ *   A list of strings ready to be sent or undefined if the data is too big.
+ * Throws:
+ *   - RangeError if maxl or maxc are negative numbers,
+ *   - RangeError if one word length was bigger than maxc,
+ *   - TypeError if data is not a string or a list of strings,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Util_cut(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        const auto list = lines(ctx, split(ctx), limit(ctx, 1, "maxc", 72));
+        const auto maxl = limit(ctx, 2, "maxl", INT_MAX);
+
+        if (list.size() > static_cast<std::size_t>(maxl))
+            return 0;
+
+        // Empty list but lines() returns at least one.
+        if (list.size() == 1 && list[0].empty()) {
+            duk_push_array(ctx);
+            return 1;
+        }
+
+        return duk::push(ctx, list);
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Util.format
+
+/*
+ * Function: Irccd.Util.format(text, parameters)
+ * --------------------------------------------------------
+ *
+ * Format a string with templates.
+ *
+ * Arguments:
+ *   - input, the text to update,
+ *   - params, the parameters.
+ * Returns:
+ *   The converted text.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Util_format(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, string_util::format(duk::get<std::string>(ctx, 0), subst(ctx, 1)));
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Util.splituser
+
+/*
+ * Function: Irccd.Util.splituser(ident)
+ * --------------------------------------------------------
+ *
+ * Return the nickname part from a full username.
+ *
+ * Arguments:
+ *   - ident, the full identity.
+ * Returns:
+ *   The nickname.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Util_splituser(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, irc::user::parse(duk::require<std::string>(ctx, 0)).nick);
+    });
+}
+
+// }}}
+
+// {{{ Irccd.Util.splithost
+
+/*
+ * Function: Irccd.Util.splithost(ident)
+ * --------------------------------------------------------
+ *
+ * Return the hostname part from a full username.
+ *
+ * Arguments:
+ *   - ident, the full identity.
+ * Returns:
+ *   The hostname.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Util_splithost(duk_context* ctx) -> duk_ret_t
+{
+    return wrap(ctx, [&] {
+        return duk::push(ctx, irc::user::parse(duk::require<std::string>(ctx, 0)).host);
+    });
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+    { "cut",        Util_cut,       DUK_VARARGS },
+    { "format",     Util_format,    DUK_VARARGS },
+    { "splituser",  Util_splituser, 1           },
+    { "splithost",  Util_splithost, 1           },
+    { nullptr,      nullptr,        0           }
+};
+
+} // !namespace
+
+auto util_js_api::get_name() const noexcept -> std::string_view
+{
+    return "Irccd.Util";
+}
+
+void util_js_api::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    duk::stack_guard sa(plugin->get_context());
+
+    duk_get_global_string(plugin->get_context(), "Irccd");
+    duk_push_object(plugin->get_context());
+    duk_put_function_list(plugin->get_context(), -1, functions);
+    duk_put_prop_string(plugin->get_context(), -2, "Util");
+    duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/util_js_api.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,50 @@
+/*
+ * util_js_api.hpp -- Irccd.Util API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_UTIL_JS_API_HPP
+#define IRCCD_JS_UTIL_JS_API_HPP
+
+/**
+ * \file util_js_api.hpp
+ * \brief Irccd.Util Javascript API.
+ */
+
+#include "js_api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Irccd.Util Javascript API.
+ * \ingroup js_api
+ */
+class util_js_api : public js_api {
+public:
+    /**
+     * \copydoc js_api::get_name
+     */
+    auto get_name() const noexcept -> std::string_view override;
+
+    /**
+     * \copydoc js_api::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_UTIL_JS_API_HPP
--- a/libirccd-js/irccd/js/util_jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,352 +0,0 @@
-/*
- * util_jsapi.cpp -- Irccd.Util API
- *
- * Copyright (c) 2013-2018 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 <climits>
-
-#include <irccd/string_util.hpp>
-
-#include "duktape_vector.hpp"
-#include "irccd_jsapi.hpp"
-#include "js_plugin.hpp"
-#include "util_jsapi.hpp"
-
-namespace irccd {
-
-namespace {
-
-// {{{ subst
-
-/*
- * Read parameters for Irccd.Util.format function, the object is defined as
- * following:
- *
- * {
- *   date: the date object
- *   flags: the flags (not implemented yet)
- *   field1: a field to substitute in #{} pattern
- *   field2: a field to substitute in #{} pattern
- *   fieldn: ...
- * }
- */
-string_util::subst subst(duk_context* ctx, int index)
-{
-    string_util::subst params;
-
-    if (!duk_is_object(ctx, index))
-        return params;
-
-    duk_enum(ctx, index, 0);
-
-    while (duk_next(ctx, -1, true)) {
-        if (dukx_get<std::string>(ctx, -2) == "date")
-            params.time = static_cast<time_t>(duk_get_number(ctx, -1) / 1000);
-        else
-            params.keywords.insert({
-                dukx_get<std::string>(ctx, -2),
-                dukx_get<std::string>(ctx, -1)
-            });
-
-        duk_pop_n(ctx, 2);
-    }
-
-    return params;
-}
-
-// }}}
-
-// {{{ split
-
-/*
- * split (for Irccd.Util.cut)
- * ------------------------------------------------------------------
- *
- * Extract individual tokens in array or a whole string as a std:::vector.
- */
-std::vector<std::string> split(duk_context* ctx)
-{
-    duk_require_type_mask(ctx, 0, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_STRING);
-
-    std::vector<std::string> result;
-    std::string pattern = " \t\n";
-
-    if (duk_is_string(ctx, 0))
-        result = string_util::split(dukx_get<std::string>(ctx, 0), pattern);
-    else if (duk_is_array(ctx, 0)) {
-        duk_enum(ctx, 0, DUK_ENUM_ARRAY_INDICES_ONLY);
-
-        while (duk_next(ctx, -1, 1)) {
-            // Split individual tokens as array if spaces are found.
-            const auto tmp = string_util::split(duk_to_string(ctx, -1), pattern);
-
-            result.insert(result.end(), tmp.begin(), tmp.end());
-            duk_pop_2(ctx);
-        }
-    }
-
-    return result;
-}
-
-// }}}
-
-// {{{ limit
-
-/*
- * limit (for Irccd.Util.cut)
- * ------------------------------------------------------------------
- *
- * Get the maxl/maxc argument.
- *
- * The argument value is the default and also used as the result returned.
- */
-int limit(duk_context* ctx, int index, const char* name, int value)
-{
-    if (duk_get_top(ctx) < index || !duk_is_number(ctx, index))
-        return value;
-
-    value = duk_to_int(ctx, index);
-
-    if (value <= 0)
-        duk_error(ctx, DUK_ERR_RANGE_ERROR, "argument %d (%s) must be positive", index, name);
-
-    return value;
-}
-
-// }}}
-
-// {{{ lines
-
-/*
- * lines (for Irccd.Util.cut)
- * ------------------------------------------------------------------
- *
- * Build a list of lines.
- *
- * Several cases possible:
- *
- *   - s is the current line
- *   - abc is the token to add
- *
- * s   = ""                 (new line)
- * s  -> "abc"
- *
- * s   = "hello world"      (enough room)
- * s  -> "hello world abc"
- *
- * s   = "hello world"      (not enough room: maxc is smaller)
- * s+1 = "abc"
- */
-std::vector<std::string> lines(duk_context* ctx, const std::vector<std::string>& tokens, int maxc)
-{
-    std::vector<std::string> result{""};
-
-    for (const auto& s : tokens) {
-        if (s.length() > static_cast<std::size_t>(maxc))
-            duk_error(ctx, DUK_ERR_RANGE_ERROR, "word '%s' could not fit in maxc limit (%d)", s.c_str(), maxc);
-
-        // Compute the length required (prepend a space if needed)
-        auto required = s.length() + (result.back().empty() ? 0 : 1);
-
-        if (result.back().length() + required > static_cast<std::size_t>(maxc))
-            result.push_back(s);
-        else {
-            if (!result.back().empty())
-                result.back() += ' ';
-
-            result.back() += s;
-        }
-    }
-
-    return result;
-}
-
-// }}}
-
-// {{{ wrap
-
-template <typename Handler>
-duk_ret_t wrap(duk_context* ctx, Handler handler)
-{
-    try {
-        return handler();
-    } catch (const std::system_error& ex) {
-        dukx_throw(ctx, ex);
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, ex);
-    }
-
-    return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Util.cut
-
-/*
- * Function: Irccd.Util.cut(data, maxc, maxl)
- * --------------------------------------------------------
- *
- * Cut a piece of data into several lines.
- *
- * The argument data is a string or a list of strings. In any case, all strings
- * are first splitted by spaces and trimmed. This ensure that useless
- * whitespaces are discarded.
- *
- * The argument maxc controls the maximum of characters allowed per line, it can
- * be a positive integer. If undefined is given, a default of 72 is used.
- *
- * The argument maxl controls the maximum of lines allowed. It can be a positive
- * integer or undefined for an infinite list.
- *
- * If maxl is used as a limit and the data can not fit within the bounds,
- * undefined is returned.
- *
- * An empty list may be returned if empty strings were found.
- *
- * Arguments:
- *   - data, a string or an array of strings,
- *   - maxc, max number of colums (Optional, default: 72),
- *   - maxl, max number of lines (Optional, default: undefined).
- * Returns:
- *   A list of strings ready to be sent or undefined if the data is too big.
- * Throws:
- *   - RangeError if maxl or maxc are negative numbers,
- *   - RangeError if one word length was bigger than maxc,
- *   - TypeError if data is not a string or a list of strings,
- *   - Irccd.SystemError on other errors.
- */
-duk_ret_t Util_cut(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        const auto list = lines(ctx, split(ctx), limit(ctx, 1, "maxc", 72));
-        const auto maxl = limit(ctx, 2, "maxl", INT_MAX);
-
-        if (list.size() > static_cast<std::size_t>(maxl))
-            return 0;
-
-        // Empty list but lines() returns at least one.
-        if (list.size() == 1 && list[0].empty()) {
-            duk_push_array(ctx);
-            return 1;
-        }
-
-        return dukx_push(ctx, list);
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Util.format
-
-/*
- * Function: Irccd.Util.format(text, parameters)
- * --------------------------------------------------------
- *
- * Format a string with templates.
- *
- * Arguments:
- *   - input, the text to update,
- *   - params, the parameters.
- * Returns:
- *   The converted text.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t Util_format(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, string_util::format(dukx_get<std::string>(ctx, 0), subst(ctx, 1)));
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Util.splituser
-
-/*
- * Function: Irccd.Util.splituser(ident)
- * --------------------------------------------------------
- *
- * Return the nickname part from a full username.
- *
- * Arguments:
- *   - ident, the full identity.
- * Returns:
- *   The nickname.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t Util_splituser(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, irc::user::parse(dukx_require<std::string>(ctx, 0)).nick);
-    });
-}
-
-// }}}
-
-// {{{ Irccd.Util.splithost
-
-/*
- * Function: Irccd.Util.splithost(ident)
- * --------------------------------------------------------
- *
- * Return the hostname part from a full username.
- *
- * Arguments:
- *   - ident, the full identity.
- * Returns:
- *   The hostname.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-duk_ret_t Util_splithost(duk_context* ctx)
-{
-    return wrap(ctx, [&] {
-        return dukx_push(ctx, irc::user::parse(dukx_require<std::string>(ctx, 0)).host);
-    });
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-    { "cut",        Util_cut,       DUK_VARARGS },
-    { "format",     Util_format,    DUK_VARARGS },
-    { "splituser",  Util_splituser, 1           },
-    { "splithost",  Util_splithost, 1           },
-    { nullptr,      nullptr,        0           }
-};
-
-} // !namespace
-
-std::string util_jsapi::get_name() const
-{
-    return "Irccd.Util";
-}
-
-void util_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    dukx_stack_assert sa(plugin->get_context());
-
-    duk_get_global_string(plugin->get_context(), "Irccd");
-    duk_push_object(plugin->get_context());
-    duk_put_function_list(plugin->get_context(), -1, functions);
-    duk_put_prop_string(plugin->get_context(), -2, "Util");
-    duk_pop(plugin->get_context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js/util_jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * util_jsapi.hpp -- Irccd.Util API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_UTIL_JSAPI_HPP
-#define IRCCD_JS_UTIL_JSAPI_HPP
-
-/**
- * \file util_jsapi.hpp
- * \brief Irccd.Util Javascript API.
- */
-
-#include "jsapi.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.Util Javascript API.
- * \ingroup jsapi
- */
-class util_jsapi : public jsapi {
-public:
-    /**
-     * \copydoc jsapi::get_name
-     */
-    std::string get_name() const override;
-
-    /**
-     * \copydoc jsapi::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_UTIL_JSAPI_HPP
--- a/libirccd-test/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ b/libirccd-test/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -20,34 +20,28 @@
 
 set(
     HEADERS
-    ${libirccd-test_SOURCE_DIR}/irccd/test/cli_test.hpp
+    ${libirccd-test_SOURCE_DIR}/irccd/test/cli_fixture.hpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/command_fixture.hpp
+    ${libirccd-test_SOURCE_DIR}/irccd/test/debug_server.hpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/irccd_fixture.hpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/debug_server.hpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/mock.hpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/mock_plugin.hpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/mock_server.hpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/plugin_cli_test.hpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/rule_cli_test.hpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/server_cli_test.hpp
+    $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/js_fixture.hpp>
     $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/plugin_test.hpp>
-    $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/javascript_fixture.hpp>
 )
 
 set(
     SOURCES
+    ${libirccd-test_SOURCE_DIR}/irccd/test/cli_fixture.cpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/command_fixture.cpp
+    ${libirccd-test_SOURCE_DIR}/irccd/test/debug_server.cpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/irccd_fixture.cpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/cli_test.cpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/debug_server.cpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/mock.cpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/mock_plugin.cpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/mock_server.cpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/plugin_cli_test.cpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/rule_cli_test.cpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/server_cli_test.cpp
+    $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/js_fixture.cpp>
     $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/plugin_test.cpp>
-    $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/javascript_fixture.cpp>
 )
 
 if (${IRCCD_HAVE_JS})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/cli_fixture.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,97 @@
+/*
+ * cli_fixture.cpp -- test fixture for irccdctl frontend
+ *
+ * Copyright (c) 2013-2018 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 <chrono>
+#include <sstream>
+
+#include <boost/process.hpp>
+
+#include <irccd/string_util.hpp>
+#include <irccd/socket_acceptor.hpp>
+
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/transport_service.hpp>
+#include <irccd/daemon/transport_server.hpp>
+
+#include "cli_fixture.hpp"
+
+namespace proc = boost::process;
+
+namespace irccd::test {
+
+cli_fixture::cli_fixture()
+    : server_(new mock_server(irccd_.get_service(), "test", "localhost"))
+{
+    std::remove(CMAKE_BINARY_DIR "/tmp/irccd.sock");
+
+    io::local_acceptor::endpoint endpoint(CMAKE_BINARY_DIR "/tmp/irccd.sock");
+    io::local_acceptor::acceptor acceptor(service_, std::move(endpoint));
+
+    for (const auto& f : command::registry)
+        irccd_.transports().get_commands().push_back(f());
+
+    irccd_.servers().add(server_);
+    irccd_.transports().add(std::make_unique<transport_server>(
+        std::make_unique<io::local_acceptor>(std::move(acceptor))));
+    server_->clear();
+}
+
+cli_fixture::~cli_fixture()
+{
+    service_.stop();
+    thread_.join();
+}
+
+void cli_fixture::start()
+{
+    thread_ = std::thread([this] { service_.run(); });
+
+    // Let irccd bind correctly.
+    std::this_thread::sleep_for(std::chrono::milliseconds(250));
+}
+
+auto cli_fixture::exec(const std::vector<std::string>& args) -> result
+{
+    static const std::string irccdctl = IRCCDCTL_EXECUTABLE;
+    static const std::string conf = CMAKE_BINARY_DIR "/tmp/irccdctl.conf";
+
+    std::ostringstream oss;
+
+    oss << irccdctl << " -c " << conf << " ";
+    oss << string_util::join(args, " ");
+
+    proc::ipstream stream_out, stream_err;
+
+    const auto ret = proc::system(
+        oss.str(),
+        proc::std_in.close(),
+        proc::std_out > stream_out,
+        proc::std_err > stream_err
+    );
+
+    outputs out, err;
+
+    for (std::string line; stream_out && std::getline(stream_out, line); )
+        out.push_back(line);
+    for (std::string line; stream_err && std::getline(stream_err, line); )
+        err.push_back(line);
+
+    return { ret, out, err };
+}
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/cli_fixture.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,112 @@
+/*
+ * cli_fixture.hpp -- test fixture for irccdctl frontend
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_TEST_CLI_FIXTURE_HPP
+#define IRCCD_TEST_CLI_FIXTURE_HPP
+
+/**
+ * \file cli_fixture.hpp
+ * \brief Test fixture for irccdctl frontend.
+ */
+
+#include <thread>
+#include <tuple>
+#include <vector>
+
+#include <boost/asio.hpp>
+
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/server_service.hpp>
+
+#include <irccd/test/mock_server.hpp>
+
+namespace irccd::test {
+
+/**
+ * \brief Test fixture for irccdctl frontend.
+ *
+ * This class will run irccd daemon in a thread when member function `start` is
+ * called.
+ *
+ * Before starting the daemon, the test can manually modify irccd instance
+ * through `irccd_` member variable. Once started, call `exec` with arguments
+ * you want to pass through irccdctl utility.
+ */
+class cli_fixture {
+private:
+    using io_service = boost::asio::io_service;
+
+    std::thread thread_;
+    io_service service_;
+
+protected:
+    /**
+     * Irccd instance.
+     *
+     * \warning Do not modify once `start()` has been called.
+     */
+    irccd irccd_{service_};
+
+    /**
+     * Server automatically added as "test".
+     */
+    std::shared_ptr<mock_server> server_;
+
+public:
+    /**
+     * Type for all lines printed.
+     */
+    using outputs = std::vector<std::string>;
+
+    /**
+     * Collection of output from stdout/stderr respectively.
+     */
+    using result = std::tuple<int, outputs, outputs>;
+
+    /**
+     * Construct and initialize and irccd daemon running in a thread.
+     */
+    cli_fixture();
+
+    /**
+     * Stop irccd and close everything.
+     */
+    ~cli_fixture();
+
+    /**
+     * Start irccd daemon.
+     *
+     * A thread will be running and closed when the destructor is called, you
+     * MUST not modify irccd while running.
+     */
+    void start();
+
+    /**
+     * Execute irccdctl.
+     *
+     * \param args the arguments to irccdctl
+     * \return the stdout/stderr and exit code
+     */
+    auto exec(const std::vector<std::string>& args) -> result;
+};
+
+} // !irccd::test
+
+#endif // !IRCCD_TEST_CLI_FIXTURE_HPP
--- a/libirccd-test/irccd/test/cli_test.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * cli_test.cpp -- test fixture for irccdctl frontend
- *
- * Copyright (c) 2013-2018 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 <chrono>
-#include <sstream>
-
-#include <irccd/string_util.hpp>
-#include <irccd/socket_acceptor.hpp>
-
-#include <irccd/daemon/transport_service.hpp>
-#include <irccd/daemon/transport_server.hpp>
-
-#include "cli_test.hpp"
-
-namespace proc = boost::process;
-
-namespace irccd {
-
-cli_test::cli_test()
-{
-    std::remove(CMAKE_BINARY_DIR "/tmp/irccd.sock");
-
-    io::local_acceptor::endpoint endpoint(CMAKE_BINARY_DIR "/tmp/irccd.sock");
-    io::local_acceptor::acceptor acceptor(service_, std::move(endpoint));
-
-    irccd_.transports().add(std::make_unique<transport_server>(
-        std::make_unique<io::local_acceptor>(std::move(acceptor))));
-}
-
-cli_test::~cli_test()
-{
-    service_.stop();
-    thread_.join();
-}
-
-void cli_test::start()
-{
-    thread_ = std::thread([this] { service_.run(); });
-
-    // Let irccd bind correctly.
-    std::this_thread::sleep_for(std::chrono::milliseconds(250));
-}
-
-auto cli_test::exec(const std::vector<std::string>& args) -> result
-{
-    static const std::string irccdctl = IRCCDCTL_EXECUTABLE;
-    static const std::string conf = CMAKE_BINARY_DIR "/tmp/irccdctl.conf";
-
-    std::ostringstream oss;
-
-    oss << irccdctl << " -c " << conf << " ";
-    oss << string_util::join(args, " ");
-
-    proc::ipstream stream_out, stream_err;
-
-    const auto ret = proc::system(
-        oss.str(),
-        proc::std_in.close(),
-        proc::std_out > stream_out,
-        proc::std_err > stream_err
-    );
-
-    outputs out, err;
-
-    for (std::string line; stream_out && std::getline(stream_out, line); )
-        out.push_back(line);
-    for (std::string line; stream_err && std::getline(stream_err, line); )
-        err.push_back(line);
-
-    return {ret, out, err};
-}
-
-} // !irccd
--- a/libirccd-test/irccd/test/cli_test.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-/*
- * cli_test.hpp -- test fixture for irccdctl frontend
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_TEST_CLI_TEST_HPP
-#define IRCCD_TEST_CLI_TEST_HPP
-
-/**
- * \file cli_test.hpp
- * \brief Test fixture for irccdctl frontend.
- */
-
-#include <thread>
-#include <tuple>
-#include <vector>
-
-#include <irccd/daemon/irccd.hpp>
-
-#include <boost/asio.hpp>
-#include <boost/process.hpp>
-
-namespace irccd {
-
-/**
- * \brief Test fixture for irccdctl frontend.
- *
- * This class will run irccd daemon in a thread when member function `start` is
- * called.
- *
- * Before starting the daemon, the test can manually modify irccd instance
- * through `irccd_` member variable. Once started, call `exec` with arguments
- * you want to pass through irccdctl utility.
- */
-class cli_test {
-private:
-    using io_service = boost::asio::io_service;
-
-    std::thread thread_;
-    io_service service_;
-
-protected:
-    /**
-     * Irccd instance.
-     *
-     * \warning Do not modify once `start()` has been called.
-     */
-    irccd irccd_{service_};
-
-public:
-    /**
-     * Type for all lines printed.
-     */
-    using outputs = std::vector<std::string>;
-
-    /**
-     * Collection of output from stdout/stderr respectively.
-     */
-    using result = std::tuple<int, outputs, outputs>;
-
-    /**
-     * Construct and initialize and irccd daemon running in a thread.
-     */
-    cli_test();
-
-    /**
-     * Stop irccd and close everything.
-     */
-    ~cli_test();
-
-    /**
-     * Start irccd daemon.
-     *
-     * A thread will be running and closed when the destructor is called, you
-     * MUST not modify irccd while running.
-     */
-    void start();
-
-    /**
-     * Execute irccdctl.
-     *
-     * \param args the arguments to irccdctl
-     * \return the stdout/stderr and exit code
-     */
-    auto exec(const std::vector<std::string>& args) -> result;
-};
-
-} // !irccd
-
-#endif // !IRCCD_TEST_CLI_TEST_HPP
--- a/libirccd-test/irccd/test/javascript_fixture.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-/*
- * javascript_fixture.cpp -- test fixture helper for Javascript modules
- *
- * Copyright (c) 2013-2018 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 "javascript_fixture.hpp"
-
-namespace irccd::test {
-
-javascript_fixture::javascript_fixture(const std::string& path)
-    : plugin_(new js_plugin("test", path))
-{
-    for (const auto& f : jsapi::registry)
-        f()->load(irccd_, plugin_);
-
-    if (!path.empty())
-        plugin_->open();
-}
-
-} // !irccd::test
--- a/libirccd-test/irccd/test/javascript_fixture.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/*
- * javascript_fixture.hpp -- test fixture helper for Javascript modules
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_TEST_JAVASCRIPT_FIXTURE_HPP
-#define IRCCD_TEST_JAVASCRIPT_FIXTURE_HPP
-
-/**
- * \file javascript_fixture.hpp
- * \brief Test fixture helper for Javascript modules.
- */
-
-#include "irccd_fixture.hpp"
-
-#include <irccd/js/js_plugin.hpp>
-#include <irccd/js/jsapi.hpp>
-
-#include <irccd/js/directory_jsapi.hpp>
-#include <irccd/js/elapsed_timer_jsapi.hpp>
-#include <irccd/js/file_jsapi.hpp>
-#include <irccd/js/irccd_jsapi.hpp>
-#include <irccd/js/jsapi.hpp>
-#include <irccd/js/logger_jsapi.hpp>
-#include <irccd/js/plugin_jsapi.hpp>
-#include <irccd/js/server_jsapi.hpp>
-#include <irccd/js/system_jsapi.hpp>
-#include <irccd/js/timer_jsapi.hpp>
-#include <irccd/js/unicode_jsapi.hpp>
-#include <irccd/js/util_jsapi.hpp>
-
-namespace irccd::test {
-
-/**
- * \brief Test fixture helper for Javascript modules.
- */
-class javascript_fixture : public irccd_fixture {
-protected:
-    /**
-     * \brief Javascript plugin.
-     */
-    std::shared_ptr<js_plugin> plugin_;
-
-    /**
-     * Constructor.
-     *
-     * Initialize a Javascript plugin with all Javascript API modules.
-     *
-     * \param path the path to a Javascript file if required
-     */
-    javascript_fixture(const std::string& path = "");
-};
-
-} // !irccd::test
-
-#endif // !IRCCD_TEST_JAVASCRIPT_FIXTURE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/js_fixture.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,33 @@
+/*
+ * js.cpp -- test fixture helper for Javascript modules
+ *
+ * Copyright (c) 2013-2018 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 "js_fixture.hpp"
+
+namespace irccd::test {
+
+js_fixture::js_fixture(const std::string& path)
+    : plugin_(new js::js_plugin("test", path))
+{
+    for (const auto& f : js::js_api::registry)
+        f()->load(irccd_, plugin_);
+
+    if (!path.empty())
+        plugin_->open();
+}
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/js_fixture.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,58 @@
+/*
+ * js_fixture.hpp -- test fixture helper for Javascript modules
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_TEST_JS_FIXTURE_HPP
+#define IRCCD_TEST_JS_FIXTURE_HPP
+
+/**
+ * \file js_fixture.hpp
+ * \brief Test fixture helper for Javascript modules.
+ */
+
+#include "irccd_fixture.hpp"
+
+#include <irccd/js/js_plugin.hpp>
+#include <irccd/js/js_api.hpp>
+
+#include <irccd/js/irccd_js_api.hpp>
+
+namespace irccd::test {
+
+/**
+ * \brief Test fixture helper for Javascript modules.
+ */
+class js_fixture : public irccd_fixture {
+protected:
+    /**
+     * \brief Javascript plugin.
+     */
+    std::shared_ptr<js::js_plugin> plugin_;
+
+    /**
+     * Constructor.
+     *
+     * Initialize a Javascript plugin with all Javascript API modules.
+     *
+     * \param path the path to a Javascript file if required
+     */
+    js_fixture(const std::string& path = "");
+};
+
+} // !irccd::test
+
+#endif // !IRCCD_TEST_JS_FIXTURE_HPP
--- a/libirccd-test/irccd/test/plugin_cli_test.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/*
- * plugin_cli_test.cpp -- test fixture for irccdctl frontend (plugins support)
- *
- * Copyright (c) 2013-2018 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 <irccd/daemon/command.hpp>
-#include <irccd/daemon/transport_service.hpp>
-
-#include "plugin_cli_test.hpp"
-
-namespace irccd {
-
-plugin_cli_test::plugin_cli_test()
-{
-    irccd_.transports().get_commands().push_back(std::make_unique<plugin_config_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<plugin_info_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<plugin_list_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<plugin_load_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<plugin_reload_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<plugin_unload_command>());
-}
-
-} // !irccd
--- a/libirccd-test/irccd/test/plugin_cli_test.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * plugin_cli_test.hpp -- test fixture for irccdctl frontend (plugins support)
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_TEST_PLUGIN_CLI_TEST_HPP
-#define IRCCD_TEST_PLUGIN_CLI_TEST_HPP
-
-/**
- * \file plugin_cli_test.hpp
- * \brief Test fixture for irccdctl frontend (plugins support).
- */
-
-#include <irccd/daemon/plugin.hpp>
-#include <irccd/daemon/plugin_service.hpp>
-
-#include "cli_test.hpp"
-
-namespace irccd {
-
-/**
- * \file plugin_cli_test.hpp
- *
- * This class adds all plugin related transport commands to irccd.
- */
-class plugin_cli_test : public cli_test {
-public:
-    /**
-     * Default constructor.
-     */
-    plugin_cli_test();
-};
-
-} // !irccd
-
-#endif // !IRCCD_TEST_PLUGIN_CLI_TEST_HPP
--- a/libirccd-test/irccd/test/plugin_test.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/libirccd-test/irccd/test/plugin_test.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -22,18 +22,18 @@
 #include <irccd/daemon/plugin_service.hpp>
 #include <irccd/daemon/server_service.hpp>
 
-#include <irccd/js/directory_jsapi.hpp>
-#include <irccd/js/elapsed_timer_jsapi.hpp>
-#include <irccd/js/file_jsapi.hpp>
-#include <irccd/js/irccd_jsapi.hpp>
+#include <irccd/js/directory_js_api.hpp>
+#include <irccd/js/elapsed_timer_js_api.hpp>
+#include <irccd/js/file_js_api.hpp>
+#include <irccd/js/irccd_js_api.hpp>
 #include <irccd/js/js_plugin.hpp>
-#include <irccd/js/logger_jsapi.hpp>
-#include <irccd/js/plugin_jsapi.hpp>
-#include <irccd/js/server_jsapi.hpp>
-#include <irccd/js/system_jsapi.hpp>
-#include <irccd/js/timer_jsapi.hpp>
-#include <irccd/js/unicode_jsapi.hpp>
-#include <irccd/js/util_jsapi.hpp>
+#include <irccd/js/logger_js_api.hpp>
+#include <irccd/js/plugin_js_api.hpp>
+#include <irccd/js/server_js_api.hpp>
+#include <irccd/js/system_js_api.hpp>
+#include <irccd/js/timer_js_api.hpp>
+#include <irccd/js/unicode_js_api.hpp>
+#include <irccd/js/util_js_api.hpp>
 
 #include "plugin_test.hpp"
 
@@ -42,7 +42,7 @@
 plugin_test::plugin_test(std::string path)
     : server_(std::make_shared<mock_server>(service_, "test", "local"))
 {
-    plugin_ = std::make_unique<js_plugin>("test", std::move(path));
+    plugin_ = std::make_unique<js::js_plugin>("test", std::move(path));
 
     irccd_.set_log(std::make_unique<logger::silent_sink>());
     irccd_.get_log().set_verbose(false);
@@ -52,17 +52,8 @@
     server_->set_nickname("irccd");
     server_->clear();
 
-    irccd_jsapi().load(irccd_, plugin_);
-    directory_jsapi().load(irccd_, plugin_);
-    elapsed_timer_jsapi().load(irccd_, plugin_);
-    file_jsapi().load(irccd_, plugin_);
-    logger_jsapi().load(irccd_, plugin_);
-    plugin_jsapi().load(irccd_, plugin_);
-    server_jsapi().load(irccd_, plugin_);
-    system_jsapi().load(irccd_, plugin_);
-    timer_jsapi().load(irccd_, plugin_);
-    unicode_jsapi().load(irccd_, plugin_);
-    util_jsapi().load(irccd_, plugin_);
+    for (const auto& f : js::js_api::registry)
+        f()->load(irccd_, plugin_);
 
     plugin_->open();
 }
--- a/libirccd-test/irccd/test/plugin_test.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/libirccd-test/irccd/test/plugin_test.hpp	Thu Aug 09 13:07:19 2018 +0200
@@ -41,7 +41,7 @@
 protected:
     boost::asio::io_service service_;
     irccd irccd_{service_};
-    std::shared_ptr<js_plugin> plugin_;
+    std::shared_ptr<js::js_plugin> plugin_;
     std::shared_ptr<mock_server> server_;
 
 public:
--- a/libirccd-test/irccd/test/rule_cli_test.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/*
- * rule_cli_test.cpp -- test fixture for irccdctl frontend (rule support)
- *
- * Copyright (c) 2013-2018 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 <irccd/daemon/command.hpp>
-#include <irccd/daemon/transport_service.hpp>
-
-#include "rule_cli_test.hpp"
-
-namespace irccd {
-
-rule_cli_test::rule_cli_test()
-{
-    irccd_.transports().get_commands().push_back(std::make_unique<rule_add_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<rule_edit_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<rule_info_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<rule_list_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<rule_move_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<rule_remove_command>());
-}
-
-} // !irccd
--- a/libirccd-test/irccd/test/rule_cli_test.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * rule_cli_test.hpp -- test fixture for irccdctl frontend (rule support)
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_TEST_RULE_CLI_TEST_HPP
-#define IRCCD_TEST_RULE_CLI_TEST_HPP
-
-/**
- * \file rule_cli_test.hpp
- * \brief Test fixture for irccdctl frontend (rule support).
- */
-
-#include <irccd/daemon/rule.hpp>
-#include <irccd/daemon/rule_service.hpp>
-
-#include "cli_test.hpp"
-
-namespace irccd {
-
-/**
- * \file rule_cli_test.hpp
- *
- * This class adds all rule related transport commands to irccd.
- */
-class rule_cli_test : public cli_test {
-public:
-    /**
-     * Default constructor.
-     */
-    rule_cli_test();
-};
-
-} // !irccd
-
-#endif // !IRCCD_TEST_RULE_CLI_TEST_HPP
--- a/libirccd-test/irccd/test/server_cli_test.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * server_cli_test.cpp -- test fixture for irccdctl frontend (server support)
- *
- * Copyright (c) 2013-2018 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 <irccd/daemon/command.hpp>
-#include <irccd/daemon/transport_service.hpp>
-
-#include "server_cli_test.hpp"
-
-namespace irccd {
-
-server_cli_test::server_cli_test()
-    : server_(new mock_server(irccd_.get_service(), "test", "localhost"))
-{
-    irccd_.servers().add(server_);
-    server_->clear();
-
-    irccd_.transports().get_commands().push_back(std::make_unique<server_connect_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_disconnect_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_info_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_invite_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_join_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_kick_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_list_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_me_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_message_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_mode_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_nick_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_notice_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_part_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_reconnect_command>());
-    irccd_.transports().get_commands().push_back(std::make_unique<server_topic_command>());
-}
-
-} // !irccd
--- a/libirccd-test/irccd/test/server_cli_test.hpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/*
- * server_cli_test.hpp -- test fixture for irccdctl frontend (server support)
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_TEST_SERVER_CLI_TEST_HPP
-#define IRCCD_TEST_SERVER_CLI_TEST_HPP
-
-/**
- * \file server_cli_test.hpp
- * \brief Test fixture for irccdctl frontend (server support).
- */
-
-#include <irccd/daemon/server_service.hpp>
-
-#include <irccd/test/mock_server.hpp>
-
-#include "cli_test.hpp"
-
-namespace irccd {
-
-/**
- * This class adds all server related transport commands to irccd and a unique
- * mock server with id "test".
- */
-class server_cli_test : public cli_test {
-protected:
-    /**
-     * Server automatically added as "test".
-     */
-    std::shared_ptr<mock_server> server_;
-
-public:
-    /**
-     * Default constructor.
-     */
-    server_cli_test();
-};
-
-} // !irccd
-
-#endif // !IRCCD_TEST_SERVER_CLI_TEST_HPP
--- a/tests/src/irccdctl/cli-plugin-config/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-plugin-config/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,16 +19,16 @@
 #define BOOST_TEST_MODULE "irccdctl plugin-config"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/plugin_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
 #include <irccd/test/mock_plugin.hpp>
 
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class configurable_plugin_cli_test : public plugin_cli_test {
+class configurable_plugin_cli_fixture : public cli_fixture {
 public:
-    configurable_plugin_cli_test()
+    configurable_plugin_cli_fixture()
     {
         auto conf1 = std::make_unique<mock_plugin>("conf1");
         auto conf2 = std::make_unique<mock_plugin>("conf2");
@@ -43,7 +43,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(plugin_config_suite, configurable_plugin_cli_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_config_suite, configurable_plugin_cli_fixture)
 
 BOOST_AUTO_TEST_CASE(set_and_get)
 {
@@ -115,4 +115,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/irccdctl/cli-plugin-info/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-plugin-info/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,14 +19,16 @@
 #define BOOST_TEST_MODULE "irccdctl plugin-info"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/plugin_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
 #include <irccd/test/mock_plugin.hpp>
 
+using namespace irccd::test;
+
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(plugin_info_suite, plugin_cli_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_info_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(simple)
 {
--- a/tests/src/irccdctl/cli-plugin-list/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-plugin-list/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,14 +19,16 @@
 #define BOOST_TEST_MODULE "irccdctl plugin-list"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/plugin_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
 #include <irccd/test/mock_plugin.hpp>
 
+using namespace irccd::test;
+
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(plugin_list_suite, plugin_cli_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_list_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(output)
 {
--- a/tests/src/irccdctl/cli-plugin-load/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-plugin-load/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,9 +19,11 @@
 #define BOOST_TEST_MODULE "irccdctl plugin-load"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/plugin_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
 #include <irccd/test/mock_plugin.hpp>
 
+using namespace irccd::test;
+
 namespace irccd {
 
 namespace {
@@ -46,7 +48,7 @@
 
 } // !namespace
 
-BOOST_FIXTURE_TEST_SUITE(plugin_load_suite, plugin_cli_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_load_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(simple)
 {
--- a/tests/src/irccdctl/cli-plugin-reload/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-plugin-reload/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,9 +19,11 @@
 #define BOOST_TEST_MODULE "irccdctl plugin-reload"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/plugin_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
 #include <irccd/test/mock.hpp>
 
+using namespace irccd::test;
+
 namespace irccd {
 
 namespace {
@@ -44,7 +46,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(plugin_reload_suite, plugin_cli_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_reload_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(simple)
 {
--- a/tests/src/irccdctl/cli-plugin-unload/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-plugin-unload/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,9 +19,11 @@
 #define BOOST_TEST_MODULE "irccdctl plugin-unload"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/plugin_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
 #include <irccd/test/mock.hpp>
 
+using namespace irccd::test;
+
 namespace irccd {
 
 namespace {
@@ -44,7 +46,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(plugin_unload_suite, plugin_cli_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_unload_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(simple)
 {
--- a/tests/src/irccdctl/cli-rule-add/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-rule-add/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl rule-add"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/rule_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(rule_add_suite, rule_cli_test)
+BOOST_FIXTURE_TEST_SUITE(rule_add_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(all)
 {
--- a/tests/src/irccdctl/cli-rule-edit/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-rule-edit/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,15 +19,17 @@
 #define BOOST_TEST_MODULE "irccdctl rule-edit"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/rule_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-class custom_rule_cli_test : public rule_cli_test {
+class custom_cli_fixture : public cli_fixture {
 public:
-    custom_rule_cli_test()
+    custom_cli_fixture()
     {
         irccd_.rules().add({
             { "s1", "s2" },
@@ -40,7 +42,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(rule_edit_suite, custom_rule_cli_test)
+BOOST_FIXTURE_TEST_SUITE(rule_edit_suite, custom_cli_fixture)
 
 BOOST_AUTO_TEST_CASE(server)
 {
--- a/tests/src/irccdctl/cli-rule-info/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-rule-info/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl rule-info"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/rule_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(rule_info_suite, rule_cli_test)
+BOOST_FIXTURE_TEST_SUITE(rule_info_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(info)
 {
--- a/tests/src/irccdctl/cli-rule-list/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-rule-list/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl rule-list"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/rule_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(rule_list_suite, rule_cli_test)
+BOOST_FIXTURE_TEST_SUITE(rule_list_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(simple)
 {
--- a/tests/src/irccdctl/cli-rule-move/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-rule-move/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,15 +19,17 @@
 #define BOOST_TEST_MODULE "irccdctl rule-move"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/rule_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-class custom_rule_cli_test : public rule_cli_test {
+class custom_cli_fixture : public cli_fixture {
 public:
-    custom_rule_cli_test()
+    custom_cli_fixture()
     {
         irccd_.rules().add({
             { "s1" },
@@ -56,7 +58,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(rule_move_suite, custom_rule_cli_test)
+BOOST_FIXTURE_TEST_SUITE(rule_move_suite, custom_cli_fixture)
 
 BOOST_AUTO_TEST_CASE(from_0_to_1)
 {
--- a/tests/src/irccdctl/cli-rule-remove/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-rule-remove/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,15 +19,17 @@
 #define BOOST_TEST_MODULE "irccdctl rule-remove"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/rule_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-class custom_rule_cli_test : public rule_cli_test {
+class custom_cli_fixture : public cli_fixture {
 public:
-    custom_rule_cli_test()
+    custom_cli_fixture()
     {
         irccd_.rules().add({
             { "s1" },
@@ -56,7 +58,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(rule_move_suite, custom_rule_cli_test)
+BOOST_FIXTURE_TEST_SUITE(rule_move_suite, custom_cli_fixture)
 
 BOOST_AUTO_TEST_CASE(simple)
 {
--- a/tests/src/irccdctl/cli-server-disconnect/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-disconnect/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-disconnect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_disconnect_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_disconnect_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(one)
 {
--- a/tests/src/irccdctl/cli-server-info/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-info/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-info"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_info_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_info_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(output)
 {
--- a/tests/src/irccdctl/cli-server-invite/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-invite/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-invite"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_invite_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_invite_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(output)
 {
--- a/tests/src/irccdctl/cli-server-join/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-join/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-join"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_join_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_join_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
--- a/tests/src/irccdctl/cli-server-kick/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-kick/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-kick"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_kick_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_kick_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
--- a/tests/src/irccdctl/cli-server-list/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-list/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-list"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_list_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_list_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(output)
 {
--- a/tests/src/irccdctl/cli-server-me/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-me/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-me"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_me_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_me_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
--- a/tests/src/irccdctl/cli-server-message/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-message/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-message"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_message_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_message_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
--- a/tests/src/irccdctl/cli-server-mode/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-mode/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-mode"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_mode_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_mode_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(user)
 {
--- a/tests/src/irccdctl/cli-server-nick/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-nick/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-nick"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_nick_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_nick_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
--- a/tests/src/irccdctl/cli-server-notice/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-notice/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-notice"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_notice_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_notice_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
--- a/tests/src/irccdctl/cli-server-part/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-part/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-part"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_part_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_part_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
--- a/tests/src/irccdctl/cli-server-reconnect/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-reconnect/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-reconnect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_reconnect_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_reconnect_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(one)
 {
--- a/tests/src/irccdctl/cli-server-topic/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/irccdctl/cli-server-topic/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -19,13 +19,15 @@
 #define BOOST_TEST_MODULE "irccdctl server-topic"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/server_cli_test.hpp>
+#include <irccd/test/cli_fixture.hpp>
+
+using namespace irccd::test;
 
 namespace irccd {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_topic_suite, server_cli_test)
+BOOST_FIXTURE_TEST_SUITE(server_topic_suite, cli_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
--- a/tests/src/libirccd-js/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/libirccd-js/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -17,13 +17,12 @@
 #
 
 add_subdirectory(js-plugin)
-
-add_subdirectory(jsapi-elapsedtimer)
-add_subdirectory(jsapi-directory)
-add_subdirectory(jsapi-file)
-add_subdirectory(jsapi-irccd)
-add_subdirectory(jsapi-logger)
-add_subdirectory(jsapi-system)
-add_subdirectory(jsapi-timer)
-add_subdirectory(jsapi-unicode)
-add_subdirectory(jsapi-util)
+add_subdirectory(js-api-elapsedtimer)
+add_subdirectory(js-api-directory)
+add_subdirectory(js-api-file)
+add_subdirectory(js-api-irccd)
+add_subdirectory(js-api-logger)
+add_subdirectory(js-api-system)
+add_subdirectory(js-api-timer)
+add_subdirectory(js-api-unicode)
+add_subdirectory(js-api-util)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-directory/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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 js-api-directory
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-directory/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,60 @@
+/*
+ * main.cpp -- test Irccd.Directory API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#define BOOST_TEST_MODULE "Directory Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/test/js_fixture.hpp>
+
+namespace irccd::test {
+
+using namespace irccd::js;
+
+namespace {
+
+class directory_js_fixture : public js_fixture {
+public:
+    directory_js_fixture()
+    {
+        duk::push(plugin_->get_context(), CMAKE_SOURCE_DIR);
+        duk_put_global_string(plugin_->get_context(), "CMAKE_SOURCE_DIR");
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(directory_js_api_suite, directory_js_fixture)
+
+BOOST_AUTO_TEST_CASE(constructor)
+{
+    const std::string script(
+        "d = new Irccd.Directory(CMAKE_SOURCE_DIR + \"/tests/root\");"
+        "p = d.path;"
+        "l = d.entries.length;"
+    );
+
+    if (duk_peval_string(plugin_->get_context(), script.c_str()) != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    duk_get_global_string(plugin_->get_context(), "l");
+    BOOST_TEST(duk_get_int(plugin_->get_context(), -1) == 3);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !namespace
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-elapsedtimer/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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 js-api-elapsedtimer
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-elapsedtimer/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,53 @@
+/*
+ * main.cpp -- test Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#define BOOST_TEST_MODULE "ElapsedTimer Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <thread>
+
+#include <irccd/test/js_fixture.hpp>
+
+using namespace std::chrono_literals;
+
+namespace irccd::test {
+
+namespace {
+
+BOOST_FIXTURE_TEST_SUITE(elapsed_timer_js_api_suite, js_fixture)
+
+BOOST_AUTO_TEST_CASE(standard)
+{
+    if (duk_peval_string(plugin_->get_context(), "timer = new Irccd.ElapsedTimer();") != 0)
+        throw js::duk::get_stack(plugin_->get_context(), -1);
+
+    std::this_thread::sleep_for(300ms);
+
+    if (duk_peval_string(plugin_->get_context(), "result = timer.elapsed();") != 0)
+        throw js::duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_REQUIRE(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_REQUIRE_GE(duk_get_int(plugin_->get_context(), -1), 250);
+    BOOST_REQUIRE_LE(duk_get_int(plugin_->get_context(), -1), 350);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !namespace
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-file/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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 js-api-file
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-file/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,320 @@
+/*
+ * main.cpp -- test Irccd.File API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#define BOOST_TEST_MODULE "File Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <fstream>
+
+#include <irccd/test/js_fixture.hpp>
+
+using namespace irccd::js;
+
+namespace irccd::test {
+
+namespace {
+
+class file_js_fixture : public js_fixture {
+public:
+    file_js_fixture()
+    {
+        duk::push(plugin_->get_context(), CMAKE_SOURCE_DIR);
+        duk_put_global_string(plugin_->get_context(), "CMAKE_SOURCE_DIR");
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(file_js_api_suite, file_js_fixture)
+
+BOOST_AUTO_TEST_CASE(function_basename)
+{
+    if (duk_peval_string(plugin_->get_context(), "result = Irccd.File.basename('/usr/local/etc/irccd.conf');"))
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST("irccd.conf" == duk_get_string(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(function_dirname)
+{
+    if (duk_peval_string(plugin_->get_context(), "result = Irccd.File.dirname('/usr/local/etc/irccd.conf');"))
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST("/usr/local/etc" == duk_get_string(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(function_exists)
+{
+    if (duk_peval_string(plugin_->get_context(), "result = Irccd.File.exists(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt')"))
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(function_exists2)
+{
+    if (duk_peval_string(plugin_->get_context(), "result = Irccd.File.exists('file_which_does_not_exist.txt')"))
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(!duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(function_remove)
+{
+    // First create a dummy file
+    std::ofstream("test-js-fs.remove");
+
+    if (duk_peval_string(plugin_->get_context(), "Irccd.File.remove('test-js-fs.remove');") != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    std::ifstream in("test-js-fs.remove");
+
+    BOOST_TEST(!in.is_open());
+}
+
+BOOST_AUTO_TEST_CASE(method_basename)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "result = f.basename();"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST("file-1.txt" == duk_get_string(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_basename_closed)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "result = f.basename();"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST("file-1.txt" == duk_get_string(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_dirname)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "result = f.dirname();"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(CMAKE_SOURCE_DIR "/tests/root" == duk_get_string(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_dirname_closed)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "result = f.dirname();"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(CMAKE_SOURCE_DIR "/tests/root" == duk_get_string(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_lines)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "result = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r').lines();"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    std::vector<std::string> expected{"a", "b", "c"};
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(expected == duk::get<std::vector<std::string>>(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek1)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.seek(Irccd.File.SeekSet, 6);"
+        "result = f.read(1);"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(".", duk::get<std::string>(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek1_closed)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "f.seek(Irccd.File.SeekSet, 4);"
+        "result = f.read(1);"
+        "result = typeof (result) === \"undefined\";"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek2)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.seek(Irccd.File.SeekSet, 2);"
+        "f.seek(Irccd.File.SeekCur, 4);"
+        "result = f.read(1);"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST("." == duk::get<std::string>(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek2c_losed)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "f.seek(Irccd.File.SeekSet, 2);"
+        "f.seek(Irccd.File.SeekCur, 2);"
+        "result = f.read(1);"
+        "result = typeof (result) === \"undefined\";"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek3)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.seek(Irccd.File.SeekEnd, -2);"
+        "result = f.read(1);"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST("t" == duk_get_string(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek3_closed)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "f.seek(Irccd.File.SeekEnd, -2);"
+        "result = f.read(1);"
+        "result = typeof (result) === \"undefined\";"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_read1)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "result = f.read();"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST("file-1.txt\n" == duk_get_string(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_readline)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "result = [];"
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
+        "for (var s; s = f.readline(); ) {"
+        "  result.push(s);"
+        "}"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    std::vector<std::string> expected{"a", "b", "c"};
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(expected == duk::get<std::vector<std::string>>(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_readline_closed)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "result = [];"
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
+        "f.close();"
+        "for (var s; s = f.readline(); ) {"
+        "  result.push(s);"
+        "}"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    std::vector<std::string> expected;
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(expected == duk::get<std::vector<std::string>>(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !namespace
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-irccd/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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 js-api-irccd
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-irccd/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,118 @@
+/*
+ * main.cpp -- test Irccd API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#define BOOST_TEST_MODULE "Irccd Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/test/js_fixture.hpp>
+
+using namespace irccd::js;
+
+namespace irccd::test {
+
+namespace {
+
+BOOST_FIXTURE_TEST_SUITE(irccd_js_api_suite, js_fixture)
+
+BOOST_AUTO_TEST_CASE(version)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "major = Irccd.version.major;"
+        "minor = Irccd.version.minor;"
+        "patch = Irccd.version.patch;"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "major"));
+    BOOST_TEST(IRCCD_VERSION_MAJOR == duk_get_int(plugin_->get_context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "minor"));
+    BOOST_TEST(IRCCD_VERSION_MINOR == duk_get_int(plugin_->get_context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "patch"));
+    BOOST_TEST(IRCCD_VERSION_PATCH == duk_get_int(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(from_javascript)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "try {"
+        "  throw new Irccd.SystemError(1, 'test');"
+        "} catch (e) {"
+        "  errno = e.errno;"
+        "  name = e.name;"
+        "  message = e.message;"
+        "  v1 = (e instanceof Error);"
+        "  v2 = (e instanceof Irccd.SystemError);"
+        "}"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "errno"));
+    BOOST_TEST(1 == duk_get_int(plugin_->get_context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
+    BOOST_TEST("SystemError" == duk_get_string(plugin_->get_context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "message"));
+    BOOST_TEST("test" == duk_get_string(plugin_->get_context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "v1"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "v2"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(from_native)
+{
+    duk_push_c_function(plugin_->get_context(), [] (duk_context *ctx) -> duk_ret_t {
+        duk::raise(ctx, std::system_error(make_error_code(std::errc::invalid_argument)));
+
+        return 0;
+    }, 0);
+
+    duk_put_global_string(plugin_->get_context(), "f");
+
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "try {"
+        "  f();"
+        "} catch (e) {"
+        "  errno = e.errno;"
+        "  name = e.name;"
+        "  v1 = (e instanceof Error);"
+        "  v2 = (e instanceof Irccd.SystemError);"
+        "}"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "errno"));
+    BOOST_TEST(EINVAL == duk_get_int(plugin_->get_context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
+    BOOST_TEST("SystemError" == duk_get_string(plugin_->get_context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "v1"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "v2"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !namespace
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-logger/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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 js-api-logger
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-logger/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,105 @@
+/*
+ * main.cpp -- test Irccd.Logger API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#define BOOST_TEST_MODULE "Logger Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/daemon/logger.hpp>
+
+#include <irccd/test/js_fixture.hpp>
+
+namespace irccd::test {
+
+using namespace irccd::js;
+
+namespace {
+
+class logger_fixture : public js_fixture {
+protected:
+    std::string line_info;
+    std::string line_warning;
+    std::string line_debug;
+
+    class sample_sink : public logger::sink {
+    private:
+        logger_fixture& test_;
+
+    public:
+        sample_sink(logger_fixture& test) noexcept
+            : test_(test)
+        {
+        }
+
+        void write_info(const std::string& line) override
+        {
+            test_.line_info = line;
+        }
+
+        void write_warning(const std::string& line) override
+        {
+            test_.line_warning = line;
+        }
+
+        void write_debug(const std::string& line) override
+        {
+            test_.line_debug = line;
+        }
+    };
+
+    logger_fixture()
+    {
+        irccd_.set_log(std::make_unique<sample_sink>(*this));
+        irccd_.get_log().set_verbose(true);
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(logger_js_api_suite, logger_fixture)
+
+BOOST_AUTO_TEST_CASE(info)
+{
+    if (duk_peval_string(plugin_->get_context(), "Irccd.Logger.info(\"hello!\");") != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST("plugin test: hello!" == line_info);
+}
+
+BOOST_AUTO_TEST_CASE(warning)
+{
+    if (duk_peval_string(plugin_->get_context(), "Irccd.Logger.warning(\"FAIL!\");") != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST("plugin test: FAIL!" == line_warning);
+}
+
+#if !defined(NDEBUG)
+
+BOOST_AUTO_TEST_CASE(debug)
+{
+    if (duk_peval_string(plugin_->get_context(), "Irccd.Logger.debug(\"starting\");") != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST("plugin test: starting" == line_debug);
+}
+
+#endif
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !namespace
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-system/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,24 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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 js-api-system
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+    FLAGS IRCCD_EXECUTABLE=\"$<TARGET_FILE:irccd>\"
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-system/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,64 @@
+/*
+ * main.cpp -- test Irccd.System API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#define BOOST_TEST_MODULE "System Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/system.hpp>
+
+#include <irccd/test/js_fixture.hpp>
+
+namespace irccd::test {
+
+using namespace irccd::js;
+
+namespace {
+
+BOOST_FIXTURE_TEST_SUITE(system_js_api_suite, js_fixture)
+
+BOOST_AUTO_TEST_CASE(home)
+{
+    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.System.home();");
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(),"result"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == sys::home());
+}
+
+#if defined(HAVE_POPEN)
+
+BOOST_AUTO_TEST_CASE(popen)
+{
+    auto ret = duk_peval_string(plugin_->get_context(),
+        "f = Irccd.System.popen(\"" IRCCD_EXECUTABLE " --version\", \"r\");"
+        "r = f.readline();"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "r"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == IRCCD_VERSION);
+}
+
+#endif
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !namespace
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-timer/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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 js-api-timer
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-timer/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,85 @@
+/*
+ * main.cpp -- test Irccd.Timer API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#define BOOST_TEST_MODULE "Timer Javascript API"
+#include <boost/test/unit_test.hpp>
+#include <boost/timer/timer.hpp>
+
+#include <irccd/test/js_fixture.hpp>
+
+namespace irccd::test {
+
+namespace {
+
+class js_timer_fixture : public js_fixture {
+public:
+    js_timer_fixture()
+        : js_fixture(CMAKE_CURRENT_SOURCE_DIR "/timer.js")
+    {
+    }
+
+    void set_type(const std::string& name)
+    {
+        duk_get_global_string(plugin_->get_context(), "Irccd");
+        duk_get_prop_string(plugin_->get_context(), -1, "Timer");
+        duk_get_prop_string(plugin_->get_context(), -1, name.c_str());
+        duk_put_global_string(plugin_->get_context(), "type");
+        duk_pop_n(plugin_->get_context(), 2);
+
+        plugin_->open();
+        plugin_->handle_load(irccd_);
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(js_timer_api_suite, js_timer_fixture)
+
+BOOST_AUTO_TEST_CASE(single)
+{
+    boost::timer::cpu_timer timer;
+
+    set_type("Single");
+
+    while (timer.elapsed().wall / 1000000LL < 3000) {
+        ctx_.reset();
+        ctx_.poll();
+    }
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "count"));
+    BOOST_TEST(duk_get_int(plugin_->get_context(), -1) == 1);
+}
+
+BOOST_AUTO_TEST_CASE(repeat)
+{
+    boost::timer::cpu_timer timer;
+
+    set_type("Repeat");
+
+    while (timer.elapsed().wall / 1000000LL < 3000) {
+        ctx_.reset();
+        ctx_.poll();
+    }
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "count"));
+    BOOST_TEST(duk_get_int(plugin_->get_context(), -1) >= 5);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !namespace
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-timer/timer.js	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,13 @@
+count = 0;
+
+function onLoad()
+{
+    if (typeof (type) === "undefined")
+        throw Error("global timer type not defined");
+
+    t = new Irccd.Timer(type, 50, function () {
+        count += 1;
+    });
+
+    t.start();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-unicode/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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 js-api-unicode
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-unicode/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,71 @@
+/*
+ * main.cpp -- test Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+/*
+ * /!\ Be sure that this file is kept saved in UTF-8 /!\
+ */
+
+#define BOOST_TEST_MODULE "Unicode Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/test/js_fixture.hpp>
+
+namespace irccd::test {
+
+namespace {
+
+BOOST_FIXTURE_TEST_SUITE(unicode_js_api_suite, js_fixture)
+
+BOOST_AUTO_TEST_CASE(is_letter)
+{
+    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isLetter(String('é').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+
+    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isLetter(String('€').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(!duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(is_lower)
+{
+    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isLower(String('é').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+
+    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isLower(String('É').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(!duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(is_upper)
+{
+    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isUpper(String('É').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
+
+    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isUpper(String('é').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(!duk_get_boolean(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !namespace
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-util/CMakeLists.txt	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 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 js-api-util
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-js/js-api-util/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -0,0 +1,264 @@
+/*
+ * main.cpp -- test Irccd.Util API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#define BOOST_TEST_MODULE "Unicode Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/test/js_fixture.hpp>
+
+namespace irccd::test {
+
+using namespace irccd::js;
+
+namespace {
+
+BOOST_FIXTURE_TEST_SUITE(util_js_api_suite, js_fixture)
+
+/*
+ * Irccd.Util misc.
+ * ------------------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_CASE(format_simple)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "result = Irccd.Util.format(\"#{target}\", { target: \"markand\" })"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "markand");
+}
+
+BOOST_AUTO_TEST_CASE(splituser)
+{
+    if (duk_peval_string(plugin_->get_context(), "result = Irccd.Util.splituser(\"user!~user@hyper/super/host\");") != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "user");
+}
+
+BOOST_AUTO_TEST_CASE(splithost)
+{
+    if (duk_peval_string(plugin_->get_context(), "result = Irccd.Util.splithost(\"user!~user@hyper/super/host\");") != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "~user@hyper/super/host");
+}
+
+/*
+ * Irccd.Util.cut.
+ * ------------------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_CASE(cut_string_simple)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "lines = Irccd.Util.cut('hello world');\n"
+        "line0 = lines[0];\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_double)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "lines = Irccd.Util.cut('hello world', 5);\n"
+        "line0 = lines[0];\n"
+        "line1 = lines[1];\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line1"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_dirty)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "lines = Irccd.Util.cut('     hello    world     ', 5);\n"
+        "line0 = lines[0];\n"
+        "line1 = lines[1];\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line1"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_too_much_lines)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "lines = Irccd.Util.cut('abc def ghi jkl', 3, 3);\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "lines"));
+    BOOST_TEST(duk_is_undefined(plugin_->get_context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_token_too_big)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "try {\n"
+        "  lines = Irccd.Util.cut('hello world', 3);\n"
+        "} catch (e) {\n"
+        "  name = e.name;\n"
+        "  message = e.message;\n"
+        "}\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "RangeError");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "message"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "word 'hello' could not fit in maxc limit (3)");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_negative_maxc)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "try {\n"
+        "  lines = Irccd.Util.cut('hello world', -3);\n"
+        "} catch (e) {\n"
+        "  name = e.name;\n"
+        "  message = e.message;\n"
+        "}\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "RangeError");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "message"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "argument 1 (maxc) must be positive");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_negative_maxl)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "try {\n"
+        "  lines = Irccd.Util.cut('hello world', undefined, -1);\n"
+        "} catch (e) {\n"
+        "  name = e.name;\n"
+        "  message = e.message;\n"
+        "}\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "RangeError");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "message"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "argument 2 (maxl) must be positive");
+}
+
+BOOST_AUTO_TEST_CASE(cut_array_simple)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "lines = Irccd.Util.cut([ 'hello', 'world' ]);\n"
+        "line0 = lines[0];\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_array_double)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "lines = Irccd.Util.cut([ 'hello', 'world' ], 5);\n"
+        "line0 = lines[0];\n"
+        "line1 = lines[1];\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line1"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_array_dirty)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "lines = Irccd.Util.cut([ '   ', ' hello  ', '  world ', '    '], 5);\n"
+        "line0 = lines[0];\n"
+        "line1 = lines[1];\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello");
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line1"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_invalid_data)
+{
+    const auto ret = duk_peval_string(plugin_->get_context(),
+        "try {\n"
+        "  lines = Irccd.Util.cut(123);\n"
+        "} catch (e) {\n"
+        "  name = e.name;\n"
+        "  message = e.message;\n"
+        "}\n"
+    );
+
+    if (ret != 0)
+        throw duk::get_stack(plugin_->get_context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
+    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "TypeError");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !namespace
+
+} // !irccd::test
--- a/tests/src/libirccd-js/js-plugin/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ b/tests/src/libirccd-js/js-plugin/main.cpp	Thu Aug 09 13:07:19 2018 +0200
@@ -17,38 +17,35 @@
  */
 
 #define BOOST_TEST_MODULE "Javascript plugin object"
-#include <boost/asio.hpp>
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/plugin_service.hpp>
 
-#include <irccd/js/irccd_jsapi.hpp>
+#include <irccd/js/js_api.hpp>
 #include <irccd/js/js_plugin.hpp>
-#include <irccd/js/plugin_jsapi.hpp>
 
-namespace irccd {
+#include <irccd/test/irccd_fixture.hpp>
+
+namespace irccd::test {
 
 namespace {
 
-class js_plugin_test {
+class js_plugin_fixture : public irccd_fixture {
 protected:
-    boost::asio::io_service service_;
-    irccd irccd_{service_};
-    std::shared_ptr<js_plugin> plugin_;
+    std::shared_ptr<js::js_plugin> plugin_;
 
-    void load(std::string path)
+    void load(const std::string& path)
     {
-        plugin_ = std::make_unique<js_plugin>("test", std::move(path));
+        plugin_ = std::make_unique<js::js_plugin>("test", path);
 
-        irccd_jsapi().load(irccd_, plugin_);
-        plugin_jsapi().load(irccd_, plugin_);
+        for (const auto& f : js::js_api::registry)
+            f()->load(irccd_, plugin_);
 
         plugin_->open();
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(js_plugin_test_suite, js_plugin_test)
+BOOST_FIXTURE_TEST_SUITE(js_plugin_suite, js_plugin_fixture)
 
 BOOST_AUTO_TEST_CASE(assign)
 {
@@ -97,20 +94,18 @@
 
 BOOST_AUTO_TEST_SUITE_END()
 
-class js_plugin_loader_test {
+class js_plugin_loader_fixture : public irccd_fixture {
 protected:
-    boost::asio::io_service service_;
-    irccd irccd_{service_};
     std::shared_ptr<plugin> plugin_;
 
-    js_plugin_loader_test()
+    js_plugin_loader_fixture()
     {
         irccd_.set_config(config(CMAKE_CURRENT_SOURCE_DIR "/irccd.conf"));
 
-        auto loader = std::make_unique<js_plugin_loader>(irccd_);
+        auto loader = std::make_unique<js::js_plugin_loader>(irccd_);
 
-        loader->get_modules().push_back(std::make_unique<irccd_jsapi>());
-        loader->get_modules().push_back(std::make_unique<plugin_jsapi>());
+        for (const auto& f : js::js_api::registry)
+            loader->get_modules().push_back(f());
 
         irccd_.plugins().add_loader(std::move(loader));
     }
@@ -122,7 +117,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(js_plugin_loader_test_suite, js_plugin_loader_test)
+BOOST_FIXTURE_TEST_SUITE(js_plugin_loader_test_suite, js_plugin_loader_fixture)
 
 BOOST_AUTO_TEST_CASE(assign)
 {
@@ -155,4 +150,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-directory/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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 jsapi-directory
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/src/libirccd-js/jsapi-directory/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-/*
- * main.cpp -- test Irccd.Directory API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#define BOOST_TEST_MODULE "Directory Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/test/javascript_fixture.hpp>
-
-namespace irccd::test {
-
-namespace {
-
-class directory_javascript_fixture : public javascript_fixture {
-public:
-    directory_javascript_fixture()
-    {
-        dukx_push(plugin_->get_context(), CMAKE_SOURCE_DIR);
-        duk_put_global_string(plugin_->get_context(), "CMAKE_SOURCE_DIR");
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(directory_jsapi_suite, directory_javascript_fixture)
-
-BOOST_AUTO_TEST_CASE(constructor)
-{
-    const std::string script(
-        "d = new Irccd.Directory(CMAKE_SOURCE_DIR + \"/tests/root\");"
-        "p = d.path;"
-        "l = d.entries.length;"
-    );
-
-    if (duk_peval_string(plugin_->get_context(), script.c_str()) != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    duk_get_global_string(plugin_->get_context(), "l");
-    BOOST_TEST(duk_get_int(plugin_->get_context(), -1) == 3);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !namespace
-
-} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-elapsedtimer/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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 jsapi-elapsedtimer
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/src/libirccd-js/jsapi-elapsedtimer/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/*
- * main.cpp -- test Irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#define BOOST_TEST_MODULE "ElapsedTimer Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <thread>
-
-#include <irccd/test/javascript_fixture.hpp>
-
-using namespace std::chrono_literals;
-
-namespace irccd::test {
-
-namespace {
-
-BOOST_FIXTURE_TEST_SUITE(elapsed_timer_jsapi_suite, javascript_fixture)
-
-BOOST_AUTO_TEST_CASE(standard)
-{
-    if (duk_peval_string(plugin_->get_context(), "timer = new Irccd.ElapsedTimer();") != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    std::this_thread::sleep_for(300ms);
-
-    if (duk_peval_string(plugin_->get_context(), "result = timer.elapsed();") != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_REQUIRE(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_REQUIRE_GE(duk_get_int(plugin_->get_context(), -1), 250);
-    BOOST_REQUIRE_LE(duk_get_int(plugin_->get_context(), -1), 350);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !namespace
-
-} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-file/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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 jsapi-file
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/src/libirccd-js/jsapi-file/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,320 +0,0 @@
-/*
- * main.cpp -- test Irccd.File API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#define BOOST_TEST_MODULE "File Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <fstream>
-
-#include <irccd/js/duktape_vector.hpp>
-
-#include <irccd/test/javascript_fixture.hpp>
-
-namespace irccd::test {
-
-namespace {
-
-class file_javascript_fixture : public javascript_fixture {
-public:
-    file_javascript_fixture()
-    {
-        dukx_push(plugin_->get_context(), CMAKE_SOURCE_DIR);
-        duk_put_global_string(plugin_->get_context(), "CMAKE_SOURCE_DIR");
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(file_jsapi_suite, file_javascript_fixture)
-
-BOOST_AUTO_TEST_CASE(function_basename)
-{
-    if (duk_peval_string(plugin_->get_context(), "result = Irccd.File.basename('/usr/local/etc/irccd.conf');"))
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST("irccd.conf" == duk_get_string(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(function_dirname)
-{
-    if (duk_peval_string(plugin_->get_context(), "result = Irccd.File.dirname('/usr/local/etc/irccd.conf');"))
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST("/usr/local/etc" == duk_get_string(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(function_exists)
-{
-    if (duk_peval_string(plugin_->get_context(), "result = Irccd.File.exists(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt')"))
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(function_exists2)
-{
-    if (duk_peval_string(plugin_->get_context(), "result = Irccd.File.exists('file_which_does_not_exist.txt')"))
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(!duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(function_remove)
-{
-    // First create a dummy file
-    std::ofstream("test-js-fs.remove");
-
-    if (duk_peval_string(plugin_->get_context(), "Irccd.File.remove('test-js-fs.remove');") != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    std::ifstream in("test-js-fs.remove");
-
-    BOOST_TEST(!in.is_open());
-}
-
-BOOST_AUTO_TEST_CASE(method_basename)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "result = f.basename();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST("file-1.txt" == duk_get_string(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_basename_closed)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "result = f.basename();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST("file-1.txt" == duk_get_string(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_dirname)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "result = f.dirname();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(CMAKE_SOURCE_DIR "/tests/root" == duk_get_string(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_dirname_closed)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "result = f.dirname();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(CMAKE_SOURCE_DIR "/tests/root" == duk_get_string(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_lines)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "result = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r').lines();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    std::vector<std::string> expected{"a", "b", "c"};
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(expected == dukx_get<std::vector<std::string>>(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek1)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.seek(Irccd.File.SeekSet, 6);"
-        "result = f.read(1);"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(".", dukx_get<std::string>(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek1_closed)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "f.seek(Irccd.File.SeekSet, 4);"
-        "result = f.read(1);"
-        "result = typeof (result) === \"undefined\";"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek2)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.seek(Irccd.File.SeekSet, 2);"
-        "f.seek(Irccd.File.SeekCur, 4);"
-        "result = f.read(1);"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST("." == dukx_get<std::string>(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek2c_losed)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "f.seek(Irccd.File.SeekSet, 2);"
-        "f.seek(Irccd.File.SeekCur, 2);"
-        "result = f.read(1);"
-        "result = typeof (result) === \"undefined\";"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek3)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.seek(Irccd.File.SeekEnd, -2);"
-        "result = f.read(1);"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST("t" == duk_get_string(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek3_closed)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "f.seek(Irccd.File.SeekEnd, -2);"
-        "result = f.read(1);"
-        "result = typeof (result) === \"undefined\";"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_read1)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "result = f.read();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST("file-1.txt\n" == duk_get_string(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_readline)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "result = [];"
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
-        "for (var s; s = f.readline(); ) {"
-        "  result.push(s);"
-        "}"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    std::vector<std::string> expected{"a", "b", "c"};
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(expected == dukx_get<std::vector<std::string>>(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_readline_closed)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "result = [];"
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
-        "f.close();"
-        "for (var s; s = f.readline(); ) {"
-        "  result.push(s);"
-        "}"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    std::vector<std::string> expected;
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(expected == dukx_get<std::vector<std::string>>(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !namespace
-
-} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-irccd/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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 jsapi-irccd
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/src/libirccd-js/jsapi-irccd/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/*
- * main.cpp -- test Irccd API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#define BOOST_TEST_MODULE "Irccd Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/test/javascript_fixture.hpp>
-
-namespace irccd::test {
-
-namespace {
-
-BOOST_FIXTURE_TEST_SUITE(irccd_jsapi_suite, javascript_fixture)
-
-BOOST_AUTO_TEST_CASE(version)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "major = Irccd.version.major;"
-        "minor = Irccd.version.minor;"
-        "patch = Irccd.version.patch;"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "major"));
-    BOOST_TEST(IRCCD_VERSION_MAJOR == duk_get_int(plugin_->get_context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "minor"));
-    BOOST_TEST(IRCCD_VERSION_MINOR == duk_get_int(plugin_->get_context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "patch"));
-    BOOST_TEST(IRCCD_VERSION_PATCH == duk_get_int(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(from_javascript)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "try {"
-        "  throw new Irccd.SystemError(1, 'test');"
-        "} catch (e) {"
-        "  errno = e.errno;"
-        "  name = e.name;"
-        "  message = e.message;"
-        "  v1 = (e instanceof Error);"
-        "  v2 = (e instanceof Irccd.SystemError);"
-        "}"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "errno"));
-    BOOST_TEST(1 == duk_get_int(plugin_->get_context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
-    BOOST_TEST("SystemError" == duk_get_string(plugin_->get_context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "message"));
-    BOOST_TEST("test" == duk_get_string(plugin_->get_context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "v1"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "v2"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(from_native)
-{
-    duk_push_c_function(plugin_->get_context(), [] (duk_context *ctx) -> duk_ret_t {
-        dukx_throw(ctx, std::system_error(make_error_code(std::errc::invalid_argument)));
-
-        return 0;
-    }, 0);
-
-    duk_put_global_string(plugin_->get_context(), "f");
-
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "try {"
-        "  f();"
-        "} catch (e) {"
-        "  errno = e.errno;"
-        "  name = e.name;"
-        "  v1 = (e instanceof Error);"
-        "  v2 = (e instanceof Irccd.SystemError);"
-        "}"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "errno"));
-    BOOST_TEST(EINVAL == duk_get_int(plugin_->get_context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
-    BOOST_TEST("SystemError" == duk_get_string(plugin_->get_context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "v1"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "v2"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !namespace
-
-} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-logger/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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 jsapi-logger
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/src/libirccd-js/jsapi-logger/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-/*
- * main.cpp -- test Irccd.Logger API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#define BOOST_TEST_MODULE "Logger Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/daemon/logger.hpp>
-
-#include <irccd/test/javascript_fixture.hpp>
-
-namespace irccd::test {
-
-namespace {
-
-class logger_fixture : public javascript_fixture {
-protected:
-    std::string line_info;
-    std::string line_warning;
-    std::string line_debug;
-
-    class sample_sink : public logger::sink {
-    private:
-        logger_fixture& test_;
-
-    public:
-        sample_sink(logger_fixture& test) noexcept
-            : test_(test)
-        {
-        }
-
-        void write_info(const std::string& line) override
-        {
-            test_.line_info = line;
-        }
-
-        void write_warning(const std::string& line) override
-        {
-            test_.line_warning = line;
-        }
-
-        void write_debug(const std::string& line) override
-        {
-            test_.line_debug = line;
-        }
-    };
-
-    logger_fixture()
-    {
-        irccd_.set_log(std::make_unique<sample_sink>(*this));
-        irccd_.get_log().set_verbose(true);
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(logger_jsapi_suite, logger_fixture)
-
-BOOST_AUTO_TEST_CASE(info)
-{
-    if (duk_peval_string(plugin_->get_context(), "Irccd.Logger.info(\"hello!\");") != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST("plugin test: hello!" == line_info);
-}
-
-BOOST_AUTO_TEST_CASE(warning)
-{
-    if (duk_peval_string(plugin_->get_context(), "Irccd.Logger.warning(\"FAIL!\");") != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST("plugin test: FAIL!" == line_warning);
-}
-
-#if !defined(NDEBUG)
-
-BOOST_AUTO_TEST_CASE(debug)
-{
-    if (duk_peval_string(plugin_->get_context(), "Irccd.Logger.debug(\"starting\");") != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST("plugin test: starting" == line_debug);
-}
-
-#endif
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !namespace
-
-} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-system/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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 jsapi-system
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-    FLAGS IRCCD_EXECUTABLE=\"$<TARGET_FILE:irccd>\"
-)
--- a/tests/src/libirccd-js/jsapi-system/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/*
- * main.cpp -- test Irccd.System API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#define BOOST_TEST_MODULE "System Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/system.hpp>
-
-#include <irccd/test/javascript_fixture.hpp>
-
-namespace irccd::test {
-
-namespace {
-
-BOOST_FIXTURE_TEST_SUITE(system_jsapi_suite, javascript_fixture)
-
-BOOST_AUTO_TEST_CASE(home)
-{
-    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.System.home();");
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(),"result"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == sys::home());
-}
-
-#if defined(HAVE_POPEN)
-
-BOOST_AUTO_TEST_CASE(popen)
-{
-    auto ret = duk_peval_string(plugin_->get_context(),
-        "f = Irccd.System.popen(\"" IRCCD_EXECUTABLE " --version\", \"r\");"
-        "r = f.readline();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "r"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == IRCCD_VERSION);
-}
-
-#endif
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !namespace
-
-} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-timer/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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 jsapi-timer
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/src/libirccd-js/jsapi-timer/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-/*
- * main.cpp -- test Irccd.Timer API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#define BOOST_TEST_MODULE "Timer Javascript API"
-#include <boost/test/unit_test.hpp>
-#include <boost/timer/timer.hpp>
-
-#include <irccd/test/javascript_fixture.hpp>
-
-namespace irccd::test {
-
-namespace {
-
-class javascript_timer_fixture : public javascript_fixture {
-public:
-    javascript_timer_fixture()
-        : javascript_fixture(CMAKE_CURRENT_SOURCE_DIR "/timer.js")
-    {
-    }
-
-    void set_type(const std::string& name)
-    {
-        duk_get_global_string(plugin_->get_context(), "Irccd");
-        duk_get_prop_string(plugin_->get_context(), -1, "Timer");
-        duk_get_prop_string(plugin_->get_context(), -1, name.c_str());
-        duk_put_global_string(plugin_->get_context(), "type");
-        duk_pop_n(plugin_->get_context(), 2);
-
-        plugin_->open();
-        plugin_->handle_load(irccd_);
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(js_timer_test_suite, javascript_timer_fixture)
-
-BOOST_AUTO_TEST_CASE(single)
-{
-    boost::timer::cpu_timer timer;
-
-    set_type("Single");
-
-    while (timer.elapsed().wall / 1000000LL < 3000) {
-        ctx_.reset();
-        ctx_.poll();
-    }
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "count"));
-    BOOST_TEST(duk_get_int(plugin_->get_context(), -1) == 1);
-}
-
-BOOST_AUTO_TEST_CASE(repeat)
-{
-    boost::timer::cpu_timer timer;
-
-    set_type("Repeat");
-
-    while (timer.elapsed().wall / 1000000LL < 3000) {
-        ctx_.reset();
-        ctx_.poll();
-    }
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "count"));
-    BOOST_TEST(duk_get_int(plugin_->get_context(), -1) >= 5);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !namespace
-
-} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-timer/timer.js	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-count = 0;
-
-function onLoad()
-{
-    if (typeof (type) === "undefined")
-        throw Error("global timer type not defined");
-
-    t = new Irccd.Timer(type, 50, function () {
-        count += 1;
-    });
-
-    t.start();
-}
--- a/tests/src/libirccd-js/jsapi-unicode/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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 jsapi-unicode
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/src/libirccd-js/jsapi-unicode/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/*
- * main.cpp -- test Irccd.Unicode API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-/*
- * /!\ Be sure that this file is kept saved in UTF-8 /!\
- */
-
-#define BOOST_TEST_MODULE "Unicode Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/test/javascript_fixture.hpp>
-
-namespace irccd::test {
-
-namespace {
-
-BOOST_FIXTURE_TEST_SUITE(unicode_jsapi_suite, javascript_fixture)
-
-BOOST_AUTO_TEST_CASE(is_letter)
-{
-    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isLetter(String('é').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-
-    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isLetter(String('€').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(!duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(is_lower)
-{
-    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isLower(String('é').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-
-    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isLower(String('É').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(!duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(is_upper)
-{
-    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isUpper(String('É').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->get_context(), -1));
-
-    duk_peval_string_noresult(plugin_->get_context(), "result = Irccd.Unicode.isUpper(String('é').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(!duk_get_boolean(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !namespace
-
-} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-util/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 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 jsapi-util
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/src/libirccd-js/jsapi-util/main.cpp	Mon Aug 06 21:27:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,262 +0,0 @@
-/*
- * main.cpp -- test Irccd.Util API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#define BOOST_TEST_MODULE "Unicode Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/test/javascript_fixture.hpp>
-
-namespace irccd::test {
-
-namespace {
-
-BOOST_FIXTURE_TEST_SUITE(util_jsapi_suite, javascript_fixture)
-
-/*
- * Irccd.Util misc.
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_CASE(format_simple)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "result = Irccd.Util.format(\"#{target}\", { target: \"markand\" })"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "markand");
-}
-
-BOOST_AUTO_TEST_CASE(splituser)
-{
-    if (duk_peval_string(plugin_->get_context(), "result = Irccd.Util.splituser(\"user!~user@hyper/super/host\");") != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "user");
-}
-
-BOOST_AUTO_TEST_CASE(splithost)
-{
-    if (duk_peval_string(plugin_->get_context(), "result = Irccd.Util.splithost(\"user!~user@hyper/super/host\");") != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "result"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "~user@hyper/super/host");
-}
-
-/*
- * Irccd.Util.cut.
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_CASE(cut_string_simple)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "lines = Irccd.Util.cut('hello world');\n"
-        "line0 = lines[0];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_double)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "lines = Irccd.Util.cut('hello world', 5);\n"
-        "line0 = lines[0];\n"
-        "line1 = lines[1];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line1"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_dirty)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "lines = Irccd.Util.cut('     hello    world     ', 5);\n"
-        "line0 = lines[0];\n"
-        "line1 = lines[1];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line1"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_too_much_lines)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "lines = Irccd.Util.cut('abc def ghi jkl', 3, 3);\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "lines"));
-    BOOST_TEST(duk_is_undefined(plugin_->get_context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_token_too_big)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "try {\n"
-        "  lines = Irccd.Util.cut('hello world', 3);\n"
-        "} catch (e) {\n"
-        "  name = e.name;\n"
-        "  message = e.message;\n"
-        "}\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "RangeError");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "message"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "word 'hello' could not fit in maxc limit (3)");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_negative_maxc)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "try {\n"
-        "  lines = Irccd.Util.cut('hello world', -3);\n"
-        "} catch (e) {\n"
-        "  name = e.name;\n"
-        "  message = e.message;\n"
-        "}\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "RangeError");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "message"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "argument 1 (maxc) must be positive");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_negative_maxl)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "try {\n"
-        "  lines = Irccd.Util.cut('hello world', undefined, -1);\n"
-        "} catch (e) {\n"
-        "  name = e.name;\n"
-        "  message = e.message;\n"
-        "}\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "RangeError");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "message"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "argument 2 (maxl) must be positive");
-}
-
-BOOST_AUTO_TEST_CASE(cut_array_simple)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "lines = Irccd.Util.cut([ 'hello', 'world' ]);\n"
-        "line0 = lines[0];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_array_double)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "lines = Irccd.Util.cut([ 'hello', 'world' ], 5);\n"
-        "line0 = lines[0];\n"
-        "line1 = lines[1];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line1"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_array_dirty)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "lines = Irccd.Util.cut([ '   ', ' hello  ', '  world ', '    '], 5);\n"
-        "line0 = lines[0];\n"
-        "line1 = lines[1];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "hello");
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "line1"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_invalid_data)
-{
-    const auto ret = duk_peval_string(plugin_->get_context(),
-        "try {\n"
-        "  lines = Irccd.Util.cut(123);\n"
-        "} catch (e) {\n"
-        "  name = e.name;\n"
-        "  message = e.message;\n"
-        "}\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->get_context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->get_context(), "name"));
-    BOOST_TEST(duk_get_string(plugin_->get_context(), -1) == "TypeError");
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !namespace
-
-} // !irccd::test