changeset 529:b3a0f61a35fe

Irccdctl: rename module to jsapi, closes #727
author David Demelier <markand@malikania.fr>
date Thu, 16 Nov 2017 23:31:28 +0100
parents 9daccaeedcce
children 7cd7b2cdf923
files irccd/main.cpp libirccd-js/CMakeLists.txt libirccd-js/irccd/duktape.hpp libirccd-js/irccd/js/directory_jsapi.cpp libirccd-js/irccd/js/directory_jsapi.hpp libirccd-js/irccd/js/duktape.hpp libirccd-js/irccd/js/elapsed_timer_jsapi.cpp libirccd-js/irccd/js/elapsed_timer_jsapi.hpp libirccd-js/irccd/js/file_jsapi.cpp libirccd-js/irccd/js/file_jsapi.hpp libirccd-js/irccd/js/irccd_jsapi.cpp libirccd-js/irccd/js/irccd_jsapi.hpp libirccd-js/irccd/js/js_plugin.cpp libirccd-js/irccd/js/js_plugin.hpp libirccd-js/irccd/js/jsapi.hpp libirccd-js/irccd/js/logger_jsapi.cpp libirccd-js/irccd/js/logger_jsapi.hpp libirccd-js/irccd/js/plugin_jsapi.cpp libirccd-js/irccd/js/plugin_jsapi.hpp libirccd-js/irccd/js/server_jsapi.cpp libirccd-js/irccd/js/server_jsapi.hpp libirccd-js/irccd/js/system_jsapi.cpp libirccd-js/irccd/js/system_jsapi.hpp libirccd-js/irccd/js/timer.cpp libirccd-js/irccd/js/timer.hpp libirccd-js/irccd/js/timer_jsapi.cpp libirccd-js/irccd/js/timer_jsapi.hpp libirccd-js/irccd/js/unicode.cpp libirccd-js/irccd/js/unicode.hpp libirccd-js/irccd/js/unicode_jsapi.cpp libirccd-js/irccd/js/unicode_jsapi.hpp libirccd-js/irccd/js/util_jsapi.cpp libirccd-js/irccd/js/util_jsapi.hpp libirccd-js/irccd/js_directory_module.cpp libirccd-js/irccd/js_directory_module.hpp libirccd-js/irccd/js_elapsed_timer_module.cpp libirccd-js/irccd/js_elapsed_timer_module.hpp libirccd-js/irccd/js_file_module.cpp libirccd-js/irccd/js_file_module.hpp libirccd-js/irccd/js_irccd_module.cpp libirccd-js/irccd/js_irccd_module.hpp libirccd-js/irccd/js_logger_module.cpp libirccd-js/irccd/js_logger_module.hpp libirccd-js/irccd/js_plugin.cpp libirccd-js/irccd/js_plugin.hpp libirccd-js/irccd/js_plugin_module.cpp libirccd-js/irccd/js_plugin_module.hpp libirccd-js/irccd/js_server_module.cpp libirccd-js/irccd/js_server_module.hpp libirccd-js/irccd/js_system_module.cpp libirccd-js/irccd/js_system_module.hpp libirccd-js/irccd/js_timer_module.cpp libirccd-js/irccd/js_timer_module.hpp libirccd-js/irccd/js_unicode_module.cpp libirccd-js/irccd/js_unicode_module.hpp libirccd-js/irccd/js_util_module.cpp libirccd-js/irccd/js_util_module.hpp libirccd-js/irccd/module.hpp libirccd-js/irccd/timer.cpp libirccd-js/irccd/timer.hpp libirccd-js/irccd/unicode.cpp libirccd-js/irccd/unicode.hpp libirccd-test/irccd/js_test.hpp libirccd-test/irccd/plugin_test.cpp libirccd-test/irccd/plugin_test.hpp tests/js-directory/main.cpp tests/js-elapsedtimer/main.cpp tests/js-file/main.cpp tests/js-irccd/main.cpp tests/js-logger/main.cpp tests/js-plugin/main.cpp tests/js-system/main.cpp tests/js-timer/main.cpp tests/js-unicode/main.cpp tests/js-util/main.cpp tests/js/main.cpp tests/timer/main.cpp
diffstat 77 files changed, 10863 insertions(+), 10902 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/irccd/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -43,18 +43,18 @@
 #include "irccd.hpp"
 
 #if defined(HAVE_JS)
-#   include <js_directory_module.hpp>
-#   include <js_elapsed_timer_module.hpp>
-#   include <js_file_module.hpp>
-#   include <js_irccd_module.hpp>
-#   include <js_logger_module.hpp>
-#   include <js_plugin_module.hpp>
-#   include <js_server_module.hpp>
-#   include <js_system_module.hpp>
-#   include <js_timer_module.hpp>
-#   include <js_unicode_module.hpp>
-#   include <js_util_module.hpp>
-#   include "js_plugin.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/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>
 #endif
 
 namespace irccd {
@@ -333,17 +333,17 @@
 #if defined(HAVE_JS)
     auto loader = std::make_unique<js_plugin_loader>(*instance);
 
-    loader->add_module(std::make_unique<js_irccd_module>());
-    loader->add_module(std::make_unique<js_directory_module>());
-    loader->add_module(std::make_unique<js_elapsed_timer_module>());
-    loader->add_module(std::make_unique<js_file_module>());
-    loader->add_module(std::make_unique<js_logger_module>());
-    loader->add_module(std::make_unique<js_plugin_module>());
-    loader->add_module(std::make_unique<js_server_module>());
-    loader->add_module(std::make_unique<js_system_module>());
-    loader->add_module(std::make_unique<js_timer_module>());
-    loader->add_module(std::make_unique<js_unicode_module>());
-    loader->add_module(std::make_unique<js_util_module>());
+    loader->modules().push_back(std::make_unique<irccd_jsapi>());
+    loader->modules().push_back(std::make_unique<directory_jsapi>());
+    loader->modules().push_back(std::make_unique<elapsed_timer_jsapi>());
+    loader->modules().push_back(std::make_unique<file_jsapi>());
+    loader->modules().push_back(std::make_unique<logger_jsapi>());
+    loader->modules().push_back(std::make_unique<plugin_jsapi>());
+    loader->modules().push_back(std::make_unique<server_jsapi>());
+    loader->modules().push_back(std::make_unique<system_jsapi>());
+    loader->modules().push_back(std::make_unique<timer_jsapi>());
+    loader->modules().push_back(std::make_unique<unicode_jsapi>());
+    loader->modules().push_back(std::make_unique<util_jsapi>());
 
     instance->plugins().add_loader(std::move(loader));
 #endif
--- a/libirccd-js/CMakeLists.txt	Thu Nov 16 23:12:45 2017 +0100
+++ b/libirccd-js/CMakeLists.txt	Thu Nov 16 23:31:28 2017 +0100
@@ -22,40 +22,40 @@
 
 set(
     HEADERS
-    ${libirccd-js_SOURCE_DIR}/irccd/duktape.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_directory_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_elapsed_timer_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_file_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_irccd_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_logger_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_plugin_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_server_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_system_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_timer_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_unicode_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_util_module.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_plugin.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/timer.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/unicode.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/duktape.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/directory_jsapi.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/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/jsapi.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/js_plugin.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/timer.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.hpp
 )
 
 set(
     SOURCES
-    ${libirccd-js_SOURCE_DIR}/irccd/js_directory_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_elapsed_timer_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_file_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_irccd_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_logger_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_plugin_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_server_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_system_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_timer_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_unicode_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_util_module.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js_plugin.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/timer.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/unicode.cpp
+    ${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/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/unicode_jsapi.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/util_jsapi.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/timer.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.cpp
 )
 
 irccd_define_library(
--- a/libirccd-js/irccd/duktape.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,512 +0,0 @@
-/*
- * duktape.hpp -- Duktape extras
- *
- * Copyright (c) 2016-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_DUKTAPE_HPP
-#define IRCCD_DUKTAPE_HPP
-
-/**
- * \file duktape.hpp
- * \brief Bring some extras to Duktape C library.
- * \author David Demelier <markand@malikania.fr>
- */
-
-#include <cerrno>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <exception>
-#include <fstream>
-#include <iterator>
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#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 StackAssert {
-#if !defined(NDEBUG)
-private:
-    duk_context *m_context;
-    unsigned m_expected;
-    unsigned m_begin;
-#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 StackAssert(duk_context *ctx, unsigned expected = 0) noexcept
-#if !defined(NDEBUG)
-        : m_context(ctx)
-        , m_expected(expected)
-        , m_begin(static_cast<unsigned>(duk_get_top(ctx)))
-#endif
-    {
-#if defined(NDEBUG)
-        (void)ctx;
-        (void)expected;
-#endif
-    }
-
-    /**
-     * Verify the expected size.
-     *
-     * No-op if NDEBUG is set.
-     */
-    inline ~StackAssert() noexcept
-    {
-#if !defined(NDEBUG)
-        if (static_cast<unsigned>(duk_get_top(m_context)) - m_begin != m_expected) {
-            std::fprintf(stderr, "Corrupt stack detection in StackAssert:\n");
-            std::fprintf(stderr, "  Size at start:            %u\n", m_begin);
-            std::fprintf(stderr, "  Size at end:              %d\n", duk_get_top(m_context));
-            std::fprintf(stderr, "  Expected (user):          %u\n", m_expected);
-            std::fprintf(stderr, "  Expected (adjusted):      %u\n", m_expected + m_begin);
-            std::fprintf(stderr, "  Number of stale values:   %u\n", duk_get_top(m_context) - m_begin - m_expected);
-            std::abort();
-        }
-#endif
-    }
-};
-
-/**
- * \brief Error description.
- *
- * This class fills the fields got in an Error object.
- */
-class Exception : public std::exception {
-public:
-    std::string name;       //!< name of error
-    std::string message;    //!< error message
-    std::string stack;      //!< stack if available
-    std::string fileName;   //!< filename if applicable
-    int lineNumber{0};      //!< line number if applicable
-
-    /**
-     * Get the error message. This effectively returns message field.
-     *
-     * \return the message
-     */
-    const char *what() const noexcept override
-    {
-        return message.c_str();
-    }
-};
-
-/**
- * \brief RAII based Duktape handler.
- *
- * This class is implicitly convertible to duk_context for convenience.
- */
-class UniqueContext {
-private:
-    using Deleter = void (*)(duk_context *);
-    using Handle = std::unique_ptr<duk_context, Deleter>;
-
-    Handle m_handle;
-
-    UniqueContext(const UniqueContext &) = delete;
-    UniqueContext &operator=(const UniqueContext &) = delete;
-
-public:
-    /**
-     * Create default context.
-     */
-    inline UniqueContext()
-        : m_handle(duk_create_heap_default(), duk_destroy_heap)
-    {
-    }
-
-    /**
-     * Default move constructor.
-     */
-    UniqueContext(UniqueContext &&) noexcept = default;
-
-    /**
-     * Convert the context to the native Duktape/C type.
-     *
-     * \return the duk_context
-     */
-    inline operator duk_context *() noexcept
-    {
-        return m_handle.get();
-    }
-
-    /**
-     * Convert the context to the native Duktape/C type.
-     *
-     * \return the duk_context
-     */
-    inline operator duk_context *() const noexcept
-    {
-        return m_handle.get();
-    }
-
-    /**
-     * Default move assignment operator.
-     *
-     * \return this
-     */
-    UniqueContext &operator=(UniqueContext &&) noexcept = delete;
-};
-
-/**
- * \brief Base ECMAScript error class.
- * \warning Override the function create for your own exceptions
- */
-class Error {
-private:
-    int m_type{DUK_ERR_ERROR};
-    std::string m_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 Error(int type, std::string message) noexcept
-        : m_type(type)
-        , m_message(std::move(message))
-    {
-    }
-
-public:
-    /**
-     * Constructor with a message.
-     *
-     * \param message the message
-     */
-    inline Error(std::string message) noexcept
-        : m_message(std::move(message))
-    {
-    }
-
-    /**
-     * Create the exception on the stack.
-     *
-     * \note the default implementation search for the global variables
-     * \param ctx the context
-     */
-    virtual void raise(duk_context *ctx) const
-    {
-        duk_error(ctx, m_type, "%s", m_message.c_str());
-    }
-};
-
-/**
- * \brief Error in eval() function.
- */
-class EvalError : public Error {
-public:
-    /**
-     * Construct an EvalError.
-     *
-     * \param message the message
-     */
-    inline EvalError(std::string message) noexcept
-        : Error(DUK_ERR_EVAL_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Value is out of range.
- */
-class RangeError : public Error {
-public:
-    /**
-     * Construct an RangeError.
-     *
-     * \param message the message
-     */
-    inline RangeError(std::string message) noexcept
-        : Error(DUK_ERR_RANGE_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Trying to use a variable that does not exist.
- */
-class ReferenceError : public Error {
-public:
-    /**
-     * Construct an ReferenceError.
-     *
-     * \param message the message
-     */
-    inline ReferenceError(std::string message) noexcept
-        : Error(DUK_ERR_REFERENCE_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Syntax error in the script.
- */
-class SyntaxError : public Error {
-public:
-    /**
-     * Construct an SyntaxError.
-     *
-     * \param message the message
-     */
-    inline SyntaxError(std::string message) noexcept
-        : Error(DUK_ERR_SYNTAX_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief Invalid type given.
- */
-class TypeError : public Error {
-public:
-    /**
-     * Construct an TypeError.
-     *
-     * \param message the message
-     */
-    inline TypeError(std::string message) noexcept
-        : Error(DUK_ERR_TYPE_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * \brief URI manipulation failure.
- */
-class URIError : public Error {
-public:
-    /**
-     * Construct an URIError.
-     *
-     * \param message the message
-     */
-    inline URIError(std::string message) noexcept
-        : Error(DUK_ERR_URI_ERROR, std::move(message))
-    {
-    }
-};
-
-/**
- * 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 Exception dukx_exception(duk_context *ctx, int index, bool pop = true)
-{
-    Exception ex;
-
-    index = duk_normalize_index(ctx, index);
-
-    duk_get_prop_string(ctx, index, "name");
-    ex.name = duk_to_string(ctx, -1);
-    duk_get_prop_string(ctx, index, "message");
-    ex.message = duk_to_string(ctx, -1);
-    duk_get_prop_string(ctx, index, "fileName");
-    ex.fileName = duk_to_string(ctx, -1);
-    duk_get_prop_string(ctx, index, "lineNumber");
-    ex.lineNumber = duk_to_int(ctx, -1);
-    duk_get_prop_string(ctx, index, "stack");
-    ex.stack = duk_to_string(ctx, -1);
-    duk_pop_n(ctx, 5);
-
-    if (pop)
-        duk_remove(ctx, index);
-
-    return ex;
-}
-
-/**
- * Enumerate an object or an array at the specified index.
- *
- * \param ctx the context
- * \param index the object or array index
- * \param flags the optional flags to pass to duk_enum
- * \param getvalue set to true if you want to extract the value
- * \param func the function to call for each properties
- */
-template <typename Func>
-void dukx_enumerate(duk_context *ctx, int index, duk_uint_t flags, duk_bool_t getvalue, Func &&func)
-{
-    duk_enum(ctx, index, flags);
-
-    while (duk_next(ctx, -1, getvalue)) {
-        func(ctx);
-        duk_pop_n(ctx, 1 + (getvalue ? 1 : 0));
-    }
-
-    duk_pop(ctx);
-}
-
-/**
- * Throw an ECMAScript exception.
- *
- * \param ctx the context
- * \param ex the exception
- */
-template <typename Exception>
-void dukx_throw(duk_context *ctx, const Exception &ex)
-{
-    ex.raise(ctx);
-}
-
-/**
- * Get a string, return 0 if not a string.
- *
- * \param ctx the context
- * \param index the index
- * \return the string
- */
-inline std::string dukx_get_std_string(duk_context *ctx, int index)
-{
-    duk_size_t size;
-    const char *text = duk_get_lstring(ctx, index, &size);
-
-    return std::string(text, size);
-}
-
-/**
- * Require a string, throws a JavaScript exception if not a string.
- *
- * \param ctx the context
- * \param index the index
- * \return the string
- */
-inline std::string dukx_require_std_string(duk_context *ctx, int index)
-{
-    duk_size_t size;
-    const char *text = duk_require_lstring(ctx, index, &size);
-
-    return std::string(text, size);
-}
-
-/**
- * Push a C++ string.
- *
- * \param ctx the context
- * \param str the string
- */
-inline void dukx_push_std_string(duk_context *ctx, const std::string &str)
-{
-    duk_push_lstring(ctx, str.data(), str.length());
-}
-
-/**
- * Get an array.
- *
- * \param ctx the context
- * \param index the array index
- * \param get the conversion function (e.g. duk_get_int)
- */
-template <typename Getter>
-auto dukx_get_array(duk_context *ctx, duk_idx_t index, Getter &&get)
-{
-    using T = decltype(get(ctx, 0));
-
-    std::vector<T> result;
-    std::size_t length = duk_get_length(ctx, index);
-
-    for (std::size_t i = 0; i < length; ++i) {
-        duk_get_prop_index(ctx, -1, i);
-        result.push_back(get(ctx, -1));
-        duk_pop(ctx);
-    }
-
-    return result;
-}
-
-/**
- * Push an array.
- *
- * \param ctx the context
- * \param values the values
- * \param push the function to push values
- */
-template <typename T, typename Pusher>
-void dukx_push_array(duk_context *ctx, const std::vector<T> &values, Pusher &&push)
-{
-    duk_push_array(ctx);
-
-    int i = 0;
-    for (auto x : values) {
-        push(ctx, x);
-        duk_put_prop_index(ctx, -2, i++);
-    }
-}
-
-/**
- * Replace the removed duk_peval_string with one throwing exception.
- *
- * \param ctx the context
- * \param path the path to the file
- * \throw Exception on errors
- */
-inline void dukx_peval_file(duk_context* ctx, const std::string& path)
-{
-    std::ifstream input(path);
-
-    if (!input) {
-        Exception ex;
-
-        ex.name = "Error";
-        ex.message = std::strerror(errno);
-        ex.fileName = path;
-
-        throw ex;
-    }
-
-    std::string data(std::istreambuf_iterator<char>(input), {});
-
-    if (duk_peval_lstring(ctx, data.c_str(), data.length()) != 0) {
-        auto ex = dukx_exception(ctx, -1);
-
-        ex.fileName = path;
-
-        throw ex;
-    }
-}
-
-} // !irccd
-
-#endif // !IRCCD_DUKTAPE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/directory_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,330 @@
+/*
+ * directory_jsapi.cpp -- Irccd.Directory API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <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 {
+
+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");
+
+    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:
+ *
+ * - Directory.find
+ * - Directory.prototype.find
+ *
+ * The patternIndex 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)
+{
+    try {
+        std::string path;
+
+        if (duk_is_string(ctx, pattern_index))
+            path = fs_util::find(base, dukx_get_std_string(ctx, pattern_index), recursive);
+        else {
+            // Check if it's a valid RegExp object.
+            duk_get_global_string(ctx, "RegExp");
+            auto is_regex = duk_instanceof(ctx, pattern_index, -1);
+            duk_pop(ctx);
+
+            if (is_regex) {
+                duk_get_prop_string(ctx, pattern_index, "source");
+                auto pattern = duk_to_string(ctx, -1);
+                duk_pop(ctx);
+
+                path = fs_util::find(base, std::regex(pattern), recursive);
+            } else
+                duk_error(ctx, DUK_ERR_TYPE_ERROR, "pattern must be a string or a regex expression");
+        }
+
+        if (path.empty())
+            return 0;
+
+        dukx_push_std_string(ctx, path);
+    } catch (const std::exception& ex) {
+        duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+    }
+
+    return 1;
+}
+
+/*
+ * Generic remove function for:
+ *
+ * - Directory.remove
+ * - Directory.prototype.remove
+ */
+duk_ret_t remove(duk_context* ctx, const std::string& path, bool recursive)
+{
+    boost::system::error_code ec;
+
+    if (!boost::filesystem::is_directory(path, ec) || ec)
+        dukx_throw(ctx, system_error(EINVAL, "not a directory"));
+
+    if (!recursive)
+        boost::filesystem::remove(path, ec);
+    else
+        boost::filesystem::remove_all(path, ec);
+
+    return 0;
+}
+
+/*
+ * Method: Directory.find(pattern, recursive)
+ * --------------------------------------------------------
+ *
+ * Synonym of 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:
+ *   - Any exception on error.
+ */
+duk_ret_t method_find(duk_context* ctx)
+{
+    return find(ctx, path(ctx), duk_get_boolean(ctx, 1), 0);
+}
+
+/*
+ * Method: Directory.remove(recursive)
+ * --------------------------------------------------------
+ *
+ * Synonym of Directory.remove(recursive) but the path is taken from the
+ * directory object.
+ *
+ * Arguments:
+ *   - recursive, recursively or not (default: false).
+ * Throws:
+ *   - Any exception on error.
+ */
+duk_ret_t method_remove(duk_context* ctx)
+{
+    return remove(ctx, path(ctx), duk_get_boolean(ctx, 0));
+}
+
+const duk_function_list_entry methods[] = {
+    { "find",       method_find,    DUK_VARARGS },
+    { "remove",     method_remove,  1           },
+    { nullptr,      nullptr,        0           }
+};
+
+/*
+ * Directory "static" functions
+ * ------------------------------------------------------------------
+ */
+
+/*
+ * Function: Irccd.Directory(path) [constructor]
+ * --------------------------------------------------------
+ *
+ * Opens and read the directory at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the directory,
+ * Throws:
+ *   - Any exception on error
+ */
+duk_ret_t constructor(duk_context* ctx)
+{
+    if (!duk_is_constructor_call(ctx))
+        return 0;
+
+    try {
+        auto path = duk_require_string(ctx, 0);
+
+        if (!boost::filesystem::is_directory(path))
+            dukx_throw(ctx, system_error(EINVAL, "not a directory"));
+
+        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_std_string(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_string(ctx, "path");
+        dukx_push_std_string(ctx, path);
+        duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+    } catch (const std::exception& ex) {
+        dukx_throw(ctx, system_error(errno, ex.what()));
+    }
+
+    return 0;
+}
+
+/*
+ * 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.
+ */
+duk_ret_t func_find(duk_context* ctx)
+{
+    return find(ctx, duk_require_string(ctx, 0), duk_get_boolean(ctx, 2), 1);
+}
+
+/*
+ * 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:
+ *   - Any exception on error.
+ */
+duk_ret_t func_remove(duk_context *ctx)
+{
+    return remove(ctx, duk_require_string(ctx, 0), duk_get_boolean(ctx, 1));
+}
+
+/*
+ * 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:
+ *   - Any exception on error.
+ */
+duk_ret_t func_mkdir(duk_context *ctx)
+{
+    try {
+        boost::filesystem::create_directories(duk_require_string(ctx, 0));
+    } catch (const std::exception &ex) {
+        dukx_throw(ctx, system_error(errno, ex.what()));
+    }
+
+    return 0;
+}
+
+const duk_function_list_entry functions[] = {
+    { "find",           func_find,      DUK_VARARGS },
+    { "mkdir",          func_mkdir,     DUK_VARARGS },
+    { "remove",         func_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::name() const
+{
+    return "Irccd.Directory";
+}
+
+void directory_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_c_function(plugin->context(), constructor, 2);
+    duk_put_number_list(plugin->context(), -1, constants);
+    duk_put_function_list(plugin->context(), -1, functions);
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+    duk_push_string(plugin->context(), "\\");
+#else
+    duk_push_string(plugin->context(), "/");
+#endif
+
+    duk_put_prop_string(plugin->context(), -2, "separator");
+
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, methods);
+    duk_put_prop_string(plugin->context(), -2, "prototype");
+    duk_put_prop_string(plugin->context(), -2, "Directory");
+    duk_pop(plugin->context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/directory_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,50 @@
+/*
+ * directory_jsapi.hpp -- Irccd.Directory API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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::name
+     */
+    std::string 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/duktape.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,512 @@
+/*
+ * duktape.hpp -- Duktape extras
+ *
+ * Copyright (c) 2016-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_DUKTAPE_HPP
+#define IRCCD_DUKTAPE_HPP
+
+/**
+ * \file duktape.hpp
+ * \brief Bring some extras to Duktape C library.
+ * \author David Demelier <markand@malikania.fr>
+ */
+
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <exception>
+#include <fstream>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#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 StackAssert {
+#if !defined(NDEBUG)
+private:
+    duk_context *m_context;
+    unsigned m_expected;
+    unsigned m_begin;
+#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 StackAssert(duk_context *ctx, unsigned expected = 0) noexcept
+#if !defined(NDEBUG)
+        : m_context(ctx)
+        , m_expected(expected)
+        , m_begin(static_cast<unsigned>(duk_get_top(ctx)))
+#endif
+    {
+#if defined(NDEBUG)
+        (void)ctx;
+        (void)expected;
+#endif
+    }
+
+    /**
+     * Verify the expected size.
+     *
+     * No-op if NDEBUG is set.
+     */
+    inline ~StackAssert() noexcept
+    {
+#if !defined(NDEBUG)
+        if (static_cast<unsigned>(duk_get_top(m_context)) - m_begin != m_expected) {
+            std::fprintf(stderr, "Corrupt stack detection in StackAssert:\n");
+            std::fprintf(stderr, "  Size at start:            %u\n", m_begin);
+            std::fprintf(stderr, "  Size at end:              %d\n", duk_get_top(m_context));
+            std::fprintf(stderr, "  Expected (user):          %u\n", m_expected);
+            std::fprintf(stderr, "  Expected (adjusted):      %u\n", m_expected + m_begin);
+            std::fprintf(stderr, "  Number of stale values:   %u\n", duk_get_top(m_context) - m_begin - m_expected);
+            std::abort();
+        }
+#endif
+    }
+};
+
+/**
+ * \brief Error description.
+ *
+ * This class fills the fields got in an Error object.
+ */
+class Exception : public std::exception {
+public:
+    std::string name;       //!< name of error
+    std::string message;    //!< error message
+    std::string stack;      //!< stack if available
+    std::string fileName;   //!< filename if applicable
+    int lineNumber{0};      //!< line number if applicable
+
+    /**
+     * Get the error message. This effectively returns message field.
+     *
+     * \return the message
+     */
+    const char *what() const noexcept override
+    {
+        return message.c_str();
+    }
+};
+
+/**
+ * \brief RAII based Duktape handler.
+ *
+ * This class is implicitly convertible to duk_context for convenience.
+ */
+class UniqueContext {
+private:
+    using Deleter = void (*)(duk_context *);
+    using Handle = std::unique_ptr<duk_context, Deleter>;
+
+    Handle m_handle;
+
+    UniqueContext(const UniqueContext &) = delete;
+    UniqueContext &operator=(const UniqueContext &) = delete;
+
+public:
+    /**
+     * Create default context.
+     */
+    inline UniqueContext()
+        : m_handle(duk_create_heap_default(), duk_destroy_heap)
+    {
+    }
+
+    /**
+     * Default move constructor.
+     */
+    UniqueContext(UniqueContext &&) noexcept = default;
+
+    /**
+     * Convert the context to the native Duktape/C type.
+     *
+     * \return the duk_context
+     */
+    inline operator duk_context *() noexcept
+    {
+        return m_handle.get();
+    }
+
+    /**
+     * Convert the context to the native Duktape/C type.
+     *
+     * \return the duk_context
+     */
+    inline operator duk_context *() const noexcept
+    {
+        return m_handle.get();
+    }
+
+    /**
+     * Default move assignment operator.
+     *
+     * \return this
+     */
+    UniqueContext &operator=(UniqueContext &&) noexcept = delete;
+};
+
+/**
+ * \brief Base ECMAScript error class.
+ * \warning Override the function create for your own exceptions
+ */
+class Error {
+private:
+    int m_type{DUK_ERR_ERROR};
+    std::string m_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 Error(int type, std::string message) noexcept
+        : m_type(type)
+        , m_message(std::move(message))
+    {
+    }
+
+public:
+    /**
+     * Constructor with a message.
+     *
+     * \param message the message
+     */
+    inline Error(std::string message) noexcept
+        : m_message(std::move(message))
+    {
+    }
+
+    /**
+     * Create the exception on the stack.
+     *
+     * \note the default implementation search for the global variables
+     * \param ctx the context
+     */
+    virtual void raise(duk_context *ctx) const
+    {
+        duk_error(ctx, m_type, "%s", m_message.c_str());
+    }
+};
+
+/**
+ * \brief Error in eval() function.
+ */
+class EvalError : public Error {
+public:
+    /**
+     * Construct an EvalError.
+     *
+     * \param message the message
+     */
+    inline EvalError(std::string message) noexcept
+        : Error(DUK_ERR_EVAL_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief Value is out of range.
+ */
+class RangeError : public Error {
+public:
+    /**
+     * Construct an RangeError.
+     *
+     * \param message the message
+     */
+    inline RangeError(std::string message) noexcept
+        : Error(DUK_ERR_RANGE_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief Trying to use a variable that does not exist.
+ */
+class ReferenceError : public Error {
+public:
+    /**
+     * Construct an ReferenceError.
+     *
+     * \param message the message
+     */
+    inline ReferenceError(std::string message) noexcept
+        : Error(DUK_ERR_REFERENCE_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief Syntax error in the script.
+ */
+class SyntaxError : public Error {
+public:
+    /**
+     * Construct an SyntaxError.
+     *
+     * \param message the message
+     */
+    inline SyntaxError(std::string message) noexcept
+        : Error(DUK_ERR_SYNTAX_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief Invalid type given.
+ */
+class TypeError : public Error {
+public:
+    /**
+     * Construct an TypeError.
+     *
+     * \param message the message
+     */
+    inline TypeError(std::string message) noexcept
+        : Error(DUK_ERR_TYPE_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * \brief URI manipulation failure.
+ */
+class URIError : public Error {
+public:
+    /**
+     * Construct an URIError.
+     *
+     * \param message the message
+     */
+    inline URIError(std::string message) noexcept
+        : Error(DUK_ERR_URI_ERROR, std::move(message))
+    {
+    }
+};
+
+/**
+ * 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 Exception dukx_exception(duk_context *ctx, int index, bool pop = true)
+{
+    Exception ex;
+
+    index = duk_normalize_index(ctx, index);
+
+    duk_get_prop_string(ctx, index, "name");
+    ex.name = duk_to_string(ctx, -1);
+    duk_get_prop_string(ctx, index, "message");
+    ex.message = duk_to_string(ctx, -1);
+    duk_get_prop_string(ctx, index, "fileName");
+    ex.fileName = duk_to_string(ctx, -1);
+    duk_get_prop_string(ctx, index, "lineNumber");
+    ex.lineNumber = duk_to_int(ctx, -1);
+    duk_get_prop_string(ctx, index, "stack");
+    ex.stack = duk_to_string(ctx, -1);
+    duk_pop_n(ctx, 5);
+
+    if (pop)
+        duk_remove(ctx, index);
+
+    return ex;
+}
+
+/**
+ * Enumerate an object or an array at the specified index.
+ *
+ * \param ctx the context
+ * \param index the object or array index
+ * \param flags the optional flags to pass to duk_enum
+ * \param getvalue set to true if you want to extract the value
+ * \param func the function to call for each properties
+ */
+template <typename Func>
+void dukx_enumerate(duk_context *ctx, int index, duk_uint_t flags, duk_bool_t getvalue, Func &&func)
+{
+    duk_enum(ctx, index, flags);
+
+    while (duk_next(ctx, -1, getvalue)) {
+        func(ctx);
+        duk_pop_n(ctx, 1 + (getvalue ? 1 : 0));
+    }
+
+    duk_pop(ctx);
+}
+
+/**
+ * Throw an ECMAScript exception.
+ *
+ * \param ctx the context
+ * \param ex the exception
+ */
+template <typename Exception>
+void dukx_throw(duk_context *ctx, const Exception &ex)
+{
+    ex.raise(ctx);
+}
+
+/**
+ * Get a string, return 0 if not a string.
+ *
+ * \param ctx the context
+ * \param index the index
+ * \return the string
+ */
+inline std::string dukx_get_std_string(duk_context *ctx, int index)
+{
+    duk_size_t size;
+    const char *text = duk_get_lstring(ctx, index, &size);
+
+    return std::string(text, size);
+}
+
+/**
+ * Require a string, throws a JavaScript exception if not a string.
+ *
+ * \param ctx the context
+ * \param index the index
+ * \return the string
+ */
+inline std::string dukx_require_std_string(duk_context *ctx, int index)
+{
+    duk_size_t size;
+    const char *text = duk_require_lstring(ctx, index, &size);
+
+    return std::string(text, size);
+}
+
+/**
+ * Push a C++ string.
+ *
+ * \param ctx the context
+ * \param str the string
+ */
+inline void dukx_push_std_string(duk_context *ctx, const std::string &str)
+{
+    duk_push_lstring(ctx, str.data(), str.length());
+}
+
+/**
+ * Get an array.
+ *
+ * \param ctx the context
+ * \param index the array index
+ * \param get the conversion function (e.g. duk_get_int)
+ */
+template <typename Getter>
+auto dukx_get_array(duk_context *ctx, duk_idx_t index, Getter &&get)
+{
+    using T = decltype(get(ctx, 0));
+
+    std::vector<T> result;
+    std::size_t length = duk_get_length(ctx, index);
+
+    for (std::size_t i = 0; i < length; ++i) {
+        duk_get_prop_index(ctx, -1, i);
+        result.push_back(get(ctx, -1));
+        duk_pop(ctx);
+    }
+
+    return result;
+}
+
+/**
+ * Push an array.
+ *
+ * \param ctx the context
+ * \param values the values
+ * \param push the function to push values
+ */
+template <typename T, typename Pusher>
+void dukx_push_array(duk_context *ctx, const std::vector<T> &values, Pusher &&push)
+{
+    duk_push_array(ctx);
+
+    int i = 0;
+    for (auto x : values) {
+        push(ctx, x);
+        duk_put_prop_index(ctx, -2, i++);
+    }
+}
+
+/**
+ * Replace the removed duk_peval_string with one throwing exception.
+ *
+ * \param ctx the context
+ * \param path the path to the file
+ * \throw Exception on errors
+ */
+inline void dukx_peval_file(duk_context* ctx, const std::string& path)
+{
+    std::ifstream input(path);
+
+    if (!input) {
+        Exception ex;
+
+        ex.name = "Error";
+        ex.message = std::strerror(errno);
+        ex.fileName = path;
+
+        throw ex;
+    }
+
+    std::string data(std::istreambuf_iterator<char>(input), {});
+
+    if (duk_peval_lstring(ctx, data.c_str(), data.length()) != 0) {
+        auto ex = dukx_exception(ctx, -1);
+
+        ex.fileName = path;
+
+        throw ex;
+    }
+}
+
+} // !irccd
+
+#endif // !IRCCD_DUKTAPE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/elapsed_timer_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,148 @@
+/*
+ * elapsed_timer_jsapi.cpp -- Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <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");
+
+boost::timer::cpu_timer* self(duk_context* ctx)
+{
+    StackAssert sa(ctx);
+
+    duk_push_this(ctx);
+    duk_get_prop_string(ctx, -1, signature);
+    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;
+}
+
+/*
+ * Method: ElapsedTimer.pause
+ * ------------------------------------------------------------------
+ *
+ * Pause the timer, without resetting the current elapsed time stored.
+ */
+duk_ret_t pause(duk_context* ctx)
+{
+    self(ctx)->stop();
+
+    return 0;
+}
+
+/*
+ * Method: ElapsedTimer.restart
+ * ------------------------------------------------------------------
+ *
+ * Restart the timer without resetting the current elapsed time.
+ */
+duk_ret_t restart(duk_context* ctx)
+{
+    self(ctx)->resume();
+
+    return 0;
+}
+
+/*
+ * Method: ElapsedTimer.elapsed
+ * ------------------------------------------------------------------
+ *
+ * Get the number of elapsed milliseconds.
+ *
+ * Returns:
+ *   The time elapsed.
+ */
+duk_ret_t elapsed(duk_context* ctx)
+{
+    duk_push_uint(ctx, self(ctx)->elapsed().wall / 1000000LL);
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.ElapsedTimer [constructor]
+ * ------------------------------------------------------------------
+ *
+ * Construct a new ElapsedTimer object.
+ */
+duk_ret_t 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;
+}
+
+/*
+ * Function: Irccd.ElapsedTimer [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Delete the property.
+ */
+duk_ret_t 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",    elapsed,    0 },
+    { "pause",      pause,      0 },
+    { "restart",    restart,    0 },
+    { nullptr,      nullptr,    0 }
+};
+
+} // !namespace
+
+std::string elapsed_timer_jsapi::name() const
+{
+    return "Irccd.ElapsedTimer";
+}
+
+void elapsed_timer_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_c_function(plugin->context(), constructor, 0);
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, methods);
+    duk_push_c_function(plugin->context(), destructor, 1);
+    duk_set_finalizer(plugin->context(), -2);
+    duk_put_prop_string(plugin->context(), -2, "prototype");
+    duk_put_prop_string(plugin->context(), -2, "ElapsedTimer");
+    duk_pop(plugin->context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/elapsed_timer_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,50 @@
+/*
+ * elapsed_timer_jsapi.hpp -- Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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::name
+     */
+    std::string 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_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,700 @@
+/*
+ * file_jsapi.cpp -- Irccd.File API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <iterator>
+#include <vector>
+
+#include <boost/filesystem.hpp>
+
+#if defined(HAVE_STAT)
+#  include <sys/types.h>
+#  include <sys/stat.h>
+#endif
+
+#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");
+
+#if defined(HAVE_STAT)
+
+/*
+ * push_stat
+ * ------------------------------------------------------------------
+ */
+
+void push_stat(duk_context* ctx, const struct stat& st)
+{
+    StackAssert 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
+
+// Remove trailing \r for CRLF line style.
+inline std::string clear_crlf(std::string input)
+{
+    if (input.length() > 0 && input.back() == '\r')
+        input.pop_back();
+
+    return input;
+}
+
+file* self(duk_context* ctx)
+{
+    StackAssert sa(ctx);
+
+    duk_push_this(ctx);
+    duk_get_prop_string(ctx, -1, signature);
+    auto ptr = static_cast<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;
+}
+
+/*
+ * File methods.
+ * ------------------------------------------------------------------
+ */
+
+/*
+ * Method: File.basename()
+ * --------------------------------------------------------
+ *
+ * Synonym of `irccd.File.basename(path)` but with the path from the file.
+ *
+ * duk_ret_turns:
+ *   The base name.
+ */
+duk_ret_t method_basename(duk_context* ctx)
+{
+    dukx_push_std_string(ctx, fs_util::base_name(self(ctx)->path()));
+
+    return 1;
+}
+
+/*
+ * Method: File.close()
+ * --------------------------------------------------------
+ *
+ * Force close of the file, automatically called when object is collected.
+ */
+duk_ret_t method_close(duk_context* ctx)
+{
+    self(ctx)->close();
+
+    return 0;
+}
+
+/*
+ * Method: File.dirname()
+ * --------------------------------------------------------
+ *
+ * Synonym of `irccd.File.dirname(path)` but with the path from the file.
+ *
+ * duk_ret_turns:
+ *   The directory name.
+ */
+duk_ret_t method_dirname(duk_context* ctx)
+{
+    dukx_push_std_string(ctx, fs_util::dir_name(self(ctx)->path()));
+
+    return 1;
+}
+
+/*
+ * Method: File.lines()
+ * --------------------------------------------------------
+ *
+ * Read all lines and return an array.
+ *
+ * duk_ret_turns:
+ *   An array with all lines.
+ * Throws
+ *   - Any exception on error.
+ */
+duk_ret_t method_lines(duk_context* ctx)
+{
+    duk_push_array(ctx);
+
+    std::FILE* fp = self(ctx)->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();
+
+        auto pos = buffer.find('\n');
+
+        if (pos != std::string::npos) {
+            dukx_push_std_string(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))
+        dukx_throw(ctx, system_error());
+
+    // Missing '\n' in end of file.
+    if (!buffer.empty()) {
+        dukx_push_std_string(ctx, clear_crlf(buffer));
+        duk_put_prop_index(ctx, -2, i++);
+    }
+
+    return 1;
+}
+
+/*
+ * Method: File.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).
+ * duk_ret_turns:
+ *   The string.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk_ret_t method_read(duk_context* ctx)
+{
+    auto file = self(ctx);
+    auto amount = duk_is_number(ctx, 0) ? duk_get_int(ctx, 0) : -1;
+
+    if (amount == 0 || file->handle() == nullptr)
+        return 0;
+
+    try {
+        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(), file->handle())) > 0) {
+                if (std::ferror(file->handle()))
+                    dukx_throw(ctx, system_error());
+
+                std::copy(buffer.begin(), buffer.begin() + nread, std::back_inserter(data));
+                total += nread;
+            }
+        } else {
+            data.resize((std::size_t)amount);
+            total = std::fread(&data[0], sizeof (data[0]), (std::size_t)amount, file->handle());
+
+            if (std::ferror(file->handle()))
+                dukx_throw(ctx, system_error());
+
+            data.resize(total);
+        }
+
+        dukx_push_std_string(ctx, data);
+    } catch (const std::exception&) {
+        dukx_throw(ctx, system_error());
+    }
+
+    return 1;
+}
+
+/*
+ * Method: File.readline()
+ * --------------------------------------------------------
+ *
+ * Read the next line available.
+ *
+ * duk_ret_turns:
+ *   The next line or undefined if eof.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk_ret_t method_readline(duk_context* ctx)
+{
+    std::FILE* fp = self(ctx)->handle();
+    std::string result;
+
+    if (fp == nullptr || std::feof(fp))
+        return 0;
+    for (int ch; (ch = std::fgetc(fp)) != EOF && ch != '\n'; )
+        result += (char)ch;
+    if (std::ferror(fp))
+        dukx_throw(ctx, system_error());
+
+    dukx_push_std_string(ctx, clear_crlf(result));
+
+    return 1;
+}
+
+/*
+ * Method: File.remove()
+ * --------------------------------------------------------
+ *
+ * Synonym of File.remove(path) but with the path from the file.
+ *
+ * Throws:
+ *   - Any exception on error.
+ */
+duk_ret_t method_remove(duk_context* ctx)
+{
+    if (::remove(self(ctx)->path().c_str()) < 0)
+        dukx_throw(ctx, system_error());
+
+    return 0;
+}
+
+/*
+ * Method: File.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:
+ *   - Any exception on error.
+ */
+duk_ret_t method_seek(duk_context* ctx)
+{
+    auto fp = self(ctx)->handle();
+    auto type = duk_require_int(ctx, 0);
+    auto amount = duk_require_int(ctx, 1);
+
+    if (fp != nullptr && std::fseek(fp, amount, type) != 0)
+        dukx_throw(ctx, system_error());
+
+    return 0;
+}
+
+#if defined(HAVE_STAT)
+
+/*
+ * Method: File.stat() [optional]
+ * --------------------------------------------------------
+ *
+ * Synonym of File.stat(path) but with the path from the file.
+ *
+ * duk_ret_turns:
+ *   The stat information.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk_ret_t method_stat(duk_context* ctx)
+{
+    auto file = self(ctx);
+    struct stat st;
+
+    if (file->handle() == nullptr && ::stat(file->path().c_str(), &st) < 0)
+        dukx_throw(ctx, system_error());
+    else
+        push_stat(ctx, st);
+
+    return 1;
+}
+
+#endif // !HAVE_STAT
+
+/*
+ * Method: File.tell()
+ * --------------------------------------------------------
+ *
+ * Get the actual position in the file.
+ *
+ * duk_ret_turns:
+ *   The position.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk_ret_t method_tell(duk_context* ctx)
+{
+    auto fp = self(ctx)->handle();
+    long pos;
+
+    if (fp == nullptr)
+        return 0;
+
+    if ((pos = std::ftell(fp)) == -1L)
+        dukx_throw(ctx, system_error());
+    else
+        duk_push_int(ctx, pos);
+
+    return 1;
+}
+
+/*
+ * Method: File.write(data)
+ * --------------------------------------------------------
+ *
+ * Write some characters to the file.
+ *
+ * Arguments:
+ *   - data, the character to write.
+ * duk_ret_turns:
+ *   The number of bytes written.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk_ret_t method_write(duk_context* ctx)
+{
+    std::FILE* fp = self(ctx)->handle();
+    std::string data = duk_require_string(ctx, 0);
+
+    if (fp == nullptr)
+        return 0;
+
+    auto nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
+
+    if (std::ferror(fp))
+        dukx_throw(ctx, system_error());
+
+    duk_push_uint(ctx, nwritten);
+
+    return 1;
+}
+
+const duk_function_list_entry methods[] = {
+    { "basename",   method_basename,    0 },
+    { "close",      method_close,       0 },
+    { "dirname",    method_dirname,     0 },
+    { "lines",      method_lines,       0 },
+    { "read",       method_read,        1 },
+    { "readline",   method_readline,    0 },
+    { "remove",     method_remove,      0 },
+    { "seek",       method_seek,        2 },
+#if defined(HAVE_STAT)
+    { "stat",       method_stat,        0 },
+#endif
+    { "tell",       method_tell,        0 },
+    { "write",      method_write,       1 },
+    { nullptr,      nullptr,            0 }
+};
+
+/*
+ * File "static" functions
+ * ------------------------------------------------------------------
+ */
+
+/*
+ * 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:
+ *   - Any exception on error.
+ */
+duk_ret_t constructor(duk_context* ctx)
+{
+    if (!duk_is_constructor_call(ctx))
+        return 0;
+
+    try {
+        dukx_new_file(ctx, new file(duk_require_string(ctx, 0), duk_require_string(ctx, 1)));
+    } catch (const std::exception &) {
+        dukx_throw(ctx, system_error());
+    }
+
+    return 0;
+}
+
+/*
+ * Function: Irccd.File() [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Delete the property.
+ */
+duk_ret_t destructor(duk_context* ctx)
+{
+    duk_get_prop_string(ctx, 0, signature);
+    delete static_cast<file*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+    duk_del_prop_string(ctx, 0, signature);
+
+    return 0;
+}
+
+/*
+ * 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.
+ * duk_ret_turns:
+ *   The base name.
+ */
+duk_ret_t function_basename(duk_context* ctx)
+{
+    dukx_push_std_string(ctx, fs_util::base_name(duk_require_string(ctx, 0)));
+
+    return 1;
+}
+
+/*
+ * 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.
+ * duk_ret_turns:
+ *   The directory name.
+ */
+duk_ret_t function_dirname(duk_context* ctx)
+{
+    dukx_push_std_string(ctx, fs_util::dir_name(duk_require_string(ctx, 0)));
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.File.exists(path)
+ * --------------------------------------------------------
+ *
+ * Check if the file exists.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * duk_ret_turns:
+ *   True if exists.
+ * Throws:
+ *   - Any exception if we don't have access.
+ */
+duk_ret_t function_exists(duk_context* ctx)
+{
+    try {
+        duk_push_boolean(ctx, boost::filesystem::exists(duk_require_string(ctx, 0)));
+    } catch (...) {
+        duk_push_boolean(ctx, false);
+    }
+
+    return 1;
+}
+
+/*
+ * function Irccd.File.remove(path)
+ * --------------------------------------------------------
+ *
+ * Remove the file at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk_ret_t function_remove(duk_context* ctx)
+{
+    if (::remove(duk_require_string(ctx, 0)) < 0)
+        dukx_throw(ctx, system_error());
+
+    return 0;
+}
+
+#if defined(HAVE_STAT)
+
+/*
+ * function Irccd.File.stat(path) [optional]
+ * --------------------------------------------------------
+ *
+ * Get file information at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * duk_ret_turns:
+ *   The stat information.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk_ret_t function_stat(duk_context* ctx)
+{
+    struct stat st;
+
+    if (::stat(duk_require_string(ctx, 0), &st) < 0)
+        dukx_throw(ctx, system_error());
+
+    push_stat(ctx, st);
+
+    return 1;
+}
+
+#endif // !HAVE_STAT
+
+const duk_function_list_entry functions[] = {
+    { "basename",   function_basename,  1 },
+    { "dirname",    function_dirname,   1 },
+    { "exists",     function_exists,    1 },
+    { "remove",     function_remove,    1 },
+#if defined(HAVE_STAT)
+    { "stat",       function_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::name() const
+{
+    return "Irccd.File";
+}
+
+void file_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_c_function(plugin->context(), constructor, 2);
+    duk_put_number_list(plugin->context(), -1, constants);
+    duk_put_function_list(plugin->context(), -1, functions);
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, methods);
+    duk_push_c_function(plugin->context(), destructor, 1);
+    duk_set_finalizer(plugin->context(), -2);
+    duk_dup(plugin->context(), -1);
+    duk_put_global_string(plugin->context(), prototype);
+    duk_put_prop_string(plugin->context(), -2, "prototype");
+    duk_put_prop_string(plugin->context(), -2, "File");
+    duk_pop(plugin->context());
+}
+
+void dukx_new_file(duk_context* ctx, file* fp)
+{
+    assert(ctx);
+    assert(fp);
+
+    StackAssert sa(ctx);
+
+    duk_push_this(ctx);
+    duk_push_pointer(ctx, fp);
+    duk_put_prop_string(ctx, -2, signature);
+    duk_pop(ctx);
+}
+
+void dukx_push_file(duk_context* ctx, file* fp)
+{
+    assert(ctx);
+    assert(fp);
+
+    StackAssert sa(ctx, 1);
+
+    duk_push_object(ctx);
+    duk_push_pointer(ctx, fp);
+    duk_put_prop_string(ctx, -2, signature);
+    duk_get_global_string(ctx, prototype);
+    duk_set_prototype(ctx, -2);
+}
+
+file* dukx_require_file(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);
+    auto fp = static_cast<file*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+
+    return fp;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/file_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,183 @@
+/*
+ * file_jsapi.hpp -- Irccd.File API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_JS_FILE_JSAPI_HPP
+#define IRCCD_JS_FILE_JSAPI_HPP
+
+/**
+ * \file file_jsapi.hpp
+ * \brief Irccd.File Javascript API.
+ */
+
+#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& path() const noexcept
+    {
+        return path_;
+    }
+
+    /**
+     * Get the handle.
+     *
+     * \return the handle or nullptr if the stream was closed
+     */
+    inline std::FILE* 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::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc jsapi::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+/**
+ * Construct the file as this.
+ *
+ * The object prototype takes ownership of fp and will be deleted once
+ * collected.
+ *
+ * \pre fp != nullptr
+ * \param ctx the the context
+ * \param fp the file
+ */
+void dukx_new_file(duk_context* ctx, file* fp);
+
+/**
+ * Push a file.
+ *
+ * \pre fp != nullptr
+ * \param ctx the the context
+ * \param fp the file
+ */
+void dukx_push_file(duk_context* ctx, file* fp);
+
+/**
+ * Require a file. Raises a JavaScript error if not a File.
+ *
+ * \param ctx the context
+ * \param index the index
+ */
+file* dukx_require_file(duk_context* ctx, duk_idx_t index);
+
+} // !irccd
+
+#endif // !IRCCD_JS_FILE_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/irccd_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,232 @@
+/*
+ * irccd_jsapi.cpp -- Irccd API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include <cerrno>
+#include <string>
+#include <unordered_map>
+
+#include "irccd_jsapi.hpp"
+#include "js_plugin.hpp"
+
+namespace irccd {
+
+namespace {
+
+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
+
+system_error::system_error()
+    : errno_(errno)
+    , message_(std::strerror(errno_))
+{
+}
+
+system_error::system_error(int e, std::string message)
+    : errno_(e)
+    , message_(std::move(message))
+{
+}
+
+void system_error::raise(duk_context *ctx) const
+{
+    StackAssert sa(ctx, 0);
+
+    duk_get_global_string(ctx, "Irccd");
+    duk_get_prop_string(ctx, -1, "SystemError");
+    duk_remove(ctx, -2);
+    duk_push_int(ctx, errno_);
+    dukx_push_std_string(ctx, message_);
+    duk_new(ctx, 2);
+    duk_throw(ctx);
+}
+
+std::string irccd_jsapi::name() const
+{
+    return "Irccd";
+}
+
+void irccd_jsapi::load(irccd& irccd, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    // irccd.
+    duk_push_object(plugin->context());
+
+    // Version.
+    duk_push_object(plugin->context());
+    duk_push_int(plugin->context(), IRCCD_VERSION_MAJOR);
+    duk_put_prop_string(plugin->context(), -2, "major");
+    duk_push_int(plugin->context(), IRCCD_VERSION_MINOR);
+    duk_put_prop_string(plugin->context(), -2, "minor");
+    duk_push_int(plugin->context(), IRCCD_VERSION_PATCH);
+    duk_put_prop_string(plugin->context(), -2, "patch");
+    duk_put_prop_string(plugin->context(), -2, "version");
+
+    // Create the system_error that inherits from Error.
+    duk_push_c_function(plugin->context(), constructor, 2);
+
+    // Put errno codes into the irccd.system_error object.
+    for (const auto& pair : errors) {
+        duk_push_int(plugin->context(), pair.second);
+        duk_put_prop_string(plugin->context(), -2, pair.first.c_str());
+    }
+
+    duk_push_object(plugin->context());
+    duk_get_global_string(plugin->context(), "Error");
+    duk_get_prop_string(plugin->context(), -1, "prototype");
+    duk_remove(plugin->context(), -2);
+    duk_set_prototype(plugin->context(), -2);
+    duk_put_prop_string(plugin->context(), -2, "prototype");
+    duk_put_prop_string(plugin->context(), -2, "SystemError");
+
+    // Set irccd as global.
+    duk_put_global_string(plugin->context(), "Irccd");
+
+    // Store global instance.
+    duk_push_pointer(plugin->context(), &irccd);
+    duk_put_global_string(plugin->context(), "\xff""\xff""irccd-ref");
+}
+
+irccd& dukx_get_irccd(duk_context *ctx)
+{
+    StackAssert sa(ctx);
+
+    duk_get_global_string(ctx, "\xff""\xff""irccd-ref");
+    auto ptr = static_cast<irccd*>(duk_to_pointer(ctx, -1));
+    duk_pop(ctx);
+
+    return *ptr;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/irccd_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,92 @@
+/*
+ * irccd_jsapi.hpp -- Irccd API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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 "jsapi.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Custom Javascript exception for system error.
+ */
+class system_error {
+private:
+    int errno_;
+    std::string message_;
+
+public:
+    /**
+     * Create a system error from the current errno value.
+     */
+    system_error();
+
+    /**
+     * Create a system error with the given errno and message.
+     *
+     * \param e the errno number
+     * \param message the message
+     */
+    system_error(int e, std::string message);
+
+    /**
+     * Raise the SystemError Javascript exception.
+     *
+     * \param ctx the context
+     */
+    void raise(duk_context* ctx) const;
+};
+
+/**
+ * \brief Irccd Javascript API.
+ * \ingroup jsapi
+ */
+class irccd_jsapi : public jsapi {
+public:
+    /**
+     * \copydoc jsapi::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc jsapi::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+/**
+ * Get irccd instance stored in this context.
+ *
+ * \param ctx the context
+ * \return the irccd reference
+ */
+irccd& dukx_get_irccd(duk_context* ctx);
+
+} // !irccd
+
+#endif // !IRCCD_JS_IRCCD_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/js_plugin.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,397 @@
+/*
+ * plugin-js.cpp -- Javascript plugins for irccd
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cstring>
+#include <cerrno>
+#include <fstream>
+#include <iterator>
+#include <stdexcept>
+
+#include "irccd.hpp"
+#include "logger.hpp"
+//#include "plugin_module.hpp"
+//#include "server_module.hpp"
+#include "js_plugin.hpp"
+#include "service.hpp"
+#include "server_jsapi.hpp"
+//#include "timer.hpp"
+
+namespace irccd {
+
+const std::string js_plugin::config_property{"\xff""\xff""irccd-plugin-config"};
+const std::string js_plugin::format_property{"\xff""\xff""irccd-plugin-format"};
+const std::string js_plugin::paths_property{"\xff""\xff""irccd-plugin-paths"};
+
+std::unordered_map<std::string, std::string> js_plugin::get_table(const std::string& name) const
+{
+    StackAssert sa(context_);
+    std::unordered_map<std::string, std::string> result;
+
+    duk_get_global_string(context_, name.c_str());
+    dukx_enumerate(context_, -1, 0, true, [&] (auto ctx) {
+        result.emplace(duk_to_string(ctx, -2), duk_to_string(ctx, -1));
+    });
+    duk_pop(context_);
+
+    return result;
+}
+
+void js_plugin::put_table(const std::string& name, const std::unordered_map<std::string, std::string>& vars)
+{
+    StackAssert sa(context_);
+
+    duk_get_global_string(context_, name.c_str());
+
+    for (const auto &pair : vars) {
+        dukx_push_std_string(context_, pair.second);
+        duk_put_prop_string(context_, -2, pair.first.c_str());
+    }
+
+    duk_pop(context_);
+}
+
+void js_plugin::call(const std::string& name, unsigned nargs)
+{
+    duk_get_global_string(context_, name.c_str());
+
+    if (duk_get_type(context_, -1) == DUK_TYPE_UNDEFINED)
+        // Function not defined, remove the undefined value and all arguments.
+        duk_pop_n(context_, nargs + 1);
+    else {
+        // Call the function and discard the result.
+        duk_insert(context_, -nargs - 1);
+
+        if (duk_pcall(context_, nargs) != 0)
+            throw dukx_exception(context_, -1, true);
+
+        duk_pop(context_);
+    }
+}
+
+js_plugin::js_plugin(std::string name, std::string path)
+    : plugin(name, path)
+{
+    StackAssert sa(context_);
+
+    /*
+     * Create two special tables for configuration and formats, they are
+     * referenced later as
+     *
+     *   - Irccd.Plugin.config
+     *   - Irccd.Plugin.format
+     *   - Irccd.Plugin.paths
+     *
+     * In js_plugin_module.cpp.
+     */
+    duk_push_object(context_);
+    duk_put_global_string(context_, config_property.c_str());
+    duk_push_object(context_);
+    duk_put_global_string(context_, format_property.c_str());
+    duk_push_object(context_);
+    duk_put_global_string(context_, paths_property.c_str());
+
+    duk_push_pointer(context_, this);
+    duk_put_global_string(context_, "\xff""\xff""plugin");
+    dukx_push_std_string(context_, name);
+    duk_put_global_string(context_, "\xff""\xff""name");
+    dukx_push_std_string(context_, path);
+    duk_put_global_string(context_, "\xff""\xff""path");
+}
+
+void js_plugin::open()
+{
+    std::ifstream input(path());
+
+    if (!input)
+        throw std::runtime_error(std::strerror(errno));
+
+    std::string data(
+        std::istreambuf_iterator<char>(input.rdbuf()),
+        std::istreambuf_iterator<char>()
+    );
+
+    if (duk_peval_string(context_, data.c_str()))
+        throw dukx_exception(context_, -1);
+
+    // Read metadata.
+    duk_get_global_string(context_, "info");
+
+    if (duk_get_type(context_, -1) == DUK_TYPE_OBJECT) {
+        duk_get_prop_string(context_, -1, "author");
+        set_author(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : author());
+        duk_get_prop_string(context_, -2, "license");
+        set_license(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : license());
+        duk_get_prop_string(context_, -3, "summary");
+        set_summary(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : summary());
+        duk_get_prop_string(context_, -4, "version");
+        set_version(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : version());
+        duk_pop_n(context_, 4);
+    }
+
+    duk_pop(context_);
+}
+
+void js_plugin::on_channel_mode(irccd& , const channel_mode_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    dukx_push_std_string(context_, event.mode);
+    dukx_push_std_string(context_, event.argument);
+    call("onChannelMode", 5);
+}
+
+void js_plugin::on_channel_notice(irccd& , const channel_notice_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    dukx_push_std_string(context_, event.message);
+    call("onChannelNotice", 4);
+}
+
+void js_plugin::on_command(irccd& , const message_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    dukx_push_std_string(context_, event.message);
+    call("onCommand", 4);
+}
+
+void js_plugin::on_connect(irccd& , const connect_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    call("onConnect", 1);
+}
+
+void js_plugin::on_invite(irccd& , const invite_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    call("onInvite", 3);
+}
+
+void js_plugin::on_join(irccd& , const join_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    call("onJoin", 3);
+}
+
+void js_plugin::on_kick(irccd& , const kick_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    dukx_push_std_string(context_, event.target);
+    dukx_push_std_string(context_, event.reason);
+    call("onKick", 5);
+}
+
+void js_plugin::on_load(irccd&)
+{
+    StackAssert sa(context_);
+
+    call("onLoad", 0);
+}
+
+void js_plugin::on_message(irccd& , const message_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    dukx_push_std_string(context_, event.message);
+    call("onMessage", 4);
+}
+
+void js_plugin::on_me(irccd& , const me_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    dukx_push_std_string(context_, event.message);
+    call("onMe", 4);
+}
+
+void js_plugin::on_mode(irccd& , const mode_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.mode);
+    call("onMode", 3);
+}
+
+void js_plugin::on_names(irccd& , const names_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.channel);
+    dukx_push_array(context_, event.names, dukx_push_std_string);
+    call("onNames", 3);
+}
+
+void js_plugin::on_nick(irccd& , const nick_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.nickname);
+    call("onNick", 3);
+}
+
+void js_plugin::on_notice(irccd& , const notice_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.message);
+    call("onNotice", 3);
+}
+
+void js_plugin::on_part(irccd& , const part_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    dukx_push_std_string(context_, event.reason);
+    call("onPart", 4);
+}
+
+void js_plugin::on_query(irccd& , const query_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.message);
+    call("onQuery", 3);
+}
+
+void js_plugin::on_query_command(irccd& , const query_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.message);
+    call("onQueryCommand", 3);
+}
+
+void js_plugin::on_reload(irccd& )
+{
+    StackAssert sa(context_);
+
+    call("onReload");
+}
+
+void js_plugin::on_topic(irccd& , const topic_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    dukx_push_std_string(context_, event.origin);
+    dukx_push_std_string(context_, event.channel);
+    dukx_push_std_string(context_, event.topic);
+    call("onTopic", 4);
+}
+
+void js_plugin::on_unload(irccd& )
+{
+    StackAssert sa(context_);
+
+    call("onUnload");
+}
+
+void js_plugin::on_whois(irccd& , const whois_event &event)
+{
+    StackAssert sa(context_);
+
+    dukx_push_server(context_, std::move(event.server));
+    duk_push_object(context_);
+    dukx_push_std_string(context_, event.whois.nick);
+    duk_put_prop_string(context_, -2, "nickname");
+    dukx_push_std_string(context_, event.whois.user);
+    duk_put_prop_string(context_, -2, "username");
+    dukx_push_std_string(context_, event.whois.realname);
+    duk_put_prop_string(context_, -2, "realname");
+    dukx_push_std_string(context_, event.whois.host);
+    duk_put_prop_string(context_, -2, "host");
+    dukx_push_array(context_, event.whois.channels, dukx_push_std_string);
+    duk_put_prop_string(context_, -2, "channels");
+    call("onWhois", 2);
+}
+
+js_plugin_loader::js_plugin_loader(irccd& irccd) noexcept
+    : plugin_loader({}, { ".js" })
+    , irccd_(irccd)
+{
+}
+
+js_plugin_loader::~js_plugin_loader() noexcept = default;
+
+std::shared_ptr<plugin> js_plugin_loader::open(const std::string& id,
+                                               const std::string& path) noexcept
+{
+    if (path.rfind(".js") == std::string::npos)
+        return nullptr;
+
+    try {
+        auto plugin = std::make_shared<js_plugin>(id, path);
+
+        for (const auto& mod : modules_)
+            mod->load(irccd_, plugin);
+
+        plugin->open();
+
+        return plugin;
+    } catch (const std::exception &ex) {
+        log::warning() << "plugin " << id << ": " << ex.what() << std::endl;
+    }
+
+    return nullptr;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/js_plugin.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,297 @@
+/*
+ * js_plugin.hpp -- JavaScript plugins for irccd
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_JS_PLUGIN_HPP
+#define IRCCD_JS_PLUGIN_HPP
+
+/**
+ * \file js_plugin.hpp
+ * \brief JavaScript plugins for irccd.
+ */
+
+#include <vector>
+
+#include "duktape.hpp"
+#include "plugin.hpp"
+
+namespace irccd {
+
+class jsapi;
+
+/**
+ * \brief JavaScript plugins for irccd.
+ * \ingroup plugins
+ */
+class js_plugin : public plugin {
+public:
+    /**
+     * Global property where to read/write plugin configuration (object).
+     */
+    static const std::string config_property;
+
+    /**
+     * Global property where to read/write plugin formats (object).
+     */
+    static const std::string format_property;
+
+    /**
+     * Global property where paths are defined (object).
+     */
+    static const std::string paths_property;
+
+private:
+    // JavaScript context
+    UniqueContext context_;
+
+    // Private helpers.
+    std::unordered_map<std::string, std::string> get_table(const std::string&) const;
+    void put_table(const std::string&, const std::unordered_map<std::string, std::string>&);
+    void call(const std::string&, unsigned = 0);
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param name the plugin name
+     * \param path the path to the plugin
+     */
+    js_plugin(std::string name, std::string path);
+
+    /**
+     * Access the Duktape context.
+     *
+     * \return the context
+     */
+    inline UniqueContext& context() noexcept
+    {
+        return context_;
+    }
+
+    /**
+     * Open the script file associated.
+     */
+    void open();
+
+    /**
+     * \copydoc Plugin::config
+     */
+    plugin_config config() override
+    {
+        return get_table(config_property);
+    }
+
+    /**
+     * \copydoc Plugin::setConfig
+     */
+    void set_config(plugin_config config) override
+    {
+        put_table(config_property, config);
+    }
+
+    /**
+     * \copydoc Plugin::formats
+     */
+    plugin_formats formats() override
+    {
+        return get_table(format_property);
+    }
+
+    /**
+     * \copydoc Plugin::setFormats
+     */
+    void set_formats(plugin_formats formats) override
+    {
+        put_table(format_property, formats);
+    }
+
+    /**
+     * \copydoc Plugin::paths
+     */
+    plugin_paths paths() override
+    {
+        return get_table(paths_property);
+    }
+
+    /**
+     * \copydoc Plugin::set_paths
+     */
+    void set_paths(plugin_paths paths) override
+    {
+        put_table(paths_property, std::move(paths));
+    }
+
+    /**
+     * \copydoc Plugin::on_command
+     */
+    void on_command(irccd& irccd, const message_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_connect
+     */
+    void on_connect(irccd& irccd, const connect_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_channel_mode
+     */
+    void on_channel_mode(irccd& irccd, const channel_mode_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_channel_notice
+     */
+    void on_channel_notice(irccd& irccd, const channel_notice_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_invite
+     */
+    void on_invite(irccd& irccd, const invite_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_join
+     */
+    void on_join(irccd& irccd, const join_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_kick
+     */
+    void on_kick(irccd& irccd, const kick_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_load
+     */
+    void on_load(irccd& irccd) override;
+
+    /**
+     * \copydoc Plugin::on_message
+     */
+    void on_message(irccd& irccd, const message_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_me
+     */
+    void on_me(irccd& irccd, const me_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_mode
+     */
+    void on_mode(irccd& irccd, const mode_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_names
+     */
+    void on_names(irccd& irccd, const names_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_nick
+     */
+    void on_nick(irccd& irccd, const nick_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_notice
+     */
+    void on_notice(irccd& irccd, const notice_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_part
+     */
+    void on_part(irccd& irccd, const part_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_query
+     */
+    void on_query(irccd& irccd, const query_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_query_command
+     */
+    void on_query_command(irccd& irccd, const query_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_reload
+     */
+    void on_reload(irccd& irccd) override;
+
+    /**
+     * \copydoc Plugin::on_topic
+     */
+    void on_topic(irccd& irccd, const topic_event& event) override;
+
+    /**
+     * \copydoc Plugin::on_unload
+     */
+    void on_unload(irccd& irccd) override;
+
+    /**
+     * \copydoc Plugin::on_whois
+     */
+    void on_whois(irccd& irccd, const whois_event& event) override;
+};
+
+/**
+ * \brief Implementation for searching Javascript plugins.
+ */
+class js_plugin_loader : public plugin_loader {
+public:
+    using modules_t = std::vector<std::unique_ptr<jsapi>>;
+
+private:
+    irccd& irccd_;
+    modules_t modules_;
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param irccd the irccd instance
+     */
+    js_plugin_loader(irccd& irccd) noexcept;
+
+    /**
+     * Destructor defaulted.
+     */
+    ~js_plugin_loader() noexcept;
+
+    /**
+     * Get the list of modules.
+     *
+     * \return the modules
+     */
+    inline const modules_t& modules() const noexcept
+    {
+        return modules_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the modules
+     */
+    inline modules_t& modules() noexcept
+    {
+        return modules_;
+    }
+
+    /**
+     * \copydoc PluginLoader::open
+     */
+    std::shared_ptr<plugin> open(const std::string& id,
+                                 const std::string& path) noexcept override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_JS_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,75 @@
+/*
+ * jsapi.hpp -- Javascript API module
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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 <memory>
+#include <string>
+
+#include "duktape.hpp"
+
+namespace irccd {
+
+class irccd;
+class js_plugin;
+
+/**
+ * \brief Javascript API module.
+ */
+class jsapi {
+public:
+    /**
+     * Default constructor.
+     */
+    jsapi() noexcept = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~jsapi() noexcept = default;
+
+    /**
+     * Get the module name.
+     *
+     * \return the name
+     */
+    virtual std::string 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_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,103 @@
+/*
+ * logger_jsapi.cpp -- Irccd.Logger API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/logger.hpp>
+
+#include "js_plugin.hpp"
+#include "logger_jsapi.hpp"
+#include "plugin_jsapi.hpp"
+
+namespace irccd {
+
+namespace {
+
+duk_ret_t print(duk_context* ctx, std::ostream &out)
+{
+    out << "plugin " << dukx_get_plugin(ctx)->name() << ": " << duk_require_string(ctx, 0) << std::endl;
+
+    return 0;
+}
+
+/*
+ * Function: Irccd.Logger.info(message)
+ * --------------------------------------------------------
+ *
+ * Write a verbose message.
+ *
+ * Arguments:
+ *   - message, the message.
+ */
+duk_ret_t info(duk_context* ctx)
+{
+    return print(ctx, log::info());
+}
+
+/*
+ * Function: irccd.Logger.warning(message)
+ * --------------------------------------------------------
+ *
+ * Write a warning message.
+ *
+ * Arguments:
+ *   - message, the warning.
+ */
+duk_ret_t warning(duk_context* ctx)
+{
+    return print(ctx, log::warning());
+}
+
+/*
+ * Function: Logger.debug(message)
+ * --------------------------------------------------------
+ *
+ * Write a debug message, only shown if irccd is compiled in debug.
+ *
+ * Arguments:
+ *   - message, the message.
+ */
+duk_ret_t debug(duk_context* ctx)
+{
+    return print(ctx, log::debug());
+}
+
+const duk_function_list_entry functions[] = {
+    { "info",       info,       1 },
+    { "warning",    warning,    1 },
+    { "debug",      debug,      1 },
+    { nullptr,      nullptr,    0 }
+};
+
+} // !namespace
+
+std::string logger_jsapi::name() const
+{
+    return "Irccd.Logger";
+}
+
+void logger_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, functions);
+    duk_put_prop_string(plugin->context(), -2, "Logger");
+    duk_pop(plugin->context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/logger_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,50 @@
+/*
+ * logger_jsapi.hpp -- Irccd.Logger API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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::name
+     */
+    std::string 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_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,385 @@
+/*
+ * plugin_jsapi.cpp -- Irccd.Plugin API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/irccd.hpp>
+#include <irccd/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";
+
+/*
+ * wrap
+ * ------------------------------------------------------------------
+ *
+ * Wrap function for these functions because they all takes the same arguments.
+ *
+ * - load,
+ * - reload,
+ * - unload.
+ */
+template <typename Func>
+duk_idx_t wrap(duk_context* ctx, int nret, Func&& func)
+{
+    std::string name = duk_require_string(ctx, 0);
+
+    try {
+        func(dukx_get_irccd(ctx), name);
+    } catch (const std::out_of_range &ex) {
+        dukx_throw(ctx, ReferenceError(ex.what()));
+    } catch (const std::exception &ex) {
+        dukx_throw(ctx, Error(ex.what()));
+    }
+
+    return nret;
+}
+
+/*
+ * 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, const std::string& name)
+{
+    if (!duk_is_object(ctx, 0))
+        duk_error(ctx, DUK_ERR_TYPE_ERROR, "'%s' property must be object", name.c_str());
+
+    // Merge old table with new one.
+    duk_get_global_string(ctx, name.c_str());
+    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.c_str());
+
+    return 0;
+}
+
+/*
+ * get
+ * ------------------------------------------------------------------
+ *
+ * Get the Irccd.Plugin.(config|format|paths) property.
+ */
+duk_ret_t get(duk_context* ctx, const std::string& name)
+{
+    duk_get_global_string(ctx, name.c_str());
+
+    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);
+}
+
+/*
+ * 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.
+ */
+duk_idx_t info(duk_context* ctx)
+{
+    std::shared_ptr<plugin> plugin;
+
+    if (duk_get_top(ctx) >= 1)
+        plugin = dukx_get_irccd(ctx).plugins().get(duk_require_string(ctx, 0));
+    else
+        plugin = dukx_get_plugin(ctx);
+
+    if (!plugin)
+        return 0;
+
+    duk_push_object(ctx);
+    dukx_push_std_string(ctx, plugin->name());
+    duk_put_prop_string(ctx, -2, "name");
+    dukx_push_std_string(ctx, plugin->author());
+    duk_put_prop_string(ctx, -2, "author");
+    dukx_push_std_string(ctx, plugin->license());
+    duk_put_prop_string(ctx, -2, "license");
+    dukx_push_std_string(ctx, plugin->summary());
+    duk_put_prop_string(ctx, -2, "summary");
+    dukx_push_std_string(ctx, plugin->version());
+    duk_put_prop_string(ctx, -2, "version");
+
+    return 1;
+}
+
+/*
+ * 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 list(duk_context* ctx)
+{
+    dukx_push_array(ctx, dukx_get_irccd(ctx).plugins().list(), [] (auto ctx, auto plugin) {
+        dukx_push_std_string(ctx, plugin->name());
+    });
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Plugin.load(name)
+ * ------------------------------------------------------------------
+ *
+ * Load a plugin by name. This function will search through the standard
+ * directories.
+ *
+ * Arguments:
+ *   - name, the plugin identifier.
+ * Throws:
+ *   - Error on errors,
+ *   - ReferenceError if the plugin was not found.
+ */
+duk_idx_t load(duk_context* ctx)
+{
+    return wrap(ctx, 0, [&] (irccd &irccd, const std::string &name) {
+        irccd.plugins().load(name);
+    });
+}
+
+/*
+ * Function: Irccd.Plugin.reload(name)
+ * ------------------------------------------------------------------
+ *
+ * Reload a plugin by name.
+ *
+ * Arguments:
+ *   - name, the plugin identifier.
+ * Throws:
+ *   - Error on errors,
+ *   - ReferenceError if the plugin was not found.
+ */
+duk_idx_t reload(duk_context* ctx)
+{
+    return wrap(ctx, 0, [&] (irccd &irccd, const std::string &name) {
+        irccd.plugins().reload(name);
+    });
+}
+
+/*
+ * Function: Irccd.Plugin.unload(name)
+ * ------------------------------------------------------------------
+ *
+ * Unload a plugin by name.
+ *
+ * Arguments:
+ *   - name, the plugin identifier.
+ * Throws:
+ *   - Error on errors,
+ *   - ReferenceError if the plugin was not found.
+ */
+duk_idx_t unload(duk_context* ctx)
+{
+    return wrap(ctx, 0, [&] (irccd &irccd, const std::string &name) {
+        irccd.plugins().unload(name);
+    });
+}
+
+const duk_function_list_entry functions[] = {
+    { "info",   info,       DUK_VARARGS     },
+    { "list",   list,       0               },
+    { "load",   load,       1               },
+    { "reload", reload,     1               },
+    { "unload", unload,     1               },
+    { nullptr,  nullptr,    0               }
+};
+
+} // !namespace
+
+std::string plugin_jsapi::name() const
+{
+    return "Irccd.Plugin";
+}
+
+void plugin_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_push_pointer(plugin->context(), new std::weak_ptr<js_plugin>(plugin));
+    duk_push_object(plugin->context());
+    duk_push_c_function(plugin->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->context(), -2);
+    duk_put_global_string(plugin->context(), "\xff""\xff""dummy-shared-ptr");
+    duk_put_global_string(plugin->context(), plugin_ref);
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, functions);
+
+    // 'config' property.
+    duk_push_string(plugin->context(), "config");
+    duk_push_c_function(plugin->context(), get_config, 0);
+    duk_push_c_function(plugin->context(), set_config, 1);
+    duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
+    // 'format' property.
+    duk_push_string(plugin->context(), "format");
+    duk_push_c_function(plugin->context(), get_format, 0);
+    duk_push_c_function(plugin->context(), set_format, 1);
+    duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
+    // 'format' property.
+    duk_push_string(plugin->context(), "paths");
+    duk_push_c_function(plugin->context(), get_paths, 0);
+    duk_push_c_function(plugin->context(), set_paths, 1);
+    duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
+    duk_put_prop_string(plugin->context(), -2, "Plugin");
+    duk_pop(plugin->context());
+}
+
+std::shared_ptr<js_plugin> dukx_get_plugin(duk_context* ctx)
+{
+    StackAssert 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();
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/plugin_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,58 @@
+/*
+ * plugin_jsapi.hpp -- Irccd.Plugin API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_JS_PLUGIN_JSAPI_HPP
+#define IRCCD_JS_PLUGIN_JSAPI_HPP
+
+/**
+ * \file plugin_jsapi.hpp
+ * \brief Irccd.Plugin Javascript API.
+ */
+
+#include "jsapi.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Irccd.Plugin Javascript API.
+ * \ingroup jsapi
+ */
+class plugin_jsapi : public jsapi {
+public:
+    /**
+     * \copydoc jsapi::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc Module::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+/**
+ * Access the plugin stored in this context.
+ *
+ * \param ctx the context
+ * \return the plugin
+ */
+std::shared_ptr<js_plugin> dukx_get_plugin(duk_context* ctx);
+
+} // !irccd
+
+#endif // !IRCCD_JS_PLUGIN_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/server_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,581 @@
+/*
+ * server_jsapi.cpp -- Irccd.Server API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+#include <sstream>
+#include <unordered_map>
+
+#include <irccd.hpp>
+#include <irccd/server.hpp>
+#include <irccd/service.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)
+{
+    StackAssert 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);
+}
+
+/*
+ * Method: Server.cmode(channel, mode)
+ * ------------------------------------------------------------------
+ *
+ * Change a channel mode.
+ *
+ * Arguments:
+ *   - channel, the channel,
+ *   - mode, the mode.
+ */
+duk_ret_t cmode(duk_context* ctx)
+{
+    self(ctx)->cmode(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+    return 0;
+}
+
+/*
+ * Method: Server.cnotice(channel, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a channel notice.
+ *
+ * Arguments:
+ *   - channel, the channel,
+ *   - message, the message.
+ */
+duk_ret_t cnotice(duk_context* ctx)
+{
+    self(ctx)->cnotice(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+    return 0;
+}
+
+/*
+ * Method: Server.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 info(duk_context* ctx)
+{
+    auto server = self(ctx);
+
+    duk_push_object(ctx);
+    dukx_push_std_string(ctx, server->name());
+    duk_put_prop_string(ctx, -2, "name");
+    dukx_push_std_string(ctx, server->host());
+    duk_put_prop_string(ctx, -2, "host");
+    duk_push_int(ctx, server->port());
+    duk_put_prop_string(ctx, -2, "port");
+    duk_push_boolean(ctx, server->flags() & server::ssl);
+    duk_put_prop_string(ctx, -2, "ssl");
+    duk_push_boolean(ctx, server->flags() & server::ssl_verify);
+    duk_put_prop_string(ctx, -2, "sslVerify");
+    dukx_push_std_string(ctx, server->command_char());
+    duk_put_prop_string(ctx, -2, "commandChar");
+    dukx_push_std_string(ctx, server->realname());
+    duk_put_prop_string(ctx, -2, "realname");
+    dukx_push_std_string(ctx, server->nickname());
+    duk_put_prop_string(ctx, -2, "nickname");
+    dukx_push_std_string(ctx, server->username());
+    duk_put_prop_string(ctx, -2, "username");
+    dukx_push_array(ctx, server->channels(), [&] (auto ctx, auto channel) {
+        dukx_push_std_string(ctx, channel);
+    });
+    duk_put_prop_string(ctx, -2, "channels");
+
+    return 1;
+}
+
+/*
+ * Method: Server.invite(target, channel)
+ * ------------------------------------------------------------------
+ *
+ * Invite someone to a channel.
+ *
+ * Arguments:
+ *   - target, the target to invite,
+ *   - channel, the channel.
+ */
+duk_ret_t invite(duk_context* ctx)
+{
+    self(ctx)->invite(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+    return 0;
+}
+
+/*
+ * Method: Server.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.
+ */
+duk_ret_t join(duk_context* ctx)
+{
+    self(ctx)->join(duk_require_string(ctx, 0), dukx_get_std_string(ctx, 1));
+
+    return 0;
+}
+
+/*
+ * Method: Server.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.
+ */
+duk_ret_t kick(duk_context* ctx)
+{
+    self(ctx)->kick(duk_require_string(ctx, 0), duk_require_string(ctx, 1), dukx_get_std_string(ctx, 2));
+
+    return 0;
+}
+
+/*
+ * Method: Server.me(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a CTCP Action.
+ *
+ * Arguments:
+ *   - target, the target or a channel,
+ *   - message, the message.
+ */
+duk_ret_t me(duk_context* ctx)
+{
+    self(ctx)->me(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+    return 0;
+}
+
+/*
+ * Method: Server.message(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a message.
+ *
+ * Arguments:
+ *   - target, the target or a channel,
+ *   - message, the message.
+ */
+duk_ret_t message(duk_context* ctx)
+{
+    self(ctx)->message(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+    return 0;
+}
+
+/*
+ * Method: Server.mode(mode)
+ * ------------------------------------------------------------------
+ *
+ * Change your mode.
+ *
+ * Arguments:
+ *   - mode, the new mode.
+ */
+duk_ret_t mode(duk_context* ctx)
+{
+    self(ctx)->mode(duk_require_string(ctx, 0));
+
+    return 0;
+}
+
+/*
+ * Method: Server.names(channel)
+ * ------------------------------------------------------------------
+ *
+ * Get the list of names from a channel.
+ *
+ * Arguments:
+ *   - channel, the channel.
+ */
+duk_ret_t names(duk_context* ctx)
+{
+    self(ctx)->names(duk_require_string(ctx, 0));
+
+    return 0;
+}
+
+/*
+ * Method: Server.nick(nickname)
+ * ------------------------------------------------------------------
+ *
+ * Change the nickname.
+ *
+ * Arguments:
+ *   - nickname, the nickname.
+ */
+duk_ret_t nick(duk_context* ctx)
+{
+    self(ctx)->set_nickname(duk_require_string(ctx, 0));
+
+    return 0;
+}
+
+/*
+ * Method: Server.notice(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a private notice.
+ *
+ * Arguments:
+ *   - target, the target,
+ *   - message, the notice message.
+ */
+duk_ret_t notice(duk_context* ctx)
+{
+    self(ctx)->notice(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+    return 0;
+}
+
+/*
+ * Method: Server.part(channel, reason = undefined)
+ * ------------------------------------------------------------------
+ *
+ * Leave a channel.
+ *
+ * Arguments:
+ *   - channel, the channel to leave,
+ *   - reason, the optional reason, keep undefined for portability.
+ */
+duk_ret_t part(duk_context* ctx)
+{
+    self(ctx)->part(duk_require_string(ctx, 0), dukx_get_std_string(ctx, 1));
+
+    return 0;
+}
+
+/*
+ * Method: Server.send(raw)
+ * ------------------------------------------------------------------
+ *
+ * Send a raw message to the IRC server.
+ *
+ * Arguments:
+ *   - raw, the raw message (without terminators).
+ */
+duk_ret_t send(duk_context* ctx)
+{
+    self(ctx)->send(duk_require_string(ctx, 0));
+
+    return 0;
+}
+
+/*
+ * Method: Server.topic(channel, topic)
+ * ------------------------------------------------------------------
+ *
+ * Change a channel topic.
+ *
+ * Arguments:
+ *   - channel, the channel,
+ *   - topic, the new topic.
+ */
+duk_ret_t topic(duk_context* ctx)
+{
+    self(ctx)->topic(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+    return 0;
+}
+
+/*
+ * Method: Server.whois(target)
+ * ------------------------------------------------------------------
+ *
+ * Get whois information.
+ *
+ * Arguments:
+ *   - target, the target.
+ */
+duk_ret_t whois(duk_context* ctx)
+{
+    self(ctx)->whois(duk_require_string(ctx, 0));
+
+    return 0;
+}
+
+/*
+ * Method: Server.toString()
+ * ------------------------------------------------------------------
+ *
+ * Convert the object to std::string, convenience for adding the object
+ * as property key.
+ *
+ * duk_ret_turns:
+ *   The server name (unique).
+ */
+duk_ret_t toString(duk_context* ctx)
+{
+    dukx_push_std_string(ctx, self(ctx)->name());
+
+    return 1;
+}
+
+/*
+ * 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: "!")
+ */
+duk_ret_t constructor(duk_context* ctx)
+{
+    if (!duk_is_constructor_call(ctx))
+        return 0;
+
+    duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
+
+    try {
+        auto json = duk_json_encode(ctx, 0);
+        auto s = server::from_json(nlohmann::json::parse(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);
+    } catch (const std::exception& ex) {
+        duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
+    }
+
+    return 0;
+}
+
+/*
+ * Function: Irccd.Server() [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Delete the property.
+ */
+duk_ret_t 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;
+}
+
+/*
+ * Function: Irccd.Server.add(s)
+ * ------------------------------------------------------------------
+ *
+ * Register a new server to the irccd instance.
+ *
+ * Arguments:
+ *   - s, the server to add.
+ */
+duk_ret_t add(duk_context* ctx)
+{
+    dukx_get_irccd(ctx).servers().add(dukx_require_server(ctx, 0));
+
+    return 0;
+}
+
+/*
+ * Function: Irccd.Server.find(name)
+ * ------------------------------------------------------------------
+ *
+ * Find a server by name.
+ *
+ * Arguments:
+ *   - name, the server name
+ * duk_ret_turns:
+ *   The server object or undefined if not found.
+ */
+duk_ret_t find(duk_context* ctx)
+{
+    auto server = dukx_get_irccd(ctx).servers().get(duk_require_string(ctx, 0));
+
+    if (!server)
+        return 0;
+
+    dukx_push_server(ctx, server);
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Server.list()
+ * ------------------------------------------------------------------
+ *
+ * Get the map of all loaded servers.
+ *
+ * duk_ret_turns:
+ *   An object with string-to-servers pairs.
+ */
+duk_ret_t list(duk_context* ctx)
+{
+    duk_push_object(ctx);
+
+    for (const auto &server : dukx_get_irccd(ctx).servers().servers()) {
+        dukx_push_server(ctx, server);
+        duk_put_prop_string(ctx, -2, server->name().c_str());
+    }
+
+    return 1;
+}
+
+/*
+ * 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 remove(duk_context* ctx)
+{
+    dukx_get_irccd(ctx).servers().remove(duk_require_string(ctx, 0));
+
+    return 0;
+}
+
+const duk_function_list_entry methods[] = {
+    { "cmode",      cmode,      2           },
+    { "cnotice",    cnotice,    2           },
+    { "info",       info,       0           },
+    { "invite",     invite,     2           },
+    { "join",       join,       DUK_VARARGS },
+    { "kick",       kick,       DUK_VARARGS },
+    { "me",         me,         2           },
+    { "message",    message,    2           },
+    { "mode",       mode,       1           },
+    { "names",      names,      1           },
+    { "nick",       nick,       1           },
+    { "notice",     notice,     2           },
+    { "part",       part,       DUK_VARARGS },
+    { "send",       send,       1           },
+    { "topic",      topic,      2           },
+    { "whois",      whois,      1           },
+    { "toString",   toString,   0           },
+    { nullptr,      nullptr,    0           }
+};
+
+const duk_function_list_entry functions[] = {
+    { "add",        add,        1           },
+    { "find",       find,       1           },
+    { "list",       list,       0           },
+    { "remove",     remove,     1           },
+    { nullptr,      nullptr,    0           }
+};
+
+} // !namespace
+
+std::string server_jsapi::name() const
+{
+    return "Irccd.Server";
+}
+
+void server_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_c_function(plugin->context(), constructor, 1);
+    duk_put_function_list(plugin->context(), -1, functions);
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, methods);
+    duk_push_c_function(plugin->context(), destructor, 1);
+    duk_set_finalizer(plugin->context(), -2);
+    duk_dup_top(plugin->context());
+    duk_put_global_string(plugin->context(), prototype);
+    duk_put_prop_string(plugin->context(), -2, "prototype");
+    duk_put_prop_string(plugin->context(), -2, "Server");
+    duk_pop(plugin->context());
+}
+
+void dukx_push_server(duk_context* ctx, std::shared_ptr<server> server)
+{
+    assert(ctx);
+    assert(server);
+
+    StackAssert 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> dukx_require_server(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;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/server_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,69 @@
+/*
+ * server_jsapi.hpp -- Irccd.Server API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_JS_SERVER_JSAPI_HPP
+#define IRCCD_JS_SERVER_JSAPI_HPP
+
+/**
+ * \file mod-server.hpp
+ * \brief irccd.Server Javascript API.
+ */
+
+#include "jsapi.hpp"
+#include "server.hpp"
+
+namespace irccd {
+
+/**
+ * \brief irccd.Server Javascript API.
+ * \ingroup jsapi
+ */
+class server_jsapi : public jsapi {
+public:
+    /**
+     * \copydoc jsapi::name
+     */
+    std::string name() const override;
+
+    /**
+     * \copydoc jsapi::load
+     */
+    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
+};
+
+/**
+ * Push a server.
+ *
+ * \pre server != nullptr
+ * \param ctx the context
+ * \param server the server
+ */
+void dukx_push_server(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
+ */
+std::shared_ptr<server> dukx_require_server(duk_context* ctx, duk_idx_t index);
+
+} // !irccd
+
+#endif // !IRCCD_JS_SERVER_JSAPI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/system_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,244 @@
+/*
+ * system_jsapi.cpp -- Irccd.System API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <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 {
+
+/*
+ * Function: irccd.System.env(key)
+ * ------------------------------------------------------------------
+ *
+ * Get an environment system variable.
+ *
+ * Arguments:
+ *   - key, the environment variable.
+ * Returns:
+ *   The value.
+ */
+duk_ret_t env(duk_context* ctx)
+{
+    dukx_push_std_string(ctx, sys::env(dukx_get_std_string(ctx, 0)));
+
+    return 1;
+}
+
+/*
+ * Function: irccd.System.exec(cmd)
+ * ------------------------------------------------------------------
+ *
+ * Execute a system command.
+ *
+ * Arguments:
+ *   - cmd, the command to execute.
+ */
+duk_ret_t exec(duk_context* ctx)
+{
+    std::system(duk_get_string(ctx, 0));
+
+    return 0;
+}
+
+/*
+ * Function: irccd.System.home()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system user's home.
+ *
+ * Returns:
+ *   The user home directory.
+ */
+duk_ret_t home(duk_context* ctx)
+{
+    dukx_push_std_string(ctx, sys::home());
+
+    return 1;
+}
+
+/*
+ * Function: irccd.System.name()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system name.
+ *
+ * Returns:
+ *   The system name.
+ */
+duk_ret_t name(duk_context* ctx)
+{
+    dukx_push_std_string(ctx, sys::name());
+
+    return 1;
+}
+
+#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.system_error on failures.
+ */
+duk_ret_t popen(duk_context* ctx)
+{
+    auto fp = ::popen(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+    if (fp == nullptr)
+        dukx_throw(ctx, system_error());
+
+    dukx_push_file(ctx, new file(fp, [] (auto fp) { ::pclose(fp); }));
+
+    return 1;
+}
+
+#endif // !HAVE_POPEN
+
+/*
+ * Function: irccd.System.sleep(delay)
+ * ------------------------------------------------------------------
+ *
+ * Sleep the main loop for the specific delay in seconds.
+ */
+duk_ret_t sleep(duk_context* ctx)
+{
+    std::this_thread::sleep_for(std::chrono::seconds(duk_get_int(ctx, 0)));
+
+    return 0;
+}
+
+/*
+ * Function: irccd.System.ticks()
+ * ------------------------------------------------------------------
+ *
+ * Get the number of milliseconds since irccd was started.
+ *
+ * Returns:
+ *   The number of milliseconds.
+ */
+duk_ret_t ticks(duk_context* ctx)
+{
+    duk_push_int(ctx, sys::ticks());
+
+    return 1;
+}
+
+/*
+ * Function: irccd.System.usleep(delay)
+ * ------------------------------------------------------------------
+ *
+ * Sleep the main loop for the specific delay in microseconds.
+ */
+duk_ret_t usleep(duk_context* ctx)
+{
+    std::this_thread::sleep_for(std::chrono::microseconds(duk_get_int(ctx, 0)));
+
+    return 0;
+}
+
+/*
+ * Function: irccd.System.uptime()
+ * ------------------------------------------------------------------
+ *
+ * Get the system uptime.
+ *
+ * Returns:
+ *   The system uptime.
+ */
+duk_ret_t uptime(duk_context* ctx)
+{
+    duk_push_int(ctx, sys::uptime());
+
+    return 0;
+}
+
+/*
+ * Function: irccd.System.version()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system version.
+ *
+ * Returns:
+ *   The system version.
+ */
+duk_ret_t version(duk_context* ctx)
+{
+    dukx_push_std_string(ctx, sys::version());
+
+    return 1;
+}
+
+const duk_function_list_entry functions[] = {
+    { "env",        env,        1 },
+    { "exec",       exec,       1 },
+    { "home",       home,       0 },
+    { "name",       name,       0 },
+#if defined(HAVE_POPEN)
+    { "popen",      popen,      2 },
+#endif
+    { "sleep",      sleep,      1 },
+    { "ticks",      ticks,      0 },
+    { "uptime",     uptime,     0 },
+    { "usleep",     usleep,     1 },
+    { "version",    version,    0 },
+    { nullptr,      nullptr,    0 }
+};
+
+} // !namespace
+
+std::string system_jsapi::name() const
+{
+    return "Irccd.System";
+}
+
+void system_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, functions);
+    duk_put_prop_string(plugin->context(), -2, "System");
+    duk_pop(plugin->context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/system_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,50 @@
+/*
+ * system_jsapi.hpp -- Irccd.System API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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::name
+     */
+    std::string 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.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,104 @@
+/*
+ * timer.cpp -- threaded timers
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+#include <chrono>
+
+#include <iostream>
+
+#include "timer.hpp"
+
+namespace irccd {
+
+void timer::run()
+{
+    while (state_ != state::stopped) {
+        std::unique_lock<std::mutex> lock(mutex_);
+
+        // Wait in case the timer is paused.
+        condition_.wait(lock, [&] () {
+            return state_ == state::running;
+        });
+
+        if (state_ != state::running)
+            continue;
+
+        // Wait the timer delay or the interrupt.
+        condition_.wait_for(lock, std::chrono::milliseconds(delay_), [&] () {
+            return state_ != state::running;
+        });
+
+        if (state_ == state::running) {
+            // Signal process.
+            on_signal();
+
+            if (type_ == type::single)
+                state_ = state::stopped;
+        }
+    }
+
+    on_end();
+}
+
+timer::timer(type type, unsigned delay) noexcept
+    : type_(type)
+    , delay_(delay)
+    , thread_(std::bind(&timer::run, this))
+{
+}
+
+timer::~timer()
+{
+    assert(state_ != state::running);
+
+    try {
+        {
+            std::lock_guard<std::mutex> lk(mutex_);
+
+            state_ = state::stopped;
+            condition_.notify_one();
+        }
+
+        thread_.join();
+    } catch (...) {
+    }
+}
+
+void timer::start()
+{
+    assert(state_ != state::running);
+
+    {
+        std::lock_guard<std::mutex> lk(mutex_);
+        state_ = state::running;
+    }
+
+    condition_.notify_one();
+}
+
+void timer::stop()
+{
+    {
+        std::lock_guard<std::mutex> lk(mutex_);
+        state_ = state::paused;
+    }
+
+    condition_.notify_one();
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/timer.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,154 @@
+/*
+ * timer.hpp -- threaded timers
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_TIMER_HPP
+#define IRCCD_TIMER_HPP
+
+/**
+ * \file timer.hpp
+ * \brief Provides interval based timers for JavaScript
+ */
+
+#include <atomic>
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <thread>
+
+#include "signals.hpp"
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Timer class
+ *
+ * A timer is a thread object that emits a signal periodically or just one time. It is perfectly pausable and resumable
+ * to reuse the same object.
+ *
+ * The delay is configured in milliseconds and the user has choice to use any
+ * delay needed.
+ *
+ * We use a condition variable to wait for the specified delay unless the timer
+ * must be stopped.
+ */
+class timer {
+public:
+    /**
+     * \brief Type of timer
+     */
+    enum class type {
+        single,             //!< The timer ends after execution
+        repeat              //!< The timer loops
+    };
+
+    /**
+     * Signal: onSignal
+     * ----------------------------------------------------------
+     *
+     * Called when the timeout expires.
+     */
+    Signal<> on_signal;
+
+    /**
+     * Signal: onEnd
+     * ----------------------------------------------------------
+     *
+     * Called when the timeout ends.
+     */
+    Signal<> on_end;
+
+private:
+    enum class state {
+        paused,
+        running,
+        stopped
+    };
+
+    type type_;
+    unsigned delay_;
+
+    // Thread management.
+    std::atomic<state> state_{state::paused};
+    std::mutex mutex_;
+    std::condition_variable condition_;
+    std::thread thread_;
+
+    void run();
+
+public:
+    /**
+     * Timer constructor.
+     *
+     * The timer is not started, use start().
+     *
+     * \param type the timer type
+     * \param delay the delay in milliseconds
+     * \post isRunning() returns false
+     */
+    timer(type type, unsigned delay) noexcept;
+
+    /**
+     * Destructor, closes the thread.
+     *
+     * \pre stop() must have been called.
+     */
+    virtual ~timer();
+
+    /**
+     * Start the thread.
+     *
+     * \pre isRunning() must return false
+     * \pre onSignal() must have been called
+     * \pre onEnd() must have been called
+     * \note Thread-safe
+     */
+    void start();
+
+    /**
+     * Stop the timer, may be used by the user to stop it.
+     *
+     * \note Thread-safe
+     */
+    void stop();
+
+    /**
+     * Get the type of timer.
+     *
+     * \return the type.
+     */
+    inline type get_type() const noexcept
+    {
+        return type_;
+    }
+
+    /**
+     * Tells if the timer has still a running thread.
+     *
+     * \return true if still alive
+     * \note Thread-safe
+     */
+    inline bool is_running() const noexcept
+    {
+        return state_ == state::running;
+    }
+};
+
+} // !irccd
+
+#endif // !IRCCD_TIMER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/timer_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,208 @@
+/*
+ * timer_jsapi.cpp -- Irccd.timer API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/irccd.hpp>
+#include <irccd/logger.hpp>
+#include <irccd/string_util.hpp>
+
+#include "irccd_jsapi.hpp"
+#include "js_plugin.hpp"
+#include "plugin_jsapi.hpp"
+#include "timer.hpp"
+#include "timer_jsapi.hpp"
+
+namespace irccd {
+
+namespace {
+
+const char* signature("\xff""\xff""irccd-timer-ptr");
+const char* callback_table("\xff""\xff""irccd-timer-callbacks");
+
+void handle_signal(irccd& instance, std::weak_ptr<js_plugin> ptr, std::string key)
+{
+    auto plugin = ptr.lock();
+
+    if (!plugin)
+        return;
+
+    instance.post([plugin, key] (irccd &) {
+        StackAssert sa(plugin->context());
+
+        duk_get_global_string(plugin->context(), callback_table);
+        duk_get_prop_string(plugin->context(), -1, key.c_str());
+        duk_remove(plugin->context(), -2);
+
+        if (duk_is_callable(plugin->context(), -1)) {
+            if (duk_pcall(plugin->context(), 0) != 0)
+                log::warning(string_util::sprintf("plugin %s: %s", plugin->name(),
+                    dukx_exception(plugin->context(), -1).stack));
+            else
+                duk_pop(plugin->context());
+        } else
+            duk_pop(plugin->context());
+    });
+}
+
+std::shared_ptr<timer> self(duk_context* ctx)
+{
+    StackAssert 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<std::shared_ptr<timer>*>(ptr);
+}
+
+/*
+ * Method: timer.start()
+ * --------------------------------------------------------
+ *
+ * Start the timer. If the timer is already started the method is a no-op.
+ */
+duk_ret_t start(duk_context* ctx)
+{
+    auto timer = self(ctx);
+
+    if (!timer->is_running())
+        timer->start();
+
+    return 0;
+}
+
+/*
+ * Method: timer.stop()
+ * --------------------------------------------------------
+ *
+ * Stop the timer.
+ */
+duk_ret_t stop(duk_context* ctx)
+{
+    auto timer = self(ctx);
+
+    if (timer->is_running())
+        timer->stop();
+
+    return 0;
+}
+
+const duk_function_list_entry methods[] = {
+    { "start",  start,      0 },
+    { "stop",   stop,       0 },
+    { nullptr,  nullptr,    0 }
+};
+
+/*
+ * 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 constructor(duk_context* ctx)
+{
+    // Check parameters.
+    auto type = duk_require_int(ctx, 0);
+    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");
+
+    // Construct the timer in 'this'.
+    auto& irccd = dukx_get_irccd(ctx);
+    auto tm = std::make_shared<timer>(static_cast<timer::type>(type), delay);
+    auto hash = std::to_string(reinterpret_cast<std::uintptr_t>(tm.get()));
+
+    tm->on_signal.connect(std::bind(handle_signal, std::ref(irccd),
+        std::weak_ptr<js_plugin>(dukx_get_plugin(ctx)), hash));
+
+    duk_push_this(ctx);
+    duk_push_pointer(ctx, new std::shared_ptr<timer>(std::move(tm)));
+    duk_put_prop_string(ctx, -2, signature);
+    duk_push_string(ctx, hash.c_str());
+    duk_put_prop_string(ctx, -2, "\xff""\xff""timer-key");
+    duk_push_c_function(ctx, [] (duk_context* ctx) -> duk_ret_t {
+        StackAssert sa(ctx);
+
+        duk_get_prop_string(ctx, 0, "\xff""\xff""timer-key");
+        auto hash = duk_get_string(ctx, -1);
+        duk_pop(ctx);
+        duk_get_prop_string(ctx, 0, signature);
+        static_cast<std::shared_ptr<timer>*>(duk_to_pointer(ctx, -1))->get()->stop();
+        delete static_cast<std::shared_ptr<timer>*>(duk_to_pointer(ctx, -1));
+        duk_pop(ctx);
+        duk_get_global_string(ctx, callback_table);
+        duk_del_prop_string(ctx, -1, hash);
+        duk_pop(ctx);
+        log::debug("plugin: timer destroyed");
+
+        return 0;
+    }, 1);
+    duk_set_finalizer(ctx, -2);
+
+    // Save a callback function into the callback table.
+    duk_get_global_string(ctx, callback_table);
+    duk_dup(ctx, 2);
+    duk_put_prop_string(ctx, -2, hash.c_str());
+    duk_pop(ctx);
+
+    return 0;
+}
+
+const duk_number_list_entry constants[] = {
+    { "Single",     static_cast<int>(timer::type::single)   },
+    { "Repeat",     static_cast<int>(timer::type::repeat)   },
+    { nullptr,      0                                       }
+};
+
+} // !namespace
+
+std::string timer_jsapi::name() const
+{
+    return "Irccd.Timer";
+}
+
+void timer_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_c_function(plugin->context(), constructor, 3);
+    duk_put_number_list(plugin->context(), -1, constants);
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, methods);
+    duk_put_prop_string(plugin->context(), -2, "prototype");
+    duk_put_prop_string(plugin->context(), -2, "Timer");
+    duk_pop(plugin->context());
+    duk_push_object(plugin->context());
+    duk_put_global_string(plugin->context(), callback_table);
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/timer_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,50 @@
+/*
+ * timer_jsapi.hpp -- Irccd.Timer API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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::name
+     */
+    std::string 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.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,4796 @@
+/*
+ * unicode.cpp -- UTF-8 to UTF-32 conversions and various operations
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "unicode.hpp"
+
+/*
+ * The following code has been generated from Go mkrunetype adapted to our
+ * needs.
+ */
+
+namespace irccd {
+
+namespace unicode {
+
+#define nelem(x) (sizeof (x) / sizeof ((x)[0]))
+
+namespace {
+
+const char32_t *rbsearch(char32_t c, const char32_t *t, int n, int ne) noexcept
+{
+   const char32_t *p;
+   int m;
+
+   while (n > 1) {
+       m = n >> 1;
+       p = t + m * ne;
+
+       if (c >= p[0]) {
+           t = p;
+           n = n - m;
+       } else
+           n = m;
+   }
+
+   if (n && c >= t[0])
+       return t;
+
+   return nullptr;
+}
+
+} // !namespace
+
+namespace {
+
+const char32_t isspacer[] = {
+    0x0009, 0x000d,
+    0x0020, 0x0020,
+    0x0085, 0x0085,
+    0x00a0, 0x00a0,
+    0x1680, 0x1680,
+    0x2000, 0x200a,
+    0x2028, 0x2029,
+    0x202f, 0x202f,
+    0x205f, 0x205f,
+    0x3000, 0x3000,
+    0xfeff, 0xfeff,
+};
+
+} // !namespace
+
+bool isspace(char32_t c) noexcept
+{
+   const char32_t *p;
+
+   p = rbsearch(c, isspacer, nelem (isspacer)/2, 2);
+
+   if (p && c >= p[0] && c <= p[1])
+       return true;
+
+   return false;
+}
+
+namespace {
+
+const char32_t isdigitr[] = {
+    0x0030, 0x0039,
+    0x0660, 0x0669,
+    0x06f0, 0x06f9,
+    0x07c0, 0x07c9,
+    0x0966, 0x096f,
+    0x09e6, 0x09ef,
+    0x0a66, 0x0a6f,
+    0x0ae6, 0x0aef,
+    0x0b66, 0x0b6f,
+    0x0be6, 0x0bef,
+    0x0c66, 0x0c6f,
+    0x0ce6, 0x0cef,
+    0x0d66, 0x0d6f,
+    0x0de6, 0x0def,
+    0x0e50, 0x0e59,
+    0x0ed0, 0x0ed9,
+    0x0f20, 0x0f29,
+    0x1040, 0x1049,
+    0x1090, 0x1099,
+    0x17e0, 0x17e9,
+    0x1810, 0x1819,
+    0x1946, 0x194f,
+    0x19d0, 0x19d9,
+    0x1a80, 0x1a89,
+    0x1a90, 0x1a99,
+    0x1b50, 0x1b59,
+    0x1bb0, 0x1bb9,
+    0x1c40, 0x1c49,
+    0x1c50, 0x1c59,
+    0xa620, 0xa629,
+    0xa8d0, 0xa8d9,
+    0xa900, 0xa909,
+    0xa9d0, 0xa9d9,
+    0xa9f0, 0xa9f9,
+    0xaa50, 0xaa59,
+    0xabf0, 0xabf9,
+    0xff10, 0xff19,
+    0x104a0, 0x104a9,
+    0x11066, 0x1106f,
+    0x110f0, 0x110f9,
+    0x11136, 0x1113f,
+    0x111d0, 0x111d9,
+    0x112f0, 0x112f9,
+    0x114d0, 0x114d9,
+    0x11650, 0x11659,
+    0x116c0, 0x116c9,
+    0x118e0, 0x118e9,
+    0x16a60, 0x16a69,
+    0x16b50, 0x16b59,
+    0x1d7ce, 0x1d7ff,
+};
+
+} // !namespace
+
+bool isdigit(char32_t c) noexcept
+{
+   const char32_t *p;
+
+   p = rbsearch(c, isdigitr, nelem (isdigitr)/2, 2);
+
+   if (p && c >= p[0] && c <= p[1])
+       return true;
+
+   return false;
+}
+
+namespace {
+
+const char32_t isalphar[] = {
+    0x0041, 0x005a,
+    0x0061, 0x007a,
+    0x00c0, 0x00d6,
+    0x00d8, 0x00f6,
+    0x00f8, 0x02c1,
+    0x02c6, 0x02d1,
+    0x02e0, 0x02e4,
+    0x0370, 0x0374,
+    0x0376, 0x0377,
+    0x037a, 0x037d,
+    0x0388, 0x038a,
+    0x038e, 0x03a1,
+    0x03a3, 0x03f5,
+    0x03f7, 0x0481,
+    0x048a, 0x052f,
+    0x0531, 0x0556,
+    0x0561, 0x0587,
+    0x05d0, 0x05ea,
+    0x05f0, 0x05f2,
+    0x0620, 0x064a,
+    0x066e, 0x066f,
+    0x0671, 0x06d3,
+    0x06e5, 0x06e6,
+    0x06ee, 0x06ef,
+    0x06fa, 0x06fc,
+    0x0712, 0x072f,
+    0x074d, 0x07a5,
+    0x07ca, 0x07ea,
+    0x07f4, 0x07f5,
+    0x0800, 0x0815,
+    0x0840, 0x0858,
+    0x08a0, 0x08b2,
+    0x0904, 0x0939,
+    0x0958, 0x0961,
+    0x0971, 0x0980,
+    0x0985, 0x098c,
+    0x098f, 0x0990,
+    0x0993, 0x09a8,
+    0x09aa, 0x09b0,
+    0x09b6, 0x09b9,
+    0x09dc, 0x09dd,
+    0x09df, 0x09e1,
+    0x09f0, 0x09f1,
+    0x0a05, 0x0a0a,
+    0x0a0f, 0x0a10,
+    0x0a13, 0x0a28,
+    0x0a2a, 0x0a30,
+    0x0a32, 0x0a33,
+    0x0a35, 0x0a36,
+    0x0a38, 0x0a39,
+    0x0a59, 0x0a5c,
+    0x0a72, 0x0a74,
+    0x0a85, 0x0a8d,
+    0x0a8f, 0x0a91,
+    0x0a93, 0x0aa8,
+    0x0aaa, 0x0ab0,
+    0x0ab2, 0x0ab3,
+    0x0ab5, 0x0ab9,
+    0x0ae0, 0x0ae1,
+    0x0b05, 0x0b0c,
+    0x0b0f, 0x0b10,
+    0x0b13, 0x0b28,
+    0x0b2a, 0x0b30,
+    0x0b32, 0x0b33,
+    0x0b35, 0x0b39,
+    0x0b5c, 0x0b5d,
+    0x0b5f, 0x0b61,
+    0x0b85, 0x0b8a,
+    0x0b8e, 0x0b90,
+    0x0b92, 0x0b95,
+    0x0b99, 0x0b9a,
+    0x0b9e, 0x0b9f,
+    0x0ba3, 0x0ba4,
+    0x0ba8, 0x0baa,
+    0x0bae, 0x0bb9,
+    0x0c05, 0x0c0c,
+    0x0c0e, 0x0c10,
+    0x0c12, 0x0c28,
+    0x0c2a, 0x0c39,
+    0x0c58, 0x0c59,
+    0x0c60, 0x0c61,
+    0x0c85, 0x0c8c,
+    0x0c8e, 0x0c90,
+    0x0c92, 0x0ca8,
+    0x0caa, 0x0cb3,
+    0x0cb5, 0x0cb9,
+    0x0ce0, 0x0ce1,
+    0x0cf1, 0x0cf2,
+    0x0d05, 0x0d0c,
+    0x0d0e, 0x0d10,
+    0x0d12, 0x0d3a,
+    0x0d60, 0x0d61,
+    0x0d7a, 0x0d7f,
+    0x0d85, 0x0d96,
+    0x0d9a, 0x0db1,
+    0x0db3, 0x0dbb,
+    0x0dc0, 0x0dc6,
+    0x0e01, 0x0e30,
+    0x0e32, 0x0e33,
+    0x0e40, 0x0e46,
+    0x0e81, 0x0e82,
+    0x0e87, 0x0e88,
+    0x0e94, 0x0e97,
+    0x0e99, 0x0e9f,
+    0x0ea1, 0x0ea3,
+    0x0eaa, 0x0eab,
+    0x0ead, 0x0eb0,
+    0x0eb2, 0x0eb3,
+    0x0ec0, 0x0ec4,
+    0x0edc, 0x0edf,
+    0x0f40, 0x0f47,
+    0x0f49, 0x0f6c,
+    0x0f88, 0x0f8c,
+    0x1000, 0x102a,
+    0x1050, 0x1055,
+    0x105a, 0x105d,
+    0x1065, 0x1066,
+    0x106e, 0x1070,
+    0x1075, 0x1081,
+    0x10a0, 0x10c5,
+    0x10d0, 0x10fa,
+    0x10fc, 0x1248,
+    0x124a, 0x124d,
+    0x1250, 0x1256,
+    0x125a, 0x125d,
+    0x1260, 0x1288,
+    0x128a, 0x128d,
+    0x1290, 0x12b0,
+    0x12b2, 0x12b5,
+    0x12b8, 0x12be,
+    0x12c2, 0x12c5,
+    0x12c8, 0x12d6,
+    0x12d8, 0x1310,
+    0x1312, 0x1315,
+    0x1318, 0x135a,
+    0x1380, 0x138f,
+    0x13a0, 0x13f4,
+    0x1401, 0x166c,
+    0x166f, 0x167f,
+    0x1681, 0x169a,
+    0x16a0, 0x16ea,
+    0x16f1, 0x16f8,
+    0x1700, 0x170c,
+    0x170e, 0x1711,
+    0x1720, 0x1731,
+    0x1740, 0x1751,
+    0x1760, 0x176c,
+    0x176e, 0x1770,
+    0x1780, 0x17b3,
+    0x1820, 0x1877,
+    0x1880, 0x18a8,
+    0x18b0, 0x18f5,
+    0x1900, 0x191e,
+    0x1950, 0x196d,
+    0x1970, 0x1974,
+    0x1980, 0x19ab,
+    0x19c1, 0x19c7,
+    0x1a00, 0x1a16,
+    0x1a20, 0x1a54,
+    0x1b05, 0x1b33,
+    0x1b45, 0x1b4b,
+    0x1b83, 0x1ba0,
+    0x1bae, 0x1baf,
+    0x1bba, 0x1be5,
+    0x1c00, 0x1c23,
+    0x1c4d, 0x1c4f,
+    0x1c5a, 0x1c7d,
+    0x1ce9, 0x1cec,
+    0x1cee, 0x1cf1,
+    0x1cf5, 0x1cf6,
+    0x1d00, 0x1dbf,
+    0x1e00, 0x1f15,
+    0x1f18, 0x1f1d,
+    0x1f20, 0x1f45,
+    0x1f48, 0x1f4d,
+    0x1f50, 0x1f57,
+    0x1f5f, 0x1f7d,
+    0x1f80, 0x1fb4,
+    0x1fb6, 0x1fbc,
+    0x1fc2, 0x1fc4,
+    0x1fc6, 0x1fcc,
+    0x1fd0, 0x1fd3,
+    0x1fd6, 0x1fdb,
+    0x1fe0, 0x1fec,
+    0x1ff2, 0x1ff4,
+    0x1ff6, 0x1ffc,
+    0x2090, 0x209c,
+    0x210a, 0x2113,
+    0x2119, 0x211d,
+    0x212a, 0x212d,
+    0x212f, 0x2139,
+    0x213c, 0x213f,
+    0x2145, 0x2149,
+    0x2183, 0x2184,
+    0x2c00, 0x2c2e,
+    0x2c30, 0x2c5e,
+    0x2c60, 0x2ce4,
+    0x2ceb, 0x2cee,
+    0x2cf2, 0x2cf3,
+    0x2d00, 0x2d25,
+    0x2d30, 0x2d67,
+    0x2d80, 0x2d96,
+    0x2da0, 0x2da6,
+    0x2da8, 0x2dae,
+    0x2db0, 0x2db6,
+    0x2db8, 0x2dbe,
+    0x2dc0, 0x2dc6,
+    0x2dc8, 0x2dce,
+    0x2dd0, 0x2dd6,
+    0x2dd8, 0x2dde,
+    0x3005, 0x3006,
+    0x3031, 0x3035,
+    0x303b, 0x303c,
+    0x3041, 0x3096,
+    0x309d, 0x309f,
+    0x30a1, 0x30fa,
+    0x30fc, 0x30ff,
+    0x3105, 0x312d,
+    0x3131, 0x318e,
+    0x31a0, 0x31ba,
+    0x31f0, 0x31ff,
+    0x3400, 0x4db5,
+    0x4e00, 0x9fcc,
+    0xa000, 0xa48c,
+    0xa4d0, 0xa4fd,
+    0xa500, 0xa60c,
+    0xa610, 0xa61f,
+    0xa62a, 0xa62b,
+    0xa640, 0xa66e,
+    0xa67f, 0xa69d,
+    0xa6a0, 0xa6e5,
+    0xa717, 0xa71f,
+    0xa722, 0xa788,
+    0xa78b, 0xa78e,
+    0xa790, 0xa7ad,
+    0xa7b0, 0xa7b1,
+    0xa7f7, 0xa801,
+    0xa803, 0xa805,
+    0xa807, 0xa80a,
+    0xa80c, 0xa822,
+    0xa840, 0xa873,
+    0xa882, 0xa8b3,
+    0xa8f2, 0xa8f7,
+    0xa90a, 0xa925,
+    0xa930, 0xa946,
+    0xa960, 0xa97c,
+    0xa984, 0xa9b2,
+    0xa9e0, 0xa9e4,
+    0xa9e6, 0xa9ef,
+    0xa9fa, 0xa9fe,
+    0xaa00, 0xaa28,
+    0xaa40, 0xaa42,
+    0xaa44, 0xaa4b,
+    0xaa60, 0xaa76,
+    0xaa7e, 0xaaaf,
+    0xaab5, 0xaab6,
+    0xaab9, 0xaabd,
+    0xaadb, 0xaadd,
+    0xaae0, 0xaaea,
+    0xaaf2, 0xaaf4,
+    0xab01, 0xab06,
+    0xab09, 0xab0e,
+    0xab11, 0xab16,
+    0xab20, 0xab26,
+    0xab28, 0xab2e,
+    0xab30, 0xab5a,
+    0xab5c, 0xab5f,
+    0xab64, 0xab65,
+    0xabc0, 0xabe2,
+    0xac00, 0xd7a3,
+    0xd7b0, 0xd7c6,
+    0xd7cb, 0xd7fb,
+    0xf900, 0xfa6d,
+    0xfa70, 0xfad9,
+    0xfb00, 0xfb06,
+    0xfb13, 0xfb17,
+    0xfb1f, 0xfb28,
+    0xfb2a, 0xfb36,
+    0xfb38, 0xfb3c,
+    0xfb40, 0xfb41,
+    0xfb43, 0xfb44,
+    0xfb46, 0xfbb1,
+    0xfbd3, 0xfd3d,
+    0xfd50, 0xfd8f,
+    0xfd92, 0xfdc7,
+    0xfdf0, 0xfdfb,
+    0xfe70, 0xfe74,
+    0xfe76, 0xfefc,
+    0xff21, 0xff3a,
+    0xff41, 0xff5a,
+    0xff66, 0xffbe,
+    0xffc2, 0xffc7,
+    0xffca, 0xffcf,
+    0xffd2, 0xffd7,
+    0xffda, 0xffdc,
+    0x10000, 0x1000b,
+    0x1000d, 0x10026,
+    0x10028, 0x1003a,
+    0x1003c, 0x1003d,
+    0x1003f, 0x1004d,
+    0x10050, 0x1005d,
+    0x10080, 0x100fa,
+    0x10280, 0x1029c,
+    0x102a0, 0x102d0,
+    0x10300, 0x1031f,
+    0x10330, 0x10340,
+    0x10342, 0x10349,
+    0x10350, 0x10375,
+    0x10380, 0x1039d,
+    0x103a0, 0x103c3,
+    0x103c8, 0x103cf,
+    0x10400, 0x1049d,
+    0x10500, 0x10527,
+    0x10530, 0x10563,
+    0x10600, 0x10736,
+    0x10740, 0x10755,
+    0x10760, 0x10767,
+    0x10800, 0x10805,
+    0x1080a, 0x10835,
+    0x10837, 0x10838,
+    0x1083f, 0x10855,
+    0x10860, 0x10876,
+    0x10880, 0x1089e,
+    0x10900, 0x10915,
+    0x10920, 0x10939,
+    0x10980, 0x109b7,
+    0x109be, 0x109bf,
+    0x10a10, 0x10a13,
+    0x10a15, 0x10a17,
+    0x10a19, 0x10a33,
+    0x10a60, 0x10a7c,
+    0x10a80, 0x10a9c,
+    0x10ac0, 0x10ac7,
+    0x10ac9, 0x10ae4,
+    0x10b00, 0x10b35,
+    0x10b40, 0x10b55,
+    0x10b60, 0x10b72,
+    0x10b80, 0x10b91,
+    0x10c00, 0x10c48,
+    0x11003, 0x11037,
+    0x11083, 0x110af,
+    0x110d0, 0x110e8,
+    0x11103, 0x11126,
+    0x11150, 0x11172,
+    0x11183, 0x111b2,
+    0x111c1, 0x111c4,
+    0x11200, 0x11211,
+    0x11213, 0x1122b,
+    0x112b0, 0x112de,
+    0x11305, 0x1130c,
+    0x1130f, 0x11310,
+    0x11313, 0x11328,
+    0x1132a, 0x11330,
+    0x11332, 0x11333,
+    0x11335, 0x11339,
+    0x1135d, 0x11361,
+    0x11480, 0x114af,
+    0x114c4, 0x114c5,
+    0x11580, 0x115ae,
+    0x11600, 0x1162f,
+    0x11680, 0x116aa,
+    0x118a0, 0x118df,
+    0x11ac0, 0x11af8,
+    0x12000, 0x12398,
+    0x13000, 0x1342e,
+    0x16800, 0x16a38,
+    0x16a40, 0x16a5e,
+    0x16ad0, 0x16aed,
+    0x16b00, 0x16b2f,
+    0x16b40, 0x16b43,
+    0x16b63, 0x16b77,
+    0x16b7d, 0x16b8f,
+    0x16f00, 0x16f44,
+    0x16f93, 0x16f9f,
+    0x1b000, 0x1b001,
+    0x1bc00, 0x1bc6a,
+    0x1bc70, 0x1bc7c,
+    0x1bc80, 0x1bc88,
+    0x1bc90, 0x1bc99,
+    0x1d400, 0x1d454,
+    0x1d456, 0x1d49c,
+    0x1d49e, 0x1d49f,
+    0x1d4a5, 0x1d4a6,
+    0x1d4a9, 0x1d4ac,
+    0x1d4ae, 0x1d4b9,
+    0x1d4bd, 0x1d4c3,
+    0x1d4c5, 0x1d505,
+    0x1d507, 0x1d50a,
+    0x1d50d, 0x1d514,
+    0x1d516, 0x1d51c,
+    0x1d51e, 0x1d539,
+    0x1d53b, 0x1d53e,
+    0x1d540, 0x1d544,
+    0x1d54a, 0x1d550,
+    0x1d552, 0x1d6a5,
+    0x1d6a8, 0x1d6c0,
+    0x1d6c2, 0x1d6da,
+    0x1d6dc, 0x1d6fa,
+    0x1d6fc, 0x1d714,
+    0x1d716, 0x1d734,
+    0x1d736, 0x1d74e,
+    0x1d750, 0x1d76e,
+    0x1d770, 0x1d788,
+    0x1d78a, 0x1d7a8,
+    0x1d7aa, 0x1d7c2,
+    0x1d7c4, 0x1d7cb,
+    0x1e800, 0x1e8c4,
+    0x1ee00, 0x1ee03,
+    0x1ee05, 0x1ee1f,
+    0x1ee21, 0x1ee22,
+    0x1ee29, 0x1ee32,
+    0x1ee34, 0x1ee37,
+    0x1ee4d, 0x1ee4f,
+    0x1ee51, 0x1ee52,
+    0x1ee61, 0x1ee62,
+    0x1ee67, 0x1ee6a,
+    0x1ee6c, 0x1ee72,
+    0x1ee74, 0x1ee77,
+    0x1ee79, 0x1ee7c,
+    0x1ee80, 0x1ee89,
+    0x1ee8b, 0x1ee9b,
+    0x1eea1, 0x1eea3,
+    0x1eea5, 0x1eea9,
+    0x1eeab, 0x1eebb,
+    0x20000, 0x2a6d6,
+    0x2a700, 0x2b734,
+    0x2b740, 0x2b81d,
+    0x2f800, 0x2fa1d,
+};
+
+} // !namespace
+
+namespace {
+
+const char32_t isalphas[] = {
+    0x00aa,
+    0x00b5,
+    0x00ba,
+    0x02ec,
+    0x02ee,
+    0x037f,
+    0x0386,
+    0x038c,
+    0x0559,
+    0x06d5,
+    0x06ff,
+    0x0710,
+    0x07b1,
+    0x07fa,
+    0x081a,
+    0x0824,
+    0x0828,
+    0x093d,
+    0x0950,
+    0x09b2,
+    0x09bd,
+    0x09ce,
+    0x0a5e,
+    0x0abd,
+    0x0ad0,
+    0x0b3d,
+    0x0b71,
+    0x0b83,
+    0x0b9c,
+    0x0bd0,
+    0x0c3d,
+    0x0cbd,
+    0x0cde,
+    0x0d3d,
+    0x0d4e,
+    0x0dbd,
+    0x0e84,
+    0x0e8a,
+    0x0e8d,
+    0x0ea5,
+    0x0ea7,
+    0x0ebd,
+    0x0ec6,
+    0x0f00,
+    0x103f,
+    0x1061,
+    0x108e,
+    0x10c7,
+    0x10cd,
+    0x1258,
+    0x12c0,
+    0x17d7,
+    0x17dc,
+    0x18aa,
+    0x1aa7,
+    0x1f59,
+    0x1f5b,
+    0x1f5d,
+    0x1fbe,
+    0x2071,
+    0x207f,
+    0x2102,
+    0x2107,
+    0x2115,
+    0x2124,
+    0x2126,
+    0x2128,
+    0x214e,
+    0x2d27,
+    0x2d2d,
+    0x2d6f,
+    0x2e2f,
+    0xa8fb,
+    0xa9cf,
+    0xaa7a,
+    0xaab1,
+    0xaac0,
+    0xaac2,
+    0xfb1d,
+    0xfb3e,
+    0x10808,
+    0x1083c,
+    0x10a00,
+    0x11176,
+    0x111da,
+    0x1133d,
+    0x114c7,
+    0x11644,
+    0x118ff,
+    0x16f50,
+    0x1d4a2,
+    0x1d4bb,
+    0x1d546,
+    0x1ee24,
+    0x1ee27,
+    0x1ee39,
+    0x1ee3b,
+    0x1ee42,
+    0x1ee47,
+    0x1ee49,
+    0x1ee4b,
+    0x1ee54,
+    0x1ee57,
+    0x1ee59,
+    0x1ee5b,
+    0x1ee5d,
+    0x1ee5f,
+    0x1ee64,
+    0x1ee7e,
+};
+
+} // !namespace
+
+bool isalpha(char32_t c) noexcept
+{
+   const char32_t *p;
+
+   p = rbsearch(c, isalphar, nelem (isalphar)/2, 2);
+
+   if (p && c >= p[0] && c <= p[1])
+       return true;
+
+ p = rbsearch(c, isalphas, nelem (isalphas), 1);
+
+   if (p && c == p[0])
+       return true;
+
+ return false;
+}
+
+namespace {
+
+const char32_t isupperr[] = {
+    0x0041, 0x005a,
+    0x00c0, 0x00d6,
+    0x00d8, 0x00de,
+    0x0178, 0x0179,
+    0x0181, 0x0182,
+    0x0186, 0x0187,
+    0x0189, 0x018b,
+    0x018e, 0x0191,
+    0x0193, 0x0194,
+    0x0196, 0x0198,
+    0x019c, 0x019d,
+    0x019f, 0x01a0,
+    0x01a6, 0x01a7,
+    0x01ae, 0x01af,
+    0x01b1, 0x01b3,
+    0x01b7, 0x01b8,
+    0x01f6, 0x01f8,
+    0x023a, 0x023b,
+    0x023d, 0x023e,
+    0x0243, 0x0246,
+    0x0388, 0x038a,
+    0x038e, 0x038f,
+    0x0391, 0x03a1,
+    0x03a3, 0x03ab,
+    0x03d2, 0x03d4,
+    0x03f9, 0x03fa,
+    0x03fd, 0x042f,
+    0x04c0, 0x04c1,
+    0x0531, 0x0556,
+    0x10a0, 0x10c5,
+    0x1f08, 0x1f0f,
+    0x1f18, 0x1f1d,
+    0x1f28, 0x1f2f,
+    0x1f38, 0x1f3f,
+    0x1f48, 0x1f4d,
+    0x1f68, 0x1f6f,
+    0x1f88, 0x1f8f,
+    0x1f98, 0x1f9f,
+    0x1fa8, 0x1faf,
+    0x1fb8, 0x1fbc,
+    0x1fc8, 0x1fcc,
+    0x1fd8, 0x1fdb,
+    0x1fe8, 0x1fec,
+    0x1ff8, 0x1ffc,
+    0x210b, 0x210d,
+    0x2110, 0x2112,
+    0x2119, 0x211d,
+    0x212a, 0x212d,
+    0x2130, 0x2133,
+    0x213e, 0x213f,
+    0x2160, 0x216f,
+    0x24b6, 0x24cf,
+    0x2c00, 0x2c2e,
+    0x2c62, 0x2c64,
+    0x2c6d, 0x2c70,
+    0x2c7e, 0x2c80,
+    0xa77d, 0xa77e,
+    0xa7aa, 0xa7ad,
+    0xa7b0, 0xa7b1,
+    0xff21, 0xff3a,
+    0x10400, 0x10427,
+    0x118a0, 0x118bf,
+    0x1d400, 0x1d419,
+    0x1d434, 0x1d44d,
+    0x1d468, 0x1d481,
+    0x1d49e, 0x1d49f,
+    0x1d4a5, 0x1d4a6,
+    0x1d4a9, 0x1d4ac,
+    0x1d4ae, 0x1d4b5,
+    0x1d4d0, 0x1d4e9,
+    0x1d504, 0x1d505,
+    0x1d507, 0x1d50a,
+    0x1d50d, 0x1d514,
+    0x1d516, 0x1d51c,
+    0x1d538, 0x1d539,
+    0x1d53b, 0x1d53e,
+    0x1d540, 0x1d544,
+    0x1d54a, 0x1d550,
+    0x1d56c, 0x1d585,
+    0x1d5a0, 0x1d5b9,
+    0x1d5d4, 0x1d5ed,
+    0x1d608, 0x1d621,
+    0x1d63c, 0x1d655,
+    0x1d670, 0x1d689,
+    0x1d6a8, 0x1d6c0,
+    0x1d6e2, 0x1d6fa,
+    0x1d71c, 0x1d734,
+    0x1d756, 0x1d76e,
+    0x1d790, 0x1d7a8,
+};
+
+} // !namespace
+
+namespace {
+
+const char32_t isuppers[] = {
+    0x0100,
+    0x0102,
+    0x0104,
+    0x0106,
+    0x0108,
+    0x010a,
+    0x010c,
+    0x010e,
+    0x0110,
+    0x0112,
+    0x0114,
+    0x0116,
+    0x0118,
+    0x011a,
+    0x011c,
+    0x011e,
+    0x0120,
+    0x0122,
+    0x0124,
+    0x0126,
+    0x0128,
+    0x012a,
+    0x012c,
+    0x012e,
+    0x0130,
+    0x0132,
+    0x0134,
+    0x0136,
+    0x0139,
+    0x013b,
+    0x013d,
+    0x013f,
+    0x0141,
+    0x0143,
+    0x0145,
+    0x0147,
+    0x014a,
+    0x014c,
+    0x014e,
+    0x0150,
+    0x0152,
+    0x0154,
+    0x0156,
+    0x0158,
+    0x015a,
+    0x015c,
+    0x015e,
+    0x0160,
+    0x0162,
+    0x0164,
+    0x0166,
+    0x0168,
+    0x016a,
+    0x016c,
+    0x016e,
+    0x0170,
+    0x0172,
+    0x0174,
+    0x0176,
+    0x017b,
+    0x017d,
+    0x0184,
+    0x01a2,
+    0x01a4,
+    0x01a9,
+    0x01ac,
+    0x01b5,
+    0x01bc,
+    0x01c4,
+    0x01c7,
+    0x01ca,
+    0x01cd,
+    0x01cf,
+    0x01d1,
+    0x01d3,
+    0x01d5,
+    0x01d7,
+    0x01d9,
+    0x01db,
+    0x01de,
+    0x01e0,
+    0x01e2,
+    0x01e4,
+    0x01e6,
+    0x01e8,
+    0x01ea,
+    0x01ec,
+    0x01ee,
+    0x01f1,
+    0x01f4,
+    0x01fa,
+    0x01fc,
+    0x01fe,
+    0x0200,
+    0x0202,
+    0x0204,
+    0x0206,
+    0x0208,
+    0x020a,
+    0x020c,
+    0x020e,
+    0x0210,
+    0x0212,
+    0x0214,
+    0x0216,
+    0x0218,
+    0x021a,
+    0x021c,
+    0x021e,
+    0x0220,
+    0x0222,
+    0x0224,
+    0x0226,
+    0x0228,
+    0x022a,
+    0x022c,
+    0x022e,
+    0x0230,
+    0x0232,
+    0x0241,
+    0x0248,
+    0x024a,
+    0x024c,
+    0x024e,
+    0x0370,
+    0x0372,
+    0x0376,
+    0x037f,
+    0x0386,
+    0x038c,
+    0x03cf,
+    0x03d8,
+    0x03da,
+    0x03dc,
+    0x03de,
+    0x03e0,
+    0x03e2,
+    0x03e4,
+    0x03e6,
+    0x03e8,
+    0x03ea,
+    0x03ec,
+    0x03ee,
+    0x03f4,
+    0x03f7,
+    0x0460,
+    0x0462,
+    0x0464,
+    0x0466,
+    0x0468,
+    0x046a,
+    0x046c,
+    0x046e,
+    0x0470,
+    0x0472,
+    0x0474,
+    0x0476,
+    0x0478,
+    0x047a,
+    0x047c,
+    0x047e,
+    0x0480,
+    0x048a,
+    0x048c,
+    0x048e,
+    0x0490,
+    0x0492,
+    0x0494,
+    0x0496,
+    0x0498,
+    0x049a,
+    0x049c,
+    0x049e,
+    0x04a0,
+    0x04a2,
+    0x04a4,
+    0x04a6,
+    0x04a8,
+    0x04aa,
+    0x04ac,
+    0x04ae,
+    0x04b0,
+    0x04b2,
+    0x04b4,
+    0x04b6,
+    0x04b8,
+    0x04ba,
+    0x04bc,
+    0x04be,
+    0x04c3,
+    0x04c5,
+    0x04c7,
+    0x04c9,
+    0x04cb,
+    0x04cd,
+    0x04d0,
+    0x04d2,
+    0x04d4,
+    0x04d6,
+    0x04d8,
+    0x04da,
+    0x04dc,
+    0x04de,
+    0x04e0,
+    0x04e2,
+    0x04e4,
+    0x04e6,
+    0x04e8,
+    0x04ea,
+    0x04ec,
+    0x04ee,
+    0x04f0,
+    0x04f2,
+    0x04f4,
+    0x04f6,
+    0x04f8,
+    0x04fa,
+    0x04fc,
+    0x04fe,
+    0x0500,
+    0x0502,
+    0x0504,
+    0x0506,
+    0x0508,
+    0x050a,
+    0x050c,
+    0x050e,
+    0x0510,
+    0x0512,
+    0x0514,
+    0x0516,
+    0x0518,
+    0x051a,
+    0x051c,
+    0x051e,
+    0x0520,
+    0x0522,
+    0x0524,
+    0x0526,
+    0x0528,
+    0x052a,
+    0x052c,
+    0x052e,
+    0x10c7,
+    0x10cd,
+    0x1e00,
+    0x1e02,
+    0x1e04,
+    0x1e06,
+    0x1e08,
+    0x1e0a,
+    0x1e0c,
+    0x1e0e,
+    0x1e10,
+    0x1e12,
+    0x1e14,
+    0x1e16,
+    0x1e18,
+    0x1e1a,
+    0x1e1c,
+    0x1e1e,
+    0x1e20,
+    0x1e22,
+    0x1e24,
+    0x1e26,
+    0x1e28,
+    0x1e2a,
+    0x1e2c,
+    0x1e2e,
+    0x1e30,
+    0x1e32,
+    0x1e34,
+    0x1e36,
+    0x1e38,
+    0x1e3a,
+    0x1e3c,
+    0x1e3e,
+    0x1e40,
+    0x1e42,
+    0x1e44,
+    0x1e46,
+    0x1e48,
+    0x1e4a,
+    0x1e4c,
+    0x1e4e,
+    0x1e50,
+    0x1e52,
+    0x1e54,
+    0x1e56,
+    0x1e58,
+    0x1e5a,
+    0x1e5c,
+    0x1e5e,
+    0x1e60,
+    0x1e62,
+    0x1e64,
+    0x1e66,
+    0x1e68,
+    0x1e6a,
+    0x1e6c,
+    0x1e6e,
+    0x1e70,
+    0x1e72,
+    0x1e74,
+    0x1e76,
+    0x1e78,
+    0x1e7a,
+    0x1e7c,
+    0x1e7e,
+    0x1e80,
+    0x1e82,
+    0x1e84,
+    0x1e86,
+    0x1e88,
+    0x1e8a,
+    0x1e8c,
+    0x1e8e,
+    0x1e90,
+    0x1e92,
+    0x1e94,
+    0x1e9e,
+    0x1ea0,
+    0x1ea2,
+    0x1ea4,
+    0x1ea6,
+    0x1ea8,
+    0x1eaa,
+    0x1eac,
+    0x1eae,
+    0x1eb0,
+    0x1eb2,
+    0x1eb4,
+    0x1eb6,
+    0x1eb8,
+    0x1eba,
+    0x1ebc,
+    0x1ebe,
+    0x1ec0,
+    0x1ec2,
+    0x1ec4,
+    0x1ec6,
+    0x1ec8,
+    0x1eca,
+    0x1ecc,
+    0x1ece,
+    0x1ed0,
+    0x1ed2,
+    0x1ed4,
+    0x1ed6,
+    0x1ed8,
+    0x1eda,
+    0x1edc,
+    0x1ede,
+    0x1ee0,
+    0x1ee2,
+    0x1ee4,
+    0x1ee6,
+    0x1ee8,
+    0x1eea,
+    0x1eec,
+    0x1eee,
+    0x1ef0,
+    0x1ef2,
+    0x1ef4,
+    0x1ef6,
+    0x1ef8,
+    0x1efa,
+    0x1efc,
+    0x1efe,
+    0x1f59,
+    0x1f5b,
+    0x1f5d,
+    0x1f5f,
+    0x2102,
+    0x2107,
+    0x2115,
+    0x2124,
+    0x2126,
+    0x2128,
+    0x2145,
+    0x2183,
+    0x2c60,
+    0x2c67,
+    0x2c69,
+    0x2c6b,
+    0x2c72,
+    0x2c75,
+    0x2c82,
+    0x2c84,
+    0x2c86,
+    0x2c88,
+    0x2c8a,
+    0x2c8c,
+    0x2c8e,
+    0x2c90,
+    0x2c92,
+    0x2c94,
+    0x2c96,
+    0x2c98,
+    0x2c9a,
+    0x2c9c,
+    0x2c9e,
+    0x2ca0,
+    0x2ca2,
+    0x2ca4,
+    0x2ca6,
+    0x2ca8,
+    0x2caa,
+    0x2cac,
+    0x2cae,
+    0x2cb0,
+    0x2cb2,
+    0x2cb4,
+    0x2cb6,
+    0x2cb8,
+    0x2cba,
+    0x2cbc,
+    0x2cbe,
+    0x2cc0,
+    0x2cc2,
+    0x2cc4,
+    0x2cc6,
+    0x2cc8,
+    0x2cca,
+    0x2ccc,
+    0x2cce,
+    0x2cd0,
+    0x2cd2,
+    0x2cd4,
+    0x2cd6,
+    0x2cd8,
+    0x2cda,
+    0x2cdc,
+    0x2cde,
+    0x2ce0,
+    0x2ce2,
+    0x2ceb,
+    0x2ced,
+    0x2cf2,
+    0xa640,
+    0xa642,
+    0xa644,
+    0xa646,
+    0xa648,
+    0xa64a,
+    0xa64c,
+    0xa64e,
+    0xa650,
+    0xa652,
+    0xa654,
+    0xa656,
+    0xa658,
+    0xa65a,
+    0xa65c,
+    0xa65e,
+    0xa660,
+    0xa662,
+    0xa664,
+    0xa666,
+    0xa668,
+    0xa66a,
+    0xa66c,
+    0xa680,
+    0xa682,
+    0xa684,
+    0xa686,
+    0xa688,
+    0xa68a,
+    0xa68c,
+    0xa68e,
+    0xa690,
+    0xa692,
+    0xa694,
+    0xa696,
+    0xa698,
+    0xa69a,
+    0xa722,
+    0xa724,
+    0xa726,
+    0xa728,
+    0xa72a,
+    0xa72c,
+    0xa72e,
+    0xa732,
+    0xa734,
+    0xa736,
+    0xa738,
+    0xa73a,
+    0xa73c,
+    0xa73e,
+    0xa740,
+    0xa742,
+    0xa744,
+    0xa746,
+    0xa748,
+    0xa74a,
+    0xa74c,
+    0xa74e,
+    0xa750,
+    0xa752,
+    0xa754,
+    0xa756,
+    0xa758,
+    0xa75a,
+    0xa75c,
+    0xa75e,
+    0xa760,
+    0xa762,
+    0xa764,
+    0xa766,
+    0xa768,
+    0xa76a,
+    0xa76c,
+    0xa76e,
+    0xa779,
+    0xa77b,
+    0xa780,
+    0xa782,
+    0xa784,
+    0xa786,
+    0xa78b,
+    0xa78d,
+    0xa790,
+    0xa792,
+    0xa796,
+    0xa798,
+    0xa79a,
+    0xa79c,
+    0xa79e,
+    0xa7a0,
+    0xa7a2,
+    0xa7a4,
+    0xa7a6,
+    0xa7a8,
+    0x1d49c,
+    0x1d4a2,
+    0x1d546,
+    0x1d7ca,
+};
+
+} // !namespace
+
+bool isupper(char32_t c) noexcept
+{
+   const char32_t *p;
+
+   p = rbsearch(c, isupperr, nelem (isupperr)/2, 2);
+
+   if (p && c >= p[0] && c <= p[1])
+       return true;
+
+ p = rbsearch(c, isuppers, nelem (isuppers), 1);
+
+   if (p && c == p[0])
+       return true;
+
+ return false;
+}
+
+namespace {
+
+const char32_t islowerr[] = {
+    0x0061, 0x007a,
+    0x00df, 0x00f6,
+    0x00f8, 0x00ff,
+    0x0137, 0x0138,
+    0x0148, 0x0149,
+    0x017e, 0x0180,
+    0x018c, 0x018d,
+    0x0199, 0x019b,
+    0x01aa, 0x01ab,
+    0x01b9, 0x01ba,
+    0x01bd, 0x01bf,
+    0x01dc, 0x01dd,
+    0x01ef, 0x01f0,
+    0x0233, 0x0239,
+    0x023f, 0x0240,
+    0x024f, 0x0293,
+    0x0295, 0x02af,
+    0x037b, 0x037d,
+    0x03ac, 0x03ce,
+    0x03d0, 0x03d1,
+    0x03d5, 0x03d7,
+    0x03ef, 0x03f3,
+    0x03fb, 0x03fc,
+    0x0430, 0x045f,
+    0x04ce, 0x04cf,
+    0x0561, 0x0587,
+    0x1d00, 0x1d2b,
+    0x1d6b, 0x1d77,
+    0x1d79, 0x1d9a,
+    0x1e95, 0x1e9d,
+    0x1eff, 0x1f07,
+    0x1f10, 0x1f15,
+    0x1f20, 0x1f27,
+    0x1f30, 0x1f37,
+    0x1f40, 0x1f45,
+    0x1f50, 0x1f57,
+    0x1f60, 0x1f67,
+    0x1f70, 0x1f7d,
+    0x1f80, 0x1f87,
+    0x1f90, 0x1f97,
+    0x1fa0, 0x1fa7,
+    0x1fb0, 0x1fb4,
+    0x1fb6, 0x1fb7,
+    0x1fc2, 0x1fc4,
+    0x1fc6, 0x1fc7,
+    0x1fd0, 0x1fd3,
+    0x1fd6, 0x1fd7,
+    0x1fe0, 0x1fe7,
+    0x1ff2, 0x1ff4,
+    0x1ff6, 0x1ff7,
+    0x210e, 0x210f,
+    0x213c, 0x213d,
+    0x2146, 0x2149,
+    0x2170, 0x217f,
+    0x24d0, 0x24e9,
+    0x2c30, 0x2c5e,
+    0x2c65, 0x2c66,
+    0x2c73, 0x2c74,
+    0x2c76, 0x2c7b,
+    0x2ce3, 0x2ce4,
+    0x2d00, 0x2d25,
+    0xa72f, 0xa731,
+    0xa771, 0xa778,
+    0xa793, 0xa795,
+    0xab30, 0xab5a,
+    0xab64, 0xab65,
+    0xfb00, 0xfb06,
+    0xfb13, 0xfb17,
+    0xff41, 0xff5a,
+    0x10428, 0x1044f,
+    0x118c0, 0x118df,
+    0x1d41a, 0x1d433,
+    0x1d44e, 0x1d454,
+    0x1d456, 0x1d467,
+    0x1d482, 0x1d49b,
+    0x1d4b6, 0x1d4b9,
+    0x1d4bd, 0x1d4c3,
+    0x1d4c5, 0x1d4cf,
+    0x1d4ea, 0x1d503,
+    0x1d51e, 0x1d537,
+    0x1d552, 0x1d56b,
+    0x1d586, 0x1d59f,
+    0x1d5ba, 0x1d5d3,
+    0x1d5ee, 0x1d607,
+    0x1d622, 0x1d63b,
+    0x1d656, 0x1d66f,
+    0x1d68a, 0x1d6a5,
+    0x1d6c2, 0x1d6da,
+    0x1d6dc, 0x1d6e1,
+    0x1d6fc, 0x1d714,
+    0x1d716, 0x1d71b,
+    0x1d736, 0x1d74e,
+    0x1d750, 0x1d755,
+    0x1d770, 0x1d788,
+    0x1d78a, 0x1d78f,
+    0x1d7aa, 0x1d7c2,
+    0x1d7c4, 0x1d7c9,
+};
+
+} // !namespace
+
+namespace {
+
+const char32_t islowers[] = {
+    0x00b5,
+    0x0101,
+    0x0103,
+    0x0105,
+    0x0107,
+    0x0109,
+    0x010b,
+    0x010d,
+    0x010f,
+    0x0111,
+    0x0113,
+    0x0115,
+    0x0117,
+    0x0119,
+    0x011b,
+    0x011d,
+    0x011f,
+    0x0121,
+    0x0123,
+    0x0125,
+    0x0127,
+    0x0129,
+    0x012b,
+    0x012d,
+    0x012f,
+    0x0131,
+    0x0133,
+    0x0135,
+    0x013a,
+    0x013c,
+    0x013e,
+    0x0140,
+    0x0142,
+    0x0144,
+    0x0146,
+    0x014b,
+    0x014d,
+    0x014f,
+    0x0151,
+    0x0153,
+    0x0155,
+    0x0157,
+    0x0159,
+    0x015b,
+    0x015d,
+    0x015f,
+    0x0161,
+    0x0163,
+    0x0165,
+    0x0167,
+    0x0169,
+    0x016b,
+    0x016d,
+    0x016f,
+    0x0171,
+    0x0173,
+    0x0175,
+    0x0177,
+    0x017a,
+    0x017c,
+    0x0183,
+    0x0185,
+    0x0188,
+    0x0192,
+    0x0195,
+    0x019e,
+    0x01a1,
+    0x01a3,
+    0x01a5,
+    0x01a8,
+    0x01ad,
+    0x01b0,
+    0x01b4,
+    0x01b6,
+    0x01c6,
+    0x01c9,
+    0x01cc,
+    0x01ce,
+    0x01d0,
+    0x01d2,
+    0x01d4,
+    0x01d6,
+    0x01d8,
+    0x01da,
+    0x01df,
+    0x01e1,
+    0x01e3,
+    0x01e5,
+    0x01e7,
+    0x01e9,
+    0x01eb,
+    0x01ed,
+    0x01f3,
+    0x01f5,
+    0x01f9,
+    0x01fb,
+    0x01fd,
+    0x01ff,
+    0x0201,
+    0x0203,
+    0x0205,
+    0x0207,
+    0x0209,
+    0x020b,
+    0x020d,
+    0x020f,
+    0x0211,
+    0x0213,
+    0x0215,
+    0x0217,
+    0x0219,
+    0x021b,
+    0x021d,
+    0x021f,
+    0x0221,
+    0x0223,
+    0x0225,
+    0x0227,
+    0x0229,
+    0x022b,
+    0x022d,
+    0x022f,
+    0x0231,
+    0x023c,
+    0x0242,
+    0x0247,
+    0x0249,
+    0x024b,
+    0x024d,
+    0x0371,
+    0x0373,
+    0x0377,
+    0x0390,
+    0x03d9,
+    0x03db,
+    0x03dd,
+    0x03df,
+    0x03e1,
+    0x03e3,
+    0x03e5,
+    0x03e7,
+    0x03e9,
+    0x03eb,
+    0x03ed,
+    0x03f5,
+    0x03f8,
+    0x0461,
+    0x0463,
+    0x0465,
+    0x0467,
+    0x0469,
+    0x046b,
+    0x046d,
+    0x046f,
+    0x0471,
+    0x0473,
+    0x0475,
+    0x0477,
+    0x0479,
+    0x047b,
+    0x047d,
+    0x047f,
+    0x0481,
+    0x048b,
+    0x048d,
+    0x048f,
+    0x0491,
+    0x0493,
+    0x0495,
+    0x0497,
+    0x0499,
+    0x049b,
+    0x049d,
+    0x049f,
+    0x04a1,
+    0x04a3,
+    0x04a5,
+    0x04a7,
+    0x04a9,
+    0x04ab,
+    0x04ad,
+    0x04af,
+    0x04b1,
+    0x04b3,
+    0x04b5,
+    0x04b7,
+    0x04b9,
+    0x04bb,
+    0x04bd,
+    0x04bf,
+    0x04c2,
+    0x04c4,
+    0x04c6,
+    0x04c8,
+    0x04ca,
+    0x04cc,
+    0x04d1,
+    0x04d3,
+    0x04d5,
+    0x04d7,
+    0x04d9,
+    0x04db,
+    0x04dd,
+    0x04df,
+    0x04e1,
+    0x04e3,
+    0x04e5,
+    0x04e7,
+    0x04e9,
+    0x04eb,
+    0x04ed,
+    0x04ef,
+    0x04f1,
+    0x04f3,
+    0x04f5,
+    0x04f7,
+    0x04f9,
+    0x04fb,
+    0x04fd,
+    0x04ff,
+    0x0501,
+    0x0503,
+    0x0505,
+    0x0507,
+    0x0509,
+    0x050b,
+    0x050d,
+    0x050f,
+    0x0511,
+    0x0513,
+    0x0515,
+    0x0517,
+    0x0519,
+    0x051b,
+    0x051d,
+    0x051f,
+    0x0521,
+    0x0523,
+    0x0525,
+    0x0527,
+    0x0529,
+    0x052b,
+    0x052d,
+    0x052f,
+    0x1e01,
+    0x1e03,
+    0x1e05,
+    0x1e07,
+    0x1e09,
+    0x1e0b,
+    0x1e0d,
+    0x1e0f,
+    0x1e11,
+    0x1e13,
+    0x1e15,
+    0x1e17,
+    0x1e19,
+    0x1e1b,
+    0x1e1d,
+    0x1e1f,
+    0x1e21,
+    0x1e23,
+    0x1e25,
+    0x1e27,
+    0x1e29,
+    0x1e2b,
+    0x1e2d,
+    0x1e2f,
+    0x1e31,
+    0x1e33,
+    0x1e35,
+    0x1e37,
+    0x1e39,
+    0x1e3b,
+    0x1e3d,
+    0x1e3f,
+    0x1e41,
+    0x1e43,
+    0x1e45,
+    0x1e47,
+    0x1e49,
+    0x1e4b,
+    0x1e4d,
+    0x1e4f,
+    0x1e51,
+    0x1e53,
+    0x1e55,
+    0x1e57,
+    0x1e59,
+    0x1e5b,
+    0x1e5d,
+    0x1e5f,
+    0x1e61,
+    0x1e63,
+    0x1e65,
+    0x1e67,
+    0x1e69,
+    0x1e6b,
+    0x1e6d,
+    0x1e6f,
+    0x1e71,
+    0x1e73,
+    0x1e75,
+    0x1e77,
+    0x1e79,
+    0x1e7b,
+    0x1e7d,
+    0x1e7f,
+    0x1e81,
+    0x1e83,
+    0x1e85,
+    0x1e87,
+    0x1e89,
+    0x1e8b,
+    0x1e8d,
+    0x1e8f,
+    0x1e91,
+    0x1e93,
+    0x1e9f,
+    0x1ea1,
+    0x1ea3,
+    0x1ea5,
+    0x1ea7,
+    0x1ea9,
+    0x1eab,
+    0x1ead,
+    0x1eaf,
+    0x1eb1,
+    0x1eb3,
+    0x1eb5,
+    0x1eb7,
+    0x1eb9,
+    0x1ebb,
+    0x1ebd,
+    0x1ebf,
+    0x1ec1,
+    0x1ec3,
+    0x1ec5,
+    0x1ec7,
+    0x1ec9,
+    0x1ecb,
+    0x1ecd,
+    0x1ecf,
+    0x1ed1,
+    0x1ed3,
+    0x1ed5,
+    0x1ed7,
+    0x1ed9,
+    0x1edb,
+    0x1edd,
+    0x1edf,
+    0x1ee1,
+    0x1ee3,
+    0x1ee5,
+    0x1ee7,
+    0x1ee9,
+    0x1eeb,
+    0x1eed,
+    0x1eef,
+    0x1ef1,
+    0x1ef3,
+    0x1ef5,
+    0x1ef7,
+    0x1ef9,
+    0x1efb,
+    0x1efd,
+    0x1fbe,
+    0x210a,
+    0x2113,
+    0x212f,
+    0x2134,
+    0x2139,
+    0x214e,
+    0x2184,
+    0x2c61,
+    0x2c68,
+    0x2c6a,
+    0x2c6c,
+    0x2c71,
+    0x2c81,
+    0x2c83,
+    0x2c85,
+    0x2c87,
+    0x2c89,
+    0x2c8b,
+    0x2c8d,
+    0x2c8f,
+    0x2c91,
+    0x2c93,
+    0x2c95,
+    0x2c97,
+    0x2c99,
+    0x2c9b,
+    0x2c9d,
+    0x2c9f,
+    0x2ca1,
+    0x2ca3,
+    0x2ca5,
+    0x2ca7,
+    0x2ca9,
+    0x2cab,
+    0x2cad,
+    0x2caf,
+    0x2cb1,
+    0x2cb3,
+    0x2cb5,
+    0x2cb7,
+    0x2cb9,
+    0x2cbb,
+    0x2cbd,
+    0x2cbf,
+    0x2cc1,
+    0x2cc3,
+    0x2cc5,
+    0x2cc7,
+    0x2cc9,
+    0x2ccb,
+    0x2ccd,
+    0x2ccf,
+    0x2cd1,
+    0x2cd3,
+    0x2cd5,
+    0x2cd7,
+    0x2cd9,
+    0x2cdb,
+    0x2cdd,
+    0x2cdf,
+    0x2ce1,
+    0x2cec,
+    0x2cee,
+    0x2cf3,
+    0x2d27,
+    0x2d2d,
+    0xa641,
+    0xa643,
+    0xa645,
+    0xa647,
+    0xa649,
+    0xa64b,
+    0xa64d,
+    0xa64f,
+    0xa651,
+    0xa653,
+    0xa655,
+    0xa657,
+    0xa659,
+    0xa65b,
+    0xa65d,
+    0xa65f,
+    0xa661,
+    0xa663,
+    0xa665,
+    0xa667,
+    0xa669,
+    0xa66b,
+    0xa66d,
+    0xa681,
+    0xa683,
+    0xa685,
+    0xa687,
+    0xa689,
+    0xa68b,
+    0xa68d,
+    0xa68f,
+    0xa691,
+    0xa693,
+    0xa695,
+    0xa697,
+    0xa699,
+    0xa69b,
+    0xa723,
+    0xa725,
+    0xa727,
+    0xa729,
+    0xa72b,
+    0xa72d,
+    0xa733,
+    0xa735,
+    0xa737,
+    0xa739,
+    0xa73b,
+    0xa73d,
+    0xa73f,
+    0xa741,
+    0xa743,
+    0xa745,
+    0xa747,
+    0xa749,
+    0xa74b,
+    0xa74d,
+    0xa74f,
+    0xa751,
+    0xa753,
+    0xa755,
+    0xa757,
+    0xa759,
+    0xa75b,
+    0xa75d,
+    0xa75f,
+    0xa761,
+    0xa763,
+    0xa765,
+    0xa767,
+    0xa769,
+    0xa76b,
+    0xa76d,
+    0xa76f,
+    0xa77a,
+    0xa77c,
+    0xa77f,
+    0xa781,
+    0xa783,
+    0xa785,
+    0xa787,
+    0xa78c,
+    0xa78e,
+    0xa791,
+    0xa797,
+    0xa799,
+    0xa79b,
+    0xa79d,
+    0xa79f,
+    0xa7a1,
+    0xa7a3,
+    0xa7a5,
+    0xa7a7,
+    0xa7a9,
+    0xa7fa,
+    0x1d4bb,
+    0x1d7cb,
+};
+
+} // !namespace
+
+bool islower(char32_t c) noexcept
+{
+   const char32_t *p;
+
+   p = rbsearch(c, islowerr, nelem (islowerr)/2, 2);
+
+   if (p && c >= p[0] && c <= p[1])
+       return true;
+
+ p = rbsearch(c, islowers, nelem (islowers), 1);
+
+   if (p && c == p[0])
+       return true;
+
+ return false;
+}
+
+namespace {
+
+const char32_t istitler[] = {
+    0x0041, 0x005a,
+    0x00c0, 0x00d6,
+    0x00d8, 0x00de,
+    0x0178, 0x0179,
+    0x0181, 0x0182,
+    0x0186, 0x0187,
+    0x0189, 0x018b,
+    0x018e, 0x0191,
+    0x0193, 0x0194,
+    0x0196, 0x0198,
+    0x019c, 0x019d,
+    0x019f, 0x01a0,
+    0x01a6, 0x01a7,
+    0x01ae, 0x01af,
+    0x01b1, 0x01b3,
+    0x01b7, 0x01b8,
+    0x01f6, 0x01f8,
+    0x023a, 0x023b,
+    0x023d, 0x023e,
+    0x0243, 0x0246,
+    0x0388, 0x038a,
+    0x038e, 0x038f,
+    0x0391, 0x03a1,
+    0x03a3, 0x03ab,
+    0x03f9, 0x03fa,
+    0x03fd, 0x042f,
+    0x04c0, 0x04c1,
+    0x0531, 0x0556,
+    0x10a0, 0x10c5,
+    0x1f08, 0x1f0f,
+    0x1f18, 0x1f1d,
+    0x1f28, 0x1f2f,
+    0x1f38, 0x1f3f,
+    0x1f48, 0x1f4d,
+    0x1f68, 0x1f6f,
+    0x1f88, 0x1f8f,
+    0x1f98, 0x1f9f,
+    0x1fa8, 0x1faf,
+    0x1fb8, 0x1fbc,
+    0x1fc8, 0x1fcc,
+    0x1fd8, 0x1fdb,
+    0x1fe8, 0x1fec,
+    0x1ff8, 0x1ffc,
+    0x2160, 0x216f,
+    0x24b6, 0x24cf,
+    0x2c00, 0x2c2e,
+    0x2c62, 0x2c64,
+    0x2c6d, 0x2c70,
+    0x2c7e, 0x2c80,
+    0xa77d, 0xa77e,
+    0xa7aa, 0xa7ad,
+    0xa7b0, 0xa7b1,
+    0xff21, 0xff3a,
+    0x10400, 0x10427,
+    0x118a0, 0x118bf,
+};
+
+} // !namespace
+
+namespace {
+
+const char32_t istitles[] = {
+    0x0100,
+    0x0102,
+    0x0104,
+    0x0106,
+    0x0108,
+    0x010a,
+    0x010c,
+    0x010e,
+    0x0110,
+    0x0112,
+    0x0114,
+    0x0116,
+    0x0118,
+    0x011a,
+    0x011c,
+    0x011e,
+    0x0120,
+    0x0122,
+    0x0124,
+    0x0126,
+    0x0128,
+    0x012a,
+    0x012c,
+    0x012e,
+    0x0132,
+    0x0134,
+    0x0136,
+    0x0139,
+    0x013b,
+    0x013d,
+    0x013f,
+    0x0141,
+    0x0143,
+    0x0145,
+    0x0147,
+    0x014a,
+    0x014c,
+    0x014e,
+    0x0150,
+    0x0152,
+    0x0154,
+    0x0156,
+    0x0158,
+    0x015a,
+    0x015c,
+    0x015e,
+    0x0160,
+    0x0162,
+    0x0164,
+    0x0166,
+    0x0168,
+    0x016a,
+    0x016c,
+    0x016e,
+    0x0170,
+    0x0172,
+    0x0174,
+    0x0176,
+    0x017b,
+    0x017d,
+    0x0184,
+    0x01a2,
+    0x01a4,
+    0x01a9,
+    0x01ac,
+    0x01b5,
+    0x01bc,
+    0x01c5,
+    0x01c8,
+    0x01cb,
+    0x01cd,
+    0x01cf,
+    0x01d1,
+    0x01d3,
+    0x01d5,
+    0x01d7,
+    0x01d9,
+    0x01db,
+    0x01de,
+    0x01e0,
+    0x01e2,
+    0x01e4,
+    0x01e6,
+    0x01e8,
+    0x01ea,
+    0x01ec,
+    0x01ee,
+    0x01f2,
+    0x01f4,
+    0x01fa,
+    0x01fc,
+    0x01fe,
+    0x0200,
+    0x0202,
+    0x0204,
+    0x0206,
+    0x0208,
+    0x020a,
+    0x020c,
+    0x020e,
+    0x0210,
+    0x0212,
+    0x0214,
+    0x0216,
+    0x0218,
+    0x021a,
+    0x021c,
+    0x021e,
+    0x0220,
+    0x0222,
+    0x0224,
+    0x0226,
+    0x0228,
+    0x022a,
+    0x022c,
+    0x022e,
+    0x0230,
+    0x0232,
+    0x0241,
+    0x0248,
+    0x024a,
+    0x024c,
+    0x024e,
+    0x0370,
+    0x0372,
+    0x0376,
+    0x037f,
+    0x0386,
+    0x038c,
+    0x03cf,
+    0x03d8,
+    0x03da,
+    0x03dc,
+    0x03de,
+    0x03e0,
+    0x03e2,
+    0x03e4,
+    0x03e6,
+    0x03e8,
+    0x03ea,
+    0x03ec,
+    0x03ee,
+    0x03f7,
+    0x0460,
+    0x0462,
+    0x0464,
+    0x0466,
+    0x0468,
+    0x046a,
+    0x046c,
+    0x046e,
+    0x0470,
+    0x0472,
+    0x0474,
+    0x0476,
+    0x0478,
+    0x047a,
+    0x047c,
+    0x047e,
+    0x0480,
+    0x048a,
+    0x048c,
+    0x048e,
+    0x0490,
+    0x0492,
+    0x0494,
+    0x0496,
+    0x0498,
+    0x049a,
+    0x049c,
+    0x049e,
+    0x04a0,
+    0x04a2,
+    0x04a4,
+    0x04a6,
+    0x04a8,
+    0x04aa,
+    0x04ac,
+    0x04ae,
+    0x04b0,
+    0x04b2,
+    0x04b4,
+    0x04b6,
+    0x04b8,
+    0x04ba,
+    0x04bc,
+    0x04be,
+    0x04c3,
+    0x04c5,
+    0x04c7,
+    0x04c9,
+    0x04cb,
+    0x04cd,
+    0x04d0,
+    0x04d2,
+    0x04d4,
+    0x04d6,
+    0x04d8,
+    0x04da,
+    0x04dc,
+    0x04de,
+    0x04e0,
+    0x04e2,
+    0x04e4,
+    0x04e6,
+    0x04e8,
+    0x04ea,
+    0x04ec,
+    0x04ee,
+    0x04f0,
+    0x04f2,
+    0x04f4,
+    0x04f6,
+    0x04f8,
+    0x04fa,
+    0x04fc,
+    0x04fe,
+    0x0500,
+    0x0502,
+    0x0504,
+    0x0506,
+    0x0508,
+    0x050a,
+    0x050c,
+    0x050e,
+    0x0510,
+    0x0512,
+    0x0514,
+    0x0516,
+    0x0518,
+    0x051a,
+    0x051c,
+    0x051e,
+    0x0520,
+    0x0522,
+    0x0524,
+    0x0526,
+    0x0528,
+    0x052a,
+    0x052c,
+    0x052e,
+    0x10c7,
+    0x10cd,
+    0x1e00,
+    0x1e02,
+    0x1e04,
+    0x1e06,
+    0x1e08,
+    0x1e0a,
+    0x1e0c,
+    0x1e0e,
+    0x1e10,
+    0x1e12,
+    0x1e14,
+    0x1e16,
+    0x1e18,
+    0x1e1a,
+    0x1e1c,
+    0x1e1e,
+    0x1e20,
+    0x1e22,
+    0x1e24,
+    0x1e26,
+    0x1e28,
+    0x1e2a,
+    0x1e2c,
+    0x1e2e,
+    0x1e30,
+    0x1e32,
+    0x1e34,
+    0x1e36,
+    0x1e38,
+    0x1e3a,
+    0x1e3c,
+    0x1e3e,
+    0x1e40,
+    0x1e42,
+    0x1e44,
+    0x1e46,
+    0x1e48,
+    0x1e4a,
+    0x1e4c,
+    0x1e4e,
+    0x1e50,
+    0x1e52,
+    0x1e54,
+    0x1e56,
+    0x1e58,
+    0x1e5a,
+    0x1e5c,
+    0x1e5e,
+    0x1e60,
+    0x1e62,
+    0x1e64,
+    0x1e66,
+    0x1e68,
+    0x1e6a,
+    0x1e6c,
+    0x1e6e,
+    0x1e70,
+    0x1e72,
+    0x1e74,
+    0x1e76,
+    0x1e78,
+    0x1e7a,
+    0x1e7c,
+    0x1e7e,
+    0x1e80,
+    0x1e82,
+    0x1e84,
+    0x1e86,
+    0x1e88,
+    0x1e8a,
+    0x1e8c,
+    0x1e8e,
+    0x1e90,
+    0x1e92,
+    0x1e94,
+    0x1ea0,
+    0x1ea2,
+    0x1ea4,
+    0x1ea6,
+    0x1ea8,
+    0x1eaa,
+    0x1eac,
+    0x1eae,
+    0x1eb0,
+    0x1eb2,
+    0x1eb4,
+    0x1eb6,
+    0x1eb8,
+    0x1eba,
+    0x1ebc,
+    0x1ebe,
+    0x1ec0,
+    0x1ec2,
+    0x1ec4,
+    0x1ec6,
+    0x1ec8,
+    0x1eca,
+    0x1ecc,
+    0x1ece,
+    0x1ed0,
+    0x1ed2,
+    0x1ed4,
+    0x1ed6,
+    0x1ed8,
+    0x1eda,
+    0x1edc,
+    0x1ede,
+    0x1ee0,
+    0x1ee2,
+    0x1ee4,
+    0x1ee6,
+    0x1ee8,
+    0x1eea,
+    0x1eec,
+    0x1eee,
+    0x1ef0,
+    0x1ef2,
+    0x1ef4,
+    0x1ef6,
+    0x1ef8,
+    0x1efa,
+    0x1efc,
+    0x1efe,
+    0x1f59,
+    0x1f5b,
+    0x1f5d,
+    0x1f5f,
+    0x2132,
+    0x2183,
+    0x2c60,
+    0x2c67,
+    0x2c69,
+    0x2c6b,
+    0x2c72,
+    0x2c75,
+    0x2c82,
+    0x2c84,
+    0x2c86,
+    0x2c88,
+    0x2c8a,
+    0x2c8c,
+    0x2c8e,
+    0x2c90,
+    0x2c92,
+    0x2c94,
+    0x2c96,
+    0x2c98,
+    0x2c9a,
+    0x2c9c,
+    0x2c9e,
+    0x2ca0,
+    0x2ca2,
+    0x2ca4,
+    0x2ca6,
+    0x2ca8,
+    0x2caa,
+    0x2cac,
+    0x2cae,
+    0x2cb0,
+    0x2cb2,
+    0x2cb4,
+    0x2cb6,
+    0x2cb8,
+    0x2cba,
+    0x2cbc,
+    0x2cbe,
+    0x2cc0,
+    0x2cc2,
+    0x2cc4,
+    0x2cc6,
+    0x2cc8,
+    0x2cca,
+    0x2ccc,
+    0x2cce,
+    0x2cd0,
+    0x2cd2,
+    0x2cd4,
+    0x2cd6,
+    0x2cd8,
+    0x2cda,
+    0x2cdc,
+    0x2cde,
+    0x2ce0,
+    0x2ce2,
+    0x2ceb,
+    0x2ced,
+    0x2cf2,
+    0xa640,
+    0xa642,
+    0xa644,
+    0xa646,
+    0xa648,
+    0xa64a,
+    0xa64c,
+    0xa64e,
+    0xa650,
+    0xa652,
+    0xa654,
+    0xa656,
+    0xa658,
+    0xa65a,
+    0xa65c,
+    0xa65e,
+    0xa660,
+    0xa662,
+    0xa664,
+    0xa666,
+    0xa668,
+    0xa66a,
+    0xa66c,
+    0xa680,
+    0xa682,
+    0xa684,
+    0xa686,
+    0xa688,
+    0xa68a,
+    0xa68c,
+    0xa68e,
+    0xa690,
+    0xa692,
+    0xa694,
+    0xa696,
+    0xa698,
+    0xa69a,
+    0xa722,
+    0xa724,
+    0xa726,
+    0xa728,
+    0xa72a,
+    0xa72c,
+    0xa72e,
+    0xa732,
+    0xa734,
+    0xa736,
+    0xa738,
+    0xa73a,
+    0xa73c,
+    0xa73e,
+    0xa740,
+    0xa742,
+    0xa744,
+    0xa746,
+    0xa748,
+    0xa74a,
+    0xa74c,
+    0xa74e,
+    0xa750,
+    0xa752,
+    0xa754,
+    0xa756,
+    0xa758,
+    0xa75a,
+    0xa75c,
+    0xa75e,
+    0xa760,
+    0xa762,
+    0xa764,
+    0xa766,
+    0xa768,
+    0xa76a,
+    0xa76c,
+    0xa76e,
+    0xa779,
+    0xa77b,
+    0xa780,
+    0xa782,
+    0xa784,
+    0xa786,
+    0xa78b,
+    0xa78d,
+    0xa790,
+    0xa792,
+    0xa796,
+    0xa798,
+    0xa79a,
+    0xa79c,
+    0xa79e,
+    0xa7a0,
+    0xa7a2,
+    0xa7a4,
+    0xa7a6,
+    0xa7a8,
+};
+
+} // !namespace
+
+bool istitle(char32_t c) noexcept
+{
+   const char32_t *p;
+
+   p = rbsearch(c, istitler, nelem (istitler)/2, 2);
+
+   if (p && c >= p[0] && c <= p[1])
+       return true;
+
+ p = rbsearch(c, istitles, nelem (istitles), 1);
+
+   if (p && c == p[0])
+       return true;
+
+ return false;
+}
+
+namespace {
+
+const char32_t toupperr[] = {
+    0x0061, 0x007a, 1048544,
+    0x00e0, 0x00f6, 1048544,
+    0x00f8, 0x00fe, 1048544,
+    0x023f, 0x0240, 1059391,
+    0x0256, 0x0257, 1048371,
+    0x028a, 0x028b, 1048359,
+    0x037b, 0x037d, 1048706,
+    0x03ad, 0x03af, 1048539,
+    0x03b1, 0x03c1, 1048544,
+    0x03c3, 0x03cb, 1048544,
+    0x03cd, 0x03ce, 1048513,
+    0x0430, 0x044f, 1048544,
+    0x0450, 0x045f, 1048496,
+    0x0561, 0x0586, 1048528,
+    0x1f00, 0x1f07, 1048584,
+    0x1f10, 0x1f15, 1048584,
+    0x1f20, 0x1f27, 1048584,
+    0x1f30, 0x1f37, 1048584,
+    0x1f40, 0x1f45, 1048584,
+    0x1f60, 0x1f67, 1048584,
+    0x1f70, 0x1f71, 1048650,
+    0x1f72, 0x1f75, 1048662,
+    0x1f76, 0x1f77, 1048676,
+    0x1f78, 0x1f79, 1048704,
+    0x1f7a, 0x1f7b, 1048688,
+    0x1f7c, 0x1f7d, 1048702,
+    0x1f80, 0x1f87, 1048584,
+    0x1f90, 0x1f97, 1048584,
+    0x1fa0, 0x1fa7, 1048584,
+    0x1fb0, 0x1fb1, 1048584,
+    0x1fd0, 0x1fd1, 1048584,
+    0x1fe0, 0x1fe1, 1048584,
+    0x2170, 0x217f, 1048560,
+    0x24d0, 0x24e9, 1048550,
+    0x2c30, 0x2c5e, 1048528,
+    0x2d00, 0x2d25, 1041312,
+    0xff41, 0xff5a, 1048544,
+    0x10428, 0x1044f, 1048536,
+    0x118c0, 0x118df, 1048544,
+};
+
+} // !namespace
+
+namespace {
+
+const char32_t touppers[] = {
+    0x00b5, 1049319,
+    0x00ff, 1048697,
+    0x0101, 1048575,
+    0x0103, 1048575,
+    0x0105, 1048575,
+    0x0107, 1048575,
+    0x0109, 1048575,
+    0x010b, 1048575,
+    0x010d, 1048575,
+    0x010f, 1048575,
+    0x0111, 1048575,
+    0x0113, 1048575,
+    0x0115, 1048575,
+    0x0117, 1048575,
+    0x0119, 1048575,
+    0x011b, 1048575,
+    0x011d, 1048575,
+    0x011f, 1048575,
+    0x0121, 1048575,
+    0x0123, 1048575,
+    0x0125, 1048575,
+    0x0127, 1048575,
+    0x0129, 1048575,
+    0x012b, 1048575,
+    0x012d, 1048575,
+    0x012f, 1048575,
+    0x0131, 1048344,
+    0x0133, 1048575,
+    0x0135, 1048575,
+    0x0137, 1048575,
+    0x013a, 1048575,
+    0x013c, 1048575,
+    0x013e, 1048575,
+    0x0140, 1048575,
+    0x0142, 1048575,
+    0x0144, 1048575,
+    0x0146, 1048575,
+    0x0148, 1048575,
+    0x014b, 1048575,
+    0x014d, 1048575,
+    0x014f, 1048575,
+    0x0151, 1048575,
+    0x0153, 1048575,
+    0x0155, 1048575,
+    0x0157, 1048575,
+    0x0159, 1048575,
+    0x015b, 1048575,
+    0x015d, 1048575,
+    0x015f, 1048575,
+    0x0161, 1048575,
+    0x0163, 1048575,
+    0x0165, 1048575,
+    0x0167, 1048575,
+    0x0169, 1048575,
+    0x016b, 1048575,
+    0x016d, 1048575,
+    0x016f, 1048575,
+    0x0171, 1048575,
+    0x0173, 1048575,
+    0x0175, 1048575,
+    0x0177, 1048575,
+    0x017a, 1048575,
+    0x017c, 1048575,
+    0x017e, 1048575,
+    0x017f, 1048276,
+    0x0180, 1048771,
+    0x0183, 1048575,
+    0x0185, 1048575,
+    0x0188, 1048575,
+    0x018c, 1048575,
+    0x0192, 1048575,
+    0x0195, 1048673,
+    0x0199, 1048575,
+    0x019a, 1048739,
+    0x019e, 1048706,
+    0x01a1, 1048575,
+    0x01a3, 1048575,
+    0x01a5, 1048575,
+    0x01a8, 1048575,
+    0x01ad, 1048575,
+    0x01b0, 1048575,
+    0x01b4, 1048575,
+    0x01b6, 1048575,
+    0x01b9, 1048575,
+    0x01bd, 1048575,
+    0x01bf, 1048632,
+    0x01c5, 1048575,
+    0x01c6, 1048574,
+    0x01c8, 1048575,
+    0x01c9, 1048574,
+    0x01cb, 1048575,
+    0x01cc, 1048574,
+    0x01ce, 1048575,
+    0x01d0, 1048575,
+    0x01d2, 1048575,
+    0x01d4, 1048575,
+    0x01d6, 1048575,
+    0x01d8, 1048575,
+    0x01da, 1048575,
+    0x01dc, 1048575,
+    0x01dd, 1048497,
+    0x01df, 1048575,
+    0x01e1, 1048575,
+    0x01e3, 1048575,
+    0x01e5, 1048575,
+    0x01e7, 1048575,
+    0x01e9, 1048575,
+    0x01eb, 1048575,
+    0x01ed, 1048575,
+    0x01ef, 1048575,
+    0x01f2, 1048575,
+    0x01f3, 1048574,
+    0x01f5, 1048575,
+    0x01f9, 1048575,
+    0x01fb, 1048575,
+    0x01fd, 1048575,
+    0x01ff, 1048575,
+    0x0201, 1048575,
+    0x0203, 1048575,
+    0x0205, 1048575,
+    0x0207, 1048575,
+    0x0209, 1048575,
+    0x020b, 1048575,
+    0x020d, 1048575,
+    0x020f, 1048575,
+    0x0211, 1048575,
+    0x0213, 1048575,
+    0x0215, 1048575,
+    0x0217, 1048575,
+    0x0219, 1048575,
+    0x021b, 1048575,
+    0x021d, 1048575,
+    0x021f, 1048575,
+    0x0223, 1048575,
+    0x0225, 1048575,
+    0x0227, 1048575,
+    0x0229, 1048575,
+    0x022b, 1048575,
+    0x022d, 1048575,
+    0x022f, 1048575,
+    0x0231, 1048575,
+    0x0233, 1048575,
+    0x023c, 1048575,
+    0x0242, 1048575,
+    0x0247, 1048575,
+    0x0249, 1048575,
+    0x024b, 1048575,
+    0x024d, 1048575,
+    0x024f, 1048575,
+    0x0250, 1059359,
+    0x0251, 1059356,
+    0x0252, 1059358,
+    0x0253, 1048366,
+    0x0254, 1048370,
+    0x0259, 1048374,
+    0x025b, 1048373,
+    0x025c, 1090895,
+    0x0260, 1048371,
+    0x0261, 1090891,
+    0x0263, 1048369,
+    0x0265, 1090856,
+    0x0266, 1090884,
+    0x0268, 1048367,
+    0x0269, 1048365,
+    0x026b, 1059319,
+    0x026c, 1090881,
+    0x026f, 1048365,
+    0x0271, 1059325,
+    0x0272, 1048363,
+    0x0275, 1048362,
+    0x027d, 1059303,
+    0x0280, 1048358,
+    0x0283, 1048358,
+    0x0287, 1090858,
+    0x0288, 1048358,
+    0x0289, 1048507,
+    0x028c, 1048505,
+    0x0292, 1048357,
+    0x029e, 1090834,
+    0x0345, 1048660,
+    0x0371, 1048575,
+    0x0373, 1048575,
+    0x0377, 1048575,
+    0x03ac, 1048538,
+    0x03c2, 1048545,
+    0x03cc, 1048512,
+    0x03d0, 1048514,
+    0x03d1, 1048519,
+    0x03d5, 1048529,
+    0x03d6, 1048522,
+    0x03d7, 1048568,
+    0x03d9, 1048575,
+    0x03db, 1048575,
+    0x03dd, 1048575,
+    0x03df, 1048575,
+    0x03e1, 1048575,
+    0x03e3, 1048575,
+    0x03e5, 1048575,
+    0x03e7, 1048575,
+    0x03e9, 1048575,
+    0x03eb, 1048575,
+    0x03ed, 1048575,
+    0x03ef, 1048575,
+    0x03f0, 1048490,
+    0x03f1, 1048496,
+    0x03f2, 1048583,
+    0x03f3, 1048460,
+    0x03f5, 1048480,
+    0x03f8, 1048575,
+    0x03fb, 1048575,
+    0x0461, 1048575,
+    0x0463, 1048575,
+    0x0465, 1048575,
+    0x0467, 1048575,
+    0x0469, 1048575,
+    0x046b, 1048575,
+    0x046d, 1048575,
+    0x046f, 1048575,
+    0x0471, 1048575,
+    0x0473, 1048575,
+    0x0475, 1048575,
+    0x0477, 1048575,
+    0x0479, 1048575,
+    0x047b, 1048575,
+    0x047d, 1048575,
+    0x047f, 1048575,
+    0x0481, 1048575,
+    0x048b, 1048575,
+    0x048d, 1048575,
+    0x048f, 1048575,
+    0x0491, 1048575,
+    0x0493, 1048575,
+    0x0495, 1048575,
+    0x0497, 1048575,
+    0x0499, 1048575,
+    0x049b, 1048575,
+    0x049d, 1048575,
+    0x049f, 1048575,
+    0x04a1, 1048575,
+    0x04a3, 1048575,
+    0x04a5, 1048575,
+    0x04a7, 1048575,
+    0x04a9, 1048575,
+    0x04ab, 1048575,
+    0x04ad, 1048575,
+    0x04af, 1048575,
+    0x04b1, 1048575,
+    0x04b3, 1048575,
+    0x04b5, 1048575,
+    0x04b7, 1048575,
+    0x04b9, 1048575,
+    0x04bb, 1048575,
+    0x04bd, 1048575,
+    0x04bf, 1048575,
+    0x04c2, 1048575,
+    0x04c4, 1048575,
+    0x04c6, 1048575,
+    0x04c8, 1048575,
+    0x04ca, 1048575,
+    0x04cc, 1048575,
+    0x04ce, 1048575,
+    0x04cf, 1048561,
+    0x04d1, 1048575,
+    0x04d3, 1048575,
+    0x04d5, 1048575,
+    0x04d7, 1048575,
+    0x04d9, 1048575,
+    0x04db, 1048575,
+    0x04dd, 1048575,
+    0x04df, 1048575,
+    0x04e1, 1048575,
+    0x04e3, 1048575,
+    0x04e5, 1048575,
+    0x04e7, 1048575,
+    0x04e9, 1048575,
+    0x04eb, 1048575,
+    0x04ed, 1048575,
+    0x04ef, 1048575,
+    0x04f1, 1048575,
+    0x04f3, 1048575,
+    0x04f5, 1048575,
+    0x04f7, 1048575,
+    0x04f9, 1048575,
+    0x04fb, 1048575,
+    0x04fd, 1048575,
+    0x04ff, 1048575,
+    0x0501, 1048575,
+    0x0503, 1048575,
+    0x0505, 1048575,
+    0x0507, 1048575,
+    0x0509, 1048575,
+    0x050b, 1048575,
+    0x050d, 1048575,
+    0x050f, 1048575,
+    0x0511, 1048575,
+    0x0513, 1048575,
+    0x0515, 1048575,
+    0x0517, 1048575,
+    0x0519, 1048575,
+    0x051b, 1048575,
+    0x051d, 1048575,
+    0x051f, 1048575,
+    0x0521, 1048575,
+    0x0523, 1048575,
+    0x0525, 1048575,
+    0x0527, 1048575,
+    0x0529, 1048575,
+    0x052b, 1048575,
+    0x052d, 1048575,
+    0x052f, 1048575,
+    0x1d79, 1083908,
+    0x1d7d, 1052390,
+    0x1e01, 1048575,
+    0x1e03, 1048575,
+    0x1e05, 1048575,
+    0x1e07, 1048575,
+    0x1e09, 1048575,
+    0x1e0b, 1048575,
+    0x1e0d, 1048575,
+    0x1e0f, 1048575,
+    0x1e11, 1048575,
+    0x1e13, 1048575,
+    0x1e15, 1048575,
+    0x1e17, 1048575,
+    0x1e19, 1048575,
+    0x1e1b, 1048575,
+    0x1e1d, 1048575,
+    0x1e1f, 1048575,
+    0x1e21, 1048575,
+    0x1e23, 1048575,
+    0x1e25, 1048575,
+    0x1e27, 1048575,
+    0x1e29, 1048575,
+    0x1e2b, 1048575,
+    0x1e2d, 1048575,
+    0x1e2f, 1048575,
+    0x1e31, 1048575,
+    0x1e33, 1048575,
+    0x1e35, 1048575,
+    0x1e37, 1048575,
+    0x1e39, 1048575,
+    0x1e3b, 1048575,
+    0x1e3d, 1048575,
+    0x1e3f, 1048575,
+    0x1e41, 1048575,
+    0x1e43, 1048575,
+    0x1e45, 1048575,
+    0x1e47, 1048575,
+    0x1e49, 1048575,
+    0x1e4b, 1048575,
+    0x1e4d, 1048575,
+    0x1e4f, 1048575,
+    0x1e51, 1048575,
+    0x1e53, 1048575,
+    0x1e55, 1048575,
+    0x1e57, 1048575,
+    0x1e59, 1048575,
+    0x1e5b, 1048575,
+    0x1e5d, 1048575,
+    0x1e5f, 1048575,
+    0x1e61, 1048575,
+    0x1e63, 1048575,
+    0x1e65, 1048575,
+    0x1e67, 1048575,
+    0x1e69, 1048575,
+    0x1e6b, 1048575,
+    0x1e6d, 1048575,
+    0x1e6f, 1048575,
+    0x1e71, 1048575,
+    0x1e73, 1048575,
+    0x1e75, 1048575,
+    0x1e77, 1048575,
+    0x1e79, 1048575,
+    0x1e7b, 1048575,
+    0x1e7d, 1048575,
+    0x1e7f, 1048575,
+    0x1e81, 1048575,
+    0x1e83, 1048575,
+    0x1e85, 1048575,
+    0x1e87, 1048575,
+    0x1e89, 1048575,
+    0x1e8b, 1048575,
+    0x1e8d, 1048575,
+    0x1e8f, 1048575,
+    0x1e91, 1048575,
+    0x1e93, 1048575,
+    0x1e95, 1048575,
+    0x1e9b, 1048517,
+    0x1ea1, 1048575,
+    0x1ea3, 1048575,
+    0x1ea5, 1048575,
+    0x1ea7, 1048575,
+    0x1ea9, 1048575,
+    0x1eab, 1048575,
+    0x1ead, 1048575,
+    0x1eaf, 1048575,
+    0x1eb1, 1048575,
+    0x1eb3, 1048575,
+    0x1eb5, 1048575,
+    0x1eb7, 1048575,
+    0x1eb9, 1048575,
+    0x1ebb, 1048575,
+    0x1ebd, 1048575,
+    0x1ebf, 1048575,
+    0x1ec1, 1048575,
+    0x1ec3, 1048575,
+    0x1ec5, 1048575,
+    0x1ec7, 1048575,
+    0x1ec9, 1048575,
+    0x1ecb, 1048575,
+    0x1ecd, 1048575,
+    0x1ecf, 1048575,
+    0x1ed1, 1048575,
+    0x1ed3, 1048575,
+    0x1ed5, 1048575,
+    0x1ed7, 1048575,
+    0x1ed9, 1048575,
+    0x1edb, 1048575,
+    0x1edd, 1048575,
+    0x1edf, 1048575,
+    0x1ee1, 1048575,
+    0x1ee3, 1048575,
+    0x1ee5, 1048575,
+    0x1ee7, 1048575,
+    0x1ee9, 1048575,
+    0x1eeb, 1048575,
+    0x1eed, 1048575,
+    0x1eef, 1048575,
+    0x1ef1, 1048575,
+    0x1ef3, 1048575,
+    0x1ef5, 1048575,
+    0x1ef7, 1048575,
+    0x1ef9, 1048575,
+    0x1efb, 1048575,
+    0x1efd, 1048575,
+    0x1eff, 1048575,
+    0x1f51, 1048584,
+    0x1f53, 1048584,
+    0x1f55, 1048584,
+    0x1f57, 1048584,
+    0x1fb3, 1048585,
+    0x1fbe, 1041371,
+    0x1fc3, 1048585,
+    0x1fe5, 1048583,
+    0x1ff3, 1048585,
+    0x214e, 1048548,
+    0x2184, 1048575,
+    0x2c61, 1048575,
+    0x2c65, 1037781,
+    0x2c66, 1037784,
+    0x2c68, 1048575,
+    0x2c6a, 1048575,
+    0x2c6c, 1048575,
+    0x2c73, 1048575,
+    0x2c76, 1048575,
+    0x2c81, 1048575,
+    0x2c83, 1048575,
+    0x2c85, 1048575,
+    0x2c87, 1048575,
+    0x2c89, 1048575,
+    0x2c8b, 1048575,
+    0x2c8d, 1048575,
+    0x2c8f, 1048575,
+    0x2c91, 1048575,
+    0x2c93, 1048575,
+    0x2c95, 1048575,
+    0x2c97, 1048575,
+    0x2c99, 1048575,
+    0x2c9b, 1048575,
+    0x2c9d, 1048575,
+    0x2c9f, 1048575,
+    0x2ca1, 1048575,
+    0x2ca3, 1048575,
+    0x2ca5, 1048575,
+    0x2ca7, 1048575,
+    0x2ca9, 1048575,
+    0x2cab, 1048575,
+    0x2cad, 1048575,
+    0x2caf, 1048575,
+    0x2cb1, 1048575,
+    0x2cb3, 1048575,
+    0x2cb5, 1048575,
+    0x2cb7, 1048575,
+    0x2cb9, 1048575,
+    0x2cbb, 1048575,
+    0x2cbd, 1048575,
+    0x2cbf, 1048575,
+    0x2cc1, 1048575,
+    0x2cc3, 1048575,
+    0x2cc5, 1048575,
+    0x2cc7, 1048575,
+    0x2cc9, 1048575,
+    0x2ccb, 1048575,
+    0x2ccd, 1048575,
+    0x2ccf, 1048575,
+    0x2cd1, 1048575,
+    0x2cd3, 1048575,
+    0x2cd5, 1048575,
+    0x2cd7, 1048575,
+    0x2cd9, 1048575,
+    0x2cdb, 1048575,
+    0x2cdd, 1048575,
+    0x2cdf, 1048575,
+    0x2ce1, 1048575,
+    0x2ce3, 1048575,
+    0x2cec, 1048575,
+    0x2cee, 1048575,
+    0x2cf3, 1048575,
+    0x2d27, 1041312,
+    0x2d2d, 1041312,
+    0xa641, 1048575,
+    0xa643, 1048575,
+    0xa645, 1048575,
+    0xa647, 1048575,
+    0xa649, 1048575,
+    0xa64b, 1048575,
+    0xa64d, 1048575,
+    0xa64f, 1048575,
+    0xa651, 1048575,
+    0xa653, 1048575,
+    0xa655, 1048575,
+    0xa657, 1048575,
+    0xa659, 1048575,
+    0xa65b, 1048575,
+    0xa65d, 1048575,
+    0xa65f, 1048575,
+    0xa661, 1048575,
+    0xa663, 1048575,
+    0xa665, 1048575,
+    0xa667, 1048575,
+    0xa669, 1048575,
+    0xa66b, 1048575,
+    0xa66d, 1048575,
+    0xa681, 1048575,
+    0xa683, 1048575,
+    0xa685, 1048575,
+    0xa687, 1048575,
+    0xa689, 1048575,
+    0xa68b, 1048575,
+    0xa68d, 1048575,
+    0xa68f, 1048575,
+    0xa691, 1048575,
+    0xa693, 1048575,
+    0xa695, 1048575,
+    0xa697, 1048575,
+    0xa699, 1048575,
+    0xa69b, 1048575,
+    0xa723, 1048575,
+    0xa725, 1048575,
+    0xa727, 1048575,
+    0xa729, 1048575,
+    0xa72b, 1048575,
+    0xa72d, 1048575,
+    0xa72f, 1048575,
+    0xa733, 1048575,
+    0xa735, 1048575,
+    0xa737, 1048575,
+    0xa739, 1048575,
+    0xa73b, 1048575,
+    0xa73d, 1048575,
+    0xa73f, 1048575,
+    0xa741, 1048575,
+    0xa743, 1048575,
+    0xa745, 1048575,
+    0xa747, 1048575,
+    0xa749, 1048575,
+    0xa74b, 1048575,
+    0xa74d, 1048575,
+    0xa74f, 1048575,
+    0xa751, 1048575,
+    0xa753, 1048575,
+    0xa755, 1048575,
+    0xa757, 1048575,
+    0xa759, 1048575,
+    0xa75b, 1048575,
+    0xa75d, 1048575,
+    0xa75f, 1048575,
+    0xa761, 1048575,
+    0xa763, 1048575,
+    0xa765, 1048575,
+    0xa767, 1048575,
+    0xa769, 1048575,
+    0xa76b, 1048575,
+    0xa76d, 1048575,
+    0xa76f, 1048575,
+    0xa77a, 1048575,
+    0xa77c, 1048575,
+    0xa77f, 1048575,
+    0xa781, 1048575,
+    0xa783, 1048575,
+    0xa785, 1048575,
+    0xa787, 1048575,
+    0xa78c, 1048575,
+    0xa791, 1048575,
+    0xa793, 1048575,
+    0xa797, 1048575,
+    0xa799, 1048575,
+    0xa79b, 1048575,
+    0xa79d, 1048575,
+    0xa79f, 1048575,
+    0xa7a1, 1048575,
+    0xa7a3, 1048575,
+    0xa7a5, 1048575,
+    0xa7a7, 1048575,
+    0xa7a9, 1048575,
+};
+
+} // !namespace
+
+char32_t toupper(char32_t c) noexcept
+{
+   const char32_t *p;
+
+   p = rbsearch(c, toupperr, nelem (toupperr)/3, 3);
+
+   if (p && c >= p[0] && c <= p[1])
+       return c + p[2] - 1048576;
+
+ p = rbsearch(c, touppers, nelem (touppers)/2, 2);
+
+   if (p && c == p[0])
+       return c + p[1] - 1048576;
+
+   return c;
+}
+
+namespace {
+
+const char32_t tolowerr[] = {
+    0x0041, 0x005a, 1048608,
+    0x00c0, 0x00d6, 1048608,
+    0x00d8, 0x00de, 1048608,
+    0x0189, 0x018a, 1048781,
+    0x01b1, 0x01b2, 1048793,
+    0x0388, 0x038a, 1048613,
+    0x038e, 0x038f, 1048639,
+    0x0391, 0x03a1, 1048608,
+    0x03a3, 0x03ab, 1048608,
+    0x03fd, 0x03ff, 1048446,
+    0x0400, 0x040f, 1048656,
+    0x0410, 0x042f, 1048608,
+    0x0531, 0x0556, 1048624,
+    0x10a0, 0x10c5, 1055840,
+    0x1f08, 0x1f0f, 1048568,
+    0x1f18, 0x1f1d, 1048568,
+    0x1f28, 0x1f2f, 1048568,
+    0x1f38, 0x1f3f, 1048568,
+    0x1f48, 0x1f4d, 1048568,
+    0x1f68, 0x1f6f, 1048568,
+    0x1f88, 0x1f8f, 1048568,
+    0x1f98, 0x1f9f, 1048568,
+    0x1fa8, 0x1faf, 1048568,
+    0x1fb8, 0x1fb9, 1048568,
+    0x1fba, 0x1fbb, 1048502,
+    0x1fc8, 0x1fcb, 1048490,
+    0x1fd8, 0x1fd9, 1048568,
+    0x1fda, 0x1fdb, 1048476,
+    0x1fe8, 0x1fe9, 1048568,
+    0x1fea, 0x1feb, 1048464,
+    0x1ff8, 0x1ff9, 1048448,
+    0x1ffa, 0x1ffb, 1048450,
+    0x2160, 0x216f, 1048592,
+    0x24b6, 0x24cf, 1048602,
+    0x2c00, 0x2c2e, 1048624,
+    0x2c7e, 0x2c7f, 1037761,
+    0xff21, 0xff3a, 1048608,
+    0x10400, 0x10427, 1048616,
+    0x118a0, 0x118bf, 1048608,
+};
+
+} // !namespace
+
+namespace {
+
+const char32_t tolowers[] = {
+    0x0100, 1048577,
+    0x0102, 1048577,
+    0x0104, 1048577,
+    0x0106, 1048577,
+    0x0108, 1048577,
+    0x010a, 1048577,
+    0x010c, 1048577,
+    0x010e, 1048577,
+    0x0110, 1048577,
+    0x0112, 1048577,
+    0x0114, 1048577,
+    0x0116, 1048577,
+    0x0118, 1048577,
+    0x011a, 1048577,
+    0x011c, 1048577,
+    0x011e, 1048577,
+    0x0120, 1048577,
+    0x0122, 1048577,
+    0x0124, 1048577,
+    0x0126, 1048577,
+    0x0128, 1048577,
+    0x012a, 1048577,
+    0x012c, 1048577,
+    0x012e, 1048577,
+    0x0130, 1048377,
+    0x0132, 1048577,
+    0x0134, 1048577,
+    0x0136, 1048577,
+    0x0139, 1048577,
+    0x013b, 1048577,
+    0x013d, 1048577,
+    0x013f, 1048577,
+    0x0141, 1048577,
+    0x0143, 1048577,
+    0x0145, 1048577,
+    0x0147, 1048577,
+    0x014a, 1048577,
+    0x014c, 1048577,
+    0x014e, 1048577,
+    0x0150, 1048577,
+    0x0152, 1048577,
+    0x0154, 1048577,
+    0x0156, 1048577,
+    0x0158, 1048577,
+    0x015a, 1048577,
+    0x015c, 1048577,
+    0x015e, 1048577,
+    0x0160, 1048577,
+    0x0162, 1048577,
+    0x0164, 1048577,
+    0x0166, 1048577,
+    0x0168, 1048577,
+    0x016a, 1048577,
+    0x016c, 1048577,
+    0x016e, 1048577,
+    0x0170, 1048577,
+    0x0172, 1048577,
+    0x0174, 1048577,
+    0x0176, 1048577,
+    0x0178, 1048455,
+    0x0179, 1048577,
+    0x017b, 1048577,
+    0x017d, 1048577,
+    0x0181, 1048786,
+    0x0182, 1048577,
+    0x0184, 1048577,
+    0x0186, 1048782,
+    0x0187, 1048577,
+    0x018b, 1048577,
+    0x018e, 1048655,
+    0x018f, 1048778,
+    0x0190, 1048779,
+    0x0191, 1048577,
+    0x0193, 1048781,
+    0x0194, 1048783,
+    0x0196, 1048787,
+    0x0197, 1048785,
+    0x0198, 1048577,
+    0x019c, 1048787,
+    0x019d, 1048789,
+    0x019f, 1048790,
+    0x01a0, 1048577,
+    0x01a2, 1048577,
+    0x01a4, 1048577,
+    0x01a6, 1048794,
+    0x01a7, 1048577,
+    0x01a9, 1048794,
+    0x01ac, 1048577,
+    0x01ae, 1048794,
+    0x01af, 1048577,
+    0x01b3, 1048577,
+    0x01b5, 1048577,
+    0x01b7, 1048795,
+    0x01b8, 1048577,
+    0x01bc, 1048577,
+    0x01c4, 1048578,
+    0x01c5, 1048577,
+    0x01c7, 1048578,
+    0x01c8, 1048577,
+    0x01ca, 1048578,
+    0x01cb, 1048577,
+    0x01cd, 1048577,
+    0x01cf, 1048577,
+    0x01d1, 1048577,
+    0x01d3, 1048577,
+    0x01d5, 1048577,
+    0x01d7, 1048577,
+    0x01d9, 1048577,
+    0x01db, 1048577,
+    0x01de, 1048577,
+    0x01e0, 1048577,
+    0x01e2, 1048577,
+    0x01e4, 1048577,
+    0x01e6, 1048577,
+    0x01e8, 1048577,
+    0x01ea, 1048577,
+    0x01ec, 1048577,
+    0x01ee, 1048577,
+    0x01f1, 1048578,
+    0x01f2, 1048577,
+    0x01f4, 1048577,
+    0x01f6, 1048479,
+    0x01f7, 1048520,
+    0x01f8, 1048577,
+    0x01fa, 1048577,
+    0x01fc, 1048577,
+    0x01fe, 1048577,
+    0x0200, 1048577,
+    0x0202, 1048577,
+    0x0204, 1048577,
+    0x0206, 1048577,
+    0x0208, 1048577,
+    0x020a, 1048577,
+    0x020c, 1048577,
+    0x020e, 1048577,
+    0x0210, 1048577,
+    0x0212, 1048577,
+    0x0214, 1048577,
+    0x0216, 1048577,
+    0x0218, 1048577,
+    0x021a, 1048577,
+    0x021c, 1048577,
+    0x021e, 1048577,
+    0x0220, 1048446,
+    0x0222, 1048577,
+    0x0224, 1048577,
+    0x0226, 1048577,
+    0x0228, 1048577,
+    0x022a, 1048577,
+    0x022c, 1048577,
+    0x022e, 1048577,
+    0x0230, 1048577,
+    0x0232, 1048577,
+    0x023a, 1059371,
+    0x023b, 1048577,
+    0x023d, 1048413,
+    0x023e, 1059368,
+    0x0241, 1048577,
+    0x0243, 1048381,
+    0x0244, 1048645,
+    0x0245, 1048647,
+    0x0246, 1048577,
+    0x0248, 1048577,
+    0x024a, 1048577,
+    0x024c, 1048577,
+    0x024e, 1048577,
+    0x0370, 1048577,
+    0x0372, 1048577,
+    0x0376, 1048577,
+    0x037f, 1048692,
+    0x0386, 1048614,
+    0x038c, 1048640,
+    0x03cf, 1048584,
+    0x03d8, 1048577,
+    0x03da, 1048577,
+    0x03dc, 1048577,
+    0x03de, 1048577,
+    0x03e0, 1048577,
+    0x03e2, 1048577,
+    0x03e4, 1048577,
+    0x03e6, 1048577,
+    0x03e8, 1048577,
+    0x03ea, 1048577,
+    0x03ec, 1048577,
+    0x03ee, 1048577,
+    0x03f4, 1048516,
+    0x03f7, 1048577,
+    0x03f9, 1048569,
+    0x03fa, 1048577,
+    0x0460, 1048577,
+    0x0462, 1048577,
+    0x0464, 1048577,
+    0x0466, 1048577,
+    0x0468, 1048577,
+    0x046a, 1048577,
+    0x046c, 1048577,
+    0x046e, 1048577,
+    0x0470, 1048577,
+    0x0472, 1048577,
+    0x0474, 1048577,
+    0x0476, 1048577,
+    0x0478, 1048577,
+    0x047a, 1048577,
+    0x047c, 1048577,
+    0x047e, 1048577,
+    0x0480, 1048577,
+    0x048a, 1048577,
+    0x048c, 1048577,
+    0x048e, 1048577,
+    0x0490, 1048577,
+    0x0492, 1048577,
+    0x0494, 1048577,
+    0x0496, 1048577,
+    0x0498, 1048577,
+    0x049a, 1048577,
+    0x049c, 1048577,
+    0x049e, 1048577,
+    0x04a0, 1048577,
+    0x04a2, 1048577,
+    0x04a4, 1048577,
+    0x04a6, 1048577,
+    0x04a8, 1048577,
+    0x04aa, 1048577,
+    0x04ac, 1048577,
+    0x04ae, 1048577,
+    0x04b0, 1048577,
+    0x04b2, 1048577,
+    0x04b4, 1048577,
+    0x04b6, 1048577,
+    0x04b8, 1048577,
+    0x04ba, 1048577,
+    0x04bc, 1048577,
+    0x04be, 1048577,
+    0x04c0, 1048591,
+    0x04c1, 1048577,
+    0x04c3, 1048577,
+    0x04c5, 1048577,
+    0x04c7, 1048577,
+    0x04c9, 1048577,
+    0x04cb, 1048577,
+    0x04cd, 1048577,
+    0x04d0, 1048577,
+    0x04d2, 1048577,
+    0x04d4, 1048577,
+    0x04d6, 1048577,
+    0x04d8, 1048577,
+    0x04da, 1048577,
+    0x04dc, 1048577,
+    0x04de, 1048577,
+    0x04e0, 1048577,
+    0x04e2, 1048577,
+    0x04e4, 1048577,
+    0x04e6, 1048577,
+    0x04e8, 1048577,
+    0x04ea, 1048577,
+    0x04ec, 1048577,
+    0x04ee, 1048577,
+    0x04f0, 1048577,
+    0x04f2, 1048577,
+    0x04f4, 1048577,
+    0x04f6, 1048577,
+    0x04f8, 1048577,
+    0x04fa, 1048577,
+    0x04fc, 1048577,
+    0x04fe, 1048577,
+    0x0500, 1048577,
+    0x0502, 1048577,
+    0x0504, 1048577,
+    0x0506, 1048577,
+    0x0508, 1048577,
+    0x050a, 1048577,
+    0x050c, 1048577,
+    0x050e, 1048577,
+    0x0510, 1048577,
+    0x0512, 1048577,
+    0x0514, 1048577,
+    0x0516, 1048577,
+    0x0518, 1048577,
+    0x051a, 1048577,
+    0x051c, 1048577,
+    0x051e, 1048577,
+    0x0520, 1048577,
+    0x0522, 1048577,
+    0x0524, 1048577,
+    0x0526, 1048577,
+    0x0528, 1048577,
+    0x052a, 1048577,
+    0x052c, 1048577,
+    0x052e, 1048577,
+    0x10c7, 1055840,
+    0x10cd, 1055840,
+    0x1e00, 1048577,
+    0x1e02, 1048577,
+    0x1e04, 1048577,
+    0x1e06, 1048577,
+    0x1e08, 1048577,
+    0x1e0a, 1048577,
+    0x1e0c, 1048577,
+    0x1e0e, 1048577,
+    0x1e10, 1048577,
+    0x1e12, 1048577,
+    0x1e14, 1048577,
+    0x1e16, 1048577,
+    0x1e18, 1048577,
+    0x1e1a, 1048577,
+    0x1e1c, 1048577,
+    0x1e1e, 1048577,
+    0x1e20, 1048577,
+    0x1e22, 1048577,
+    0x1e24, 1048577,
+    0x1e26, 1048577,
+    0x1e28, 1048577,
+    0x1e2a, 1048577,
+    0x1e2c, 1048577,
+    0x1e2e, 1048577,
+    0x1e30, 1048577,
+    0x1e32, 1048577,
+    0x1e34, 1048577,
+    0x1e36, 1048577,
+    0x1e38, 1048577,
+    0x1e3a, 1048577,
+    0x1e3c, 1048577,
+    0x1e3e, 1048577,
+    0x1e40, 1048577,
+    0x1e42, 1048577,
+    0x1e44, 1048577,
+    0x1e46, 1048577,
+    0x1e48, 1048577,
+    0x1e4a, 1048577,
+    0x1e4c, 1048577,
+    0x1e4e, 1048577,
+    0x1e50, 1048577,
+    0x1e52, 1048577,
+    0x1e54, 1048577,
+    0x1e56, 1048577,
+    0x1e58, 1048577,
+    0x1e5a, 1048577,
+    0x1e5c, 1048577,
+    0x1e5e, 1048577,
+    0x1e60, 1048577,
+    0x1e62, 1048577,
+    0x1e64, 1048577,
+    0x1e66, 1048577,
+    0x1e68, 1048577,
+    0x1e6a, 1048577,
+    0x1e6c, 1048577,
+    0x1e6e, 1048577,
+    0x1e70, 1048577,
+    0x1e72, 1048577,
+    0x1e74, 1048577,
+    0x1e76, 1048577,
+    0x1e78, 1048577,
+    0x1e7a, 1048577,
+    0x1e7c, 1048577,
+    0x1e7e, 1048577,
+    0x1e80, 1048577,
+    0x1e82, 1048577,
+    0x1e84, 1048577,
+    0x1e86, 1048577,
+    0x1e88, 1048577,
+    0x1e8a, 1048577,
+    0x1e8c, 1048577,
+    0x1e8e, 1048577,
+    0x1e90, 1048577,
+    0x1e92, 1048577,
+    0x1e94, 1048577,
+    0x1e9e, 1040961,
+    0x1ea0, 1048577,
+    0x1ea2, 1048577,
+    0x1ea4, 1048577,
+    0x1ea6, 1048577,
+    0x1ea8, 1048577,
+    0x1eaa, 1048577,
+    0x1eac, 1048577,
+    0x1eae, 1048577,
+    0x1eb0, 1048577,
+    0x1eb2, 1048577,
+    0x1eb4, 1048577,
+    0x1eb6, 1048577,
+    0x1eb8, 1048577,
+    0x1eba, 1048577,
+    0x1ebc, 1048577,
+    0x1ebe, 1048577,
+    0x1ec0, 1048577,
+    0x1ec2, 1048577,
+    0x1ec4, 1048577,
+    0x1ec6, 1048577,
+    0x1ec8, 1048577,
+    0x1eca, 1048577,
+    0x1ecc, 1048577,
+    0x1ece, 1048577,
+    0x1ed0, 1048577,
+    0x1ed2, 1048577,
+    0x1ed4, 1048577,
+    0x1ed6, 1048577,
+    0x1ed8, 1048577,
+    0x1eda, 1048577,
+    0x1edc, 1048577,
+    0x1ede, 1048577,
+    0x1ee0, 1048577,
+    0x1ee2, 1048577,
+    0x1ee4, 1048577,
+    0x1ee6, 1048577,
+    0x1ee8, 1048577,
+    0x1eea, 1048577,
+    0x1eec, 1048577,
+    0x1eee, 1048577,
+    0x1ef0, 1048577,
+    0x1ef2, 1048577,
+    0x1ef4, 1048577,
+    0x1ef6, 1048577,
+    0x1ef8, 1048577,
+    0x1efa, 1048577,
+    0x1efc, 1048577,
+    0x1efe, 1048577,
+    0x1f59, 1048568,
+    0x1f5b, 1048568,
+    0x1f5d, 1048568,
+    0x1f5f, 1048568,
+    0x1fbc, 1048567,
+    0x1fcc, 1048567,
+    0x1fec, 1048569,
+    0x1ffc, 1048567,
+    0x2126, 1041059,
+    0x212a, 1040193,
+    0x212b, 1040314,
+    0x2132, 1048604,
+    0x2183, 1048577,
+    0x2c60, 1048577,
+    0x2c62, 1037833,
+    0x2c63, 1044762,
+    0x2c64, 1037849,
+    0x2c67, 1048577,
+    0x2c69, 1048577,
+    0x2c6b, 1048577,
+    0x2c6d, 1037796,
+    0x2c6e, 1037827,
+    0x2c6f, 1037793,
+    0x2c70, 1037794,
+    0x2c72, 1048577,
+    0x2c75, 1048577,
+    0x2c80, 1048577,
+    0x2c82, 1048577,
+    0x2c84, 1048577,
+    0x2c86, 1048577,
+    0x2c88, 1048577,
+    0x2c8a, 1048577,
+    0x2c8c, 1048577,
+    0x2c8e, 1048577,
+    0x2c90, 1048577,
+    0x2c92, 1048577,
+    0x2c94, 1048577,
+    0x2c96, 1048577,
+    0x2c98, 1048577,
+    0x2c9a, 1048577,
+    0x2c9c, 1048577,
+    0x2c9e, 1048577,
+    0x2ca0, 1048577,
+    0x2ca2, 1048577,
+    0x2ca4, 1048577,
+    0x2ca6, 1048577,
+    0x2ca8, 1048577,
+    0x2caa, 1048577,
+    0x2cac, 1048577,
+    0x2cae, 1048577,
+    0x2cb0, 1048577,
+    0x2cb2, 1048577,
+    0x2cb4, 1048577,
+    0x2cb6, 1048577,
+    0x2cb8, 1048577,
+    0x2cba, 1048577,
+    0x2cbc, 1048577,
+    0x2cbe, 1048577,
+    0x2cc0, 1048577,
+    0x2cc2, 1048577,
+    0x2cc4, 1048577,
+    0x2cc6, 1048577,
+    0x2cc8, 1048577,
+    0x2cca, 1048577,
+    0x2ccc, 1048577,
+    0x2cce, 1048577,
+    0x2cd0, 1048577,
+    0x2cd2, 1048577,
+    0x2cd4, 1048577,
+    0x2cd6, 1048577,
+    0x2cd8, 1048577,
+    0x2cda, 1048577,
+    0x2cdc, 1048577,
+    0x2cde, 1048577,
+    0x2ce0, 1048577,
+    0x2ce2, 1048577,
+    0x2ceb, 1048577,
+    0x2ced, 1048577,
+    0x2cf2, 1048577,
+    0xa640, 1048577,
+    0xa642, 1048577,
+    0xa644, 1048577,
+    0xa646, 1048577,
+    0xa648, 1048577,
+    0xa64a, 1048577,
+    0xa64c, 1048577,
+    0xa64e, 1048577,
+    0xa650, 1048577,
+    0xa652, 1048577,
+    0xa654, 1048577,
+    0xa656, 1048577,
+    0xa658, 1048577,
+    0xa65a, 1048577,
+    0xa65c, 1048577,
+    0xa65e, 1048577,
+    0xa660, 1048577,
+    0xa662, 1048577,
+    0xa664, 1048577,
+    0xa666, 1048577,
+    0xa668, 1048577,
+    0xa66a, 1048577,
+    0xa66c, 1048577,
+    0xa680, 1048577,
+    0xa682, 1048577,
+    0xa684, 1048577,
+    0xa686, 1048577,
+    0xa688, 1048577,
+    0xa68a, 1048577,
+    0xa68c, 1048577,
+    0xa68e, 1048577,
+    0xa690, 1048577,
+    0xa692, 1048577,
+    0xa694, 1048577,
+    0xa696, 1048577,
+    0xa698, 1048577,
+    0xa69a, 1048577,
+    0xa722, 1048577,
+    0xa724, 1048577,
+    0xa726, 1048577,
+    0xa728, 1048577,
+    0xa72a, 1048577,
+    0xa72c, 1048577,
+    0xa72e, 1048577,
+    0xa732, 1048577,
+    0xa734, 1048577,
+    0xa736, 1048577,
+    0xa738, 1048577,
+    0xa73a, 1048577,
+    0xa73c, 1048577,
+    0xa73e, 1048577,
+    0xa740, 1048577,
+    0xa742, 1048577,
+    0xa744, 1048577,
+    0xa746, 1048577,
+    0xa748, 1048577,
+    0xa74a, 1048577,
+    0xa74c, 1048577,
+    0xa74e, 1048577,
+    0xa750, 1048577,
+    0xa752, 1048577,
+    0xa754, 1048577,
+    0xa756, 1048577,
+    0xa758, 1048577,
+    0xa75a, 1048577,
+    0xa75c, 1048577,
+    0xa75e, 1048577,
+    0xa760, 1048577,
+    0xa762, 1048577,
+    0xa764, 1048577,
+    0xa766, 1048577,
+    0xa768, 1048577,
+    0xa76a, 1048577,
+    0xa76c, 1048577,
+    0xa76e, 1048577,
+    0xa779, 1048577,
+    0xa77b, 1048577,
+    0xa77d, 1013244,
+    0xa77e, 1048577,
+    0xa780, 1048577,
+    0xa782, 1048577,
+    0xa784, 1048577,
+    0xa786, 1048577,
+    0xa78b, 1048577,
+    0xa78d, 1006296,
+    0xa790, 1048577,
+    0xa792, 1048577,
+    0xa796, 1048577,
+    0xa798, 1048577,
+    0xa79a, 1048577,
+    0xa79c, 1048577,
+    0xa79e, 1048577,
+    0xa7a0, 1048577,
+    0xa7a2, 1048577,
+    0xa7a4, 1048577,
+    0xa7a6, 1048577,
+    0xa7a8, 1048577,
+    0xa7aa, 1006268,
+    0xa7ab, 1006257,
+    0xa7ac, 1006261,
+    0xa7ad, 1006271,
+    0xa7b0, 1006318,
+    0xa7b1, 1006294,
+};
+
+} // !namespace
+
+char32_t tolower(char32_t c) noexcept
+{
+   const char32_t *p;
+
+   p = rbsearch(c, tolowerr, nelem (tolowerr)/3, 3);
+
+   if (p && c >= p[0] && c <= p[1])
+       return c + p[2] - 1048576;
+
+ p = rbsearch(c, tolowers, nelem (tolowers)/2, 2);
+
+   if (p && c == p[0])
+       return c + p[1] - 1048576;
+
+   return c;
+}
+
+namespace {
+
+const char32_t totitler[] = {
+    0x0061, 0x007a, 1048544,
+    0x00e0, 0x00f6, 1048544,
+    0x00f8, 0x00fe, 1048544,
+    0x023f, 0x0240, 1059391,
+    0x0256, 0x0257, 1048371,
+    0x028a, 0x028b, 1048359,
+    0x037b, 0x037d, 1048706,
+    0x03ad, 0x03af, 1048539,
+    0x03b1, 0x03c1, 1048544,
+    0x03c3, 0x03cb, 1048544,
+    0x03cd, 0x03ce, 1048513,
+    0x0430, 0x044f, 1048544,
+    0x0450, 0x045f, 1048496,
+    0x0561, 0x0586, 1048528,
+    0x1f00, 0x1f07, 1048584,
+    0x1f10, 0x1f15, 1048584,
+    0x1f20, 0x1f27, 1048584,
+    0x1f30, 0x1f37, 1048584,
+    0x1f40, 0x1f45, 1048584,
+    0x1f60, 0x1f67, 1048584,
+    0x1f70, 0x1f71, 1048650,
+    0x1f72, 0x1f75, 1048662,
+    0x1f76, 0x1f77, 1048676,
+    0x1f78, 0x1f79, 1048704,
+    0x1f7a, 0x1f7b, 1048688,
+    0x1f7c, 0x1f7d, 1048702,
+    0x1f80, 0x1f87, 1048584,
+    0x1f90, 0x1f97, 1048584,
+    0x1fa0, 0x1fa7, 1048584,
+    0x1fb0, 0x1fb1, 1048584,
+    0x1fd0, 0x1fd1, 1048584,
+    0x1fe0, 0x1fe1, 1048584,
+    0x2170, 0x217f, 1048560,
+    0x24d0, 0x24e9, 1048550,
+    0x2c30, 0x2c5e, 1048528,
+    0x2d00, 0x2d25, 1041312,
+    0xff41, 0xff5a, 1048544,
+    0x10428, 0x1044f, 1048536,
+    0x118c0, 0x118df, 1048544,
+};
+
+} // !namespace
+
+namespace {
+
+const char32_t totitles[] = {
+    0x00b5, 1049319,
+    0x00ff, 1048697,
+    0x0101, 1048575,
+    0x0103, 1048575,
+    0x0105, 1048575,
+    0x0107, 1048575,
+    0x0109, 1048575,
+    0x010b, 1048575,
+    0x010d, 1048575,
+    0x010f, 1048575,
+    0x0111, 1048575,
+    0x0113, 1048575,
+    0x0115, 1048575,
+    0x0117, 1048575,
+    0x0119, 1048575,
+    0x011b, 1048575,
+    0x011d, 1048575,
+    0x011f, 1048575,
+    0x0121, 1048575,
+    0x0123, 1048575,
+    0x0125, 1048575,
+    0x0127, 1048575,
+    0x0129, 1048575,
+    0x012b, 1048575,
+    0x012d, 1048575,
+    0x012f, 1048575,
+    0x0131, 1048344,
+    0x0133, 1048575,
+    0x0135, 1048575,
+    0x0137, 1048575,
+    0x013a, 1048575,
+    0x013c, 1048575,
+    0x013e, 1048575,
+    0x0140, 1048575,
+    0x0142, 1048575,
+    0x0144, 1048575,
+    0x0146, 1048575,
+    0x0148, 1048575,
+    0x014b, 1048575,
+    0x014d, 1048575,
+    0x014f, 1048575,
+    0x0151, 1048575,
+    0x0153, 1048575,
+    0x0155, 1048575,
+    0x0157, 1048575,
+    0x0159, 1048575,
+    0x015b, 1048575,
+    0x015d, 1048575,
+    0x015f, 1048575,
+    0x0161, 1048575,
+    0x0163, 1048575,
+    0x0165, 1048575,
+    0x0167, 1048575,
+    0x0169, 1048575,
+    0x016b, 1048575,
+    0x016d, 1048575,
+    0x016f, 1048575,
+    0x0171, 1048575,
+    0x0173, 1048575,
+    0x0175, 1048575,
+    0x0177, 1048575,
+    0x017a, 1048575,
+    0x017c, 1048575,
+    0x017e, 1048575,
+    0x017f, 1048276,
+    0x0180, 1048771,
+    0x0183, 1048575,
+    0x0185, 1048575,
+    0x0188, 1048575,
+    0x018c, 1048575,
+    0x0192, 1048575,
+    0x0195, 1048673,
+    0x0199, 1048575,
+    0x019a, 1048739,
+    0x019e, 1048706,
+    0x01a1, 1048575,
+    0x01a3, 1048575,
+    0x01a5, 1048575,
+    0x01a8, 1048575,
+    0x01ad, 1048575,
+    0x01b0, 1048575,
+    0x01b4, 1048575,
+    0x01b6, 1048575,
+    0x01b9, 1048575,
+    0x01bd, 1048575,
+    0x01bf, 1048632,
+    0x01c4, 1048577,
+    0x01c6, 1048575,
+    0x01c7, 1048577,
+    0x01c9, 1048575,
+    0x01ca, 1048577,
+    0x01cc, 1048575,
+    0x01ce, 1048575,
+    0x01d0, 1048575,
+    0x01d2, 1048575,
+    0x01d4, 1048575,
+    0x01d6, 1048575,
+    0x01d8, 1048575,
+    0x01da, 1048575,
+    0x01dc, 1048575,
+    0x01dd, 1048497,
+    0x01df, 1048575,
+    0x01e1, 1048575,
+    0x01e3, 1048575,
+    0x01e5, 1048575,
+    0x01e7, 1048575,
+    0x01e9, 1048575,
+    0x01eb, 1048575,
+    0x01ed, 1048575,
+    0x01ef, 1048575,
+    0x01f1, 1048577,
+    0x01f3, 1048575,
+    0x01f5, 1048575,
+    0x01f9, 1048575,
+    0x01fb, 1048575,
+    0x01fd, 1048575,
+    0x01ff, 1048575,
+    0x0201, 1048575,
+    0x0203, 1048575,
+    0x0205, 1048575,
+    0x0207, 1048575,
+    0x0209, 1048575,
+    0x020b, 1048575,
+    0x020d, 1048575,
+    0x020f, 1048575,
+    0x0211, 1048575,
+    0x0213, 1048575,
+    0x0215, 1048575,
+    0x0217, 1048575,
+    0x0219, 1048575,
+    0x021b, 1048575,
+    0x021d, 1048575,
+    0x021f, 1048575,
+    0x0223, 1048575,
+    0x0225, 1048575,
+    0x0227, 1048575,
+    0x0229, 1048575,
+    0x022b, 1048575,
+    0x022d, 1048575,
+    0x022f, 1048575,
+    0x0231, 1048575,
+    0x0233, 1048575,
+    0x023c, 1048575,
+    0x0242, 1048575,
+    0x0247, 1048575,
+    0x0249, 1048575,
+    0x024b, 1048575,
+    0x024d, 1048575,
+    0x024f, 1048575,
+    0x0250, 1059359,
+    0x0251, 1059356,
+    0x0252, 1059358,
+    0x0253, 1048366,
+    0x0254, 1048370,
+    0x0259, 1048374,
+    0x025b, 1048373,
+    0x025c, 1090895,
+    0x0260, 1048371,
+    0x0261, 1090891,
+    0x0263, 1048369,
+    0x0265, 1090856,
+    0x0266, 1090884,
+    0x0268, 1048367,
+    0x0269, 1048365,
+    0x026b, 1059319,
+    0x026c, 1090881,
+    0x026f, 1048365,
+    0x0271, 1059325,
+    0x0272, 1048363,
+    0x0275, 1048362,
+    0x027d, 1059303,
+    0x0280, 1048358,
+    0x0283, 1048358,
+    0x0287, 1090858,
+    0x0288, 1048358,
+    0x0289, 1048507,
+    0x028c, 1048505,
+    0x0292, 1048357,
+    0x029e, 1090834,
+    0x0345, 1048660,
+    0x0371, 1048575,
+    0x0373, 1048575,
+    0x0377, 1048575,
+    0x03ac, 1048538,
+    0x03c2, 1048545,
+    0x03cc, 1048512,
+    0x03d0, 1048514,
+    0x03d1, 1048519,
+    0x03d5, 1048529,
+    0x03d6, 1048522,
+    0x03d7, 1048568,
+    0x03d9, 1048575,
+    0x03db, 1048575,
+    0x03dd, 1048575,
+    0x03df, 1048575,
+    0x03e1, 1048575,
+    0x03e3, 1048575,
+    0x03e5, 1048575,
+    0x03e7, 1048575,
+    0x03e9, 1048575,
+    0x03eb, 1048575,
+    0x03ed, 1048575,
+    0x03ef, 1048575,
+    0x03f0, 1048490,
+    0x03f1, 1048496,
+    0x03f2, 1048583,
+    0x03f3, 1048460,
+    0x03f5, 1048480,
+    0x03f8, 1048575,
+    0x03fb, 1048575,
+    0x0461, 1048575,
+    0x0463, 1048575,
+    0x0465, 1048575,
+    0x0467, 1048575,
+    0x0469, 1048575,
+    0x046b, 1048575,
+    0x046d, 1048575,
+    0x046f, 1048575,
+    0x0471, 1048575,
+    0x0473, 1048575,
+    0x0475, 1048575,
+    0x0477, 1048575,
+    0x0479, 1048575,
+    0x047b, 1048575,
+    0x047d, 1048575,
+    0x047f, 1048575,
+    0x0481, 1048575,
+    0x048b, 1048575,
+    0x048d, 1048575,
+    0x048f, 1048575,
+    0x0491, 1048575,
+    0x0493, 1048575,
+    0x0495, 1048575,
+    0x0497, 1048575,
+    0x0499, 1048575,
+    0x049b, 1048575,
+    0x049d, 1048575,
+    0x049f, 1048575,
+    0x04a1, 1048575,
+    0x04a3, 1048575,
+    0x04a5, 1048575,
+    0x04a7, 1048575,
+    0x04a9, 1048575,
+    0x04ab, 1048575,
+    0x04ad, 1048575,
+    0x04af, 1048575,
+    0x04b1, 1048575,
+    0x04b3, 1048575,
+    0x04b5, 1048575,
+    0x04b7, 1048575,
+    0x04b9, 1048575,
+    0x04bb, 1048575,
+    0x04bd, 1048575,
+    0x04bf, 1048575,
+    0x04c2, 1048575,
+    0x04c4, 1048575,
+    0x04c6, 1048575,
+    0x04c8, 1048575,
+    0x04ca, 1048575,
+    0x04cc, 1048575,
+    0x04ce, 1048575,
+    0x04cf, 1048561,
+    0x04d1, 1048575,
+    0x04d3, 1048575,
+    0x04d5, 1048575,
+    0x04d7, 1048575,
+    0x04d9, 1048575,
+    0x04db, 1048575,
+    0x04dd, 1048575,
+    0x04df, 1048575,
+    0x04e1, 1048575,
+    0x04e3, 1048575,
+    0x04e5, 1048575,
+    0x04e7, 1048575,
+    0x04e9, 1048575,
+    0x04eb, 1048575,
+    0x04ed, 1048575,
+    0x04ef, 1048575,
+    0x04f1, 1048575,
+    0x04f3, 1048575,
+    0x04f5, 1048575,
+    0x04f7, 1048575,
+    0x04f9, 1048575,
+    0x04fb, 1048575,
+    0x04fd, 1048575,
+    0x04ff, 1048575,
+    0x0501, 1048575,
+    0x0503, 1048575,
+    0x0505, 1048575,
+    0x0507, 1048575,
+    0x0509, 1048575,
+    0x050b, 1048575,
+    0x050d, 1048575,
+    0x050f, 1048575,
+    0x0511, 1048575,
+    0x0513, 1048575,
+    0x0515, 1048575,
+    0x0517, 1048575,
+    0x0519, 1048575,
+    0x051b, 1048575,
+    0x051d, 1048575,
+    0x051f, 1048575,
+    0x0521, 1048575,
+    0x0523, 1048575,
+    0x0525, 1048575,
+    0x0527, 1048575,
+    0x0529, 1048575,
+    0x052b, 1048575,
+    0x052d, 1048575,
+    0x052f, 1048575,
+    0x1d79, 1083908,
+    0x1d7d, 1052390,
+    0x1e01, 1048575,
+    0x1e03, 1048575,
+    0x1e05, 1048575,
+    0x1e07, 1048575,
+    0x1e09, 1048575,
+    0x1e0b, 1048575,
+    0x1e0d, 1048575,
+    0x1e0f, 1048575,
+    0x1e11, 1048575,
+    0x1e13, 1048575,
+    0x1e15, 1048575,
+    0x1e17, 1048575,
+    0x1e19, 1048575,
+    0x1e1b, 1048575,
+    0x1e1d, 1048575,
+    0x1e1f, 1048575,
+    0x1e21, 1048575,
+    0x1e23, 1048575,
+    0x1e25, 1048575,
+    0x1e27, 1048575,
+    0x1e29, 1048575,
+    0x1e2b, 1048575,
+    0x1e2d, 1048575,
+    0x1e2f, 1048575,
+    0x1e31, 1048575,
+    0x1e33, 1048575,
+    0x1e35, 1048575,
+    0x1e37, 1048575,
+    0x1e39, 1048575,
+    0x1e3b, 1048575,
+    0x1e3d, 1048575,
+    0x1e3f, 1048575,
+    0x1e41, 1048575,
+    0x1e43, 1048575,
+    0x1e45, 1048575,
+    0x1e47, 1048575,
+    0x1e49, 1048575,
+    0x1e4b, 1048575,
+    0x1e4d, 1048575,
+    0x1e4f, 1048575,
+    0x1e51, 1048575,
+    0x1e53, 1048575,
+    0x1e55, 1048575,
+    0x1e57, 1048575,
+    0x1e59, 1048575,
+    0x1e5b, 1048575,
+    0x1e5d, 1048575,
+    0x1e5f, 1048575,
+    0x1e61, 1048575,
+    0x1e63, 1048575,
+    0x1e65, 1048575,
+    0x1e67, 1048575,
+    0x1e69, 1048575,
+    0x1e6b, 1048575,
+    0x1e6d, 1048575,
+    0x1e6f, 1048575,
+    0x1e71, 1048575,
+    0x1e73, 1048575,
+    0x1e75, 1048575,
+    0x1e77, 1048575,
+    0x1e79, 1048575,
+    0x1e7b, 1048575,
+    0x1e7d, 1048575,
+    0x1e7f, 1048575,
+    0x1e81, 1048575,
+    0x1e83, 1048575,
+    0x1e85, 1048575,
+    0x1e87, 1048575,
+    0x1e89, 1048575,
+    0x1e8b, 1048575,
+    0x1e8d, 1048575,
+    0x1e8f, 1048575,
+    0x1e91, 1048575,
+    0x1e93, 1048575,
+    0x1e95, 1048575,
+    0x1e9b, 1048517,
+    0x1ea1, 1048575,
+    0x1ea3, 1048575,
+    0x1ea5, 1048575,
+    0x1ea7, 1048575,
+    0x1ea9, 1048575,
+    0x1eab, 1048575,
+    0x1ead, 1048575,
+    0x1eaf, 1048575,
+    0x1eb1, 1048575,
+    0x1eb3, 1048575,
+    0x1eb5, 1048575,
+    0x1eb7, 1048575,
+    0x1eb9, 1048575,
+    0x1ebb, 1048575,
+    0x1ebd, 1048575,
+    0x1ebf, 1048575,
+    0x1ec1, 1048575,
+    0x1ec3, 1048575,
+    0x1ec5, 1048575,
+    0x1ec7, 1048575,
+    0x1ec9, 1048575,
+    0x1ecb, 1048575,
+    0x1ecd, 1048575,
+    0x1ecf, 1048575,
+    0x1ed1, 1048575,
+    0x1ed3, 1048575,
+    0x1ed5, 1048575,
+    0x1ed7, 1048575,
+    0x1ed9, 1048575,
+    0x1edb, 1048575,
+    0x1edd, 1048575,
+    0x1edf, 1048575,
+    0x1ee1, 1048575,
+    0x1ee3, 1048575,
+    0x1ee5, 1048575,
+    0x1ee7, 1048575,
+    0x1ee9, 1048575,
+    0x1eeb, 1048575,
+    0x1eed, 1048575,
+    0x1eef, 1048575,
+    0x1ef1, 1048575,
+    0x1ef3, 1048575,
+    0x1ef5, 1048575,
+    0x1ef7, 1048575,
+    0x1ef9, 1048575,
+    0x1efb, 1048575,
+    0x1efd, 1048575,
+    0x1eff, 1048575,
+    0x1f51, 1048584,
+    0x1f53, 1048584,
+    0x1f55, 1048584,
+    0x1f57, 1048584,
+    0x1fb3, 1048585,
+    0x1fbe, 1041371,
+    0x1fc3, 1048585,
+    0x1fe5, 1048583,
+    0x1ff3, 1048585,
+    0x214e, 1048548,
+    0x2184, 1048575,
+    0x2c61, 1048575,
+    0x2c65, 1037781,
+    0x2c66, 1037784,
+    0x2c68, 1048575,
+    0x2c6a, 1048575,
+    0x2c6c, 1048575,
+    0x2c73, 1048575,
+    0x2c76, 1048575,
+    0x2c81, 1048575,
+    0x2c83, 1048575,
+    0x2c85, 1048575,
+    0x2c87, 1048575,
+    0x2c89, 1048575,
+    0x2c8b, 1048575,
+    0x2c8d, 1048575,
+    0x2c8f, 1048575,
+    0x2c91, 1048575,
+    0x2c93, 1048575,
+    0x2c95, 1048575,
+    0x2c97, 1048575,
+    0x2c99, 1048575,
+    0x2c9b, 1048575,
+    0x2c9d, 1048575,
+    0x2c9f, 1048575,
+    0x2ca1, 1048575,
+    0x2ca3, 1048575,
+    0x2ca5, 1048575,
+    0x2ca7, 1048575,
+    0x2ca9, 1048575,
+    0x2cab, 1048575,
+    0x2cad, 1048575,
+    0x2caf, 1048575,
+    0x2cb1, 1048575,
+    0x2cb3, 1048575,
+    0x2cb5, 1048575,
+    0x2cb7, 1048575,
+    0x2cb9, 1048575,
+    0x2cbb, 1048575,
+    0x2cbd, 1048575,
+    0x2cbf, 1048575,
+    0x2cc1, 1048575,
+    0x2cc3, 1048575,
+    0x2cc5, 1048575,
+    0x2cc7, 1048575,
+    0x2cc9, 1048575,
+    0x2ccb, 1048575,
+    0x2ccd, 1048575,
+    0x2ccf, 1048575,
+    0x2cd1, 1048575,
+    0x2cd3, 1048575,
+    0x2cd5, 1048575,
+    0x2cd7, 1048575,
+    0x2cd9, 1048575,
+    0x2cdb, 1048575,
+    0x2cdd, 1048575,
+    0x2cdf, 1048575,
+    0x2ce1, 1048575,
+    0x2ce3, 1048575,
+    0x2cec, 1048575,
+    0x2cee, 1048575,
+    0x2cf3, 1048575,
+    0x2d27, 1041312,
+    0x2d2d, 1041312,
+    0xa641, 1048575,
+    0xa643, 1048575,
+    0xa645, 1048575,
+    0xa647, 1048575,
+    0xa649, 1048575,
+    0xa64b, 1048575,
+    0xa64d, 1048575,
+    0xa64f, 1048575,
+    0xa651, 1048575,
+    0xa653, 1048575,
+    0xa655, 1048575,
+    0xa657, 1048575,
+    0xa659, 1048575,
+    0xa65b, 1048575,
+    0xa65d, 1048575,
+    0xa65f, 1048575,
+    0xa661, 1048575,
+    0xa663, 1048575,
+    0xa665, 1048575,
+    0xa667, 1048575,
+    0xa669, 1048575,
+    0xa66b, 1048575,
+    0xa66d, 1048575,
+    0xa681, 1048575,
+    0xa683, 1048575,
+    0xa685, 1048575,
+    0xa687, 1048575,
+    0xa689, 1048575,
+    0xa68b, 1048575,
+    0xa68d, 1048575,
+    0xa68f, 1048575,
+    0xa691, 1048575,
+    0xa693, 1048575,
+    0xa695, 1048575,
+    0xa697, 1048575,
+    0xa699, 1048575,
+    0xa69b, 1048575,
+    0xa723, 1048575,
+    0xa725, 1048575,
+    0xa727, 1048575,
+    0xa729, 1048575,
+    0xa72b, 1048575,
+    0xa72d, 1048575,
+    0xa72f, 1048575,
+    0xa733, 1048575,
+    0xa735, 1048575,
+    0xa737, 1048575,
+    0xa739, 1048575,
+    0xa73b, 1048575,
+    0xa73d, 1048575,
+    0xa73f, 1048575,
+    0xa741, 1048575,
+    0xa743, 1048575,
+    0xa745, 1048575,
+    0xa747, 1048575,
+    0xa749, 1048575,
+    0xa74b, 1048575,
+    0xa74d, 1048575,
+    0xa74f, 1048575,
+    0xa751, 1048575,
+    0xa753, 1048575,
+    0xa755, 1048575,
+    0xa757, 1048575,
+    0xa759, 1048575,
+    0xa75b, 1048575,
+    0xa75d, 1048575,
+    0xa75f, 1048575,
+    0xa761, 1048575,
+    0xa763, 1048575,
+    0xa765, 1048575,
+    0xa767, 1048575,
+    0xa769, 1048575,
+    0xa76b, 1048575,
+    0xa76d, 1048575,
+    0xa76f, 1048575,
+    0xa77a, 1048575,
+    0xa77c, 1048575,
+    0xa77f, 1048575,
+    0xa781, 1048575,
+    0xa783, 1048575,
+    0xa785, 1048575,
+    0xa787, 1048575,
+    0xa78c, 1048575,
+    0xa791, 1048575,
+    0xa793, 1048575,
+    0xa797, 1048575,
+    0xa799, 1048575,
+    0xa79b, 1048575,
+    0xa79d, 1048575,
+    0xa79f, 1048575,
+    0xa7a1, 1048575,
+    0xa7a3, 1048575,
+    0xa7a5, 1048575,
+    0xa7a7, 1048575,
+    0xa7a9, 1048575,
+};
+
+} // !namespace
+
+char32_t totitle(char32_t c) noexcept
+{
+   const char32_t *p;
+
+   p = rbsearch(c, totitler, nelem (totitler)/3, 3);
+
+   if (p && c >= p[0] && c <= p[1])
+       return c + p[2] - 1048576;
+
+ p = rbsearch(c, totitles, nelem (totitles)/2, 2);
+
+   if (p && c == p[0])
+       return c + p[1] - 1048576;
+
+   return c;
+}
+
+void encode(char32_t c, char res[5]) noexcept
+{
+    switch (nbytesPoint(c)) {
+    case 1:
+        res[0] = static_cast<char>(c);
+        res[1] = '\0';
+        break;
+    case 2:
+        res[0] = 0xC0 | ((c >> 6)  & 0x1F);
+        res[1] = 0x80 | (c & 0x3F);
+        res[2] = '\0';
+        break;
+    case 3:
+        res[0] = 0xE0 | ((c >> 12) & 0xF );
+        res[1] = 0x80 | ((c >> 6)  & 0x3F);
+        res[2] = 0x80 | (c & 0x3F);
+        res[3] = '\0';
+        break;
+    case 4:
+        res[0] = 0xF0 | ((c >> 18) & 0x7 );
+        res[1] = 0x80 | ((c >> 12) & 0x3F);
+        res[2] = 0x80 | ((c >> 6)  & 0x3F);
+        res[3] = 0x80 | (c & 0x3F);
+        res[4] = '\0';
+        break;
+    default:
+        break;
+    }
+}
+
+void decode(char32_t &c, const char *res) noexcept
+{
+    c = 0;
+
+    switch (nbytesUtf8(res[0])) {
+    case 1:
+        c = res[0];
+        break;
+    case 2:
+        c =  (res[0] & 0x1f) << 6;
+        c |= (res[1] & 0x3f);
+        break;
+    case 3:
+        c =  (res[0] & 0x0f) << 12;
+        c |= (res[1] & 0x3f) << 6;
+        c |= (res[2] & 0x3f);
+        break;
+    case 4:
+        c =  (res[0] & 0x07) << 16;
+        c |= (res[1] & 0x3f) << 12;
+        c |= (res[2] & 0x3f) << 6;
+        c |= (res[3] & 0x3f);
+    default:
+        break;
+    }
+}
+
+int nbytesUtf8(char c) noexcept
+{
+    if (static_cast<unsigned char>(c) <= 127)
+        return 1;
+    if ((c & 0xE0) == 0xC0)
+        return 2;
+    if ((c & 0xF0) == 0xE0)
+        return 3;
+    if ((c & 0xF8) == 0xF0)
+        return 4;
+
+    return -1;
+}
+
+int nbytesPoint(char32_t c) noexcept
+{
+    if (c <= 0x7F)
+        return 1;
+    if (c <= 0x7FF)
+        return 2;
+    if (c <= 0xFFFF)
+        return 3;
+    if (c <= 0x1FFFFF)
+        return 4;
+
+    return -1;
+}
+
+unsigned length(const std::string &str)
+{
+    unsigned total = 0;
+
+    forEach(str, [&] (char32_t) {
+        ++ total;
+    });
+
+    return total;
+}
+
+std::string toUtf8(const std::u32string &array)
+{
+    std::string res;
+
+    for (size_t i = 0; i < array.size(); ++i) {
+        char tmp[5];
+        int size = nbytesPoint(array[i]);
+
+        if (size < 0)
+            throw std::invalid_argument("invalid sequence");
+
+        encode(array[i], tmp);
+        res.insert(res.length(), tmp);
+    }
+
+    return res;
+}
+
+std::u32string toUtf32(const std::string &str)
+{
+    std::u32string res;
+
+    forEach(str, [&] (char32_t code) {
+        res.push_back(code);
+    });
+
+    return res;
+}
+
+} // !unicode
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/unicode.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,273 @@
+/*
+ * unicode.hpp -- UTF-8 to UTF-32 conversions and various operations
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_UNICODE_HPP
+#define IRCCD_UNICODE_HPP
+
+/**
+ * \file unicode.hpp
+ * \brief UTF-8 to UTF-32 conversions
+ * \author David Demelier <markand@malikania.fr>
+ * \warning These files are auto-generated!
+ */
+
+#include <stdexcept>
+#include <string>
+
+namespace irccd {
+
+/**
+ * \brief Unicode namespace.
+ */
+namespace unicode {
+
+/**
+ * Encode the unicode code point into multibyte string.
+ *
+ * \param point the unicode code point
+ * \param res the output buffer
+ */
+void encode(char32_t point, char res[5]) noexcept;
+
+/**
+ * Decode the multibyte buffer into an unicode code point.
+ *
+ * \param c the code point destination
+ * \param res the multibyte string.
+ */
+void decode(char32_t &c, const char *res) noexcept;
+
+/**
+ * Get the number of bytes for the first multi byte character from a
+ * utf-8 string.
+ *
+ * This can be used to iterate a valid UTF-8 string to jump to the next
+ * real character.
+ *
+ * \param c the first multi byte character
+ * \return the number of bytes [1-4] or -1 if invalid
+ */
+int nbytesUtf8(char c) noexcept;
+
+/**
+ * Get the number of bytes for the unicode point.
+ *
+ * \param point the unicode point
+ * \return the number of bytes [1-4] or -1 if invalid
+ */
+int nbytesPoint(char32_t point) noexcept;
+
+/**
+ * Get real number of character in a string.
+ *
+ * \param str the string
+ * \return the length
+ * \throw std::invalid_argument on invalid sequence
+ */
+unsigned length(const std::string &str);
+
+/**
+ * Iterate over all real characters in the UTF-8 string.
+ *
+ * The function must have the following signature:
+ *  void f(char ch)
+ *
+ * \param str the UTF-8 string
+ * \param function the function callback
+ * \throw std::invalid_argument on invalid sequence
+ */
+template <typename Func>
+void forEach(const std::string &str, Func function)
+{
+    for (size_t i = 0; i < str.size(); ) {
+        char32_t point = 0;
+        int size = nbytesUtf8(str[i]);
+
+        if (size < 0)
+            throw std::invalid_argument("invalid sequence");
+
+        decode(point, str.data() + i);
+        function(point);
+
+        i += size;
+    }
+}
+
+/**
+ * Convert a UTF-32 string to UTF-8 string.
+ *
+ * \param array the UTF-32 string
+ * \return the UTF-8 string
+ * \throw std::invalid_argument on invalid sequence
+ */
+std::string toUtf8(const std::u32string &array);
+
+/**
+ * Convert a UTF-8 string to UTF-32 string.
+ *
+ * \param str the UTF-8 string
+ * \return the UTF-32 string
+ * \throw std::invalid_argument on invalid sequence
+ */
+std::u32string toUtf32(const std::string &str);
+
+/**
+ * Check if the unicode character is space.
+ *
+ * \param c the character
+ * \return true if space
+ */
+bool isspace(char32_t c) noexcept;
+
+/**
+ * Check if the unicode character is digit.
+ *
+ * \param c the character
+ * \return true if digit
+ */
+bool isdigit(char32_t c) noexcept;
+
+/**
+ * Check if the unicode character is alpha category.
+ *
+ * \param c the character
+ * \return true if alpha
+ */
+bool isalpha(char32_t c) noexcept;
+
+/**
+ * Check if the unicode character is upper case.
+ *
+ * \param c the character
+ * \return true if upper case
+ */
+bool isupper(char32_t c) noexcept;
+
+/**
+ * Check if the unicode character is lower case.
+ *
+ * \param c the character
+ * \return true if lower case
+ */
+bool islower(char32_t c) noexcept;
+
+/**
+ * Check if the unicode character is title case.
+ *
+ * \param c the character
+ * \return true if title case
+ */
+bool istitle(char32_t c) noexcept;
+
+/**
+ * Convert to upper case.
+ *
+ * \param c the character
+ * \return the upper case character
+ */
+char32_t toupper(char32_t c) noexcept;
+
+/**
+ * Convert to lower case.
+ *
+ * \param c the character
+ * \return the lower case character
+ */
+char32_t tolower(char32_t c) noexcept;
+
+/**
+ * Convert to title case.
+ *
+ * \param c the character
+ * \return the title case character
+ */
+char32_t totitle(char32_t c) noexcept;
+
+/**
+ * Convert the UTF-32 string to upper case.
+ *
+ * \param str the str
+ * \return the upper case string
+ */
+inline std::u32string toupper(std::u32string str)
+{
+    for (size_t i = 0; i < str.size(); ++i)
+        str[i] = toupper(str[i]);
+
+    return str;
+}
+
+/**
+ * Convert the UTF-8 string to upper case.
+ *
+ * \param str the str
+ * \return the upper case string
+ * \warning very slow at the moment
+ */
+inline std::string toupper(const std::string &str)
+{
+    std::string result;
+    char buffer[5];
+
+    forEach(str, [&] (char32_t code) {
+        encode(toupper(code), buffer);
+        result += buffer;
+    });
+
+    return result;
+}
+
+/**
+ * Convert the UTF-32 string to lower case.
+ *
+ * \param str the str
+ * \return the lower case string
+ */
+inline std::u32string tolower(std::u32string str)
+{
+    for (size_t i = 0; i < str.size(); ++i)
+        str[i] = tolower(str[i]);
+
+    return str;
+}
+
+/**
+ * Convert the UTF-8 string to lower case.
+ *
+ * \param str the str
+ * \return the lower case string
+ * \warning very slow at the moment
+ */
+inline std::string tolower(const std::string &str)
+{
+    std::string result;
+    char buffer[5];
+
+    forEach(str, [&] (char32_t code) {
+        encode(tolower(code), buffer);
+        result += buffer;
+    });
+
+    return result;
+}
+
+} // !unicode
+
+} // !irccd
+
+#endif // !IRCCD_UNICODE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/unicode_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,151 @@
+/*
+ * unicode_jsapi.cpp -- Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "js_plugin.hpp"
+#include "unicode.hpp"
+#include "unicode_jsapi.hpp"
+
+namespace irccd {
+
+namespace {
+
+/*
+ * Function: Irccd.Unicode.isDigit(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is in the digit category.
+ */
+duk_ret_t is_digit(duk_context* ctx)
+{
+    duk_push_boolean(ctx, unicode::isdigit(duk_get_int(ctx, 0)));
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Unicode.isLetter(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is in the letter category.
+ */
+duk_ret_t is_letter(duk_context* ctx)
+{
+    duk_push_boolean(ctx, unicode::isalpha(duk_get_int(ctx, 0)));
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Unicode.isLower(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is lower case.
+ */
+duk_ret_t is_lower(duk_context* ctx)
+{
+    duk_push_boolean(ctx, unicode::islower(duk_get_int(ctx, 0)));
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Unicode.isSpace(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is in the space category.
+ */
+duk_ret_t is_space(duk_context* ctx)
+{
+    duk_push_boolean(ctx, unicode::isspace(duk_get_int(ctx, 0)));
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Unicode.isTitle(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is title case.
+ */
+duk_ret_t is_title(duk_context* ctx)
+{
+    duk_push_boolean(ctx, unicode::istitle(duk_get_int(ctx, 0)));
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Unicode.isUpper(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is upper case.
+ */
+duk_ret_t is_upper(duk_context* ctx)
+{
+    duk_push_boolean(ctx, unicode::isupper(duk_get_int(ctx, 0)));
+
+    return 1;
+}
+
+const duk_function_list_entry functions[] = {
+    { "isDigit",        is_digit,   1 },
+    { "isLetter",       is_letter,  1 },
+    { "isLower",        is_lower,   1 },
+    { "isSpace",        is_space,   1 },
+    { "isTitle",        is_title,   1 },
+    { "isUpper",        is_upper,   1 },
+    { nullptr,          nullptr,    0 }
+};
+
+} // !namespace
+
+std::string unicode_jsapi::name() const
+{
+    return "Irccd.Unicode";
+}
+
+void unicode_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, functions);
+    duk_put_prop_string(plugin->context(), -2, "Unicode");
+    duk_pop(plugin->context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/unicode_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,50 @@
+/*
+ * unicode_jsapi.hpp -- Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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::name
+     */
+    std::string 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_jsapi.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,300 @@
+/*
+ * util_jsapi.cpp -- Irccd.Util API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <climits>
+
+#include <libircclient.h>
+
+#include <irccd/string_util.hpp>
+
+#include "js_plugin.hpp"
+#include "util_jsapi.hpp"
+
+namespace irccd {
+
+namespace {
+
+/*
+ * 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 get_subst(duk_context* ctx, int index)
+{
+    string_util::subst params;
+
+    if (!duk_is_object(ctx, index))
+        return params;
+
+    dukx_enumerate(ctx, index, 0, true, [&] (auto) {
+        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)});
+    });
+
+    return params;
+}
+
+/*
+ * 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.
+            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 (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 (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;
+}
+
+/*
+ * 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.
+ */
+duk_ret_t cut(duk_context* ctx)
+{
+    auto list = lines(ctx, split(ctx), limit(ctx, 1, "maxc", 72));
+    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;
+    }
+
+    dukx_push_array(ctx, list, dukx_push_std_string);
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Util.format(text, parameters)
+ * --------------------------------------------------------
+ *
+ * Format a string with templates.
+ *
+ * Arguments:
+ *   - input, the text to update,
+ *   - params, the parameters.
+ * Returns:
+ *   The converted text.
+ */
+duk_ret_t format(duk_context* ctx)
+{
+    try {
+        dukx_push_std_string(ctx, string_util::format(dukx_get_std_string(ctx, 0), get_subst(ctx, 1)));
+    } catch (const std::exception &ex) {
+        dukx_throw(ctx, SyntaxError(ex.what()));
+    }
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Util.splituser(ident)
+ * --------------------------------------------------------
+ *
+ * Return the nickname part from a full username.
+ *
+ * Arguments:
+ *   - ident, the full identity.
+ * Returns:
+ *   The nickname.
+ */
+duk_ret_t splituser(duk_context* ctx)
+{
+    auto target = duk_require_string(ctx, 0);
+    char nick[32] = {0};
+
+    irc_target_get_nick(target, nick, sizeof (nick) -1);
+    duk_push_string(ctx, nick);
+
+    return 1;
+}
+
+/*
+ * Function: Irccd.Util.splithost(ident)
+ * --------------------------------------------------------
+ *
+ * Return the hostname part from a full username.
+ *
+ * Arguments:
+ *   - ident, the full identity.
+ * Returns:
+ *   The hostname.
+ */
+duk_ret_t splithost(duk_context* ctx)
+{
+    auto target = duk_require_string(ctx, 0);
+    char host[32] = {0};
+
+    irc_target_get_host(target, host, sizeof (host) -1);
+    duk_push_string(ctx, host);
+
+    return 1;
+}
+
+const duk_function_list_entry functions[] = {
+    { "cut",        cut,        DUK_VARARGS },
+    { "format",     format,     DUK_VARARGS },
+    { "splituser",  splituser,  1           },
+    { "splithost",  splithost,  1           },
+    { nullptr,      nullptr,    0           }
+};
+
+} // !namespace
+
+std::string util_jsapi::name() const
+{
+    return "Irccd.Util";
+}
+
+void util_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin)
+{
+    StackAssert sa(plugin->context());
+
+    duk_get_global_string(plugin->context(), "Irccd");
+    duk_push_object(plugin->context());
+    duk_put_function_list(plugin->context(), -1, functions);
+    duk_put_prop_string(plugin->context(), -2, "Util");
+    duk_pop(plugin->context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/util_jsapi.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -0,0 +1,50 @@
+/*
+ * util_jsapi.hpp -- Irccd.Util API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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::name
+     */
+    std::string 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-js/irccd/js_directory_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,329 +0,0 @@
-/*
- * js_directory_module.cpp -- irccd.Directory API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cerrno>
-#include <cstdio>
-#include <cstring>
-#include <fstream>
-#include <regex>
-#include <stdexcept>
-#include <string>
-
-#include "fs_util.hpp"
-#include "duktape.hpp"
-#include "js_directory_module.hpp"
-#include "js_irccd_module.hpp"
-#include "js_plugin.hpp"
-#include "sysconfig.hpp"
-
-namespace fs = boost::filesystem;
-
-namespace irccd {
-
-namespace {
-
-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");
-
-    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:
- *
- * - Directory.find
- * - Directory.prototype.find
- *
- * The patternIndex 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)
-{
-    try {
-        std::string path;
-
-        if (duk_is_string(ctx, pattern_index))
-            path = fs_util::find(base, dukx_get_std_string(ctx, pattern_index), recursive);
-        else {
-            // Check if it's a valid RegExp object.
-            duk_get_global_string(ctx, "RegExp");
-            auto is_regex = duk_instanceof(ctx, pattern_index, -1);
-            duk_pop(ctx);
-
-            if (is_regex) {
-                duk_get_prop_string(ctx, pattern_index, "source");
-                auto pattern = duk_to_string(ctx, -1);
-                duk_pop(ctx);
-
-                path = fs_util::find(base, std::regex(pattern), recursive);
-            } else
-                duk_error(ctx, DUK_ERR_TYPE_ERROR, "pattern must be a string or a regex expression");
-        }
-
-        if (path.empty())
-            return 0;
-
-        dukx_push_std_string(ctx, path);
-    } catch (const std::exception& ex) {
-        duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-    }
-
-    return 1;
-}
-
-/*
- * Generic remove function for:
- *
- * - Directory.remove
- * - Directory.prototype.remove
- */
-duk_ret_t remove(duk_context* ctx, const std::string& path, bool recursive)
-{
-    boost::system::error_code ec;
-
-    if (!boost::filesystem::is_directory(path, ec) || ec)
-        dukx_throw(ctx, system_error(EINVAL, "not a directory"));
-
-    if (!recursive)
-        boost::filesystem::remove(path, ec);
-    else
-        boost::filesystem::remove_all(path, ec);
-
-    return 0;
-}
-
-/*
- * Method: Directory.find(pattern, recursive)
- * --------------------------------------------------------
- *
- * Synonym of 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:
- *   - Any exception on error.
- */
-duk_ret_t method_find(duk_context* ctx)
-{
-    return find(ctx, path(ctx), duk_get_boolean(ctx, 1), 0);
-}
-
-/*
- * Method: Directory.remove(recursive)
- * --------------------------------------------------------
- *
- * Synonym of Directory.remove(recursive) but the path is taken from the
- * directory object.
- *
- * Arguments:
- *   - recursive, recursively or not (default: false).
- * Throws:
- *   - Any exception on error.
- */
-duk_ret_t method_remove(duk_context* ctx)
-{
-    return remove(ctx, path(ctx), duk_get_boolean(ctx, 0));
-}
-
-const duk_function_list_entry methods[] = {
-    { "find",       method_find,    DUK_VARARGS },
-    { "remove",     method_remove,  1           },
-    { nullptr,      nullptr,        0           }
-};
-
-/*
- * Directory "static" functions
- * ------------------------------------------------------------------
- */
-
-/*
- * Function: Irccd.Directory(path) [constructor]
- * --------------------------------------------------------
- *
- * Opens and read the directory at the specified path.
- *
- * Arguments:
- *   - path, the path to the directory,
- * Throws:
- *   - Any exception on error
- */
-duk_ret_t constructor(duk_context* ctx)
-{
-    if (!duk_is_constructor_call(ctx))
-        return 0;
-
-    try {
-        auto path = duk_require_string(ctx, 0);
-
-        if (!boost::filesystem::is_directory(path))
-            dukx_throw(ctx, system_error(EINVAL, "not a directory"));
-
-        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_std_string(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_string(ctx, "path");
-        dukx_push_std_string(ctx, path);
-        duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-    } catch (const std::exception& ex) {
-        dukx_throw(ctx, system_error(errno, ex.what()));
-    }
-
-    return 0;
-}
-
-/*
- * 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.
- */
-duk_ret_t func_find(duk_context* ctx)
-{
-    return find(ctx, duk_require_string(ctx, 0), duk_get_boolean(ctx, 2), 1);
-}
-
-/*
- * 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:
- *   - Any exception on error.
- */
-duk_ret_t func_remove(duk_context *ctx)
-{
-    return remove(ctx, duk_require_string(ctx, 0), duk_get_boolean(ctx, 1));
-}
-
-/*
- * 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:
- *   - Any exception on error.
- */
-duk_ret_t func_mkdir(duk_context *ctx)
-{
-    try {
-        boost::filesystem::create_directories(duk_require_string(ctx, 0));
-    } catch (const std::exception &ex) {
-        dukx_throw(ctx, system_error(errno, ex.what()));
-    }
-
-    return 0;
-}
-
-const duk_function_list_entry functions[] = {
-    { "find",           func_find,      DUK_VARARGS },
-    { "mkdir",          func_mkdir,     DUK_VARARGS },
-    { "remove",         func_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
-
-js_directory_module::js_directory_module() noexcept
-    : module("Irccd.Directory")
-{
-}
-
-void js_directory_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 2);
-    duk_put_number_list(plugin->context(), -1, constants);
-    duk_put_function_list(plugin->context(), -1, functions);
-
-#if defined(IRCCD_SYSTEM_WINDOWS)
-    duk_push_string(plugin->context(), "\\");
-#else
-    duk_push_string(plugin->context(), "/");
-#endif
-
-    duk_put_prop_string(plugin->context(), -2, "separator");
-
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, methods);
-    duk_put_prop_string(plugin->context(), -2, "prototype");
-    duk_put_prop_string(plugin->context(), -2, "Directory");
-    duk_pop(plugin->context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_directory_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * js_directory_module.hpp -- irccd.Directory API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_DIRECTORY_MODULE_HPP
-#define IRCCD_JS_DIRECTORY_MODULE_HPP
-
-/**
- * \file mod-directory.hpp
- * \brief irccd.Directory JavaScript API.
- */
-
-#include "module.hpp"
-
-namespace irccd {
-
-/**
- * \brief irccd.Directory JavaScript API.
- * \ingroup modules
- */
-class js_directory_module : public module {
-public:
-    /**
-     * Constructor.
-     */
-    js_directory_module() noexcept;
-
-    /**
-     * \copydoc Module::load
-     */
-    void load(irccd &irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_DIRECTORY_MODULE_HPP
--- a/libirccd-js/irccd/js_elapsed_timer_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/*
- * js_elapsed_timer_module.cpp -- Irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <boost/timer/timer.hpp>
-
-#include <irccd/js_plugin.hpp>
-
-#include "js_elapsed_timer_module.hpp"
-
-namespace irccd {
-
-namespace {
-
-const char* signature("\xff""\xff""irccd-elapsed-timer-ptr");
-
-boost::timer::cpu_timer* self(duk_context* ctx)
-{
-    StackAssert sa(ctx);
-
-    duk_push_this(ctx);
-    duk_get_prop_string(ctx, -1, signature);
-    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;
-}
-
-/*
- * Method: ElapsedTimer.pause
- * ------------------------------------------------------------------
- *
- * Pause the timer, without resetting the current elapsed time stored.
- */
-duk_ret_t pause(duk_context* ctx)
-{
-    self(ctx)->stop();
-
-    return 0;
-}
-
-/*
- * Method: ElapsedTimer.restart
- * ------------------------------------------------------------------
- *
- * Restart the timer without resetting the current elapsed time.
- */
-duk_ret_t restart(duk_context* ctx)
-{
-    self(ctx)->resume();
-
-    return 0;
-}
-
-/*
- * Method: ElapsedTimer.elapsed
- * ------------------------------------------------------------------
- *
- * Get the number of elapsed milliseconds.
- *
- * Returns:
- *   The time elapsed.
- */
-duk_ret_t elapsed(duk_context* ctx)
-{
-    duk_push_uint(ctx, self(ctx)->elapsed().wall / 1000000LL);
-
-    return 1;
-}
-
-/*
- * Function: Irccd.ElapsedTimer [constructor]
- * ------------------------------------------------------------------
- *
- * Construct a new ElapsedTimer object.
- */
-duk_ret_t 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;
-}
-
-/*
- * Function: Irccd.ElapsedTimer [destructor]
- * ------------------------------------------------------------------
- *
- * Delete the property.
- */
-duk_ret_t 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",    elapsed,    0 },
-    { "pause",      pause,      0 },
-    { "restart",    restart,    0 },
-    { nullptr,      nullptr,    0 }
-};
-
-} // !namespace
-
-js_elapsed_timer_module::js_elapsed_timer_module() noexcept
-    : module("Irccd.ElapsedTimer")
-{
-}
-
-void js_elapsed_timer_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 0);
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, methods);
-    duk_push_c_function(plugin->context(), destructor, 1);
-    duk_set_finalizer(plugin->context(), -2);
-    duk_put_prop_string(plugin->context(), -2, "prototype");
-    duk_put_prop_string(plugin->context(), -2, "ElapsedTimer");
-    duk_pop(plugin->context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_elapsed_timer_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * js_elapsed_timer_module.hpp -- irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_ELAPSED_TIMER_MODULE_HPP
-#define IRCCD_JS_ELAPSED_TIMER_MODULE_HPP
-
-/**
- * \file js_elapsed_timer_module.hpp
- * \brief irccd.ElapsedTimer JavaScript API.
- */
-
-#include "module.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.ElapsedTimer JavaScript API.
- * \ingroup Javascript modules
- */
-class js_elapsed_timer_module : public module {
-public:
-    /**
-     * Constructor.
-     */
-    js_elapsed_timer_module() noexcept;
-
-    /**
-     * \copydoc module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_ELAPSED_TIMER_MODULE_HPP
--- a/libirccd-js/irccd/js_file_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,700 +0,0 @@
-/*
- * js_file_module.cpp -- Irccd.File API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <algorithm>
-#include <array>
-#include <cassert>
-#include <iterator>
-#include <vector>
-
-#include <boost/filesystem.hpp>
-
-#include <irccd/sysconfig.hpp>
-
-#if defined(HAVE_STAT)
-#  include <sys/types.h>
-#  include <sys/stat.h>
-#endif
-
-#include <irccd/js_plugin.hpp>
-
-#include "fs_util.hpp"
-#include "js_file_module.hpp"
-#include "js_irccd_module.hpp"
-
-namespace irccd {
-
-namespace {
-
-const char *signature("\xff""\xff""irccd-file-ptr");
-const char *prototype("\xff""\xff""irccd-file-prototype");
-
-#if defined(HAVE_STAT)
-
-/*
- * push_stat
- * ------------------------------------------------------------------
- */
-
-void push_stat(duk_context* ctx, const struct stat& st)
-{
-    StackAssert 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
-
-// Remove trailing \r for CRLF line style.
-inline std::string clear_crlf(std::string input)
-{
-    if (input.length() > 0 && input.back() == '\r')
-        input.pop_back();
-
-    return input;
-}
-
-file* self(duk_context* ctx)
-{
-    StackAssert sa(ctx);
-
-    duk_push_this(ctx);
-    duk_get_prop_string(ctx, -1, signature);
-    auto ptr = static_cast<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;
-}
-
-/*
- * File methods.
- * ------------------------------------------------------------------
- */
-
-/*
- * Method: File.basename()
- * --------------------------------------------------------
- *
- * Synonym of `irccd.File.basename(path)` but with the path from the file.
- *
- * duk_ret_turns:
- *   The base name.
- */
-duk_ret_t method_basename(duk_context* ctx)
-{
-    dukx_push_std_string(ctx, fs_util::base_name(self(ctx)->path()));
-
-    return 1;
-}
-
-/*
- * Method: File.close()
- * --------------------------------------------------------
- *
- * Force close of the file, automatically called when object is collected.
- */
-duk_ret_t method_close(duk_context* ctx)
-{
-    self(ctx)->close();
-
-    return 0;
-}
-
-/*
- * Method: File.dirname()
- * --------------------------------------------------------
- *
- * Synonym of `irccd.File.dirname(path)` but with the path from the file.
- *
- * duk_ret_turns:
- *   The directory name.
- */
-duk_ret_t method_dirname(duk_context* ctx)
-{
-    dukx_push_std_string(ctx, fs_util::dir_name(self(ctx)->path()));
-
-    return 1;
-}
-
-/*
- * Method: File.lines()
- * --------------------------------------------------------
- *
- * Read all lines and return an array.
- *
- * duk_ret_turns:
- *   An array with all lines.
- * Throws
- *   - Any exception on error.
- */
-duk_ret_t method_lines(duk_context* ctx)
-{
-    duk_push_array(ctx);
-
-    std::FILE* fp = self(ctx)->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();
-
-        auto pos = buffer.find('\n');
-
-        if (pos != std::string::npos) {
-            dukx_push_std_string(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))
-        dukx_throw(ctx, system_error());
-
-    // Missing '\n' in end of file.
-    if (!buffer.empty()) {
-        dukx_push_std_string(ctx, clear_crlf(buffer));
-        duk_put_prop_index(ctx, -2, i++);
-    }
-
-    return 1;
-}
-
-/*
- * Method: File.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).
- * duk_ret_turns:
- *   The string.
- * Throws:
- *   - Any exception on error.
- */
-duk_ret_t method_read(duk_context* ctx)
-{
-    auto file = self(ctx);
-    auto amount = duk_is_number(ctx, 0) ? duk_get_int(ctx, 0) : -1;
-
-    if (amount == 0 || file->handle() == nullptr)
-        return 0;
-
-    try {
-        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(), file->handle())) > 0) {
-                if (std::ferror(file->handle()))
-                    dukx_throw(ctx, system_error());
-
-                std::copy(buffer.begin(), buffer.begin() + nread, std::back_inserter(data));
-                total += nread;
-            }
-        } else {
-            data.resize((std::size_t)amount);
-            total = std::fread(&data[0], sizeof (data[0]), (std::size_t)amount, file->handle());
-
-            if (std::ferror(file->handle()))
-                dukx_throw(ctx, system_error());
-
-            data.resize(total);
-        }
-
-        dukx_push_std_string(ctx, data);
-    } catch (const std::exception&) {
-        dukx_throw(ctx, system_error());
-    }
-
-    return 1;
-}
-
-/*
- * Method: File.readline()
- * --------------------------------------------------------
- *
- * Read the next line available.
- *
- * duk_ret_turns:
- *   The next line or undefined if eof.
- * Throws:
- *   - Any exception on error.
- */
-duk_ret_t method_readline(duk_context* ctx)
-{
-    std::FILE* fp = self(ctx)->handle();
-    std::string result;
-
-    if (fp == nullptr || std::feof(fp))
-        return 0;
-    for (int ch; (ch = std::fgetc(fp)) != EOF && ch != '\n'; )
-        result += (char)ch;
-    if (std::ferror(fp))
-        dukx_throw(ctx, system_error());
-
-    dukx_push_std_string(ctx, clear_crlf(result));
-
-    return 1;
-}
-
-/*
- * Method: File.remove()
- * --------------------------------------------------------
- *
- * Synonym of File.remove(path) but with the path from the file.
- *
- * Throws:
- *   - Any exception on error.
- */
-duk_ret_t method_remove(duk_context* ctx)
-{
-    if (::remove(self(ctx)->path().c_str()) < 0)
-        dukx_throw(ctx, system_error());
-
-    return 0;
-}
-
-/*
- * Method: File.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:
- *   - Any exception on error.
- */
-duk_ret_t method_seek(duk_context* ctx)
-{
-    auto fp = self(ctx)->handle();
-    auto type = duk_require_int(ctx, 0);
-    auto amount = duk_require_int(ctx, 1);
-
-    if (fp != nullptr && std::fseek(fp, amount, type) != 0)
-        dukx_throw(ctx, system_error());
-
-    return 0;
-}
-
-#if defined(HAVE_STAT)
-
-/*
- * Method: File.stat() [optional]
- * --------------------------------------------------------
- *
- * Synonym of File.stat(path) but with the path from the file.
- *
- * duk_ret_turns:
- *   The stat information.
- * Throws:
- *   - Any exception on error.
- */
-duk_ret_t method_stat(duk_context* ctx)
-{
-    auto file = self(ctx);
-    struct stat st;
-
-    if (file->handle() == nullptr && ::stat(file->path().c_str(), &st) < 0)
-        dukx_throw(ctx, system_error());
-    else
-        push_stat(ctx, st);
-
-    return 1;
-}
-
-#endif // !HAVE_STAT
-
-/*
- * Method: File.tell()
- * --------------------------------------------------------
- *
- * Get the actual position in the file.
- *
- * duk_ret_turns:
- *   The position.
- * Throws:
- *   - Any exception on error.
- */
-duk_ret_t method_tell(duk_context* ctx)
-{
-    auto fp = self(ctx)->handle();
-    long pos;
-
-    if (fp == nullptr)
-        return 0;
-
-    if ((pos = std::ftell(fp)) == -1L)
-        dukx_throw(ctx, system_error());
-    else
-        duk_push_int(ctx, pos);
-
-    return 1;
-}
-
-/*
- * Method: File.write(data)
- * --------------------------------------------------------
- *
- * Write some characters to the file.
- *
- * Arguments:
- *   - data, the character to write.
- * duk_ret_turns:
- *   The number of bytes written.
- * Throws:
- *   - Any exception on error.
- */
-duk_ret_t method_write(duk_context* ctx)
-{
-    std::FILE* fp = self(ctx)->handle();
-    std::string data = duk_require_string(ctx, 0);
-
-    if (fp == nullptr)
-        return 0;
-
-    auto nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
-
-    if (std::ferror(fp))
-        dukx_throw(ctx, system_error());
-
-    duk_push_uint(ctx, nwritten);
-
-    return 1;
-}
-
-const duk_function_list_entry methods[] = {
-    { "basename",   method_basename,    0 },
-    { "close",      method_close,       0 },
-    { "dirname",    method_dirname,     0 },
-    { "lines",      method_lines,       0 },
-    { "read",       method_read,        1 },
-    { "readline",   method_readline,    0 },
-    { "remove",     method_remove,      0 },
-    { "seek",       method_seek,        2 },
-#if defined(HAVE_STAT)
-    { "stat",       method_stat,        0 },
-#endif
-    { "tell",       method_tell,        0 },
-    { "write",      method_write,       1 },
-    { nullptr,      nullptr,            0 }
-};
-
-/*
- * File "static" functions
- * ------------------------------------------------------------------
- */
-
-/*
- * 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:
- *   - Any exception on error.
- */
-duk_ret_t constructor(duk_context* ctx)
-{
-    if (!duk_is_constructor_call(ctx))
-        return 0;
-
-    try {
-        dukx_new_file(ctx, new file(duk_require_string(ctx, 0), duk_require_string(ctx, 1)));
-    } catch (const std::exception &) {
-        dukx_throw(ctx, system_error());
-    }
-
-    return 0;
-}
-
-/*
- * Function: Irccd.File() [destructor]
- * ------------------------------------------------------------------
- *
- * Delete the property.
- */
-duk_ret_t destructor(duk_context* ctx)
-{
-    duk_get_prop_string(ctx, 0, signature);
-    delete static_cast<file*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-    duk_del_prop_string(ctx, 0, signature);
-
-    return 0;
-}
-
-/*
- * 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.
- * duk_ret_turns:
- *   The base name.
- */
-duk_ret_t function_basename(duk_context* ctx)
-{
-    dukx_push_std_string(ctx, fs_util::base_name(duk_require_string(ctx, 0)));
-
-    return 1;
-}
-
-/*
- * 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.
- * duk_ret_turns:
- *   The directory name.
- */
-duk_ret_t function_dirname(duk_context* ctx)
-{
-    dukx_push_std_string(ctx, fs_util::dir_name(duk_require_string(ctx, 0)));
-
-    return 1;
-}
-
-/*
- * Function: Irccd.File.exists(path)
- * --------------------------------------------------------
- *
- * Check if the file exists.
- *
- * Arguments:
- *   - path, the path to the file.
- * duk_ret_turns:
- *   True if exists.
- * Throws:
- *   - Any exception if we don't have access.
- */
-duk_ret_t function_exists(duk_context* ctx)
-{
-    try {
-        duk_push_boolean(ctx, boost::filesystem::exists(duk_require_string(ctx, 0)));
-    } catch (...) {
-        duk_push_boolean(ctx, false);
-    }
-
-    return 1;
-}
-
-/*
- * function Irccd.File.remove(path)
- * --------------------------------------------------------
- *
- * Remove the file at the specified path.
- *
- * Arguments:
- *   - path, the path to the file.
- * Throws:
- *   - Any exception on error.
- */
-duk_ret_t function_remove(duk_context* ctx)
-{
-    if (::remove(duk_require_string(ctx, 0)) < 0)
-        dukx_throw(ctx, system_error());
-
-    return 0;
-}
-
-#if defined(HAVE_STAT)
-
-/*
- * function Irccd.File.stat(path) [optional]
- * --------------------------------------------------------
- *
- * Get file information at the specified path.
- *
- * Arguments:
- *   - path, the path to the file.
- * duk_ret_turns:
- *   The stat information.
- * Throws:
- *   - Any exception on error.
- */
-duk_ret_t function_stat(duk_context* ctx)
-{
-    struct stat st;
-
-    if (::stat(duk_require_string(ctx, 0), &st) < 0)
-        dukx_throw(ctx, system_error());
-
-    push_stat(ctx, st);
-
-    return 1;
-}
-
-#endif // !HAVE_STAT
-
-const duk_function_list_entry functions[] = {
-    { "basename",   function_basename,  1 },
-    { "dirname",    function_dirname,   1 },
-    { "exists",     function_exists,    1 },
-    { "remove",     function_remove,    1 },
-#if defined(HAVE_STAT)
-    { "stat",       function_stat,      1 },
-#endif
-    { nullptr,      nullptr,            0 }
-};
-
-const duk_number_list_entry constants[] = {
-    { "SeekCur",    SEEK_CUR },
-    { "SeekEnd",    SEEK_END },
-    { "SeekSet",    SEEK_SET },
-    { nullptr,      0        }
-};
-
-} // !namespace
-
-js_file_module::js_file_module() noexcept
-    : module("Irccd.File")
-{
-}
-
-void js_file_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 2);
-    duk_put_number_list(plugin->context(), -1, constants);
-    duk_put_function_list(plugin->context(), -1, functions);
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, methods);
-    duk_push_c_function(plugin->context(), destructor, 1);
-    duk_set_finalizer(plugin->context(), -2);
-    duk_dup(plugin->context(), -1);
-    duk_put_global_string(plugin->context(), prototype);
-    duk_put_prop_string(plugin->context(), -2, "prototype");
-    duk_put_prop_string(plugin->context(), -2, "File");
-    duk_pop(plugin->context());
-}
-
-void dukx_new_file(duk_context* ctx, file* fp)
-{
-    assert(ctx);
-    assert(fp);
-
-    StackAssert sa(ctx);
-
-    duk_push_this(ctx);
-    duk_push_pointer(ctx, fp);
-    duk_put_prop_string(ctx, -2, signature);
-    duk_pop(ctx);
-}
-
-void dukx_push_file(duk_context* ctx, file* fp)
-{
-    assert(ctx);
-    assert(fp);
-
-    StackAssert sa(ctx, 1);
-
-    duk_push_object(ctx);
-    duk_push_pointer(ctx, fp);
-    duk_put_prop_string(ctx, -2, signature);
-    duk_get_global_string(ctx, prototype);
-    duk_set_prototype(ctx, -2);
-}
-
-file* dukx_require_file(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);
-    auto fp = static_cast<file*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-
-    return fp;
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_file_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,184 +0,0 @@
-/*
- * js_file_module.hpp -- Irccd.File API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_FILE_MODULE_HPP
-#define IRCCD_JS_FILE_MODULE_HPP
-
-/**
- * \file js_file_module.hpp
- * \brief Irccd.File JavaScript API.
- */
-
-#include <cassert>
-#include <cerrno>
-#include <cstdio>
-#include <cstring>
-#include <functional>
-#include <stdexcept>
-#include <string>
-
-#include "duktape.hpp"
-#include "module.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& path() const noexcept
-    {
-        return path_;
-    }
-
-    /**
-     * Get the handle.
-     *
-     * \return the handle or nullptr if the stream was closed
-     */
-    inline std::FILE* 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 modules
- */
-class js_file_module : public module {
-public:
-    /**
-     * Constructor.
-     */
-    js_file_module() noexcept;
-
-    /**
-     * \copydoc Module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-/**
- * Construct the file as this.
- *
- * The object prototype takes ownership of fp and will be deleted once
- * collected.
- *
- * \pre fp != nullptr
- * \param ctx the the context
- * \param fp the file
- */
-IRCCD_EXPORT void dukx_new_file(duk_context* ctx, file* fp);
-
-/**
- * Push a file.
- *
- * \pre fp != nullptr
- * \param ctx the the context
- * \param fp the file
- */
-IRCCD_EXPORT void dukx_push_file(duk_context* ctx, file* fp);
-
-/**
- * Require a file. Raises a JavaScript error if not a File.
- *
- * \param ctx the context
- * \param index the index
- */
-IRCCD_EXPORT file* dukx_require_file(duk_context* ctx, duk_idx_t index);
-
-} // !irccd
-
-#endif // !IRCCD_JS_FILE_MODULE_HPP
--- a/libirccd-js/irccd/js_irccd_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-/*
- * js_irccd_module.cpp -- Irccd API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cerrno>
-#include <string>
-#include <unordered_map>
-
-#include <irccd/js_plugin.hpp>
-#include <irccd/sysconfig.hpp>
-
-#include "js_irccd_module.hpp"
-
-namespace irccd {
-
-namespace {
-
-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
-
-system_error::system_error()
-    : errno_(errno)
-    , message_(std::strerror(errno_))
-{
-}
-
-system_error::system_error(int e, std::string message)
-    : errno_(e)
-    , message_(std::move(message))
-{
-}
-
-void system_error::raise(duk_context *ctx) const
-{
-    StackAssert sa(ctx, 0);
-
-    duk_get_global_string(ctx, "Irccd");
-    duk_get_prop_string(ctx, -1, "SystemError");
-    duk_remove(ctx, -2);
-    duk_push_int(ctx, errno_);
-    dukx_push_std_string(ctx, message_);
-    duk_new(ctx, 2);
-    duk_throw(ctx);
-}
-
-js_irccd_module::js_irccd_module() noexcept
-    : module("Irccd")
-{
-}
-
-void js_irccd_module::load(irccd& irccd, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    // irccd.
-    duk_push_object(plugin->context());
-
-    // Version.
-    duk_push_object(plugin->context());
-    duk_push_int(plugin->context(), IRCCD_VERSION_MAJOR);
-    duk_put_prop_string(plugin->context(), -2, "major");
-    duk_push_int(plugin->context(), IRCCD_VERSION_MINOR);
-    duk_put_prop_string(plugin->context(), -2, "minor");
-    duk_push_int(plugin->context(), IRCCD_VERSION_PATCH);
-    duk_put_prop_string(plugin->context(), -2, "patch");
-    duk_put_prop_string(plugin->context(), -2, "version");
-
-    // Create the system_error that inherits from Error.
-    duk_push_c_function(plugin->context(), constructor, 2);
-
-    // Put errno codes into the irccd.system_error object.
-    for (const auto& pair : errors) {
-        duk_push_int(plugin->context(), pair.second);
-        duk_put_prop_string(plugin->context(), -2, pair.first.c_str());
-    }
-
-    duk_push_object(plugin->context());
-    duk_get_global_string(plugin->context(), "Error");
-    duk_get_prop_string(plugin->context(), -1, "prototype");
-    duk_remove(plugin->context(), -2);
-    duk_set_prototype(plugin->context(), -2);
-    duk_put_prop_string(plugin->context(), -2, "prototype");
-    duk_put_prop_string(plugin->context(), -2, "SystemError");
-
-    // Set irccd as global.
-    duk_put_global_string(plugin->context(), "Irccd");
-
-    // Store global instance.
-    duk_push_pointer(plugin->context(), &irccd);
-    duk_put_global_string(plugin->context(), "\xff""\xff""irccd-ref");
-}
-
-irccd& dukx_get_irccd(duk_context *ctx)
-{
-    StackAssert sa(ctx);
-
-    duk_get_global_string(ctx, "\xff""\xff""irccd-ref");
-    auto ptr = static_cast<irccd*>(duk_to_pointer(ctx, -1));
-    duk_pop(ctx);
-
-    return *ptr;
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_irccd_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-/*
- * js_irccd_module.hpp -- Irccd API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_IRCCD_MODULE_HPP
-#define IRCCD_JS_IRCCD_MODULE_HPP
-
-/**
- * \file js_irccd_module.hpp
- * \brief irccd JavaScript API.
- */
-
-#include <cerrno>
-#include <cstring>
-#include <string>
-
-#include "duktape.hpp"
-#include "module.hpp"
-
-namespace irccd {
-
-/**
- * \brief Custom JavaScript exception for system error.
- */
-class system_error {
-private:
-    int errno_;
-    std::string message_;
-
-public:
-    /**
-     * Create a system error from the current errno value.
-     */
-    system_error();
-
-    /**
-     * Create a system error with the given errno and message.
-     *
-     * \param e the errno number
-     * \param message the message
-     */
-    system_error(int e, std::string message);
-
-    /**
-     * Raise the SystemError Javascript exception.
-     *
-     * \param ctx the context
-     */
-    void raise(duk_context* ctx) const;
-};
-
-/**
- * \brief Irccd JavaScript API.
- * \ingroup modules
- */
-class js_irccd_module : public module {
-public:
-    /**
-     * Constructor.
-     */
-    js_irccd_module() noexcept;
-
-    /**
-     * \copydoc module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-/**
- * Get irccd instance stored in this context.
- *
- * \param ctx the context
- * \return the irccd reference
- */
-irccd& dukx_get_irccd(duk_context* ctx);
-
-} // !irccd
-
-#endif // !IRCCD_JS_IRCCD_MODULE_HPP
--- a/libirccd-js/irccd/js_logger_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-/*
- * js_logger_module.cpp -- Irccd.Logger API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/logger.hpp>
-
-#include "js_plugin.hpp"
-#include "js_logger_module.hpp"
-#include "js_plugin_module.hpp"
-
-namespace irccd {
-
-namespace {
-
-duk_ret_t print(duk_context* ctx, std::ostream &out)
-{
-    out << "plugin " << dukx_get_plugin(ctx)->name() << ": " << duk_require_string(ctx, 0) << std::endl;
-
-    return 0;
-}
-
-/*
- * Function: Irccd.Logger.info(message)
- * --------------------------------------------------------
- *
- * Write a verbose message.
- *
- * Arguments:
- *   - message, the message.
- */
-duk_ret_t info(duk_context* ctx)
-{
-    return print(ctx, log::info());
-}
-
-/*
- * Function: irccd.Logger.warning(message)
- * --------------------------------------------------------
- *
- * Write a warning message.
- *
- * Arguments:
- *   - message, the warning.
- */
-duk_ret_t warning(duk_context* ctx)
-{
-    return print(ctx, log::warning());
-}
-
-/*
- * Function: Logger.debug(message)
- * --------------------------------------------------------
- *
- * Write a debug message, only shown if irccd is compiled in debug.
- *
- * Arguments:
- *   - message, the message.
- */
-duk_ret_t debug(duk_context* ctx)
-{
-    return print(ctx, log::debug());
-}
-
-const duk_function_list_entry functions[] = {
-    { "info",       info,       1 },
-    { "warning",    warning,    1 },
-    { "debug",      debug,      1 },
-    { nullptr,      nullptr,    0 }
-};
-
-} // !namespace
-
-js_logger_module::js_logger_module() noexcept
-    : module("Irccd.Logger")
-{
-}
-
-void js_logger_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, functions);
-    duk_put_prop_string(plugin->context(), -2, "Logger");
-    duk_pop(plugin->context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_logger_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * js_logger_module.hpp -- Irccd.Logger API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_LOGGER_MODULE_HPP
-#define IRCCD_JS_LOGGER_MODULE_HPP
-
-/**
- * \file js_logger_module.hpp
- * \brief Irccd.Logger JavaScript API.
- */
-
-#include "module.hpp"
-
-namespace irccd {
-
-/**
- * \brief irccd.Logger JavaScript API.
- * \ingroup modules
- */
-class js_logger_module : public module {
-public:
-    /**
-     * Constructor.
-     */
-    js_logger_module() noexcept;
-
-    /**
-     * \copydoc Module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_LOGGER_MODULE_HPP
--- a/libirccd-js/irccd/js_plugin.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,403 +0,0 @@
-/*
- * plugin-js.cpp -- JavaScript plugins for irccd
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cstring>
-#include <cerrno>
-#include <fstream>
-#include <iterator>
-#include <stdexcept>
-
-#include "irccd.hpp"
-#include "logger.hpp"
-#include "js_plugin_module.hpp"
-#include "js_server_module.hpp"
-#include "js_plugin.hpp"
-#include "service.hpp"
-#include "timer.hpp"
-
-namespace irccd {
-
-const std::string js_plugin::config_property{"\xff""\xff""irccd-plugin-config"};
-const std::string js_plugin::format_property{"\xff""\xff""irccd-plugin-format"};
-const std::string js_plugin::paths_property{"\xff""\xff""irccd-plugin-paths"};
-
-std::unordered_map<std::string, std::string> js_plugin::get_table(const std::string& name) const
-{
-    StackAssert sa(context_);
-    std::unordered_map<std::string, std::string> result;
-
-    duk_get_global_string(context_, name.c_str());
-    dukx_enumerate(context_, -1, 0, true, [&] (auto ctx) {
-        result.emplace(duk_to_string(ctx, -2), duk_to_string(ctx, -1));
-    });
-    duk_pop(context_);
-
-    return result;
-}
-
-void js_plugin::put_table(const std::string& name, const std::unordered_map<std::string, std::string>& vars)
-{
-    StackAssert sa(context_);
-
-    duk_get_global_string(context_, name.c_str());
-
-    for (const auto &pair : vars) {
-        dukx_push_std_string(context_, pair.second);
-        duk_put_prop_string(context_, -2, pair.first.c_str());
-    }
-
-    duk_pop(context_);
-}
-
-void js_plugin::call(const std::string& name, unsigned nargs)
-{
-    duk_get_global_string(context_, name.c_str());
-
-    if (duk_get_type(context_, -1) == DUK_TYPE_UNDEFINED)
-        // Function not defined, remove the undefined value and all arguments.
-        duk_pop_n(context_, nargs + 1);
-    else {
-        // Call the function and discard the result.
-        duk_insert(context_, -nargs - 1);
-
-        if (duk_pcall(context_, nargs) != 0)
-            throw dukx_exception(context_, -1, true);
-
-        duk_pop(context_);
-    }
-}
-
-js_plugin::js_plugin(std::string name, std::string path)
-    : plugin(name, path)
-{
-    StackAssert sa(context_);
-
-    /*
-     * Create two special tables for configuration and formats, they are
-     * referenced later as
-     *
-     *   - Irccd.Plugin.config
-     *   - Irccd.Plugin.format
-     *   - Irccd.Plugin.paths
-     *
-     * In js_plugin_module.cpp.
-     */
-    duk_push_object(context_);
-    duk_put_global_string(context_, config_property.c_str());
-    duk_push_object(context_);
-    duk_put_global_string(context_, format_property.c_str());
-    duk_push_object(context_);
-    duk_put_global_string(context_, paths_property.c_str());
-
-    duk_push_pointer(context_, this);
-    duk_put_global_string(context_, "\xff""\xff""plugin");
-    dukx_push_std_string(context_, name);
-    duk_put_global_string(context_, "\xff""\xff""name");
-    dukx_push_std_string(context_, path);
-    duk_put_global_string(context_, "\xff""\xff""path");
-}
-
-void js_plugin::open()
-{
-    std::ifstream input(path());
-
-    if (!input)
-        throw std::runtime_error(std::strerror(errno));
-
-    std::string data(
-        std::istreambuf_iterator<char>(input.rdbuf()),
-        std::istreambuf_iterator<char>()
-    );
-
-    if (duk_peval_string(context_, data.c_str()))
-        throw dukx_exception(context_, -1);
-
-    // Read metadata.
-    duk_get_global_string(context_, "info");
-
-    if (duk_get_type(context_, -1) == DUK_TYPE_OBJECT) {
-        duk_get_prop_string(context_, -1, "author");
-        set_author(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : author());
-        duk_get_prop_string(context_, -2, "license");
-        set_license(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : license());
-        duk_get_prop_string(context_, -3, "summary");
-        set_summary(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : summary());
-        duk_get_prop_string(context_, -4, "version");
-        set_version(duk_is_string(context_, -1) ? duk_get_string(context_, -1) : version());
-        duk_pop_n(context_, 4);
-    }
-
-    duk_pop(context_);
-}
-
-void js_plugin::on_channel_mode(irccd& , const channel_mode_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    dukx_push_std_string(context_, event.mode);
-    dukx_push_std_string(context_, event.argument);
-    call("onChannelMode", 5);
-}
-
-void js_plugin::on_channel_notice(irccd& , const channel_notice_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    dukx_push_std_string(context_, event.message);
-    call("onChannelNotice", 4);
-}
-
-void js_plugin::on_command(irccd& , const message_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    dukx_push_std_string(context_, event.message);
-    call("onCommand", 4);
-}
-
-void js_plugin::on_connect(irccd& , const connect_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    call("onConnect", 1);
-}
-
-void js_plugin::on_invite(irccd& , const invite_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    call("onInvite", 3);
-}
-
-void js_plugin::on_join(irccd& , const join_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    call("onJoin", 3);
-}
-
-void js_plugin::on_kick(irccd& , const kick_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    dukx_push_std_string(context_, event.target);
-    dukx_push_std_string(context_, event.reason);
-    call("onKick", 5);
-}
-
-void js_plugin::on_load(irccd&)
-{
-    StackAssert sa(context_);
-
-    call("onLoad", 0);
-}
-
-void js_plugin::on_message(irccd& , const message_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    dukx_push_std_string(context_, event.message);
-    call("onMessage", 4);
-}
-
-void js_plugin::on_me(irccd& , const me_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    dukx_push_std_string(context_, event.message);
-    call("onMe", 4);
-}
-
-void js_plugin::on_mode(irccd& , const mode_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.mode);
-    call("onMode", 3);
-}
-
-void js_plugin::on_names(irccd& , const names_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.channel);
-    dukx_push_array(context_, event.names, dukx_push_std_string);
-    call("onNames", 3);
-}
-
-void js_plugin::on_nick(irccd& , const nick_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.nickname);
-    call("onNick", 3);
-}
-
-void js_plugin::on_notice(irccd& , const notice_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.message);
-    call("onNotice", 3);
-}
-
-void js_plugin::on_part(irccd& , const part_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    dukx_push_std_string(context_, event.reason);
-    call("onPart", 4);
-}
-
-void js_plugin::on_query(irccd& , const query_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.message);
-    call("onQuery", 3);
-}
-
-void js_plugin::on_query_command(irccd& , const query_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.message);
-    call("onQueryCommand", 3);
-}
-
-void js_plugin::on_reload(irccd& )
-{
-    StackAssert sa(context_);
-
-    call("onReload");
-}
-
-void js_plugin::on_topic(irccd& , const topic_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    dukx_push_std_string(context_, event.origin);
-    dukx_push_std_string(context_, event.channel);
-    dukx_push_std_string(context_, event.topic);
-    call("onTopic", 4);
-}
-
-void js_plugin::on_unload(irccd& )
-{
-    StackAssert sa(context_);
-
-    call("onUnload");
-}
-
-void js_plugin::on_whois(irccd& , const whois_event &event)
-{
-    StackAssert sa(context_);
-
-    dukx_push_server(context_, std::move(event.server));
-    duk_push_object(context_);
-    dukx_push_std_string(context_, event.whois.nick);
-    duk_put_prop_string(context_, -2, "nickname");
-    dukx_push_std_string(context_, event.whois.user);
-    duk_put_prop_string(context_, -2, "username");
-    dukx_push_std_string(context_, event.whois.realname);
-    duk_put_prop_string(context_, -2, "realname");
-    dukx_push_std_string(context_, event.whois.host);
-    duk_put_prop_string(context_, -2, "host");
-    dukx_push_array(context_, event.whois.channels, dukx_push_std_string);
-    duk_put_prop_string(context_, -2, "channels");
-    call("onWhois", 2);
-}
-
-js_plugin_loader::js_plugin_loader(irccd& irccd) noexcept
-    : plugin_loader({}, { ".js" })
-    , irccd_(irccd)
-{
-}
-
-js_plugin_loader::~js_plugin_loader() noexcept = default;
-
-void js_plugin_loader::add_module(std::unique_ptr<module> module)
-{
-    assert(module);
-
-    modules_.push_back(std::move(module));
-}
-
-std::shared_ptr<plugin> js_plugin_loader::open(const std::string& id,
-                                               const std::string& path) noexcept
-{
-    if (path.rfind(".js") == std::string::npos)
-        return nullptr;
-
-    try {
-        auto plugin = std::make_shared<js_plugin>(id, path);
-
-        for (const auto& mod : modules_)
-            mod->load(irccd_, plugin);
-
-        plugin->open();
-
-        return plugin;
-    } catch (const std::exception &ex) {
-        log::warning() << "plugin " << id << ": " << ex.what() << std::endl;
-    }
-
-    return nullptr;
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_plugin.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,310 +0,0 @@
-/*
- * js_plugin.hpp -- JavaScript plugins for irccd
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_PLUGIN_HPP
-#define IRCCD_JS_PLUGIN_HPP
-
-/**
- * \file js_plugin.hpp
- * \brief JavaScript plugins for irccd.
- */
-
-#include <vector>
-
-#include "duktape.hpp"
-#include "plugin.hpp"
-
-namespace irccd {
-
-class module;
-
-/**
- * \brief JavaScript plugins for irccd.
- * \ingroup plugins
- */
-class js_plugin : public plugin {
-public:
-    /**
-     * List of modules to enable.
-     */
-    using modules_t = std::vector<std::unique_ptr<module>>;
-
-    /**
-     * Global property where to read/write plugin configuration (object).
-     */
-    static const std::string config_property;
-
-    /**
-     * Global property where to read/write plugin formats (object).
-     */
-    static const std::string format_property;
-
-    /**
-     * Global property where paths are defined (object).
-     */
-    static const std::string paths_property;
-
-private:
-    // JavaScript context
-    UniqueContext context_;
-
-    // Private helpers.
-    std::unordered_map<std::string, std::string> get_table(const std::string&) const;
-    void put_table(const std::string&, const std::unordered_map<std::string, std::string>&);
-    void call(const std::string&, unsigned = 0);
-
-public:
-    /**
-     * Constructor.
-     *
-     * \param name the plugin name
-     * \param path the path to the plugin
-     */
-    js_plugin(std::string name, std::string path);
-
-    /**
-     * Access the Duktape context.
-     *
-     * \return the context
-     */
-    inline UniqueContext& context() noexcept
-    {
-        return context_;
-    }
-
-    /**
-     * Open the script file associated.
-     */
-    void open();
-
-    /**
-     * \copydoc Plugin::config
-     */
-    plugin_config config() override
-    {
-        return get_table(config_property);
-    }
-
-    /**
-     * \copydoc Plugin::setConfig
-     */
-    void set_config(plugin_config config) override
-    {
-        put_table(config_property, config);
-    }
-
-    /**
-     * \copydoc Plugin::formats
-     */
-    plugin_formats formats() override
-    {
-        return get_table(format_property);
-    }
-
-    /**
-     * \copydoc Plugin::setFormats
-     */
-    void set_formats(plugin_formats formats) override
-    {
-        put_table(format_property, formats);
-    }
-
-    /**
-     * \copydoc Plugin::paths
-     */
-    plugin_paths paths() override
-    {
-        return get_table(paths_property);
-    }
-
-    /**
-     * \copydoc Plugin::set_paths
-     */
-    void set_paths(plugin_paths paths) override
-    {
-        put_table(paths_property, std::move(paths));
-    }
-
-    /**
-     * \copydoc Plugin::on_command
-     */
-    void on_command(irccd& irccd, const message_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_connect
-     */
-    void on_connect(irccd& irccd, const connect_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_channel_mode
-     */
-    void on_channel_mode(irccd& irccd, const channel_mode_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_channel_notice
-     */
-    void on_channel_notice(irccd& irccd, const channel_notice_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_invite
-     */
-    void on_invite(irccd& irccd, const invite_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_join
-     */
-    void on_join(irccd& irccd, const join_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_kick
-     */
-    void on_kick(irccd& irccd, const kick_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_load
-     */
-    void on_load(irccd& irccd) override;
-
-    /**
-     * \copydoc Plugin::on_message
-     */
-    void on_message(irccd& irccd, const message_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_me
-     */
-    void on_me(irccd& irccd, const me_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_mode
-     */
-    void on_mode(irccd& irccd, const mode_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_names
-     */
-    void on_names(irccd& irccd, const names_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_nick
-     */
-    void on_nick(irccd& irccd, const nick_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_notice
-     */
-    void on_notice(irccd& irccd, const notice_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_part
-     */
-    void on_part(irccd& irccd, const part_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_query
-     */
-    void on_query(irccd& irccd, const query_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_query_command
-     */
-    void on_query_command(irccd& irccd, const query_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_reload
-     */
-    void on_reload(irccd& irccd) override;
-
-    /**
-     * \copydoc Plugin::on_topic
-     */
-    void on_topic(irccd& irccd, const topic_event& event) override;
-
-    /**
-     * \copydoc Plugin::on_unload
-     */
-    void on_unload(irccd& irccd) override;
-
-    /**
-     * \copydoc Plugin::on_whois
-     */
-    void on_whois(irccd& irccd, const whois_event& event) override;
-};
-
-/**
- * \brief Implementation for searching Javascript plugins.
- */
-class js_plugin_loader : public plugin_loader {
-public:
-    using modules_t = std::vector<std::unique_ptr<module>>;
-
-private:
-    irccd& irccd_;
-    modules_t modules_;
-
-public:
-    /**
-     * Constructor.
-     *
-     * \param irccd the irccd instance
-     */
-    js_plugin_loader(irccd& irccd) noexcept;
-
-    /**
-     * Destructor defaulted.
-     */
-    ~js_plugin_loader() noexcept;
-
-    /**
-     * Get the list of modules.
-     *
-     * \return the modules
-     */
-    inline const modules_t& modules() const noexcept
-    {
-        return modules_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the modules
-     */
-    inline modules_t& modules() noexcept
-    {
-        return modules_;
-    }
-
-    /**
-     * Register a new module for loading new plugins.
-     *
-     * \deprecated use modules() instead
-     * \param module the module to add
-     */
-    void add_module(std::unique_ptr<module> module);
-
-    /**
-     * \copydoc PluginLoader::open
-     */
-    std::shared_ptr<plugin> open(const std::string& id,
-                                 const std::string& path) noexcept override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_PLUGIN_JS_HPP
--- a/libirccd-js/irccd/js_plugin_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,385 +0,0 @@
-/*
- * js_plugin_module.cpp -- Irccd.Plugin API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/irccd.hpp>
-#include <irccd/service.hpp>
-
-#include "js_irccd_module.hpp"
-#include "js_plugin.hpp"
-#include "js_plugin_module.hpp"
-
-namespace irccd {
-
-namespace {
-
-const char plugin_ref[] = "\xff""\xff""irccd-plugin-ptr";
-
-/*
- * wrap
- * ------------------------------------------------------------------
- *
- * Wrap function for these functions because they all takes the same arguments.
- *
- * - load,
- * - reload,
- * - unload.
- */
-template <typename Func>
-duk_idx_t wrap(duk_context* ctx, int nret, Func&& func)
-{
-    std::string name = duk_require_string(ctx, 0);
-
-    try {
-        func(dukx_get_irccd(ctx), name);
-    } catch (const std::out_of_range &ex) {
-        dukx_throw(ctx, ReferenceError(ex.what()));
-    } catch (const std::exception &ex) {
-        dukx_throw(ctx, Error(ex.what()));
-    }
-
-    return nret;
-}
-
-/*
- * 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, const std::string& name)
-{
-    if (!duk_is_object(ctx, 0))
-        duk_error(ctx, DUK_ERR_TYPE_ERROR, "'%s' property must be object", name.c_str());
-
-    // Merge old table with new one.
-    duk_get_global_string(ctx, name.c_str());
-    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.c_str());
-
-    return 0;
-}
-
-/*
- * get
- * ------------------------------------------------------------------
- *
- * Get the Irccd.Plugin.(config|format|paths) property.
- */
-duk_ret_t get(duk_context* ctx, const std::string& name)
-{
-    duk_get_global_string(ctx, name.c_str());
-
-    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);
-}
-
-/*
- * 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.
- */
-duk_idx_t info(duk_context* ctx)
-{
-    std::shared_ptr<plugin> plugin;
-
-    if (duk_get_top(ctx) >= 1)
-        plugin = dukx_get_irccd(ctx).plugins().get(duk_require_string(ctx, 0));
-    else
-        plugin = dukx_get_plugin(ctx);
-
-    if (!plugin)
-        return 0;
-
-    duk_push_object(ctx);
-    dukx_push_std_string(ctx, plugin->name());
-    duk_put_prop_string(ctx, -2, "name");
-    dukx_push_std_string(ctx, plugin->author());
-    duk_put_prop_string(ctx, -2, "author");
-    dukx_push_std_string(ctx, plugin->license());
-    duk_put_prop_string(ctx, -2, "license");
-    dukx_push_std_string(ctx, plugin->summary());
-    duk_put_prop_string(ctx, -2, "summary");
-    dukx_push_std_string(ctx, plugin->version());
-    duk_put_prop_string(ctx, -2, "version");
-
-    return 1;
-}
-
-/*
- * 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 list(duk_context* ctx)
-{
-    dukx_push_array(ctx, dukx_get_irccd(ctx).plugins().list(), [] (auto ctx, auto plugin) {
-        dukx_push_std_string(ctx, plugin->name());
-    });
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Plugin.load(name)
- * ------------------------------------------------------------------
- *
- * Load a plugin by name. This function will search through the standard
- * directories.
- *
- * Arguments:
- *   - name, the plugin identifier.
- * Throws:
- *   - Error on errors,
- *   - ReferenceError if the plugin was not found.
- */
-duk_idx_t load(duk_context* ctx)
-{
-    return wrap(ctx, 0, [&] (irccd &irccd, const std::string &name) {
-        irccd.plugins().load(name);
-    });
-}
-
-/*
- * Function: Irccd.Plugin.reload(name)
- * ------------------------------------------------------------------
- *
- * Reload a plugin by name.
- *
- * Arguments:
- *   - name, the plugin identifier.
- * Throws:
- *   - Error on errors,
- *   - ReferenceError if the plugin was not found.
- */
-duk_idx_t reload(duk_context* ctx)
-{
-    return wrap(ctx, 0, [&] (irccd &irccd, const std::string &name) {
-        irccd.plugins().reload(name);
-    });
-}
-
-/*
- * Function: Irccd.Plugin.unload(name)
- * ------------------------------------------------------------------
- *
- * Unload a plugin by name.
- *
- * Arguments:
- *   - name, the plugin identifier.
- * Throws:
- *   - Error on errors,
- *   - ReferenceError if the plugin was not found.
- */
-duk_idx_t unload(duk_context* ctx)
-{
-    return wrap(ctx, 0, [&] (irccd &irccd, const std::string &name) {
-        irccd.plugins().unload(name);
-    });
-}
-
-const duk_function_list_entry functions[] = {
-    { "info",   info,       DUK_VARARGS     },
-    { "list",   list,       0               },
-    { "load",   load,       1               },
-    { "reload", reload,     1               },
-    { "unload", unload,     1               },
-    { nullptr,  nullptr,    0               }
-};
-
-} // !namespace
-
-js_plugin_module::js_plugin_module() noexcept
-    : module("Irccd.Plugin")
-{
-}
-
-void js_plugin_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_push_pointer(plugin->context(), new std::weak_ptr<js_plugin>(plugin));
-    duk_push_object(plugin->context());
-    duk_push_c_function(plugin->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->context(), -2);
-    duk_put_global_string(plugin->context(), "\xff""\xff""dummy-shared-ptr");
-    duk_put_global_string(plugin->context(), plugin_ref);
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, functions);
-
-    // 'config' property.
-    duk_push_string(plugin->context(), "config");
-    duk_push_c_function(plugin->context(), get_config, 0);
-    duk_push_c_function(plugin->context(), set_config, 1);
-    duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
-
-    // 'format' property.
-    duk_push_string(plugin->context(), "format");
-    duk_push_c_function(plugin->context(), get_format, 0);
-    duk_push_c_function(plugin->context(), set_format, 1);
-    duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
-
-    // 'format' property.
-    duk_push_string(plugin->context(), "paths");
-    duk_push_c_function(plugin->context(), get_paths, 0);
-    duk_push_c_function(plugin->context(), set_paths, 1);
-    duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
-
-    duk_put_prop_string(plugin->context(), -2, "Plugin");
-    duk_pop(plugin->context());
-}
-
-std::shared_ptr<js_plugin> dukx_get_plugin(duk_context* ctx)
-{
-    StackAssert 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();
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_plugin_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * js_plugin_module.hpp -- Irccd.Plugin API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_PLUGIN_MODULE_HPP
-#define IRCCD_JS_PLUGIN_MODULE_HPP
-
-/**
- * \file js_plugin_module.hpp
- * \brief Irccd.Plugin JavaScript API.
- */
-
-#include "duktape.hpp"
-#include "module.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.Plugin JavaScript API.
- * \ingroup modules
- */
-class js_plugin_module : public module {
-public:
-    /**
-     * Irccd.Plugin.
-     */
-    js_plugin_module() noexcept;
-
-    /**
-     * \copydoc Module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-/**
- * Access the plugin stored in this context.
- *
- * \param ctx the context
- * \return the plugin
- */
-std::shared_ptr<js_plugin> dukx_get_plugin(duk_context* ctx);
-
-} // !irccd
-
-#endif // !IRCCD_JS_PLUGIN_MODULE_HPP
--- a/libirccd-js/irccd/js_server_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,581 +0,0 @@
-/*
- * js_server_module.cpp -- Irccd.Server API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-#include <sstream>
-#include <unordered_map>
-
-#include <irccd.hpp>
-#include <irccd/js_plugin.hpp>
-#include <irccd/server.hpp>
-#include <irccd/service.hpp>
-
-#include "js_irccd_module.hpp"
-#include "js_server_module.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)
-{
-    StackAssert 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);
-}
-
-/*
- * Method: Server.cmode(channel, mode)
- * ------------------------------------------------------------------
- *
- * Change a channel mode.
- *
- * Arguments:
- *   - channel, the channel,
- *   - mode, the mode.
- */
-duk_ret_t cmode(duk_context* ctx)
-{
-    self(ctx)->cmode(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-    return 0;
-}
-
-/*
- * Method: Server.cnotice(channel, message)
- * ------------------------------------------------------------------
- *
- * Send a channel notice.
- *
- * Arguments:
- *   - channel, the channel,
- *   - message, the message.
- */
-duk_ret_t cnotice(duk_context* ctx)
-{
-    self(ctx)->cnotice(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-    return 0;
-}
-
-/*
- * Method: Server.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 info(duk_context* ctx)
-{
-    auto server = self(ctx);
-
-    duk_push_object(ctx);
-    dukx_push_std_string(ctx, server->name());
-    duk_put_prop_string(ctx, -2, "name");
-    dukx_push_std_string(ctx, server->host());
-    duk_put_prop_string(ctx, -2, "host");
-    duk_push_int(ctx, server->port());
-    duk_put_prop_string(ctx, -2, "port");
-    duk_push_boolean(ctx, server->flags() & server::ssl);
-    duk_put_prop_string(ctx, -2, "ssl");
-    duk_push_boolean(ctx, server->flags() & server::ssl_verify);
-    duk_put_prop_string(ctx, -2, "sslVerify");
-    dukx_push_std_string(ctx, server->command_char());
-    duk_put_prop_string(ctx, -2, "commandChar");
-    dukx_push_std_string(ctx, server->realname());
-    duk_put_prop_string(ctx, -2, "realname");
-    dukx_push_std_string(ctx, server->nickname());
-    duk_put_prop_string(ctx, -2, "nickname");
-    dukx_push_std_string(ctx, server->username());
-    duk_put_prop_string(ctx, -2, "username");
-    dukx_push_array(ctx, server->channels(), [&] (auto ctx, auto channel) {
-        dukx_push_std_string(ctx, channel);
-    });
-    duk_put_prop_string(ctx, -2, "channels");
-
-    return 1;
-}
-
-/*
- * Method: Server.invite(target, channel)
- * ------------------------------------------------------------------
- *
- * Invite someone to a channel.
- *
- * Arguments:
- *   - target, the target to invite,
- *   - channel, the channel.
- */
-duk_ret_t invite(duk_context* ctx)
-{
-    self(ctx)->invite(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-    return 0;
-}
-
-/*
- * Method: Server.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.
- */
-duk_ret_t join(duk_context* ctx)
-{
-    self(ctx)->join(duk_require_string(ctx, 0), dukx_get_std_string(ctx, 1));
-
-    return 0;
-}
-
-/*
- * Method: Server.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.
- */
-duk_ret_t kick(duk_context* ctx)
-{
-    self(ctx)->kick(duk_require_string(ctx, 0), duk_require_string(ctx, 1), dukx_get_std_string(ctx, 2));
-
-    return 0;
-}
-
-/*
- * Method: Server.me(target, message)
- * ------------------------------------------------------------------
- *
- * Send a CTCP Action.
- *
- * Arguments:
- *   - target, the target or a channel,
- *   - message, the message.
- */
-duk_ret_t me(duk_context* ctx)
-{
-    self(ctx)->me(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-    return 0;
-}
-
-/*
- * Method: Server.message(target, message)
- * ------------------------------------------------------------------
- *
- * Send a message.
- *
- * Arguments:
- *   - target, the target or a channel,
- *   - message, the message.
- */
-duk_ret_t message(duk_context* ctx)
-{
-    self(ctx)->message(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-    return 0;
-}
-
-/*
- * Method: Server.mode(mode)
- * ------------------------------------------------------------------
- *
- * Change your mode.
- *
- * Arguments:
- *   - mode, the new mode.
- */
-duk_ret_t mode(duk_context* ctx)
-{
-    self(ctx)->mode(duk_require_string(ctx, 0));
-
-    return 0;
-}
-
-/*
- * Method: Server.names(channel)
- * ------------------------------------------------------------------
- *
- * Get the list of names from a channel.
- *
- * Arguments:
- *   - channel, the channel.
- */
-duk_ret_t names(duk_context* ctx)
-{
-    self(ctx)->names(duk_require_string(ctx, 0));
-
-    return 0;
-}
-
-/*
- * Method: Server.nick(nickname)
- * ------------------------------------------------------------------
- *
- * Change the nickname.
- *
- * Arguments:
- *   - nickname, the nickname.
- */
-duk_ret_t nick(duk_context* ctx)
-{
-    self(ctx)->set_nickname(duk_require_string(ctx, 0));
-
-    return 0;
-}
-
-/*
- * Method: Server.notice(target, message)
- * ------------------------------------------------------------------
- *
- * Send a private notice.
- *
- * Arguments:
- *   - target, the target,
- *   - message, the notice message.
- */
-duk_ret_t notice(duk_context* ctx)
-{
-    self(ctx)->notice(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-    return 0;
-}
-
-/*
- * Method: Server.part(channel, reason = undefined)
- * ------------------------------------------------------------------
- *
- * Leave a channel.
- *
- * Arguments:
- *   - channel, the channel to leave,
- *   - reason, the optional reason, keep undefined for portability.
- */
-duk_ret_t part(duk_context* ctx)
-{
-    self(ctx)->part(duk_require_string(ctx, 0), dukx_get_std_string(ctx, 1));
-
-    return 0;
-}
-
-/*
- * Method: Server.send(raw)
- * ------------------------------------------------------------------
- *
- * Send a raw message to the IRC server.
- *
- * Arguments:
- *   - raw, the raw message (without terminators).
- */
-duk_ret_t send(duk_context* ctx)
-{
-    self(ctx)->send(duk_require_string(ctx, 0));
-
-    return 0;
-}
-
-/*
- * Method: Server.topic(channel, topic)
- * ------------------------------------------------------------------
- *
- * Change a channel topic.
- *
- * Arguments:
- *   - channel, the channel,
- *   - topic, the new topic.
- */
-duk_ret_t topic(duk_context* ctx)
-{
-    self(ctx)->topic(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-    return 0;
-}
-
-/*
- * Method: Server.whois(target)
- * ------------------------------------------------------------------
- *
- * Get whois information.
- *
- * Arguments:
- *   - target, the target.
- */
-duk_ret_t whois(duk_context* ctx)
-{
-    self(ctx)->whois(duk_require_string(ctx, 0));
-
-    return 0;
-}
-
-/*
- * Method: Server.toString()
- * ------------------------------------------------------------------
- *
- * Convert the object to std::string, convenience for adding the object
- * as property key.
- *
- * duk_ret_turns:
- *   The server name (unique).
- */
-duk_ret_t toString(duk_context* ctx)
-{
-    dukx_push_std_string(ctx, self(ctx)->name());
-
-    return 1;
-}
-
-/*
- * 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: "!")
- */
-duk_ret_t constructor(duk_context* ctx)
-{
-    if (!duk_is_constructor_call(ctx))
-        return 0;
-
-    duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
-
-    try {
-        auto json = duk_json_encode(ctx, 0);
-        auto s = server::from_json(nlohmann::json::parse(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);
-    } catch (const std::exception& ex) {
-        duk_error(ctx, DUK_ERR_ERROR, "%s", ex.what());
-    }
-
-    return 0;
-}
-
-/*
- * Function: Irccd.Server() [destructor]
- * ------------------------------------------------------------------
- *
- * Delete the property.
- */
-duk_ret_t 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;
-}
-
-/*
- * Function: Irccd.Server.add(s)
- * ------------------------------------------------------------------
- *
- * Register a new server to the irccd instance.
- *
- * Arguments:
- *   - s, the server to add.
- */
-duk_ret_t add(duk_context* ctx)
-{
-    dukx_get_irccd(ctx).servers().add(dukx_require_server(ctx, 0));
-
-    return 0;
-}
-
-/*
- * Function: Irccd.Server.find(name)
- * ------------------------------------------------------------------
- *
- * Find a server by name.
- *
- * Arguments:
- *   - name, the server name
- * duk_ret_turns:
- *   The server object or undefined if not found.
- */
-duk_ret_t find(duk_context* ctx)
-{
-    auto server = dukx_get_irccd(ctx).servers().get(duk_require_string(ctx, 0));
-
-    if (!server)
-        return 0;
-
-    dukx_push_server(ctx, server);
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Server.list()
- * ------------------------------------------------------------------
- *
- * Get the map of all loaded servers.
- *
- * duk_ret_turns:
- *   An object with string-to-servers pairs.
- */
-duk_ret_t list(duk_context* ctx)
-{
-    duk_push_object(ctx);
-
-    for (const auto &server : dukx_get_irccd(ctx).servers().servers()) {
-        dukx_push_server(ctx, server);
-        duk_put_prop_string(ctx, -2, server->name().c_str());
-    }
-
-    return 1;
-}
-
-/*
- * 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 remove(duk_context* ctx)
-{
-    dukx_get_irccd(ctx).servers().remove(duk_require_string(ctx, 0));
-
-    return 0;
-}
-
-const duk_function_list_entry methods[] = {
-    { "cmode",      cmode,      2           },
-    { "cnotice",    cnotice,    2           },
-    { "info",       info,       0           },
-    { "invite",     invite,     2           },
-    { "join",       join,       DUK_VARARGS },
-    { "kick",       kick,       DUK_VARARGS },
-    { "me",         me,         2           },
-    { "message",    message,    2           },
-    { "mode",       mode,       1           },
-    { "names",      names,      1           },
-    { "nick",       nick,       1           },
-    { "notice",     notice,     2           },
-    { "part",       part,       DUK_VARARGS },
-    { "send",       send,       1           },
-    { "topic",      topic,      2           },
-    { "whois",      whois,      1           },
-    { "toString",   toString,   0           },
-    { nullptr,      nullptr,    0           }
-};
-
-const duk_function_list_entry functions[] = {
-    { "add",        add,        1           },
-    { "find",       find,       1           },
-    { "list",       list,       0           },
-    { "remove",     remove,     1           },
-    { nullptr,      nullptr,    0           }
-};
-
-} // !namespace
-
-js_server_module::js_server_module() noexcept
-    : module("Irccd.Server")
-{
-}
-
-void js_server_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 1);
-    duk_put_function_list(plugin->context(), -1, functions);
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, methods);
-    duk_push_c_function(plugin->context(), destructor, 1);
-    duk_set_finalizer(plugin->context(), -2);
-    duk_dup_top(plugin->context());
-    duk_put_global_string(plugin->context(), prototype);
-    duk_put_prop_string(plugin->context(), -2, "prototype");
-    duk_put_prop_string(plugin->context(), -2, "Server");
-    duk_pop(plugin->context());
-}
-
-void dukx_push_server(duk_context* ctx, std::shared_ptr<server> server)
-{
-    assert(ctx);
-    assert(server);
-
-    StackAssert 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> dukx_require_server(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;
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_server_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/*
- * js_server_module.hpp -- Irccd.Server API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_SERVER_MODULE_HPP
-#define IRCCD_JS_SERVER_MODULE_HPP
-
-/**
- * \file mod-server.hpp
- * \brief irccd.Server JavaScript API.
- */
-
-#include "duktape.hpp"
-#include "module.hpp"
-#include "server.hpp"
-
-namespace irccd {
-
-/**
- * \brief irccd.Server JavaScript API.
- * \ingroup modules
- */
-class js_server_module : public module {
-public:
-    /**
-     * irccd.Server.
-     */
-    js_server_module() noexcept;
-
-    /**
-     * \copydoc Module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-/**
- * Push a server.
- *
- * \pre server != nullptr
- * \param ctx the context
- * \param server the server
- */
-void dukx_push_server(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
- */
-std::shared_ptr<server> dukx_require_server(duk_context* ctx, duk_idx_t index);
-
-} // !irccd
-
-#endif // !IRCCD_JS_SERVER_MODULE_HPP
--- a/libirccd-js/irccd/js_system_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,244 +0,0 @@
-/*
- * js_system_module.cpp -- Irccd.System API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <chrono>
-#include <cstdlib>
-#include <thread>
-
-#include <irccd/sysconfig.hpp>
-
-#if defined(HAVE_POPEN)
-#  include <cstdio>
-#endif
-
-#include <irccd/js_plugin.hpp>
-#include <irccd/system.hpp>
-
-#include "js_file_module.hpp"
-#include "js_irccd_module.hpp"
-#include "js_system_module.hpp"
-
-namespace irccd {
-
-namespace {
-
-/*
- * Function: irccd.System.env(key)
- * ------------------------------------------------------------------
- *
- * Get an environment system variable.
- *
- * Arguments:
- *   - key, the environment variable.
- * Returns:
- *   The value.
- */
-duk_ret_t env(duk_context* ctx)
-{
-    dukx_push_std_string(ctx, sys::env(dukx_get_std_string(ctx, 0)));
-
-    return 1;
-}
-
-/*
- * Function: irccd.System.exec(cmd)
- * ------------------------------------------------------------------
- *
- * Execute a system command.
- *
- * Arguments:
- *   - cmd, the command to execute.
- */
-duk_ret_t exec(duk_context* ctx)
-{
-    std::system(duk_get_string(ctx, 0));
-
-    return 0;
-}
-
-/*
- * Function: irccd.System.home()
- * ------------------------------------------------------------------
- *
- * Get the operating system user's home.
- *
- * Returns:
- *   The user home directory.
- */
-duk_ret_t home(duk_context* ctx)
-{
-    dukx_push_std_string(ctx, sys::home());
-
-    return 1;
-}
-
-/*
- * Function: irccd.System.name()
- * ------------------------------------------------------------------
- *
- * Get the operating system name.
- *
- * Returns:
- *   The system name.
- */
-duk_ret_t name(duk_context* ctx)
-{
-    dukx_push_std_string(ctx, sys::name());
-
-    return 1;
-}
-
-#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.system_error on failures.
- */
-duk_ret_t popen(duk_context* ctx)
-{
-    auto fp = ::popen(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-    if (fp == nullptr)
-        dukx_throw(ctx, system_error());
-
-    dukx_push_file(ctx, new file(fp, [] (auto fp) { ::pclose(fp); }));
-
-    return 1;
-}
-
-#endif // !HAVE_POPEN
-
-/*
- * Function: irccd.System.sleep(delay)
- * ------------------------------------------------------------------
- *
- * Sleep the main loop for the specific delay in seconds.
- */
-duk_ret_t sleep(duk_context* ctx)
-{
-    std::this_thread::sleep_for(std::chrono::seconds(duk_get_int(ctx, 0)));
-
-    return 0;
-}
-
-/*
- * Function: irccd.System.ticks()
- * ------------------------------------------------------------------
- *
- * Get the number of milliseconds since irccd was started.
- *
- * Returns:
- *   The number of milliseconds.
- */
-duk_ret_t ticks(duk_context* ctx)
-{
-    duk_push_int(ctx, sys::ticks());
-
-    return 1;
-}
-
-/*
- * Function: irccd.System.usleep(delay)
- * ------------------------------------------------------------------
- *
- * Sleep the main loop for the specific delay in microseconds.
- */
-duk_ret_t usleep(duk_context* ctx)
-{
-    std::this_thread::sleep_for(std::chrono::microseconds(duk_get_int(ctx, 0)));
-
-    return 0;
-}
-
-/*
- * Function: irccd.System.uptime()
- * ------------------------------------------------------------------
- *
- * Get the system uptime.
- *
- * Returns:
- *   The system uptime.
- */
-duk_ret_t uptime(duk_context* ctx)
-{
-    duk_push_int(ctx, sys::uptime());
-
-    return 0;
-}
-
-/*
- * Function: irccd.System.version()
- * ------------------------------------------------------------------
- *
- * Get the operating system version.
- *
- * Returns:
- *   The system version.
- */
-duk_ret_t version(duk_context* ctx)
-{
-    dukx_push_std_string(ctx, sys::version());
-
-    return 1;
-}
-
-const duk_function_list_entry functions[] = {
-    { "env",        env,        1 },
-    { "exec",       exec,       1 },
-    { "home",       home,       0 },
-    { "name",       name,       0 },
-#if defined(HAVE_POPEN)
-    { "popen",      popen,      2 },
-#endif
-    { "sleep",      sleep,      1 },
-    { "ticks",      ticks,      0 },
-    { "uptime",     uptime,     0 },
-    { "usleep",     usleep,     1 },
-    { "version",    version,    0 },
-    { nullptr,      nullptr,    0 }
-};
-
-} // !namespace
-
-js_system_module::js_system_module() noexcept
-    : module("Irccd.System")
-{
-}
-
-void js_system_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, functions);
-    duk_put_prop_string(plugin->context(), -2, "System");
-    duk_pop(plugin->context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_system_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * js_system_module.hpp -- Irccd.System API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_SYSTEM_MODULE_HPP
-#define IRCCD_JS_SYSTEM_MODULE_HPP
-
-/**
- * \file mod-system.hpp
- * \brief irccd.System JavaScript API.
- */
-
-#include "module.hpp"
-
-namespace irccd {
-
-/**
- * \brief irccd.System JavaScript API.
- * \ingroup modules
- */
-class js_system_module : public module {
-public:
-    /**
-     * irccd.System.
-     */
-    js_system_module() noexcept;
-
-    /**
-     * \copydoc Module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_SYSTEM_MODULE_HPP
--- a/libirccd-js/irccd/js_timer_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,208 +0,0 @@
-/*
- * js_timer_module.cpp -- Irccd.timer API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/irccd.hpp>
-#include <irccd/logger.hpp>
-#include <irccd/js_plugin.hpp>
-#include <irccd/string_util.hpp>
-#include <irccd/timer.hpp>
-
-#include "js_irccd_module.hpp"
-#include "js_plugin_module.hpp"
-#include "js_timer_module.hpp"
-
-namespace irccd {
-
-namespace {
-
-const char* signature("\xff""\xff""irccd-timer-ptr");
-const char* callback_table("\xff""\xff""irccd-timer-callbacks");
-
-void handle_signal(irccd& instance, std::weak_ptr<js_plugin> ptr, std::string key)
-{
-    auto plugin = ptr.lock();
-
-    if (!plugin)
-        return;
-
-    instance.post([plugin, key] (irccd &) {
-        StackAssert sa(plugin->context());
-
-        duk_get_global_string(plugin->context(), callback_table);
-        duk_get_prop_string(plugin->context(), -1, key.c_str());
-        duk_remove(plugin->context(), -2);
-
-        if (duk_is_callable(plugin->context(), -1)) {
-            if (duk_pcall(plugin->context(), 0) != 0)
-                log::warning(string_util::sprintf("plugin %s: %s", plugin->name(),
-                    dukx_exception(plugin->context(), -1).stack));
-            else
-                duk_pop(plugin->context());
-        } else
-            duk_pop(plugin->context());
-    });
-}
-
-std::shared_ptr<timer> self(duk_context* ctx)
-{
-    StackAssert 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<std::shared_ptr<timer>*>(ptr);
-}
-
-/*
- * Method: timer.start()
- * --------------------------------------------------------
- *
- * Start the timer. If the timer is already started the method is a no-op.
- */
-duk_ret_t start(duk_context* ctx)
-{
-    auto timer = self(ctx);
-
-    if (!timer->is_running())
-        timer->start();
-
-    return 0;
-}
-
-/*
- * Method: timer.stop()
- * --------------------------------------------------------
- *
- * Stop the timer.
- */
-duk_ret_t stop(duk_context* ctx)
-{
-    auto timer = self(ctx);
-
-    if (timer->is_running())
-        timer->stop();
-
-    return 0;
-}
-
-const duk_function_list_entry methods[] = {
-    { "start",  start,      0 },
-    { "stop",   stop,       0 },
-    { nullptr,  nullptr,    0 }
-};
-
-/*
- * 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 constructor(duk_context* ctx)
-{
-    // Check parameters.
-    auto type = duk_require_int(ctx, 0);
-    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");
-
-    // Construct the timer in 'this'.
-    auto& irccd = dukx_get_irccd(ctx);
-    auto tm = std::make_shared<timer>(static_cast<timer::type>(type), delay);
-    auto hash = std::to_string(reinterpret_cast<std::uintptr_t>(tm.get()));
-
-    tm->on_signal.connect(std::bind(handle_signal, std::ref(irccd),
-        std::weak_ptr<js_plugin>(dukx_get_plugin(ctx)), hash));
-
-    duk_push_this(ctx);
-    duk_push_pointer(ctx, new std::shared_ptr<timer>(std::move(tm)));
-    duk_put_prop_string(ctx, -2, signature);
-    duk_push_string(ctx, hash.c_str());
-    duk_put_prop_string(ctx, -2, "\xff""\xff""timer-key");
-    duk_push_c_function(ctx, [] (duk_context* ctx) -> duk_ret_t {
-        StackAssert sa(ctx);
-
-        duk_get_prop_string(ctx, 0, "\xff""\xff""timer-key");
-        auto hash = duk_get_string(ctx, -1);
-        duk_pop(ctx);
-        duk_get_prop_string(ctx, 0, signature);
-        static_cast<std::shared_ptr<timer>*>(duk_to_pointer(ctx, -1))->get()->stop();
-        delete static_cast<std::shared_ptr<timer>*>(duk_to_pointer(ctx, -1));
-        duk_pop(ctx);
-        duk_get_global_string(ctx, callback_table);
-        duk_del_prop_string(ctx, -1, hash);
-        duk_pop(ctx);
-        log::debug("plugin: timer destroyed");
-
-        return 0;
-    }, 1);
-    duk_set_finalizer(ctx, -2);
-
-    // Save a callback function into the callback table.
-    duk_get_global_string(ctx, callback_table);
-    duk_dup(ctx, 2);
-    duk_put_prop_string(ctx, -2, hash.c_str());
-    duk_pop(ctx);
-
-    return 0;
-}
-
-const duk_number_list_entry constants[] = {
-    { "Single",     static_cast<int>(timer::type::single)   },
-    { "Repeat",     static_cast<int>(timer::type::repeat)   },
-    { nullptr,      0                                       }
-};
-
-} // !namespace
-
-js_timer_module::js_timer_module() noexcept
-    : module("Irccd.timer")
-{
-}
-
-void js_timer_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_c_function(plugin->context(), constructor, 3);
-    duk_put_number_list(plugin->context(), -1, constants);
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, methods);
-    duk_put_prop_string(plugin->context(), -2, "prototype");
-    duk_put_prop_string(plugin->context(), -2, "Timer");
-    duk_pop(plugin->context());
-    duk_push_object(plugin->context());
-    duk_put_global_string(plugin->context(), callback_table);
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_timer_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * js_timer_module.hpp -- Irccd.Timer API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_TIMER_MODULE_HPP
-#define IRCCD_JS_TIMER_MODULE_HPP
-
-/**
- * \file js_timer_module
- * \brief irccd.Timer JavaScript API.
- */
-
-#include "module.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.Timer JavaScript API.
- * \ingroup modules
- */
-class js_timer_module : public module {
-public:
-    /**
-     * Constructor.
-     */
-    js_timer_module() noexcept;
-
-    /**
-     * \copydoc module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_MOD_TIMER_HPP
--- a/libirccd-js/irccd/js_unicode_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-/*
- * js_unicode_module.cpp -- Irccd.Unicode API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/js_plugin.hpp>
-
-#include "duktape.hpp"
-#include "js_unicode_module.hpp"
-#include "unicode.hpp"
-
-namespace irccd {
-
-namespace {
-
-/*
- * Function: Irccd.Unicode.isDigit(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is in the digit category.
- */
-duk_ret_t is_digit(duk_context* ctx)
-{
-    duk_push_boolean(ctx, unicode::isdigit(duk_get_int(ctx, 0)));
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Unicode.isLetter(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is in the letter category.
- */
-duk_ret_t is_letter(duk_context* ctx)
-{
-    duk_push_boolean(ctx, unicode::isalpha(duk_get_int(ctx, 0)));
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Unicode.isLower(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is lower case.
- */
-duk_ret_t is_lower(duk_context* ctx)
-{
-    duk_push_boolean(ctx, unicode::islower(duk_get_int(ctx, 0)));
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Unicode.isSpace(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is in the space category.
- */
-duk_ret_t is_space(duk_context* ctx)
-{
-    duk_push_boolean(ctx, unicode::isspace(duk_get_int(ctx, 0)));
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Unicode.isTitle(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is title case.
- */
-duk_ret_t is_title(duk_context* ctx)
-{
-    duk_push_boolean(ctx, unicode::istitle(duk_get_int(ctx, 0)));
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Unicode.isUpper(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is upper case.
- */
-duk_ret_t is_upper(duk_context* ctx)
-{
-    duk_push_boolean(ctx, unicode::isupper(duk_get_int(ctx, 0)));
-
-    return 1;
-}
-
-const duk_function_list_entry functions[] = {
-    { "isDigit",        is_digit,   1 },
-    { "isLetter",       is_letter,  1 },
-    { "isLower",        is_lower,   1 },
-    { "isSpace",        is_space,   1 },
-    { "isTitle",        is_title,   1 },
-    { "isUpper",        is_upper,   1 },
-    { nullptr,          nullptr,    0 }
-};
-
-} // !namespace
-
-js_unicode_module::js_unicode_module() noexcept
-    : module("Irccd.Unicode")
-{
-}
-
-void js_unicode_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, functions);
-    duk_put_prop_string(plugin->context(), -2, "Unicode");
-    duk_pop(plugin->context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_unicode_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * js_unicode_module.hpp -- Irccd.Unicode API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_UNICODE_MODULE_HPP
-#define IRCCD_JS_UNICODE_MODULE_HPP
-
-/**
- * \file js_unicode_module.hpp
- * \brief irccd.Unicode JavaScript API.
- */
-
-#include "module.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.Unicode JavaScript API.
- * \ingroup modules
- */
-class js_unicode_module : public module {
-public:
-    /**
-     * Constructor.
-     */
-    js_unicode_module() noexcept;
-
-    /**
-     * \copydoc Module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_JS_UNICODE_MODULE_HPP
--- a/libirccd-js/irccd/js_util_module.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,300 +0,0 @@
-/*
- * js_util_module.cpp -- Irccd.Util API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <climits>
-
-#include <libircclient.h>
-
-#include <irccd/string_util.hpp>
-
-#include "js_util_module.hpp"
-#include "js_plugin.hpp"
-
-namespace irccd {
-
-namespace {
-
-/*
- * 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 get_subst(duk_context* ctx, int index)
-{
-    string_util::subst params;
-
-    if (!duk_is_object(ctx, index))
-        return params;
-
-    dukx_enumerate(ctx, index, 0, true, [&] (auto) {
-        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)});
-    });
-
-    return params;
-}
-
-/*
- * 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.
-            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 (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 (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;
-}
-
-/*
- * 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.
- */
-duk_ret_t cut(duk_context* ctx)
-{
-    auto list = lines(ctx, split(ctx), limit(ctx, 1, "maxc", 72));
-    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;
-    }
-
-    dukx_push_array(ctx, list, dukx_push_std_string);
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Util.format(text, parameters)
- * --------------------------------------------------------
- *
- * Format a string with templates.
- *
- * Arguments:
- *   - input, the text to update,
- *   - params, the parameters.
- * Returns:
- *   The converted text.
- */
-duk_ret_t format(duk_context* ctx)
-{
-    try {
-        dukx_push_std_string(ctx, string_util::format(dukx_get_std_string(ctx, 0), get_subst(ctx, 1)));
-    } catch (const std::exception &ex) {
-        dukx_throw(ctx, SyntaxError(ex.what()));
-    }
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Util.splituser(ident)
- * --------------------------------------------------------
- *
- * Return the nickname part from a full username.
- *
- * Arguments:
- *   - ident, the full identity.
- * Returns:
- *   The nickname.
- */
-duk_ret_t splituser(duk_context* ctx)
-{
-    auto target = duk_require_string(ctx, 0);
-    char nick[32] = {0};
-
-    irc_target_get_nick(target, nick, sizeof (nick) -1);
-    duk_push_string(ctx, nick);
-
-    return 1;
-}
-
-/*
- * Function: Irccd.Util.splithost(ident)
- * --------------------------------------------------------
- *
- * Return the hostname part from a full username.
- *
- * Arguments:
- *   - ident, the full identity.
- * Returns:
- *   The hostname.
- */
-duk_ret_t splithost(duk_context* ctx)
-{
-    auto target = duk_require_string(ctx, 0);
-    char host[32] = {0};
-
-    irc_target_get_host(target, host, sizeof (host) -1);
-    duk_push_string(ctx, host);
-
-    return 1;
-}
-
-const duk_function_list_entry functions[] = {
-    { "cut",        cut,        DUK_VARARGS },
-    { "format",     format,     DUK_VARARGS },
-    { "splituser",  splituser,  1           },
-    { "splithost",  splithost,  1           },
-    { nullptr,      nullptr,    0           }
-};
-
-} // !namespace
-
-js_util_module::js_util_module() noexcept
-    : module("Irccd.Util")
-{
-}
-
-void js_util_module::load(irccd&, std::shared_ptr<js_plugin> plugin)
-{
-    StackAssert sa(plugin->context());
-
-    duk_get_global_string(plugin->context(), "Irccd");
-    duk_push_object(plugin->context());
-    duk_put_function_list(plugin->context(), -1, functions);
-    duk_put_prop_string(plugin->context(), -2, "Util");
-    duk_pop(plugin->context());
-}
-
-} // !irccd
--- a/libirccd-js/irccd/js_util_module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * js_util_module.hpp -- Irccd.Util API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JS_UTIL_MODULE_HPP
-#define IRCCD_JS_UTIL_MODULE_HPP
-
-/**
- * \file js_util_module.hpp
- * \brief irccd.Util JavaScript API.
- */
-
-#include "module.hpp"
-
-namespace irccd {
-
-/**
- * \brief Irccd.Util JavaScript API.
- * \ingroup modules
- */
-class js_util_module : public module {
-public:
-    /**
-     * Constructor.
-     */
-    js_util_module() noexcept;
-
-    /**
-     * \copydoc module::load
-     */
-    void load(irccd& irccd, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_MOD_UTIL_HPP
--- a/libirccd-js/irccd/module.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-/*
- * module.hpp -- JavaScript API module
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_MODULE_HPP
-#define IRCCD_MODULE_HPP
-
-/**
- * \file module.hpp
- * \brief Javascript API module.
- */
-
-/**
- * \defgroup Javascript modules.
- * \brief Modules for the JavaScript API.
- */
-
-#include <cassert>
-#include <memory>
-#include <string>
-
-#include "sysconfig.hpp"
-#include "util.hpp"
-
-namespace irccd {
-
-class irccd;
-class js_plugin;
-
-/**
- * \brief JavaScript API module.
- */
-class module {
-private:
-    std::string name_;
-
-public:
-    /**
-     * Default constructor.
-     *
-     * \pre !name.empty()
-     */
-    inline module(std::string name) noexcept
-        : name_(std::move(name))
-    {
-        assert(!name_.empty());
-    }
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~module() noexcept = default;
-
-    /**
-     * Get the module name.
-     *
-     * \return the name
-     */
-    inline const std::string& name() const noexcept
-    {
-        return name_;
-    }
-
-    /**
-     * 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)
-    {
-        util::unused(irccd, plugin);
-    }
-};
-
-} // !irccd
-
-#endif // !IRCCD_MODULE_HPP
--- a/libirccd-js/irccd/timer.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/*
- * timer.cpp -- threaded timers
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-#include <chrono>
-
-#include <iostream>
-
-#include "timer.hpp"
-
-namespace irccd {
-
-void timer::run()
-{
-    while (state_ != state::stopped) {
-        std::unique_lock<std::mutex> lock(mutex_);
-
-        // Wait in case the timer is paused.
-        condition_.wait(lock, [&] () {
-            return state_ == state::running;
-        });
-
-        if (state_ != state::running)
-            continue;
-
-        // Wait the timer delay or the interrupt.
-        condition_.wait_for(lock, std::chrono::milliseconds(delay_), [&] () {
-            return state_ != state::running;
-        });
-
-        if (state_ == state::running) {
-            // Signal process.
-            on_signal();
-
-            if (type_ == type::single)
-                state_ = state::stopped;
-        }
-    }
-
-    on_end();
-}
-
-timer::timer(type type, unsigned delay) noexcept
-    : type_(type)
-    , delay_(delay)
-    , thread_(std::bind(&timer::run, this))
-{
-}
-
-timer::~timer()
-{
-    assert(state_ != state::running);
-
-    try {
-        {
-            std::lock_guard<std::mutex> lk(mutex_);
-
-            state_ = state::stopped;
-            condition_.notify_one();
-        }
-
-        thread_.join();
-    } catch (...) {
-    }
-}
-
-void timer::start()
-{
-    assert(state_ != state::running);
-
-    {
-        std::lock_guard<std::mutex> lk(mutex_);
-        state_ = state::running;
-    }
-
-    condition_.notify_one();
-}
-
-void timer::stop()
-{
-    {
-        std::lock_guard<std::mutex> lk(mutex_);
-        state_ = state::paused;
-    }
-
-    condition_.notify_one();
-}
-
-} // !irccd
--- a/libirccd-js/irccd/timer.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-/*
- * timer.hpp -- threaded timers
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_TIMER_HPP
-#define IRCCD_TIMER_HPP
-
-/**
- * \file timer.hpp
- * \brief Provides interval based timers for JavaScript
- */
-
-#include <atomic>
-#include <condition_variable>
-#include <functional>
-#include <mutex>
-#include <thread>
-
-#include "signals.hpp"
-#include "sysconfig.hpp"
-
-namespace irccd {
-
-/**
- * \brief Timer class
- *
- * A timer is a thread object that emits a signal periodically or just one time. It is perfectly pausable and resumable
- * to reuse the same object.
- *
- * The delay is configured in milliseconds and the user has choice to use any
- * delay needed.
- *
- * We use a condition variable to wait for the specified delay unless the timer
- * must be stopped.
- */
-class timer {
-public:
-    /**
-     * \brief Type of timer
-     */
-    enum class type {
-        single,             //!< The timer ends after execution
-        repeat              //!< The timer loops
-    };
-
-    /**
-     * Signal: onSignal
-     * ----------------------------------------------------------
-     *
-     * Called when the timeout expires.
-     */
-    Signal<> on_signal;
-
-    /**
-     * Signal: onEnd
-     * ----------------------------------------------------------
-     *
-     * Called when the timeout ends.
-     */
-    Signal<> on_end;
-
-private:
-    enum class state {
-        paused,
-        running,
-        stopped
-    };
-
-    type type_;
-    unsigned delay_;
-
-    // Thread management.
-    std::atomic<state> state_{state::paused};
-    std::mutex mutex_;
-    std::condition_variable condition_;
-    std::thread thread_;
-
-    void run();
-
-public:
-    /**
-     * Timer constructor.
-     *
-     * The timer is not started, use start().
-     *
-     * \param type the timer type
-     * \param delay the delay in milliseconds
-     * \post isRunning() returns false
-     */
-    timer(type type, unsigned delay) noexcept;
-
-    /**
-     * Destructor, closes the thread.
-     *
-     * \pre stop() must have been called.
-     */
-    virtual ~timer();
-
-    /**
-     * Start the thread.
-     *
-     * \pre isRunning() must return false
-     * \pre onSignal() must have been called
-     * \pre onEnd() must have been called
-     * \note Thread-safe
-     */
-    void start();
-
-    /**
-     * Stop the timer, may be used by the user to stop it.
-     *
-     * \note Thread-safe
-     */
-    void stop();
-
-    /**
-     * Get the type of timer.
-     *
-     * \return the type.
-     */
-    inline type get_type() const noexcept
-    {
-        return type_;
-    }
-
-    /**
-     * Tells if the timer has still a running thread.
-     *
-     * \return true if still alive
-     * \note Thread-safe
-     */
-    inline bool is_running() const noexcept
-    {
-        return state_ == state::running;
-    }
-};
-
-} // !irccd
-
-#endif // !IRCCD_TIMER_HPP
--- a/libirccd-js/irccd/unicode.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4796 +0,0 @@
-/*
- * unicode.cpp -- UTF-8 to UTF-32 conversions and various operations
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "unicode.hpp"
-
-/*
- * The following code has been generated from Go mkrunetype adapted to our
- * needs.
- */
-
-namespace irccd {
-
-namespace unicode {
-
-#define nelem(x) (sizeof (x) / sizeof ((x)[0]))
-
-namespace {
-
-const char32_t *rbsearch(char32_t c, const char32_t *t, int n, int ne) noexcept
-{
-   const char32_t *p;
-   int m;
-
-   while (n > 1) {
-       m = n >> 1;
-       p = t + m * ne;
-
-       if (c >= p[0]) {
-           t = p;
-           n = n - m;
-       } else
-           n = m;
-   }
-
-   if (n && c >= t[0])
-       return t;
-
-   return nullptr;
-}
-
-} // !namespace
-
-namespace {
-
-const char32_t isspacer[] = {
-    0x0009, 0x000d,
-    0x0020, 0x0020,
-    0x0085, 0x0085,
-    0x00a0, 0x00a0,
-    0x1680, 0x1680,
-    0x2000, 0x200a,
-    0x2028, 0x2029,
-    0x202f, 0x202f,
-    0x205f, 0x205f,
-    0x3000, 0x3000,
-    0xfeff, 0xfeff,
-};
-
-} // !namespace
-
-bool isspace(char32_t c) noexcept
-{
-   const char32_t *p;
-
-   p = rbsearch(c, isspacer, nelem (isspacer)/2, 2);
-
-   if (p && c >= p[0] && c <= p[1])
-       return true;
-
-   return false;
-}
-
-namespace {
-
-const char32_t isdigitr[] = {
-    0x0030, 0x0039,
-    0x0660, 0x0669,
-    0x06f0, 0x06f9,
-    0x07c0, 0x07c9,
-    0x0966, 0x096f,
-    0x09e6, 0x09ef,
-    0x0a66, 0x0a6f,
-    0x0ae6, 0x0aef,
-    0x0b66, 0x0b6f,
-    0x0be6, 0x0bef,
-    0x0c66, 0x0c6f,
-    0x0ce6, 0x0cef,
-    0x0d66, 0x0d6f,
-    0x0de6, 0x0def,
-    0x0e50, 0x0e59,
-    0x0ed0, 0x0ed9,
-    0x0f20, 0x0f29,
-    0x1040, 0x1049,
-    0x1090, 0x1099,
-    0x17e0, 0x17e9,
-    0x1810, 0x1819,
-    0x1946, 0x194f,
-    0x19d0, 0x19d9,
-    0x1a80, 0x1a89,
-    0x1a90, 0x1a99,
-    0x1b50, 0x1b59,
-    0x1bb0, 0x1bb9,
-    0x1c40, 0x1c49,
-    0x1c50, 0x1c59,
-    0xa620, 0xa629,
-    0xa8d0, 0xa8d9,
-    0xa900, 0xa909,
-    0xa9d0, 0xa9d9,
-    0xa9f0, 0xa9f9,
-    0xaa50, 0xaa59,
-    0xabf0, 0xabf9,
-    0xff10, 0xff19,
-    0x104a0, 0x104a9,
-    0x11066, 0x1106f,
-    0x110f0, 0x110f9,
-    0x11136, 0x1113f,
-    0x111d0, 0x111d9,
-    0x112f0, 0x112f9,
-    0x114d0, 0x114d9,
-    0x11650, 0x11659,
-    0x116c0, 0x116c9,
-    0x118e0, 0x118e9,
-    0x16a60, 0x16a69,
-    0x16b50, 0x16b59,
-    0x1d7ce, 0x1d7ff,
-};
-
-} // !namespace
-
-bool isdigit(char32_t c) noexcept
-{
-   const char32_t *p;
-
-   p = rbsearch(c, isdigitr, nelem (isdigitr)/2, 2);
-
-   if (p && c >= p[0] && c <= p[1])
-       return true;
-
-   return false;
-}
-
-namespace {
-
-const char32_t isalphar[] = {
-    0x0041, 0x005a,
-    0x0061, 0x007a,
-    0x00c0, 0x00d6,
-    0x00d8, 0x00f6,
-    0x00f8, 0x02c1,
-    0x02c6, 0x02d1,
-    0x02e0, 0x02e4,
-    0x0370, 0x0374,
-    0x0376, 0x0377,
-    0x037a, 0x037d,
-    0x0388, 0x038a,
-    0x038e, 0x03a1,
-    0x03a3, 0x03f5,
-    0x03f7, 0x0481,
-    0x048a, 0x052f,
-    0x0531, 0x0556,
-    0x0561, 0x0587,
-    0x05d0, 0x05ea,
-    0x05f0, 0x05f2,
-    0x0620, 0x064a,
-    0x066e, 0x066f,
-    0x0671, 0x06d3,
-    0x06e5, 0x06e6,
-    0x06ee, 0x06ef,
-    0x06fa, 0x06fc,
-    0x0712, 0x072f,
-    0x074d, 0x07a5,
-    0x07ca, 0x07ea,
-    0x07f4, 0x07f5,
-    0x0800, 0x0815,
-    0x0840, 0x0858,
-    0x08a0, 0x08b2,
-    0x0904, 0x0939,
-    0x0958, 0x0961,
-    0x0971, 0x0980,
-    0x0985, 0x098c,
-    0x098f, 0x0990,
-    0x0993, 0x09a8,
-    0x09aa, 0x09b0,
-    0x09b6, 0x09b9,
-    0x09dc, 0x09dd,
-    0x09df, 0x09e1,
-    0x09f0, 0x09f1,
-    0x0a05, 0x0a0a,
-    0x0a0f, 0x0a10,
-    0x0a13, 0x0a28,
-    0x0a2a, 0x0a30,
-    0x0a32, 0x0a33,
-    0x0a35, 0x0a36,
-    0x0a38, 0x0a39,
-    0x0a59, 0x0a5c,
-    0x0a72, 0x0a74,
-    0x0a85, 0x0a8d,
-    0x0a8f, 0x0a91,
-    0x0a93, 0x0aa8,
-    0x0aaa, 0x0ab0,
-    0x0ab2, 0x0ab3,
-    0x0ab5, 0x0ab9,
-    0x0ae0, 0x0ae1,
-    0x0b05, 0x0b0c,
-    0x0b0f, 0x0b10,
-    0x0b13, 0x0b28,
-    0x0b2a, 0x0b30,
-    0x0b32, 0x0b33,
-    0x0b35, 0x0b39,
-    0x0b5c, 0x0b5d,
-    0x0b5f, 0x0b61,
-    0x0b85, 0x0b8a,
-    0x0b8e, 0x0b90,
-    0x0b92, 0x0b95,
-    0x0b99, 0x0b9a,
-    0x0b9e, 0x0b9f,
-    0x0ba3, 0x0ba4,
-    0x0ba8, 0x0baa,
-    0x0bae, 0x0bb9,
-    0x0c05, 0x0c0c,
-    0x0c0e, 0x0c10,
-    0x0c12, 0x0c28,
-    0x0c2a, 0x0c39,
-    0x0c58, 0x0c59,
-    0x0c60, 0x0c61,
-    0x0c85, 0x0c8c,
-    0x0c8e, 0x0c90,
-    0x0c92, 0x0ca8,
-    0x0caa, 0x0cb3,
-    0x0cb5, 0x0cb9,
-    0x0ce0, 0x0ce1,
-    0x0cf1, 0x0cf2,
-    0x0d05, 0x0d0c,
-    0x0d0e, 0x0d10,
-    0x0d12, 0x0d3a,
-    0x0d60, 0x0d61,
-    0x0d7a, 0x0d7f,
-    0x0d85, 0x0d96,
-    0x0d9a, 0x0db1,
-    0x0db3, 0x0dbb,
-    0x0dc0, 0x0dc6,
-    0x0e01, 0x0e30,
-    0x0e32, 0x0e33,
-    0x0e40, 0x0e46,
-    0x0e81, 0x0e82,
-    0x0e87, 0x0e88,
-    0x0e94, 0x0e97,
-    0x0e99, 0x0e9f,
-    0x0ea1, 0x0ea3,
-    0x0eaa, 0x0eab,
-    0x0ead, 0x0eb0,
-    0x0eb2, 0x0eb3,
-    0x0ec0, 0x0ec4,
-    0x0edc, 0x0edf,
-    0x0f40, 0x0f47,
-    0x0f49, 0x0f6c,
-    0x0f88, 0x0f8c,
-    0x1000, 0x102a,
-    0x1050, 0x1055,
-    0x105a, 0x105d,
-    0x1065, 0x1066,
-    0x106e, 0x1070,
-    0x1075, 0x1081,
-    0x10a0, 0x10c5,
-    0x10d0, 0x10fa,
-    0x10fc, 0x1248,
-    0x124a, 0x124d,
-    0x1250, 0x1256,
-    0x125a, 0x125d,
-    0x1260, 0x1288,
-    0x128a, 0x128d,
-    0x1290, 0x12b0,
-    0x12b2, 0x12b5,
-    0x12b8, 0x12be,
-    0x12c2, 0x12c5,
-    0x12c8, 0x12d6,
-    0x12d8, 0x1310,
-    0x1312, 0x1315,
-    0x1318, 0x135a,
-    0x1380, 0x138f,
-    0x13a0, 0x13f4,
-    0x1401, 0x166c,
-    0x166f, 0x167f,
-    0x1681, 0x169a,
-    0x16a0, 0x16ea,
-    0x16f1, 0x16f8,
-    0x1700, 0x170c,
-    0x170e, 0x1711,
-    0x1720, 0x1731,
-    0x1740, 0x1751,
-    0x1760, 0x176c,
-    0x176e, 0x1770,
-    0x1780, 0x17b3,
-    0x1820, 0x1877,
-    0x1880, 0x18a8,
-    0x18b0, 0x18f5,
-    0x1900, 0x191e,
-    0x1950, 0x196d,
-    0x1970, 0x1974,
-    0x1980, 0x19ab,
-    0x19c1, 0x19c7,
-    0x1a00, 0x1a16,
-    0x1a20, 0x1a54,
-    0x1b05, 0x1b33,
-    0x1b45, 0x1b4b,
-    0x1b83, 0x1ba0,
-    0x1bae, 0x1baf,
-    0x1bba, 0x1be5,
-    0x1c00, 0x1c23,
-    0x1c4d, 0x1c4f,
-    0x1c5a, 0x1c7d,
-    0x1ce9, 0x1cec,
-    0x1cee, 0x1cf1,
-    0x1cf5, 0x1cf6,
-    0x1d00, 0x1dbf,
-    0x1e00, 0x1f15,
-    0x1f18, 0x1f1d,
-    0x1f20, 0x1f45,
-    0x1f48, 0x1f4d,
-    0x1f50, 0x1f57,
-    0x1f5f, 0x1f7d,
-    0x1f80, 0x1fb4,
-    0x1fb6, 0x1fbc,
-    0x1fc2, 0x1fc4,
-    0x1fc6, 0x1fcc,
-    0x1fd0, 0x1fd3,
-    0x1fd6, 0x1fdb,
-    0x1fe0, 0x1fec,
-    0x1ff2, 0x1ff4,
-    0x1ff6, 0x1ffc,
-    0x2090, 0x209c,
-    0x210a, 0x2113,
-    0x2119, 0x211d,
-    0x212a, 0x212d,
-    0x212f, 0x2139,
-    0x213c, 0x213f,
-    0x2145, 0x2149,
-    0x2183, 0x2184,
-    0x2c00, 0x2c2e,
-    0x2c30, 0x2c5e,
-    0x2c60, 0x2ce4,
-    0x2ceb, 0x2cee,
-    0x2cf2, 0x2cf3,
-    0x2d00, 0x2d25,
-    0x2d30, 0x2d67,
-    0x2d80, 0x2d96,
-    0x2da0, 0x2da6,
-    0x2da8, 0x2dae,
-    0x2db0, 0x2db6,
-    0x2db8, 0x2dbe,
-    0x2dc0, 0x2dc6,
-    0x2dc8, 0x2dce,
-    0x2dd0, 0x2dd6,
-    0x2dd8, 0x2dde,
-    0x3005, 0x3006,
-    0x3031, 0x3035,
-    0x303b, 0x303c,
-    0x3041, 0x3096,
-    0x309d, 0x309f,
-    0x30a1, 0x30fa,
-    0x30fc, 0x30ff,
-    0x3105, 0x312d,
-    0x3131, 0x318e,
-    0x31a0, 0x31ba,
-    0x31f0, 0x31ff,
-    0x3400, 0x4db5,
-    0x4e00, 0x9fcc,
-    0xa000, 0xa48c,
-    0xa4d0, 0xa4fd,
-    0xa500, 0xa60c,
-    0xa610, 0xa61f,
-    0xa62a, 0xa62b,
-    0xa640, 0xa66e,
-    0xa67f, 0xa69d,
-    0xa6a0, 0xa6e5,
-    0xa717, 0xa71f,
-    0xa722, 0xa788,
-    0xa78b, 0xa78e,
-    0xa790, 0xa7ad,
-    0xa7b0, 0xa7b1,
-    0xa7f7, 0xa801,
-    0xa803, 0xa805,
-    0xa807, 0xa80a,
-    0xa80c, 0xa822,
-    0xa840, 0xa873,
-    0xa882, 0xa8b3,
-    0xa8f2, 0xa8f7,
-    0xa90a, 0xa925,
-    0xa930, 0xa946,
-    0xa960, 0xa97c,
-    0xa984, 0xa9b2,
-    0xa9e0, 0xa9e4,
-    0xa9e6, 0xa9ef,
-    0xa9fa, 0xa9fe,
-    0xaa00, 0xaa28,
-    0xaa40, 0xaa42,
-    0xaa44, 0xaa4b,
-    0xaa60, 0xaa76,
-    0xaa7e, 0xaaaf,
-    0xaab5, 0xaab6,
-    0xaab9, 0xaabd,
-    0xaadb, 0xaadd,
-    0xaae0, 0xaaea,
-    0xaaf2, 0xaaf4,
-    0xab01, 0xab06,
-    0xab09, 0xab0e,
-    0xab11, 0xab16,
-    0xab20, 0xab26,
-    0xab28, 0xab2e,
-    0xab30, 0xab5a,
-    0xab5c, 0xab5f,
-    0xab64, 0xab65,
-    0xabc0, 0xabe2,
-    0xac00, 0xd7a3,
-    0xd7b0, 0xd7c6,
-    0xd7cb, 0xd7fb,
-    0xf900, 0xfa6d,
-    0xfa70, 0xfad9,
-    0xfb00, 0xfb06,
-    0xfb13, 0xfb17,
-    0xfb1f, 0xfb28,
-    0xfb2a, 0xfb36,
-    0xfb38, 0xfb3c,
-    0xfb40, 0xfb41,
-    0xfb43, 0xfb44,
-    0xfb46, 0xfbb1,
-    0xfbd3, 0xfd3d,
-    0xfd50, 0xfd8f,
-    0xfd92, 0xfdc7,
-    0xfdf0, 0xfdfb,
-    0xfe70, 0xfe74,
-    0xfe76, 0xfefc,
-    0xff21, 0xff3a,
-    0xff41, 0xff5a,
-    0xff66, 0xffbe,
-    0xffc2, 0xffc7,
-    0xffca, 0xffcf,
-    0xffd2, 0xffd7,
-    0xffda, 0xffdc,
-    0x10000, 0x1000b,
-    0x1000d, 0x10026,
-    0x10028, 0x1003a,
-    0x1003c, 0x1003d,
-    0x1003f, 0x1004d,
-    0x10050, 0x1005d,
-    0x10080, 0x100fa,
-    0x10280, 0x1029c,
-    0x102a0, 0x102d0,
-    0x10300, 0x1031f,
-    0x10330, 0x10340,
-    0x10342, 0x10349,
-    0x10350, 0x10375,
-    0x10380, 0x1039d,
-    0x103a0, 0x103c3,
-    0x103c8, 0x103cf,
-    0x10400, 0x1049d,
-    0x10500, 0x10527,
-    0x10530, 0x10563,
-    0x10600, 0x10736,
-    0x10740, 0x10755,
-    0x10760, 0x10767,
-    0x10800, 0x10805,
-    0x1080a, 0x10835,
-    0x10837, 0x10838,
-    0x1083f, 0x10855,
-    0x10860, 0x10876,
-    0x10880, 0x1089e,
-    0x10900, 0x10915,
-    0x10920, 0x10939,
-    0x10980, 0x109b7,
-    0x109be, 0x109bf,
-    0x10a10, 0x10a13,
-    0x10a15, 0x10a17,
-    0x10a19, 0x10a33,
-    0x10a60, 0x10a7c,
-    0x10a80, 0x10a9c,
-    0x10ac0, 0x10ac7,
-    0x10ac9, 0x10ae4,
-    0x10b00, 0x10b35,
-    0x10b40, 0x10b55,
-    0x10b60, 0x10b72,
-    0x10b80, 0x10b91,
-    0x10c00, 0x10c48,
-    0x11003, 0x11037,
-    0x11083, 0x110af,
-    0x110d0, 0x110e8,
-    0x11103, 0x11126,
-    0x11150, 0x11172,
-    0x11183, 0x111b2,
-    0x111c1, 0x111c4,
-    0x11200, 0x11211,
-    0x11213, 0x1122b,
-    0x112b0, 0x112de,
-    0x11305, 0x1130c,
-    0x1130f, 0x11310,
-    0x11313, 0x11328,
-    0x1132a, 0x11330,
-    0x11332, 0x11333,
-    0x11335, 0x11339,
-    0x1135d, 0x11361,
-    0x11480, 0x114af,
-    0x114c4, 0x114c5,
-    0x11580, 0x115ae,
-    0x11600, 0x1162f,
-    0x11680, 0x116aa,
-    0x118a0, 0x118df,
-    0x11ac0, 0x11af8,
-    0x12000, 0x12398,
-    0x13000, 0x1342e,
-    0x16800, 0x16a38,
-    0x16a40, 0x16a5e,
-    0x16ad0, 0x16aed,
-    0x16b00, 0x16b2f,
-    0x16b40, 0x16b43,
-    0x16b63, 0x16b77,
-    0x16b7d, 0x16b8f,
-    0x16f00, 0x16f44,
-    0x16f93, 0x16f9f,
-    0x1b000, 0x1b001,
-    0x1bc00, 0x1bc6a,
-    0x1bc70, 0x1bc7c,
-    0x1bc80, 0x1bc88,
-    0x1bc90, 0x1bc99,
-    0x1d400, 0x1d454,
-    0x1d456, 0x1d49c,
-    0x1d49e, 0x1d49f,
-    0x1d4a5, 0x1d4a6,
-    0x1d4a9, 0x1d4ac,
-    0x1d4ae, 0x1d4b9,
-    0x1d4bd, 0x1d4c3,
-    0x1d4c5, 0x1d505,
-    0x1d507, 0x1d50a,
-    0x1d50d, 0x1d514,
-    0x1d516, 0x1d51c,
-    0x1d51e, 0x1d539,
-    0x1d53b, 0x1d53e,
-    0x1d540, 0x1d544,
-    0x1d54a, 0x1d550,
-    0x1d552, 0x1d6a5,
-    0x1d6a8, 0x1d6c0,
-    0x1d6c2, 0x1d6da,
-    0x1d6dc, 0x1d6fa,
-    0x1d6fc, 0x1d714,
-    0x1d716, 0x1d734,
-    0x1d736, 0x1d74e,
-    0x1d750, 0x1d76e,
-    0x1d770, 0x1d788,
-    0x1d78a, 0x1d7a8,
-    0x1d7aa, 0x1d7c2,
-    0x1d7c4, 0x1d7cb,
-    0x1e800, 0x1e8c4,
-    0x1ee00, 0x1ee03,
-    0x1ee05, 0x1ee1f,
-    0x1ee21, 0x1ee22,
-    0x1ee29, 0x1ee32,
-    0x1ee34, 0x1ee37,
-    0x1ee4d, 0x1ee4f,
-    0x1ee51, 0x1ee52,
-    0x1ee61, 0x1ee62,
-    0x1ee67, 0x1ee6a,
-    0x1ee6c, 0x1ee72,
-    0x1ee74, 0x1ee77,
-    0x1ee79, 0x1ee7c,
-    0x1ee80, 0x1ee89,
-    0x1ee8b, 0x1ee9b,
-    0x1eea1, 0x1eea3,
-    0x1eea5, 0x1eea9,
-    0x1eeab, 0x1eebb,
-    0x20000, 0x2a6d6,
-    0x2a700, 0x2b734,
-    0x2b740, 0x2b81d,
-    0x2f800, 0x2fa1d,
-};
-
-} // !namespace
-
-namespace {
-
-const char32_t isalphas[] = {
-    0x00aa,
-    0x00b5,
-    0x00ba,
-    0x02ec,
-    0x02ee,
-    0x037f,
-    0x0386,
-    0x038c,
-    0x0559,
-    0x06d5,
-    0x06ff,
-    0x0710,
-    0x07b1,
-    0x07fa,
-    0x081a,
-    0x0824,
-    0x0828,
-    0x093d,
-    0x0950,
-    0x09b2,
-    0x09bd,
-    0x09ce,
-    0x0a5e,
-    0x0abd,
-    0x0ad0,
-    0x0b3d,
-    0x0b71,
-    0x0b83,
-    0x0b9c,
-    0x0bd0,
-    0x0c3d,
-    0x0cbd,
-    0x0cde,
-    0x0d3d,
-    0x0d4e,
-    0x0dbd,
-    0x0e84,
-    0x0e8a,
-    0x0e8d,
-    0x0ea5,
-    0x0ea7,
-    0x0ebd,
-    0x0ec6,
-    0x0f00,
-    0x103f,
-    0x1061,
-    0x108e,
-    0x10c7,
-    0x10cd,
-    0x1258,
-    0x12c0,
-    0x17d7,
-    0x17dc,
-    0x18aa,
-    0x1aa7,
-    0x1f59,
-    0x1f5b,
-    0x1f5d,
-    0x1fbe,
-    0x2071,
-    0x207f,
-    0x2102,
-    0x2107,
-    0x2115,
-    0x2124,
-    0x2126,
-    0x2128,
-    0x214e,
-    0x2d27,
-    0x2d2d,
-    0x2d6f,
-    0x2e2f,
-    0xa8fb,
-    0xa9cf,
-    0xaa7a,
-    0xaab1,
-    0xaac0,
-    0xaac2,
-    0xfb1d,
-    0xfb3e,
-    0x10808,
-    0x1083c,
-    0x10a00,
-    0x11176,
-    0x111da,
-    0x1133d,
-    0x114c7,
-    0x11644,
-    0x118ff,
-    0x16f50,
-    0x1d4a2,
-    0x1d4bb,
-    0x1d546,
-    0x1ee24,
-    0x1ee27,
-    0x1ee39,
-    0x1ee3b,
-    0x1ee42,
-    0x1ee47,
-    0x1ee49,
-    0x1ee4b,
-    0x1ee54,
-    0x1ee57,
-    0x1ee59,
-    0x1ee5b,
-    0x1ee5d,
-    0x1ee5f,
-    0x1ee64,
-    0x1ee7e,
-};
-
-} // !namespace
-
-bool isalpha(char32_t c) noexcept
-{
-   const char32_t *p;
-
-   p = rbsearch(c, isalphar, nelem (isalphar)/2, 2);
-
-   if (p && c >= p[0] && c <= p[1])
-       return true;
-
- p = rbsearch(c, isalphas, nelem (isalphas), 1);
-
-   if (p && c == p[0])
-       return true;
-
- return false;
-}
-
-namespace {
-
-const char32_t isupperr[] = {
-    0x0041, 0x005a,
-    0x00c0, 0x00d6,
-    0x00d8, 0x00de,
-    0x0178, 0x0179,
-    0x0181, 0x0182,
-    0x0186, 0x0187,
-    0x0189, 0x018b,
-    0x018e, 0x0191,
-    0x0193, 0x0194,
-    0x0196, 0x0198,
-    0x019c, 0x019d,
-    0x019f, 0x01a0,
-    0x01a6, 0x01a7,
-    0x01ae, 0x01af,
-    0x01b1, 0x01b3,
-    0x01b7, 0x01b8,
-    0x01f6, 0x01f8,
-    0x023a, 0x023b,
-    0x023d, 0x023e,
-    0x0243, 0x0246,
-    0x0388, 0x038a,
-    0x038e, 0x038f,
-    0x0391, 0x03a1,
-    0x03a3, 0x03ab,
-    0x03d2, 0x03d4,
-    0x03f9, 0x03fa,
-    0x03fd, 0x042f,
-    0x04c0, 0x04c1,
-    0x0531, 0x0556,
-    0x10a0, 0x10c5,
-    0x1f08, 0x1f0f,
-    0x1f18, 0x1f1d,
-    0x1f28, 0x1f2f,
-    0x1f38, 0x1f3f,
-    0x1f48, 0x1f4d,
-    0x1f68, 0x1f6f,
-    0x1f88, 0x1f8f,
-    0x1f98, 0x1f9f,
-    0x1fa8, 0x1faf,
-    0x1fb8, 0x1fbc,
-    0x1fc8, 0x1fcc,
-    0x1fd8, 0x1fdb,
-    0x1fe8, 0x1fec,
-    0x1ff8, 0x1ffc,
-    0x210b, 0x210d,
-    0x2110, 0x2112,
-    0x2119, 0x211d,
-    0x212a, 0x212d,
-    0x2130, 0x2133,
-    0x213e, 0x213f,
-    0x2160, 0x216f,
-    0x24b6, 0x24cf,
-    0x2c00, 0x2c2e,
-    0x2c62, 0x2c64,
-    0x2c6d, 0x2c70,
-    0x2c7e, 0x2c80,
-    0xa77d, 0xa77e,
-    0xa7aa, 0xa7ad,
-    0xa7b0, 0xa7b1,
-    0xff21, 0xff3a,
-    0x10400, 0x10427,
-    0x118a0, 0x118bf,
-    0x1d400, 0x1d419,
-    0x1d434, 0x1d44d,
-    0x1d468, 0x1d481,
-    0x1d49e, 0x1d49f,
-    0x1d4a5, 0x1d4a6,
-    0x1d4a9, 0x1d4ac,
-    0x1d4ae, 0x1d4b5,
-    0x1d4d0, 0x1d4e9,
-    0x1d504, 0x1d505,
-    0x1d507, 0x1d50a,
-    0x1d50d, 0x1d514,
-    0x1d516, 0x1d51c,
-    0x1d538, 0x1d539,
-    0x1d53b, 0x1d53e,
-    0x1d540, 0x1d544,
-    0x1d54a, 0x1d550,
-    0x1d56c, 0x1d585,
-    0x1d5a0, 0x1d5b9,
-    0x1d5d4, 0x1d5ed,
-    0x1d608, 0x1d621,
-    0x1d63c, 0x1d655,
-    0x1d670, 0x1d689,
-    0x1d6a8, 0x1d6c0,
-    0x1d6e2, 0x1d6fa,
-    0x1d71c, 0x1d734,
-    0x1d756, 0x1d76e,
-    0x1d790, 0x1d7a8,
-};
-
-} // !namespace
-
-namespace {
-
-const char32_t isuppers[] = {
-    0x0100,
-    0x0102,
-    0x0104,
-    0x0106,
-    0x0108,
-    0x010a,
-    0x010c,
-    0x010e,
-    0x0110,
-    0x0112,
-    0x0114,
-    0x0116,
-    0x0118,
-    0x011a,
-    0x011c,
-    0x011e,
-    0x0120,
-    0x0122,
-    0x0124,
-    0x0126,
-    0x0128,
-    0x012a,
-    0x012c,
-    0x012e,
-    0x0130,
-    0x0132,
-    0x0134,
-    0x0136,
-    0x0139,
-    0x013b,
-    0x013d,
-    0x013f,
-    0x0141,
-    0x0143,
-    0x0145,
-    0x0147,
-    0x014a,
-    0x014c,
-    0x014e,
-    0x0150,
-    0x0152,
-    0x0154,
-    0x0156,
-    0x0158,
-    0x015a,
-    0x015c,
-    0x015e,
-    0x0160,
-    0x0162,
-    0x0164,
-    0x0166,
-    0x0168,
-    0x016a,
-    0x016c,
-    0x016e,
-    0x0170,
-    0x0172,
-    0x0174,
-    0x0176,
-    0x017b,
-    0x017d,
-    0x0184,
-    0x01a2,
-    0x01a4,
-    0x01a9,
-    0x01ac,
-    0x01b5,
-    0x01bc,
-    0x01c4,
-    0x01c7,
-    0x01ca,
-    0x01cd,
-    0x01cf,
-    0x01d1,
-    0x01d3,
-    0x01d5,
-    0x01d7,
-    0x01d9,
-    0x01db,
-    0x01de,
-    0x01e0,
-    0x01e2,
-    0x01e4,
-    0x01e6,
-    0x01e8,
-    0x01ea,
-    0x01ec,
-    0x01ee,
-    0x01f1,
-    0x01f4,
-    0x01fa,
-    0x01fc,
-    0x01fe,
-    0x0200,
-    0x0202,
-    0x0204,
-    0x0206,
-    0x0208,
-    0x020a,
-    0x020c,
-    0x020e,
-    0x0210,
-    0x0212,
-    0x0214,
-    0x0216,
-    0x0218,
-    0x021a,
-    0x021c,
-    0x021e,
-    0x0220,
-    0x0222,
-    0x0224,
-    0x0226,
-    0x0228,
-    0x022a,
-    0x022c,
-    0x022e,
-    0x0230,
-    0x0232,
-    0x0241,
-    0x0248,
-    0x024a,
-    0x024c,
-    0x024e,
-    0x0370,
-    0x0372,
-    0x0376,
-    0x037f,
-    0x0386,
-    0x038c,
-    0x03cf,
-    0x03d8,
-    0x03da,
-    0x03dc,
-    0x03de,
-    0x03e0,
-    0x03e2,
-    0x03e4,
-    0x03e6,
-    0x03e8,
-    0x03ea,
-    0x03ec,
-    0x03ee,
-    0x03f4,
-    0x03f7,
-    0x0460,
-    0x0462,
-    0x0464,
-    0x0466,
-    0x0468,
-    0x046a,
-    0x046c,
-    0x046e,
-    0x0470,
-    0x0472,
-    0x0474,
-    0x0476,
-    0x0478,
-    0x047a,
-    0x047c,
-    0x047e,
-    0x0480,
-    0x048a,
-    0x048c,
-    0x048e,
-    0x0490,
-    0x0492,
-    0x0494,
-    0x0496,
-    0x0498,
-    0x049a,
-    0x049c,
-    0x049e,
-    0x04a0,
-    0x04a2,
-    0x04a4,
-    0x04a6,
-    0x04a8,
-    0x04aa,
-    0x04ac,
-    0x04ae,
-    0x04b0,
-    0x04b2,
-    0x04b4,
-    0x04b6,
-    0x04b8,
-    0x04ba,
-    0x04bc,
-    0x04be,
-    0x04c3,
-    0x04c5,
-    0x04c7,
-    0x04c9,
-    0x04cb,
-    0x04cd,
-    0x04d0,
-    0x04d2,
-    0x04d4,
-    0x04d6,
-    0x04d8,
-    0x04da,
-    0x04dc,
-    0x04de,
-    0x04e0,
-    0x04e2,
-    0x04e4,
-    0x04e6,
-    0x04e8,
-    0x04ea,
-    0x04ec,
-    0x04ee,
-    0x04f0,
-    0x04f2,
-    0x04f4,
-    0x04f6,
-    0x04f8,
-    0x04fa,
-    0x04fc,
-    0x04fe,
-    0x0500,
-    0x0502,
-    0x0504,
-    0x0506,
-    0x0508,
-    0x050a,
-    0x050c,
-    0x050e,
-    0x0510,
-    0x0512,
-    0x0514,
-    0x0516,
-    0x0518,
-    0x051a,
-    0x051c,
-    0x051e,
-    0x0520,
-    0x0522,
-    0x0524,
-    0x0526,
-    0x0528,
-    0x052a,
-    0x052c,
-    0x052e,
-    0x10c7,
-    0x10cd,
-    0x1e00,
-    0x1e02,
-    0x1e04,
-    0x1e06,
-    0x1e08,
-    0x1e0a,
-    0x1e0c,
-    0x1e0e,
-    0x1e10,
-    0x1e12,
-    0x1e14,
-    0x1e16,
-    0x1e18,
-    0x1e1a,
-    0x1e1c,
-    0x1e1e,
-    0x1e20,
-    0x1e22,
-    0x1e24,
-    0x1e26,
-    0x1e28,
-    0x1e2a,
-    0x1e2c,
-    0x1e2e,
-    0x1e30,
-    0x1e32,
-    0x1e34,
-    0x1e36,
-    0x1e38,
-    0x1e3a,
-    0x1e3c,
-    0x1e3e,
-    0x1e40,
-    0x1e42,
-    0x1e44,
-    0x1e46,
-    0x1e48,
-    0x1e4a,
-    0x1e4c,
-    0x1e4e,
-    0x1e50,
-    0x1e52,
-    0x1e54,
-    0x1e56,
-    0x1e58,
-    0x1e5a,
-    0x1e5c,
-    0x1e5e,
-    0x1e60,
-    0x1e62,
-    0x1e64,
-    0x1e66,
-    0x1e68,
-    0x1e6a,
-    0x1e6c,
-    0x1e6e,
-    0x1e70,
-    0x1e72,
-    0x1e74,
-    0x1e76,
-    0x1e78,
-    0x1e7a,
-    0x1e7c,
-    0x1e7e,
-    0x1e80,
-    0x1e82,
-    0x1e84,
-    0x1e86,
-    0x1e88,
-    0x1e8a,
-    0x1e8c,
-    0x1e8e,
-    0x1e90,
-    0x1e92,
-    0x1e94,
-    0x1e9e,
-    0x1ea0,
-    0x1ea2,
-    0x1ea4,
-    0x1ea6,
-    0x1ea8,
-    0x1eaa,
-    0x1eac,
-    0x1eae,
-    0x1eb0,
-    0x1eb2,
-    0x1eb4,
-    0x1eb6,
-    0x1eb8,
-    0x1eba,
-    0x1ebc,
-    0x1ebe,
-    0x1ec0,
-    0x1ec2,
-    0x1ec4,
-    0x1ec6,
-    0x1ec8,
-    0x1eca,
-    0x1ecc,
-    0x1ece,
-    0x1ed0,
-    0x1ed2,
-    0x1ed4,
-    0x1ed6,
-    0x1ed8,
-    0x1eda,
-    0x1edc,
-    0x1ede,
-    0x1ee0,
-    0x1ee2,
-    0x1ee4,
-    0x1ee6,
-    0x1ee8,
-    0x1eea,
-    0x1eec,
-    0x1eee,
-    0x1ef0,
-    0x1ef2,
-    0x1ef4,
-    0x1ef6,
-    0x1ef8,
-    0x1efa,
-    0x1efc,
-    0x1efe,
-    0x1f59,
-    0x1f5b,
-    0x1f5d,
-    0x1f5f,
-    0x2102,
-    0x2107,
-    0x2115,
-    0x2124,
-    0x2126,
-    0x2128,
-    0x2145,
-    0x2183,
-    0x2c60,
-    0x2c67,
-    0x2c69,
-    0x2c6b,
-    0x2c72,
-    0x2c75,
-    0x2c82,
-    0x2c84,
-    0x2c86,
-    0x2c88,
-    0x2c8a,
-    0x2c8c,
-    0x2c8e,
-    0x2c90,
-    0x2c92,
-    0x2c94,
-    0x2c96,
-    0x2c98,
-    0x2c9a,
-    0x2c9c,
-    0x2c9e,
-    0x2ca0,
-    0x2ca2,
-    0x2ca4,
-    0x2ca6,
-    0x2ca8,
-    0x2caa,
-    0x2cac,
-    0x2cae,
-    0x2cb0,
-    0x2cb2,
-    0x2cb4,
-    0x2cb6,
-    0x2cb8,
-    0x2cba,
-    0x2cbc,
-    0x2cbe,
-    0x2cc0,
-    0x2cc2,
-    0x2cc4,
-    0x2cc6,
-    0x2cc8,
-    0x2cca,
-    0x2ccc,
-    0x2cce,
-    0x2cd0,
-    0x2cd2,
-    0x2cd4,
-    0x2cd6,
-    0x2cd8,
-    0x2cda,
-    0x2cdc,
-    0x2cde,
-    0x2ce0,
-    0x2ce2,
-    0x2ceb,
-    0x2ced,
-    0x2cf2,
-    0xa640,
-    0xa642,
-    0xa644,
-    0xa646,
-    0xa648,
-    0xa64a,
-    0xa64c,
-    0xa64e,
-    0xa650,
-    0xa652,
-    0xa654,
-    0xa656,
-    0xa658,
-    0xa65a,
-    0xa65c,
-    0xa65e,
-    0xa660,
-    0xa662,
-    0xa664,
-    0xa666,
-    0xa668,
-    0xa66a,
-    0xa66c,
-    0xa680,
-    0xa682,
-    0xa684,
-    0xa686,
-    0xa688,
-    0xa68a,
-    0xa68c,
-    0xa68e,
-    0xa690,
-    0xa692,
-    0xa694,
-    0xa696,
-    0xa698,
-    0xa69a,
-    0xa722,
-    0xa724,
-    0xa726,
-    0xa728,
-    0xa72a,
-    0xa72c,
-    0xa72e,
-    0xa732,
-    0xa734,
-    0xa736,
-    0xa738,
-    0xa73a,
-    0xa73c,
-    0xa73e,
-    0xa740,
-    0xa742,
-    0xa744,
-    0xa746,
-    0xa748,
-    0xa74a,
-    0xa74c,
-    0xa74e,
-    0xa750,
-    0xa752,
-    0xa754,
-    0xa756,
-    0xa758,
-    0xa75a,
-    0xa75c,
-    0xa75e,
-    0xa760,
-    0xa762,
-    0xa764,
-    0xa766,
-    0xa768,
-    0xa76a,
-    0xa76c,
-    0xa76e,
-    0xa779,
-    0xa77b,
-    0xa780,
-    0xa782,
-    0xa784,
-    0xa786,
-    0xa78b,
-    0xa78d,
-    0xa790,
-    0xa792,
-    0xa796,
-    0xa798,
-    0xa79a,
-    0xa79c,
-    0xa79e,
-    0xa7a0,
-    0xa7a2,
-    0xa7a4,
-    0xa7a6,
-    0xa7a8,
-    0x1d49c,
-    0x1d4a2,
-    0x1d546,
-    0x1d7ca,
-};
-
-} // !namespace
-
-bool isupper(char32_t c) noexcept
-{
-   const char32_t *p;
-
-   p = rbsearch(c, isupperr, nelem (isupperr)/2, 2);
-
-   if (p && c >= p[0] && c <= p[1])
-       return true;
-
- p = rbsearch(c, isuppers, nelem (isuppers), 1);
-
-   if (p && c == p[0])
-       return true;
-
- return false;
-}
-
-namespace {
-
-const char32_t islowerr[] = {
-    0x0061, 0x007a,
-    0x00df, 0x00f6,
-    0x00f8, 0x00ff,
-    0x0137, 0x0138,
-    0x0148, 0x0149,
-    0x017e, 0x0180,
-    0x018c, 0x018d,
-    0x0199, 0x019b,
-    0x01aa, 0x01ab,
-    0x01b9, 0x01ba,
-    0x01bd, 0x01bf,
-    0x01dc, 0x01dd,
-    0x01ef, 0x01f0,
-    0x0233, 0x0239,
-    0x023f, 0x0240,
-    0x024f, 0x0293,
-    0x0295, 0x02af,
-    0x037b, 0x037d,
-    0x03ac, 0x03ce,
-    0x03d0, 0x03d1,
-    0x03d5, 0x03d7,
-    0x03ef, 0x03f3,
-    0x03fb, 0x03fc,
-    0x0430, 0x045f,
-    0x04ce, 0x04cf,
-    0x0561, 0x0587,
-    0x1d00, 0x1d2b,
-    0x1d6b, 0x1d77,
-    0x1d79, 0x1d9a,
-    0x1e95, 0x1e9d,
-    0x1eff, 0x1f07,
-    0x1f10, 0x1f15,
-    0x1f20, 0x1f27,
-    0x1f30, 0x1f37,
-    0x1f40, 0x1f45,
-    0x1f50, 0x1f57,
-    0x1f60, 0x1f67,
-    0x1f70, 0x1f7d,
-    0x1f80, 0x1f87,
-    0x1f90, 0x1f97,
-    0x1fa0, 0x1fa7,
-    0x1fb0, 0x1fb4,
-    0x1fb6, 0x1fb7,
-    0x1fc2, 0x1fc4,
-    0x1fc6, 0x1fc7,
-    0x1fd0, 0x1fd3,
-    0x1fd6, 0x1fd7,
-    0x1fe0, 0x1fe7,
-    0x1ff2, 0x1ff4,
-    0x1ff6, 0x1ff7,
-    0x210e, 0x210f,
-    0x213c, 0x213d,
-    0x2146, 0x2149,
-    0x2170, 0x217f,
-    0x24d0, 0x24e9,
-    0x2c30, 0x2c5e,
-    0x2c65, 0x2c66,
-    0x2c73, 0x2c74,
-    0x2c76, 0x2c7b,
-    0x2ce3, 0x2ce4,
-    0x2d00, 0x2d25,
-    0xa72f, 0xa731,
-    0xa771, 0xa778,
-    0xa793, 0xa795,
-    0xab30, 0xab5a,
-    0xab64, 0xab65,
-    0xfb00, 0xfb06,
-    0xfb13, 0xfb17,
-    0xff41, 0xff5a,
-    0x10428, 0x1044f,
-    0x118c0, 0x118df,
-    0x1d41a, 0x1d433,
-    0x1d44e, 0x1d454,
-    0x1d456, 0x1d467,
-    0x1d482, 0x1d49b,
-    0x1d4b6, 0x1d4b9,
-    0x1d4bd, 0x1d4c3,
-    0x1d4c5, 0x1d4cf,
-    0x1d4ea, 0x1d503,
-    0x1d51e, 0x1d537,
-    0x1d552, 0x1d56b,
-    0x1d586, 0x1d59f,
-    0x1d5ba, 0x1d5d3,
-    0x1d5ee, 0x1d607,
-    0x1d622, 0x1d63b,
-    0x1d656, 0x1d66f,
-    0x1d68a, 0x1d6a5,
-    0x1d6c2, 0x1d6da,
-    0x1d6dc, 0x1d6e1,
-    0x1d6fc, 0x1d714,
-    0x1d716, 0x1d71b,
-    0x1d736, 0x1d74e,
-    0x1d750, 0x1d755,
-    0x1d770, 0x1d788,
-    0x1d78a, 0x1d78f,
-    0x1d7aa, 0x1d7c2,
-    0x1d7c4, 0x1d7c9,
-};
-
-} // !namespace
-
-namespace {
-
-const char32_t islowers[] = {
-    0x00b5,
-    0x0101,
-    0x0103,
-    0x0105,
-    0x0107,
-    0x0109,
-    0x010b,
-    0x010d,
-    0x010f,
-    0x0111,
-    0x0113,
-    0x0115,
-    0x0117,
-    0x0119,
-    0x011b,
-    0x011d,
-    0x011f,
-    0x0121,
-    0x0123,
-    0x0125,
-    0x0127,
-    0x0129,
-    0x012b,
-    0x012d,
-    0x012f,
-    0x0131,
-    0x0133,
-    0x0135,
-    0x013a,
-    0x013c,
-    0x013e,
-    0x0140,
-    0x0142,
-    0x0144,
-    0x0146,
-    0x014b,
-    0x014d,
-    0x014f,
-    0x0151,
-    0x0153,
-    0x0155,
-    0x0157,
-    0x0159,
-    0x015b,
-    0x015d,
-    0x015f,
-    0x0161,
-    0x0163,
-    0x0165,
-    0x0167,
-    0x0169,
-    0x016b,
-    0x016d,
-    0x016f,
-    0x0171,
-    0x0173,
-    0x0175,
-    0x0177,
-    0x017a,
-    0x017c,
-    0x0183,
-    0x0185,
-    0x0188,
-    0x0192,
-    0x0195,
-    0x019e,
-    0x01a1,
-    0x01a3,
-    0x01a5,
-    0x01a8,
-    0x01ad,
-    0x01b0,
-    0x01b4,
-    0x01b6,
-    0x01c6,
-    0x01c9,
-    0x01cc,
-    0x01ce,
-    0x01d0,
-    0x01d2,
-    0x01d4,
-    0x01d6,
-    0x01d8,
-    0x01da,
-    0x01df,
-    0x01e1,
-    0x01e3,
-    0x01e5,
-    0x01e7,
-    0x01e9,
-    0x01eb,
-    0x01ed,
-    0x01f3,
-    0x01f5,
-    0x01f9,
-    0x01fb,
-    0x01fd,
-    0x01ff,
-    0x0201,
-    0x0203,
-    0x0205,
-    0x0207,
-    0x0209,
-    0x020b,
-    0x020d,
-    0x020f,
-    0x0211,
-    0x0213,
-    0x0215,
-    0x0217,
-    0x0219,
-    0x021b,
-    0x021d,
-    0x021f,
-    0x0221,
-    0x0223,
-    0x0225,
-    0x0227,
-    0x0229,
-    0x022b,
-    0x022d,
-    0x022f,
-    0x0231,
-    0x023c,
-    0x0242,
-    0x0247,
-    0x0249,
-    0x024b,
-    0x024d,
-    0x0371,
-    0x0373,
-    0x0377,
-    0x0390,
-    0x03d9,
-    0x03db,
-    0x03dd,
-    0x03df,
-    0x03e1,
-    0x03e3,
-    0x03e5,
-    0x03e7,
-    0x03e9,
-    0x03eb,
-    0x03ed,
-    0x03f5,
-    0x03f8,
-    0x0461,
-    0x0463,
-    0x0465,
-    0x0467,
-    0x0469,
-    0x046b,
-    0x046d,
-    0x046f,
-    0x0471,
-    0x0473,
-    0x0475,
-    0x0477,
-    0x0479,
-    0x047b,
-    0x047d,
-    0x047f,
-    0x0481,
-    0x048b,
-    0x048d,
-    0x048f,
-    0x0491,
-    0x0493,
-    0x0495,
-    0x0497,
-    0x0499,
-    0x049b,
-    0x049d,
-    0x049f,
-    0x04a1,
-    0x04a3,
-    0x04a5,
-    0x04a7,
-    0x04a9,
-    0x04ab,
-    0x04ad,
-    0x04af,
-    0x04b1,
-    0x04b3,
-    0x04b5,
-    0x04b7,
-    0x04b9,
-    0x04bb,
-    0x04bd,
-    0x04bf,
-    0x04c2,
-    0x04c4,
-    0x04c6,
-    0x04c8,
-    0x04ca,
-    0x04cc,
-    0x04d1,
-    0x04d3,
-    0x04d5,
-    0x04d7,
-    0x04d9,
-    0x04db,
-    0x04dd,
-    0x04df,
-    0x04e1,
-    0x04e3,
-    0x04e5,
-    0x04e7,
-    0x04e9,
-    0x04eb,
-    0x04ed,
-    0x04ef,
-    0x04f1,
-    0x04f3,
-    0x04f5,
-    0x04f7,
-    0x04f9,
-    0x04fb,
-    0x04fd,
-    0x04ff,
-    0x0501,
-    0x0503,
-    0x0505,
-    0x0507,
-    0x0509,
-    0x050b,
-    0x050d,
-    0x050f,
-    0x0511,
-    0x0513,
-    0x0515,
-    0x0517,
-    0x0519,
-    0x051b,
-    0x051d,
-    0x051f,
-    0x0521,
-    0x0523,
-    0x0525,
-    0x0527,
-    0x0529,
-    0x052b,
-    0x052d,
-    0x052f,
-    0x1e01,
-    0x1e03,
-    0x1e05,
-    0x1e07,
-    0x1e09,
-    0x1e0b,
-    0x1e0d,
-    0x1e0f,
-    0x1e11,
-    0x1e13,
-    0x1e15,
-    0x1e17,
-    0x1e19,
-    0x1e1b,
-    0x1e1d,
-    0x1e1f,
-    0x1e21,
-    0x1e23,
-    0x1e25,
-    0x1e27,
-    0x1e29,
-    0x1e2b,
-    0x1e2d,
-    0x1e2f,
-    0x1e31,
-    0x1e33,
-    0x1e35,
-    0x1e37,
-    0x1e39,
-    0x1e3b,
-    0x1e3d,
-    0x1e3f,
-    0x1e41,
-    0x1e43,
-    0x1e45,
-    0x1e47,
-    0x1e49,
-    0x1e4b,
-    0x1e4d,
-    0x1e4f,
-    0x1e51,
-    0x1e53,
-    0x1e55,
-    0x1e57,
-    0x1e59,
-    0x1e5b,
-    0x1e5d,
-    0x1e5f,
-    0x1e61,
-    0x1e63,
-    0x1e65,
-    0x1e67,
-    0x1e69,
-    0x1e6b,
-    0x1e6d,
-    0x1e6f,
-    0x1e71,
-    0x1e73,
-    0x1e75,
-    0x1e77,
-    0x1e79,
-    0x1e7b,
-    0x1e7d,
-    0x1e7f,
-    0x1e81,
-    0x1e83,
-    0x1e85,
-    0x1e87,
-    0x1e89,
-    0x1e8b,
-    0x1e8d,
-    0x1e8f,
-    0x1e91,
-    0x1e93,
-    0x1e9f,
-    0x1ea1,
-    0x1ea3,
-    0x1ea5,
-    0x1ea7,
-    0x1ea9,
-    0x1eab,
-    0x1ead,
-    0x1eaf,
-    0x1eb1,
-    0x1eb3,
-    0x1eb5,
-    0x1eb7,
-    0x1eb9,
-    0x1ebb,
-    0x1ebd,
-    0x1ebf,
-    0x1ec1,
-    0x1ec3,
-    0x1ec5,
-    0x1ec7,
-    0x1ec9,
-    0x1ecb,
-    0x1ecd,
-    0x1ecf,
-    0x1ed1,
-    0x1ed3,
-    0x1ed5,
-    0x1ed7,
-    0x1ed9,
-    0x1edb,
-    0x1edd,
-    0x1edf,
-    0x1ee1,
-    0x1ee3,
-    0x1ee5,
-    0x1ee7,
-    0x1ee9,
-    0x1eeb,
-    0x1eed,
-    0x1eef,
-    0x1ef1,
-    0x1ef3,
-    0x1ef5,
-    0x1ef7,
-    0x1ef9,
-    0x1efb,
-    0x1efd,
-    0x1fbe,
-    0x210a,
-    0x2113,
-    0x212f,
-    0x2134,
-    0x2139,
-    0x214e,
-    0x2184,
-    0x2c61,
-    0x2c68,
-    0x2c6a,
-    0x2c6c,
-    0x2c71,
-    0x2c81,
-    0x2c83,
-    0x2c85,
-    0x2c87,
-    0x2c89,
-    0x2c8b,
-    0x2c8d,
-    0x2c8f,
-    0x2c91,
-    0x2c93,
-    0x2c95,
-    0x2c97,
-    0x2c99,
-    0x2c9b,
-    0x2c9d,
-    0x2c9f,
-    0x2ca1,
-    0x2ca3,
-    0x2ca5,
-    0x2ca7,
-    0x2ca9,
-    0x2cab,
-    0x2cad,
-    0x2caf,
-    0x2cb1,
-    0x2cb3,
-    0x2cb5,
-    0x2cb7,
-    0x2cb9,
-    0x2cbb,
-    0x2cbd,
-    0x2cbf,
-    0x2cc1,
-    0x2cc3,
-    0x2cc5,
-    0x2cc7,
-    0x2cc9,
-    0x2ccb,
-    0x2ccd,
-    0x2ccf,
-    0x2cd1,
-    0x2cd3,
-    0x2cd5,
-    0x2cd7,
-    0x2cd9,
-    0x2cdb,
-    0x2cdd,
-    0x2cdf,
-    0x2ce1,
-    0x2cec,
-    0x2cee,
-    0x2cf3,
-    0x2d27,
-    0x2d2d,
-    0xa641,
-    0xa643,
-    0xa645,
-    0xa647,
-    0xa649,
-    0xa64b,
-    0xa64d,
-    0xa64f,
-    0xa651,
-    0xa653,
-    0xa655,
-    0xa657,
-    0xa659,
-    0xa65b,
-    0xa65d,
-    0xa65f,
-    0xa661,
-    0xa663,
-    0xa665,
-    0xa667,
-    0xa669,
-    0xa66b,
-    0xa66d,
-    0xa681,
-    0xa683,
-    0xa685,
-    0xa687,
-    0xa689,
-    0xa68b,
-    0xa68d,
-    0xa68f,
-    0xa691,
-    0xa693,
-    0xa695,
-    0xa697,
-    0xa699,
-    0xa69b,
-    0xa723,
-    0xa725,
-    0xa727,
-    0xa729,
-    0xa72b,
-    0xa72d,
-    0xa733,
-    0xa735,
-    0xa737,
-    0xa739,
-    0xa73b,
-    0xa73d,
-    0xa73f,
-    0xa741,
-    0xa743,
-    0xa745,
-    0xa747,
-    0xa749,
-    0xa74b,
-    0xa74d,
-    0xa74f,
-    0xa751,
-    0xa753,
-    0xa755,
-    0xa757,
-    0xa759,
-    0xa75b,
-    0xa75d,
-    0xa75f,
-    0xa761,
-    0xa763,
-    0xa765,
-    0xa767,
-    0xa769,
-    0xa76b,
-    0xa76d,
-    0xa76f,
-    0xa77a,
-    0xa77c,
-    0xa77f,
-    0xa781,
-    0xa783,
-    0xa785,
-    0xa787,
-    0xa78c,
-    0xa78e,
-    0xa791,
-    0xa797,
-    0xa799,
-    0xa79b,
-    0xa79d,
-    0xa79f,
-    0xa7a1,
-    0xa7a3,
-    0xa7a5,
-    0xa7a7,
-    0xa7a9,
-    0xa7fa,
-    0x1d4bb,
-    0x1d7cb,
-};
-
-} // !namespace
-
-bool islower(char32_t c) noexcept
-{
-   const char32_t *p;
-
-   p = rbsearch(c, islowerr, nelem (islowerr)/2, 2);
-
-   if (p && c >= p[0] && c <= p[1])
-       return true;
-
- p = rbsearch(c, islowers, nelem (islowers), 1);
-
-   if (p && c == p[0])
-       return true;
-
- return false;
-}
-
-namespace {
-
-const char32_t istitler[] = {
-    0x0041, 0x005a,
-    0x00c0, 0x00d6,
-    0x00d8, 0x00de,
-    0x0178, 0x0179,
-    0x0181, 0x0182,
-    0x0186, 0x0187,
-    0x0189, 0x018b,
-    0x018e, 0x0191,
-    0x0193, 0x0194,
-    0x0196, 0x0198,
-    0x019c, 0x019d,
-    0x019f, 0x01a0,
-    0x01a6, 0x01a7,
-    0x01ae, 0x01af,
-    0x01b1, 0x01b3,
-    0x01b7, 0x01b8,
-    0x01f6, 0x01f8,
-    0x023a, 0x023b,
-    0x023d, 0x023e,
-    0x0243, 0x0246,
-    0x0388, 0x038a,
-    0x038e, 0x038f,
-    0x0391, 0x03a1,
-    0x03a3, 0x03ab,
-    0x03f9, 0x03fa,
-    0x03fd, 0x042f,
-    0x04c0, 0x04c1,
-    0x0531, 0x0556,
-    0x10a0, 0x10c5,
-    0x1f08, 0x1f0f,
-    0x1f18, 0x1f1d,
-    0x1f28, 0x1f2f,
-    0x1f38, 0x1f3f,
-    0x1f48, 0x1f4d,
-    0x1f68, 0x1f6f,
-    0x1f88, 0x1f8f,
-    0x1f98, 0x1f9f,
-    0x1fa8, 0x1faf,
-    0x1fb8, 0x1fbc,
-    0x1fc8, 0x1fcc,
-    0x1fd8, 0x1fdb,
-    0x1fe8, 0x1fec,
-    0x1ff8, 0x1ffc,
-    0x2160, 0x216f,
-    0x24b6, 0x24cf,
-    0x2c00, 0x2c2e,
-    0x2c62, 0x2c64,
-    0x2c6d, 0x2c70,
-    0x2c7e, 0x2c80,
-    0xa77d, 0xa77e,
-    0xa7aa, 0xa7ad,
-    0xa7b0, 0xa7b1,
-    0xff21, 0xff3a,
-    0x10400, 0x10427,
-    0x118a0, 0x118bf,
-};
-
-} // !namespace
-
-namespace {
-
-const char32_t istitles[] = {
-    0x0100,
-    0x0102,
-    0x0104,
-    0x0106,
-    0x0108,
-    0x010a,
-    0x010c,
-    0x010e,
-    0x0110,
-    0x0112,
-    0x0114,
-    0x0116,
-    0x0118,
-    0x011a,
-    0x011c,
-    0x011e,
-    0x0120,
-    0x0122,
-    0x0124,
-    0x0126,
-    0x0128,
-    0x012a,
-    0x012c,
-    0x012e,
-    0x0132,
-    0x0134,
-    0x0136,
-    0x0139,
-    0x013b,
-    0x013d,
-    0x013f,
-    0x0141,
-    0x0143,
-    0x0145,
-    0x0147,
-    0x014a,
-    0x014c,
-    0x014e,
-    0x0150,
-    0x0152,
-    0x0154,
-    0x0156,
-    0x0158,
-    0x015a,
-    0x015c,
-    0x015e,
-    0x0160,
-    0x0162,
-    0x0164,
-    0x0166,
-    0x0168,
-    0x016a,
-    0x016c,
-    0x016e,
-    0x0170,
-    0x0172,
-    0x0174,
-    0x0176,
-    0x017b,
-    0x017d,
-    0x0184,
-    0x01a2,
-    0x01a4,
-    0x01a9,
-    0x01ac,
-    0x01b5,
-    0x01bc,
-    0x01c5,
-    0x01c8,
-    0x01cb,
-    0x01cd,
-    0x01cf,
-    0x01d1,
-    0x01d3,
-    0x01d5,
-    0x01d7,
-    0x01d9,
-    0x01db,
-    0x01de,
-    0x01e0,
-    0x01e2,
-    0x01e4,
-    0x01e6,
-    0x01e8,
-    0x01ea,
-    0x01ec,
-    0x01ee,
-    0x01f2,
-    0x01f4,
-    0x01fa,
-    0x01fc,
-    0x01fe,
-    0x0200,
-    0x0202,
-    0x0204,
-    0x0206,
-    0x0208,
-    0x020a,
-    0x020c,
-    0x020e,
-    0x0210,
-    0x0212,
-    0x0214,
-    0x0216,
-    0x0218,
-    0x021a,
-    0x021c,
-    0x021e,
-    0x0220,
-    0x0222,
-    0x0224,
-    0x0226,
-    0x0228,
-    0x022a,
-    0x022c,
-    0x022e,
-    0x0230,
-    0x0232,
-    0x0241,
-    0x0248,
-    0x024a,
-    0x024c,
-    0x024e,
-    0x0370,
-    0x0372,
-    0x0376,
-    0x037f,
-    0x0386,
-    0x038c,
-    0x03cf,
-    0x03d8,
-    0x03da,
-    0x03dc,
-    0x03de,
-    0x03e0,
-    0x03e2,
-    0x03e4,
-    0x03e6,
-    0x03e8,
-    0x03ea,
-    0x03ec,
-    0x03ee,
-    0x03f7,
-    0x0460,
-    0x0462,
-    0x0464,
-    0x0466,
-    0x0468,
-    0x046a,
-    0x046c,
-    0x046e,
-    0x0470,
-    0x0472,
-    0x0474,
-    0x0476,
-    0x0478,
-    0x047a,
-    0x047c,
-    0x047e,
-    0x0480,
-    0x048a,
-    0x048c,
-    0x048e,
-    0x0490,
-    0x0492,
-    0x0494,
-    0x0496,
-    0x0498,
-    0x049a,
-    0x049c,
-    0x049e,
-    0x04a0,
-    0x04a2,
-    0x04a4,
-    0x04a6,
-    0x04a8,
-    0x04aa,
-    0x04ac,
-    0x04ae,
-    0x04b0,
-    0x04b2,
-    0x04b4,
-    0x04b6,
-    0x04b8,
-    0x04ba,
-    0x04bc,
-    0x04be,
-    0x04c3,
-    0x04c5,
-    0x04c7,
-    0x04c9,
-    0x04cb,
-    0x04cd,
-    0x04d0,
-    0x04d2,
-    0x04d4,
-    0x04d6,
-    0x04d8,
-    0x04da,
-    0x04dc,
-    0x04de,
-    0x04e0,
-    0x04e2,
-    0x04e4,
-    0x04e6,
-    0x04e8,
-    0x04ea,
-    0x04ec,
-    0x04ee,
-    0x04f0,
-    0x04f2,
-    0x04f4,
-    0x04f6,
-    0x04f8,
-    0x04fa,
-    0x04fc,
-    0x04fe,
-    0x0500,
-    0x0502,
-    0x0504,
-    0x0506,
-    0x0508,
-    0x050a,
-    0x050c,
-    0x050e,
-    0x0510,
-    0x0512,
-    0x0514,
-    0x0516,
-    0x0518,
-    0x051a,
-    0x051c,
-    0x051e,
-    0x0520,
-    0x0522,
-    0x0524,
-    0x0526,
-    0x0528,
-    0x052a,
-    0x052c,
-    0x052e,
-    0x10c7,
-    0x10cd,
-    0x1e00,
-    0x1e02,
-    0x1e04,
-    0x1e06,
-    0x1e08,
-    0x1e0a,
-    0x1e0c,
-    0x1e0e,
-    0x1e10,
-    0x1e12,
-    0x1e14,
-    0x1e16,
-    0x1e18,
-    0x1e1a,
-    0x1e1c,
-    0x1e1e,
-    0x1e20,
-    0x1e22,
-    0x1e24,
-    0x1e26,
-    0x1e28,
-    0x1e2a,
-    0x1e2c,
-    0x1e2e,
-    0x1e30,
-    0x1e32,
-    0x1e34,
-    0x1e36,
-    0x1e38,
-    0x1e3a,
-    0x1e3c,
-    0x1e3e,
-    0x1e40,
-    0x1e42,
-    0x1e44,
-    0x1e46,
-    0x1e48,
-    0x1e4a,
-    0x1e4c,
-    0x1e4e,
-    0x1e50,
-    0x1e52,
-    0x1e54,
-    0x1e56,
-    0x1e58,
-    0x1e5a,
-    0x1e5c,
-    0x1e5e,
-    0x1e60,
-    0x1e62,
-    0x1e64,
-    0x1e66,
-    0x1e68,
-    0x1e6a,
-    0x1e6c,
-    0x1e6e,
-    0x1e70,
-    0x1e72,
-    0x1e74,
-    0x1e76,
-    0x1e78,
-    0x1e7a,
-    0x1e7c,
-    0x1e7e,
-    0x1e80,
-    0x1e82,
-    0x1e84,
-    0x1e86,
-    0x1e88,
-    0x1e8a,
-    0x1e8c,
-    0x1e8e,
-    0x1e90,
-    0x1e92,
-    0x1e94,
-    0x1ea0,
-    0x1ea2,
-    0x1ea4,
-    0x1ea6,
-    0x1ea8,
-    0x1eaa,
-    0x1eac,
-    0x1eae,
-    0x1eb0,
-    0x1eb2,
-    0x1eb4,
-    0x1eb6,
-    0x1eb8,
-    0x1eba,
-    0x1ebc,
-    0x1ebe,
-    0x1ec0,
-    0x1ec2,
-    0x1ec4,
-    0x1ec6,
-    0x1ec8,
-    0x1eca,
-    0x1ecc,
-    0x1ece,
-    0x1ed0,
-    0x1ed2,
-    0x1ed4,
-    0x1ed6,
-    0x1ed8,
-    0x1eda,
-    0x1edc,
-    0x1ede,
-    0x1ee0,
-    0x1ee2,
-    0x1ee4,
-    0x1ee6,
-    0x1ee8,
-    0x1eea,
-    0x1eec,
-    0x1eee,
-    0x1ef0,
-    0x1ef2,
-    0x1ef4,
-    0x1ef6,
-    0x1ef8,
-    0x1efa,
-    0x1efc,
-    0x1efe,
-    0x1f59,
-    0x1f5b,
-    0x1f5d,
-    0x1f5f,
-    0x2132,
-    0x2183,
-    0x2c60,
-    0x2c67,
-    0x2c69,
-    0x2c6b,
-    0x2c72,
-    0x2c75,
-    0x2c82,
-    0x2c84,
-    0x2c86,
-    0x2c88,
-    0x2c8a,
-    0x2c8c,
-    0x2c8e,
-    0x2c90,
-    0x2c92,
-    0x2c94,
-    0x2c96,
-    0x2c98,
-    0x2c9a,
-    0x2c9c,
-    0x2c9e,
-    0x2ca0,
-    0x2ca2,
-    0x2ca4,
-    0x2ca6,
-    0x2ca8,
-    0x2caa,
-    0x2cac,
-    0x2cae,
-    0x2cb0,
-    0x2cb2,
-    0x2cb4,
-    0x2cb6,
-    0x2cb8,
-    0x2cba,
-    0x2cbc,
-    0x2cbe,
-    0x2cc0,
-    0x2cc2,
-    0x2cc4,
-    0x2cc6,
-    0x2cc8,
-    0x2cca,
-    0x2ccc,
-    0x2cce,
-    0x2cd0,
-    0x2cd2,
-    0x2cd4,
-    0x2cd6,
-    0x2cd8,
-    0x2cda,
-    0x2cdc,
-    0x2cde,
-    0x2ce0,
-    0x2ce2,
-    0x2ceb,
-    0x2ced,
-    0x2cf2,
-    0xa640,
-    0xa642,
-    0xa644,
-    0xa646,
-    0xa648,
-    0xa64a,
-    0xa64c,
-    0xa64e,
-    0xa650,
-    0xa652,
-    0xa654,
-    0xa656,
-    0xa658,
-    0xa65a,
-    0xa65c,
-    0xa65e,
-    0xa660,
-    0xa662,
-    0xa664,
-    0xa666,
-    0xa668,
-    0xa66a,
-    0xa66c,
-    0xa680,
-    0xa682,
-    0xa684,
-    0xa686,
-    0xa688,
-    0xa68a,
-    0xa68c,
-    0xa68e,
-    0xa690,
-    0xa692,
-    0xa694,
-    0xa696,
-    0xa698,
-    0xa69a,
-    0xa722,
-    0xa724,
-    0xa726,
-    0xa728,
-    0xa72a,
-    0xa72c,
-    0xa72e,
-    0xa732,
-    0xa734,
-    0xa736,
-    0xa738,
-    0xa73a,
-    0xa73c,
-    0xa73e,
-    0xa740,
-    0xa742,
-    0xa744,
-    0xa746,
-    0xa748,
-    0xa74a,
-    0xa74c,
-    0xa74e,
-    0xa750,
-    0xa752,
-    0xa754,
-    0xa756,
-    0xa758,
-    0xa75a,
-    0xa75c,
-    0xa75e,
-    0xa760,
-    0xa762,
-    0xa764,
-    0xa766,
-    0xa768,
-    0xa76a,
-    0xa76c,
-    0xa76e,
-    0xa779,
-    0xa77b,
-    0xa780,
-    0xa782,
-    0xa784,
-    0xa786,
-    0xa78b,
-    0xa78d,
-    0xa790,
-    0xa792,
-    0xa796,
-    0xa798,
-    0xa79a,
-    0xa79c,
-    0xa79e,
-    0xa7a0,
-    0xa7a2,
-    0xa7a4,
-    0xa7a6,
-    0xa7a8,
-};
-
-} // !namespace
-
-bool istitle(char32_t c) noexcept
-{
-   const char32_t *p;
-
-   p = rbsearch(c, istitler, nelem (istitler)/2, 2);
-
-   if (p && c >= p[0] && c <= p[1])
-       return true;
-
- p = rbsearch(c, istitles, nelem (istitles), 1);
-
-   if (p && c == p[0])
-       return true;
-
- return false;
-}
-
-namespace {
-
-const char32_t toupperr[] = {
-    0x0061, 0x007a, 1048544,
-    0x00e0, 0x00f6, 1048544,
-    0x00f8, 0x00fe, 1048544,
-    0x023f, 0x0240, 1059391,
-    0x0256, 0x0257, 1048371,
-    0x028a, 0x028b, 1048359,
-    0x037b, 0x037d, 1048706,
-    0x03ad, 0x03af, 1048539,
-    0x03b1, 0x03c1, 1048544,
-    0x03c3, 0x03cb, 1048544,
-    0x03cd, 0x03ce, 1048513,
-    0x0430, 0x044f, 1048544,
-    0x0450, 0x045f, 1048496,
-    0x0561, 0x0586, 1048528,
-    0x1f00, 0x1f07, 1048584,
-    0x1f10, 0x1f15, 1048584,
-    0x1f20, 0x1f27, 1048584,
-    0x1f30, 0x1f37, 1048584,
-    0x1f40, 0x1f45, 1048584,
-    0x1f60, 0x1f67, 1048584,
-    0x1f70, 0x1f71, 1048650,
-    0x1f72, 0x1f75, 1048662,
-    0x1f76, 0x1f77, 1048676,
-    0x1f78, 0x1f79, 1048704,
-    0x1f7a, 0x1f7b, 1048688,
-    0x1f7c, 0x1f7d, 1048702,
-    0x1f80, 0x1f87, 1048584,
-    0x1f90, 0x1f97, 1048584,
-    0x1fa0, 0x1fa7, 1048584,
-    0x1fb0, 0x1fb1, 1048584,
-    0x1fd0, 0x1fd1, 1048584,
-    0x1fe0, 0x1fe1, 1048584,
-    0x2170, 0x217f, 1048560,
-    0x24d0, 0x24e9, 1048550,
-    0x2c30, 0x2c5e, 1048528,
-    0x2d00, 0x2d25, 1041312,
-    0xff41, 0xff5a, 1048544,
-    0x10428, 0x1044f, 1048536,
-    0x118c0, 0x118df, 1048544,
-};
-
-} // !namespace
-
-namespace {
-
-const char32_t touppers[] = {
-    0x00b5, 1049319,
-    0x00ff, 1048697,
-    0x0101, 1048575,
-    0x0103, 1048575,
-    0x0105, 1048575,
-    0x0107, 1048575,
-    0x0109, 1048575,
-    0x010b, 1048575,
-    0x010d, 1048575,
-    0x010f, 1048575,
-    0x0111, 1048575,
-    0x0113, 1048575,
-    0x0115, 1048575,
-    0x0117, 1048575,
-    0x0119, 1048575,
-    0x011b, 1048575,
-    0x011d, 1048575,
-    0x011f, 1048575,
-    0x0121, 1048575,
-    0x0123, 1048575,
-    0x0125, 1048575,
-    0x0127, 1048575,
-    0x0129, 1048575,
-    0x012b, 1048575,
-    0x012d, 1048575,
-    0x012f, 1048575,
-    0x0131, 1048344,
-    0x0133, 1048575,
-    0x0135, 1048575,
-    0x0137, 1048575,
-    0x013a, 1048575,
-    0x013c, 1048575,
-    0x013e, 1048575,
-    0x0140, 1048575,
-    0x0142, 1048575,
-    0x0144, 1048575,
-    0x0146, 1048575,
-    0x0148, 1048575,
-    0x014b, 1048575,
-    0x014d, 1048575,
-    0x014f, 1048575,
-    0x0151, 1048575,
-    0x0153, 1048575,
-    0x0155, 1048575,
-    0x0157, 1048575,
-    0x0159, 1048575,
-    0x015b, 1048575,
-    0x015d, 1048575,
-    0x015f, 1048575,
-    0x0161, 1048575,
-    0x0163, 1048575,
-    0x0165, 1048575,
-    0x0167, 1048575,
-    0x0169, 1048575,
-    0x016b, 1048575,
-    0x016d, 1048575,
-    0x016f, 1048575,
-    0x0171, 1048575,
-    0x0173, 1048575,
-    0x0175, 1048575,
-    0x0177, 1048575,
-    0x017a, 1048575,
-    0x017c, 1048575,
-    0x017e, 1048575,
-    0x017f, 1048276,
-    0x0180, 1048771,
-    0x0183, 1048575,
-    0x0185, 1048575,
-    0x0188, 1048575,
-    0x018c, 1048575,
-    0x0192, 1048575,
-    0x0195, 1048673,
-    0x0199, 1048575,
-    0x019a, 1048739,
-    0x019e, 1048706,
-    0x01a1, 1048575,
-    0x01a3, 1048575,
-    0x01a5, 1048575,
-    0x01a8, 1048575,
-    0x01ad, 1048575,
-    0x01b0, 1048575,
-    0x01b4, 1048575,
-    0x01b6, 1048575,
-    0x01b9, 1048575,
-    0x01bd, 1048575,
-    0x01bf, 1048632,
-    0x01c5, 1048575,
-    0x01c6, 1048574,
-    0x01c8, 1048575,
-    0x01c9, 1048574,
-    0x01cb, 1048575,
-    0x01cc, 1048574,
-    0x01ce, 1048575,
-    0x01d0, 1048575,
-    0x01d2, 1048575,
-    0x01d4, 1048575,
-    0x01d6, 1048575,
-    0x01d8, 1048575,
-    0x01da, 1048575,
-    0x01dc, 1048575,
-    0x01dd, 1048497,
-    0x01df, 1048575,
-    0x01e1, 1048575,
-    0x01e3, 1048575,
-    0x01e5, 1048575,
-    0x01e7, 1048575,
-    0x01e9, 1048575,
-    0x01eb, 1048575,
-    0x01ed, 1048575,
-    0x01ef, 1048575,
-    0x01f2, 1048575,
-    0x01f3, 1048574,
-    0x01f5, 1048575,
-    0x01f9, 1048575,
-    0x01fb, 1048575,
-    0x01fd, 1048575,
-    0x01ff, 1048575,
-    0x0201, 1048575,
-    0x0203, 1048575,
-    0x0205, 1048575,
-    0x0207, 1048575,
-    0x0209, 1048575,
-    0x020b, 1048575,
-    0x020d, 1048575,
-    0x020f, 1048575,
-    0x0211, 1048575,
-    0x0213, 1048575,
-    0x0215, 1048575,
-    0x0217, 1048575,
-    0x0219, 1048575,
-    0x021b, 1048575,
-    0x021d, 1048575,
-    0x021f, 1048575,
-    0x0223, 1048575,
-    0x0225, 1048575,
-    0x0227, 1048575,
-    0x0229, 1048575,
-    0x022b, 1048575,
-    0x022d, 1048575,
-    0x022f, 1048575,
-    0x0231, 1048575,
-    0x0233, 1048575,
-    0x023c, 1048575,
-    0x0242, 1048575,
-    0x0247, 1048575,
-    0x0249, 1048575,
-    0x024b, 1048575,
-    0x024d, 1048575,
-    0x024f, 1048575,
-    0x0250, 1059359,
-    0x0251, 1059356,
-    0x0252, 1059358,
-    0x0253, 1048366,
-    0x0254, 1048370,
-    0x0259, 1048374,
-    0x025b, 1048373,
-    0x025c, 1090895,
-    0x0260, 1048371,
-    0x0261, 1090891,
-    0x0263, 1048369,
-    0x0265, 1090856,
-    0x0266, 1090884,
-    0x0268, 1048367,
-    0x0269, 1048365,
-    0x026b, 1059319,
-    0x026c, 1090881,
-    0x026f, 1048365,
-    0x0271, 1059325,
-    0x0272, 1048363,
-    0x0275, 1048362,
-    0x027d, 1059303,
-    0x0280, 1048358,
-    0x0283, 1048358,
-    0x0287, 1090858,
-    0x0288, 1048358,
-    0x0289, 1048507,
-    0x028c, 1048505,
-    0x0292, 1048357,
-    0x029e, 1090834,
-    0x0345, 1048660,
-    0x0371, 1048575,
-    0x0373, 1048575,
-    0x0377, 1048575,
-    0x03ac, 1048538,
-    0x03c2, 1048545,
-    0x03cc, 1048512,
-    0x03d0, 1048514,
-    0x03d1, 1048519,
-    0x03d5, 1048529,
-    0x03d6, 1048522,
-    0x03d7, 1048568,
-    0x03d9, 1048575,
-    0x03db, 1048575,
-    0x03dd, 1048575,
-    0x03df, 1048575,
-    0x03e1, 1048575,
-    0x03e3, 1048575,
-    0x03e5, 1048575,
-    0x03e7, 1048575,
-    0x03e9, 1048575,
-    0x03eb, 1048575,
-    0x03ed, 1048575,
-    0x03ef, 1048575,
-    0x03f0, 1048490,
-    0x03f1, 1048496,
-    0x03f2, 1048583,
-    0x03f3, 1048460,
-    0x03f5, 1048480,
-    0x03f8, 1048575,
-    0x03fb, 1048575,
-    0x0461, 1048575,
-    0x0463, 1048575,
-    0x0465, 1048575,
-    0x0467, 1048575,
-    0x0469, 1048575,
-    0x046b, 1048575,
-    0x046d, 1048575,
-    0x046f, 1048575,
-    0x0471, 1048575,
-    0x0473, 1048575,
-    0x0475, 1048575,
-    0x0477, 1048575,
-    0x0479, 1048575,
-    0x047b, 1048575,
-    0x047d, 1048575,
-    0x047f, 1048575,
-    0x0481, 1048575,
-    0x048b, 1048575,
-    0x048d, 1048575,
-    0x048f, 1048575,
-    0x0491, 1048575,
-    0x0493, 1048575,
-    0x0495, 1048575,
-    0x0497, 1048575,
-    0x0499, 1048575,
-    0x049b, 1048575,
-    0x049d, 1048575,
-    0x049f, 1048575,
-    0x04a1, 1048575,
-    0x04a3, 1048575,
-    0x04a5, 1048575,
-    0x04a7, 1048575,
-    0x04a9, 1048575,
-    0x04ab, 1048575,
-    0x04ad, 1048575,
-    0x04af, 1048575,
-    0x04b1, 1048575,
-    0x04b3, 1048575,
-    0x04b5, 1048575,
-    0x04b7, 1048575,
-    0x04b9, 1048575,
-    0x04bb, 1048575,
-    0x04bd, 1048575,
-    0x04bf, 1048575,
-    0x04c2, 1048575,
-    0x04c4, 1048575,
-    0x04c6, 1048575,
-    0x04c8, 1048575,
-    0x04ca, 1048575,
-    0x04cc, 1048575,
-    0x04ce, 1048575,
-    0x04cf, 1048561,
-    0x04d1, 1048575,
-    0x04d3, 1048575,
-    0x04d5, 1048575,
-    0x04d7, 1048575,
-    0x04d9, 1048575,
-    0x04db, 1048575,
-    0x04dd, 1048575,
-    0x04df, 1048575,
-    0x04e1, 1048575,
-    0x04e3, 1048575,
-    0x04e5, 1048575,
-    0x04e7, 1048575,
-    0x04e9, 1048575,
-    0x04eb, 1048575,
-    0x04ed, 1048575,
-    0x04ef, 1048575,
-    0x04f1, 1048575,
-    0x04f3, 1048575,
-    0x04f5, 1048575,
-    0x04f7, 1048575,
-    0x04f9, 1048575,
-    0x04fb, 1048575,
-    0x04fd, 1048575,
-    0x04ff, 1048575,
-    0x0501, 1048575,
-    0x0503, 1048575,
-    0x0505, 1048575,
-    0x0507, 1048575,
-    0x0509, 1048575,
-    0x050b, 1048575,
-    0x050d, 1048575,
-    0x050f, 1048575,
-    0x0511, 1048575,
-    0x0513, 1048575,
-    0x0515, 1048575,
-    0x0517, 1048575,
-    0x0519, 1048575,
-    0x051b, 1048575,
-    0x051d, 1048575,
-    0x051f, 1048575,
-    0x0521, 1048575,
-    0x0523, 1048575,
-    0x0525, 1048575,
-    0x0527, 1048575,
-    0x0529, 1048575,
-    0x052b, 1048575,
-    0x052d, 1048575,
-    0x052f, 1048575,
-    0x1d79, 1083908,
-    0x1d7d, 1052390,
-    0x1e01, 1048575,
-    0x1e03, 1048575,
-    0x1e05, 1048575,
-    0x1e07, 1048575,
-    0x1e09, 1048575,
-    0x1e0b, 1048575,
-    0x1e0d, 1048575,
-    0x1e0f, 1048575,
-    0x1e11, 1048575,
-    0x1e13, 1048575,
-    0x1e15, 1048575,
-    0x1e17, 1048575,
-    0x1e19, 1048575,
-    0x1e1b, 1048575,
-    0x1e1d, 1048575,
-    0x1e1f, 1048575,
-    0x1e21, 1048575,
-    0x1e23, 1048575,
-    0x1e25, 1048575,
-    0x1e27, 1048575,
-    0x1e29, 1048575,
-    0x1e2b, 1048575,
-    0x1e2d, 1048575,
-    0x1e2f, 1048575,
-    0x1e31, 1048575,
-    0x1e33, 1048575,
-    0x1e35, 1048575,
-    0x1e37, 1048575,
-    0x1e39, 1048575,
-    0x1e3b, 1048575,
-    0x1e3d, 1048575,
-    0x1e3f, 1048575,
-    0x1e41, 1048575,
-    0x1e43, 1048575,
-    0x1e45, 1048575,
-    0x1e47, 1048575,
-    0x1e49, 1048575,
-    0x1e4b, 1048575,
-    0x1e4d, 1048575,
-    0x1e4f, 1048575,
-    0x1e51, 1048575,
-    0x1e53, 1048575,
-    0x1e55, 1048575,
-    0x1e57, 1048575,
-    0x1e59, 1048575,
-    0x1e5b, 1048575,
-    0x1e5d, 1048575,
-    0x1e5f, 1048575,
-    0x1e61, 1048575,
-    0x1e63, 1048575,
-    0x1e65, 1048575,
-    0x1e67, 1048575,
-    0x1e69, 1048575,
-    0x1e6b, 1048575,
-    0x1e6d, 1048575,
-    0x1e6f, 1048575,
-    0x1e71, 1048575,
-    0x1e73, 1048575,
-    0x1e75, 1048575,
-    0x1e77, 1048575,
-    0x1e79, 1048575,
-    0x1e7b, 1048575,
-    0x1e7d, 1048575,
-    0x1e7f, 1048575,
-    0x1e81, 1048575,
-    0x1e83, 1048575,
-    0x1e85, 1048575,
-    0x1e87, 1048575,
-    0x1e89, 1048575,
-    0x1e8b, 1048575,
-    0x1e8d, 1048575,
-    0x1e8f, 1048575,
-    0x1e91, 1048575,
-    0x1e93, 1048575,
-    0x1e95, 1048575,
-    0x1e9b, 1048517,
-    0x1ea1, 1048575,
-    0x1ea3, 1048575,
-    0x1ea5, 1048575,
-    0x1ea7, 1048575,
-    0x1ea9, 1048575,
-    0x1eab, 1048575,
-    0x1ead, 1048575,
-    0x1eaf, 1048575,
-    0x1eb1, 1048575,
-    0x1eb3, 1048575,
-    0x1eb5, 1048575,
-    0x1eb7, 1048575,
-    0x1eb9, 1048575,
-    0x1ebb, 1048575,
-    0x1ebd, 1048575,
-    0x1ebf, 1048575,
-    0x1ec1, 1048575,
-    0x1ec3, 1048575,
-    0x1ec5, 1048575,
-    0x1ec7, 1048575,
-    0x1ec9, 1048575,
-    0x1ecb, 1048575,
-    0x1ecd, 1048575,
-    0x1ecf, 1048575,
-    0x1ed1, 1048575,
-    0x1ed3, 1048575,
-    0x1ed5, 1048575,
-    0x1ed7, 1048575,
-    0x1ed9, 1048575,
-    0x1edb, 1048575,
-    0x1edd, 1048575,
-    0x1edf, 1048575,
-    0x1ee1, 1048575,
-    0x1ee3, 1048575,
-    0x1ee5, 1048575,
-    0x1ee7, 1048575,
-    0x1ee9, 1048575,
-    0x1eeb, 1048575,
-    0x1eed, 1048575,
-    0x1eef, 1048575,
-    0x1ef1, 1048575,
-    0x1ef3, 1048575,
-    0x1ef5, 1048575,
-    0x1ef7, 1048575,
-    0x1ef9, 1048575,
-    0x1efb, 1048575,
-    0x1efd, 1048575,
-    0x1eff, 1048575,
-    0x1f51, 1048584,
-    0x1f53, 1048584,
-    0x1f55, 1048584,
-    0x1f57, 1048584,
-    0x1fb3, 1048585,
-    0x1fbe, 1041371,
-    0x1fc3, 1048585,
-    0x1fe5, 1048583,
-    0x1ff3, 1048585,
-    0x214e, 1048548,
-    0x2184, 1048575,
-    0x2c61, 1048575,
-    0x2c65, 1037781,
-    0x2c66, 1037784,
-    0x2c68, 1048575,
-    0x2c6a, 1048575,
-    0x2c6c, 1048575,
-    0x2c73, 1048575,
-    0x2c76, 1048575,
-    0x2c81, 1048575,
-    0x2c83, 1048575,
-    0x2c85, 1048575,
-    0x2c87, 1048575,
-    0x2c89, 1048575,
-    0x2c8b, 1048575,
-    0x2c8d, 1048575,
-    0x2c8f, 1048575,
-    0x2c91, 1048575,
-    0x2c93, 1048575,
-    0x2c95, 1048575,
-    0x2c97, 1048575,
-    0x2c99, 1048575,
-    0x2c9b, 1048575,
-    0x2c9d, 1048575,
-    0x2c9f, 1048575,
-    0x2ca1, 1048575,
-    0x2ca3, 1048575,
-    0x2ca5, 1048575,
-    0x2ca7, 1048575,
-    0x2ca9, 1048575,
-    0x2cab, 1048575,
-    0x2cad, 1048575,
-    0x2caf, 1048575,
-    0x2cb1, 1048575,
-    0x2cb3, 1048575,
-    0x2cb5, 1048575,
-    0x2cb7, 1048575,
-    0x2cb9, 1048575,
-    0x2cbb, 1048575,
-    0x2cbd, 1048575,
-    0x2cbf, 1048575,
-    0x2cc1, 1048575,
-    0x2cc3, 1048575,
-    0x2cc5, 1048575,
-    0x2cc7, 1048575,
-    0x2cc9, 1048575,
-    0x2ccb, 1048575,
-    0x2ccd, 1048575,
-    0x2ccf, 1048575,
-    0x2cd1, 1048575,
-    0x2cd3, 1048575,
-    0x2cd5, 1048575,
-    0x2cd7, 1048575,
-    0x2cd9, 1048575,
-    0x2cdb, 1048575,
-    0x2cdd, 1048575,
-    0x2cdf, 1048575,
-    0x2ce1, 1048575,
-    0x2ce3, 1048575,
-    0x2cec, 1048575,
-    0x2cee, 1048575,
-    0x2cf3, 1048575,
-    0x2d27, 1041312,
-    0x2d2d, 1041312,
-    0xa641, 1048575,
-    0xa643, 1048575,
-    0xa645, 1048575,
-    0xa647, 1048575,
-    0xa649, 1048575,
-    0xa64b, 1048575,
-    0xa64d, 1048575,
-    0xa64f, 1048575,
-    0xa651, 1048575,
-    0xa653, 1048575,
-    0xa655, 1048575,
-    0xa657, 1048575,
-    0xa659, 1048575,
-    0xa65b, 1048575,
-    0xa65d, 1048575,
-    0xa65f, 1048575,
-    0xa661, 1048575,
-    0xa663, 1048575,
-    0xa665, 1048575,
-    0xa667, 1048575,
-    0xa669, 1048575,
-    0xa66b, 1048575,
-    0xa66d, 1048575,
-    0xa681, 1048575,
-    0xa683, 1048575,
-    0xa685, 1048575,
-    0xa687, 1048575,
-    0xa689, 1048575,
-    0xa68b, 1048575,
-    0xa68d, 1048575,
-    0xa68f, 1048575,
-    0xa691, 1048575,
-    0xa693, 1048575,
-    0xa695, 1048575,
-    0xa697, 1048575,
-    0xa699, 1048575,
-    0xa69b, 1048575,
-    0xa723, 1048575,
-    0xa725, 1048575,
-    0xa727, 1048575,
-    0xa729, 1048575,
-    0xa72b, 1048575,
-    0xa72d, 1048575,
-    0xa72f, 1048575,
-    0xa733, 1048575,
-    0xa735, 1048575,
-    0xa737, 1048575,
-    0xa739, 1048575,
-    0xa73b, 1048575,
-    0xa73d, 1048575,
-    0xa73f, 1048575,
-    0xa741, 1048575,
-    0xa743, 1048575,
-    0xa745, 1048575,
-    0xa747, 1048575,
-    0xa749, 1048575,
-    0xa74b, 1048575,
-    0xa74d, 1048575,
-    0xa74f, 1048575,
-    0xa751, 1048575,
-    0xa753, 1048575,
-    0xa755, 1048575,
-    0xa757, 1048575,
-    0xa759, 1048575,
-    0xa75b, 1048575,
-    0xa75d, 1048575,
-    0xa75f, 1048575,
-    0xa761, 1048575,
-    0xa763, 1048575,
-    0xa765, 1048575,
-    0xa767, 1048575,
-    0xa769, 1048575,
-    0xa76b, 1048575,
-    0xa76d, 1048575,
-    0xa76f, 1048575,
-    0xa77a, 1048575,
-    0xa77c, 1048575,
-    0xa77f, 1048575,
-    0xa781, 1048575,
-    0xa783, 1048575,
-    0xa785, 1048575,
-    0xa787, 1048575,
-    0xa78c, 1048575,
-    0xa791, 1048575,
-    0xa793, 1048575,
-    0xa797, 1048575,
-    0xa799, 1048575,
-    0xa79b, 1048575,
-    0xa79d, 1048575,
-    0xa79f, 1048575,
-    0xa7a1, 1048575,
-    0xa7a3, 1048575,
-    0xa7a5, 1048575,
-    0xa7a7, 1048575,
-    0xa7a9, 1048575,
-};
-
-} // !namespace
-
-char32_t toupper(char32_t c) noexcept
-{
-   const char32_t *p;
-
-   p = rbsearch(c, toupperr, nelem (toupperr)/3, 3);
-
-   if (p && c >= p[0] && c <= p[1])
-       return c + p[2] - 1048576;
-
- p = rbsearch(c, touppers, nelem (touppers)/2, 2);
-
-   if (p && c == p[0])
-       return c + p[1] - 1048576;
-
-   return c;
-}
-
-namespace {
-
-const char32_t tolowerr[] = {
-    0x0041, 0x005a, 1048608,
-    0x00c0, 0x00d6, 1048608,
-    0x00d8, 0x00de, 1048608,
-    0x0189, 0x018a, 1048781,
-    0x01b1, 0x01b2, 1048793,
-    0x0388, 0x038a, 1048613,
-    0x038e, 0x038f, 1048639,
-    0x0391, 0x03a1, 1048608,
-    0x03a3, 0x03ab, 1048608,
-    0x03fd, 0x03ff, 1048446,
-    0x0400, 0x040f, 1048656,
-    0x0410, 0x042f, 1048608,
-    0x0531, 0x0556, 1048624,
-    0x10a0, 0x10c5, 1055840,
-    0x1f08, 0x1f0f, 1048568,
-    0x1f18, 0x1f1d, 1048568,
-    0x1f28, 0x1f2f, 1048568,
-    0x1f38, 0x1f3f, 1048568,
-    0x1f48, 0x1f4d, 1048568,
-    0x1f68, 0x1f6f, 1048568,
-    0x1f88, 0x1f8f, 1048568,
-    0x1f98, 0x1f9f, 1048568,
-    0x1fa8, 0x1faf, 1048568,
-    0x1fb8, 0x1fb9, 1048568,
-    0x1fba, 0x1fbb, 1048502,
-    0x1fc8, 0x1fcb, 1048490,
-    0x1fd8, 0x1fd9, 1048568,
-    0x1fda, 0x1fdb, 1048476,
-    0x1fe8, 0x1fe9, 1048568,
-    0x1fea, 0x1feb, 1048464,
-    0x1ff8, 0x1ff9, 1048448,
-    0x1ffa, 0x1ffb, 1048450,
-    0x2160, 0x216f, 1048592,
-    0x24b6, 0x24cf, 1048602,
-    0x2c00, 0x2c2e, 1048624,
-    0x2c7e, 0x2c7f, 1037761,
-    0xff21, 0xff3a, 1048608,
-    0x10400, 0x10427, 1048616,
-    0x118a0, 0x118bf, 1048608,
-};
-
-} // !namespace
-
-namespace {
-
-const char32_t tolowers[] = {
-    0x0100, 1048577,
-    0x0102, 1048577,
-    0x0104, 1048577,
-    0x0106, 1048577,
-    0x0108, 1048577,
-    0x010a, 1048577,
-    0x010c, 1048577,
-    0x010e, 1048577,
-    0x0110, 1048577,
-    0x0112, 1048577,
-    0x0114, 1048577,
-    0x0116, 1048577,
-    0x0118, 1048577,
-    0x011a, 1048577,
-    0x011c, 1048577,
-    0x011e, 1048577,
-    0x0120, 1048577,
-    0x0122, 1048577,
-    0x0124, 1048577,
-    0x0126, 1048577,
-    0x0128, 1048577,
-    0x012a, 1048577,
-    0x012c, 1048577,
-    0x012e, 1048577,
-    0x0130, 1048377,
-    0x0132, 1048577,
-    0x0134, 1048577,
-    0x0136, 1048577,
-    0x0139, 1048577,
-    0x013b, 1048577,
-    0x013d, 1048577,
-    0x013f, 1048577,
-    0x0141, 1048577,
-    0x0143, 1048577,
-    0x0145, 1048577,
-    0x0147, 1048577,
-    0x014a, 1048577,
-    0x014c, 1048577,
-    0x014e, 1048577,
-    0x0150, 1048577,
-    0x0152, 1048577,
-    0x0154, 1048577,
-    0x0156, 1048577,
-    0x0158, 1048577,
-    0x015a, 1048577,
-    0x015c, 1048577,
-    0x015e, 1048577,
-    0x0160, 1048577,
-    0x0162, 1048577,
-    0x0164, 1048577,
-    0x0166, 1048577,
-    0x0168, 1048577,
-    0x016a, 1048577,
-    0x016c, 1048577,
-    0x016e, 1048577,
-    0x0170, 1048577,
-    0x0172, 1048577,
-    0x0174, 1048577,
-    0x0176, 1048577,
-    0x0178, 1048455,
-    0x0179, 1048577,
-    0x017b, 1048577,
-    0x017d, 1048577,
-    0x0181, 1048786,
-    0x0182, 1048577,
-    0x0184, 1048577,
-    0x0186, 1048782,
-    0x0187, 1048577,
-    0x018b, 1048577,
-    0x018e, 1048655,
-    0x018f, 1048778,
-    0x0190, 1048779,
-    0x0191, 1048577,
-    0x0193, 1048781,
-    0x0194, 1048783,
-    0x0196, 1048787,
-    0x0197, 1048785,
-    0x0198, 1048577,
-    0x019c, 1048787,
-    0x019d, 1048789,
-    0x019f, 1048790,
-    0x01a0, 1048577,
-    0x01a2, 1048577,
-    0x01a4, 1048577,
-    0x01a6, 1048794,
-    0x01a7, 1048577,
-    0x01a9, 1048794,
-    0x01ac, 1048577,
-    0x01ae, 1048794,
-    0x01af, 1048577,
-    0x01b3, 1048577,
-    0x01b5, 1048577,
-    0x01b7, 1048795,
-    0x01b8, 1048577,
-    0x01bc, 1048577,
-    0x01c4, 1048578,
-    0x01c5, 1048577,
-    0x01c7, 1048578,
-    0x01c8, 1048577,
-    0x01ca, 1048578,
-    0x01cb, 1048577,
-    0x01cd, 1048577,
-    0x01cf, 1048577,
-    0x01d1, 1048577,
-    0x01d3, 1048577,
-    0x01d5, 1048577,
-    0x01d7, 1048577,
-    0x01d9, 1048577,
-    0x01db, 1048577,
-    0x01de, 1048577,
-    0x01e0, 1048577,
-    0x01e2, 1048577,
-    0x01e4, 1048577,
-    0x01e6, 1048577,
-    0x01e8, 1048577,
-    0x01ea, 1048577,
-    0x01ec, 1048577,
-    0x01ee, 1048577,
-    0x01f1, 1048578,
-    0x01f2, 1048577,
-    0x01f4, 1048577,
-    0x01f6, 1048479,
-    0x01f7, 1048520,
-    0x01f8, 1048577,
-    0x01fa, 1048577,
-    0x01fc, 1048577,
-    0x01fe, 1048577,
-    0x0200, 1048577,
-    0x0202, 1048577,
-    0x0204, 1048577,
-    0x0206, 1048577,
-    0x0208, 1048577,
-    0x020a, 1048577,
-    0x020c, 1048577,
-    0x020e, 1048577,
-    0x0210, 1048577,
-    0x0212, 1048577,
-    0x0214, 1048577,
-    0x0216, 1048577,
-    0x0218, 1048577,
-    0x021a, 1048577,
-    0x021c, 1048577,
-    0x021e, 1048577,
-    0x0220, 1048446,
-    0x0222, 1048577,
-    0x0224, 1048577,
-    0x0226, 1048577,
-    0x0228, 1048577,
-    0x022a, 1048577,
-    0x022c, 1048577,
-    0x022e, 1048577,
-    0x0230, 1048577,
-    0x0232, 1048577,
-    0x023a, 1059371,
-    0x023b, 1048577,
-    0x023d, 1048413,
-    0x023e, 1059368,
-    0x0241, 1048577,
-    0x0243, 1048381,
-    0x0244, 1048645,
-    0x0245, 1048647,
-    0x0246, 1048577,
-    0x0248, 1048577,
-    0x024a, 1048577,
-    0x024c, 1048577,
-    0x024e, 1048577,
-    0x0370, 1048577,
-    0x0372, 1048577,
-    0x0376, 1048577,
-    0x037f, 1048692,
-    0x0386, 1048614,
-    0x038c, 1048640,
-    0x03cf, 1048584,
-    0x03d8, 1048577,
-    0x03da, 1048577,
-    0x03dc, 1048577,
-    0x03de, 1048577,
-    0x03e0, 1048577,
-    0x03e2, 1048577,
-    0x03e4, 1048577,
-    0x03e6, 1048577,
-    0x03e8, 1048577,
-    0x03ea, 1048577,
-    0x03ec, 1048577,
-    0x03ee, 1048577,
-    0x03f4, 1048516,
-    0x03f7, 1048577,
-    0x03f9, 1048569,
-    0x03fa, 1048577,
-    0x0460, 1048577,
-    0x0462, 1048577,
-    0x0464, 1048577,
-    0x0466, 1048577,
-    0x0468, 1048577,
-    0x046a, 1048577,
-    0x046c, 1048577,
-    0x046e, 1048577,
-    0x0470, 1048577,
-    0x0472, 1048577,
-    0x0474, 1048577,
-    0x0476, 1048577,
-    0x0478, 1048577,
-    0x047a, 1048577,
-    0x047c, 1048577,
-    0x047e, 1048577,
-    0x0480, 1048577,
-    0x048a, 1048577,
-    0x048c, 1048577,
-    0x048e, 1048577,
-    0x0490, 1048577,
-    0x0492, 1048577,
-    0x0494, 1048577,
-    0x0496, 1048577,
-    0x0498, 1048577,
-    0x049a, 1048577,
-    0x049c, 1048577,
-    0x049e, 1048577,
-    0x04a0, 1048577,
-    0x04a2, 1048577,
-    0x04a4, 1048577,
-    0x04a6, 1048577,
-    0x04a8, 1048577,
-    0x04aa, 1048577,
-    0x04ac, 1048577,
-    0x04ae, 1048577,
-    0x04b0, 1048577,
-    0x04b2, 1048577,
-    0x04b4, 1048577,
-    0x04b6, 1048577,
-    0x04b8, 1048577,
-    0x04ba, 1048577,
-    0x04bc, 1048577,
-    0x04be, 1048577,
-    0x04c0, 1048591,
-    0x04c1, 1048577,
-    0x04c3, 1048577,
-    0x04c5, 1048577,
-    0x04c7, 1048577,
-    0x04c9, 1048577,
-    0x04cb, 1048577,
-    0x04cd, 1048577,
-    0x04d0, 1048577,
-    0x04d2, 1048577,
-    0x04d4, 1048577,
-    0x04d6, 1048577,
-    0x04d8, 1048577,
-    0x04da, 1048577,
-    0x04dc, 1048577,
-    0x04de, 1048577,
-    0x04e0, 1048577,
-    0x04e2, 1048577,
-    0x04e4, 1048577,
-    0x04e6, 1048577,
-    0x04e8, 1048577,
-    0x04ea, 1048577,
-    0x04ec, 1048577,
-    0x04ee, 1048577,
-    0x04f0, 1048577,
-    0x04f2, 1048577,
-    0x04f4, 1048577,
-    0x04f6, 1048577,
-    0x04f8, 1048577,
-    0x04fa, 1048577,
-    0x04fc, 1048577,
-    0x04fe, 1048577,
-    0x0500, 1048577,
-    0x0502, 1048577,
-    0x0504, 1048577,
-    0x0506, 1048577,
-    0x0508, 1048577,
-    0x050a, 1048577,
-    0x050c, 1048577,
-    0x050e, 1048577,
-    0x0510, 1048577,
-    0x0512, 1048577,
-    0x0514, 1048577,
-    0x0516, 1048577,
-    0x0518, 1048577,
-    0x051a, 1048577,
-    0x051c, 1048577,
-    0x051e, 1048577,
-    0x0520, 1048577,
-    0x0522, 1048577,
-    0x0524, 1048577,
-    0x0526, 1048577,
-    0x0528, 1048577,
-    0x052a, 1048577,
-    0x052c, 1048577,
-    0x052e, 1048577,
-    0x10c7, 1055840,
-    0x10cd, 1055840,
-    0x1e00, 1048577,
-    0x1e02, 1048577,
-    0x1e04, 1048577,
-    0x1e06, 1048577,
-    0x1e08, 1048577,
-    0x1e0a, 1048577,
-    0x1e0c, 1048577,
-    0x1e0e, 1048577,
-    0x1e10, 1048577,
-    0x1e12, 1048577,
-    0x1e14, 1048577,
-    0x1e16, 1048577,
-    0x1e18, 1048577,
-    0x1e1a, 1048577,
-    0x1e1c, 1048577,
-    0x1e1e, 1048577,
-    0x1e20, 1048577,
-    0x1e22, 1048577,
-    0x1e24, 1048577,
-    0x1e26, 1048577,
-    0x1e28, 1048577,
-    0x1e2a, 1048577,
-    0x1e2c, 1048577,
-    0x1e2e, 1048577,
-    0x1e30, 1048577,
-    0x1e32, 1048577,
-    0x1e34, 1048577,
-    0x1e36, 1048577,
-    0x1e38, 1048577,
-    0x1e3a, 1048577,
-    0x1e3c, 1048577,
-    0x1e3e, 1048577,
-    0x1e40, 1048577,
-    0x1e42, 1048577,
-    0x1e44, 1048577,
-    0x1e46, 1048577,
-    0x1e48, 1048577,
-    0x1e4a, 1048577,
-    0x1e4c, 1048577,
-    0x1e4e, 1048577,
-    0x1e50, 1048577,
-    0x1e52, 1048577,
-    0x1e54, 1048577,
-    0x1e56, 1048577,
-    0x1e58, 1048577,
-    0x1e5a, 1048577,
-    0x1e5c, 1048577,
-    0x1e5e, 1048577,
-    0x1e60, 1048577,
-    0x1e62, 1048577,
-    0x1e64, 1048577,
-    0x1e66, 1048577,
-    0x1e68, 1048577,
-    0x1e6a, 1048577,
-    0x1e6c, 1048577,
-    0x1e6e, 1048577,
-    0x1e70, 1048577,
-    0x1e72, 1048577,
-    0x1e74, 1048577,
-    0x1e76, 1048577,
-    0x1e78, 1048577,
-    0x1e7a, 1048577,
-    0x1e7c, 1048577,
-    0x1e7e, 1048577,
-    0x1e80, 1048577,
-    0x1e82, 1048577,
-    0x1e84, 1048577,
-    0x1e86, 1048577,
-    0x1e88, 1048577,
-    0x1e8a, 1048577,
-    0x1e8c, 1048577,
-    0x1e8e, 1048577,
-    0x1e90, 1048577,
-    0x1e92, 1048577,
-    0x1e94, 1048577,
-    0x1e9e, 1040961,
-    0x1ea0, 1048577,
-    0x1ea2, 1048577,
-    0x1ea4, 1048577,
-    0x1ea6, 1048577,
-    0x1ea8, 1048577,
-    0x1eaa, 1048577,
-    0x1eac, 1048577,
-    0x1eae, 1048577,
-    0x1eb0, 1048577,
-    0x1eb2, 1048577,
-    0x1eb4, 1048577,
-    0x1eb6, 1048577,
-    0x1eb8, 1048577,
-    0x1eba, 1048577,
-    0x1ebc, 1048577,
-    0x1ebe, 1048577,
-    0x1ec0, 1048577,
-    0x1ec2, 1048577,
-    0x1ec4, 1048577,
-    0x1ec6, 1048577,
-    0x1ec8, 1048577,
-    0x1eca, 1048577,
-    0x1ecc, 1048577,
-    0x1ece, 1048577,
-    0x1ed0, 1048577,
-    0x1ed2, 1048577,
-    0x1ed4, 1048577,
-    0x1ed6, 1048577,
-    0x1ed8, 1048577,
-    0x1eda, 1048577,
-    0x1edc, 1048577,
-    0x1ede, 1048577,
-    0x1ee0, 1048577,
-    0x1ee2, 1048577,
-    0x1ee4, 1048577,
-    0x1ee6, 1048577,
-    0x1ee8, 1048577,
-    0x1eea, 1048577,
-    0x1eec, 1048577,
-    0x1eee, 1048577,
-    0x1ef0, 1048577,
-    0x1ef2, 1048577,
-    0x1ef4, 1048577,
-    0x1ef6, 1048577,
-    0x1ef8, 1048577,
-    0x1efa, 1048577,
-    0x1efc, 1048577,
-    0x1efe, 1048577,
-    0x1f59, 1048568,
-    0x1f5b, 1048568,
-    0x1f5d, 1048568,
-    0x1f5f, 1048568,
-    0x1fbc, 1048567,
-    0x1fcc, 1048567,
-    0x1fec, 1048569,
-    0x1ffc, 1048567,
-    0x2126, 1041059,
-    0x212a, 1040193,
-    0x212b, 1040314,
-    0x2132, 1048604,
-    0x2183, 1048577,
-    0x2c60, 1048577,
-    0x2c62, 1037833,
-    0x2c63, 1044762,
-    0x2c64, 1037849,
-    0x2c67, 1048577,
-    0x2c69, 1048577,
-    0x2c6b, 1048577,
-    0x2c6d, 1037796,
-    0x2c6e, 1037827,
-    0x2c6f, 1037793,
-    0x2c70, 1037794,
-    0x2c72, 1048577,
-    0x2c75, 1048577,
-    0x2c80, 1048577,
-    0x2c82, 1048577,
-    0x2c84, 1048577,
-    0x2c86, 1048577,
-    0x2c88, 1048577,
-    0x2c8a, 1048577,
-    0x2c8c, 1048577,
-    0x2c8e, 1048577,
-    0x2c90, 1048577,
-    0x2c92, 1048577,
-    0x2c94, 1048577,
-    0x2c96, 1048577,
-    0x2c98, 1048577,
-    0x2c9a, 1048577,
-    0x2c9c, 1048577,
-    0x2c9e, 1048577,
-    0x2ca0, 1048577,
-    0x2ca2, 1048577,
-    0x2ca4, 1048577,
-    0x2ca6, 1048577,
-    0x2ca8, 1048577,
-    0x2caa, 1048577,
-    0x2cac, 1048577,
-    0x2cae, 1048577,
-    0x2cb0, 1048577,
-    0x2cb2, 1048577,
-    0x2cb4, 1048577,
-    0x2cb6, 1048577,
-    0x2cb8, 1048577,
-    0x2cba, 1048577,
-    0x2cbc, 1048577,
-    0x2cbe, 1048577,
-    0x2cc0, 1048577,
-    0x2cc2, 1048577,
-    0x2cc4, 1048577,
-    0x2cc6, 1048577,
-    0x2cc8, 1048577,
-    0x2cca, 1048577,
-    0x2ccc, 1048577,
-    0x2cce, 1048577,
-    0x2cd0, 1048577,
-    0x2cd2, 1048577,
-    0x2cd4, 1048577,
-    0x2cd6, 1048577,
-    0x2cd8, 1048577,
-    0x2cda, 1048577,
-    0x2cdc, 1048577,
-    0x2cde, 1048577,
-    0x2ce0, 1048577,
-    0x2ce2, 1048577,
-    0x2ceb, 1048577,
-    0x2ced, 1048577,
-    0x2cf2, 1048577,
-    0xa640, 1048577,
-    0xa642, 1048577,
-    0xa644, 1048577,
-    0xa646, 1048577,
-    0xa648, 1048577,
-    0xa64a, 1048577,
-    0xa64c, 1048577,
-    0xa64e, 1048577,
-    0xa650, 1048577,
-    0xa652, 1048577,
-    0xa654, 1048577,
-    0xa656, 1048577,
-    0xa658, 1048577,
-    0xa65a, 1048577,
-    0xa65c, 1048577,
-    0xa65e, 1048577,
-    0xa660, 1048577,
-    0xa662, 1048577,
-    0xa664, 1048577,
-    0xa666, 1048577,
-    0xa668, 1048577,
-    0xa66a, 1048577,
-    0xa66c, 1048577,
-    0xa680, 1048577,
-    0xa682, 1048577,
-    0xa684, 1048577,
-    0xa686, 1048577,
-    0xa688, 1048577,
-    0xa68a, 1048577,
-    0xa68c, 1048577,
-    0xa68e, 1048577,
-    0xa690, 1048577,
-    0xa692, 1048577,
-    0xa694, 1048577,
-    0xa696, 1048577,
-    0xa698, 1048577,
-    0xa69a, 1048577,
-    0xa722, 1048577,
-    0xa724, 1048577,
-    0xa726, 1048577,
-    0xa728, 1048577,
-    0xa72a, 1048577,
-    0xa72c, 1048577,
-    0xa72e, 1048577,
-    0xa732, 1048577,
-    0xa734, 1048577,
-    0xa736, 1048577,
-    0xa738, 1048577,
-    0xa73a, 1048577,
-    0xa73c, 1048577,
-    0xa73e, 1048577,
-    0xa740, 1048577,
-    0xa742, 1048577,
-    0xa744, 1048577,
-    0xa746, 1048577,
-    0xa748, 1048577,
-    0xa74a, 1048577,
-    0xa74c, 1048577,
-    0xa74e, 1048577,
-    0xa750, 1048577,
-    0xa752, 1048577,
-    0xa754, 1048577,
-    0xa756, 1048577,
-    0xa758, 1048577,
-    0xa75a, 1048577,
-    0xa75c, 1048577,
-    0xa75e, 1048577,
-    0xa760, 1048577,
-    0xa762, 1048577,
-    0xa764, 1048577,
-    0xa766, 1048577,
-    0xa768, 1048577,
-    0xa76a, 1048577,
-    0xa76c, 1048577,
-    0xa76e, 1048577,
-    0xa779, 1048577,
-    0xa77b, 1048577,
-    0xa77d, 1013244,
-    0xa77e, 1048577,
-    0xa780, 1048577,
-    0xa782, 1048577,
-    0xa784, 1048577,
-    0xa786, 1048577,
-    0xa78b, 1048577,
-    0xa78d, 1006296,
-    0xa790, 1048577,
-    0xa792, 1048577,
-    0xa796, 1048577,
-    0xa798, 1048577,
-    0xa79a, 1048577,
-    0xa79c, 1048577,
-    0xa79e, 1048577,
-    0xa7a0, 1048577,
-    0xa7a2, 1048577,
-    0xa7a4, 1048577,
-    0xa7a6, 1048577,
-    0xa7a8, 1048577,
-    0xa7aa, 1006268,
-    0xa7ab, 1006257,
-    0xa7ac, 1006261,
-    0xa7ad, 1006271,
-    0xa7b0, 1006318,
-    0xa7b1, 1006294,
-};
-
-} // !namespace
-
-char32_t tolower(char32_t c) noexcept
-{
-   const char32_t *p;
-
-   p = rbsearch(c, tolowerr, nelem (tolowerr)/3, 3);
-
-   if (p && c >= p[0] && c <= p[1])
-       return c + p[2] - 1048576;
-
- p = rbsearch(c, tolowers, nelem (tolowers)/2, 2);
-
-   if (p && c == p[0])
-       return c + p[1] - 1048576;
-
-   return c;
-}
-
-namespace {
-
-const char32_t totitler[] = {
-    0x0061, 0x007a, 1048544,
-    0x00e0, 0x00f6, 1048544,
-    0x00f8, 0x00fe, 1048544,
-    0x023f, 0x0240, 1059391,
-    0x0256, 0x0257, 1048371,
-    0x028a, 0x028b, 1048359,
-    0x037b, 0x037d, 1048706,
-    0x03ad, 0x03af, 1048539,
-    0x03b1, 0x03c1, 1048544,
-    0x03c3, 0x03cb, 1048544,
-    0x03cd, 0x03ce, 1048513,
-    0x0430, 0x044f, 1048544,
-    0x0450, 0x045f, 1048496,
-    0x0561, 0x0586, 1048528,
-    0x1f00, 0x1f07, 1048584,
-    0x1f10, 0x1f15, 1048584,
-    0x1f20, 0x1f27, 1048584,
-    0x1f30, 0x1f37, 1048584,
-    0x1f40, 0x1f45, 1048584,
-    0x1f60, 0x1f67, 1048584,
-    0x1f70, 0x1f71, 1048650,
-    0x1f72, 0x1f75, 1048662,
-    0x1f76, 0x1f77, 1048676,
-    0x1f78, 0x1f79, 1048704,
-    0x1f7a, 0x1f7b, 1048688,
-    0x1f7c, 0x1f7d, 1048702,
-    0x1f80, 0x1f87, 1048584,
-    0x1f90, 0x1f97, 1048584,
-    0x1fa0, 0x1fa7, 1048584,
-    0x1fb0, 0x1fb1, 1048584,
-    0x1fd0, 0x1fd1, 1048584,
-    0x1fe0, 0x1fe1, 1048584,
-    0x2170, 0x217f, 1048560,
-    0x24d0, 0x24e9, 1048550,
-    0x2c30, 0x2c5e, 1048528,
-    0x2d00, 0x2d25, 1041312,
-    0xff41, 0xff5a, 1048544,
-    0x10428, 0x1044f, 1048536,
-    0x118c0, 0x118df, 1048544,
-};
-
-} // !namespace
-
-namespace {
-
-const char32_t totitles[] = {
-    0x00b5, 1049319,
-    0x00ff, 1048697,
-    0x0101, 1048575,
-    0x0103, 1048575,
-    0x0105, 1048575,
-    0x0107, 1048575,
-    0x0109, 1048575,
-    0x010b, 1048575,
-    0x010d, 1048575,
-    0x010f, 1048575,
-    0x0111, 1048575,
-    0x0113, 1048575,
-    0x0115, 1048575,
-    0x0117, 1048575,
-    0x0119, 1048575,
-    0x011b, 1048575,
-    0x011d, 1048575,
-    0x011f, 1048575,
-    0x0121, 1048575,
-    0x0123, 1048575,
-    0x0125, 1048575,
-    0x0127, 1048575,
-    0x0129, 1048575,
-    0x012b, 1048575,
-    0x012d, 1048575,
-    0x012f, 1048575,
-    0x0131, 1048344,
-    0x0133, 1048575,
-    0x0135, 1048575,
-    0x0137, 1048575,
-    0x013a, 1048575,
-    0x013c, 1048575,
-    0x013e, 1048575,
-    0x0140, 1048575,
-    0x0142, 1048575,
-    0x0144, 1048575,
-    0x0146, 1048575,
-    0x0148, 1048575,
-    0x014b, 1048575,
-    0x014d, 1048575,
-    0x014f, 1048575,
-    0x0151, 1048575,
-    0x0153, 1048575,
-    0x0155, 1048575,
-    0x0157, 1048575,
-    0x0159, 1048575,
-    0x015b, 1048575,
-    0x015d, 1048575,
-    0x015f, 1048575,
-    0x0161, 1048575,
-    0x0163, 1048575,
-    0x0165, 1048575,
-    0x0167, 1048575,
-    0x0169, 1048575,
-    0x016b, 1048575,
-    0x016d, 1048575,
-    0x016f, 1048575,
-    0x0171, 1048575,
-    0x0173, 1048575,
-    0x0175, 1048575,
-    0x0177, 1048575,
-    0x017a, 1048575,
-    0x017c, 1048575,
-    0x017e, 1048575,
-    0x017f, 1048276,
-    0x0180, 1048771,
-    0x0183, 1048575,
-    0x0185, 1048575,
-    0x0188, 1048575,
-    0x018c, 1048575,
-    0x0192, 1048575,
-    0x0195, 1048673,
-    0x0199, 1048575,
-    0x019a, 1048739,
-    0x019e, 1048706,
-    0x01a1, 1048575,
-    0x01a3, 1048575,
-    0x01a5, 1048575,
-    0x01a8, 1048575,
-    0x01ad, 1048575,
-    0x01b0, 1048575,
-    0x01b4, 1048575,
-    0x01b6, 1048575,
-    0x01b9, 1048575,
-    0x01bd, 1048575,
-    0x01bf, 1048632,
-    0x01c4, 1048577,
-    0x01c6, 1048575,
-    0x01c7, 1048577,
-    0x01c9, 1048575,
-    0x01ca, 1048577,
-    0x01cc, 1048575,
-    0x01ce, 1048575,
-    0x01d0, 1048575,
-    0x01d2, 1048575,
-    0x01d4, 1048575,
-    0x01d6, 1048575,
-    0x01d8, 1048575,
-    0x01da, 1048575,
-    0x01dc, 1048575,
-    0x01dd, 1048497,
-    0x01df, 1048575,
-    0x01e1, 1048575,
-    0x01e3, 1048575,
-    0x01e5, 1048575,
-    0x01e7, 1048575,
-    0x01e9, 1048575,
-    0x01eb, 1048575,
-    0x01ed, 1048575,
-    0x01ef, 1048575,
-    0x01f1, 1048577,
-    0x01f3, 1048575,
-    0x01f5, 1048575,
-    0x01f9, 1048575,
-    0x01fb, 1048575,
-    0x01fd, 1048575,
-    0x01ff, 1048575,
-    0x0201, 1048575,
-    0x0203, 1048575,
-    0x0205, 1048575,
-    0x0207, 1048575,
-    0x0209, 1048575,
-    0x020b, 1048575,
-    0x020d, 1048575,
-    0x020f, 1048575,
-    0x0211, 1048575,
-    0x0213, 1048575,
-    0x0215, 1048575,
-    0x0217, 1048575,
-    0x0219, 1048575,
-    0x021b, 1048575,
-    0x021d, 1048575,
-    0x021f, 1048575,
-    0x0223, 1048575,
-    0x0225, 1048575,
-    0x0227, 1048575,
-    0x0229, 1048575,
-    0x022b, 1048575,
-    0x022d, 1048575,
-    0x022f, 1048575,
-    0x0231, 1048575,
-    0x0233, 1048575,
-    0x023c, 1048575,
-    0x0242, 1048575,
-    0x0247, 1048575,
-    0x0249, 1048575,
-    0x024b, 1048575,
-    0x024d, 1048575,
-    0x024f, 1048575,
-    0x0250, 1059359,
-    0x0251, 1059356,
-    0x0252, 1059358,
-    0x0253, 1048366,
-    0x0254, 1048370,
-    0x0259, 1048374,
-    0x025b, 1048373,
-    0x025c, 1090895,
-    0x0260, 1048371,
-    0x0261, 1090891,
-    0x0263, 1048369,
-    0x0265, 1090856,
-    0x0266, 1090884,
-    0x0268, 1048367,
-    0x0269, 1048365,
-    0x026b, 1059319,
-    0x026c, 1090881,
-    0x026f, 1048365,
-    0x0271, 1059325,
-    0x0272, 1048363,
-    0x0275, 1048362,
-    0x027d, 1059303,
-    0x0280, 1048358,
-    0x0283, 1048358,
-    0x0287, 1090858,
-    0x0288, 1048358,
-    0x0289, 1048507,
-    0x028c, 1048505,
-    0x0292, 1048357,
-    0x029e, 1090834,
-    0x0345, 1048660,
-    0x0371, 1048575,
-    0x0373, 1048575,
-    0x0377, 1048575,
-    0x03ac, 1048538,
-    0x03c2, 1048545,
-    0x03cc, 1048512,
-    0x03d0, 1048514,
-    0x03d1, 1048519,
-    0x03d5, 1048529,
-    0x03d6, 1048522,
-    0x03d7, 1048568,
-    0x03d9, 1048575,
-    0x03db, 1048575,
-    0x03dd, 1048575,
-    0x03df, 1048575,
-    0x03e1, 1048575,
-    0x03e3, 1048575,
-    0x03e5, 1048575,
-    0x03e7, 1048575,
-    0x03e9, 1048575,
-    0x03eb, 1048575,
-    0x03ed, 1048575,
-    0x03ef, 1048575,
-    0x03f0, 1048490,
-    0x03f1, 1048496,
-    0x03f2, 1048583,
-    0x03f3, 1048460,
-    0x03f5, 1048480,
-    0x03f8, 1048575,
-    0x03fb, 1048575,
-    0x0461, 1048575,
-    0x0463, 1048575,
-    0x0465, 1048575,
-    0x0467, 1048575,
-    0x0469, 1048575,
-    0x046b, 1048575,
-    0x046d, 1048575,
-    0x046f, 1048575,
-    0x0471, 1048575,
-    0x0473, 1048575,
-    0x0475, 1048575,
-    0x0477, 1048575,
-    0x0479, 1048575,
-    0x047b, 1048575,
-    0x047d, 1048575,
-    0x047f, 1048575,
-    0x0481, 1048575,
-    0x048b, 1048575,
-    0x048d, 1048575,
-    0x048f, 1048575,
-    0x0491, 1048575,
-    0x0493, 1048575,
-    0x0495, 1048575,
-    0x0497, 1048575,
-    0x0499, 1048575,
-    0x049b, 1048575,
-    0x049d, 1048575,
-    0x049f, 1048575,
-    0x04a1, 1048575,
-    0x04a3, 1048575,
-    0x04a5, 1048575,
-    0x04a7, 1048575,
-    0x04a9, 1048575,
-    0x04ab, 1048575,
-    0x04ad, 1048575,
-    0x04af, 1048575,
-    0x04b1, 1048575,
-    0x04b3, 1048575,
-    0x04b5, 1048575,
-    0x04b7, 1048575,
-    0x04b9, 1048575,
-    0x04bb, 1048575,
-    0x04bd, 1048575,
-    0x04bf, 1048575,
-    0x04c2, 1048575,
-    0x04c4, 1048575,
-    0x04c6, 1048575,
-    0x04c8, 1048575,
-    0x04ca, 1048575,
-    0x04cc, 1048575,
-    0x04ce, 1048575,
-    0x04cf, 1048561,
-    0x04d1, 1048575,
-    0x04d3, 1048575,
-    0x04d5, 1048575,
-    0x04d7, 1048575,
-    0x04d9, 1048575,
-    0x04db, 1048575,
-    0x04dd, 1048575,
-    0x04df, 1048575,
-    0x04e1, 1048575,
-    0x04e3, 1048575,
-    0x04e5, 1048575,
-    0x04e7, 1048575,
-    0x04e9, 1048575,
-    0x04eb, 1048575,
-    0x04ed, 1048575,
-    0x04ef, 1048575,
-    0x04f1, 1048575,
-    0x04f3, 1048575,
-    0x04f5, 1048575,
-    0x04f7, 1048575,
-    0x04f9, 1048575,
-    0x04fb, 1048575,
-    0x04fd, 1048575,
-    0x04ff, 1048575,
-    0x0501, 1048575,
-    0x0503, 1048575,
-    0x0505, 1048575,
-    0x0507, 1048575,
-    0x0509, 1048575,
-    0x050b, 1048575,
-    0x050d, 1048575,
-    0x050f, 1048575,
-    0x0511, 1048575,
-    0x0513, 1048575,
-    0x0515, 1048575,
-    0x0517, 1048575,
-    0x0519, 1048575,
-    0x051b, 1048575,
-    0x051d, 1048575,
-    0x051f, 1048575,
-    0x0521, 1048575,
-    0x0523, 1048575,
-    0x0525, 1048575,
-    0x0527, 1048575,
-    0x0529, 1048575,
-    0x052b, 1048575,
-    0x052d, 1048575,
-    0x052f, 1048575,
-    0x1d79, 1083908,
-    0x1d7d, 1052390,
-    0x1e01, 1048575,
-    0x1e03, 1048575,
-    0x1e05, 1048575,
-    0x1e07, 1048575,
-    0x1e09, 1048575,
-    0x1e0b, 1048575,
-    0x1e0d, 1048575,
-    0x1e0f, 1048575,
-    0x1e11, 1048575,
-    0x1e13, 1048575,
-    0x1e15, 1048575,
-    0x1e17, 1048575,
-    0x1e19, 1048575,
-    0x1e1b, 1048575,
-    0x1e1d, 1048575,
-    0x1e1f, 1048575,
-    0x1e21, 1048575,
-    0x1e23, 1048575,
-    0x1e25, 1048575,
-    0x1e27, 1048575,
-    0x1e29, 1048575,
-    0x1e2b, 1048575,
-    0x1e2d, 1048575,
-    0x1e2f, 1048575,
-    0x1e31, 1048575,
-    0x1e33, 1048575,
-    0x1e35, 1048575,
-    0x1e37, 1048575,
-    0x1e39, 1048575,
-    0x1e3b, 1048575,
-    0x1e3d, 1048575,
-    0x1e3f, 1048575,
-    0x1e41, 1048575,
-    0x1e43, 1048575,
-    0x1e45, 1048575,
-    0x1e47, 1048575,
-    0x1e49, 1048575,
-    0x1e4b, 1048575,
-    0x1e4d, 1048575,
-    0x1e4f, 1048575,
-    0x1e51, 1048575,
-    0x1e53, 1048575,
-    0x1e55, 1048575,
-    0x1e57, 1048575,
-    0x1e59, 1048575,
-    0x1e5b, 1048575,
-    0x1e5d, 1048575,
-    0x1e5f, 1048575,
-    0x1e61, 1048575,
-    0x1e63, 1048575,
-    0x1e65, 1048575,
-    0x1e67, 1048575,
-    0x1e69, 1048575,
-    0x1e6b, 1048575,
-    0x1e6d, 1048575,
-    0x1e6f, 1048575,
-    0x1e71, 1048575,
-    0x1e73, 1048575,
-    0x1e75, 1048575,
-    0x1e77, 1048575,
-    0x1e79, 1048575,
-    0x1e7b, 1048575,
-    0x1e7d, 1048575,
-    0x1e7f, 1048575,
-    0x1e81, 1048575,
-    0x1e83, 1048575,
-    0x1e85, 1048575,
-    0x1e87, 1048575,
-    0x1e89, 1048575,
-    0x1e8b, 1048575,
-    0x1e8d, 1048575,
-    0x1e8f, 1048575,
-    0x1e91, 1048575,
-    0x1e93, 1048575,
-    0x1e95, 1048575,
-    0x1e9b, 1048517,
-    0x1ea1, 1048575,
-    0x1ea3, 1048575,
-    0x1ea5, 1048575,
-    0x1ea7, 1048575,
-    0x1ea9, 1048575,
-    0x1eab, 1048575,
-    0x1ead, 1048575,
-    0x1eaf, 1048575,
-    0x1eb1, 1048575,
-    0x1eb3, 1048575,
-    0x1eb5, 1048575,
-    0x1eb7, 1048575,
-    0x1eb9, 1048575,
-    0x1ebb, 1048575,
-    0x1ebd, 1048575,
-    0x1ebf, 1048575,
-    0x1ec1, 1048575,
-    0x1ec3, 1048575,
-    0x1ec5, 1048575,
-    0x1ec7, 1048575,
-    0x1ec9, 1048575,
-    0x1ecb, 1048575,
-    0x1ecd, 1048575,
-    0x1ecf, 1048575,
-    0x1ed1, 1048575,
-    0x1ed3, 1048575,
-    0x1ed5, 1048575,
-    0x1ed7, 1048575,
-    0x1ed9, 1048575,
-    0x1edb, 1048575,
-    0x1edd, 1048575,
-    0x1edf, 1048575,
-    0x1ee1, 1048575,
-    0x1ee3, 1048575,
-    0x1ee5, 1048575,
-    0x1ee7, 1048575,
-    0x1ee9, 1048575,
-    0x1eeb, 1048575,
-    0x1eed, 1048575,
-    0x1eef, 1048575,
-    0x1ef1, 1048575,
-    0x1ef3, 1048575,
-    0x1ef5, 1048575,
-    0x1ef7, 1048575,
-    0x1ef9, 1048575,
-    0x1efb, 1048575,
-    0x1efd, 1048575,
-    0x1eff, 1048575,
-    0x1f51, 1048584,
-    0x1f53, 1048584,
-    0x1f55, 1048584,
-    0x1f57, 1048584,
-    0x1fb3, 1048585,
-    0x1fbe, 1041371,
-    0x1fc3, 1048585,
-    0x1fe5, 1048583,
-    0x1ff3, 1048585,
-    0x214e, 1048548,
-    0x2184, 1048575,
-    0x2c61, 1048575,
-    0x2c65, 1037781,
-    0x2c66, 1037784,
-    0x2c68, 1048575,
-    0x2c6a, 1048575,
-    0x2c6c, 1048575,
-    0x2c73, 1048575,
-    0x2c76, 1048575,
-    0x2c81, 1048575,
-    0x2c83, 1048575,
-    0x2c85, 1048575,
-    0x2c87, 1048575,
-    0x2c89, 1048575,
-    0x2c8b, 1048575,
-    0x2c8d, 1048575,
-    0x2c8f, 1048575,
-    0x2c91, 1048575,
-    0x2c93, 1048575,
-    0x2c95, 1048575,
-    0x2c97, 1048575,
-    0x2c99, 1048575,
-    0x2c9b, 1048575,
-    0x2c9d, 1048575,
-    0x2c9f, 1048575,
-    0x2ca1, 1048575,
-    0x2ca3, 1048575,
-    0x2ca5, 1048575,
-    0x2ca7, 1048575,
-    0x2ca9, 1048575,
-    0x2cab, 1048575,
-    0x2cad, 1048575,
-    0x2caf, 1048575,
-    0x2cb1, 1048575,
-    0x2cb3, 1048575,
-    0x2cb5, 1048575,
-    0x2cb7, 1048575,
-    0x2cb9, 1048575,
-    0x2cbb, 1048575,
-    0x2cbd, 1048575,
-    0x2cbf, 1048575,
-    0x2cc1, 1048575,
-    0x2cc3, 1048575,
-    0x2cc5, 1048575,
-    0x2cc7, 1048575,
-    0x2cc9, 1048575,
-    0x2ccb, 1048575,
-    0x2ccd, 1048575,
-    0x2ccf, 1048575,
-    0x2cd1, 1048575,
-    0x2cd3, 1048575,
-    0x2cd5, 1048575,
-    0x2cd7, 1048575,
-    0x2cd9, 1048575,
-    0x2cdb, 1048575,
-    0x2cdd, 1048575,
-    0x2cdf, 1048575,
-    0x2ce1, 1048575,
-    0x2ce3, 1048575,
-    0x2cec, 1048575,
-    0x2cee, 1048575,
-    0x2cf3, 1048575,
-    0x2d27, 1041312,
-    0x2d2d, 1041312,
-    0xa641, 1048575,
-    0xa643, 1048575,
-    0xa645, 1048575,
-    0xa647, 1048575,
-    0xa649, 1048575,
-    0xa64b, 1048575,
-    0xa64d, 1048575,
-    0xa64f, 1048575,
-    0xa651, 1048575,
-    0xa653, 1048575,
-    0xa655, 1048575,
-    0xa657, 1048575,
-    0xa659, 1048575,
-    0xa65b, 1048575,
-    0xa65d, 1048575,
-    0xa65f, 1048575,
-    0xa661, 1048575,
-    0xa663, 1048575,
-    0xa665, 1048575,
-    0xa667, 1048575,
-    0xa669, 1048575,
-    0xa66b, 1048575,
-    0xa66d, 1048575,
-    0xa681, 1048575,
-    0xa683, 1048575,
-    0xa685, 1048575,
-    0xa687, 1048575,
-    0xa689, 1048575,
-    0xa68b, 1048575,
-    0xa68d, 1048575,
-    0xa68f, 1048575,
-    0xa691, 1048575,
-    0xa693, 1048575,
-    0xa695, 1048575,
-    0xa697, 1048575,
-    0xa699, 1048575,
-    0xa69b, 1048575,
-    0xa723, 1048575,
-    0xa725, 1048575,
-    0xa727, 1048575,
-    0xa729, 1048575,
-    0xa72b, 1048575,
-    0xa72d, 1048575,
-    0xa72f, 1048575,
-    0xa733, 1048575,
-    0xa735, 1048575,
-    0xa737, 1048575,
-    0xa739, 1048575,
-    0xa73b, 1048575,
-    0xa73d, 1048575,
-    0xa73f, 1048575,
-    0xa741, 1048575,
-    0xa743, 1048575,
-    0xa745, 1048575,
-    0xa747, 1048575,
-    0xa749, 1048575,
-    0xa74b, 1048575,
-    0xa74d, 1048575,
-    0xa74f, 1048575,
-    0xa751, 1048575,
-    0xa753, 1048575,
-    0xa755, 1048575,
-    0xa757, 1048575,
-    0xa759, 1048575,
-    0xa75b, 1048575,
-    0xa75d, 1048575,
-    0xa75f, 1048575,
-    0xa761, 1048575,
-    0xa763, 1048575,
-    0xa765, 1048575,
-    0xa767, 1048575,
-    0xa769, 1048575,
-    0xa76b, 1048575,
-    0xa76d, 1048575,
-    0xa76f, 1048575,
-    0xa77a, 1048575,
-    0xa77c, 1048575,
-    0xa77f, 1048575,
-    0xa781, 1048575,
-    0xa783, 1048575,
-    0xa785, 1048575,
-    0xa787, 1048575,
-    0xa78c, 1048575,
-    0xa791, 1048575,
-    0xa793, 1048575,
-    0xa797, 1048575,
-    0xa799, 1048575,
-    0xa79b, 1048575,
-    0xa79d, 1048575,
-    0xa79f, 1048575,
-    0xa7a1, 1048575,
-    0xa7a3, 1048575,
-    0xa7a5, 1048575,
-    0xa7a7, 1048575,
-    0xa7a9, 1048575,
-};
-
-} // !namespace
-
-char32_t totitle(char32_t c) noexcept
-{
-   const char32_t *p;
-
-   p = rbsearch(c, totitler, nelem (totitler)/3, 3);
-
-   if (p && c >= p[0] && c <= p[1])
-       return c + p[2] - 1048576;
-
- p = rbsearch(c, totitles, nelem (totitles)/2, 2);
-
-   if (p && c == p[0])
-       return c + p[1] - 1048576;
-
-   return c;
-}
-
-void encode(char32_t c, char res[5]) noexcept
-{
-    switch (nbytesPoint(c)) {
-    case 1:
-        res[0] = static_cast<char>(c);
-        res[1] = '\0';
-        break;
-    case 2:
-        res[0] = 0xC0 | ((c >> 6)  & 0x1F);
-        res[1] = 0x80 | (c & 0x3F);
-        res[2] = '\0';
-        break;
-    case 3:
-        res[0] = 0xE0 | ((c >> 12) & 0xF );
-        res[1] = 0x80 | ((c >> 6)  & 0x3F);
-        res[2] = 0x80 | (c & 0x3F);
-        res[3] = '\0';
-        break;
-    case 4:
-        res[0] = 0xF0 | ((c >> 18) & 0x7 );
-        res[1] = 0x80 | ((c >> 12) & 0x3F);
-        res[2] = 0x80 | ((c >> 6)  & 0x3F);
-        res[3] = 0x80 | (c & 0x3F);
-        res[4] = '\0';
-        break;
-    default:
-        break;
-    }
-}
-
-void decode(char32_t &c, const char *res) noexcept
-{
-    c = 0;
-
-    switch (nbytesUtf8(res[0])) {
-    case 1:
-        c = res[0];
-        break;
-    case 2:
-        c =  (res[0] & 0x1f) << 6;
-        c |= (res[1] & 0x3f);
-        break;
-    case 3:
-        c =  (res[0] & 0x0f) << 12;
-        c |= (res[1] & 0x3f) << 6;
-        c |= (res[2] & 0x3f);
-        break;
-    case 4:
-        c =  (res[0] & 0x07) << 16;
-        c |= (res[1] & 0x3f) << 12;
-        c |= (res[2] & 0x3f) << 6;
-        c |= (res[3] & 0x3f);
-    default:
-        break;
-    }
-}
-
-int nbytesUtf8(char c) noexcept
-{
-    if (static_cast<unsigned char>(c) <= 127)
-        return 1;
-    if ((c & 0xE0) == 0xC0)
-        return 2;
-    if ((c & 0xF0) == 0xE0)
-        return 3;
-    if ((c & 0xF8) == 0xF0)
-        return 4;
-
-    return -1;
-}
-
-int nbytesPoint(char32_t c) noexcept
-{
-    if (c <= 0x7F)
-        return 1;
-    if (c <= 0x7FF)
-        return 2;
-    if (c <= 0xFFFF)
-        return 3;
-    if (c <= 0x1FFFFF)
-        return 4;
-
-    return -1;
-}
-
-unsigned length(const std::string &str)
-{
-    unsigned total = 0;
-
-    forEach(str, [&] (char32_t) {
-        ++ total;
-    });
-
-    return total;
-}
-
-std::string toUtf8(const std::u32string &array)
-{
-    std::string res;
-
-    for (size_t i = 0; i < array.size(); ++i) {
-        char tmp[5];
-        int size = nbytesPoint(array[i]);
-
-        if (size < 0)
-            throw std::invalid_argument("invalid sequence");
-
-        encode(array[i], tmp);
-        res.insert(res.length(), tmp);
-    }
-
-    return res;
-}
-
-std::u32string toUtf32(const std::string &str)
-{
-    std::u32string res;
-
-    forEach(str, [&] (char32_t code) {
-        res.push_back(code);
-    });
-
-    return res;
-}
-
-} // !unicode
-
-} // !irccd
--- a/libirccd-js/irccd/unicode.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,273 +0,0 @@
-/*
- * unicode.hpp -- UTF-8 to UTF-32 conversions and various operations
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_UNICODE_HPP
-#define IRCCD_UNICODE_HPP
-
-/**
- * \file unicode.hpp
- * \brief UTF-8 to UTF-32 conversions
- * \author David Demelier <markand@malikania.fr>
- * \warning These files are auto-generated!
- */
-
-#include <stdexcept>
-#include <string>
-
-namespace irccd {
-
-/**
- * \brief Unicode namespace.
- */
-namespace unicode {
-
-/**
- * Encode the unicode code point into multibyte string.
- *
- * \param point the unicode code point
- * \param res the output buffer
- */
-void encode(char32_t point, char res[5]) noexcept;
-
-/**
- * Decode the multibyte buffer into an unicode code point.
- *
- * \param c the code point destination
- * \param res the multibyte string.
- */
-void decode(char32_t &c, const char *res) noexcept;
-
-/**
- * Get the number of bytes for the first multi byte character from a
- * utf-8 string.
- *
- * This can be used to iterate a valid UTF-8 string to jump to the next
- * real character.
- *
- * \param c the first multi byte character
- * \return the number of bytes [1-4] or -1 if invalid
- */
-int nbytesUtf8(char c) noexcept;
-
-/**
- * Get the number of bytes for the unicode point.
- *
- * \param point the unicode point
- * \return the number of bytes [1-4] or -1 if invalid
- */
-int nbytesPoint(char32_t point) noexcept;
-
-/**
- * Get real number of character in a string.
- *
- * \param str the string
- * \return the length
- * \throw std::invalid_argument on invalid sequence
- */
-unsigned length(const std::string &str);
-
-/**
- * Iterate over all real characters in the UTF-8 string.
- *
- * The function must have the following signature:
- *  void f(char ch)
- *
- * \param str the UTF-8 string
- * \param function the function callback
- * \throw std::invalid_argument on invalid sequence
- */
-template <typename Func>
-void forEach(const std::string &str, Func function)
-{
-    for (size_t i = 0; i < str.size(); ) {
-        char32_t point = 0;
-        int size = nbytesUtf8(str[i]);
-
-        if (size < 0)
-            throw std::invalid_argument("invalid sequence");
-
-        decode(point, str.data() + i);
-        function(point);
-
-        i += size;
-    }
-}
-
-/**
- * Convert a UTF-32 string to UTF-8 string.
- *
- * \param array the UTF-32 string
- * \return the UTF-8 string
- * \throw std::invalid_argument on invalid sequence
- */
-std::string toUtf8(const std::u32string &array);
-
-/**
- * Convert a UTF-8 string to UTF-32 string.
- *
- * \param str the UTF-8 string
- * \return the UTF-32 string
- * \throw std::invalid_argument on invalid sequence
- */
-std::u32string toUtf32(const std::string &str);
-
-/**
- * Check if the unicode character is space.
- *
- * \param c the character
- * \return true if space
- */
-bool isspace(char32_t c) noexcept;
-
-/**
- * Check if the unicode character is digit.
- *
- * \param c the character
- * \return true if digit
- */
-bool isdigit(char32_t c) noexcept;
-
-/**
- * Check if the unicode character is alpha category.
- *
- * \param c the character
- * \return true if alpha
- */
-bool isalpha(char32_t c) noexcept;
-
-/**
- * Check if the unicode character is upper case.
- *
- * \param c the character
- * \return true if upper case
- */
-bool isupper(char32_t c) noexcept;
-
-/**
- * Check if the unicode character is lower case.
- *
- * \param c the character
- * \return true if lower case
- */
-bool islower(char32_t c) noexcept;
-
-/**
- * Check if the unicode character is title case.
- *
- * \param c the character
- * \return true if title case
- */
-bool istitle(char32_t c) noexcept;
-
-/**
- * Convert to upper case.
- *
- * \param c the character
- * \return the upper case character
- */
-char32_t toupper(char32_t c) noexcept;
-
-/**
- * Convert to lower case.
- *
- * \param c the character
- * \return the lower case character
- */
-char32_t tolower(char32_t c) noexcept;
-
-/**
- * Convert to title case.
- *
- * \param c the character
- * \return the title case character
- */
-char32_t totitle(char32_t c) noexcept;
-
-/**
- * Convert the UTF-32 string to upper case.
- *
- * \param str the str
- * \return the upper case string
- */
-inline std::u32string toupper(std::u32string str)
-{
-    for (size_t i = 0; i < str.size(); ++i)
-        str[i] = toupper(str[i]);
-
-    return str;
-}
-
-/**
- * Convert the UTF-8 string to upper case.
- *
- * \param str the str
- * \return the upper case string
- * \warning very slow at the moment
- */
-inline std::string toupper(const std::string &str)
-{
-    std::string result;
-    char buffer[5];
-
-    forEach(str, [&] (char32_t code) {
-        encode(toupper(code), buffer);
-        result += buffer;
-    });
-
-    return result;
-}
-
-/**
- * Convert the UTF-32 string to lower case.
- *
- * \param str the str
- * \return the lower case string
- */
-inline std::u32string tolower(std::u32string str)
-{
-    for (size_t i = 0; i < str.size(); ++i)
-        str[i] = tolower(str[i]);
-
-    return str;
-}
-
-/**
- * Convert the UTF-8 string to lower case.
- *
- * \param str the str
- * \return the lower case string
- * \warning very slow at the moment
- */
-inline std::string tolower(const std::string &str)
-{
-    std::string result;
-    char buffer[5];
-
-    forEach(str, [&] (char32_t code) {
-        encode(tolower(code), buffer);
-        result += buffer;
-    });
-
-    return result;
-}
-
-} // !unicode
-
-} // !irccd
-
-#endif // !IRCCD_UNICODE_HPP
--- a/libirccd-test/irccd/js_test.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/libirccd-test/irccd/js_test.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -25,8 +25,9 @@
  */
 
 #include <irccd/irccd.hpp>
-#include <irccd/js_plugin.hpp>
-#include <irccd/js_irccd_module.hpp>
+
+#include <irccd/js/js_plugin.hpp>
+#include <irccd/js/irccd_jsapi.hpp>
 
 #include "journal_server.hpp"
 
@@ -66,7 +67,7 @@
         , server_(new journal_server("test"))
     {
         // Irccd is mandatory at the moment.
-        add<js_irccd_module>();
+        add<irccd_jsapi>();
         add<Modules...>();
 
         plugin_->open();
--- a/libirccd-test/irccd/plugin_test.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/libirccd-test/irccd/plugin_test.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -18,21 +18,22 @@
 
 #include <cassert>
 
-#include <irccd/js_directory_module.hpp>
-#include <irccd/js_elapsed_timer_module.hpp>
-#include <irccd/js_file_module.hpp>
-#include <irccd/js_irccd_module.hpp>
-#include <irccd/js_logger_module.hpp>
-#include <irccd/js_plugin_module.hpp>
-#include <irccd/js_server_module.hpp>
-#include <irccd/js_system_module.hpp>
-#include <irccd/js_timer_module.hpp>
-#include <irccd/js_unicode_module.hpp>
-#include <irccd/js_util_module.hpp>
-#include <irccd/js_plugin.hpp>
 #include <irccd/logger.hpp>
 #include <irccd/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/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 "plugin_test.hpp"
 
 namespace irccd {
@@ -48,17 +49,17 @@
     irccd_.plugins().add(plugin_);
     irccd_.servers().add(server_);
 
-    js_irccd_module().load(irccd_, plugin_);
-    js_directory_module().load(irccd_, plugin_);
-    js_elapsed_timer_module().load(irccd_, plugin_);
-    js_file_module().load(irccd_, plugin_);
-    js_logger_module().load(irccd_, plugin_);
-    js_plugin_module().load(irccd_, plugin_);
-    js_server_module().load(irccd_, plugin_);
-    js_system_module().load(irccd_, plugin_);
-    js_timer_module().load(irccd_, plugin_);
-    js_unicode_module().load(irccd_, plugin_);
-    js_util_module().load(irccd_, plugin_);
+    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_);
 
     plugin_->open();
 }
--- a/libirccd-test/irccd/plugin_test.hpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/libirccd-test/irccd/plugin_test.hpp	Thu Nov 16 23:31:28 2017 +0100
@@ -24,8 +24,9 @@
  * \brief test fixture helper for Javascript plugins.
  */
 
+#include <js/js_plugin.hpp>
+
 #include "irccd.hpp"
-#include "js_plugin.hpp"
 #include "journal_server.hpp"
 
 namespace irccd {
--- a/tests/js-directory/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-directory/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -19,17 +19,13 @@
 #define BOOST_TEST_MODULE "Directory Javascript API"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/js_directory_module.hpp>
+#include <irccd/js/directory_jsapi.hpp>
 
 #include <js_test.hpp>
 
 namespace irccd {
 
-class directory_test : public js_test<js_directory_module> {
-public:
-};
-
-BOOST_FIXTURE_TEST_SUITE(js_directory_suite, directory_test)
+BOOST_FIXTURE_TEST_SUITE(directory_jsapi_suite, js_test<directory_jsapi>)
 
 BOOST_AUTO_TEST_CASE(constructor)
 {
--- a/tests/js-elapsedtimer/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-elapsedtimer/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -21,7 +21,7 @@
 
 #include <thread>
 
-#include <irccd/js_elapsed_timer_module.hpp>
+#include <irccd/js/elapsed_timer_jsapi.hpp>
 
 #include <js_test.hpp>
 
@@ -29,7 +29,7 @@
 
 namespace irccd {
 
-BOOST_FIXTURE_TEST_SUITE(js_elapsed_timer_suite, js_test<js_elapsed_timer_module>)
+BOOST_FIXTURE_TEST_SUITE(elapsed_timer_jsapi_suite, js_test<elapsed_timer_jsapi>)
 
 BOOST_AUTO_TEST_CASE(standard)
 {
--- a/tests/js-file/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-file/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -21,13 +21,13 @@
 
 #include <fstream>
 
-#include <irccd/js_file_module.hpp>
+#include <irccd/js/file_jsapi.hpp>
 
 #include <js_test.hpp>
 
 namespace irccd {
 
-BOOST_FIXTURE_TEST_SUITE(js_file_suite, js_test<js_file_module>)
+BOOST_FIXTURE_TEST_SUITE(file_jsapi_suite, js_test<file_jsapi>)
 
 BOOST_AUTO_TEST_CASE(function_basename)
 {
@@ -55,7 +55,7 @@
 
     if (duk_peval_string(plugin_->context(), "result = Irccd.File.exists(directory + '/file.txt')"))
         throw dukx_exception(plugin_->context(), -1);
-    
+
     BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
     BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
 }
@@ -64,7 +64,7 @@
 {
     if (duk_peval_string(plugin_->context(), "result = Irccd.File.exists('file_which_does_not_exist.txt')"))
         throw dukx_exception(plugin_->context(), -1);
-    
+
     BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
     BOOST_TEST(!duk_get_boolean(plugin_->context(), -1));
 }
--- a/tests/js-irccd/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-irccd/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -23,7 +23,7 @@
 
 namespace irccd {
 
-BOOST_FIXTURE_TEST_SUITE(js_irccd_suite, js_test<js_irccd_module>)
+BOOST_FIXTURE_TEST_SUITE(irccd_jsapi_suite, js_test<irccd_jsapi>)
 
 BOOST_AUTO_TEST_CASE(version)
 {
--- a/tests/js-logger/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-logger/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -20,14 +20,15 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/logger.hpp>
-#include <irccd/js_logger_module.hpp>
-#include <irccd/js_plugin_module.hpp>
+
+#include <irccd/js/logger_jsapi.hpp>
+#include <irccd/js/plugin_jsapi.hpp>
 
 #include <js_test.hpp>
 
 namespace irccd {
 
-class logger_test : public js_test<js_logger_module, js_plugin_module> {
+class logger_test : public js_test<logger_jsapi, plugin_jsapi> {
 protected:
     std::string line_info;
     std::string line_warning;
@@ -66,7 +67,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(js_logger_suite, logger_test)
+BOOST_FIXTURE_TEST_SUITE(logger_jsapi_suite, logger_test)
 
 BOOST_AUTO_TEST_CASE(info)
 {
--- a/tests/js-plugin/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-plugin/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -21,9 +21,10 @@
 
 #include <irccd/irccd.hpp>
 #include <irccd/service.hpp>
-#include <irccd/js_plugin.hpp>
-#include <irccd/js_irccd_module.hpp>
-#include <irccd/js_plugin_module.hpp>
+
+#include <irccd/js/irccd_jsapi.hpp>
+#include <irccd/js/js_plugin.hpp>
+#include <irccd/js/plugin_jsapi.hpp>
 
 namespace irccd {
 
@@ -36,8 +37,8 @@
     {
         plugin_ = std::make_unique<js_plugin>(std::move(name), std::move(path));
 
-        js_irccd_module().load(irccd_, plugin_);
-        js_plugin_module().load(irccd_, plugin_);
+        irccd_jsapi().load(irccd_, plugin_);
+        plugin_jsapi().load(irccd_, plugin_);
 
         plugin_->open();
     }
@@ -103,8 +104,8 @@
 
         auto loader = std::make_unique<js_plugin_loader>(irccd_);
 
-        loader->add_module(std::make_unique<js_irccd_module>());
-        loader->add_module(std::make_unique<js_plugin_module>());
+        loader->modules().push_back(std::make_unique<irccd_jsapi>());
+        loader->modules().push_back(std::make_unique<plugin_jsapi>());
 
         irccd_.plugins().add_loader(std::move(loader));
     }
--- a/tests/js-system/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-system/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -20,17 +20,18 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/irccd.hpp>
-#include <irccd/js_file_module.hpp>
-#include <irccd/js_system_module.hpp>
 #include <irccd/system.hpp>
 
+#include <irccd/js/file_jsapi.hpp>
+#include <irccd/js/system_jsapi.hpp>
+
 #include <js_test.hpp>
 
 namespace irccd {
 
-using fixture = js_test<js_file_module, js_system_module>;
+using fixture = js_test<file_jsapi, system_jsapi>;
 
-BOOST_FIXTURE_TEST_SUITE(js_system_suite, fixture)
+BOOST_FIXTURE_TEST_SUITE(system_jsapi_suite, fixture)
 
 BOOST_AUTO_TEST_CASE(home)
 {
--- a/tests/js-timer/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-timer/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -20,20 +20,21 @@
 #include <boost/test/unit_test.hpp>
 #include <boost/timer/timer.hpp>
 
-#include <irccd/js_plugin_module.hpp>
-#include <irccd/js_timer_module.hpp>
 #include <irccd/net_util.hpp>
 
+#include <irccd/js/plugin_jsapi.hpp>
+#include <irccd/js/timer_jsapi.hpp>
+
 #include <js_test.hpp>
 
 namespace irccd {
 
-class fixture : public js_test<js_plugin_module, js_timer_module> {
+class fixture : public js_test<plugin_jsapi, timer_jsapi> {
 public:
     using js_test::js_test;
 };
 
-BOOST_FIXTURE_TEST_SUITE(js_timer_suite, fixture)
+BOOST_FIXTURE_TEST_SUITE(timer_jsapi_suite, fixture)
 
 BOOST_AUTO_TEST_CASE(single)
 {
--- a/tests/js-unicode/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-unicode/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -23,13 +23,13 @@
 #define BOOST_TEST_MODULE "Unicode Javascript API"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/js_unicode_module.hpp>
+#include <irccd/js/unicode_jsapi.hpp>
 
 #include <js_test.hpp>
 
 namespace irccd {
 
-BOOST_FIXTURE_TEST_SUITE(js_unicode_suite, js_test<js_unicode_module>)
+BOOST_FIXTURE_TEST_SUITE(unicode_jsapi_suite, js_test<unicode_jsapi>)
 
 BOOST_AUTO_TEST_CASE(is_letter)
 {
--- a/tests/js-util/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js-util/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -19,13 +19,13 @@
 #define BOOST_TEST_MODULE "Unicode Javascript API"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/js_util_module.hpp>
+#include <irccd/js/util_jsapi.hpp>
 
 #include <js_test.hpp>
 
 namespace irccd {
 
-BOOST_FIXTURE_TEST_SUITE(js_util_suite, js_test<js_util_module>)
+BOOST_FIXTURE_TEST_SUITE(util_jsapi_suite, js_test<util_jsapi>)
 
 /*
  * Irccd.Util misc.
--- a/tests/js/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/js/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "Javascript"
 #include <boost/test/unit_test.hpp>
 
-#include <duktape.hpp>
+#include <js/duktape.hpp>
 
 #include <irccd/fs_util.hpp>
 #include <irccd/util.hpp>
--- a/tests/timer/main.cpp	Thu Nov 16 23:12:45 2017 +0100
+++ b/tests/timer/main.cpp	Thu Nov 16 23:31:28 2017 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 #include <boost/timer/timer.hpp>
 
-#include <irccd/timer.hpp>
+#include <irccd/js/timer.hpp>
 
 using namespace std::chrono_literals;