changeset 134:dc7d6ba08122

Irccd: load modules and apply them to the plugins
author David Demelier <markand@malikania.fr>
date Sun, 15 May 2016 21:36:04 +0200
parents 2eda98f3db1b
children 6be066ad2329
files irccd/main.cpp lib/irccd/CMakeSources.cmake lib/irccd/config.cpp lib/irccd/config.hpp lib/irccd/irccd.cpp lib/irccd/irccd.hpp lib/irccd/js-directory.cpp lib/irccd/js-directory.hpp lib/irccd/js-elapsed-timer.cpp lib/irccd/js-elapsed-timer.hpp lib/irccd/js-file.cpp lib/irccd/js-file.hpp lib/irccd/js-irccd.cpp lib/irccd/js-irccd.hpp lib/irccd/js-logger.cpp lib/irccd/js-logger.hpp lib/irccd/js-plugin.cpp lib/irccd/js-plugin.hpp lib/irccd/js-server.cpp lib/irccd/js-server.hpp lib/irccd/js-system.cpp lib/irccd/js-system.hpp lib/irccd/js-timer.cpp lib/irccd/js-timer.hpp lib/irccd/js-unicode.cpp lib/irccd/js-unicode.hpp lib/irccd/js-util.cpp lib/irccd/js-util.hpp lib/irccd/mod-directory.cpp lib/irccd/mod-directory.hpp lib/irccd/mod-elapsed-timer.cpp lib/irccd/mod-elapsed-timer.hpp lib/irccd/mod-file.cpp lib/irccd/mod-file.hpp lib/irccd/mod-irccd.cpp lib/irccd/mod-irccd.hpp lib/irccd/mod-logger.cpp lib/irccd/mod-logger.hpp lib/irccd/mod-plugin.cpp lib/irccd/mod-plugin.hpp lib/irccd/mod-server.cpp lib/irccd/mod-server.hpp lib/irccd/mod-system.cpp lib/irccd/mod-system.hpp lib/irccd/mod-timer.cpp lib/irccd/mod-timer.hpp lib/irccd/mod-unicode.cpp lib/irccd/mod-unicode.hpp lib/irccd/mod-util.cpp lib/irccd/mod-util.hpp lib/irccd/module.hpp lib/irccd/plugin-js.cpp lib/irccd/plugin-js.hpp lib/irccd/plugin.hpp lib/irccd/server-state-disconnected.cpp lib/irccd/service-module.cpp lib/irccd/service-module.hpp lib/irccd/service-plugin.cpp lib/irccd/service-plugin.hpp tests/js-elapsedtimer/main.cpp
diffstat 60 files changed, 3992 insertions(+), 3604 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Fri May 13 12:06:06 2016 +0200
+++ b/irccd/main.cpp	Sun May 15 21:36:04 2016 +0200
@@ -249,9 +249,7 @@
 	}
 
 	// [plugin] section.
-	for (const auto &plugin : config.loadPlugins()) {
-		instance->pluginService().add(plugin);
-	}
+	config.loadPlugins(*instance);
 }
 
 } // !namespace
@@ -266,9 +264,7 @@
 	instance = std::make_unique<Irccd>();
 
 	try {
-		Config cfg = open(options);
-
-		load(cfg, options);
+		load(open(options), options);
 	} catch (const std::exception &ex) {
 		log::warning() << "irccd: " << ex.what() << std::endl;
 		return 1;
--- a/lib/irccd/CMakeSources.cmake	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/CMakeSources.cmake	Sun May 15 21:36:04 2016 +0200
@@ -36,18 +36,19 @@
 	${CMAKE_CURRENT_LIST_DIR}/irccdctl.hpp
 	${CMAKE_CURRENT_LIST_DIR}/js.hpp
 	${CMAKE_CURRENT_LIST_DIR}/json.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-directory.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-elapsed-timer.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-file.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-irccd.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-logger.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-plugin.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-server.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-system.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-timer.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-unicode.hpp
-	${CMAKE_CURRENT_LIST_DIR}/js-util.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-directory.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-elapsed-timer.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-file.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-irccd.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-logger.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-plugin.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-server.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-system.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-timer.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-unicode.hpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-util.hpp
 	${CMAKE_CURRENT_LIST_DIR}/logger.hpp
+	${CMAKE_CURRENT_LIST_DIR}/module.hpp
 	${CMAKE_CURRENT_LIST_DIR}/options.hpp
 	${CMAKE_CURRENT_LIST_DIR}/path.hpp
 	${CMAKE_CURRENT_LIST_DIR}/plugin.hpp
@@ -62,6 +63,7 @@
 	${CMAKE_CURRENT_LIST_DIR}/server-state-disconnected.hpp
 	${CMAKE_CURRENT_LIST_DIR}/service.hpp
 	${CMAKE_CURRENT_LIST_DIR}/service-interrupt.hpp
+	${CMAKE_CURRENT_LIST_DIR}/service-module.hpp
 	${CMAKE_CURRENT_LIST_DIR}/service-plugin.hpp
 	${CMAKE_CURRENT_LIST_DIR}/service-rule.hpp
 	${CMAKE_CURRENT_LIST_DIR}/service-server.hpp
@@ -112,17 +114,17 @@
 	${CMAKE_CURRENT_LIST_DIR}/irccd.cpp
 	${CMAKE_CURRENT_LIST_DIR}/irccdctl.cpp
 	${CMAKE_CURRENT_LIST_DIR}/json.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-directory.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-elapsed-timer.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-file.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-irccd.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-logger.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-plugin.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-server.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-system.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-timer.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-unicode.cpp
-	${CMAKE_CURRENT_LIST_DIR}/js-util.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-directory.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-elapsed-timer.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-file.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-irccd.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-logger.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-plugin.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-server.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-system.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-timer.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-unicode.cpp
+	${CMAKE_CURRENT_LIST_DIR}/mod-util.cpp
 	${CMAKE_CURRENT_LIST_DIR}/logger.cpp
 	${CMAKE_CURRENT_LIST_DIR}/options.cpp
 	${CMAKE_CURRENT_LIST_DIR}/path.cpp
@@ -134,6 +136,7 @@
 	${CMAKE_CURRENT_LIST_DIR}/server-state-connecting.cpp
 	${CMAKE_CURRENT_LIST_DIR}/server-state-disconnected.cpp
 	${CMAKE_CURRENT_LIST_DIR}/service-interrupt.cpp
+	${CMAKE_CURRENT_LIST_DIR}/service-module.cpp
 	${CMAKE_CURRENT_LIST_DIR}/service-plugin.cpp
 	${CMAKE_CURRENT_LIST_DIR}/service-rule.cpp
 	${CMAKE_CURRENT_LIST_DIR}/service-server.cpp
--- a/lib/irccd/config.cpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/config.cpp	Sun May 15 21:36:04 2016 +0200
@@ -28,6 +28,7 @@
 #include "plugin-js.hpp"
 #include "rule.hpp"
 #include "server.hpp"
+#include "service-plugin.hpp"
 #include "sysconfig.hpp"
 #include "transport-server.hpp"
 #include "util.hpp"
@@ -610,39 +611,20 @@
 	return servers;
 }
 
-std::vector<std::shared_ptr<Plugin>> Config::loadPlugins() const
+void Config::loadPlugins(Irccd &irccd) const
 {
-	std::vector<std::shared_ptr<Plugin>> plugins;
-
-	// Plugins are defined in only one section.
 	auto it = m_document.find("plugins");
 
-	if (it == m_document.end()) {
-		return plugins;
-	}
-
-	for (const auto &option : *it) {
-		std::string name = option.key();
-		std::string path = option.value();
-
-		try {
-			log::info("plugin {}: loading"_format(name));
+	if (it != m_document.end()) {
+		for (const auto &option : *it) {
+			if (!util::isIdentifierValid(option.key())) {
+				continue;
+			}
 
-			if (path.empty()) {
-				// plugins.push_back(Plugin::find(name, findPluginConfig(name)));
-			} else {
-				log::info("plugin {}: trying {}"_format(name, path));
-				plugins.push_back(std::make_shared<JsPlugin>(name, path, findPluginConfig(name)));
-			}
-		} catch (const duk::ErrorInfo &ex) {
-			log::warning("plugin {}: {}"_format(option.key(), ex.what()));
-			log::warning("plugin {}: {}"_format(option.key(), ex.stack));
-		} catch (const std::exception &ex) {
-			log::warning("plugin {}: {}"_format(option.key(), ex.what()));
+			irccd.pluginService().configure(option.key(), findPluginConfig(option.key()));
+			irccd.pluginService().load(option.key(), option.value());
 		}
 	}
-
-	return plugins;
 }
 
 } // !irccd
--- a/lib/irccd/config.hpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/config.hpp	Sun May 15 21:36:04 2016 +0200
@@ -33,11 +33,10 @@
 
 namespace irccd {
 
+class Irccd;
 class Rule;
-
 class Server;
 class ServerIdentity;
-
 class TransportServer;
 
 /**
@@ -164,9 +163,10 @@
 	/**
 	 * Get the list of defined plugins.
 	 *
+	 * \param irccd the irccd instance
 	 * \return the list of plugins
 	 */
-	std::vector<std::shared_ptr<Plugin>> loadPlugins() const;
+	void loadPlugins(Irccd &irccd) const;
 };
 
 } // !irccd
--- a/lib/irccd/irccd.cpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/irccd.cpp	Sun May 15 21:36:04 2016 +0200
@@ -19,6 +19,7 @@
 #include "irccd.hpp"
 #include "logger.hpp"
 #include "service-interrupt.hpp"
+#include "service-module.hpp"
 #include "service-plugin.hpp"
 #include "service-rule.hpp"
 #include "service-server.hpp"
@@ -36,6 +37,7 @@
 	, m_serverService(std::make_shared<ServerService>(*this))
 	, m_transportService(std::make_shared<TransportService>(*this))
 	, m_ruleService(std::make_shared<RuleService>())
+	, m_moduleService(std::make_shared<ModuleService>())
 	, m_pluginService(std::make_shared<PluginService>(*this))
 {
 	m_services.push_back(m_interruptService);
--- a/lib/irccd/irccd.hpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/irccd.hpp	Sun May 15 21:36:04 2016 +0200
@@ -37,6 +37,7 @@
 
 class InterruptService;
 class Irccd;
+class ModuleService;
 class PluginService;
 class RuleService;
 class ServerService;
@@ -59,6 +60,7 @@
 	std::shared_ptr<ServerService> m_serverService;
 	std::shared_ptr<TransportService> m_transportService;
 	std::shared_ptr<RuleService> m_ruleService;
+	std::shared_ptr<ModuleService> m_moduleService;
 	std::shared_ptr<PluginService> m_pluginService;
 	std::vector<std::shared_ptr<Service>> m_services;
 
@@ -116,6 +118,16 @@
 	}
 
 	/**
+	 * Access the module service.
+	 *
+	 * \return the service
+	 */
+	inline ModuleService &moduleService() noexcept
+	{
+		return *m_moduleService;
+	}
+
+	/**
 	 * Access the plugin service.
 	 *
 	 * \return the service
--- a/lib/irccd/js-directory.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,383 +0,0 @@
-/*
- * js-directory.cpp -- Irccd.Directory API
- *
- * Copyright (c) 2013-2016 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.hpp"
-#include "js.hpp"
-#include "js-irccd.hpp"
-#include "path.hpp"
-#include "sysconfig.hpp"
-
-namespace irccd {
-
-namespace {
-
-std::string path(duk::ContextPtr ctx)
-{
-	duk::push(ctx, duk::This{});
-	duk::getProperty<void>(ctx, -1, "path");
-
-	if (duk::type(ctx, -1) != DUK_TYPE_STRING) {
-		duk::raise(ctx, duk::TypeError("invalid this binding"));
-	}
-
-	std::string ret = duk::get<std::string>(ctx, -1);
-
-	if (ret.empty()) {
-		duk::raise(ctx, duk::TypeError("invalid directory with empty path"));
-	}
-
-	duk::pop(ctx, 2);
-
-	return ret;
-}
-
-/*
- * Find an entry recursively (or not) in a directory using a predicate
- * which can be used to test for regular expression, equality.
- *
- * Do not use this function directly, use:
- *
- * - findName
- * - findRegex
- */
-template <typename Pred>
-std::string findPath(const std::string &base, bool recursive, Pred pred)
-{
-	/*
-	 * For performance reason, we first iterate over all entries that are
-	 * not directories to avoid going deeper recursively if the requested
-	 * file is in the current directory.
-	 */
-	auto entries = fs::readdir(base);
-
-	for (const auto &entry : entries) {
-		if (entry.type != fs::Entry::Dir && pred(entry.name)) {
-			return base + entry.name;
-		}
-	}
-
-	if (!recursive) {
-		return "";
-	}
-
-	for (const auto &entry : entries) {
-		if (entry.type == fs::Entry::Dir) {
-			std::string next = base + entry.name + fs::separator();
-			std::string path = findPath(next, true, pred);
-
-			if (!path.empty()) {
-				return path;
-			}
-		}
-	}
-
-	return "";
-}
-
-/*
- * Helper for finding by equality.
- */
-std::string findName(std::string base, const std::string &pattern, bool recursive)
-{
-	return findPath(base, recursive, [&] (const std::string &entryname) -> bool {
-		return pattern == entryname;
-	});
-}
-
-/*
- * Helper for finding by regular expression
- */
-std::string findRegex(const std::string &base, std::string pattern, bool recursive)
-{
-	std::regex regexp(pattern, std::regex::ECMAScript);
-	std::smatch smatch;
-
-	return findPath(base, recursive, [&] (const std::string &entryname) -> bool {
-		return std::regex_match(entryname, smatch, regexp);
-	});
-}
-
-/*
- * 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 find(duk::ContextPtr ctx, std::string base, bool recursive, int patternIndex)
-{
-	base = path::clean(base);
-
-	try {
-		std::string path;
-
-		if (duk::is<std::string>(ctx, patternIndex)) {
-			path = findName(base, duk::get<std::string>(ctx, patternIndex), recursive);
-		} else {
-			/* Check if it's a valid RegExp object */
-			duk::getGlobal<void>(ctx, "RegExp");
-
-			bool isRegex = duk::instanceof(ctx, patternIndex, -1);
-
-			duk::pop(ctx);
-
-			if (isRegex) {
-				path = findRegex(base, duk::getProperty<std::string>(ctx, patternIndex, "source"), recursive);
-			} else {
-				duk::raise(ctx, duk::TypeError("pattern must be a string or a regex expression"));
-			}
-		}
-
-		if (path.empty()) {
-			return 0;
-		}
-
-		duk::push(ctx, path);
-	} catch (const std::exception &ex) {
-		duk::raise(ctx, duk::Error(ex.what()));
-	}
-
-	return 1;
-}
-
-/*
- * Generic remove function for:
- *
- * - Directory.remove
- * - Directory.prototype.remove
- */
-duk::Ret remove(duk::ContextPtr ctx, const std::string &path, bool recursive)
-{
-	if (!fs::isDirectory(path)) {
-		duk::raise(ctx, SystemError(EINVAL, "not a directory"));
-	}
-
-	if (!recursive) {
-#if defined(_WIN32)
-		::RemoveDirectory(path.c_str());
-#else
-		::remove(path.c_str());
-#endif
-	} else {
-		fs::rmdir(path.c_str());
-	}
-
-	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 methodFind(duk::ContextPtr ctx)
-{
-	return find(ctx, path(ctx), duk::optional<bool>(ctx, 1, false), 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 methodRemove(duk::ContextPtr ctx)
-{
-	return remove(ctx, path(ctx), duk::optional<bool>(ctx, 0, false));
-}
-
-const duk::FunctionMap methods{
-	{ "find",		{ methodFind,		DUK_VARARGS	} },
-	{ "remove",		{ methodRemove,		1		} }
-};
-
-/* --------------------------------------------------------
- * Directory "static" functions
- * -------------------------------------------------------- */
-
-/*
- * Function: Irccd.Directory(path, flags) [constructor]
- * --------------------------------------------------------
- *
- * Opens and read the directory at the specified path.
- *
- * Arguments:
- *   - path, the path to the directory,
- *   - flags, the optional flags (default: 0).
- * Throws:
- *   - Any exception on error
- */
-duk::Ret constructor(duk::ContextPtr ctx)
-{
-	if (!duk_is_constructor_call(ctx)) {
-		return 0;
-	}
-
-	try {
-		std::string path = duk::require<std::string>(ctx, 0);
-		std::int8_t flags = duk::optional<int>(ctx, 1, 0);
-
-		if (!fs::isDirectory(path)) {
-			duk::raise(ctx, SystemError(EINVAL, "not a directory"));
-		}
-
-		std::vector<fs::Entry> list = fs::readdir(path, flags);
-
-		duk::push(ctx, duk::This{});
-		duk::push(ctx, "count");
-		duk::push(ctx, (int)list.size());
-		duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-		duk::push(ctx, "path");
-		duk::push(ctx, path);
-		duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-		duk::push(ctx, "entries");
-		duk::push(ctx, duk::Array{});
-
-		for (unsigned i = 0; i < list.size(); ++i) {
-			duk::push(ctx, duk::Object{});
-			duk::putProperty(ctx, -1, "name", list[i].name);
-			duk::putProperty(ctx, -1, "type", static_cast<int>(list[i].type));
-			duk::putProperty(ctx, -2, (int)i);
-		}
-
-		duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-	} catch (const std::exception &ex) {
-		duk::raise(ctx, SystemError(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 funcFind(duk::ContextPtr ctx)
-{
-	return find(ctx, duk::require<std::string>(ctx, 0), duk::optional<bool>(ctx, 2, false), 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 funcRemove(duk::ContextPtr ctx)
-{
-	return remove(ctx, duk::require<std::string>(ctx, 0), duk::optional<bool>(ctx, 1, false));
-}
-
-/*
- * 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,
- *   - mode, the mode, not available on all platforms.
- * Throws:
- *   - Any exception on error.
- */
-duk::Ret funcMkdir(duk::ContextPtr ctx)
-{
-	try {
-		fs::mkdir(duk::require<std::string>(ctx, 0), duk::optional<int>(ctx, 1, 0700));
-	} catch (const std::exception &ex) {
-		duk::raise(ctx, SystemError(errno, ex.what()));
-	}
-
-	return 0;
-}
-
-const duk::FunctionMap functions{
-	{ "find",		{ funcFind,	DUK_VARARGS } },
-	{ "mkdir",		{ funcMkdir,	DUK_VARARGS } },
-	{ "remove",		{ funcRemove,	DUK_VARARGS } }
-};
-
-const duk::Map<int> constants{
-	{ "Dot",		static_cast<int>(fs::Dot)			},
-	{ "DotDot",		static_cast<int>(fs::DotDot)			},
-	{ "TypeUnknown",	static_cast<int>(fs::Entry::Unknown)		},
-	{ "TypeDir",		static_cast<int>(fs::Entry::Dir)		},
-	{ "TypeFile",		static_cast<int>(fs::Entry::File)		},
-	{ "TypeLink",		static_cast<int>(fs::Entry::Link)		}
-};
-
-} // !namespace
-
-void loadJsDirectory(duk::ContextPtr ctx) noexcept
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Function{constructor, 2});
-	duk::push(ctx, constants);
-	duk::push(ctx, functions);
-	duk::putProperty(ctx, -1, "separator", std::string{fs::separator()});
-	duk::push(ctx, duk::Object{});
-	duk::push(ctx, methods);
-	duk::putProperty(ctx, -2, "prototype");
-	duk::putProperty(ctx, -2, "Directory");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-directory.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/*
- * js-directory.hpp -- Irccd.Directory API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_DIRECTORY_HPP
-
-/**
- * \file js-directory.hpp
- * \brief Irccd.Directory JavaScript API.
- */
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsDirectory(duk::ContextPtr ctx) noexcept;
-
-} // !irccd
-
-#endif // !IRCCD_JS_DIRECTORY_HPP
-
--- a/lib/irccd/js-elapsed-timer.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,134 +0,0 @@
-/*
- * js-elapsed-timer.cpp -- Irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2016 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 "elapsed-timer.hpp"
-#include "js.hpp"
-
-namespace irccd {
-
-namespace duk {
-
-template <>
-class TypeTraits<ElapsedTimer> {
-public:
-	static inline std::string name()
-	{
-		return "\xff""\xff""ElapsedTimer";
-	}
-
-	static inline std::vector<std::string> inherits()
-	{
-		return {};
-	}
-};
-
-} // !duk
-
-namespace {
-
-/*
- * Method: ElapsedTimer.pause
- * ------------------------------------------------------------------
- *
- * Pause the timer, without resetting the current elapsed time stored.
- */
-duk::Ret pause(duk::ContextPtr ctx)
-{
-	duk::self<duk::Pointer<ElapsedTimer>>(ctx)->pause();
-
-	return 0;
-}
-
-/*
- * Method: ElapsedTimer.reset
- * ------------------------------------------------------------------
- *
- * Reset the elapsed time to 0, the status is not modified.
- */
-duk::Ret reset(duk::ContextPtr ctx)
-{
-	duk::self<duk::Pointer<ElapsedTimer>>(ctx)->reset();
-
-	return 0;
-}
-
-/*
- * Method: ElapsedTimer.restart
- * ------------------------------------------------------------------
- *
- * Restart the timer without resetting the current elapsed time.
- */
-duk::Ret restart(duk::ContextPtr ctx)
-{
-	duk::self<duk::Pointer<ElapsedTimer>>(ctx)->restart();
-
-	return 0;
-}
-
-/*
- * Method: ElapsedTimer.elapsed
- * ------------------------------------------------------------------
- *
- * Get the number of elapsed milliseconds.
- *
- * Returns:
- *   The time elapsed.
- */
-duk::Ret elapsed(duk::ContextPtr ctx)
-{
-	duk::push(ctx, (int)duk::self<duk::Pointer<ElapsedTimer>>(ctx)->elapsed());
-
-	return 1;
-}
-
-/*
- * Function: Irccd.ElapsedTimer() [constructor]
- * ------------------------------------------------------------------
- *
- * Construct a new ElapsedTimer object.
- */
-duk::Ret constructor(duk::ContextPtr ctx)
-{
-	duk::construct(ctx, duk::Pointer<ElapsedTimer>{new ElapsedTimer});
-
-	return 0;
-}
-
-const duk::FunctionMap methods{
-	{ "elapsed",	{ elapsed,	0 } },
-	{ "pause",	{ pause,	0 } },
-	{ "reset",	{ reset,	0 } },
-	{ "restart",	{ restart,	0 } }
-};
-
-} // !namespace
-
-void loadJsElapsedTimer(duk::ContextPtr ctx) noexcept
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Function{constructor, 0});
-	duk::push(ctx, duk::Object{});
-	duk::push(ctx, methods);
-	duk::putProperty(ctx, -2, "prototype");
-	duk::putProperty(ctx, -2, "ElapsedTimer");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-elapsed-timer.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/*
- * js-elapsed-timer.hpp -- Irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_ELAPSED_TIMER_HPP
-
-/**
- * \file js-elapsed-timer.hpp
- * \brief Irccd.ElapsedTimer JavaScript API.
- */
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsElapsedTimer(duk::ContextPtr ctx) noexcept;
-
-} // !irccd
-
-#endif // !IRCCD_JS_ELAPSED_TIMER_HPP
-
--- a/lib/irccd/js-file.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,626 +0,0 @@
-/*
- * js-file.cpp -- Irccd.File API
- *
- * Copyright (c) 2013-2016 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 <iterator>
-#include <vector>
-
-#include "sysconfig.hpp"
-
-#if defined(HAVE_STAT)
-#  include <sys/types.h>
-#  include <sys/stat.h>
-#endif
-
-#include "fs.hpp"
-#include "js-irccd.hpp"
-#include "js-file.hpp"
-
-namespace irccd {
-
-#if defined(HAVE_STAT)
-
-/*
- * duk::TypeInfo specialization for struct stat
- * ------------------------------------------------------------------
- */
-
-namespace duk {
-
-template <>
-class TypeTraits<struct stat> {
-public:
-	static void push(ContextPtr ctx, const struct stat &st)
-	{
-		duk::push(ctx, Object{});
-
-#if defined(HAVE_STAT_ST_ATIME)
-		duk::putProperty(ctx, -2, "atime", static_cast<int>(st.st_atime));
-#endif
-#if defined(HAVE_STAT_ST_BLKSIZE)
-		duk::putProperty(ctx, -2, "blksize", static_cast<int>(st.st_blksize));
-#endif
-#if defined(HAVE_STAT_ST_BLOCKS)
-		duk::putProperty(ctx, -2, "blocks", static_cast<int>(st.st_blocks));
-#endif
-#if defined(HAVE_STAT_ST_CTIME)
-		duk::putProperty(ctx, -2, "ctime", static_cast<int>(st.st_ctime));
-#endif
-#if defined(HAVE_STAT_ST_DEV)
-		duk::putProperty(ctx, -2, "dev", static_cast<int>(st.st_dev));
-#endif
-#if defined(HAVE_STAT_ST_GID)
-		duk::putProperty(ctx, -2, "gid", static_cast<int>(st.st_gid));
-#endif
-#if defined(HAVE_STAT_ST_INO)
-		duk::putProperty(ctx, -2, "ino", static_cast<int>(st.st_ino));
-#endif
-#if defined(HAVE_STAT_ST_MODE)
-		duk::putProperty(ctx, -2, "mode", static_cast<int>(st.st_mode));
-#endif
-#if defined(HAVE_STAT_ST_MTIME)
-		duk::putProperty(ctx, -2, "mtime", static_cast<int>(st.st_mtime));
-#endif
-#if defined(HAVE_STAT_ST_NLINK)
-		duk::putProperty(ctx, -2, "nlink", static_cast<int>(st.st_nlink));
-#endif
-#if defined(HAVE_STAT_ST_RDEV)
-		duk::putProperty(ctx, -2, "rdev", static_cast<int>(st.st_rdev));
-#endif
-#if defined(HAVE_STAT_ST_SIZE)
-		duk::putProperty(ctx, -2, "size", static_cast<int>(st.st_size));
-#endif
-#if defined(HAVE_STAT_ST_UID)
-		duk::putProperty(ctx, -2, "uid", static_cast<int>(st.st_uid));
-#endif
-	}
-};
-
-} // !duk
-
-#endif // !HAVE_STAT
-
-namespace {
-
-/*
- * Anonymous helpers.
- * ------------------------------------------------------------------
- */
-
-/* Remove trailing \r for CRLF line style */
-inline std::string clearCr(std::string input)
-{
-	if (input.length() > 0 && input.back() == '\r') {
-		input.pop_back();
-	}
-
-	return input;
-}
-
-/*
- * File methods.
- * ------------------------------------------------------------------
- */
-
-/*
- * Method: File.basename()
- * --------------------------------------------------------
- *
- * Synonym of `Irccd.File.basename(path)` but with the path from the file.
- *
- * Returns:
- *   The base name.
- */
-duk::Ret methodBasename(duk::ContextPtr ctx)
-{
-	duk::push(ctx, fs::baseName(duk::self<duk::Pointer<File>>(ctx)->path()));
-
-	return 1;
-}
-
-/*
- * Method: File.close()
- * --------------------------------------------------------
- *
- * Force close of the file, automatically called when object is collected.
- */
-duk::Ret methodClose(duk::ContextPtr ctx)
-{
-	duk::self<duk::Pointer<File>>(ctx)->close();
-
-	return 0;
-}
-
-/*
- * Method: File.dirname()
- * --------------------------------------------------------
- *
- * Synonym of `Irccd.File.dirname(path)` but with the path from the file.
- *
- * Returns:
- *   The directory name.
- */
-duk::Ret methodDirname(duk::ContextPtr ctx)
-{
-	duk::push(ctx, fs::dirName(duk::self<duk::Pointer<File>>(ctx)->path()));
-
-	return 1;
-}
-
-/*
- * Method: File.lines()
- * --------------------------------------------------------
- *
- * Read all lines and return an array.
- *
- * Returns:
- *   An array with all lines.
- * Throws
- *   - Any exception on error.
- */
-duk::Ret methodLines(duk::ContextPtr ctx)
-{
-	duk::push(ctx, duk::Array{});
-
-	std::FILE *fp = duk::self<duk::Pointer<File>>(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) {
-			duk::putProperty(ctx, -1, i++, clearCr(buffer.substr(0, pos)));
-			buffer.erase(0, pos + 1);
-		}
-	}
-
-	/* Maybe an error in the stream */
-	if (std::ferror(fp)) {
-		duk::raise(ctx, SystemError());
-	}
-
-	/* Missing '\n' in end of file */
-	if (!buffer.empty()) {
-		duk::putProperty(ctx, -1, i++, clearCr(buffer));
-	}
-
-	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).
- * Returns:
- *   The string.
- * Throws:
- *   - Any exception on error.
- */
-duk::Ret methodRead(duk::ContextPtr ctx)
-{
-	auto amount = duk::optional<int>(ctx, 0, -1);
-	auto file = duk::self<duk::Pointer<File>>(ctx);
-
-	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 (!std::feof(file->handle())) {
-				nread = std::fread(&buffer[0], sizeof (buffer[0]), buffer.size(), file->handle());
-
-				if (std::ferror(file->handle())) {
-					duk::raise(ctx, SystemError());
-				}
-
-				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())) {
-				duk::raise(ctx, SystemError());
-			}
-
-			data.resize(total);
-		}
-
-		duk::push(ctx, data);
-	} catch (const std::exception &) {
-		duk::raise(ctx, SystemError());
-	}
-
-	return 1;
-}
-
-/*
- * Method: File.readline()
- * --------------------------------------------------------
- *
- * Read the next line available.
- *
- * Returns:
- *   The next line or undefined if eof.
- * Throws:
- *   - Any exception on error.
- */
-duk::Ret methodReadline(duk::ContextPtr ctx)
-{
-	std::FILE *fp = duk::self<duk::Pointer<File>>(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)) {
-		duk::raise(ctx, SystemError());
-	}
-
-	duk::push(ctx, clearCr(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 methodRemove(duk::ContextPtr ctx)
-{
-	if (::remove(duk::self<duk::Pointer<File>>(ctx)->path().c_str()) < 0) {
-		duk::raise(ctx, SystemError());
-	}
-
-	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 methodSeek(duk::ContextPtr ctx)
-{
-	auto type = duk::require<int>(ctx, 0);
-	auto amount = duk::require<int>(ctx, 1);
-	auto fp = duk::self<duk::Pointer<File>>(ctx)->handle();
-
-	if (fp != nullptr && std::fseek(fp, amount, type) != 0) {
-		duk::raise(ctx, SystemError());
-	}
-
-	return 0;
-}
-
-#if defined(HAVE_STAT)
-
-/*
- * Method: File.stat() [optional]
- * --------------------------------------------------------
- *
- * Synonym of File.stat(path) but with the path from the file.
- *
- * Returns:
- *   The stat information.
- * Throws:
- *   - Any exception on error.
- */
-duk::Ret methodStat(duk::ContextPtr ctx)
-{
-	struct stat st;
-	auto file = duk::self<duk::Pointer<File>>(ctx);
-
-	if (file->handle() == nullptr && ::stat(file->path().c_str(), &st) < 0) {
-		duk::raise(ctx, SystemError());
-	} else {
-		duk::push(ctx, st);
-	}
-
-	return 1;
-}
-
-#endif // !HAVE_STAT
-
-/*
- * Method: File.tell()
- * --------------------------------------------------------
- *
- * Get the actual position in the file.
- *
- * Returns:
- *   The position.
- * Throws:
- *   - Any exception on error.
- */
-duk::Ret methodTell(duk::ContextPtr ctx)
-{
-	auto fp = duk::self<duk::Pointer<File>>(ctx)->handle();
-	long pos;
-
-	if (fp == nullptr) {
-		return 0;
-	}
-
-	if ((pos = std::ftell(fp)) == -1L) {
-		duk::raise(ctx, SystemError());
-	} else {
-		duk::push(ctx, (int)pos);
-	}
-
-	return 1;
-}
-
-/*
- * Method: File.write(data)
- * --------------------------------------------------------
- *
- * Write some characters to the file.
- *
- * Arguments:
- *   - data, the character to write.
- * Returns:
- *   The number of bytes written.
- * Throws:
- *   - Any exception on error.
- */
-duk::Ret methodWrite(duk::ContextPtr ctx)
-{
-	std::FILE *fp = duk::self<duk::Pointer<File>>(ctx)->handle();
-	std::string data = duk::require<std::string>(ctx, 0);
-
-	if (fp == nullptr) {
-		return 0;
-	}
-
-	std::size_t nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
-
-	if (std::ferror(fp)) {
-		duk::raise(ctx, SystemError());
-	}
-
-	duk::push(ctx, (int)nwritten);
-
-	return 1;
-}
-
-const duk::FunctionMap methods{
-	{ "basename",	{ methodBasename,	0	} },
-	{ "close",	{ methodClose,		0	} },
-	{ "dirname",	{ methodDirname,	0	} },
-	{ "lines",	{ methodLines,		0	} },
-	{ "read",	{ methodRead,		1	} },
-	{ "readline",	{ methodReadline,	0	} },
-	{ "remove",	{ methodRemove,		0	} },
-	{ "seek",	{ methodSeek,		2	} },
-#if defined(HAVE_STAT)
-	{ "stat",	{ methodStat,		0	} },
-#endif
-	{ "tell",	{ methodTell,		0	} },
-	{ "write",	{ methodWrite,		1	} },
-};
-
-/*
- * 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 constructor(duk::ContextPtr ctx)
-{
-	if (!duk_is_constructor_call(ctx)) {
-		return 0;
-	}
-
-	std::string path = duk::require<std::string>(ctx, 0);
-	std::string mode = duk::require<std::string>(ctx, 1);
-
-	try {
-		duk::construct(ctx, duk::Pointer<File>{new File(path, mode)});
-	} catch (const std::exception &) {
-		duk::raise(ctx, SystemError());
-	}
-
-	return 0;
-}
-
-/*
- * Function: Irccd.File.basename(path)
- * --------------------------------------------------------
- *
- * Return the file basename as specified in `basename(3)` C function.
- *
- * Arguments:
- *   - path, the path to the file.
- * Returns:
- *   The base name.
- */
-duk::Ret functionBasename(duk::ContextPtr ctx)
-{
-	duk::push(ctx, fs::baseName(duk::require<std::string>(ctx, 0)));
-
-	return 1;
-}
-
-/*
- * Function: Irccd.File.dirname(path)
- * --------------------------------------------------------
- *
- * Return the file directory name as specified in `dirname(3)` C function.
- *
- * Arguments:
- *   - path, the path to the file.
- * Returns:
- *   The directory name.
- */
-duk::Ret functionDirname(duk::ContextPtr ctx)
-{
-	duk::push(ctx, fs::dirName( duk::require<std::string>(ctx, 0)));
-
-	return 1;
-}
-
-/*
- * Function: Irccd.File.exists(path)
- * --------------------------------------------------------
- *
- * Check if the file exists.
- *
- * Arguments:
- *   - path, the path to the file.
- * Returns:
- *   True if exists.
- * Throws:
- *   - Any exception if we don't have access.
- */
-duk::Ret functionExists(duk::ContextPtr ctx)
-{
-	duk::push(ctx, fs::exists(duk::require<std::string>(ctx, 0)));
-
-	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 functionRemove(duk::ContextPtr ctx)
-{
-	if (::remove(duk::require<std::string>(ctx, 0).c_str()) < 0) {
-		duk::raise(ctx, SystemError());
-	}
-
-	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.
- * Returns:
- *   The stat information.
- * Throws:
- *   - Any exception on error.
- */
-duk::Ret functionStat(duk::ContextPtr ctx)
-{
-	struct stat st;
-
-	if (::stat(duk::require<std::string>(ctx, 0).c_str(), &st) < 0) {
-		duk::raise(ctx, SystemError());
-	}
-
-	duk::push(ctx, st);
-
-	return 1;
-}
-
-#endif // !HAVE_STAT
-
-const duk::FunctionMap functions{
-	{ "basename",	{ functionBasename,	1			} },
-	{ "dirname",	{ functionDirname,	1			} },
-	{ "exists",	{ functionExists,	1			} },
-	{ "remove",	{ functionRemove,	1			} },
-#if defined(HAVE_STAT)
-	{ "stat",	{ functionStat,		1			} },
-#endif
-};
-
-const duk::Map<int> constants{
-	{ "SeekCur",	SEEK_CUR },
-	{ "SeekEnd",	SEEK_END },
-	{ "SeekSet",	SEEK_SET },
-};
-
-} // !namespace
-
-void loadJsFile(duk::ContextPtr ctx)
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Function{constructor, 2});
-	duk::push(ctx, constants);
-	duk::push(ctx, functions);
-	duk::push(ctx, duk::Object{});
-	duk::push(ctx, methods);
-	duk::putProperty(ctx, -2, "prototype");
-	duk::putProperty(ctx, -2, "File");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-file.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,191 +0,0 @@
-/*
- * js-file.hpp -- Irccd.File API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_FILE_HPP
-
-/**
- * \file js-file.hpp
- * \brief Irccd.File JavaScript API.
- */
-
-#include <cassert>
-#include <cerrno>
-#include <cstdio>
-#include <cstring>
-#include <functional>
-#include <stdexcept>
-#include <string>
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * \class File
- * \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 m_path;
-	std::FILE *m_stream;
-	std::function<void (std::FILE *)> m_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)
-		: m_path(std::move(path))
-		, m_destructor([] (std::FILE *fp) { std::fclose(fp); })
-	{
-		if ((m_stream = std::fopen(m_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
-		: m_stream(fp)
-		, m_destructor(std::move(destructor))
-	{
-		assert(m_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 m_path;
-	}
-
-	/**
-	 * Get the handle.
-	 *
-	 * \return the handle or nullptr if the stream was closed
-	 */
-	inline std::FILE *handle() noexcept
-	{
-		return m_stream;
-	}
-
-	/**
-	 * Force close, can be safely called multiple times.
-	 */
-	inline void close() noexcept
-	{
-		if (m_stream) {
-			m_destructor(m_stream);
-			m_stream = nullptr;
-		}
-	}
-};
-
-namespace duk {
-
-/**
- * \brief JavaScript binding for File.
- */
-template <>
-class TypeTraits<File> {
-public:
-	/**
-	 * Push the File prototype.
-	 *
-	 * \param ctx the context
-	 */
-	static inline void prototype(ContextPtr ctx)
-	{
-		getGlobal<void>(ctx, "Irccd");
-		getGlobal<void>(ctx, "File");
-		getProperty<void>(ctx, -1, "prototype");
-		remove(ctx, -2);
-		remove(ctx, -2);
-	}
-
-	/**
-	 * Get the File signature.
-	 *
-	 * \return File
-	 */
-	static inline std::string name()
-	{
-		return "\xff""\xff""File";
-	}
-
-	/**
-	 * Get the inheritance list.
-	 *
-	 * \return empty
-	 */
-	static inline std::vector<std::string> inherits()
-	{
-		return {};
-	}
-};
-
-} // !duk
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsFile(duk::ContextPtr ctx);
-
-} // !irccd
-
-#endif // !IRCCD_JS_FILE_HPP
-
--- a/lib/irccd/js-irccd.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * js-irccd.cpp -- Irccd API
- *
- * Copyright (c) 2013-2016 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-irccd.hpp"
-#include "sysconfig.hpp"
-
-namespace irccd {
-
-SystemError::SystemError()
-	: m_errno(errno)
-	, m_message(std::strerror(m_errno))
-{
-}
-
-SystemError::SystemError(int e, std::string message)
-	: m_errno(e)
-	, m_message(std::move(message))
-{
-}
-
-void SystemError::raise(duk::ContextPtr ctx) const
-{
-	duk::StackAssert sa(ctx, 1);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::getProperty<void>(ctx, -1, "SystemError");
-	duk::remove(ctx, -2);
-	duk::push(ctx, m_errno);
-	duk::push(ctx, m_message);
-	duk::create(ctx, 2);
-	duk::raise(ctx);
-}
-
-duk::Ret constructor(duk::ContextPtr ctx)
-{
-	duk::push(ctx, duk::This{});
-	duk::putProperty(ctx, -1, "errno", duk::require<int>(ctx, 0));
-	duk::putProperty(ctx, -1, "message", duk::require<std::string>(ctx, 1));
-	duk::putProperty(ctx, -1, "name", "SystemError");
-	duk::pop(ctx);
-
-	return 0;
-}
-
-void loadJsIrccd(duk::Context &ctx)
-{
-	duk::StackAssert sa(ctx);
-
-	/* Irccd */
-	duk::push(ctx, duk::Object{});
-
-	/* Version */
-	duk::push(ctx, duk::Object{});
-	duk::putProperty(ctx, -1, "major", IRCCD_VERSION_MAJOR);
-	duk::putProperty(ctx, -1, "minor", IRCCD_VERSION_MINOR);
-	duk::putProperty(ctx, -1, "patch", IRCCD_VERSION_PATCH);
-	duk::putProperty(ctx, -2, "version");
-
-	/* Create the SystemError that inherits from Error */
-	duk::push(ctx, duk::Function{constructor, 2});
-	duk::push(ctx, duk::Object{});
-	duk::getGlobal<void>(ctx, "Error");
-	duk::getProperty<void>(ctx, -1, "prototype");
-	duk::remove(ctx, -2);
-	duk::setPrototype(ctx, -2);
-	duk::putProperty(ctx, -2, "prototype");
-	duk::putProperty(ctx, -2, "SystemError");
-
-	/* Set Irccd as global */
-	duk::putGlobal(ctx, "Irccd");
-}
-
-} // !irccd
--- a/lib/irccd/js-irccd.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * js-irccd.hpp -- Irccd API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_IRCCD_HPP
-
-/**
- * \file js-irccd.hpp
- * \brief Irccd.Irccd JavaScript API.
- */
-
-#include <cerrno>
-#include <cstring>
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * \brief Custom JavaScript exception for system error.
- */
-class SystemError {
-private:
-	int m_errno;
-	std::string m_message;
-
-public:
-	/**
-	 * Create a system error from the current errno value.
-	 */
-	SystemError();
-
-	/**
-	 * Create a system error with the given errno and message.
-	 *
-	 * \param e the errno number
-	 * \param message the message
-	 */
-	SystemError(int e, std::string message);
-
-	/**
-	 * Raise the SystemError.
-	 *
-	 * \param ctx the context
-	 */
-	void raise(duk::ContextPtr ctx) const;
-};
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsIrccd(duk::Context &ctx);
-
-} // !irccd
-
-#endif // !IRCCD_JS_IRCCD_HPP
--- a/lib/irccd/js-logger.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-/*
- * js-logger.cpp -- Irccd.Logger API
- *
- * Copyright (c) 2013-2016 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-logger.hpp"
-#include "logger.hpp"
-
-namespace irccd {
-
-namespace {
-
-duk::Ret print(duk::ContextPtr ctx, std::ostream &out)
-{
-	/*
-	 * Get the message before we start printing stuff to avoid
-	 * empty lines.
-	 */
-	out << "plugin " << duk::getGlobal<std::string>(ctx, "\xff""\xff""name");
-	out << ": " << duk::require<std::string>(ctx, 0) << std::endl;
-
-	return 0;
-}
-
-/*
- * Function: Irccd.Logger.info(message)
- * --------------------------------------------------------
- *
- * Write a verbose message.
- *
- * Arguments:
- *   - message, the message.
- */
-duk::Ret info(duk::ContextPtr ctx)
-{
-	return print(ctx, log::info());
-}
-
-/*
- * Function: Irccd.Logger.warning(message)
- * --------------------------------------------------------
- *
- * Write a warning message.
- *
- * Arguments:
- *   - message, the warning.
- */
-duk::Ret warning(duk::ContextPtr 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 debug(duk::ContextPtr ctx)
-{
-	return print(ctx, log::debug());
-}
-
-const duk::FunctionMap functions{
-	{ "info",	{ info,		1 } },
-	{ "warning",	{ warning,	1 } },
-	{ "debug",	{ debug,	1 } }
-};
-
-} // !namespace
-
-void loadJsLogger(duk::ContextPtr ctx)
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Object{});
-	duk::push(ctx, functions);
-	duk::putProperty(ctx, -2, "Logger");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-logger.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * js-logger.hpp -- Irccd.Logger API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_LOGGER_HPP
-
-/**
- * \file js-logger.hpp
- * \brief Irccd.Logger JavaScript API.
- */
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsLogger(duk::ContextPtr ctx);
-
-} // !irccd
-
-#endif // !IRCCD_JS_LOGGER_HPP
--- a/lib/irccd/js-plugin.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +0,0 @@
-/*
- * js-plugin.cpp -- Irccd.Plugin API
- *
- * Copyright (c) 2013-2016 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.hpp"
-#include "plugin.hpp"
-#include "service-plugin.hpp"
-#include "js-plugin.hpp"
-
-namespace irccd {
-
-namespace {
-
-/*
- * Wrap function for these functions because they all takes the same arguments.
- *
- * - load,
- * - reload,
- * - unload.
- */
-template <typename Func>
-duk::Ret wrap(duk::ContextPtr ctx, int nret, Func &&func)
-{
-	std::string name = duk::require<std::string>(ctx, 0);
-
-	try {
-		func(*duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd"), name);
-	} catch (const std::out_of_range &ex) {
-		duk::raise(ctx, duk::ReferenceError(ex.what()));
-	} catch (const std::exception &ex) {
-		duk::raise(ctx, duk::Error(ex.what()));
-	}
-
-	return nret;
-}
-
-/*
- * 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::Ret info(duk::ContextPtr ctx)
-{
-	Plugin *plugin = nullptr;
-
-	if (duk::top(ctx) >= 1) {
-		plugin = duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->pluginService().get(duk::require<std::string>(ctx, 0)).get();
-	} else {
-		plugin = duk::getGlobal<duk::RawPointer<Plugin>>(ctx, "\xff""\xff""plugin");
-	}
-
-	if (!plugin) {
-		return 0;
-	}
-
-	duk::push(ctx, duk::Object{});
-	duk::putProperty(ctx, -1, "name", plugin->name());
-	duk::putProperty(ctx, -1, "author", plugin->author());
-	duk::putProperty(ctx, -1, "license", plugin->license());
-	duk::putProperty(ctx, -1, "summary", plugin->summary());
-	duk::putProperty(ctx, -1, "version", plugin->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::Ret list(duk::ContextPtr ctx)
-{
-	duk::push(ctx, duk::Array{});
-
-	int i = 0;
-	for (const auto &plugin : duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->pluginService().plugins()) {
-		duk::putProperty(ctx, -1, i++, 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::Ret load(duk::ContextPtr ctx)
-{
-	return wrap(ctx, 0, [&] (Irccd &irccd, const std::string &name) {
-		irccd.pluginService().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::Ret reload(duk::ContextPtr ctx)
-{
-	return wrap(ctx, 0, [&] (Irccd &irccd, const std::string &name) {
-		irccd.pluginService().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::Ret unload(duk::ContextPtr ctx)
-{
-	return wrap(ctx, 0, [&] (Irccd &irccd, const std::string &name) {
-		irccd.pluginService().unload(name);
-	});
-}
-
-const duk::FunctionMap functions{
-	{ "info",	{ info,		DUK_VARARGS	} },
-	{ "list",	{ list,		0		} },
-	{ "load",	{ load,		1		} },
-	{ "reload",	{ reload,	1		} },
-	{ "unload",	{ unload,	1		} }
-};
-
-} // !namespace
-
-void loadJsPlugin(duk::Context &ctx) noexcept
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Object{});
-	duk::push(ctx, functions);
-	duk::push(ctx, duk::Object{});
-	duk::putProperty(ctx, -2, "config");
-	duk::putProperty(ctx, -2, "Plugin");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-plugin.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/*
- * js-plugin.hpp -- Irccd.Plugin API
- *
- * Copyright (c) 2013-2016 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 Irccd.Plugin JavaScript API.
- */
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsPlugin(duk::Context &ctx) noexcept;
-
-} // !irccd
-
-#endif // !IRCCD_JS_PLUGIN_HPP
-
--- a/lib/irccd/js-server.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,540 +0,0 @@
-/*
- * js-server.cpp -- Irccd.Server API
- *
- * Copyright (c) 2013-2016 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 <sstream>
-#include <unordered_map>
-
-#include "irccd.hpp"
-#include "js-server.hpp"
-#include "server.hpp"
-#include "service-server.hpp"
-
-namespace irccd {
-
-namespace {
-
-/*
- * Method: Server.cmode(channel, mode)
- * ------------------------------------------------------------------
- *
- * Change a channel mode.
- *
- * Arguments:
- *   - channel, the channel,
- *   - mode, the mode.
- */
-duk::Ret cmode(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->cmode(duk::require<std::string>(ctx, 0), duk::require<std::string>(ctx, 1));
-
-	return 0;
-}
-
-/*
- * Method: Server.cnotice(channel, message)
- * ------------------------------------------------------------------
- *
- * Send a channel notice.
- *
- * Arguments:
- *   - channel, the channel,
- *   - message, the message.
- */
-duk::Ret cnotice(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->cnotice(duk::require<std::string>(ctx, 0), duk::require<std::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 info(duk::ContextPtr ctx)
-{
-	auto server = duk::self<duk::Shared<Server>>(ctx);
-
-	duk::push(ctx, duk::Object{});
-	duk::putProperty(ctx, -1, "name", server->name());
-	duk::putProperty(ctx, -1, "host", server->info().host);
-	duk::putProperty<int>(ctx, -1, "port", server->info().port);
-	duk::putProperty<bool>(ctx, -1, "ssl", server->info().flags & ServerInfo::Ssl);
-	duk::putProperty<bool>(ctx, -1, "sslVerify", server->info().flags & ServerInfo::SslVerify);
-	duk::putProperty(ctx, -1, "commandChar", server->settings().command);
-	duk::putProperty(ctx, -1, "realname", server->identity().realname);
-	duk::putProperty(ctx, -1, "nickname", server->identity().nickname);
-	duk::putProperty(ctx, -1, "username", server->identity().username);
-
-	/* Channels */
-	duk::push(ctx, duk::Array{});
-
-	int i = 0;
-	for (const auto &channel : server->settings().channels) {
-		duk::putProperty(ctx, -1, i++, channel.name);
-	}
-
-	duk::putProperty(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 invite(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->invite(duk::require<std::string>(ctx, 0), duk::require<std::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 join(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->join(duk::require<std::string>(ctx, 0), duk::optional<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 kick(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->kick(
-		duk::require<std::string>(ctx, 0),
-		duk::require<std::string>(ctx, 1),
-		duk::optional<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 me(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->me(duk::require<std::string>(ctx, 0), duk::require<std::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 message(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->message(duk::require<std::string>(ctx, 0), duk::require<std::string>(ctx, 1));
-
-	return 0;
-}
-
-/*
- * Method: Server.mode(mode)
- * ------------------------------------------------------------------
- *
- * Change your mode.
- *
- * Arguments:
- *   - mode, the new mode.
- */
-duk::Ret mode(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->mode(duk::require<std::string>(ctx, 0));
-
-	return 0;
-}
-
-/*
- * Method: Server.names(channel)
- * ------------------------------------------------------------------
- *
- * Get the list of names from a channel.
- *
- * Arguments:
- *   - channel, the channel.
- */
-duk::Ret names(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->names(duk::require<std::string>(ctx, 0));
-
-	return 0;
-}
-
-/*
- * Method: Server.nick(nickname)
- * ------------------------------------------------------------------
- *
- * Change the nickname.
- *
- * Arguments:
- *   - nickname, the nickname.
- */
-duk::Ret nick(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->nick(duk::require<std::string>(ctx, 0));
-
-	return 0;
-}
-
-/*
- * Method: Server.notice(target, message)
- * ------------------------------------------------------------------
- *
- * Send a private notice.
- *
- * Arguments:
- *   - target, the target,
- *   - message, the notice message.
- */
-duk::Ret notice(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->notice(duk::require<std::string>(ctx, 0), duk::require<std::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 part(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->part(duk::require<std::string>(ctx, 0), duk::optional<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 send(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->send(duk::require<std::string>(ctx, 0));
-
-	return 0;
-}
-
-/*
- * Method: Server.topic(channel, topic)
- * ------------------------------------------------------------------
- *
- * Change a channel topic.
- *
- * Arguments:
- *   - channel, the channel,
- *   - topic, the new topic.
- */
-duk::Ret topic(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->topic(duk::require<std::string>(ctx, 0), duk::require<std::string>(ctx, 1));
-
-	return 0;
-}
-
-/*
- * Method: Server.whois(target)
- * ------------------------------------------------------------------
- *
- * Get whois information.
- *
- * Arguments:
- *   - target, the target.
- */
-duk::Ret whois(duk::ContextPtr ctx)
-{
-	duk::self<duk::Shared<Server>>(ctx)->whois(duk::require<std::string>(ctx, 0));
-
-	return 0;
-}
-
-/*
- * Method: Server.toString()
- * ------------------------------------------------------------------
- *
- * Convert the object to std::string, convenience for adding the object
- * as property key.
- *
- * Returns:
- *   The server name (unique).
- */
-duk::Ret toString(duk::ContextPtr ctx)
-{
-	duk::push(ctx, duk::self<duk::Shared<Server>>(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 constructor(duk::ContextPtr ctx)
-{
-	if (!duk_is_constructor_call(ctx))
-		return 0;
-
-	std::string name;
-	ServerInfo info;
-	ServerIdentity identity;
-	ServerSettings settings;
-
-	// Information part.
-	name = duk::getProperty<std::string>(ctx, 0, "name");
-	info.host = duk::getProperty<std::string>(ctx, 0, "host");
-	info.port = duk::optionalProperty<int>(ctx, 0, "port", (int)info.port);
-	info.password = duk::optionalProperty<std::string>(ctx, 0, "password", "");
-
-	if (duk::optionalProperty<bool>(ctx, 0, "ipv6", false)) {
-		info.flags |= ServerInfo::Ipv6;
-	}
-
-	// Identity part.
-	identity.nickname = duk::optionalProperty<std::string>(ctx, 0, "nickname", identity.nickname);
-	identity.username = duk::optionalProperty<std::string>(ctx, 0, "username", identity.username);
-	identity.realname = duk::optionalProperty<std::string>(ctx, 0, "realname", identity.realname);
-	identity.ctcpversion = duk::optionalProperty<std::string>(ctx, 0, "version", identity.ctcpversion);
-
-	// Settings part.
-	for (const auto &chan: duk::getProperty<std::vector<std::string>>(ctx, 0, "channels")) {
-		settings.channels.push_back(Server::splitChannel(chan));
-	}
-
-	settings.reconnectTries = duk::optionalProperty<int>(ctx, 0, "recoTries", (int)settings.reconnectTries);
-	settings.reconnectDelay = duk::optionalProperty<int>(ctx, 0, "recoTimeout", (int)settings.reconnectDelay);
-
-	if (duk::optionalProperty<bool>(ctx, 0, "joinInvite", false)) {
-		settings.flags |= ServerSettings::JoinInvite;
-	}
-	if (duk::optionalProperty<bool>(ctx, 0, "autoRejoin", false)) {
-		settings.flags |= ServerSettings::AutoRejoin;
-	}
-
-	try {
-		duk::construct(ctx, duk::Shared<Server>{std::make_shared<Server>(std::move(name), std::move(info),
-							std::move(identity), std::move(settings))});
-	} catch (const std::exception &ex) {
-		duk::raise(ctx, duk::Error(ex.what()));
-	}
-
-	return 0;
-}
-
-/*
- * Function: Irccd.Server.add(s)
- * ------------------------------------------------------------------
- *
- * Register a new server to the irccd instance.
- *
- * Arguments:
- *   - s, the server to add.
- */
-duk::Ret add(duk::ContextPtr ctx)
-{
-	auto server = duk::get<duk::Shared<Server>>(ctx, 0);
-
-	if (server) {
-		duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->serverService().add(server);
-	}
-
-	return 0;
-}
-
-/*
- * Function: Irccd.Server.find(name)
- * ------------------------------------------------------------------
- *
- * Find a server by name.
- *
- * Arguments:
- *   - name, the server name
- * Returns:
- *   The server object or undefined if not found.
- */
-duk::Ret find(duk::ContextPtr ctx)
-{
-	const auto name = duk::require<std::string>(ctx, 0);
-	const auto irccd = duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd");
-
-	try {
-		duk::push(ctx, duk::Shared<Server>{irccd->serverService().require(name)});
-	} catch (...) {
-		return 0;
-	}
-
-	return 1;
-}
-
-/*
- * Function: Irccd.Server.list()
- * ------------------------------------------------------------------
- *
- * Get the map of all loaded servers.
- *
- * Returns:
- *   An object with string-to-servers pairs.
- */
-duk::Ret list(duk::ContextPtr ctx)
-{
-	duk::push(ctx, duk::Object{});
-
-	for (const auto &server : duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->serverService().servers()) {
-		duk::putProperty(ctx, -1, server->name(), duk::Shared<Server>{server});
-	}
-
-	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 remove(duk::ContextPtr ctx)
-{
-	duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->serverService().remove(duk::require<std::string>(ctx, 0));
-
-	return 0;
-}
-
-const duk::FunctionMap 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		} }
-};
-
-const duk::FunctionMap functions{
-	{ "add",	{ add,		1		} },
-	{ "find",	{ find,		1		} },
-	{ "list",	{ list,		0		} },
-	{ "remove",	{ remove,	1		} }
-};
-
-} // !namespace
-
-void loadJsServer(duk::ContextPtr ctx)
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Function{constructor, 1});
-	duk::push(ctx, functions);
-	duk::push(ctx, duk::Object());
-	duk::push(ctx, methods);
-	duk::putProperty(ctx, -2, "prototype");
-	duk::putProperty(ctx, -2, "Server");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-server.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/*
- * js-server.hpp -- Irccd.Server API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_SERVER_HPP
-
-/**
- * \file js-server.hpp
- * \brief Irccd.Server JavaScript API.
- */
-
-#include "js.hpp"
-#include "server.hpp"
-
-namespace irccd {
-
-namespace duk {
-
-/**
- * \brief JavaScript binding for Server.
- */
-template <>
-class TypeTraits<irccd::Server> {
-public:
-	/**
-	 * Push the Server prototype.
-	 *
-	 * \param ctx the context
-	 */
-	static inline void prototype(ContextPtr ctx)
-	{
-		getGlobal<void>(ctx, "Irccd");
-		getProperty<void>(ctx, -1, "Server");
-		getProperty<void>(ctx, -1, "prototype");
-		remove(ctx, -2);
-		remove(ctx, -2);
-	}
-
-	/**
-	 * Get the Server signature.
-	 *
-	 * \return Server
-	 */
-	static inline std::string name()
-	{
-		return "\xff""\xff""Server";
-	}
-
-	/**
-	 * Get the inheritance list.
-	 *
-	 * \return empty
-	 */
-	static inline std::vector<std::string> inherits()
-	{
-		return {};
-	}
-};
-
-} // !duk
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsServer(duk::ContextPtr ctx);
-
-} // !irccd
-
-#endif // !IRCCD_JS_SERVER_HPP
--- a/lib/irccd/js-system.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,237 +0,0 @@
-/*
- * js-system.cpp -- Irccd.System API
- *
- * Copyright (c) 2013-2016 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 "sysconfig.hpp"
-
-#if defined(HAVE_POPEN)
-#  include <cstdio>
-#endif
-
-#include "js-file.hpp"
-#include "js-irccd.hpp"
-#include "js-system.hpp"
-#include "system.hpp"
-
-namespace irccd {
-
-namespace {
-
-/*
- * Function: Irccd.System.env(key)
- * ------------------------------------------------------------------
- *
- * Get an environment system variable.
- *
- * Arguments:
- *   - key, the environment variable.
- * Returns:
- *   The value.
- */
-int env(duk::ContextPtr ctx)
-{
-	duk::push(ctx, sys::env(duk::get<std::string>(ctx, 0)));
-
-	return 1;
-}
-
-/*
- * Function: Irccd.System.exec(cmd)
- * ------------------------------------------------------------------
- *
- * Execute a system command.
- *
- * Arguments:
- *   - cmd, the command to execute.
- */
-int exec(duk::ContextPtr ctx)
-{
-	std::system(duk::get<const char *>(ctx, 0));
-
-	return 0;
-}
-
-/*
- * Function: Irccd.System.home()
- * ------------------------------------------------------------------
- *
- * Get the operating system user's home.
- *
- * Returns:
- *   The user home directory.
- */
-int home(duk::ContextPtr ctx)
-{
-	duk::push(ctx, sys::home());
-
-	return 1;
-}
-
-/*
- * Function: Irccd.System.name()
- * ------------------------------------------------------------------
- *
- * Get the operating system name.
- *
- * Returns:
- *   The system name.
- */
-int name(duk::ContextPtr ctx)
-{
-	duk::push(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.SystemError on failures.
- */
-int popen(duk::ContextPtr ctx)
-{
-	auto fp = ::popen(duk::require<const char *>(ctx, 0), duk::require<const char *>(ctx, 1));
-
-	if (fp == nullptr) {
-		duk::raise(ctx, SystemError{});
-	}
-
-	duk::push(ctx, duk::Pointer<File>{new File(fp, [] (std::FILE *fp) { ::pclose(fp); })});
-
-	return 1;
-}
-
-#endif // !HAVE_POPEN
-
-/*
- * Function: Irccd.System.sleep(delay)
- * ------------------------------------------------------------------
- *
- * Sleep the main loop for the specific delay in seconds.
- */
-int sleep(duk::ContextPtr 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.
- */
-int ticks(duk::ContextPtr ctx)
-{
-	duk::push(ctx, static_cast<int>(sys::ticks()));
-
-	return 1;
-}
-
-/*
- * Function: Irccd.System.usleep(delay)
- * ------------------------------------------------------------------
- *
- * Sleep the main loop for the specific delay in microseconds.
- */
-int usleep(duk::ContextPtr 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.
- */
-int uptime(duk::ContextPtr ctx)
-{
-	duk::push<int>(ctx, sys::uptime());
-
-	return 0;
-}
-
-/*
- * Function: Irccd.System.version()
- * ------------------------------------------------------------------
- *
- * Get the operating system version.
- *
- * Returns:
- *   The system version.
- */
-int version(duk::ContextPtr ctx)
-{
-	duk::push(ctx, sys::version());
-
-	return 1;
-}
-
-const duk::FunctionMap 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	} }
-};
-
-} // !namespace
-
-void loadJsSystem(duk::ContextPtr ctx)
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Object{});
-	duk::push(ctx, functions);
-	duk::putProperty(ctx, -2, "System");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-system.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * js-system.hpp -- Irccd.System API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_SYSTEM_HPP
-
-/**
- * \file js-system.hpp
- * \brief Irccd.System JavaScript API.
- */
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsSystem(duk::ContextPtr ctx);
-
-} // !irccd
-
-#endif // !IRCCD_JS_SYSTEM_HPP
--- a/lib/irccd/js-timer.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-/*
- * js-timer.cpp -- Irccd.Timer API
- *
- * Copyright (c) 2013-2016 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 <cstdint>
-
-#include "js.hpp"
-#include "plugin-js.hpp"
-#include "timer.hpp"
-
-namespace irccd {
-
-namespace duk {
-
-template <>
-class TypeTraits<Timer> {
-public:
-	static std::string name()
-	{
-		return "\xff""\xff""Timer";
-	}
-
-	static std::vector<std::string> inherits()
-	{
-		return {};
-	}
-};
-
-} // !duk
-
-namespace {
-
-/*
- * Method: Timer.start()
- * --------------------------------------------------------
- *
- * Start the timer. If the timer is already started the method
- * is a no-op.
- */
-duk::Ret start(duk::ContextPtr ctx)
-{
-	auto timer = duk::self<duk::Shared<Timer>>(ctx);
-
-	if (!timer->isRunning()) {
-		timer->start();
-	}
-
-	return 0;
-}
-
-/*
- * Method: Timer.stop()
- * --------------------------------------------------------
- *
- * Stop the timer.
- */
-duk::Ret stop(duk::ContextPtr ctx)
-{
-	auto timer = duk::self<duk::Shared<Timer>>(ctx);
-
-	if (timer->isRunning()) {
-		timer->stop();
-	}
-
-	return 0;
-}
-
-const duk::FunctionMap methods{
-	{ "start",	{ start,	0 } },
-	{ "stop",	{ stop,		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 constructor(duk::ContextPtr ctx)
-{
-	int type = duk::require<int>(ctx, 0);
-	int delay = duk::require<int>(ctx, 1);
-
-	if (!duk::is<duk::Function>(ctx, 2)) {
-		duk::raise(ctx, duk::TypeError("missing callback function"));
-	}
-
-	auto timer = std::make_shared<Timer>(static_cast<TimerType>(type), delay);
-
-	/* Add this timer to the underlying plugin */
-	duk::getGlobal<duk::RawPointer<JsPlugin>>(ctx, "\xff""\xff""plugin")->addTimer(timer);
-
-	/* Construct object */
-	duk::construct(ctx, duk::Shared<Timer>{timer});
-
-	/* Now store the JavaScript function to call */
-	duk::dup(ctx, 2);
-	duk::putGlobal(ctx, "\xff""\xff""timer-" + std::to_string(reinterpret_cast<std::intptr_t>(timer.get())));
-
-	return 0;
-}
-
-const duk::Map<int> constants{
-	{ "Single",	static_cast<int>(TimerType::Single) },
-	{ "Repeat",	static_cast<int>(TimerType::Repeat) },
-};
-
-} // !namespace
-
-void loadJsTimer(duk::ContextPtr ctx) noexcept
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Function{constructor, 3});
-	duk::push(ctx, constants);
-	duk::push(ctx, duk::Object{});
-	duk::push(ctx, methods);
-	duk::putProperty(ctx, -2, "prototype");
-	duk::putProperty(ctx, -2, "Timer");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-timer.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/*
- * js-timer.hpp -- Irccd.Timer API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_TIMER_HPP
-
-/**
- * \file js-timer.hpp
- * \brief Irccd.Timer JavaScript API.
- */
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsTimer(duk::ContextPtr ctx) noexcept;
-
-} // !irccd
-
-#endif // !IRCCD_JS_TIMER_HPP
-
--- a/lib/irccd/js-unicode.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-/*
- * js-unicode.cpp -- Irccd.Unicode API
- *
- * Copyright (c) 2013-2016 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.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 isDigit(duk::ContextPtr ctx)
-{
-	duk::push(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 isLetter(duk::ContextPtr ctx)
-{
-	duk::push(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 isLower(duk::ContextPtr ctx)
-{
-	duk::push(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 isSpace(duk::ContextPtr ctx)
-{
-	duk::push(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 isTitle(duk::ContextPtr ctx)
-{
-	duk::push(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 isUpper(duk::ContextPtr ctx)
-{
-	duk::push(ctx, unicode::isupper(duk::get<int>(ctx, 0)));
-
-	return 1;
-}
-
-const duk::FunctionMap functions{
-	{ "isDigit",		{ isDigit,	1	} },
-	{ "isLetter",		{ isLetter,	1	} },
-	{ "isLower",		{ isLower,	1	} },
-	{ "isSpace",		{ isSpace,	1	} },
-	{ "isTitle",		{ isTitle,	1	} },
-	{ "isUpper",		{ isUpper,	1	} },
-};
-
-} // !namespace
-
-void loadJsUnicode(duk::ContextPtr ctx)
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Object{});
-	duk::push(ctx, functions);
-	duk::putProperty(ctx, -2, "Unicode");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-unicode.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * js-unicode.cpp -- Irccd.Unicode API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_UNICODE_HPP
-
-/**
- * \file js-unicode.hpp
- * \brief Irccd.Unicode JavaScript API.
- */
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsUnicode(duk::ContextPtr ctx);
-
-} // !irccd
-
-#endif // !IRCCD_JS_UNICODE_HPP
--- a/lib/irccd/js-util.cpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-/*
- * js-util.cpp -- Irccd.Util API
- *
- * Copyright (c) 2013-2016 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 <libircclient.h>
-
-#include "js-util.hpp"
-#include "util.hpp"
-
-namespace irccd {
-
-namespace duk {
-
-/**
- * Read parameters for Irccd.Util.format function, the object is defined as follow:
- *
- * {
- *   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: ...
- * }
- */
-template <>
-class TypeTraits<util::Substitution> {
-public:
-	static util::Substitution get(ContextPtr ctx, int index)
-	{
-		util::Substitution params;
-
-		if (!duk::is<Object>(ctx, index)) {
-			return params;
-		}
-
-		duk::enumerate(ctx, index, 0, true, [&] (ContextPtr) {
-			if (duk::get<std::string>(ctx, -2) == "date") {
-				params.time = static_cast<time_t>(duk::get<double>(ctx, -1) / 1000);
-			} else {
-				params.keywords.insert({duk::get<std::string>(ctx, -2), duk::get<std::string>(ctx, -1)});
-			}
-		});
-
-		return params;
-	}
-};
-
-} // !duk
-
-namespace {
-
-/*
- * 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 format(duk::ContextPtr ctx)
-{
-	try {
-		duk::push(ctx, util::format(duk::get<std::string>(ctx, 0), duk::get<util::Substitution>(ctx, 1)));
-	} catch (const std::exception &ex) {
-		duk::raise(ctx, duk::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 splituser(duk::ContextPtr ctx)
-{
-	const char *target = duk::require<const char *>(ctx, 0);
-	char nick[32] = {0};
-
-	irc_target_get_nick(target, nick, sizeof (nick) -1);
-	duk::push(ctx, std::string(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 splithost(duk::ContextPtr ctx)
-{
-	const char *target = duk::require<const char *>(ctx, 0);
-	char host[32] = {0};
-
-	irc_target_get_host(target, host, sizeof (host) -1);
-	duk::push(ctx, std::string(host));
-
-	return 1;
-}
-
-const duk::FunctionMap functions{
-	{ "format",		{ format,	DUK_VARARGS	} },
-	{ "splituser",		{ splituser,	1		} },
-	{ "splithost",		{ splithost,	1		} }
-};
-
-} // !namespace
-
-void loadJsUtil(duk::ContextPtr ctx)
-{
-	duk::StackAssert sa(ctx);
-
-	duk::getGlobal<void>(ctx, "Irccd");
-	duk::push(ctx, duk::Object{});
-	duk::push(ctx, functions);
-	duk::putProperty(ctx, -2, "Util");
-	duk::pop(ctx);
-}
-
-} // !irccd
--- a/lib/irccd/js-util.hpp	Fri May 13 12:06:06 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * js-util.hpp -- Irccd.Util API
- *
- * Copyright (c) 2013-2016 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_HPP
-#define IRCCD_JS_UTIL_HPP
-
-/**
- * \file js-util.hpp
- * \brief Irccd.Util JavaScript API.
- */
-
-#include "js.hpp"
-
-namespace irccd {
-
-/**
- * Load the module.
- *
- * \param ctx the context.
- */
-void loadJsUtil(duk::ContextPtr ctx);
-
-} // !irccd
-
-#endif // !IRCCD_JS_UTIL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-directory.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,390 @@
+/*
+ * js-directory.cpp -- Irccd.Directory API
+ *
+ * Copyright (c) 2013-2016 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.hpp"
+#include "js.hpp"
+#include "mod-directory.hpp"
+#include "mod-irccd.hpp"
+#include "path.hpp"
+#include "plugin-js.hpp"
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+namespace {
+
+std::string path(duk::ContextPtr ctx)
+{
+	duk::push(ctx, duk::This{});
+	duk::getProperty<void>(ctx, -1, "path");
+
+	if (duk::type(ctx, -1) != DUK_TYPE_STRING) {
+		duk::raise(ctx, duk::TypeError("invalid this binding"));
+	}
+
+	std::string ret = duk::get<std::string>(ctx, -1);
+
+	if (ret.empty()) {
+		duk::raise(ctx, duk::TypeError("invalid directory with empty path"));
+	}
+
+	duk::pop(ctx, 2);
+
+	return ret;
+}
+
+/*
+ * Find an entry recursively (or not) in a directory using a predicate
+ * which can be used to test for regular expression, equality.
+ *
+ * Do not use this function directly, use:
+ *
+ * - findName
+ * - findRegex
+ */
+template <typename Pred>
+std::string findPath(const std::string &base, bool recursive, Pred pred)
+{
+	/*
+	 * For performance reason, we first iterate over all entries that are
+	 * not directories to avoid going deeper recursively if the requested
+	 * file is in the current directory.
+	 */
+	auto entries = fs::readdir(base);
+
+	for (const auto &entry : entries) {
+		if (entry.type != fs::Entry::Dir && pred(entry.name)) {
+			return base + entry.name;
+		}
+	}
+
+	if (!recursive) {
+		return "";
+	}
+
+	for (const auto &entry : entries) {
+		if (entry.type == fs::Entry::Dir) {
+			std::string next = base + entry.name + fs::separator();
+			std::string path = findPath(next, true, pred);
+
+			if (!path.empty()) {
+				return path;
+			}
+		}
+	}
+
+	return "";
+}
+
+/*
+ * Helper for finding by equality.
+ */
+std::string findName(std::string base, const std::string &pattern, bool recursive)
+{
+	return findPath(base, recursive, [&] (const std::string &entryname) -> bool {
+		return pattern == entryname;
+	});
+}
+
+/*
+ * Helper for finding by regular expression
+ */
+std::string findRegex(const std::string &base, std::string pattern, bool recursive)
+{
+	std::regex regexp(pattern, std::regex::ECMAScript);
+	std::smatch smatch;
+
+	return findPath(base, recursive, [&] (const std::string &entryname) -> bool {
+		return std::regex_match(entryname, smatch, regexp);
+	});
+}
+
+/*
+ * 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 find(duk::ContextPtr ctx, std::string base, bool recursive, int patternIndex)
+{
+	base = path::clean(base);
+
+	try {
+		std::string path;
+
+		if (duk::is<std::string>(ctx, patternIndex)) {
+			path = findName(base, duk::get<std::string>(ctx, patternIndex), recursive);
+		} else {
+			/* Check if it's a valid RegExp object */
+			duk::getGlobal<void>(ctx, "RegExp");
+
+			bool isRegex = duk::instanceof(ctx, patternIndex, -1);
+
+			duk::pop(ctx);
+
+			if (isRegex) {
+				path = findRegex(base, duk::getProperty<std::string>(ctx, patternIndex, "source"), recursive);
+			} else {
+				duk::raise(ctx, duk::TypeError("pattern must be a string or a regex expression"));
+			}
+		}
+
+		if (path.empty()) {
+			return 0;
+		}
+
+		duk::push(ctx, path);
+	} catch (const std::exception &ex) {
+		duk::raise(ctx, duk::Error(ex.what()));
+	}
+
+	return 1;
+}
+
+/*
+ * Generic remove function for:
+ *
+ * - Directory.remove
+ * - Directory.prototype.remove
+ */
+duk::Ret remove(duk::ContextPtr ctx, const std::string &path, bool recursive)
+{
+	if (!fs::isDirectory(path)) {
+		duk::raise(ctx, SystemError(EINVAL, "not a directory"));
+	}
+
+	if (!recursive) {
+#if defined(_WIN32)
+		::RemoveDirectory(path.c_str());
+#else
+		::remove(path.c_str());
+#endif
+	} else {
+		fs::rmdir(path.c_str());
+	}
+
+	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 methodFind(duk::ContextPtr ctx)
+{
+	return find(ctx, path(ctx), duk::optional<bool>(ctx, 1, false), 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 methodRemove(duk::ContextPtr ctx)
+{
+	return remove(ctx, path(ctx), duk::optional<bool>(ctx, 0, false));
+}
+
+const duk::FunctionMap methods{
+	{ "find",		{ methodFind,		DUK_VARARGS	} },
+	{ "remove",		{ methodRemove,		1		} }
+};
+
+/* --------------------------------------------------------
+ * Directory "static" functions
+ * -------------------------------------------------------- */
+
+/*
+ * Function: Irccd.Directory(path, flags) [constructor]
+ * --------------------------------------------------------
+ *
+ * Opens and read the directory at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the directory,
+ *   - flags, the optional flags (default: 0).
+ * Throws:
+ *   - Any exception on error
+ */
+duk::Ret constructor(duk::ContextPtr ctx)
+{
+	if (!duk_is_constructor_call(ctx)) {
+		return 0;
+	}
+
+	try {
+		std::string path = duk::require<std::string>(ctx, 0);
+		std::int8_t flags = duk::optional<int>(ctx, 1, 0);
+
+		if (!fs::isDirectory(path)) {
+			duk::raise(ctx, SystemError(EINVAL, "not a directory"));
+		}
+
+		std::vector<fs::Entry> list = fs::readdir(path, flags);
+
+		duk::push(ctx, duk::This{});
+		duk::push(ctx, "count");
+		duk::push(ctx, (int)list.size());
+		duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+		duk::push(ctx, "path");
+		duk::push(ctx, path);
+		duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+		duk::push(ctx, "entries");
+		duk::push(ctx, duk::Array{});
+
+		for (unsigned i = 0; i < list.size(); ++i) {
+			duk::push(ctx, duk::Object{});
+			duk::putProperty(ctx, -1, "name", list[i].name);
+			duk::putProperty(ctx, -1, "type", static_cast<int>(list[i].type));
+			duk::putProperty(ctx, -2, (int)i);
+		}
+
+		duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+	} catch (const std::exception &ex) {
+		duk::raise(ctx, SystemError(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 funcFind(duk::ContextPtr ctx)
+{
+	return find(ctx, duk::require<std::string>(ctx, 0), duk::optional<bool>(ctx, 2, false), 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 funcRemove(duk::ContextPtr ctx)
+{
+	return remove(ctx, duk::require<std::string>(ctx, 0), duk::optional<bool>(ctx, 1, false));
+}
+
+/*
+ * 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,
+ *   - mode, the mode, not available on all platforms.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk::Ret funcMkdir(duk::ContextPtr ctx)
+{
+	try {
+		fs::mkdir(duk::require<std::string>(ctx, 0), duk::optional<int>(ctx, 1, 0700));
+	} catch (const std::exception &ex) {
+		duk::raise(ctx, SystemError(errno, ex.what()));
+	}
+
+	return 0;
+}
+
+const duk::FunctionMap functions{
+	{ "find",		{ funcFind,	DUK_VARARGS } },
+	{ "mkdir",		{ funcMkdir,	DUK_VARARGS } },
+	{ "remove",		{ funcRemove,	DUK_VARARGS } }
+};
+
+const duk::Map<int> constants{
+	{ "Dot",		static_cast<int>(fs::Dot)			},
+	{ "DotDot",		static_cast<int>(fs::DotDot)			},
+	{ "TypeUnknown",	static_cast<int>(fs::Entry::Unknown)		},
+	{ "TypeDir",		static_cast<int>(fs::Entry::Dir)		},
+	{ "TypeFile",		static_cast<int>(fs::Entry::File)		},
+	{ "TypeLink",		static_cast<int>(fs::Entry::Link)		}
+};
+
+} // !namespace
+
+DirectoryModule::DirectoryModule() noexcept
+	: Module("Irccd.Directory")
+{
+}
+
+void DirectoryModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Function{constructor, 2});
+	duk::push(plugin.context(), constants);
+	duk::push(plugin.context(), functions);
+	duk::putProperty(plugin.context(), -1, "separator", std::string{fs::separator()});
+	duk::push(plugin.context(), duk::Object{});
+	duk::push(plugin.context(), methods);
+	duk::putProperty(plugin.context(), -2, "prototype");
+	duk::putProperty(plugin.context(), -2, "Directory");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-directory.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,51 @@
+/*
+ * mod-directory.hpp -- Irccd.Directory API
+ *
+ * Copyright (c) 2013-2016 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_MOD_DIRECTORY_HPP
+#define IRCCD_MOD_DIRECTORY_HPP
+
+/**
+ * \file mod-directory.hpp
+ * \brief Irccd.Directory JavaScript API.
+ */
+
+#include "module.hpp"
+
+namespace irccd {
+
+class Irccd;
+
+/**
+ * \brief Irccd.Directory JavaScript API.
+ */
+class DirectoryModule : public Module {
+public:
+	/**
+	 * Irccd.Directory.
+	 */
+	DirectoryModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	virtual void load(Irccd &irccd, JsPlugin &plugin);
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_DIRECTORY_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-elapsed-timer.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,140 @@
+/*
+ * js-elapsed-timer.cpp -- Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2016 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 "elapsed-timer.hpp"
+#include "mod-elapsed-timer.hpp"
+#include "plugin-js.hpp"
+
+namespace irccd {
+
+namespace duk {
+
+template <>
+class TypeTraits<ElapsedTimer> {
+public:
+	static inline std::string name()
+	{
+		return "\xff""\xff""ElapsedTimer";
+	}
+
+	static inline std::vector<std::string> inherits()
+	{
+		return {};
+	}
+};
+
+} // !duk
+
+namespace {
+
+/*
+ * Method: ElapsedTimer.pause
+ * ------------------------------------------------------------------
+ *
+ * Pause the timer, without resetting the current elapsed time stored.
+ */
+duk::Ret pause(duk::ContextPtr ctx)
+{
+	duk::self<duk::Pointer<ElapsedTimer>>(ctx)->pause();
+
+	return 0;
+}
+
+/*
+ * Method: ElapsedTimer.reset
+ * ------------------------------------------------------------------
+ *
+ * Reset the elapsed time to 0, the status is not modified.
+ */
+duk::Ret reset(duk::ContextPtr ctx)
+{
+	duk::self<duk::Pointer<ElapsedTimer>>(ctx)->reset();
+
+	return 0;
+}
+
+/*
+ * Method: ElapsedTimer.restart
+ * ------------------------------------------------------------------
+ *
+ * Restart the timer without resetting the current elapsed time.
+ */
+duk::Ret restart(duk::ContextPtr ctx)
+{
+	duk::self<duk::Pointer<ElapsedTimer>>(ctx)->restart();
+
+	return 0;
+}
+
+/*
+ * Method: ElapsedTimer.elapsed
+ * ------------------------------------------------------------------
+ *
+ * Get the number of elapsed milliseconds.
+ *
+ * Returns:
+ *   The time elapsed.
+ */
+duk::Ret elapsed(duk::ContextPtr ctx)
+{
+	duk::push(ctx, (int)duk::self<duk::Pointer<ElapsedTimer>>(ctx)->elapsed());
+
+	return 1;
+}
+
+/*
+ * Function: Irccd.ElapsedTimer() [constructor]
+ * ------------------------------------------------------------------
+ *
+ * Construct a new ElapsedTimer object.
+ */
+duk::Ret constructor(duk::ContextPtr ctx)
+{
+	duk::construct(ctx, duk::Pointer<ElapsedTimer>{new ElapsedTimer});
+
+	return 0;
+}
+
+const duk::FunctionMap methods{
+	{ "elapsed",	{ elapsed,	0 } },
+	{ "pause",	{ pause,	0 } },
+	{ "reset",	{ reset,	0 } },
+	{ "restart",	{ restart,	0 } }
+};
+
+} // !namespace
+
+ElapsedTimerModule::ElapsedTimerModule() noexcept
+	: Module("Irccd.ElapsedTimer")
+{
+}
+
+void ElapsedTimerModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Function{constructor, 0});
+	duk::push(plugin.context(), duk::Object{});
+	duk::push(plugin.context(), methods);
+	duk::putProperty(plugin.context(), -2, "prototype");
+	duk::putProperty(plugin.context(), -2, "ElapsedTimer");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-elapsed-timer.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * mod-elapsed-timer.hpp -- Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2016 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_MOD_ELAPSED_TIMER_HPP
+#define IRCCD_MOD_ELAPSED_TIMER_HPP
+
+/**
+ * \file mod-elapsed-timer.hpp
+ * \brief Irccd.ElapsedTimer JavaScript API.
+ */
+
+#include "module.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Irccd.ElapsedTimer JavaScript API.
+ */
+class ElapsedTimerModule : public Module {
+public:
+	/**
+	 * Irccd.ElapsedTimer.
+	 */
+	ElapsedTimerModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	virtual void load(Irccd &irccd, JsPlugin &plugin);
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_ELAPSED_TIMER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-file.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,632 @@
+/*
+ * js-file.cpp -- Irccd.File API
+ *
+ * Copyright (c) 2013-2016 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 <iterator>
+#include <vector>
+
+#include "sysconfig.hpp"
+
+#if defined(HAVE_STAT)
+#  include <sys/types.h>
+#  include <sys/stat.h>
+#endif
+
+#include "fs.hpp"
+#include "mod-file.hpp"
+#include "mod-irccd.hpp"
+#include "plugin-js.hpp"
+
+namespace irccd {
+
+#if defined(HAVE_STAT)
+
+/*
+ * duk::TypeInfo specialization for struct stat
+ * ------------------------------------------------------------------
+ */
+
+namespace duk {
+
+template <>
+class TypeTraits<struct stat> {
+public:
+	static void push(ContextPtr ctx, const struct stat &st)
+	{
+		duk::push(ctx, Object{});
+
+#if defined(HAVE_STAT_ST_ATIME)
+		duk::putProperty(ctx, -2, "atime", static_cast<int>(st.st_atime));
+#endif
+#if defined(HAVE_STAT_ST_BLKSIZE)
+		duk::putProperty(ctx, -2, "blksize", static_cast<int>(st.st_blksize));
+#endif
+#if defined(HAVE_STAT_ST_BLOCKS)
+		duk::putProperty(ctx, -2, "blocks", static_cast<int>(st.st_blocks));
+#endif
+#if defined(HAVE_STAT_ST_CTIME)
+		duk::putProperty(ctx, -2, "ctime", static_cast<int>(st.st_ctime));
+#endif
+#if defined(HAVE_STAT_ST_DEV)
+		duk::putProperty(ctx, -2, "dev", static_cast<int>(st.st_dev));
+#endif
+#if defined(HAVE_STAT_ST_GID)
+		duk::putProperty(ctx, -2, "gid", static_cast<int>(st.st_gid));
+#endif
+#if defined(HAVE_STAT_ST_INO)
+		duk::putProperty(ctx, -2, "ino", static_cast<int>(st.st_ino));
+#endif
+#if defined(HAVE_STAT_ST_MODE)
+		duk::putProperty(ctx, -2, "mode", static_cast<int>(st.st_mode));
+#endif
+#if defined(HAVE_STAT_ST_MTIME)
+		duk::putProperty(ctx, -2, "mtime", static_cast<int>(st.st_mtime));
+#endif
+#if defined(HAVE_STAT_ST_NLINK)
+		duk::putProperty(ctx, -2, "nlink", static_cast<int>(st.st_nlink));
+#endif
+#if defined(HAVE_STAT_ST_RDEV)
+		duk::putProperty(ctx, -2, "rdev", static_cast<int>(st.st_rdev));
+#endif
+#if defined(HAVE_STAT_ST_SIZE)
+		duk::putProperty(ctx, -2, "size", static_cast<int>(st.st_size));
+#endif
+#if defined(HAVE_STAT_ST_UID)
+		duk::putProperty(ctx, -2, "uid", static_cast<int>(st.st_uid));
+#endif
+	}
+};
+
+} // !duk
+
+#endif // !HAVE_STAT
+
+namespace {
+
+/*
+ * Anonymous helpers.
+ * ------------------------------------------------------------------
+ */
+
+/* Remove trailing \r for CRLF line style */
+inline std::string clearCr(std::string input)
+{
+	if (input.length() > 0 && input.back() == '\r') {
+		input.pop_back();
+	}
+
+	return input;
+}
+
+/*
+ * File methods.
+ * ------------------------------------------------------------------
+ */
+
+/*
+ * Method: File.basename()
+ * --------------------------------------------------------
+ *
+ * Synonym of `Irccd.File.basename(path)` but with the path from the file.
+ *
+ * Returns:
+ *   The base name.
+ */
+duk::Ret methodBasename(duk::ContextPtr ctx)
+{
+	duk::push(ctx, fs::baseName(duk::self<duk::Pointer<File>>(ctx)->path()));
+
+	return 1;
+}
+
+/*
+ * Method: File.close()
+ * --------------------------------------------------------
+ *
+ * Force close of the file, automatically called when object is collected.
+ */
+duk::Ret methodClose(duk::ContextPtr ctx)
+{
+	duk::self<duk::Pointer<File>>(ctx)->close();
+
+	return 0;
+}
+
+/*
+ * Method: File.dirname()
+ * --------------------------------------------------------
+ *
+ * Synonym of `Irccd.File.dirname(path)` but with the path from the file.
+ *
+ * Returns:
+ *   The directory name.
+ */
+duk::Ret methodDirname(duk::ContextPtr ctx)
+{
+	duk::push(ctx, fs::dirName(duk::self<duk::Pointer<File>>(ctx)->path()));
+
+	return 1;
+}
+
+/*
+ * Method: File.lines()
+ * --------------------------------------------------------
+ *
+ * Read all lines and return an array.
+ *
+ * Returns:
+ *   An array with all lines.
+ * Throws
+ *   - Any exception on error.
+ */
+duk::Ret methodLines(duk::ContextPtr ctx)
+{
+	duk::push(ctx, duk::Array{});
+
+	std::FILE *fp = duk::self<duk::Pointer<File>>(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) {
+			duk::putProperty(ctx, -1, i++, clearCr(buffer.substr(0, pos)));
+			buffer.erase(0, pos + 1);
+		}
+	}
+
+	/* Maybe an error in the stream */
+	if (std::ferror(fp)) {
+		duk::raise(ctx, SystemError());
+	}
+
+	/* Missing '\n' in end of file */
+	if (!buffer.empty()) {
+		duk::putProperty(ctx, -1, i++, clearCr(buffer));
+	}
+
+	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).
+ * Returns:
+ *   The string.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk::Ret methodRead(duk::ContextPtr ctx)
+{
+	auto amount = duk::optional<int>(ctx, 0, -1);
+	auto file = duk::self<duk::Pointer<File>>(ctx);
+
+	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 (!std::feof(file->handle())) {
+				nread = std::fread(&buffer[0], sizeof (buffer[0]), buffer.size(), file->handle());
+
+				if (std::ferror(file->handle())) {
+					duk::raise(ctx, SystemError());
+				}
+
+				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())) {
+				duk::raise(ctx, SystemError());
+			}
+
+			data.resize(total);
+		}
+
+		duk::push(ctx, data);
+	} catch (const std::exception &) {
+		duk::raise(ctx, SystemError());
+	}
+
+	return 1;
+}
+
+/*
+ * Method: File.readline()
+ * --------------------------------------------------------
+ *
+ * Read the next line available.
+ *
+ * Returns:
+ *   The next line or undefined if eof.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk::Ret methodReadline(duk::ContextPtr ctx)
+{
+	std::FILE *fp = duk::self<duk::Pointer<File>>(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)) {
+		duk::raise(ctx, SystemError());
+	}
+
+	duk::push(ctx, clearCr(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 methodRemove(duk::ContextPtr ctx)
+{
+	if (::remove(duk::self<duk::Pointer<File>>(ctx)->path().c_str()) < 0) {
+		duk::raise(ctx, SystemError());
+	}
+
+	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 methodSeek(duk::ContextPtr ctx)
+{
+	auto type = duk::require<int>(ctx, 0);
+	auto amount = duk::require<int>(ctx, 1);
+	auto fp = duk::self<duk::Pointer<File>>(ctx)->handle();
+
+	if (fp != nullptr && std::fseek(fp, amount, type) != 0) {
+		duk::raise(ctx, SystemError());
+	}
+
+	return 0;
+}
+
+#if defined(HAVE_STAT)
+
+/*
+ * Method: File.stat() [optional]
+ * --------------------------------------------------------
+ *
+ * Synonym of File.stat(path) but with the path from the file.
+ *
+ * Returns:
+ *   The stat information.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk::Ret methodStat(duk::ContextPtr ctx)
+{
+	struct stat st;
+	auto file = duk::self<duk::Pointer<File>>(ctx);
+
+	if (file->handle() == nullptr && ::stat(file->path().c_str(), &st) < 0) {
+		duk::raise(ctx, SystemError());
+	} else {
+		duk::push(ctx, st);
+	}
+
+	return 1;
+}
+
+#endif // !HAVE_STAT
+
+/*
+ * Method: File.tell()
+ * --------------------------------------------------------
+ *
+ * Get the actual position in the file.
+ *
+ * Returns:
+ *   The position.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk::Ret methodTell(duk::ContextPtr ctx)
+{
+	auto fp = duk::self<duk::Pointer<File>>(ctx)->handle();
+	long pos;
+
+	if (fp == nullptr) {
+		return 0;
+	}
+
+	if ((pos = std::ftell(fp)) == -1L) {
+		duk::raise(ctx, SystemError());
+	} else {
+		duk::push(ctx, (int)pos);
+	}
+
+	return 1;
+}
+
+/*
+ * Method: File.write(data)
+ * --------------------------------------------------------
+ *
+ * Write some characters to the file.
+ *
+ * Arguments:
+ *   - data, the character to write.
+ * Returns:
+ *   The number of bytes written.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk::Ret methodWrite(duk::ContextPtr ctx)
+{
+	std::FILE *fp = duk::self<duk::Pointer<File>>(ctx)->handle();
+	std::string data = duk::require<std::string>(ctx, 0);
+
+	if (fp == nullptr) {
+		return 0;
+	}
+
+	std::size_t nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
+
+	if (std::ferror(fp)) {
+		duk::raise(ctx, SystemError());
+	}
+
+	duk::push(ctx, (int)nwritten);
+
+	return 1;
+}
+
+const duk::FunctionMap methods{
+	{ "basename",	{ methodBasename,	0	} },
+	{ "close",	{ methodClose,		0	} },
+	{ "dirname",	{ methodDirname,	0	} },
+	{ "lines",	{ methodLines,		0	} },
+	{ "read",	{ methodRead,		1	} },
+	{ "readline",	{ methodReadline,	0	} },
+	{ "remove",	{ methodRemove,		0	} },
+	{ "seek",	{ methodSeek,		2	} },
+#if defined(HAVE_STAT)
+	{ "stat",	{ methodStat,		0	} },
+#endif
+	{ "tell",	{ methodTell,		0	} },
+	{ "write",	{ methodWrite,		1	} },
+};
+
+/*
+ * 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 constructor(duk::ContextPtr ctx)
+{
+	if (!duk_is_constructor_call(ctx)) {
+		return 0;
+	}
+
+	std::string path = duk::require<std::string>(ctx, 0);
+	std::string mode = duk::require<std::string>(ctx, 1);
+
+	try {
+		duk::construct(ctx, duk::Pointer<File>{new File(path, mode)});
+	} catch (const std::exception &) {
+		duk::raise(ctx, SystemError());
+	}
+
+	return 0;
+}
+
+/*
+ * Function: Irccd.File.basename(path)
+ * --------------------------------------------------------
+ *
+ * Return the file basename as specified in `basename(3)` C function.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Returns:
+ *   The base name.
+ */
+duk::Ret functionBasename(duk::ContextPtr ctx)
+{
+	duk::push(ctx, fs::baseName(duk::require<std::string>(ctx, 0)));
+
+	return 1;
+}
+
+/*
+ * Function: Irccd.File.dirname(path)
+ * --------------------------------------------------------
+ *
+ * Return the file directory name as specified in `dirname(3)` C function.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Returns:
+ *   The directory name.
+ */
+duk::Ret functionDirname(duk::ContextPtr ctx)
+{
+	duk::push(ctx, fs::dirName( duk::require<std::string>(ctx, 0)));
+
+	return 1;
+}
+
+/*
+ * Function: Irccd.File.exists(path)
+ * --------------------------------------------------------
+ *
+ * Check if the file exists.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Returns:
+ *   True if exists.
+ * Throws:
+ *   - Any exception if we don't have access.
+ */
+duk::Ret functionExists(duk::ContextPtr ctx)
+{
+	duk::push(ctx, fs::exists(duk::require<std::string>(ctx, 0)));
+
+	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 functionRemove(duk::ContextPtr ctx)
+{
+	if (::remove(duk::require<std::string>(ctx, 0).c_str()) < 0) {
+		duk::raise(ctx, SystemError());
+	}
+
+	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.
+ * Returns:
+ *   The stat information.
+ * Throws:
+ *   - Any exception on error.
+ */
+duk::Ret functionStat(duk::ContextPtr ctx)
+{
+	struct stat st;
+
+	if (::stat(duk::require<std::string>(ctx, 0).c_str(), &st) < 0) {
+		duk::raise(ctx, SystemError());
+	}
+
+	duk::push(ctx, st);
+
+	return 1;
+}
+
+#endif // !HAVE_STAT
+
+const duk::FunctionMap functions{
+	{ "basename",	{ functionBasename,	1			} },
+	{ "dirname",	{ functionDirname,	1			} },
+	{ "exists",	{ functionExists,	1			} },
+	{ "remove",	{ functionRemove,	1			} },
+#if defined(HAVE_STAT)
+	{ "stat",	{ functionStat,		1			} },
+#endif
+};
+
+const duk::Map<int> constants{
+	{ "SeekCur",	SEEK_CUR },
+	{ "SeekEnd",	SEEK_END },
+	{ "SeekSet",	SEEK_SET },
+};
+
+} // !namespace
+
+FileModule::FileModule() noexcept
+	: Module("Irccd.File")
+{
+}
+
+void FileModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Function{constructor, 2});
+	duk::push(plugin.context(), constants);
+	duk::push(plugin.context(), functions);
+	duk::push(plugin.context(), duk::Object{});
+	duk::push(plugin.context(), methods);
+	duk::putProperty(plugin.context(), -2, "prototype");
+	duk::putProperty(plugin.context(), -2, "File");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-file.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,202 @@
+/*
+ * mod-file.hpp -- Irccd.File API
+ *
+ * Copyright (c) 2013-2016 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_MOD_FILE_HPP
+#define IRCCD_MOD_FILE_HPP
+
+/**
+ * \file mod-file.hpp
+ * \brief Irccd.File JavaScript API.
+ */
+
+#include <cassert>
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <stdexcept>
+#include <string>
+
+#include "js.hpp"
+#include "module.hpp"
+
+namespace irccd {
+
+class Irccd;
+
+/**
+ * \class File
+ * \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 m_path;
+	std::FILE *m_stream;
+	std::function<void (std::FILE *)> m_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)
+		: m_path(std::move(path))
+		, m_destructor([] (std::FILE *fp) { std::fclose(fp); })
+	{
+		if ((m_stream = std::fopen(m_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
+		: m_stream(fp)
+		, m_destructor(std::move(destructor))
+	{
+		assert(m_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 m_path;
+	}
+
+	/**
+	 * Get the handle.
+	 *
+	 * \return the handle or nullptr if the stream was closed
+	 */
+	inline std::FILE *handle() noexcept
+	{
+		return m_stream;
+	}
+
+	/**
+	 * Force close, can be safely called multiple times.
+	 */
+	inline void close() noexcept
+	{
+		if (m_stream) {
+			m_destructor(m_stream);
+			m_stream = nullptr;
+		}
+	}
+};
+
+namespace duk {
+
+/**
+ * \brief JavaScript binding for File.
+ */
+template <>
+class TypeTraits<File> {
+public:
+	/**
+	 * Push the File prototype.
+	 *
+	 * \param ctx the context
+	 */
+	static inline void prototype(ContextPtr ctx)
+	{
+		getGlobal<void>(ctx, "Irccd");
+		getGlobal<void>(ctx, "File");
+		getProperty<void>(ctx, -1, "prototype");
+		remove(ctx, -2);
+		remove(ctx, -2);
+	}
+
+	/**
+	 * Get the File signature.
+	 *
+	 * \return File
+	 */
+	static inline std::string name()
+	{
+		return "\xff""\xff""File";
+	}
+
+	/**
+	 * Get the inheritance list.
+	 *
+	 * \return empty
+	 */
+	static inline std::vector<std::string> inherits()
+	{
+		return {};
+	}
+};
+
+} // !duk
+
+/**
+ * \brief Irccd.File JavaScript API.
+ */
+class FileModule : public Module {
+public:
+	/**
+	 * Irccd.File.
+	 */
+	FileModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	void load(Irccd &irccd, JsPlugin &plugin) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_FILE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-irccd.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,94 @@
+/*
+ * js-irccd.cpp -- Irccd API
+ *
+ * Copyright (c) 2013-2016 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 "mod-irccd.hpp"
+#include "plugin-js.hpp"
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+SystemError::SystemError()
+	: m_errno(errno)
+	, m_message(std::strerror(m_errno))
+{
+}
+
+SystemError::SystemError(int e, std::string message)
+	: m_errno(e)
+	, m_message(std::move(message))
+{
+}
+
+void SystemError::raise(duk::ContextPtr ctx) const
+{
+	duk::StackAssert sa(ctx, 1);
+
+	duk::getGlobal<void>(ctx, "Irccd");
+	duk::getProperty<void>(ctx, -1, "SystemError");
+	duk::remove(ctx, -2);
+	duk::push(ctx, m_errno);
+	duk::push(ctx, m_message);
+	duk::create(ctx, 2);
+	duk::raise(ctx);
+}
+
+duk::Ret constructor(duk::ContextPtr ctx)
+{
+	duk::push(ctx, duk::This{});
+	duk::putProperty(ctx, -1, "errno", duk::require<int>(ctx, 0));
+	duk::putProperty(ctx, -1, "message", duk::require<std::string>(ctx, 1));
+	duk::putProperty(ctx, -1, "name", "SystemError");
+	duk::pop(ctx);
+
+	return 0;
+}
+
+IrccdModule::IrccdModule() noexcept
+	: Module("Irccd")
+{
+}
+
+void IrccdModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	// Irccd.
+	duk::push(plugin.context(), duk::Object{});
+
+	// Version.
+	duk::push(plugin.context(), duk::Object{});
+	duk::putProperty(plugin.context(), -1, "major", IRCCD_VERSION_MAJOR);
+	duk::putProperty(plugin.context(), -1, "minor", IRCCD_VERSION_MINOR);
+	duk::putProperty(plugin.context(), -1, "patch", IRCCD_VERSION_PATCH);
+	duk::putProperty(plugin.context(), -2, "version");
+
+	// Create the SystemError that inherits from Error.
+	duk::push(plugin.context(), duk::Function{constructor, 2});
+	duk::push(plugin.context(), duk::Object{});
+	duk::getGlobal<void>(plugin.context(), "Error");
+	duk::getProperty<void>(plugin.context(), -1, "prototype");
+	duk::remove(plugin.context(), -2);
+	duk::setPrototype(plugin.context(), -2);
+	duk::putProperty(plugin.context(), -2, "prototype");
+	duk::putProperty(plugin.context(), -2, "SystemError");
+
+	// Set Irccd as global.
+	duk::putGlobal(plugin.context(), "Irccd");
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-irccd.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,86 @@
+/*
+ * mod-irccd.hpp -- Irccd API
+ *
+ * Copyright (c) 2013-2016 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_MOD_IRCCD_HPP
+#define IRCCD_MOD_IRCCD_HPP
+
+/**
+ * \file mod-irccd.hpp
+ * \brief Irccd JavaScript API.
+ */
+
+#include <cerrno>
+#include <cstring>
+#include <string>
+
+#include "js.hpp"
+#include "module.hpp"
+
+namespace irccd {
+
+class Irccd;
+
+/**
+ * \brief Custom JavaScript exception for system error.
+ */
+class SystemError {
+private:
+	int m_errno;
+	std::string m_message;
+
+public:
+	/**
+	 * Create a system error from the current errno value.
+	 */
+	SystemError();
+
+	/**
+	 * Create a system error with the given errno and message.
+	 *
+	 * \param e the errno number
+	 * \param message the message
+	 */
+	SystemError(int e, std::string message);
+
+	/**
+	 * Raise the SystemError.
+	 *
+	 * \param ctx the context
+	 */
+	void raise(duk::ContextPtr ctx) const;
+};
+
+/**
+ * Irccd JavaScript API.
+ */
+class IrccdModule : public Module {
+public:
+	/**
+	 * Irccd.
+	 */
+	IrccdModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	void load(Irccd &irccd, JsPlugin &plugin) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_IRCCD_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-logger.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,105 @@
+/*
+ * mod-logger.cpp -- Irccd.Logger API
+ *
+ * Copyright (c) 2013-2016 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 "mod-logger.hpp"
+#include "logger.hpp"
+#include "plugin-js.hpp"
+
+namespace irccd {
+
+namespace {
+
+duk::Ret print(duk::ContextPtr ctx, std::ostream &out)
+{
+	/*
+	 * Get the message before we start printing stuff to avoid
+	 * empty lines.
+	 */
+	out << "plugin " << duk::getGlobal<std::string>(ctx, "\xff""\xff""name");
+	out << ": " << duk::require<std::string>(ctx, 0) << std::endl;
+
+	return 0;
+}
+
+/*
+ * Function: Irccd.Logger.info(message)
+ * --------------------------------------------------------
+ *
+ * Write a verbose message.
+ *
+ * Arguments:
+ *   - message, the message.
+ */
+duk::Ret info(duk::ContextPtr ctx)
+{
+	return print(ctx, log::info());
+}
+
+/*
+ * Function: Irccd.Logger.warning(message)
+ * --------------------------------------------------------
+ *
+ * Write a warning message.
+ *
+ * Arguments:
+ *   - message, the warning.
+ */
+duk::Ret warning(duk::ContextPtr 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 debug(duk::ContextPtr ctx)
+{
+	return print(ctx, log::debug());
+}
+
+const duk::FunctionMap functions{
+	{ "info",	{ info,		1 } },
+	{ "warning",	{ warning,	1 } },
+	{ "debug",	{ debug,	1 } }
+};
+
+} // !namespace
+
+LoggerModule::LoggerModule() noexcept
+	: Module("Irccd.Logger")
+{
+}
+
+void LoggerModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Object{});
+	duk::push(plugin.context(), functions);
+	duk::putProperty(plugin.context(), -2, "Logger");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-logger.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * mod-logger.hpp -- Irccd.Logger API
+ *
+ * Copyright (c) 2013-2016 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_MOD_LOGGER_HPP
+#define IRCCD_MOD_LOGGER_HPP
+
+/**
+ * \file mod-logger.hpp
+ * \brief Irccd.Logger JavaScript API.
+ */
+
+#include "module.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Irccd.Logger JavaScript API.
+ */
+class LoggerModule : public Module {
+public:
+	/**
+	 * Irccd.Logger.
+	 */
+	LoggerModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	void load(Irccd &irccd, JsPlugin &plugin) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_LOGGER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-plugin.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,200 @@
+/*
+ * js-plugin.cpp -- Irccd.Plugin API
+ *
+ * Copyright (c) 2013-2016 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.hpp"
+#include "plugin-js.hpp"
+#include "service-plugin.hpp"
+#include "mod-plugin.hpp"
+
+namespace irccd {
+
+namespace {
+
+/*
+ * Wrap function for these functions because they all takes the same arguments.
+ *
+ * - load,
+ * - reload,
+ * - unload.
+ */
+template <typename Func>
+duk::Ret wrap(duk::ContextPtr ctx, int nret, Func &&func)
+{
+	std::string name = duk::require<std::string>(ctx, 0);
+
+	try {
+		func(*duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd"), name);
+	} catch (const std::out_of_range &ex) {
+		duk::raise(ctx, duk::ReferenceError(ex.what()));
+	} catch (const std::exception &ex) {
+		duk::raise(ctx, duk::Error(ex.what()));
+	}
+
+	return nret;
+}
+
+/*
+ * 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::Ret info(duk::ContextPtr ctx)
+{
+	Plugin *plugin = nullptr;
+
+	if (duk::top(ctx) >= 1) {
+		plugin = duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->pluginService().get(duk::require<std::string>(ctx, 0)).get();
+	} else {
+		plugin = duk::getGlobal<duk::RawPointer<Plugin>>(ctx, "\xff""\xff""plugin");
+	}
+
+	if (!plugin) {
+		return 0;
+	}
+
+	duk::push(ctx, duk::Object{});
+	duk::putProperty(ctx, -1, "name", plugin->name());
+	duk::putProperty(ctx, -1, "author", plugin->author());
+	duk::putProperty(ctx, -1, "license", plugin->license());
+	duk::putProperty(ctx, -1, "summary", plugin->summary());
+	duk::putProperty(ctx, -1, "version", plugin->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::Ret list(duk::ContextPtr ctx)
+{
+	duk::push(ctx, duk::Array{});
+
+	int i = 0;
+	for (const auto &plugin : duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->pluginService().plugins()) {
+		duk::putProperty(ctx, -1, i++, 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::Ret load(duk::ContextPtr ctx)
+{
+	return wrap(ctx, 0, [&] (Irccd &irccd, const std::string &name) {
+		irccd.pluginService().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::Ret reload(duk::ContextPtr ctx)
+{
+	return wrap(ctx, 0, [&] (Irccd &irccd, const std::string &name) {
+		irccd.pluginService().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::Ret unload(duk::ContextPtr ctx)
+{
+	return wrap(ctx, 0, [&] (Irccd &irccd, const std::string &name) {
+		irccd.pluginService().unload(name);
+	});
+}
+
+const duk::FunctionMap functions{
+	{ "info",	{ info,		DUK_VARARGS	} },
+	{ "list",	{ list,		0		} },
+	{ "load",	{ load,		1		} },
+	{ "reload",	{ reload,	1		} },
+	{ "unload",	{ unload,	1		} }
+};
+
+} // !namespace
+
+PluginModule::PluginModule() noexcept
+	: Module("Irccd.Plugin")
+{
+}
+
+void PluginModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Object{});
+	duk::push(plugin.context(), functions);
+	duk::push(plugin.context(), duk::Object{});
+	duk::putProperty(plugin.context(), -2, "config");
+	duk::putProperty(plugin.context(), -2, "Plugin");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-plugin.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * mod-plugin.hpp -- Irccd.Plugin API
+ *
+ * Copyright (c) 2013-2016 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_MOD_PLUGIN_HPP
+#define IRCCD_MOD_PLUGIN_HPP
+
+/**
+ * \file mod-plugin.hpp
+ * \brief Irccd.Plugin JavaScript API.
+ */
+
+#include "module.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Irccd.Plugin JavaScript API.
+ */
+class PluginModule : public Module {
+public:
+	/**
+	 * Irccd.Plugin.
+	 */
+	PluginModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	virtual void load(Irccd &irccd, JsPlugin &plugin);
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_PLUGIN_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-server.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,546 @@
+/*
+ * js-server.cpp -- Irccd.Server API
+ *
+ * Copyright (c) 2013-2016 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 <sstream>
+#include <unordered_map>
+
+#include "irccd.hpp"
+#include "mod-server.hpp"
+#include "plugin-js.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace {
+
+/*
+ * Method: Server.cmode(channel, mode)
+ * ------------------------------------------------------------------
+ *
+ * Change a channel mode.
+ *
+ * Arguments:
+ *   - channel, the channel,
+ *   - mode, the mode.
+ */
+duk::Ret cmode(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->cmode(duk::require<std::string>(ctx, 0), duk::require<std::string>(ctx, 1));
+
+	return 0;
+}
+
+/*
+ * Method: Server.cnotice(channel, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a channel notice.
+ *
+ * Arguments:
+ *   - channel, the channel,
+ *   - message, the message.
+ */
+duk::Ret cnotice(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->cnotice(duk::require<std::string>(ctx, 0), duk::require<std::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 info(duk::ContextPtr ctx)
+{
+	auto server = duk::self<duk::Shared<Server>>(ctx);
+
+	duk::push(ctx, duk::Object{});
+	duk::putProperty(ctx, -1, "name", server->name());
+	duk::putProperty(ctx, -1, "host", server->info().host);
+	duk::putProperty<int>(ctx, -1, "port", server->info().port);
+	duk::putProperty<bool>(ctx, -1, "ssl", server->info().flags & ServerInfo::Ssl);
+	duk::putProperty<bool>(ctx, -1, "sslVerify", server->info().flags & ServerInfo::SslVerify);
+	duk::putProperty(ctx, -1, "commandChar", server->settings().command);
+	duk::putProperty(ctx, -1, "realname", server->identity().realname);
+	duk::putProperty(ctx, -1, "nickname", server->identity().nickname);
+	duk::putProperty(ctx, -1, "username", server->identity().username);
+
+	/* Channels */
+	duk::push(ctx, duk::Array{});
+
+	int i = 0;
+	for (const auto &channel : server->settings().channels) {
+		duk::putProperty(ctx, -1, i++, channel.name);
+	}
+
+	duk::putProperty(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 invite(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->invite(duk::require<std::string>(ctx, 0), duk::require<std::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 join(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->join(duk::require<std::string>(ctx, 0), duk::optional<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 kick(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->kick(
+		duk::require<std::string>(ctx, 0),
+		duk::require<std::string>(ctx, 1),
+		duk::optional<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 me(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->me(duk::require<std::string>(ctx, 0), duk::require<std::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 message(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->message(duk::require<std::string>(ctx, 0), duk::require<std::string>(ctx, 1));
+
+	return 0;
+}
+
+/*
+ * Method: Server.mode(mode)
+ * ------------------------------------------------------------------
+ *
+ * Change your mode.
+ *
+ * Arguments:
+ *   - mode, the new mode.
+ */
+duk::Ret mode(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->mode(duk::require<std::string>(ctx, 0));
+
+	return 0;
+}
+
+/*
+ * Method: Server.names(channel)
+ * ------------------------------------------------------------------
+ *
+ * Get the list of names from a channel.
+ *
+ * Arguments:
+ *   - channel, the channel.
+ */
+duk::Ret names(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->names(duk::require<std::string>(ctx, 0));
+
+	return 0;
+}
+
+/*
+ * Method: Server.nick(nickname)
+ * ------------------------------------------------------------------
+ *
+ * Change the nickname.
+ *
+ * Arguments:
+ *   - nickname, the nickname.
+ */
+duk::Ret nick(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->nick(duk::require<std::string>(ctx, 0));
+
+	return 0;
+}
+
+/*
+ * Method: Server.notice(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a private notice.
+ *
+ * Arguments:
+ *   - target, the target,
+ *   - message, the notice message.
+ */
+duk::Ret notice(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->notice(duk::require<std::string>(ctx, 0), duk::require<std::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 part(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->part(duk::require<std::string>(ctx, 0), duk::optional<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 send(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->send(duk::require<std::string>(ctx, 0));
+
+	return 0;
+}
+
+/*
+ * Method: Server.topic(channel, topic)
+ * ------------------------------------------------------------------
+ *
+ * Change a channel topic.
+ *
+ * Arguments:
+ *   - channel, the channel,
+ *   - topic, the new topic.
+ */
+duk::Ret topic(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->topic(duk::require<std::string>(ctx, 0), duk::require<std::string>(ctx, 1));
+
+	return 0;
+}
+
+/*
+ * Method: Server.whois(target)
+ * ------------------------------------------------------------------
+ *
+ * Get whois information.
+ *
+ * Arguments:
+ *   - target, the target.
+ */
+duk::Ret whois(duk::ContextPtr ctx)
+{
+	duk::self<duk::Shared<Server>>(ctx)->whois(duk::require<std::string>(ctx, 0));
+
+	return 0;
+}
+
+/*
+ * Method: Server.toString()
+ * ------------------------------------------------------------------
+ *
+ * Convert the object to std::string, convenience for adding the object
+ * as property key.
+ *
+ * Returns:
+ *   The server name (unique).
+ */
+duk::Ret toString(duk::ContextPtr ctx)
+{
+	duk::push(ctx, duk::self<duk::Shared<Server>>(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 constructor(duk::ContextPtr ctx)
+{
+	if (!duk_is_constructor_call(ctx))
+		return 0;
+
+	std::string name;
+	ServerInfo info;
+	ServerIdentity identity;
+	ServerSettings settings;
+
+	// Information part.
+	name = duk::getProperty<std::string>(ctx, 0, "name");
+	info.host = duk::getProperty<std::string>(ctx, 0, "host");
+	info.port = duk::optionalProperty<int>(ctx, 0, "port", (int)info.port);
+	info.password = duk::optionalProperty<std::string>(ctx, 0, "password", "");
+
+	if (duk::optionalProperty<bool>(ctx, 0, "ipv6", false)) {
+		info.flags |= ServerInfo::Ipv6;
+	}
+
+	// Identity part.
+	identity.nickname = duk::optionalProperty<std::string>(ctx, 0, "nickname", identity.nickname);
+	identity.username = duk::optionalProperty<std::string>(ctx, 0, "username", identity.username);
+	identity.realname = duk::optionalProperty<std::string>(ctx, 0, "realname", identity.realname);
+	identity.ctcpversion = duk::optionalProperty<std::string>(ctx, 0, "version", identity.ctcpversion);
+
+	// Settings part.
+	for (const auto &chan: duk::getProperty<std::vector<std::string>>(ctx, 0, "channels")) {
+		settings.channels.push_back(Server::splitChannel(chan));
+	}
+
+	settings.reconnectTries = duk::optionalProperty<int>(ctx, 0, "recoTries", (int)settings.reconnectTries);
+	settings.reconnectDelay = duk::optionalProperty<int>(ctx, 0, "recoTimeout", (int)settings.reconnectDelay);
+
+	if (duk::optionalProperty<bool>(ctx, 0, "joinInvite", false)) {
+		settings.flags |= ServerSettings::JoinInvite;
+	}
+	if (duk::optionalProperty<bool>(ctx, 0, "autoRejoin", false)) {
+		settings.flags |= ServerSettings::AutoRejoin;
+	}
+
+	try {
+		duk::construct(ctx, duk::Shared<Server>{std::make_shared<Server>(std::move(name), std::move(info),
+							std::move(identity), std::move(settings))});
+	} catch (const std::exception &ex) {
+		duk::raise(ctx, duk::Error(ex.what()));
+	}
+
+	return 0;
+}
+
+/*
+ * Function: Irccd.Server.add(s)
+ * ------------------------------------------------------------------
+ *
+ * Register a new server to the irccd instance.
+ *
+ * Arguments:
+ *   - s, the server to add.
+ */
+duk::Ret add(duk::ContextPtr ctx)
+{
+	auto server = duk::get<duk::Shared<Server>>(ctx, 0);
+
+	if (server) {
+		duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->serverService().add(server);
+	}
+
+	return 0;
+}
+
+/*
+ * Function: Irccd.Server.find(name)
+ * ------------------------------------------------------------------
+ *
+ * Find a server by name.
+ *
+ * Arguments:
+ *   - name, the server name
+ * Returns:
+ *   The server object or undefined if not found.
+ */
+duk::Ret find(duk::ContextPtr ctx)
+{
+	const auto name = duk::require<std::string>(ctx, 0);
+	const auto irccd = duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd");
+
+	try {
+		duk::push(ctx, duk::Shared<Server>{irccd->serverService().require(name)});
+	} catch (...) {
+		return 0;
+	}
+
+	return 1;
+}
+
+/*
+ * Function: Irccd.Server.list()
+ * ------------------------------------------------------------------
+ *
+ * Get the map of all loaded servers.
+ *
+ * Returns:
+ *   An object with string-to-servers pairs.
+ */
+duk::Ret list(duk::ContextPtr ctx)
+{
+	duk::push(ctx, duk::Object{});
+
+	for (const auto &server : duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->serverService().servers()) {
+		duk::putProperty(ctx, -1, server->name(), duk::Shared<Server>{server});
+	}
+
+	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 remove(duk::ContextPtr ctx)
+{
+	duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->serverService().remove(duk::require<std::string>(ctx, 0));
+
+	return 0;
+}
+
+const duk::FunctionMap 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		} }
+};
+
+const duk::FunctionMap functions{
+	{ "add",	{ add,		1		} },
+	{ "find",	{ find,		1		} },
+	{ "list",	{ list,		0		} },
+	{ "remove",	{ remove,	1		} }
+};
+
+} // !namespace
+
+ServerModule::ServerModule() noexcept
+	: Module("Irccd.Server")
+{
+}
+
+void ServerModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Function{constructor, 1});
+	duk::push(plugin.context(), functions);
+	duk::push(plugin.context(), duk::Object());
+	duk::push(plugin.context(), methods);
+	duk::putProperty(plugin.context(), -2, "prototype");
+	duk::putProperty(plugin.context(), -2, "Server");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-server.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,96 @@
+/*
+ * mod-server.hpp -- Irccd.Server API
+ *
+ * Copyright (c) 2013-2016 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_MOD_SERVER_HPP
+#define IRCCD_MOD_SERVER_HPP
+
+/**
+ * \file mod-server.hpp
+ * \brief Irccd.Server JavaScript API.
+ */
+
+#include "js.hpp"
+#include "module.hpp"
+#include "server.hpp"
+
+namespace irccd {
+
+namespace duk {
+
+/**
+ * \brief JavaScript binding for Server.
+ */
+template <>
+class TypeTraits<irccd::Server> {
+public:
+	/**
+	 * Push the Server prototype.
+	 *
+	 * \param ctx the context
+	 */
+	static inline void prototype(ContextPtr ctx)
+	{
+		getGlobal<void>(ctx, "Irccd");
+		getProperty<void>(ctx, -1, "Server");
+		getProperty<void>(ctx, -1, "prototype");
+		remove(ctx, -2);
+		remove(ctx, -2);
+	}
+
+	/**
+	 * Get the Server signature.
+	 *
+	 * \return Server
+	 */
+	static inline std::string name()
+	{
+		return "\xff""\xff""Server";
+	}
+
+	/**
+	 * Get the inheritance list.
+	 *
+	 * \return empty
+	 */
+	static inline std::vector<std::string> inherits()
+	{
+		return {};
+	}
+};
+
+} // !duk
+
+/**
+ * \brief Irccd.Server JavaScript API.
+ */
+class ServerModule : public Module {
+public:
+	/**
+	 * Irccd.Server.
+	 */
+	ServerModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	void load(Irccd &irccd, JsPlugin &plugin) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_JS_SERVER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-system.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,243 @@
+/*
+ * js-system.cpp -- Irccd.System API
+ *
+ * Copyright (c) 2013-2016 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 "sysconfig.hpp"
+
+#if defined(HAVE_POPEN)
+#  include <cstdio>
+#endif
+
+#include "mod-file.hpp"
+#include "mod-irccd.hpp"
+#include "mod-system.hpp"
+#include "plugin-js.hpp"
+#include "system.hpp"
+
+namespace irccd {
+
+namespace {
+
+/*
+ * Function: Irccd.System.env(key)
+ * ------------------------------------------------------------------
+ *
+ * Get an environment system variable.
+ *
+ * Arguments:
+ *   - key, the environment variable.
+ * Returns:
+ *   The value.
+ */
+int env(duk::ContextPtr ctx)
+{
+	duk::push(ctx, sys::env(duk::get<std::string>(ctx, 0)));
+
+	return 1;
+}
+
+/*
+ * Function: Irccd.System.exec(cmd)
+ * ------------------------------------------------------------------
+ *
+ * Execute a system command.
+ *
+ * Arguments:
+ *   - cmd, the command to execute.
+ */
+int exec(duk::ContextPtr ctx)
+{
+	std::system(duk::get<const char *>(ctx, 0));
+
+	return 0;
+}
+
+/*
+ * Function: Irccd.System.home()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system user's home.
+ *
+ * Returns:
+ *   The user home directory.
+ */
+int home(duk::ContextPtr ctx)
+{
+	duk::push(ctx, sys::home());
+
+	return 1;
+}
+
+/*
+ * Function: Irccd.System.name()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system name.
+ *
+ * Returns:
+ *   The system name.
+ */
+int name(duk::ContextPtr ctx)
+{
+	duk::push(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.SystemError on failures.
+ */
+int popen(duk::ContextPtr ctx)
+{
+	auto fp = ::popen(duk::require<const char *>(ctx, 0), duk::require<const char *>(ctx, 1));
+
+	if (fp == nullptr) {
+		duk::raise(ctx, SystemError{});
+	}
+
+	duk::push(ctx, duk::Pointer<File>{new File(fp, [] (std::FILE *fp) { ::pclose(fp); })});
+
+	return 1;
+}
+
+#endif // !HAVE_POPEN
+
+/*
+ * Function: Irccd.System.sleep(delay)
+ * ------------------------------------------------------------------
+ *
+ * Sleep the main loop for the specific delay in seconds.
+ */
+int sleep(duk::ContextPtr 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.
+ */
+int ticks(duk::ContextPtr ctx)
+{
+	duk::push(ctx, static_cast<int>(sys::ticks()));
+
+	return 1;
+}
+
+/*
+ * Function: Irccd.System.usleep(delay)
+ * ------------------------------------------------------------------
+ *
+ * Sleep the main loop for the specific delay in microseconds.
+ */
+int usleep(duk::ContextPtr 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.
+ */
+int uptime(duk::ContextPtr ctx)
+{
+	duk::push<int>(ctx, sys::uptime());
+
+	return 0;
+}
+
+/*
+ * Function: Irccd.System.version()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system version.
+ *
+ * Returns:
+ *   The system version.
+ */
+int version(duk::ContextPtr ctx)
+{
+	duk::push(ctx, sys::version());
+
+	return 1;
+}
+
+const duk::FunctionMap 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	} }
+};
+
+} // !namespace
+
+SystemModule::SystemModule() noexcept
+	: Module("Irccd.System")
+{
+}
+
+void SystemModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Object{});
+	duk::push(plugin.context(), functions);
+	duk::putProperty(plugin.context(), -2, "System");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-system.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * mod-system.hpp -- Irccd.System API
+ *
+ * Copyright (c) 2013-2016 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_MOD_SYSTEM_HPP
+#define IRCCD_MOD_SYSTEM_HPP
+
+/**
+ * \file mod-system.hpp
+ * \brief Irccd.System JavaScript API.
+ */
+
+#include "module.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Irccd.System JavaScript API.
+ */
+class SystemModule : public Module {
+public:
+	/**
+	 * Irccd.System.
+	 */
+	SystemModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	void load(Irccd &irccd, JsPlugin &plugin) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_SYSTEM_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-timer.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,149 @@
+/*
+ * js-timer.cpp -- Irccd.Timer API
+ *
+ * Copyright (c) 2013-2016 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 <cstdint>
+
+#include "mod-timer.hpp"
+#include "plugin-js.hpp"
+#include "timer.hpp"
+
+namespace irccd {
+
+namespace duk {
+
+template <>
+class TypeTraits<Timer> {
+public:
+	static std::string name()
+	{
+		return "\xff""\xff""Timer";
+	}
+
+	static std::vector<std::string> inherits()
+	{
+		return {};
+	}
+};
+
+} // !duk
+
+namespace {
+
+/*
+ * Method: Timer.start()
+ * --------------------------------------------------------
+ *
+ * Start the timer. If the timer is already started the method
+ * is a no-op.
+ */
+duk::Ret start(duk::ContextPtr ctx)
+{
+	auto timer = duk::self<duk::Shared<Timer>>(ctx);
+
+	if (!timer->isRunning()) {
+		timer->start();
+	}
+
+	return 0;
+}
+
+/*
+ * Method: Timer.stop()
+ * --------------------------------------------------------
+ *
+ * Stop the timer.
+ */
+duk::Ret stop(duk::ContextPtr ctx)
+{
+	auto timer = duk::self<duk::Shared<Timer>>(ctx);
+
+	if (timer->isRunning()) {
+		timer->stop();
+	}
+
+	return 0;
+}
+
+const duk::FunctionMap methods{
+	{ "start",	{ start,	0 } },
+	{ "stop",	{ stop,		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 constructor(duk::ContextPtr ctx)
+{
+	int type = duk::require<int>(ctx, 0);
+	int delay = duk::require<int>(ctx, 1);
+
+	if (!duk::is<duk::Function>(ctx, 2)) {
+		duk::raise(ctx, duk::TypeError("missing callback function"));
+	}
+
+	auto timer = std::make_shared<Timer>(static_cast<TimerType>(type), delay);
+
+	/* Add this timer to the underlying plugin */
+	duk::getGlobal<duk::RawPointer<JsPlugin>>(ctx, "\xff""\xff""plugin")->addTimer(timer);
+
+	/* Construct object */
+	duk::construct(ctx, duk::Shared<Timer>{timer});
+
+	/* Now store the JavaScript function to call */
+	duk::dup(ctx, 2);
+	duk::putGlobal(ctx, "\xff""\xff""timer-" + std::to_string(reinterpret_cast<std::intptr_t>(timer.get())));
+
+	return 0;
+}
+
+const duk::Map<int> constants{
+	{ "Single",	static_cast<int>(TimerType::Single) },
+	{ "Repeat",	static_cast<int>(TimerType::Repeat) },
+};
+
+} // !namespace
+
+TimerModule::TimerModule() noexcept
+	: Module("Irccd.Timer")
+{
+}
+
+void TimerModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Function{constructor, 3});
+	duk::push(plugin.context(), constants);
+	duk::push(plugin.context(), duk::Object{});
+	duk::push(plugin.context(), methods);
+	duk::putProperty(plugin.context(), -2, "prototype");
+	duk::putProperty(plugin.context(), -2, "Timer");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-timer.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * mod-timer.hpp -- Irccd.Timer API
+ *
+ * Copyright (c) 2013-2016 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_MOD_TIMER_HPP
+#define IRCCD_MOD_TIMER_HPP
+
+/**
+ * \file mod-timer.hpp
+ * \brief Irccd.Timer JavaScript API.
+ */
+
+#include "module.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Irccd.Timer JavaScript API.
+ */
+class TimerModule : public Module {
+public:
+	/**
+	 * Irccd.Timer.
+	 */
+	TimerModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	void load(Irccd &irccd, JsPlugin &plugin) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_TIMER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-unicode.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,151 @@
+/*
+ * js-unicode.cpp -- Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2016 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.hpp"
+#include "mod-unicode.hpp"
+#include "plugin-js.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 isDigit(duk::ContextPtr ctx)
+{
+	duk::push(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 isLetter(duk::ContextPtr ctx)
+{
+	duk::push(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 isLower(duk::ContextPtr ctx)
+{
+	duk::push(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 isSpace(duk::ContextPtr ctx)
+{
+	duk::push(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 isTitle(duk::ContextPtr ctx)
+{
+	duk::push(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 isUpper(duk::ContextPtr ctx)
+{
+	duk::push(ctx, unicode::isupper(duk::get<int>(ctx, 0)));
+
+	return 1;
+}
+
+const duk::FunctionMap functions{
+	{ "isDigit",		{ isDigit,	1	} },
+	{ "isLetter",		{ isLetter,	1	} },
+	{ "isLower",		{ isLower,	1	} },
+	{ "isSpace",		{ isSpace,	1	} },
+	{ "isTitle",		{ isTitle,	1	} },
+	{ "isUpper",		{ isUpper,	1	} },
+};
+
+} // !namespace
+
+UnicodeModule::UnicodeModule() noexcept
+	: Module("Irccd.Unicode")
+{
+}
+
+void UnicodeModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Object{});
+	duk::push(plugin.context(), functions);
+	duk::putProperty(plugin.context(), -2, "Unicode");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-unicode.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * mod-unicode.cpp -- Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2016 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_MOD_UNICODE_HPP
+#define IRCCD_MOD_UNICODE_HPP
+
+/**
+ * \file mod-unicode.hpp
+ * \brief Irccd.Unicode JavaScript API.
+ */
+
+#include "module.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Irccd.Unicode JavaScript API.
+ */
+class UnicodeModule : public Module {
+public:
+	/**
+	 * Irccd.Unicode.
+	 */
+	UnicodeModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	void load(Irccd &irccd, JsPlugin &plugin) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_UNICODE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-util.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,158 @@
+/*
+ * js-util.cpp -- Irccd.Util API
+ *
+ * Copyright (c) 2013-2016 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 <libircclient.h>
+
+#include "mod-util.hpp"
+#include "plugin-js.hpp"
+#include "util.hpp"
+
+namespace irccd {
+
+namespace duk {
+
+/**
+ * Read parameters for Irccd.Util.format function, the object is defined as follow:
+ *
+ * {
+ *   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: ...
+ * }
+ */
+template <>
+class TypeTraits<util::Substitution> {
+public:
+	static util::Substitution get(ContextPtr ctx, int index)
+	{
+		util::Substitution params;
+
+		if (!duk::is<Object>(ctx, index)) {
+			return params;
+		}
+
+		duk::enumerate(ctx, index, 0, true, [&] (ContextPtr) {
+			if (duk::get<std::string>(ctx, -2) == "date") {
+				params.time = static_cast<time_t>(duk::get<double>(ctx, -1) / 1000);
+			} else {
+				params.keywords.insert({duk::get<std::string>(ctx, -2), duk::get<std::string>(ctx, -1)});
+			}
+		});
+
+		return params;
+	}
+};
+
+} // !duk
+
+namespace {
+
+/*
+ * 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 format(duk::ContextPtr ctx)
+{
+	try {
+		duk::push(ctx, util::format(duk::get<std::string>(ctx, 0), duk::get<util::Substitution>(ctx, 1)));
+	} catch (const std::exception &ex) {
+		duk::raise(ctx, duk::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 splituser(duk::ContextPtr ctx)
+{
+	const char *target = duk::require<const char *>(ctx, 0);
+	char nick[32] = {0};
+
+	irc_target_get_nick(target, nick, sizeof (nick) -1);
+	duk::push(ctx, std::string(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 splithost(duk::ContextPtr ctx)
+{
+	const char *target = duk::require<const char *>(ctx, 0);
+	char host[32] = {0};
+
+	irc_target_get_host(target, host, sizeof (host) -1);
+	duk::push(ctx, std::string(host));
+
+	return 1;
+}
+
+const duk::FunctionMap functions{
+	{ "format",		{ format,	DUK_VARARGS	} },
+	{ "splituser",		{ splituser,	1		} },
+	{ "splithost",		{ splithost,	1		} }
+};
+
+} // !namespace
+
+UtilModule::UtilModule() noexcept
+	: Module("Irccd.Util")
+{
+}
+
+void UtilModule::load(Irccd &, JsPlugin &plugin)
+{
+	duk::StackAssert sa(plugin.context());
+
+	duk::getGlobal<void>(plugin.context(), "Irccd");
+	duk::push(plugin.context(), duk::Object{});
+	duk::push(plugin.context(), functions);
+	duk::putProperty(plugin.context(), -2, "Util");
+	duk::pop(plugin.context());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/mod-util.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * mod-util.hpp -- Irccd.Util API
+ *
+ * Copyright (c) 2013-2016 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_MOD_UTIL_HPP
+#define IRCCD_MOD_UTIL_HPP
+
+/**
+ * \file mod-util.hpp
+ * \brief Irccd.Util JavaScript API.
+ */
+
+#include "module.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Irccd.Util JavaScript API.
+ */
+class UtilModule : public Module {
+public:
+	/**
+	 * Irccd.Util.
+	 */
+	UtilModule() noexcept;
+
+	/**
+	 * \copydoc Module::load
+	 */
+	void load(Irccd &irccd, JsPlugin &plugin) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_MOD_UTIL_HPP
--- a/lib/irccd/module.hpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/module.hpp	Sun May 15 21:36:04 2016 +0200
@@ -1,24 +1,71 @@
+/*
+ * module.hpp -- JavaScript API module
+ *
+ * Copyright (c) 2013-2016 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
 
-#include <memory>
+/**
+ * \file module.hpp
+ * \brief JavaScript API module.
+ */
+
+#include <cassert>
+
+#include "util.hpp"
 
 namespace irccd {
 
 class Irccd;
 class JsPlugin;
 
+/**
+ * \brief JavaScript API module.
+ */
 class Module {
+private:
+	std::string m_name;
+
 public:
 	/**
 	 * Default constructor.
+	 *
+	 * \pre !name.empty()
 	 */
-	Module() = default;
+	inline Module(std::string name) noexcept
+		: m_name(std::move(name))
+	{
+		assert(!m_name.empty());
+	}
 
 	/**
 	 * Virtual destructor defaulted.
 	 */
-	virtual ~Module() = default
+	virtual ~Module() = default;
+
+	/**
+	 * Get the module name.
+	 *
+	 * \return the name
+	 */
+	inline const std::string &name() const noexcept
+	{
+		return m_name;
+	}
 
 	/**
 	 * Load the module into the JavaScript plugin.
@@ -26,7 +73,10 @@
 	 * \param irccd the irccd instance
 	 * \param plugin the plugin
 	 */
-	void load(Irccd &irccd, const std::shared_ptr<JsPlugin> &plugin);
+	virtual void load(Irccd &irccd, JsPlugin &plugin)
+	{
+		util::unused(irccd, plugin);
+	}
 
 	/**
 	 * Unload the module from the JavaScript plugin.
@@ -34,9 +84,12 @@
 	 * \param irccd the irccd instance
 	 * \param plugin the plugin
 	 */
-	void unload(Irccd &irccd, const std::shared_ptr<Plugin> &plugin);
+	virtual void unload(Irccd &irccd, JsPlugin &plugin)
+	{
+		util::unused(irccd, plugin);
+	}
 };
 
 } // !irccd
 
-#endif // !IRCCD_MODULE_HPP
\ No newline at end of file
+#endif // !IRCCD_MODULE_HPP
--- a/lib/irccd/plugin-js.cpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/plugin-js.cpp	Sun May 15 21:36:04 2016 +0200
@@ -25,19 +25,11 @@
 #endif
 
 #include "fs.hpp"
-#include "js-directory.hpp"
-#include "js-elapsed-timer.hpp"
-#include "js-file.hpp"
-#include "js-irccd.hpp"
-#include "js-logger.hpp"
-#include "js-plugin.hpp"
-#include "js-server.hpp"
-#include "js-system.hpp"
-#include "js-timer.hpp"
-#include "js-unicode.hpp"
-#include "js-util.hpp"
+#include "irccd.hpp"
 #include "logger.hpp"
+#include "mod-server.hpp"
 #include "plugin-js.hpp"
+#include "service-module.hpp"
 #include "timer.hpp"
 
 namespace irccd {
@@ -69,7 +61,6 @@
 {
 	duk::StackAssert sa(m_context);
 
-	/* Save a reference to this */
 	duk::putGlobal(m_context, "\xff""\xff""plugin", duk::RawPointer<JsPlugin>{this});
 	duk::putGlobal(m_context, "\xff""\xff""name", name());
 	duk::putGlobal(m_context, "\xff""\xff""path", path());
@@ -82,10 +73,8 @@
 	bool found = true;
 	std::string foundpath;
 
-	/*
-	 * Use the first existing directory available.
-	 */
-	for (const std::string &p : path::list(type)) {
+	// Use the first existing directory available.
+	for (const auto &p : path::list(type)) {
 		foundpath = path::clean(p + append);
 
 		if (fs::exists(foundpath)) {
@@ -94,7 +83,7 @@
 		}
 	}
 
-	/* Use the system as default */
+	// Use the system as default.
 	if (!found) {
 		foundpath = path::clean(path::get(type, path::OwnerSystem) + append);
 	}
@@ -105,19 +94,6 @@
 	duk::pop(m_context, 2);
 }
 
-void JsPlugin::putPaths()
-{
-	duk::StackAssert sa(m_context);
-
-	/*
-	 * dataPath: DATA + plugin/name (e.g ~/.local/share/irccd/plugins/<name>/)
-	 * configPath: CONFIG + plugin/name (e.g ~/.config/irccd/plugin/<name>/)
-	 */
-	putPath("dataPath", "plugin/" + name(), path::PathData);
-	putPath("configPath", "plugin/" + name(), path::PathConfig);
-	putPath("cachePath", "plugin/" + name(), path::PathCache);
-}
-
 void JsPlugin::putConfig(const PluginConfig &config)
 {
 	duk::StackAssert sa(m_context);
@@ -145,64 +121,6 @@
 JsPlugin::JsPlugin(std::string name, std::string path, const PluginConfig &config)
 	: Plugin(name, path, config)
 {
-	duk::StackAssert sa(m_context);
-
-	/*
-	 * Duktape currently emit useless warnings when a file do
-	 * not exists so we do a homemade access.
-	 */
-#if defined(HAVE_STAT)
-	struct stat st;
-
-	if (::stat(path.c_str(), &st) < 0) {
-		throw std::runtime_error(std::strerror(errno));
-	}
-#endif
-
-	// TODO: change with future modules
-	// Load standard irccd API.
-	loadJsIrccd(m_context);
-	loadJsDirectory(m_context);
-	loadJsElapsedTimer(m_context);
-	loadJsFile(m_context);
-	loadJsLogger(m_context);
-	loadJsPlugin(m_context);
-	loadJsServer(m_context);
-	loadJsSystem(m_context);
-	loadJsUnicode(m_context);
-	loadJsTimer(m_context);
-	loadJsUtil(m_context);
-
-	putVars();
-	putPaths();
-
-	/* Try to load the file (does not call onLoad yet) */
-	if (duk::pevalFile(m_context, path) != 0) {
-		throw duk::error(m_context, -1);
-	}
-
-	duk::pop(m_context);
-
-	/* Initialize user defined options after loading to allow the plugin to define default values */
-	putConfig(config);
-
-	/* Read metadata */
-	duk::getGlobal<void>(m_context, "info");
-
-	if (duk::type(m_context, -1) == DUK_TYPE_OBJECT) {
-		setAuthor(duk::optionalProperty<std::string>(m_context, -1, "author", author()));
-		setLicense(duk::optionalProperty<std::string>(m_context, -1, "license", license()));
-		setSummary(duk::optionalProperty<std::string>(m_context, -1, "summary", summary()));
-		setVersion(duk::optionalProperty<std::string>(m_context, -1, "version", version()));
-	}
-
-	duk::pop(m_context);
-
-	log::debug() << "plugin " << name << ": " << std::endl;
-	log::debug() << "  author:  " << author() << std::endl;
-	log::debug() << "  license: " << license() << std::endl;
-	log::debug() << "  summary: " << summary() << std::endl;
-	log::debug() << "  version: " << version() << std::endl;
 }
 
 JsPlugin::~JsPlugin()
@@ -342,10 +260,60 @@
 	call("onKick", 5);
 }
 
-void JsPlugin::onLoad(Irccd &)
+void JsPlugin::putModules(Irccd &irccd)
+{
+	for (const auto &module : irccd.moduleService().modules()) {
+		module->load(irccd, *this);
+	}
+}
+
+void JsPlugin::onLoad(Irccd &irccd)
 {
 	duk::StackAssert sa(m_context);
 
+	/*
+	 * Duktape currently emit useless warnings when a file do
+	 * not exists so we do a homemade access.
+	 */
+#if defined(HAVE_STAT)
+	struct stat st;
+
+	if (::stat(path().c_str(), &st) < 0) {
+		throw std::runtime_error(std::strerror(errno));
+	}
+#endif
+
+	/*
+	 * dataPath: DATA + plugin/name (e.g ~/.local/share/irccd/plugins/<name>/)
+	 * configPath: CONFIG + plugin/name (e.g ~/.config/irccd/plugin/<name>/)
+	 */
+	putModules(irccd);
+	putVars();
+	putPath("dataPath", "plugin/" + name(), path::PathData);
+	putPath("configPath", "plugin/" + name(), path::PathConfig);
+	putPath("cachePath", "plugin/" + name(), path::PathCache);
+
+	/* Try to load the file (does not call onLoad yet) */
+	if (duk::pevalFile(m_context, path()) != 0) {
+		throw duk::error(m_context, -1);
+	}
+
+	duk::pop(m_context);
+
+	putConfig(config());
+
+	/* Read metadata */
+	duk::getGlobal<void>(m_context, "info");
+
+	if (duk::type(m_context, -1) == DUK_TYPE_OBJECT) {
+		setAuthor(duk::optionalProperty<std::string>(m_context, -1, "author", author()));
+		setLicense(duk::optionalProperty<std::string>(m_context, -1, "license", license()));
+		setSummary(duk::optionalProperty<std::string>(m_context, -1, "summary", summary()));
+		setVersion(duk::optionalProperty<std::string>(m_context, -1, "version", version()));
+	}
+
+	duk::pop(m_context);
+
 	call("onLoad", 0);
 }
 
--- a/lib/irccd/plugin-js.hpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/plugin-js.hpp	Sun May 15 21:36:04 2016 +0200
@@ -77,11 +77,14 @@
 	// Plugin info and its timers
 	PluginTimers m_timers;
 
+	// Store loaded modules.
+	std::vector<std::shared_ptr<Module>> m_modules;
+
 	// Private helpers
 	void call(const std::string &name, unsigned nargs = 0);
+	void putModules(Irccd &irccd);
 	void putVars();
 	void putPath(const std::string &varname, const std::string &append, path::Path type);
-	void putPaths();
 	void putConfig(const PluginConfig &config);
 
 public:
--- a/lib/irccd/plugin.hpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/plugin.hpp	Sun May 15 21:36:04 2016 +0200
@@ -185,6 +185,16 @@
 		m_version = std::move(version);
 	}
 
+	inline const PluginConfig &config() const noexcept
+	{
+		return m_config;
+	}
+
+	inline PluginConfig &config() noexcept
+	{
+		return m_config;
+	}
+
 	/**
 	 * On channel message. This event will call onMessage or
 	 * onCommand if the messages starts with the command character
--- a/lib/irccd/server-state-disconnected.cpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/server-state-disconnected.cpp	Sun May 15 21:36:04 2016 +0200
@@ -27,7 +27,6 @@
 
 void Disconnected::prepare(Server &server, fd_set &, fd_set &, net::Handle &)
 {
-	const ServerInfo &info = server.info();
 	ServerSettings &settings = server.settings();
 	ServerCache &cache = server.cache();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/service-module.cpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,77 @@
+/*
+ * service-module.cpp -- store and manage JavaScript modules
+ *
+ * Copyright (c) 2013-2016 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 "mod-elapsed-timer.hpp"
+#include "mod-directory.hpp"
+#include "mod-file.hpp"
+#include "mod-irccd.hpp"
+#include "mod-logger.hpp"
+#include "mod-plugin.hpp"
+#include "mod-server.hpp"
+#include "mod-system.hpp"
+#include "mod-timer.hpp"
+#include "mod-unicode.hpp"
+#include "mod-util.hpp"
+#include "service-module.hpp"
+
+namespace irccd {
+
+namespace {
+
+auto find(const std::vector<std::shared_ptr<Module>> &modules, const std::string &name) noexcept
+{
+	return std::find_if(modules.begin(), modules.end(), [&] (const auto &module) {
+		return module->name() == name;
+	});
+}
+
+} // !namespace
+
+ModuleService::ModuleService()
+{
+	// Load Irccd global first.
+	m_modules.push_back(std::make_shared<IrccdModule>());
+
+	// Additional modules.
+	m_modules.push_back(std::make_shared<ElapsedTimerModule>());
+	m_modules.push_back(std::make_shared<DirectoryModule>());
+	m_modules.push_back(std::make_shared<FileModule>());
+	m_modules.push_back(std::make_shared<LoggerModule>());
+	m_modules.push_back(std::make_shared<PluginModule>());
+	m_modules.push_back(std::make_shared<ServerModule>());
+	m_modules.push_back(std::make_shared<SystemModule>());
+	m_modules.push_back(std::make_shared<TimerModule>());
+	m_modules.push_back(std::make_shared<UnicodeModule>());
+	m_modules.push_back(std::make_shared<UtilModule>());
+}
+
+bool ModuleService::contains(const std::string &name) const
+{
+	return find(m_modules, name) != m_modules.end();
+}
+
+void ModuleService::add(std::shared_ptr<Module> module)
+{
+	assert(!contains(module->name()));
+
+	m_modules.push_back(std::move(module));
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/service-module.hpp	Sun May 15 21:36:04 2016 +0200
@@ -0,0 +1,76 @@
+/*
+ * service-module.hpp -- store and manage JavaScript modules
+ *
+ * Copyright (c) 2013-2016 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_SERVICE_MODULE_HPP
+#define IRCCD_SERVICE_MODULE_HPP
+
+/**
+ * \file service-module.hpp
+ * \brief Store and manage JavaScript modules.
+ */
+
+#include <memory>
+#include <vector>
+
+namespace irccd {
+
+class Module;
+
+/**
+ * \brief Store and manage JavaScript modules.
+ */
+class ModuleService {
+private:
+	std::vector<std::shared_ptr<Module>> m_modules;
+
+public:
+	/**
+	 * Construct the service and predefined irccd API.
+	 */
+	ModuleService();
+
+	/**
+	 * Get all modules.
+	 *
+	 * \return the modules
+	 */
+	inline const std::vector<std::shared_ptr<Module>> &modules() const noexcept
+	{
+		return m_modules;
+	}
+
+	/**
+	 * Tells if a module exist.
+	 *
+	 * \param name the name
+	 */
+	bool contains(const std::string &name) const;
+
+	/**
+	 * Add a JavaScript API module.
+	 *
+	 * \pre module != nullptr
+	 * \pre !contains(module)
+	 * \param module the module
+	 */
+	void add(std::shared_ptr<Module> module);
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVICE_MODULE_HPP
--- a/lib/irccd/service-plugin.cpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/service-plugin.cpp	Sun May 15 21:36:04 2016 +0200
@@ -39,13 +39,6 @@
 {
 }
 
-void PluginService::addModule(std::shared_ptr<Module> module)
-{
-	assert(module);
-
-	m_modules.push_back(std::move(module));
-}
-
 bool PluginService::has(const std::string &name) const noexcept
 {
 	return std::count_if(m_plugins.cbegin(), m_plugins.cend(), [&] (const auto &plugin) {
@@ -93,19 +86,12 @@
 		// Store reference to irccd.
 		duk::putGlobal(jsp->context(), "\xff""\xff""irccd", duk::RawPointer<Irccd>{&m_irccd});
 	}
-
-	// Initial load now.
-	// TODO: not responsible of this.
-	try {
-		plugin->onLoad(m_irccd);
-		m_plugins.push_back(std::move(plugin));
-	} catch (const std::exception &ex) {
-		log::warning("plugin {}: {}"_format(plugin->name(), ex.what()));
-	}
 }
 
-std::shared_ptr<Plugin> PluginService::find(std::string name, PluginConfig config)
+std::shared_ptr<Plugin> PluginService::find(std::string name)
 {
+	PluginConfig config = m_config[name];
+
 	for (const auto &path : path::list(path::PathPlugins)) {
 		std::string fullpath = path + name + ".js";
 
@@ -121,6 +107,22 @@
 	throw std::runtime_error("no suitable plugin found");
 }
 
+void PluginService::configure(const std::string &name, PluginConfig config)
+{
+	m_config.emplace(name, std::move(config));
+}
+
+PluginConfig PluginService::config(const std::string &name) const
+{
+	auto it = m_config.find(name);
+
+	if (it != m_config.end()) {
+		return it->second;
+	}
+
+	return PluginConfig();
+}
+
 void PluginService::load(std::string name, std::string path)
 {
 	auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) {
@@ -128,19 +130,21 @@
 	});
 
 	if (it != m_plugins.end()) {
-		throw std::invalid_argument("plugin already loaded");
+		return;
 	}
 
-	// TODO: LOAD CONFIG
-	std::shared_ptr<Plugin> plugin;
+	try {
+		std::shared_ptr<Plugin> plugin;
 
-	try {
 		if (path.empty()) {
-			plugin = find(std::move(name), PluginConfig());
+			plugin = find(std::move(name));
 		} else {
 			// TODO: DYNLIB BASED PLUGINS
 			plugin = std::make_shared<JsPlugin>(std::move(name), std::move(path));
 		}
+
+		plugin->onLoad(m_irccd);
+		add(std::move(plugin));
 	} catch (const std::exception &ex) {
 		log::warning("plugin {}: {}"_format(ex.what()));
 	}
--- a/lib/irccd/service-plugin.hpp	Fri May 13 12:06:06 2016 +0200
+++ b/lib/irccd/service-plugin.hpp	Sun May 15 21:36:04 2016 +0200
@@ -24,6 +24,7 @@
  * \brief Manage plugins.
  */
 
+#include <unordered_map>
 #include <memory>
 #include <vector>
 
@@ -39,8 +40,8 @@
 class PluginService {
 private:
 	Irccd &m_irccd;
-	std::vector<std::shared_ptr<Module>> m_modules;
 	std::vector<std::shared_ptr<Plugin>> m_plugins;
+	std::unordered_map<std::string, PluginConfig> m_config;
 
 	// TODO: get rid of this with future JavaScript modules.
 	void handleTimerSignal(std::weak_ptr<JsPlugin>, std::shared_ptr<Timer>);
@@ -65,14 +66,6 @@
 	}
 
 	/**
-	 * Add an API module for JavaScript plugins.
-	 *
-	 * \pre module is not null
-	 * \param module the module
-	 */
-	void addModule(std::shared_ptr<Module> module);
-
-	/**
 	 * Check if a plugin is loaded.
 	 *
 	 * \param name the plugin id
@@ -97,10 +90,50 @@
 	 */
 	std::shared_ptr<Plugin> require(const std::string &name) const;
 
+	/**
+	 * Add the specified plugin to the registry.
+	 *
+	 * \pre plugin != nullptr
+	 * \param plugin the plugin
+	 * \note the plugin is only added to the list, no action is performed on it
+	 */
 	void add(std::shared_ptr<Plugin> plugin);
 
-	std::shared_ptr<Plugin> find(std::string name, PluginConfig config = PluginConfig());
+	/**
+	 * Search a plugin through the predefined directories.
+	 *
+	 * \param name the plugin name (without any extension)
+	 * \return the plugin if found
+	 * \throw std::exception on any errors
+	 */
+	std::shared_ptr<Plugin> find(std::string name);
 
+	/**
+	 * Configure a plugin.
+	 *
+	 * If the plugin is already loaded, its configuration is updated.
+	 *
+	 * \param name the plugin name
+	 * \param config the new configuration
+	 */
+	void configure(const std::string &name, PluginConfig config);
+
+	/**
+	 * Get a configuration for a plugin.
+	 *
+	 * \param name the plugin name
+	 * \return the configuration or default one if not found
+	 */
+	PluginConfig config(const std::string &name) const;
+
+	/**
+	 * Convenient wrapper that loads a plugin, call onLoad and add it to the registry.
+	 *
+	 * Any errors are printed using logger.
+	 *
+	 * \param name the name
+	 * \param path the optional path (searched if empty)
+	 */
 	void load(std::string name, std::string path = "");
 
 	/**
--- a/tests/js-elapsedtimer/main.cpp	Fri May 13 12:06:06 2016 +0200
+++ b/tests/js-elapsedtimer/main.cpp	Sun May 15 21:36:04 2016 +0200
@@ -21,7 +21,7 @@
 #include <thread>
 
 #include <irccd/js-irccd.hpp>
-#include <irccd/js-elapsed-timer.hpp>
+#include <irccd/mod-elapsed-timer.hpp>
 
 using namespace irccd;
 using namespace std::chrono_literals;