changeset 521:e03521cf207b

Common: split util.hpp into more appropriate files, closes #721
author David Demelier <markand@malikania.fr>
date Fri, 27 Oct 2017 21:45:32 +0200
parents defacef00c82
children 683eb8ad79d1
files irccd/main.cpp irccdctl/cli.cpp irccdctl/main.cpp libcommon/CMakeLists.txt libcommon/irccd/fs_util.hpp libcommon/irccd/json_util.cpp libcommon/irccd/json_util.hpp libcommon/irccd/net_util.hpp libcommon/irccd/string_util.cpp libcommon/irccd/string_util.hpp libcommon/irccd/system.cpp libcommon/irccd/util.cpp libcommon/irccd/util.hpp libirccd-js/irccd/js_directory_module.cpp libirccd-js/irccd/js_file_module.cpp libirccd-js/irccd/js_timer_module.cpp libirccd-js/irccd/js_util_module.cpp libirccd-js/irccd/module.hpp libirccd-test/irccd/command-tester.hpp libirccd/irccd/command.cpp libirccd/irccd/config.cpp libirccd/irccd/dynlib_plugin.cpp libirccd/irccd/irccd.cpp libirccd/irccd/server.cpp libirccd/irccd/service.cpp libirccdctl/irccd/client.cpp tests/CMakeLists.txt tests/js-timer/main.cpp tests/js/main.cpp tests/plugin-hangman/main.cpp tests/plugin-plugin/main.cpp tests/util/main.cpp
diffstat 32 files changed, 1819 insertions(+), 1676 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/irccd/main.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -37,6 +37,7 @@
 #include "logger.hpp"
 #include "options.hpp"
 #include "service.hpp"
+#include "string_util.hpp"
 #include "system.hpp"
 #include "config.hpp"
 #include "irccd.hpp"
@@ -172,7 +173,7 @@
         try {
             return config(it->second);
         } catch (const std::exception &ex) {
-            throw std::runtime_error(util::sprintf("%s: %s", it->second, ex.what()));
+            throw std::runtime_error(string_util::sprintf("%s: %s", it->second, ex.what()));
         }
     }
 
@@ -189,7 +190,7 @@
         std::ofstream out(path, std::ofstream::trunc);
 
         if (!out)
-            throw std::runtime_error(util::sprintf("could not open pidfile %s: %s",
+            throw std::runtime_error(string_util::sprintf("could not open pidfile %s: %s",
                 path, std::strerror(errno)));
 
         log::debug() << "irccd: pid written in " << path << std::endl;
--- a/irccdctl/cli.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/irccdctl/cli.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -26,9 +26,11 @@
 
 #include "cli.hpp"
 #include "irccdctl.hpp"
+#include "json_util.hpp"
 #include "logger.hpp"
 #include "options.hpp"
-#include "util.hpp"
+#include "net_util.hpp"
+#include "string_util.hpp"
 
 using namespace std::string_literals;
 
@@ -41,8 +43,8 @@
 
 void Cli::check(const nlohmann::json &response)
 {
-    if (!util::json::get_bool(response, "status", false)) {
-        auto error = util::json::get_string(response, "error");
+    if (!json_util::get_bool(response, "status", false)) {
+        auto error = json_util::get_string(response, "error");
 
         if (error.empty())
             throw std::runtime_error("command failed with an unknown error");
@@ -69,7 +71,7 @@
         boost::timer::cpu_timer timer;
 
         while (irccdctl.client().isConnected() && !msg.is_object() && timer.elapsed().wall / 1000000LL < 3000)
-            util::poller::poll(3000 - timer.elapsed().wall / 1000000LL, irccdctl);
+            net_util::poll(3000 - timer.elapsed().wall / 1000000LL, irccdctl);
     } catch (const std::exception &) {
         irccdctl.client().onMessage.disconnect(id);
         throw;
@@ -79,7 +81,7 @@
 
     if (!msg.is_object())
         throw std::runtime_error("no response received");
-    if (util::json::get_string(msg, "command") != m_name)
+    if (json_util::get_string(msg, "command") != m_name)
         throw std::runtime_error("unexpected command result received");
 
     check(msg);
@@ -118,7 +120,7 @@
     check(result);
 
     if (result["variables"].is_object())
-        std::cout << util::json::pretty(result["variables"][args[1]]) << std::endl;
+        std::cout << json_util::pretty(result["variables"][args[1]]) << std::endl;
 }
 
 void PluginConfigCli::getall(Irccdctl &irccdctl, const std::vector<std::string> &args)
@@ -130,7 +132,7 @@
     auto variables = result["variables"];
 
     for (auto v = variables.begin(); v != variables.end(); ++v)
-        std::cout << std::setw(16) << std::left << v.key() << " : " << util::json::pretty(v.value()) << std::endl;
+        std::cout << std::setw(16) << std::left << v.key() << " : " << json_util::pretty(v.value()) << std::endl;
 }
 
 PluginConfigCli::PluginConfigCli()
@@ -188,10 +190,10 @@
     auto result = request(irccdctl, {{ "plugin", args[0] }});
 
     std::cout << std::boolalpha;
-    std::cout << "Author         : " << util::json::get_string(result, "author") << std::endl;
-    std::cout << "License        : " << util::json::get_string(result, "license") << std::endl;
-    std::cout << "Summary        : " << util::json::get_string(result, "summary") << std::endl;
-    std::cout << "Version        : " << util::json::get_string(result, "version") << std::endl;
+    std::cout << "Author         : " << json_util::get_string(result, "author") << std::endl;
+    std::cout << "License        : " << json_util::get_string(result, "license") << std::endl;
+    std::cout << "Summary        : " << json_util::get_string(result, "summary") << std::endl;
+    std::cout << "Version        : " << json_util::get_string(result, "version") << std::endl;
 }
 
 /*
@@ -408,7 +410,7 @@
     });
 
     if (copy.size() == 3) {
-        if (!util::is_int(copy[2]))
+        if (!string_util::is_int(copy[2]))
             throw std::invalid_argument("invalid port number");
 
         object["port"] = std::stoi(copy[2]);
@@ -484,12 +486,12 @@
     check(result);
 
     std::cout << std::boolalpha;
-    std::cout << "Name           : " << util::json::pretty(result["name"]) << std::endl;
-    std::cout << "Host           : " << util::json::pretty(result["host"]) << std::endl;
-    std::cout << "Port           : " << util::json::pretty(result["port"]) << std::endl;
-    std::cout << "Ipv6           : " << util::json::pretty(result["ipv6"]) << std::endl;
-    std::cout << "SSL            : " << util::json::pretty(result["ssl"]) << std::endl;
-    std::cout << "SSL verified   : " << util::json::pretty(result["sslVerify"]) << std::endl;
+    std::cout << "Name           : " << json_util::pretty(result["name"]) << std::endl;
+    std::cout << "Host           : " << json_util::pretty(result["host"]) << std::endl;
+    std::cout << "Port           : " << json_util::pretty(result["port"]) << std::endl;
+    std::cout << "Ipv6           : " << json_util::pretty(result["ipv6"]) << std::endl;
+    std::cout << "SSL            : " << json_util::pretty(result["ssl"]) << std::endl;
+    std::cout << "SSL verified   : " << json_util::pretty(result["sslVerify"]) << std::endl;
     std::cout << "Channels       : ";
 
     for (const auto &v : result["channels"])
@@ -498,9 +500,9 @@
 
     std::cout << std::endl;
 
-    std::cout << "Nickname       : " << util::json::pretty(result["nickname"]) << std::endl;
-    std::cout << "User name      : " << util::json::pretty(result["username"]) << std::endl;
-    std::cout << "Real name      : " << util::json::pretty(result["realname"]) << std::endl;
+    std::cout << "Nickname       : " << json_util::pretty(result["nickname"]) << std::endl;
+    std::cout << "User name      : " << json_util::pretty(result["username"]) << std::endl;
+    std::cout << "Real name      : " << json_util::pretty(result["realname"]) << std::endl;
 }
 
 /*
@@ -910,9 +912,9 @@
 
     // Index.
     if (result.count("-i") > 0)
-        json["index"] = util::to_number<unsigned>(result.find("-i")->second);
+        json["index"] = string_util::to_number<unsigned>(result.find("-i")->second);
     if (result.count("--index") > 0)
-        json["index"] = util::to_number<unsigned>(result.find("--index")->second);
+        json["index"] = string_util::to_number<unsigned>(result.find("--index")->second);
 
     // And action.
     if (copy[0] != "accept" && copy[0] != "drop")
@@ -1014,7 +1016,7 @@
     }
 
     // Index.
-    json["index"] = util::to_number<unsigned>(copy[0]);
+    json["index"] = string_util::to_number<unsigned>(copy[0]);
 
     check(request(irccdctl, json));
 }
@@ -1226,137 +1228,137 @@
 void onChannelMode(const nlohmann::json &v)
 {
     std::cout << "event:       onChannelMode\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "mode:        " << util::json::pretty(v, "mode") << "\n";
-    std::cout << "argument:    " << util::json::pretty(v, "argument") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "mode:        " << json_util::pretty(v, "mode") << "\n";
+    std::cout << "argument:    " << json_util::pretty(v, "argument") << "\n";
 }
 
 void onChannelNotice(const nlohmann::json &v)
 {
     std::cout << "event:       onChannelNotice\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << util::json::pretty(v, "channel") << "\n";
-    std::cout << "message:     " << util::json::pretty(v, "message") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
 }
 
 void onConnect(const nlohmann::json &v)
 {
     std::cout << "event:       onConnect\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
 }
 
 void onInvite(const nlohmann::json &v)
 {
     std::cout << "event:       onInvite\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << util::json::pretty(v, "channel") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
 }
 
 void onJoin(const nlohmann::json &v)
 {
     std::cout << "event:       onJoin\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << util::json::pretty(v, "channel") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
 }
 
 void onKick(const nlohmann::json &v)
 {
     std::cout << "event:       onKick\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << util::json::pretty(v, "channel") << "\n";
-    std::cout << "target:      " << util::json::pretty(v, "target") << "\n";
-    std::cout << "reason:      " << util::json::pretty(v, "reason") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "target:      " << json_util::pretty(v, "target") << "\n";
+    std::cout << "reason:      " << json_util::pretty(v, "reason") << "\n";
 }
 
 void onMessage(const nlohmann::json &v)
 {
     std::cout << "event:       onMessage\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << util::json::pretty(v, "channel") << "\n";
-    std::cout << "message:     " << util::json::pretty(v, "message") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
 }
 
 void onMe(const nlohmann::json &v)
 {
     std::cout << "event:       onMe\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "target:      " << util::json::pretty(v, "target") << "\n";
-    std::cout << "message:     " << util::json::pretty(v, "message") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "target:      " << json_util::pretty(v, "target") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
 }
 
 void onMode(const nlohmann::json &v)
 {
     std::cout << "event:       onMode\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "mode:        " << util::json::pretty(v, "mode") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "mode:        " << json_util::pretty(v, "mode") << "\n";
 }
 
 void onNames(const nlohmann::json &v)
 {
     std::cout << "event:       onNames\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "channel:     " << util::json::pretty(v, "channel") << "\n";
-    std::cout << "names:       " << util::json::pretty(v, "names") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "names:       " << json_util::pretty(v, "names") << "\n";
 }
 
 void onNick(const nlohmann::json &v)
 {
     std::cout << "event:       onNick\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "nickname:    " << util::json::pretty(v, "nickname") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "nickname:    " << json_util::pretty(v, "nickname") << "\n";
 }
 
 void onNotice(const nlohmann::json &v)
 {
     std::cout << "event:       onNotice\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "message:     " << util::json::pretty(v, "message") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
 }
 
 void onPart(const nlohmann::json &v)
 {
     std::cout << "event:       onPart\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << util::json::pretty(v, "channel") << "\n";
-    std::cout << "reason:      " << util::json::pretty(v, "reason") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "reason:      " << json_util::pretty(v, "reason") << "\n";
 }
 
 void onQuery(const nlohmann::json &v)
 {
     std::cout << "event:       onQuery\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "message:     " << util::json::pretty(v, "message") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "message:     " << json_util::pretty(v, "message") << "\n";
 }
 
 void onTopic(const nlohmann::json &v)
 {
     std::cout << "event:       onTopic\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "origin:      " << util::json::pretty(v, "origin") << "\n";
-    std::cout << "channel:     " << util::json::pretty(v, "channel") << "\n";
-    std::cout << "topic:       " << util::json::pretty(v, "topic") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "origin:      " << json_util::pretty(v, "origin") << "\n";
+    std::cout << "channel:     " << json_util::pretty(v, "channel") << "\n";
+    std::cout << "topic:       " << json_util::pretty(v, "topic") << "\n";
 }
 
 void onWhois(const nlohmann::json &v)
 {
     std::cout << "event:       onWhois\n";
-    std::cout << "server:      " << util::json::pretty(v, "server") << "\n";
-    std::cout << "nickname:    " << util::json::pretty(v, "nickname") << "\n";
-    std::cout << "username:    " << util::json::pretty(v, "username") << "\n";
-    std::cout << "host:        " << util::json::pretty(v, "host") << "\n";
-    std::cout << "realname:    " << util::json::pretty(v, "realname") << "\n";
+    std::cout << "server:      " << json_util::pretty(v, "server") << "\n";
+    std::cout << "nickname:    " << json_util::pretty(v, "nickname") << "\n";
+    std::cout << "username:    " << json_util::pretty(v, "username") << "\n";
+    std::cout << "host:        " << json_util::pretty(v, "host") << "\n";
+    std::cout << "realname:    " << json_util::pretty(v, "realname") << "\n";
 }
 
 const std::unordered_map<std::string, std::function<void (const nlohmann::json &)>> events{
@@ -1425,7 +1427,7 @@
 
     try {
         while (client.client().isConnected()) {
-            util::poller::poll(500, client);
+            net_util::poll(500, client);
         }
     } catch (const std::exception &ex) {
         log::warning() << ex.what() << std::endl;
--- a/irccdctl/main.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/irccdctl/main.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -29,6 +29,7 @@
 #include "logger.hpp"
 #include "options.hpp"
 #include "system.hpp"
+#include "string_util.hpp"
 #include "util.hpp"
 
 using namespace std::string_literals;
@@ -141,7 +142,7 @@
 
     address = net::resolveOne(host, port, domain, SOCK_STREAM);
 
-    if ((it = sc.find("ssl")) != sc.end() && util::is_boolean(it->value()))
+    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value()))
 #if defined(HAVE_SSL)
         client = std::make_unique<TlsClient>();
 #else
@@ -219,7 +220,7 @@
     auto verbose = sc.find("verbose");
 
     if (verbose != sc.end())
-        log::set_verbose(util::is_boolean(verbose->value()));
+        log::set_verbose(string_util::is_boolean(verbose->value()));
 }
 
 /*
@@ -246,7 +247,7 @@
          * argument is a command name.
          */
         if (option.size() == 1 && option[0].empty())
-            throw std::runtime_error(util::sprintf("alias %s: missing command name in '%s'", name, option.key()));
+            throw std::runtime_error(string_util::sprintf("alias %s: missing command name in '%s'", name, option.key()));
 
         std::string command = option[0];
         std::vector<AliasArg> args(option.begin() + 1, option.end());
@@ -436,7 +437,7 @@
         for (const auto &arg : cmd.args()) {
             if (arg.isPlaceholder()) {
                 if (args.size() < arg.index() + 1)
-                    throw std::invalid_argument(util::sprintf("missing argument for placeholder %d", arg.index()));
+                    throw std::invalid_argument(string_util::sprintf("missing argument for placeholder %d", arg.index()));
 
                 cmdArgs.push_back(args[arg.index()]);
 
--- a/libcommon/CMakeLists.txt	Tue Oct 31 10:48:06 2017 +0100
+++ b/libcommon/CMakeLists.txt	Fri Oct 27 21:45:32 2017 +0200
@@ -22,11 +22,15 @@
 
 set(
     HEADERS
+    ${libcommon_SOURCE_DIR}/irccd/fs_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/ini.hpp
+    ${libcommon_SOURCE_DIR}/irccd/json_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/logger.hpp
     ${libcommon_SOURCE_DIR}/irccd/net.hpp
+    ${libcommon_SOURCE_DIR}/irccd/net_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/options.hpp
     ${libcommon_SOURCE_DIR}/irccd/signals.hpp
+    ${libcommon_SOURCE_DIR}/irccd/string_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/system.hpp
     ${libcommon_SOURCE_DIR}/irccd/util.hpp
     ${libcommon_SOURCE_DIR}/irccd/xdg.hpp
@@ -35,10 +39,11 @@
 set(
     SOURCES
     ${libcommon_SOURCE_DIR}/irccd/ini.cpp
+    ${libcommon_SOURCE_DIR}/irccd/json_util.cpp
     ${libcommon_SOURCE_DIR}/irccd/logger.cpp
     ${libcommon_SOURCE_DIR}/irccd/options.cpp
+    ${libcommon_SOURCE_DIR}/irccd/string_util.cpp
     ${libcommon_SOURCE_DIR}/irccd/system.cpp
-    ${libcommon_SOURCE_DIR}/irccd/util.cpp
 )
 
 if (NOT HAVE_SSL)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/fs_util.hpp	Fri Oct 27 21:45:32 2017 +0200
@@ -0,0 +1,131 @@
+/*
+ * fs_util.hpp -- filesystem utilities
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_FS_UTIL_HPP
+#define IRCCD_COMMON_FS_UTIL_HPP
+
+/**
+ * \file fs_util.hpp
+ * \brief Filesystem utilities.
+ */
+
+#include <regex>
+#include <string>
+
+#include <boost/filesystem.hpp>
+
+namespace irccd {
+
+namespace fs_util {
+
+/**
+ * Get the base name from a path.
+ *
+ * Example, baseName("/etc/foo.conf") // foo.conf
+ *
+ * \param path the path
+ * \return the base name
+ */
+inline std::string base_name(const std::string& path)
+{
+    return boost::filesystem::path(path).filename().string();
+}
+
+/**
+ * Get the parent directory from a path.
+ *
+ * Example, dirName("/etc/foo.conf") // /etc
+ *
+ * \param path the path
+ * \return the parent directory
+ */
+inline std::string dir_name(std::string path)
+{
+    return boost::filesystem::path(path).parent_path().string();
+}
+
+/**
+ * Search an item recursively.
+ *
+ * The predicate must have the following signature:
+ *  void f(const boost::filesystem::directory_entry& entry)
+ *
+ * Where:
+ *   - base is the current parent directory in the tree
+ *   - entry is the current entry
+ *
+ * \param base the base directory
+ * \param predicate the predicate
+ * \param recursive true to do recursive search
+ * \return the full path name to the file or empty string if never found
+ * \throw std::runtime_error on read errors
+ */
+template <typename Predicate>
+std::string find_if(const std::string& base, bool recursive, Predicate&& predicate)
+{
+    auto find = [&] (auto it) -> std::string {
+        for (const auto& entry : it)
+            if (predicate(entry))
+                return entry.path().string();
+
+        return "";
+    };
+
+    if (recursive)
+        return find(boost::filesystem::recursive_directory_iterator(base));
+
+    return find(boost::filesystem::directory_iterator(base));
+}
+
+/**
+ * Find a file by name recursively.
+ *
+ * \param base the base directory
+ * \param name the file name
+ * \param recursive true to do recursive search
+ * \return the full path name to the file or empty string if never found
+ * \throw std::runtime_error on read errors
+ */
+inline std::string find(const std::string& base, const std::string& name, bool recursive = false)
+{
+    return find_if(base, recursive, [&] (const auto& entry) {
+        return entry.path().filename().string() == name;
+    });
+}
+
+/**
+ * Overload by regular expression.
+ *
+ * \param base the base directory
+ * \param regex the regular expression
+ * \param recursive true to do recursive search
+ * \return the full path name to the file or empty string if never found
+ * \throw std::runtime_error on read errors
+ */
+inline std::string find(const std::string& base, const std::regex& regex, bool recursive = false)
+{
+    return find_if(base, recursive, [&] (const auto& entry) {
+        return std::regex_match(entry.path().filename().string(), regex);
+    });
+}
+
+} // !fs_util
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_FS_UTIL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/json_util.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -0,0 +1,82 @@
+/*
+ * json_util.cpp -- utilities for JSON
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "json_util.hpp"
+#include "string_util.hpp"
+
+namespace irccd {
+
+namespace json_util {
+
+nlohmann::json require(const nlohmann::json& json, const std::string& key, nlohmann::json::value_t type)
+{
+    auto it = json.find(key);
+    auto dummy = nlohmann::json(type);
+
+    if (it == json.end())
+        throw std::runtime_error(string_util::sprintf("missing '%s' property", key));
+    if (it->type() != type)
+        throw std::runtime_error(string_util::sprintf("invalid '%s' property (%s expected, got %s)",
+            key, it->type_name(), dummy.type_name()));
+
+    return *it;
+}
+
+std::string require_identifier(const nlohmann::json& json, const std::string& key)
+{
+    auto id = require_string(json, key);
+
+    if (!string_util::is_identifier(id))
+        throw std::runtime_error(string_util::sprintf("invalid '%s' identifier property", id));
+
+    return id;
+}
+
+std::int64_t require_int(const nlohmann::json& json, const std::string& key)
+{
+    auto it = json.find(key);
+
+    if (it == json.end())
+        throw std::runtime_error(string_util::sprintf("missing '%s' property", key));
+    if (it->is_number_integer())
+        return it->get<int>();
+    if (it->is_number_unsigned() && it->get<unsigned>() <= INT_MAX)
+        return static_cast<int>(it->get<unsigned>());
+
+    throw std::runtime_error(string_util::sprintf("invalid '%s' property (%s expected, got %s)",
+        key, it->type_name(), nlohmann::json(0).type_name()));
+}
+
+std::uint64_t require_uint(const nlohmann::json& json, const std::string& key)
+{
+    auto it = json.find(key);
+
+    if (it == json.end())
+        throw std::runtime_error(string_util::sprintf("missing '%s' property", key));
+    if (it->is_number_unsigned())
+        return it->get<unsigned>();
+    if (it->is_number_integer() && it->get<int>() >= 0)
+        return static_cast<unsigned>(it->get<int>());
+
+    throw std::runtime_error(string_util::sprintf("invalid '%s' property (%s expected, got %s)",
+        key, it->type_name(), nlohmann::json(0U).type_name()));
+}
+
+} // !json_util
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/json_util.hpp	Fri Oct 27 21:45:32 2017 +0200
@@ -0,0 +1,267 @@
+/*
+ * json_util.hpp -- utilities for JSON
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_JSON_UTIL_HPP
+#define IRCCD_COMMON_JSON_UTIL_HPP
+
+#include <json.hpp>
+
+/**
+ * \file json_util.hpp
+ * \brief Utilities for JSON.
+ */
+
+namespace irccd {
+
+/**
+ * \brief Utilities for JSON.
+ */
+namespace json_util {
+
+/**
+ * Require a property.
+ *
+ * \param json the json value
+ * \param key the property name
+ * \param type the requested property type
+ * \return the value
+ * \throw std::runtime_error if the property is missing
+ */
+nlohmann::json require(const nlohmann::json& json, const std::string& key, nlohmann::json::value_t type);
+
+/**
+ * Convenient access for booleans.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the boolean
+ * \throw std::runtime_error if the property is missing or not a boolean
+ */
+inline bool require_bool(const nlohmann::json& json, const std::string& key)
+{
+    return require(json, key, nlohmann::json::value_t::boolean);
+}
+
+/**
+ * Convenient access for unique identifiers.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the identifier
+ * \throw std::runtime_error if the property is invalid
+ */
+std::string require_identifier(const nlohmann::json& json, const std::string& key);
+
+/**
+ * Convenient access for ints.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the int
+ * \throw std::runtime_error if the property is missing or not ant int
+ */
+std::int64_t require_int(const nlohmann::json& json, const std::string& key);
+
+/**
+ * Convenient access for unsigned ints.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the unsigned int
+ * \throw std::runtime_error if the property is missing or not ant int
+ */
+std::uint64_t require_uint(const nlohmann::json& json, const std::string& key);
+
+/**
+ * Convenient access for strings.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the string
+ * \throw std::runtime_error if the property is missing or not a string
+ */
+inline std::string require_string(const nlohmann::json& json, const std::string& key)
+{
+    return require(json, key, nlohmann::json::value_t::string);
+}
+
+/**
+ * Convert the json value to boolean.
+ *
+ * \param json the json value
+ * \param def the default value if not boolean
+ * \return a boolean
+ */
+inline bool to_bool(const nlohmann::json& json, bool def = false) noexcept
+{
+    return json.is_boolean() ? json.get<bool>() : def;
+}
+
+/**
+ * Convert the json value to int.
+ *
+ * \param json the json value
+ * \param def the default value if not an int
+ * \return an int
+ */
+inline std::int64_t to_int(const nlohmann::json& json, std::int64_t def = 0) noexcept
+{
+    return json.is_number_integer() ? json.get<std::int64_t>() : def;
+}
+
+/**
+ * Convert the json value to unsigned.
+ *
+ * \param json the json value
+ * \param def the default value if not a unsigned int
+ * \return an unsigned int
+ */
+inline std::uint64_t to_uint(const nlohmann::json& json, std::uint64_t def = 0) noexcept
+{
+    return json.is_number_unsigned() ? json.get<std::uint64_t>() : def;
+}
+
+/**
+ * Convert the json value to string.
+ *
+ * \param json the json value
+ * \param def the default value if not a string
+ * \return a string
+ */
+inline std::string to_string(const nlohmann::json& json, std::string def = "") noexcept
+{
+    return json.is_string() ? json.get<std::string>() : def;
+}
+
+/**
+ * Get a property or return null one if not found or if json is not an object.
+ *
+ * \param json the json value
+ * \param property the property key
+ * \return the value or null one if not found
+ */
+inline nlohmann::json get(const nlohmann::json& json, const std::string& property) noexcept
+{
+    auto it = json.find(property);
+
+    if (it == json.end())
+        return nlohmann::json();
+
+    return *it;
+}
+
+/**
+ * Convenient access for boolean with default value.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the boolean
+ */
+inline bool get_bool(const nlohmann::json& json,
+                     const std::string& key,
+                     bool def = false) noexcept
+{
+    return to_bool(get(json, key), def);
+}
+
+/**
+ * Convenient access for ints with default value.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the int
+ */
+inline std::int64_t get_int(const nlohmann::json& json,
+                            const std::string& key,
+                            std::int64_t def = 0) noexcept
+{
+    return to_int(get(json, key), def);
+}
+
+/**
+ * Convenient access for unsigned ints with default value.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the unsigned int
+ */
+inline std::uint64_t get_uint(const nlohmann::json& json,
+                              const std::string& key,
+                              std::uint64_t def = 0) noexcept
+{
+    return to_uint(get(json, key), def);
+}
+
+/**
+ * Convenient access for strings with default value.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the string
+ */
+inline std::string get_string(const nlohmann::json& json,
+                              const std::string& key,
+                              std::string def = "") noexcept
+{
+    return to_string(get(json, key), def);
+}
+
+/**
+ * Print the value as human readable.
+ *
+ * \param value the value
+ * \return the string
+ */
+inline std::string pretty(const nlohmann::json& value)
+{
+    switch (value.type()) {
+    case nlohmann::json::value_t::boolean:
+        return value.get<bool>() ? "true" : "false";
+    case nlohmann::json::value_t::string:
+        return value.get<std::string>();
+    default:
+        return value.dump();
+    }
+}
+
+/**
+ * Pretty print a json value in the given object.
+ *
+ * \param object the object
+ * \param prop the property
+ * \return the pretty value or empty if key does not exist
+ */
+inline std::string pretty(const nlohmann::json& object, const std::string& prop)
+{
+    auto it = object.find(prop);
+
+    if (it == object.end())
+        return "";
+
+    return pretty(*it);
+}
+
+} // !json_util
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_JSON_UTIL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/net_util.hpp	Fri Oct 27 21:45:32 2017 +0200
@@ -0,0 +1,141 @@
+/*
+ * net_util.hpp -- network utilities for pollable objects
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_NET_UTIL_HPP
+#define IRCCD_COMMON_NET_UTIL_HPP
+
+/**
+ * \file net_util.hpp
+ * \brief Network utilities for pollable objects.
+ */
+
+#include "net.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Miscellaneous utilities for Pollable objects
+ */
+namespace net_util {
+
+/**
+ * \cond HIDDEN_SYMBOLS
+ */
+
+inline void prepare(fd_set &, fd_set &, net::Handle &) noexcept
+{
+}
+
+/**
+ * \endcond
+ */
+
+/**
+ * Call prepare function for every Pollable objects.
+ *
+ * \param in the input set
+ * \param out the output set
+ * \param max the maximum handle
+ * \param first the first Pollable object
+ * \param rest the additional Pollable objects
+ */
+template <typename Pollable, typename... Rest>
+inline void prepare(fd_set &in, fd_set &out, net::Handle &max, Pollable &first, Rest&... rest)
+{
+    first.prepare(in, out, max);
+    prepare(in, out, max, rest...);
+}
+
+/**
+ * \cond HIDDEN_SYMBOLS
+ */
+
+inline void sync(fd_set &, fd_set &) noexcept
+{
+}
+
+/**
+ * \endcond
+ */
+
+/**
+ * Call sync function for every Pollable objects.
+ *
+ * \param in the input set
+ * \param out the output set
+ * \param first the first Pollable object
+ * \param rest the additional Pollable objects
+ */
+template <typename Pollable, typename... Rest>
+inline void sync(fd_set &in, fd_set &out, Pollable &first, Rest&... rest)
+{
+    first.sync(in, out);
+    sync(in, out, rest...);
+}
+
+/**
+ * Prepare and sync Pollable objects.
+ *
+ * \param timeout the timeout in milliseconds (< 0 means forever)
+ * \param first the the first Pollable object
+ * \param rest the additional Pollable objects
+ */
+template <typename Pollable, typename... Rest>
+void poll(int timeout, Pollable &first, Rest&... rest)
+{
+    fd_set in, out;
+    timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
+
+    FD_ZERO(&in);
+    FD_ZERO(&out);
+
+    net::Handle max = 0;
+
+    prepare(in, out, max, first, rest...);
+
+    if (select(max + 1, &in, &out, nullptr, timeout < 0 ? nullptr : &tv) < 0 && errno != EINTR) {
+        throw std::runtime_error(std::strerror(errno));
+    } else {
+        sync(in, out, first, rest...);
+    }
+}
+
+/**
+ * Parse a network message from an input buffer and remove it from it.
+ *
+ * \param input the buffer, will be updated
+ * \return the message or empty string if there is nothing
+ */
+inline std::string next_network(std::string& input)
+{
+    std::string result;
+    std::string::size_type pos = input.find("\r\n\r\n");
+
+    if ((pos = input.find("\r\n\r\n")) != std::string::npos) {
+        result = input.substr(0, pos);
+        input.erase(input.begin(), input.begin() + pos + 4);
+    }
+
+    return result;
+}
+
+} // !net_util
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_NET_UTIL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/string_util.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -0,0 +1,409 @@
+/*
+ * string_util.cpp -- string utilities
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "sysconfig.hpp"
+
+#if defined(HAVE_POPEN)
+#   include <array>
+#   include <cerrno>
+#   include <cstring>
+#   include <functional>
+#   include <memory>
+#endif
+
+#include <iomanip>
+
+#include "string_util.hpp"
+
+using namespace std::string_literals;
+
+namespace irccd {
+
+namespace string_util {
+
+namespace {
+
+const std::unordered_map<std::string, int> colors{
+    { "white",      0   },
+    { "black",      1   },
+    { "blue",       2   },
+    { "green",      3   },
+    { "red",        4   },
+    { "brown",      5   },
+    { "purple",     6   },
+    { "orange",     7   },
+    { "yellow",     8   },
+    { "lightgreen", 9   },
+    { "cyan",       10  },
+    { "lightcyan",  11  },
+    { "lightblue",  12  },
+    { "pink",       13  },
+    { "grey",       14  },
+    { "lightgrey",  15  }
+};
+
+const std::unordered_map<std::string, char> attributes{
+    { "bold",       '\x02'  },
+    { "italic",     '\x09'  },
+    { "strike",     '\x13'  },
+    { "reset",      '\x0f'  },
+    { "underline",  '\x15'  },
+    { "underline2", '\x1f'  },
+    { "reverse",    '\x16'  }
+};
+
+inline bool is_reserved(char token) noexcept
+{
+    return token == '#' || token == '@' || token == '$' || token == '!';
+}
+
+std::string subst_date(const std::string& text, const subst& params)
+{
+    std::ostringstream oss;
+
+#if defined(HAVE_STD_PUT_TIME)
+    oss << std::put_time(std::localtime(&params.time), text.c_str());
+#else
+    /*
+     * Quick and dirty hack because old version of GCC does not have this
+     * function.
+     */
+    char buffer[4096];
+
+    std::strftime(buffer, sizeof (buffer) - 1, text.c_str(), std::localtime(&params.time));
+
+    oss << buffer;
+#endif
+
+    return oss.str();
+}
+
+std::string subst_keywords(const std::string& content, const subst& params)
+{
+    auto value = params.keywords.find(content);
+
+    if (value != params.keywords.end())
+        return value->second;
+
+    return "";
+}
+
+std::string subst_env(const std::string& content)
+{
+    auto value = std::getenv(content.c_str());
+
+    if (value != nullptr)
+        return value;
+
+    return "";
+}
+
+std::string subst_attrs(const std::string& content)
+{
+    std::stringstream oss;
+    std::vector<std::string> list = split(content, ",");
+
+    // @{} means reset.
+    if (list.empty())
+        return std::string(1, attributes.at("reset"));
+
+    // Remove useless spaces.
+    std::transform(list.begin(), list.end(), list.begin(), strip);
+
+    /*
+     * 0: foreground
+     * 1: background
+     * 2-n: attributes
+     */
+    auto foreground = list[0];
+    if (!foreground.empty() || list.size() >= 2) {
+        // Color sequence.
+        oss << '\x03';
+
+        // Foreground.
+        auto it = colors.find(foreground);
+        if (it != colors.end())
+            oss << it->second;
+
+        // Background.
+        if (list.size() >= 2 && (it = colors.find(list[1])) != colors.end())
+            oss << "," << it->second;
+
+        // Attributes.
+        for (std::size_t i = 2; i < list.size(); ++i) {
+            auto attribute = attributes.find(list[i]);
+
+            if (attribute != attributes.end())
+                oss << attribute->second;
+        }
+    }
+
+    return oss.str();
+}
+
+std::string subst_shell(const std::string& command)
+{
+#if defined(HAVE_POPEN)
+    std::unique_ptr<FILE, std::function<int (FILE*)>> fp(popen(command.c_str(), "r"), pclose);
+
+    if (fp == nullptr)
+        throw std::runtime_error(std::strerror(errno));
+
+    std::string result;
+    std::array<char, 128> buffer;
+    std::size_t n;
+
+    while ((n = std::fread(buffer.data(), 1, 128, fp.get())) > 0)
+        result.append(buffer.data(), n);
+    if (std::ferror(fp.get()))
+        throw std::runtime_error(std::strerror(errno));
+
+    // Erase final '\n'.
+    auto it = result.find('\n');
+    if (it != std::string::npos)
+        result.erase(it);
+
+    return result;
+#else
+    throw std::runtime_error("shell template not available");
+#endif
+}
+
+std::string substitute(std::string::const_iterator& it, std::string::const_iterator& end, char token, const subst& params)
+{
+    assert(is_reserved(token));
+
+    std::string content, value;
+
+    if (it == end)
+        return "";
+
+    while (it != end && *it != '}')
+        content += *it++;
+
+    if (it == end || *it != '}')
+        throw std::invalid_argument("unclosed "s + token + " construct"s);
+
+    it++;
+
+    // Create default original value if flag is disabled.
+    value = std::string(1, token) + "{"s + content + "}"s;
+
+    switch (token) {
+    case '#':
+        if ((params.flags & subst_flags::keywords) == subst_flags::keywords)
+            value = subst_keywords(content, params);
+        break;
+    case '$':
+        if ((params.flags & subst_flags::env) == subst_flags::env)
+            value = subst_env(content);
+        break;
+    case '@':
+        if ((params.flags & subst_flags::irc_attrs) == subst_flags::irc_attrs)
+            value = subst_attrs(content);
+        break;
+    case '!':
+        if ((params.flags & subst_flags::shell) == subst_flags::shell)
+            value = subst_shell(content);
+        break;
+    default:
+        break;
+    }
+
+    return value;
+}
+
+} // !namespace
+
+std::string format(std::string text, const subst& params)
+{
+    /*
+     * Change the date format before anything else to avoid interpolation with
+     * keywords and user input.
+     */
+    if ((params.flags & subst_flags::date) == subst_flags::date)
+        text = subst_date(text, params);
+
+    std::ostringstream oss;
+
+    for (auto it = text.cbegin(), end = text.cend(); it != end; ) {
+        auto token = *it;
+
+        // Is the current character a reserved token or not?
+        if (!is_reserved(token)) {
+            oss << *it++;
+            continue;
+        }
+
+        // The token was at the end, just write it and return now.
+        if (++it == end) {
+            oss << token;
+            continue;
+        }
+
+        // The token is declaring a template variable, substitute it.
+        if (*it == '{') {
+            oss << substitute(++it, end, token, params);
+            continue;
+        }
+
+        /*
+         * If the next token is different from the previous one, just let the
+         * next iteration parse the string because we can have the following
+         * constructs.
+         *
+         * "@#{var}" -> "@value"
+         */
+        if (*it != token) {
+            oss << token;
+            continue;
+        }
+
+        /*
+         * Write the token only if it's not a variable because at this step we
+         * may have the following constructs.
+         *
+         * "##" -> "##"
+         * "##hello" -> "##hello"
+         * "##{hello}" -> "#{hello}"
+         */
+        if (++it == end)
+            oss << token << token;
+        else if (*it == '{')
+            oss << token;
+    }
+
+    return oss.str();
+}
+
+std::string strip(std::string str)
+{
+    auto test = [] (char c) { return !std::isspace(c); };
+
+    str.erase(str.begin(), std::find_if(str.begin(), str.end(), test));
+    str.erase(std::find_if(str.rbegin(), str.rend(), test).base(), str.end());
+
+    return str;
+}
+
+std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max)
+{
+    std::vector<std::string> result;
+    std::size_t next = -1, current;
+    int count = 1;
+    bool finished = false;
+
+    if (list.empty())
+        return result;
+
+    do {
+        std::string val;
+
+        current = next + 1;
+        next = list.find_first_of(delimiters, current);
+
+        // split max, get until the end.
+        if (max >= 0 && count++ >= max) {
+            val = list.substr(current, std::string::npos);
+            finished = true;
+        } else {
+            val = list.substr(current, next - current);
+            finished = next == std::string::npos;
+        }
+
+        result.push_back(val);
+    } while (!finished);
+
+    return result;
+}
+
+message_pack parse_message(std::string message, const std::string& cc, const std::string& name)
+{
+    auto result = message;
+    auto iscommand = false;
+
+    // handle special commands "!<plugin> command"
+    if (cc.length() > 0) {
+        auto pos = result.find_first_of(" \t");
+        auto fullcommand = cc + name;
+
+        /*
+         * If the message that comes is "!foo" without spaces we
+         * compare the command char + the plugin name. If there
+         * is a space, we check until we find a space, if not
+         * typing "!foo123123" will trigger foo plugin.
+         */
+        if (pos == std::string::npos)
+            iscommand = result == fullcommand;
+        else
+            iscommand = result.length() >= fullcommand.length() && result.compare(0, pos, fullcommand) == 0;
+
+        if (iscommand) {
+            /*
+             * If no space is found we just set the message to "" otherwise
+             * the plugin name will be passed through onCommand
+             */
+            if (pos == std::string::npos)
+                result = "";
+            else
+                result = message.substr(pos + 1);
+        }
+    }
+
+    return {
+        iscommand ? message_pack::type::command : message_pack::type::message,
+        result
+    };
+}
+
+bool is_boolean(std::string value) noexcept
+{
+    std::transform(value.begin(), value.end(), value.begin(), [] (auto c) {
+        return toupper(c);
+    });
+
+    return value == "1" || value == "YES" || value == "TRUE" || value == "ON";
+}
+
+bool is_int(const std::string &str, int base) noexcept
+{
+    if (str.empty())
+        return false;
+
+    char *ptr;
+
+    std::strtol(str.c_str(), &ptr, base);
+
+    return *ptr == 0;
+}
+
+bool is_real(const std::string &str) noexcept
+{
+    if (str.empty())
+        return false;
+
+    char *ptr;
+
+    std::strtod(str.c_str(), &ptr);
+
+    return *ptr == 0;
+}
+
+} // !string_util
+
+} // !util
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/string_util.hpp	Fri Oct 27 21:45:32 2017 +0200
@@ -0,0 +1,409 @@
+/*
+ * string_util.hpp -- string utilities
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_STRING_UTIL_HPP
+#define IRCCD_COMMON_STRING_UTIL_HPP
+
+/**
+ * \file string_util.hpp
+ * \brief String utilities.
+ */
+
+#include "sysconfig.hpp"
+
+#include <ctime>
+#include <initializer_list>
+#include <limits>
+#include <regex>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+
+#include <boost/format.hpp>
+
+namespace irccd {
+
+namespace string_util {
+
+/**
+ * \brief Pack a message and its type
+ *
+ * On channels and queries, you may have a special command or a standard message
+ * depending on the beginning of the message.
+ *
+ * Example: `!reminder help' may invoke the command event if a plugin reminder
+ * exists.
+ */
+struct message_pack {
+    /**
+     * \brief Describe which type of message has been received
+     */
+    enum class type {
+        command,                        //!< special command
+        message                         //!< standard message
+    } type;
+
+    /**
+     * Message content.
+     */
+    std::string message;
+};
+
+/**
+ * \brief Disable or enable some features.
+ */
+enum class subst_flags : std::uint8_t {
+    date        = (1 << 0),     //!< date templates
+    keywords    = (1 << 1),     //!< keywords
+    env         = (1 << 2),     //!< environment variables
+    shell       = (1 << 3),     //!< command line command
+    irc_attrs   = (1 << 4)      //!< IRC escape codes
+};
+
+/**
+ * \cond ENUM_HIDDEN_SYMBOLS
+ */
+
+inline subst_flags operator^(subst_flags v1, subst_flags v2) noexcept
+{
+    return static_cast<subst_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
+}
+
+inline subst_flags operator&(subst_flags v1, subst_flags v2) noexcept
+{
+    return static_cast<subst_flags>(static_cast<unsigned>(v1)&  static_cast<unsigned>(v2));
+}
+
+inline subst_flags operator|(subst_flags v1, subst_flags v2) noexcept
+{
+    return static_cast<subst_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
+}
+
+inline subst_flags operator~(subst_flags v) noexcept
+{
+    return static_cast<subst_flags>(~static_cast<unsigned>(v));
+}
+
+inline subst_flags& operator|=(subst_flags& v1, subst_flags v2) noexcept
+{
+    v1 = static_cast<subst_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
+
+    return v1;
+}
+
+inline subst_flags& operator&=(subst_flags& v1, subst_flags v2) noexcept
+{
+    v1 = static_cast<subst_flags>(static_cast<unsigned>(v1)&  static_cast<unsigned>(v2));
+
+    return v1;
+}
+
+inline subst_flags& operator^=(subst_flags& v1, subst_flags v2) noexcept
+{
+    v1 = static_cast<subst_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
+
+    return v1;
+}
+
+/**
+ * \endcond
+ */
+
+/**
+ * \brief Used for format() function.
+ */
+class subst {
+public:
+    /**
+     * Flags for selecting templates.
+     */
+    subst_flags flags{
+        subst_flags::date |
+        subst_flags::keywords |
+        subst_flags::env |
+        subst_flags::irc_attrs
+    };
+
+    /**
+     * Fill that field if you want a date.
+     */
+    std::time_t time{std::time(nullptr)};
+
+    /**
+     * Fill that map if you want to replace keywords.
+     */
+    std::unordered_map<std::string, std::string> keywords;
+};
+
+/**
+ * Format a string and update all templates.
+ *
+ * ## Syntax
+ *
+ * The syntax is <strong>?{}</strong> where <strong>?</strong> is replaced by
+ * one of the token defined below. Braces are mandatory and cannot be ommited.
+ *
+ * To write a literal template construct, prepend the token twice.
+ *
+ * ## Availables templates
+ *
+ * The following templates are available:
+ *
+ * - <strong>\#{name}</strong>: name will be substituted from the keywords in
+ *   params,
+ * - <strong>\${name}</strong>: name will be substituted from the environment
+ *   variable,
+ * - <strong>\@{attributes}</strong>: the attributes will be substituted to IRC
+ *   colors (see below),
+ * - <strong>%</strong>, any format accepted by strftime(3).
+ *
+ * ## Attributes
+ *
+ * The attribute format is composed of three parts, foreground, background and
+ * modifiers, each separated by a comma.
+ *
+ * **Note:** you cannot omit parameters, to specify the background, you must
+ * specify the foreground.
+ *
+ * ## Examples
+ *
+ * ### Valid constructs
+ *
+ *   - <strong>\#{target}, welcome</strong>: if target is set to "irccd",
+ *     becomes "irccd, welcome",
+ *   - <strong>\@{red}\#{target}</strong>: if target is specified, it is written
+ *     in red,
+ *
+ * ### Invalid or literals constructs
+ *
+ *   - <strong>\#\#{target}</strong>: will output "\#{target}",
+ *   - <strong>\#\#</strong>: will output "\#\#",
+ *   - <strong>\#target</strong>: will output "\#target",
+ *   - <strong>\#{target</strong>: will throw std::invalid_argument.
+ *
+ * ### Colors & attributes
+ *
+ *   - <strong>\@{red,blue}</strong>: will write text red on blue background,
+ *   - <strong>\@{default,yellow}</strong>: will write default color text on
+ *     yellow background,
+ *   - <strong>\@{white,black,bold,underline}</strong>: will write white text on
+ *     black in both bold and underline.
+ */
+IRCCD_EXPORT std::string format(std::string text, const subst& params = {});
+
+/**
+ * Remove leading and trailing spaces.
+ *
+ * \param str the string
+ * \return the removed white spaces
+ */
+IRCCD_EXPORT std::string strip(std::string str);
+
+/**
+ * Split a string by delimiters.
+ *
+ * \param list the string to split
+ * \param delimiters a list of delimiters
+ * \param max max number of split
+ * \return a list of string splitted
+ */
+IRCCD_EXPORT std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max = -1);
+
+/**
+ * Join values by a separator and return a string.
+ *
+ * \param first the first iterator
+ * \param last the last iterator
+ * \param delim the optional delimiter
+ */
+template <typename InputIt, typename DelimType = char>
+std::string join(InputIt first, InputIt last, DelimType delim = ':')
+{
+    std::ostringstream oss;
+
+    if (first != last) {
+        oss << *first;
+
+        while (++first != last)
+            oss << delim << *first;
+    }
+
+    return oss.str();
+}
+
+/**
+ * Convenient overload.
+ *
+ * \param list the initializer list
+ * \param delim the delimiter
+ * \return the string
+ */
+template <typename T, typename DelimType = char>
+inline std::string join(std::initializer_list<T> list, DelimType delim = ':')
+{
+    return join(list.begin(), list.end(), delim);
+}
+
+/**
+ * Parse IRC message and determine if it's a command or a simple message.
+ *
+ * If it's a command, the plugin invocation command is removed from the
+ * original message, otherwise it is copied verbatime.
+ *
+ * \param message the message line
+ * \param cchar the command char (e.g '!')
+ * \param plugin the plugin name
+ * \return the pair
+ */
+IRCCD_EXPORT message_pack parse_message(std::string message,
+                                        const std::string& cchar,
+                                        const std::string& plugin);
+
+/**
+ * Server and identities must have strict names. This function can
+ * be used to ensure that they are valid.
+ *
+ * \param name the identifier name
+ * \return true if is valid
+ */
+inline bool is_identifier(const std::string& name)
+{
+    return std::regex_match(name, std::regex("[A-Za-z0-9-_]+"));
+}
+
+/**
+ * Check if the value is a boolean, 1, yes and true are accepted.
+ *
+ * \param value the value
+ * \return true if is boolean
+ * \note this function is case-insensitive
+ */
+IRCCD_EXPORT bool is_boolean(std::string value) noexcept;
+
+/**
+ * Check if the string is an integer.
+ *
+ * \param value the input
+ * \param base the optional base
+ * \return true if integer
+ */
+IRCCD_EXPORT bool is_int(const std::string& value, int base = 10) noexcept;
+
+/**
+ * Check if the string is real.
+ *
+ * \param value the value
+ * \return true if real
+ */
+IRCCD_EXPORT bool is_real(const std::string& value) noexcept;
+
+/**
+ * Check if the string is a number.
+ *
+ * \param value the value
+ * \return true if it is a number
+ */
+inline bool is_number(const std::string& value) noexcept
+{
+    return is_int(value) || is_real(value);
+}
+
+/**
+ * \cond HIDDEN_SYMBOLS
+ */
+
+namespace detail {
+
+inline void sprintf(boost::format&)
+{
+}
+
+template <typename Arg, typename... Args>
+void sprintf(boost::format& fmter, const Arg& arg, const Args&... args)
+{
+    fmter % arg;
+    sprintf(fmter, args...);
+}
+
+} // !detail
+
+/**
+ * \endcond
+ */
+
+/**
+ * Convenient wrapper arount boost::format in sprintf style.
+ *
+ * This is identical as calling boost::format(format) % arg1 % arg2 % argN.
+ *
+ * \param format the format string
+ * \param args the arguments
+ * \return the string
+ */
+template <typename Format, typename... Args>
+std::string sprintf(const Format& format, const Args&... args)
+{
+    boost::format fmter(format);
+
+    detail::sprintf(fmter, args...);
+
+    return fmter.str();
+}
+
+/**
+ * Try to convert the string into number.
+ *
+ * This function will try to convert the string to number in the limits of T.
+ *
+ * If the string is not a number or if the converted value is out of range than
+ * specified boundaries, an exception is thrown.
+ *
+ * By default, the function will use numeric limits from T.
+ *
+ * \param number the string to convert
+ * \param min the minimum (defaults to T minimum)
+ * \param max the maximum (defaults to T maximum)
+ * \return the converted value
+ * \throw std::invalid_argument if number is not a string
+ * \throw std::out_of_range if the number is not between min and max
+ */
+template <typename T>
+inline T to_number(const std::string& number,
+                   T min = std::numeric_limits<T>::min(),
+                   T max = std::numeric_limits<T>::max())
+{
+    static_assert(std::is_integral<T>::value, "T must be integer type");
+
+    std::conditional_t<std::is_unsigned<T>::value, unsigned long long, long long> value;
+
+    if (std::is_unsigned<T>::value)
+        value = std::stoull(number);
+    else
+        value = std::stoll(number);
+
+    if (value < min || value > max)
+        throw std::out_of_range("out of range");
+
+    return static_cast<T>(value);
+}
+
+} // !string_util
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_STRING_UTIL_HPP
--- a/libcommon/irccd/system.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libcommon/irccd/system.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -102,7 +102,7 @@
 
 #include "logger.hpp"
 #include "system.hpp"
-#include "util.hpp"
+#include "string_util.hpp"
 #include "xdg.hpp"
 
 namespace irccd {
@@ -149,7 +149,7 @@
 {
     IntType id = 0;
 
-    if (util::is_int(value))
+    if (string_util::is_int(value))
         id = std::stoi(value);
     else {
         auto info = lookup(value.c_str());
--- a/libcommon/irccd/util.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,429 +0,0 @@
-/*
- * util.cpp -- some utilities
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "sysconfig.hpp"
-
-#include <algorithm>
-#include <cassert>
-#include <cctype>
-#include <cstdlib>
-#include <ctime>
-#include <iomanip>
-#include <sstream>
-#include <stdexcept>
-
-#if defined(HAVE_POPEN)
-#include <array>
-#include <cerrno>
-#include <cstring>
-#include <functional>
-#include <memory>
-#endif
-
-#include "util.hpp"
-
-using namespace std::string_literals;
-
-namespace irccd {
-
-namespace util {
-
-namespace {
-
-const std::unordered_map<std::string, int> colors{
-    { "white",      0   },
-    { "black",      1   },
-    { "blue",       2   },
-    { "green",      3   },
-    { "red",        4   },
-    { "brown",      5   },
-    { "purple",     6   },
-    { "orange",     7   },
-    { "yellow",     8   },
-    { "lightgreen", 9   },
-    { "cyan",       10  },
-    { "lightcyan",  11  },
-    { "lightblue",  12  },
-    { "pink",       13  },
-    { "grey",       14  },
-    { "lightgrey",  15  }
-};
-
-const std::unordered_map<std::string, char> attributes{
-    { "bold",       '\x02'  },
-    { "italic",     '\x09'  },
-    { "strike",     '\x13'  },
-    { "reset",      '\x0f'  },
-    { "underline",  '\x15'  },
-    { "underline2", '\x1f'  },
-    { "reverse",    '\x16'  }
-};
-
-inline bool is_reserved(char token) noexcept
-{
-    return token == '#' || token == '@' || token == '$' || token == '!';
-}
-
-std::string subst_date(const std::string& text, const subst& params)
-{
-    std::ostringstream oss;
-
-#if defined(HAVE_STD_PUT_TIME)
-    oss << std::put_time(std::localtime(&params.time), text.c_str());
-#else
-    /*
-     * Quick and dirty hack because old version of GCC does not have this
-     * function.
-     */
-    char buffer[4096];
-
-    std::strftime(buffer, sizeof (buffer) - 1, text.c_str(), std::localtime(&params.time));
-
-    oss << buffer;
-#endif
-
-    return oss.str();
-}
-
-std::string subst_keywords(const std::string& content, const subst& params)
-{
-    auto value = params.keywords.find(content);
-
-    if (value != params.keywords.end())
-        return value->second;
-
-    return "";
-}
-
-std::string subst_env(const std::string& content)
-{
-    auto value = std::getenv(content.c_str());
-
-    if (value != nullptr)
-        return value;
-
-    return "";
-}
-
-std::string subst_attrs(const std::string& content)
-{
-    std::stringstream oss;
-    std::vector<std::string> list = split(content, ",");
-
-    // @{} means reset.
-    if (list.empty())
-        return std::string(1, attributes.at("reset"));
-
-    // Remove useless spaces.
-    std::transform(list.begin(), list.end(), list.begin(), strip);
-
-    /*
-     * 0: foreground
-     * 1: background
-     * 2-n: attributes
-     */
-    auto foreground = list[0];
-    if (!foreground.empty() || list.size() >= 2) {
-        // Color sequence.
-        oss << '\x03';
-
-        // Foreground.
-        auto it = colors.find(foreground);
-        if (it != colors.end())
-            oss << it->second;
-
-        // Background.
-        if (list.size() >= 2 && (it = colors.find(list[1])) != colors.end())
-            oss << "," << it->second;
-
-        // Attributes.
-        for (std::size_t i = 2; i < list.size(); ++i) {
-            auto attribute = attributes.find(list[i]);
-
-            if (attribute != attributes.end())
-                oss << attribute->second;
-        }
-    }
-
-    return oss.str();
-}
-
-std::string subst_shell(const std::string& command)
-{
-#if defined(HAVE_POPEN)
-    std::unique_ptr<FILE, std::function<int (FILE*)>> fp(popen(command.c_str(), "r"), pclose);
-
-    if (fp == nullptr)
-        throw std::runtime_error(std::strerror(errno));
-
-    std::string result;
-    std::array<char, 128> buffer;
-    std::size_t n;
-
-    while ((n = std::fread(buffer.data(), 1, 128, fp.get())) > 0)
-        result.append(buffer.data(), n);
-    if (std::ferror(fp.get()))
-        throw std::runtime_error(std::strerror(errno));
-
-    // Erase final '\n'.
-    auto it = result.find('\n');
-    if (it != std::string::npos)
-        result.erase(it);
-
-    return result;
-#else
-    throw std::runtime_error("shell template not available");
-#endif
-}
-
-std::string substitute(std::string::const_iterator& it, std::string::const_iterator& end, char token, const subst& params)
-{
-    assert(is_reserved(token));
-
-    std::string content, value;
-
-    if (it == end)
-        return "";
-
-    while (it != end && *it != '}')
-        content += *it++;
-
-    if (it == end || *it != '}')
-        throw std::invalid_argument("unclosed "s + token + " construct"s);
-
-    it++;
-
-    // Create default original value if flag is disabled.
-    value = std::string(1, token) + "{"s + content + "}"s;
-
-    switch (token) {
-    case '#':
-        if ((params.flags & subst_flags::keywords) == subst_flags::keywords)
-            value = subst_keywords(content, params);
-        break;
-    case '$':
-        if ((params.flags & subst_flags::env) == subst_flags::env)
-            value = subst_env(content);
-        break;
-    case '@':
-        if ((params.flags & subst_flags::irc_attrs) == subst_flags::irc_attrs)
-            value = subst_attrs(content);
-        break;
-    case '!':
-        if ((params.flags & subst_flags::shell) == subst_flags::shell)
-            value = subst_shell(content);
-        break;
-    default:
-        break;
-    }
-
-    return value;
-}
-
-} // !namespace
-
-std::string format(std::string text, const subst& params)
-{
-    /*
-     * Change the date format before anything else to avoid interpolation with
-     * keywords and user input.
-     */
-    if ((params.flags & subst_flags::date) == subst_flags::date)
-        text = subst_date(text, params);
-
-    std::ostringstream oss;
-
-    for (auto it = text.cbegin(), end = text.cend(); it != end; ) {
-        auto token = *it;
-
-        // Is the current character a reserved token or not?
-        if (!is_reserved(token)) {
-            oss << *it++;
-            continue;
-        }
-
-        // The token was at the end, just write it and return now.
-        if (++it == end) {
-            oss << token;
-            continue;
-        }
-
-        // The token is declaring a template variable, substitute it.
-        if (*it == '{') {
-            oss << substitute(++it, end, token, params);
-            continue;
-        }
-
-        /*
-         * If the next token is different from the previous one, just let the
-         * next iteration parse the string because we can have the following
-         * constructs.
-         *
-         * "@#{var}" -> "@value"
-         */
-        if (*it != token) {
-            oss << token;
-            continue;
-        }
-
-        /*
-         * Write the token only if it's not a variable because at this step we
-         * may have the following constructs.
-         *
-         * "##" -> "##"
-         * "##hello" -> "##hello"
-         * "##{hello}" -> "#{hello}"
-         */
-        if (++it == end)
-            oss << token << token;
-        else if (*it == '{')
-            oss << token;
-    }
-
-    return oss.str();
-}
-
-std::string strip(std::string str)
-{
-    auto test = [] (char c) { return !std::isspace(c); };
-
-    str.erase(str.begin(), std::find_if(str.begin(), str.end(), test));
-    str.erase(std::find_if(str.rbegin(), str.rend(), test).base(), str.end());
-
-    return str;
-}
-
-std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max)
-{
-    std::vector<std::string> result;
-    std::size_t next = -1, current;
-    int count = 1;
-    bool finished = false;
-
-    if (list.empty())
-        return result;
-
-    do {
-        std::string val;
-
-        current = next + 1;
-        next = list.find_first_of(delimiters, current);
-
-        // split max, get until the end.
-        if (max >= 0 && count++ >= max) {
-            val = list.substr(current, std::string::npos);
-            finished = true;
-        } else {
-            val = list.substr(current, next - current);
-            finished = next == std::string::npos;
-        }
-
-        result.push_back(val);
-    } while (!finished);
-
-    return result;
-}
-
-message_pack parse_message(std::string message, const std::string& cc, const std::string& name)
-{
-    auto result = message;
-    auto iscommand = false;
-
-    // handle special commands "!<plugin> command"
-    if (cc.length() > 0) {
-        auto pos = result.find_first_of(" \t");
-        auto fullcommand = cc + name;
-
-        /*
-         * If the message that comes is "!foo" without spaces we
-         * compare the command char + the plugin name. If there
-         * is a space, we check until we find a space, if not
-         * typing "!foo123123" will trigger foo plugin.
-         */
-        if (pos == std::string::npos)
-            iscommand = result == fullcommand;
-        else
-            iscommand = result.length() >= fullcommand.length() && result.compare(0, pos, fullcommand) == 0;
-
-        if (iscommand) {
-            /*
-             * If no space is found we just set the message to "" otherwise
-             * the plugin name will be passed through onCommand
-             */
-            if (pos == std::string::npos)
-                result = "";
-            else
-                result = message.substr(pos + 1);
-        }
-    }
-
-    return {
-        iscommand ? message_pack::type::command : message_pack::type::message,
-        result
-    };
-}
-
-bool is_boolean(std::string value) noexcept
-{
-    std::transform(value.begin(), value.end(), value.begin(), [] (auto c) {
-        return toupper(c);
-    });
-
-    return value == "1" || value == "YES" || value == "TRUE" || value == "ON";
-}
-
-bool is_int(const std::string &str, int base) noexcept
-{
-    if (str.empty())
-        return false;
-
-    char *ptr;
-
-    std::strtol(str.c_str(), &ptr, base);
-
-    return *ptr == 0;
-}
-
-bool is_real(const std::string &str) noexcept
-{
-    if (str.empty())
-        return false;
-
-    char *ptr;
-
-    std::strtod(str.c_str(), &ptr);
-
-    return *ptr == 0;
-}
-
-std::string next_network(std::string& input)
-{
-    std::string result;
-    std::string::size_type pos = input.find("\r\n\r\n");
-
-    if ((pos = input.find("\r\n\r\n")) != std::string::npos) {
-        result = input.substr(0, pos);
-        input.erase(input.begin(), input.begin() + pos + 4);
-    }
-
-    return result;
-}
-
-} // util
-
-} // !irccd
--- a/libcommon/irccd/util.hpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libcommon/irccd/util.hpp	Fri Oct 27 21:45:32 2017 +0200
@@ -24,26 +24,7 @@
  * \brief Utilities.
  */
 
-#include <cerrno>
-#include <cstring>
-#include <ctime>
-#include <initializer_list>
-#include <limits>
-#include <regex>
-#include <sstream>
-#include <stdexcept>
-#include <string>
-#include <type_traits>
-#include <unordered_map>
-#include <vector>
-
-#include <boost/filesystem.hpp>
-#include <boost/format.hpp>
-
-#include <json.hpp>
-
-#include "net.hpp"
-#include "sysconfig.hpp"
+#include <algorithm>
 
 namespace irccd {
 
@@ -53,225 +34,6 @@
 namespace util {
 
 /**
- * \brief Pack a message and its type
- *
- * On channels and queries, you may have a special command or a standard message
- * depending on the beginning of the message.
- *
- * Example: `!reminder help' may invoke the command event if a plugin reminder
- * exists.
- */
-struct message_pack {
-    /**
-     * \brief Describe which type of message has been received
-     */
-    enum class type {
-        command,                        //!< special command
-        message                         //!< standard message
-    } type;
-
-    /**
-     * Message content.
-     */
-    std::string message;
-};
-
-/**
- * \brief Disable or enable some features.
- */
-enum class subst_flags : std::uint8_t {
-    date        = (1 << 0),     //!< date templates
-    keywords    = (1 << 1),     //!< keywords
-    env         = (1 << 2),     //!< environment variables
-    shell       = (1 << 3),     //!< command line command
-    irc_attrs   = (1 << 4)      //!< IRC escape codes
-};
-
-/**
- * \cond ENUM_HIDDEN_SYMBOLS
- */
-
-inline subst_flags operator^(subst_flags v1, subst_flags v2) noexcept
-{
-    return static_cast<subst_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
-}
-
-inline subst_flags operator&(subst_flags v1, subst_flags v2) noexcept
-{
-    return static_cast<subst_flags>(static_cast<unsigned>(v1)&  static_cast<unsigned>(v2));
-}
-
-inline subst_flags operator|(subst_flags v1, subst_flags v2) noexcept
-{
-    return static_cast<subst_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
-}
-
-inline subst_flags operator~(subst_flags v) noexcept
-{
-    return static_cast<subst_flags>(~static_cast<unsigned>(v));
-}
-
-inline subst_flags& operator|=(subst_flags& v1, subst_flags v2) noexcept
-{
-    v1 = static_cast<subst_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2));
-
-    return v1;
-}
-
-inline subst_flags& operator&=(subst_flags& v1, subst_flags v2) noexcept
-{
-    v1 = static_cast<subst_flags>(static_cast<unsigned>(v1)&  static_cast<unsigned>(v2));
-
-    return v1;
-}
-
-inline subst_flags& operator^=(subst_flags& v1, subst_flags v2) noexcept
-{
-    v1 = static_cast<subst_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2));
-
-    return v1;
-}
-
-/**
- * \endcond
- */
-
-/**
- * \brief Used for format() function.
- */
-class subst {
-public:
-    /**
-     * Flags for selecting templates.
-     */
-    subst_flags flags{
-        subst_flags::date |
-        subst_flags::keywords |
-        subst_flags::env |
-        subst_flags::irc_attrs
-    };
-
-    /**
-     * Fill that field if you want a date.
-     */
-    std::time_t time{std::time(nullptr)};
-
-    /**
-     * Fill that map if you want to replace keywords.
-     */
-    std::unordered_map<std::string, std::string> keywords;
-};
-
-/**
- * Format a string and update all templates.
- *
- * ## Syntax
- *
- * The syntax is <strong>?{}</strong> where <strong>?</strong> is replaced by
- * one of the token defined below. Braces are mandatory and cannot be ommited.
- *
- * To write a literal template construct, prepend the token twice.
- *
- * ## Availables templates
- *
- * The following templates are available:
- *
- * - <strong>\#{name}</strong>: name will be substituted from the keywords in
- *   params,
- * - <strong>\${name}</strong>: name will be substituted from the environment
- *   variable,
- * - <strong>\@{attributes}</strong>: the attributes will be substituted to IRC
- *   colors (see below),
- * - <strong>%</strong>, any format accepted by strftime(3).
- *
- * ## Attributes
- *
- * The attribute format is composed of three parts, foreground, background and
- * modifiers, each separated by a comma.
- *
- * **Note:** you cannot omit parameters, to specify the background, you must
- * specify the foreground.
- *
- * ## Examples
- *
- * ### Valid constructs
- *
- *   - <strong>\#{target}, welcome</strong>: if target is set to "irccd",
- *     becomes "irccd, welcome",
- *   - <strong>\@{red}\#{target}</strong>: if target is specified, it is written
- *     in red,
- *
- * ### Invalid or literals constructs
- *
- *   - <strong>\#\#{target}</strong>: will output "\#{target}",
- *   - <strong>\#\#</strong>: will output "\#\#",
- *   - <strong>\#target</strong>: will output "\#target",
- *   - <strong>\#{target</strong>: will throw std::invalid_argument.
- *
- * ### Colors & attributes
- *
- *   - <strong>\@{red,blue}</strong>: will write text red on blue background,
- *   - <strong>\@{default,yellow}</strong>: will write default color text on
- *     yellow background,
- *   - <strong>\@{white,black,bold,underline}</strong>: will write white text on
- *     black in both bold and underline.
- */
-IRCCD_EXPORT std::string format(std::string text, const subst& params = {});
-
-/**
- * Remove leading and trailing spaces.
- *
- * \param str the string
- * \return the removed white spaces
- */
-IRCCD_EXPORT std::string strip(std::string str);
-
-/**
- * Split a string by delimiters.
- *
- * \param list the string to split
- * \param delimiters a list of delimiters
- * \param max max number of split
- * \return a list of string splitted
- */
-IRCCD_EXPORT std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max = -1);
-
-/**
- * Join values by a separator and return a string.
- *
- * \param first the first iterator
- * \param last the last iterator
- * \param delim the optional delimiter
- */
-template <typename InputIt, typename DelimType = char>
-std::string join(InputIt first, InputIt last, DelimType delim = ':')
-{
-    std::ostringstream oss;
-
-    if (first != last) {
-        oss << *first;
-
-        while (++first != last)
-            oss << delim << *first;
-    }
-
-    return oss.str();
-}
-
-/**
- * Convenient overload.
- *
- * \param list the initializer list
- * \param delim the delimiter
- * \return the string
- */
-template <typename T, typename DelimType = char>
-inline std::string join(std::initializer_list<T> list, DelimType delim = ':')
-{
-    return join(list.begin(), list.end(), delim);
-}
-
-/**
  * Clamp the value between low and high.
  *
  * \param value the value
@@ -286,149 +48,6 @@
 }
 
 /**
- * Parse IRC message and determine if it's a command or a simple message.
- *
- * If it's a command, the plugin invocation command is removed from the
- * original message, otherwise it is copied verbatime.
- *
- * \param message the message line
- * \param cchar the command char (e.g '!')
- * \param plugin the plugin name
- * \return the pair
- */
-IRCCD_EXPORT message_pack parse_message(std::string message,
-                                        const std::string& cchar,
-                                        const std::string& plugin);
-
-/**
- * Server and identities must have strict names. This function can
- * be used to ensure that they are valid.
- *
- * \param name the identifier name
- * \return true if is valid
- */
-inline bool is_identifier(const std::string& name)
-{
-    return std::regex_match(name, std::regex("[A-Za-z0-9-_]+"));
-}
-
-/**
- * Check if the value is a boolean, 1, yes and true are accepted.
- *
- * \param value the value
- * \return true if is boolean
- * \note this function is case-insensitive
- */
-IRCCD_EXPORT bool is_boolean(std::string value) noexcept;
-
-/**
- * Check if the string is an integer.
- *
- * \param value the input
- * \param base the optional base
- * \return true if integer
- */
-IRCCD_EXPORT bool is_int(const std::string& value, int base = 10) noexcept;
-
-/**
- * Check if the string is real.
- *
- * \param value the value
- * \return true if real
- */
-IRCCD_EXPORT bool is_real(const std::string& value) noexcept;
-
-/**
- * Check if the string is a number.
- *
- * \param value the value
- * \return true if it is a number
- */
-inline bool is_number(const std::string& value) noexcept
-{
-    return is_int(value) || is_real(value);
-}
-
-/**
- * \cond HIDDEN_SYMBOLS
- */
-
-namespace detail {
-
-inline void sprintf(boost::format&)
-{
-}
-
-template <typename Arg, typename... Args>
-void sprintf(boost::format& fmter, const Arg& arg, const Args&... args)
-{
-    fmter % arg;
-    sprintf(fmter, args...);
-}
-
-} // !detail
-
-/**
- * \endcond
- */
-
-/**
- * Convenient wrapper arount boost::format in sprintf style.
- *
- * This is identical as calling boost::format(format) % arg1 % arg2 % argN.
- *
- * \param format the format string
- * \param args the arguments
- * \return the string
- */
-template <typename Format, typename... Args>
-std::string sprintf(const Format& format, const Args&... args)
-{
-    boost::format fmter(format);
-
-    detail::sprintf(fmter, args...);
-
-    return fmter.str();
-}
-
-/**
- * Try to convert the string into number.
- *
- * This function will try to convert the string to number in the limits of T.
- *
- * If the string is not a number or if the converted value is out of range than
- * specified boundaries, an exception is thrown.
- *
- * By default, the function will use numeric limits from T.
- *
- * \param number the string to convert
- * \param min the minimum (defaults to T minimum)
- * \param max the maximum (defaults to T maximum)
- * \return the converted value
- * \throw std::invalid_argument if number is not a string
- * \throw std::out_of_range if the number is not between min and max
- */
-template <typename T>
-inline T to_number(const std::string& number,
-                   T min = std::numeric_limits<T>::min(),
-                   T max = std::numeric_limits<T>::max())
-{
-    static_assert(std::is_integral<T>::value, "T must be integer type");
-
-    std::conditional_t<std::is_unsigned<T>::value, unsigned long long, long long> value;
-
-    if (std::is_unsigned<T>::value)
-        value = std::stoull(number);
-    else
-        value = std::stoll(number);
-
-    if (value < min || value > max)
-        throw std::out_of_range("out of range");
-
-    return static_cast<T>(value);
-}
-
-/**
  * Use arguments to avoid compiler warnings about unused parameters.
  */
 template <typename... Args>
@@ -436,513 +55,6 @@
 {
 }
 
-/**
- * Parse a network message from an input buffer and remove it from it.
- *
- * \param input the buffer, will be updated
- * \return the message or empty string if there is nothing
- */
-IRCCD_EXPORT std::string next_network(std::string& input);
-
-/**
- * Utilities for nlohmann json.
- */
-namespace json {
-
-/**
- * Require a property.
- *
- * \param json the json value
- * \param key the property name
- * \param type the requested property type
- * \return the value
- * \throw std::runtime_error if the property is missing
- */
-inline nlohmann::json require(const nlohmann::json& json, const std::string& key, nlohmann::json::value_t type)
-{
-    auto it = json.find(key);
-    auto dummy = nlohmann::json(type);
-
-    if (it == json.end())
-        throw std::runtime_error(sprintf("missing '%s' property", key));
-    if (it->type() != type)
-        throw std::runtime_error(sprintf("invalid '%s' property (%s expected, got %s)",
-            key, it->type_name(), dummy.type_name()));
-
-    return *it;
-}
-
-/**
- * Convenient access for booleans.
- *
- * \param json the json object
- * \param key the property key
- * \return the boolean
- * \throw std::runtime_error if the property is missing or not a boolean
- */
-inline bool require_bool(const nlohmann::json& json, const std::string& key)
-{
-    return require(json, key, nlohmann::json::value_t::boolean);
-}
-
-/**
- * Convenient access for ints.
- *
- * \param json the json object
- * \param key the property key
- * \return the int
- * \throw std::runtime_error if the property is missing or not ant int
- */
-inline std::int64_t require_int(const nlohmann::json& json, const std::string& key)
-{
-    auto it = json.find(key);
-
-    if (it == json.end())
-        throw std::runtime_error(sprintf("missing '%s' property", key));
-    if (it->is_number_integer())
-        return it->get<int>();
-    if (it->is_number_unsigned() && it->get<unsigned>() <= INT_MAX)
-        return static_cast<int>(it->get<unsigned>());
-
-    throw std::runtime_error(sprintf("invalid '%s' property (%s expected, got %s)",
-        key, it->type_name(), nlohmann::json(0).type_name()));
-}
-
-/**
- * Convenient access for unsigned ints.
- *
- * \param json the json object
- * \param key the property key
- * \return the unsigned int
- * \throw std::runtime_error if the property is missing or not ant int
- */
-inline std::uint64_t require_uint(const nlohmann::json& json, const std::string& key)
-{
-    auto it = json.find(key);
-
-    if (it == json.end())
-        throw std::runtime_error(sprintf("missing '%s' property", key));
-    if (it->is_number_unsigned())
-        return it->get<unsigned>();
-    if (it->is_number_integer() && it->get<int>() >= 0)
-        return static_cast<unsigned>(it->get<int>());
-
-    throw std::runtime_error(sprintf("invalid '%s' property (%s expected, got %s)",
-        key, it->type_name(), nlohmann::json(0U).type_name()));
-}
-
-/**
- * Convenient access for strings.
- *
- * \param json the json object
- * \param key the property key
- * \return the string
- * \throw std::runtime_error if the property is missing or not a string
- */
-inline std::string require_string(const nlohmann::json& json, const std::string& key)
-{
-    return require(json, key, nlohmann::json::value_t::string);
-}
-
-/**
- * Convenient access for unique identifiers.
- *
- * \param json the json object
- * \param key the property key
- * \return the identifier
- * \throw std::runtime_error if the property is invalid
- */
-inline std::string require_identifier(const nlohmann::json& json, const std::string& key)
-{
-    auto id = require_string(json, key);
-
-    if (!is_identifier(id))
-        throw std::runtime_error(sprintf("invalid '%s' identifier property", id));
-
-    return id;
-}
-
-/**
- * Convert the json value to boolean.
- *
- * \param json the json value
- * \param def the default value if not boolean
- * \return a boolean
- */
-inline bool to_bool(const nlohmann::json& json, bool def = false) noexcept
-{
-    return json.is_boolean() ? json.get<bool>() : def;
-}
-
-/**
- * Convert the json value to int.
- *
- * \param json the json value
- * \param def the default value if not an int
- * \return an int
- */
-inline std::int64_t to_int(const nlohmann::json& json, std::int64_t def = 0) noexcept
-{
-    return json.is_number_integer() ? json.get<std::int64_t>() : def;
-}
-
-/**
- * Convert the json value to unsigned.
- *
- * \param json the json value
- * \param def the default value if not a unsigned int
- * \return an unsigned int
- */
-inline std::uint64_t to_uint(const nlohmann::json& json, std::uint64_t def = 0) noexcept
-{
-    return json.is_number_unsigned() ? json.get<std::uint64_t>() : def;
-}
-
-/**
- * Convert the json value to string.
- *
- * \param json the json value
- * \param def the default value if not a string
- * \return a string
- */
-inline std::string to_string(const nlohmann::json& json, std::string def = "") noexcept
-{
-    return json.is_string() ? json.get<std::string>() : def;
-}
-
-/**
- * Get a property or return null one if not found or if json is not an object.
- *
- * \param json the json value
- * \param property the property key
- * \return the value or null one if not found
- */
-inline nlohmann::json get(const nlohmann::json& json, const std::string& property) noexcept
-{
-    auto it = json.find(property);
-
-    if (it == json.end())
-        return nlohmann::json();
-
-    return *it;
-}
-
-/**
- * Convenient access for boolean with default value.
- *
- * \param json the json value
- * \param key the property key
- * \param def the default value
- * \return the boolean
- */
-inline bool get_bool(const nlohmann::json& json,
-                     const std::string& key,
-                     bool def = false) noexcept
-{
-    return to_bool(get(json, key), def);
-}
-
-/**
- * Convenient access for ints with default value.
- *
- * \param json the json value
- * \param key the property key
- * \param def the default value
- * \return the int
- */
-inline std::int64_t get_int(const nlohmann::json& json,
-                            const std::string& key,
-                            std::int64_t def = 0) noexcept
-{
-    return to_int(get(json, key), def);
-}
-
-/**
- * Convenient access for unsigned ints with default value.
- *
- * \param json the json value
- * \param key the property key
- * \param def the default value
- * \return the unsigned int
- */
-inline std::uint64_t get_uint(const nlohmann::json& json,
-                              const std::string& key,
-                              std::uint64_t def = 0) noexcept
-{
-    return to_uint(get(json, key), def);
-}
-
-/**
- * Get an integer in the given range.
- *
- * \param json the json value
- * \param key the property key
- * \param min the minimum value
- * \param max the maximum value
- * \return the value
- */
-template <typename T>
-inline T get_int_range(const nlohmann::json& json,
-                       const std::string& key,
-                       std::int64_t min = std::numeric_limits<T>::min(),
-                       std::int64_t max = std::numeric_limits<T>::max()) noexcept
-{
-    return clamp(get_int(json, key), min, max);
-}
-
-/**
- * Get an unsigned integer in the given range.
- *
- * \param json the json value
- * \param key the property key
- * \param min the minimum value
- * \param max the maximum value
- * \return value
- */
-template <typename T>
-inline T get_uint_range(const nlohmann::json& json,
-                        const std::string& key,
-                        std::uint64_t min = std::numeric_limits<T>::min(),
-                        std::uint64_t max = std::numeric_limits<T>::max()) noexcept
-{
-    return clamp(get_uint(json, key), min, max);
-}
-
-/**
- * Convenient access for strings with default value.
- *
- * \param json the json value
- * \param key the property key
- * \param def the default value
- * \return the string
- */
-inline std::string get_string(const nlohmann::json& json,
-                              const std::string& key,
-                              std::string def = "") noexcept
-{
-    return to_string(get(json, key), def);
-}
-
-/**
- * Print the value as human readable.
- *
- * \param value the value
- * \return the string
- */
-inline std::string pretty(const nlohmann::json& value)
-{
-    switch (value.type()) {
-    case nlohmann::json::value_t::boolean:
-        return value.get<bool>() ? "true" : "false";
-    case nlohmann::json::value_t::string:
-        return value.get<std::string>();
-    default:
-        return value.dump();
-    }
-}
-
-/**
- * Pretty print a json value in the given object.
- *
- * \param object the object
- * \param prop the property
- * \return the pretty value or empty if key does not exist
- */
-inline std::string pretty(const nlohmann::json& object, const std::string& prop)
-{
-    auto it = object.find(prop);
-
-    if (it == object.end())
-        return "";
-
-    return pretty(*it);
-}
-
-} // !json
-
-/**
- * \brief Miscellaneous utilities for Pollable objects
- */
-namespace poller {
-
-/**
- * \cond HIDDEN_SYMBOLS
- */
-
-inline void prepare(fd_set &, fd_set &, net::Handle &) noexcept
-{
-}
-
-/**
- * \endcond
- */
-
-/**
- * Call prepare function for every Pollable objects.
- *
- * \param in the input set
- * \param out the output set
- * \param max the maximum handle
- * \param first the first Pollable object
- * \param rest the additional Pollable objects
- */
-template <typename Pollable, typename... Rest>
-inline void prepare(fd_set &in, fd_set &out, net::Handle &max, Pollable &first, Rest&... rest)
-{
-    first.prepare(in, out, max);
-    prepare(in, out, max, rest...);
-}
-
-/**
- * \cond HIDDEN_SYMBOLS
- */
-
-inline void sync(fd_set &, fd_set &) noexcept
-{
-}
-
-/**
- * \endcond
- */
-
-/**
- * Call sync function for every Pollable objects.
- *
- * \param in the input set
- * \param out the output set
- * \param first the first Pollable object
- * \param rest the additional Pollable objects
- */
-template <typename Pollable, typename... Rest>
-inline void sync(fd_set &in, fd_set &out, Pollable &first, Rest&... rest)
-{
-    first.sync(in, out);
-    sync(in, out, rest...);
-}
-
-/**
- * Prepare and sync Pollable objects.
- *
- * \param timeout the timeout in milliseconds (< 0 means forever)
- * \param first the the first Pollable object
- * \param rest the additional Pollable objects
- */
-template <typename Pollable, typename... Rest>
-void poll(int timeout, Pollable &first, Rest&... rest)
-{
-    fd_set in, out;
-    timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
-
-    FD_ZERO(&in);
-    FD_ZERO(&out);
-
-    net::Handle max = 0;
-
-    prepare(in, out, max, first, rest...);
-
-    if (select(max + 1, &in, &out, nullptr, timeout < 0 ? nullptr : &tv) < 0 && errno != EINTR) {
-        throw std::runtime_error(std::strerror(errno));
-    } else {
-        sync(in, out, first, rest...);
-    }
-}
-
-} // !poller
-
-namespace fs {
-
-/**
- * Get the base name from a path.
- *
- * Example, baseName("/etc/foo.conf") // foo.conf
- *
- * \param path the path
- * \return the base name
- */
-inline std::string base_name(const std::string& path)
-{
-    return boost::filesystem::path(path).filename().string();
-}
-
-/**
- * Get the parent directory from a path.
- *
- * Example, dirName("/etc/foo.conf") // /etc
- *
- * \param path the path
- * \return the parent directory
- */
-inline std::string dir_name(std::string path)
-{
-    return boost::filesystem::path(path).parent_path().string();
-}
-
-/**
- * Search an item recursively.
- *
- * The predicate must have the following signature:
- *  void f(const boost::filesystem::directory_entry& entry)
- *
- * Where:
- *   - base is the current parent directory in the tree
- *   - entry is the current entry
- *
- * \param base the base directory
- * \param predicate the predicate
- * \param recursive true to do recursive search
- * \return the full path name to the file or empty string if never found
- * \throw std::runtime_error on read errors
- */
-template <typename Predicate>
-std::string find_if(const std::string& base, bool recursive, Predicate&& predicate)
-{
-    auto find = [&] (auto it) -> std::string {
-        for (const auto& entry : it)
-            if (predicate(entry))
-                return entry.path().string();
-
-        return "";
-    };
-
-    if (recursive)
-        return find(boost::filesystem::recursive_directory_iterator(base));
-
-    return find(boost::filesystem::directory_iterator(base));
-}
-
-/**
- * Find a file by name recursively.
- *
- * \param base the base directory
- * \param name the file name
- * \param recursive true to do recursive search
- * \return the full path name to the file or empty string if never found
- * \throw std::runtime_error on read errors
- */
-inline std::string find(const std::string& base, const std::string& name, bool recursive = false)
-{
-    return find_if(base, recursive, [&] (const auto& entry) {
-        return entry.path().filename().string() == name;
-    });
-}
-
-/**
- * Overload by regular expression.
- *
- * \param base the base directory
- * \param regex the regular expression
- * \param recursive true to do recursive search
- * \return the full path name to the file or empty string if never found
- * \throw std::runtime_error on read errors
- */
-inline std::string find(const std::string& base, const std::regex& regex, bool recursive = false)
-{
-    return find_if(base, recursive, [&] (const auto& entry) {
-        return std::regex_match(entry.path().filename().string(), regex);
-    });
-}
-
-} // !fs
 
 } // !util
 
--- a/libirccd-js/irccd/js_directory_module.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd-js/irccd/js_directory_module.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -24,8 +24,7 @@
 #include <stdexcept>
 #include <string>
 
-#include <boost/filesystem.hpp>
-
+#include "fs_util.hpp"
 #include "duktape.hpp"
 #include "js_directory_module.hpp"
 #include "js_irccd_module.hpp"
@@ -71,7 +70,7 @@
         std::string path;
 
         if (duk_is_string(ctx, pattern_index))
-            path = util::fs::find(base, dukx_get_std_string(ctx, pattern_index), recursive);
+            path = fs_util::find(base, dukx_get_std_string(ctx, pattern_index), recursive);
         else {
             // Check if it's a valid RegExp object.
             duk_get_global_string(ctx, "RegExp");
@@ -83,7 +82,7 @@
                 auto pattern = duk_to_string(ctx, -1);
                 duk_pop(ctx);
 
-                path = util::fs::find(base, std::regex(pattern), recursive);
+                path = fs_util::find(base, std::regex(pattern), recursive);
             } else
                 duk_error(ctx, DUK_ERR_TYPE_ERROR, "pattern must be a string or a regex expression");
         }
--- a/libirccd-js/irccd/js_file_module.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd-js/irccd/js_file_module.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -33,6 +33,7 @@
 
 #include <irccd/js_plugin.hpp>
 
+#include "fs_util.hpp"
 #include "js_file_module.hpp"
 #include "js_irccd_module.hpp"
 
@@ -152,7 +153,7 @@
  */
 duk_ret_t method_basename(duk_context* ctx)
 {
-    dukx_push_std_string(ctx, util::fs::base_name(self(ctx)->path()));
+    dukx_push_std_string(ctx, fs_util::base_name(self(ctx)->path()));
 
     return 1;
 }
@@ -181,7 +182,7 @@
  */
 duk_ret_t method_dirname(duk_context* ctx)
 {
-    dukx_push_std_string(ctx, util::fs::dir_name(self(ctx)->path()));
+    dukx_push_std_string(ctx, fs_util::dir_name(self(ctx)->path()));
 
     return 1;
 }
@@ -518,7 +519,7 @@
  */
 duk_ret_t function_basename(duk_context* ctx)
 {
-    dukx_push_std_string(ctx, util::fs::base_name(duk_require_string(ctx, 0)));
+    dukx_push_std_string(ctx, fs_util::base_name(duk_require_string(ctx, 0)));
 
     return 1;
 }
@@ -536,7 +537,7 @@
  */
 duk_ret_t function_dirname(duk_context* ctx)
 {
-    dukx_push_std_string(ctx, util::fs::dir_name(duk_require_string(ctx, 0)));
+    dukx_push_std_string(ctx, fs_util::dir_name(duk_require_string(ctx, 0)));
 
     return 1;
 }
--- a/libirccd-js/irccd/js_timer_module.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd-js/irccd/js_timer_module.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -19,6 +19,7 @@
 #include <irccd/irccd.hpp>
 #include <irccd/logger.hpp>
 #include <irccd/js_plugin.hpp>
+#include <irccd/string_util.hpp>
 #include <irccd/timer.hpp>
 
 #include "js_irccd_module.hpp"
@@ -48,7 +49,7 @@
 
         if (duk_is_callable(plugin->context(), -1)) {
             if (duk_pcall(plugin->context(), 0) != 0)
-                log::warning(util::sprintf("plugin %s: %s", plugin->name(),
+                log::warning(string_util::sprintf("plugin %s: %s", plugin->name(),
                     dukx_exception(plugin->context(), -1).stack));
             else
                 duk_pop(plugin->context());
--- a/libirccd-js/irccd/js_util_module.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd-js/irccd/js_util_module.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -20,7 +20,7 @@
 
 #include <libircclient.h>
 
-#include <irccd/util.hpp>
+#include <irccd/string_util.hpp>
 
 #include "js_util_module.hpp"
 #include "js_plugin.hpp"
@@ -41,9 +41,9 @@
  *   fieldn: ...
  * }
  */
-util::subst get_subst(duk_context* ctx, int index)
+string_util::subst get_subst(duk_context* ctx, int index)
 {
-    util::subst params;
+    string_util::subst params;
 
     if (!duk_is_object(ctx, index))
         return params;
@@ -72,13 +72,13 @@
     std::string pattern = " \t\n";
 
     if (duk_is_string(ctx, 0))
-        result = util::split(dukx_get_std_string(ctx, 0), pattern);
+        result = string_util::split(dukx_get_std_string(ctx, 0), pattern);
     else if (duk_is_array(ctx, 0)) {
         duk_enum(ctx, 0, DUK_ENUM_ARRAY_INDICES_ONLY);
 
         while (duk_next(ctx, -1, 1)) {
             // Split individual tokens as array if spaces are found.
-            auto tmp = util::split(duk_to_string(ctx, -1), pattern);
+            auto tmp = string_util::split(duk_to_string(ctx, -1), pattern);
 
             result.insert(result.end(), tmp.begin(), tmp.end());
             duk_pop_2(ctx);
@@ -102,7 +102,7 @@
         return value;
 
     value = duk_to_int(ctx, index);
-    
+
     if (value <= 0)
         duk_error(ctx, DUK_ERR_RANGE_ERROR, "argument %d (%s) must be positive", index, name);
 
@@ -219,7 +219,7 @@
 duk_ret_t format(duk_context* ctx)
 {
     try {
-        dukx_push_std_string(ctx, util::format(dukx_get_std_string(ctx, 0), get_subst(ctx, 1)));
+        dukx_push_std_string(ctx, string_util::format(dukx_get_std_string(ctx, 0), get_subst(ctx, 1)));
     } catch (const std::exception &ex) {
         dukx_throw(ctx, SyntaxError(ex.what()));
     }
--- a/libirccd-js/irccd/module.hpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd-js/irccd/module.hpp	Fri Oct 27 21:45:32 2017 +0200
@@ -31,6 +31,7 @@
 
 #include <cassert>
 #include <memory>
+#include <string>
 
 #include "sysconfig.hpp"
 #include "util.hpp"
--- a/libirccd-test/irccd/command-tester.hpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd-test/irccd/command-tester.hpp	Fri Oct 27 21:45:32 2017 +0200
@@ -25,7 +25,7 @@
 
 #include "irccd.hpp"
 #include "irccdctl.hpp"
-#include "util.hpp"
+#include "net_util.hpp"
 
 namespace irccd {
 
@@ -47,7 +47,7 @@
         boost::timer::cpu_timer timer;
 
         while (!predicate() && timer.elapsed().wall / 1000000LL < 30000)
-            util::poller::poll(250, m_irccd, m_irccdctl);
+            net_util::poll(250, m_irccd, m_irccdctl);
     }
 };
 
--- a/libirccd/irccd/command.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd/irccd/command.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -18,6 +18,7 @@
 
 #include "command.hpp"
 #include "irccd.hpp"
+#include "json_util.hpp"
 #include "service.hpp"
 #include "transport.hpp"
 #include "util.hpp"
@@ -143,7 +144,7 @@
 
 void plugin_config_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    auto plugin = irccd.plugins().require(util::json::require_identifier(args, "plugin"));
+    auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin"));
 
     if (args.count("value") > 0)
         exec_set(client, *plugin, args);
@@ -158,7 +159,7 @@
 
 void plugin_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    auto plugin = irccd.plugins().require(util::json::require_identifier(args, "plugin"));
+    auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin"));
 
     client.success("plugin-info", {
         { "author",     plugin->author()    },
@@ -192,7 +193,7 @@
 
 void plugin_load_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.plugins().load(util::json::require_identifier(args, "plugin"));
+    irccd.plugins().load(json_util::require_identifier(args, "plugin"));
     client.success("plugin-load");
 }
 
@@ -203,7 +204,7 @@
 
 void plugin_reload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.plugins().require(util::json::require_identifier(args, "plugin"))->on_reload(irccd);
+    irccd.plugins().require(json_util::require_identifier(args, "plugin"))->on_reload(irccd);
     client.success("plugin-reload");
 }
 
@@ -214,7 +215,7 @@
 
 void plugin_unload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.plugins().unload(util::json::require_identifier(args, "plugin"));
+    irccd.plugins().unload(json_util::require_identifier(args, "plugin"));
     client.success("plugin-unload");
 }
 
@@ -225,9 +226,9 @@
 
 void server_channel_mode_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->cmode(
-        util::json::require_string(args, "channel"),
-        util::json::require_string(args, "mode")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->cmode(
+        json_util::require_string(args, "channel"),
+        json_util::require_string(args, "mode")
     );
     client.success("server-cmode");
 }
@@ -239,9 +240,9 @@
 
 void server_channel_notice_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_string(args, "server"))->cnotice(
-        util::json::require_string(args, "channel"),
-        util::json::require_string(args, "message")
+    irccd.servers().require(json_util::require_string(args, "server"))->cnotice(
+        json_util::require_string(args, "channel"),
+        json_util::require_string(args, "message")
     );
     client.success("server-cnotice");
 }
@@ -288,7 +289,7 @@
 void server_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
     auto response = nlohmann::json::object();
-    auto server = irccd.servers().require(util::json::require_identifier(args, "server"));
+    auto server = irccd.servers().require(json_util::require_identifier(args, "server"));
 
     // General stuff.
     response.push_back({"name", server->name()});
@@ -317,9 +318,9 @@
 
 void server_invite_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->invite(
-        util::json::require_string(args, "target"),
-        util::json::require_string(args, "channel")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->invite(
+        json_util::require_string(args, "target"),
+        json_util::require_string(args, "channel")
     );
     client.success("server-invite");
 }
@@ -331,9 +332,9 @@
 
 void server_join_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->join(
-        util::json::require_string(args, "channel"),
-        util::json::get_string(args, "password")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->join(
+        json_util::require_string(args, "channel"),
+        json_util::get_string(args, "password")
     );
     client.success("server-join");
 }
@@ -345,10 +346,10 @@
 
 void server_kick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->kick(
-        util::json::require_string(args, "target"),
-        util::json::require_string(args, "channel"),
-        util::json::get_string(args, "reason")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->kick(
+        json_util::require_string(args, "target"),
+        json_util::require_string(args, "channel"),
+        json_util::get_string(args, "reason")
     );
     client.success("server-kick");
 }
@@ -377,9 +378,9 @@
 
 void server_me_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->me(
-        util::json::require_string(args, "target"),
-        util::json::require_string(args, "message")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->me(
+        json_util::require_string(args, "target"),
+        json_util::require_string(args, "message")
     );
     client.success("server-me");
 }
@@ -391,9 +392,9 @@
 
 void server_message_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->message(
-        util::json::require_string(args, "target"),
-        util::json::require_string(args, "message")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->message(
+        json_util::require_string(args, "target"),
+        json_util::require_string(args, "message")
     );
     client.success("server-message");
 }
@@ -405,8 +406,8 @@
 
 void server_mode_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->mode(
-        util::json::require_string(args, "mode")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->mode(
+        json_util::require_string(args, "mode")
     );
     client.success("server-mode");
 }
@@ -418,8 +419,8 @@
 
 void server_nick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->set_nickname(
-        util::json::require_string(args, "nickname")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->set_nickname(
+        json_util::require_string(args, "nickname")
     );
     client.success("server-nick");
 }
@@ -431,9 +432,9 @@
 
 void server_notice_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->notice(
-        util::json::require_string(args, "target"),
-        util::json::require_string(args, "message")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->notice(
+        json_util::require_string(args, "target"),
+        json_util::require_string(args, "message")
     );
     client.success("server-notice");
 }
@@ -445,9 +446,9 @@
 
 void server_part_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->part(
-        util::json::require_string(args, "channel"),
-        util::json::get_string(args, "reason")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->part(
+        json_util::require_string(args, "channel"),
+        json_util::get_string(args, "reason")
     );
     client.success("server-part");
 }
@@ -477,9 +478,9 @@
 
 void server_topic_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(util::json::require_identifier(args, "server"))->topic(
-        util::json::require_string(args, "channel"),
-        util::json::require_string(args, "topic")
+    irccd.servers().require(json_util::require_identifier(args, "server"))->topic(
+        json_util::require_string(args, "channel"),
+        json_util::require_string(args, "topic")
     );
     client.success("server-topic");
 }
@@ -503,7 +504,7 @@
     };
 
     // Create a copy to avoid incomplete edition in case of errors.
-    auto index = util::json::require_uint(args, "index");
+    auto index = json_util::require_uint(args, "index");
     auto rule = irccd.rules().require(index);
 
     updateset(rule.channels(), args, "channels");
@@ -556,7 +557,7 @@
 
 void rule_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    client.success("rule-info", to_json(irccd.rules().require(util::json::require_uint(args, "index"))));
+    client.success("rule-info", to_json(irccd.rules().require(json_util::require_uint(args, "index"))));
 }
 
 rule_remove_command::rule_remove_command()
@@ -566,7 +567,7 @@
 
 void rule_remove_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    unsigned position = util::json::require_uint(args, "index");
+    unsigned position = json_util::require_uint(args, "index");
 
     if (irccd.rules().length() == 0)
         client.error("rule-remove", "rule list is empty");
@@ -585,8 +586,8 @@
 
 void rule_move_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    auto from = util::json::require_uint(args, "from");
-    auto to = util::json::require_uint(args, "to");
+    auto from = json_util::require_uint(args, "from");
+    auto to = json_util::require_uint(args, "to");
 
     /*
      * Examples of moves
@@ -639,7 +640,7 @@
 
 void rule_add_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    auto index = util::json::get_uint(args, "index", irccd.rules().length());
+    auto index = json_util::get_uint(args, "index", irccd.rules().length());
     auto rule = from_json(args);
 
     if (index > irccd.rules().length())
--- a/libirccd/irccd/config.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd/irccd/config.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -27,10 +27,10 @@
 #include "rule.hpp"
 #include "server.hpp"
 #include "service.hpp"
+#include "string_util.hpp"
 #include "sysconfig.hpp"
 #include "system.hpp"
 #include "transport.hpp"
-#include "util.hpp"
 
 namespace irccd {
 
@@ -43,12 +43,12 @@
         if (tmpl.empty())
             return input;
 
-        util::subst params;
+        string_util::subst params;
 
-        params.flags &= ~(util::subst_flags::irc_attrs);
+        params.flags &= ~(string_util::subst_flags::irc_attrs);
         params.keywords.emplace("message", std::move(input));
 
-        return util::format(tmpl, params);
+        return string_util::format(tmpl, params);
     }
 
 public:
@@ -133,9 +133,9 @@
         throw std::invalid_argument("transport: missing 'port' parameter");
 
     try {
-        port = util::to_number<std::uint16_t>(it->value());
+        port = string_util::to_number<std::uint16_t>(it->value());
     } catch (const std::exception&) {
-        throw std::invalid_argument(util::sprintf("transport: invalid port number: %s", it->value()));
+        throw std::invalid_argument(string_util::sprintf("transport: invalid port number: %s", it->value()));
     }
 
     // Address.
@@ -168,7 +168,7 @@
     std::string pkey;
     std::string cert;
 
-    if ((it = sc.find("ssl")) != sc.end() && util::is_boolean(it->value())) {
+    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) {
         if ((it = sc.find("certificate")) == sc.end())
             throw std::invalid_argument("transport: missing 'certificate' parameter");
 
@@ -226,7 +226,7 @@
     else if (it->value() == "unix")
         transport = load_transport_unix(sc);
     else
-        throw std::invalid_argument(util::sprintf("transport: invalid type given: %s", it->value()));
+        throw std::invalid_argument(string_util::sprintf("transport: invalid type given: %s", it->value()));
 
     if ((it = sc.find("password")) != sc.end())
         transport->set_password(it->value());
@@ -269,7 +269,7 @@
     else if (it->value() == "accept")
         action = rule::action_type::accept;
     else
-        throw std::invalid_argument(util::sprintf("rule: invalid action given: %s", it->value()));
+        throw std::invalid_argument(string_util::sprintf("rule: invalid action given: %s", it->value()));
 
     return {
         std::move(servers),
@@ -290,14 +290,14 @@
 
     if ((it = sc.find("name")) == sc.end())
         throw std::invalid_argument("server: missing 'name' parameter");
-    else if (!util::is_identifier(it->value()))
-        throw std::invalid_argument(util::sprintf("server: invalid identifier: %s", it->value()));
+    else if (!string_util::is_identifier(it->value()))
+        throw std::invalid_argument(string_util::sprintf("server: invalid identifier: %s", it->value()));
 
     auto sv = std::make_shared<server>(it->value());
 
     // Host
     if ((it = sc.find("host")) == sc.end())
-        throw std::invalid_argument(util::sprintf("server %s: missing host", sv->name()));
+        throw std::invalid_argument(string_util::sprintf("server %s: missing host", sv->name()));
 
     sv->set_host(it->value());
 
@@ -306,11 +306,11 @@
         sv->set_password(it->value());
 
     // Optional flags
-    if ((it = sc.find("ipv6")) != sc.end() && util::is_boolean(it->value()))
+    if ((it = sc.find("ipv6")) != sc.end() && string_util::is_boolean(it->value()))
         sv->set_flags(sv->flags() | server::ipv6);
-    if ((it = sc.find("ssl")) != sc.end() && util::is_boolean(it->value()))
+    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value()))
         sv->set_flags(sv->flags() | server::ssl);
-    if ((it = sc.find("ssl-verify")) != sc.end() && util::is_boolean(it->value()))
+    if ((it = sc.find("ssl-verify")) != sc.end() && string_util::is_boolean(it->value()))
         sv->set_flags(sv->flags() | server::ssl_verify);
 
     // Optional identity
@@ -318,9 +318,9 @@
         config.load_server_identity(*sv, it->value());
 
     // Options
-    if ((it = sc.find("auto-rejoin")) != sc.end() && util::is_boolean(it->value()))
+    if ((it = sc.find("auto-rejoin")) != sc.end() && string_util::is_boolean(it->value()))
         sv->set_flags(sv->flags() | server::auto_rejoin);
-    if ((it = sc.find("join-invite")) != sc.end() && util::is_boolean(it->value()))
+    if ((it = sc.find("join-invite")) != sc.end() && string_util::is_boolean(it->value()))
         sv->set_flags(sv->flags() | server::join_invite);
 
     // Channels
@@ -343,15 +343,15 @@
     // Reconnect and ping timeout
     try {
         if ((it = sc.find("port")) != sc.end())
-            sv->set_port(util::to_number<std::uint16_t>(it->value()));
+            sv->set_port(string_util::to_number<std::uint16_t>(it->value()));
         if ((it = sc.find("reconnect-tries")) != sc.end())
-            sv->set_reconnect_tries(util::to_number<std::int8_t>(it->value()));
+            sv->set_reconnect_tries(string_util::to_number<std::int8_t>(it->value()));
         if ((it = sc.find("reconnect-timeout")) != sc.end())
-            sv->set_reconnect_delay(util::to_number<std::uint16_t>(it->value()));
+            sv->set_reconnect_delay(string_util::to_number<std::uint16_t>(it->value()));
         if ((it = sc.find("ping-timeout")) != sc.end())
-            sv->set_ping_timeout(util::to_number<std::uint16_t>(it->value()));
+            sv->set_ping_timeout(string_util::to_number<std::uint16_t>(it->value()));
     } catch (const std::exception&) {
-        log::warning(util::sprintf("server %s: invalid number for %s: %s",
+        log::warning(string_util::sprintf("server %s: invalid number for %s: %s",
             sv->name(), it->key(), it->value()));
     }
 
@@ -404,12 +404,12 @@
 
 bool config::is_verbose() const noexcept
 {
-    return util::is_boolean(get(document_, "logs", "verbose"));
+    return string_util::is_boolean(get(document_, "logs", "verbose"));
 }
 
 bool config::is_foreground() const noexcept
 {
-    return util::is_boolean(get(document_, "general", "foreground"));
+    return string_util::is_boolean(get(document_, "general", "foreground"));
 }
 
 std::string config::pidfile() const
@@ -445,7 +445,7 @@
         else if (it->value() == "syslog")
             iface = load_log_syslog();
         else if (it->value() != "console")
-            throw std::runtime_error(util::sprintf("logs: unknown log type: %s", it->value()));
+            throw std::runtime_error(string_util::sprintf("logs: unknown log type: %s", it->value()));
 
         if (iface)
             log::set_logger(std::move(iface));
@@ -520,7 +520,7 @@
         return;
 
     for (const auto& option : *it) {
-        if (!util::is_identifier(option.key()))
+        if (!string_util::is_identifier(option.key()))
             continue;
 
         irccd.plugins().load(option.key(), option.value());
--- a/libirccd/irccd/dynlib_plugin.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd/irccd/dynlib_plugin.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -22,7 +22,7 @@
 #include <boost/filesystem.hpp>
 
 #include "dynlib_plugin.hpp"
-#include "util.hpp"
+#include "string_util.hpp"
 
 #if defined(IRCCD_SYSTEM_WINDOWS)
 #   define DYNLIB_EXTENSION ".dll"
@@ -53,11 +53,11 @@
 
     base.erase(std::remove_if(base.begin(), base.end(), need_remove), base.end());
 
-    auto fname = util::sprintf("irccd_%s_load", base);
+    auto fname = string_util::sprintf("irccd_%s_load", base);
     auto load = dso_.get<load_t>(fname);
 
     if (!load)
-        throw std::runtime_error(util::sprintf("missing plugin entry function '%s'", fname));
+        throw std::runtime_error(string_util::sprintf("missing plugin entry function '%s'", fname));
 
     plugin_ = load(name, path);
 
--- a/libirccd/irccd/irccd.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd/irccd/irccd.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -18,7 +18,7 @@
 
 #include "irccd.hpp"
 #include "logger.hpp"
-#include "net.hpp"
+#include "net_util.hpp"
 #include "service.hpp"
 #include "util.hpp"
 
@@ -48,12 +48,12 @@
 void irccd::run()
 {
     while (running_)
-        util::poller::poll(250, *this);
+        net_util::poll(250, *this);
 }
 
 void irccd::prepare(fd_set& in, fd_set& out, net::Handle& max)
 {
-    util::poller::prepare(in, out, max, *itr_service_, *server_service_, *tpt_service_);
+    net_util::prepare(in, out, max, *itr_service_, *server_service_, *tpt_service_);
 }
 
 void irccd::sync(fd_set& in, fd_set& out)
@@ -61,7 +61,7 @@
     if (!running_)
         return;
 
-    util::poller::sync(in, out, *itr_service_, *server_service_, *tpt_service_);
+    net_util::sync(in, out, *itr_service_, *server_service_, *tpt_service_);
 
     if (!running_)
         return;
--- a/libirccd/irccd/server.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd/irccd/server.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -33,9 +33,10 @@
 #  include <resolv.h>
 #endif
 
+#include "json_util.hpp"
 #include "logger.hpp"
-#include "util.hpp"
 #include "server.hpp"
+#include "string_util.hpp"
 #include "system.hpp"
 
 namespace irccd {
@@ -342,7 +343,7 @@
         if (c < 4 || params[2] == nullptr || params[3] == nullptr)
             return;
 
-        auto users = util::split(params[3], " \t");
+        auto users = string_util::split(params[3], " \t");
 
         // The listing may add some prefixes, remove them if needed.
         for (auto u : users)
@@ -404,7 +405,7 @@
 
         auto it = whois_map_.find(params[1]);
         if (it != whois_map_.end()) {
-            auto channels = util::split(params[2], " \t");
+            auto channels = string_util::split(params[2], " \t");
 
             // Clean their prefixes.
             for (auto &s : channels)
@@ -467,27 +468,27 @@
 
 std::shared_ptr<server> server::from_json(const nlohmann::json& object)
 {
-    auto sv = std::make_shared<server>(util::json::require_identifier(object, "name"));
+    auto sv = std::make_shared<server>(json_util::require_identifier(object, "name"));
 
-    sv->set_host(util::json::require_string(object, "host"));
-    sv->set_password(util::json::get_string(object, "password"));
-    sv->set_nickname(util::json::get_string(object, "nickname", sv->nickname()));
-    sv->set_realname(util::json::get_string(object, "realname", sv->realname()));
-    sv->set_username(util::json::get_string(object, "username", sv->username()));
-    sv->set_ctcp_version(util::json::get_string(object, "ctcpVersion", sv->ctcp_version()));
-    sv->set_command_char(util::json::get_string(object, "commandChar", sv->command_char()));
+    sv->set_host(json_util::require_string(object, "host"));
+    sv->set_password(json_util::get_string(object, "password"));
+    sv->set_nickname(json_util::get_string(object, "nickname", sv->nickname()));
+    sv->set_realname(json_util::get_string(object, "realname", sv->realname()));
+    sv->set_username(json_util::get_string(object, "username", sv->username()));
+    sv->set_ctcp_version(json_util::get_string(object, "ctcpVersion", sv->ctcp_version()));
+    sv->set_command_char(json_util::get_string(object, "commandChar", sv->command_char()));
 
     if (object.find("port") != object.end())
-        sv->set_port(util::json::get_uint_range<std::uint16_t>(object, "port"));
-    if (util::json::get_bool(object, "ipv6"))
+        sv->set_port(json_util::get_uint(object, "port"));
+    if (json_util::get_bool(object, "ipv6"))
         sv->set_flags(sv->flags() | server::ipv6);
-    if (util::json::get_bool(object, "ssl"))
+    if (json_util::get_bool(object, "ssl"))
         sv->set_flags(sv->flags() | server::ssl);
-    if (util::json::get_bool(object, "sslVerify"))
+    if (json_util::get_bool(object, "sslVerify"))
         sv->set_flags(sv->flags() | server::ssl_verify);
-    if (util::json::get_bool(object, "autoRejoin"))
+    if (json_util::get_bool(object, "autoRejoin"))
         sv->set_flags(sv->flags() | server::auto_rejoin);
-    if (util::json::get_bool(object, "joinInvite"))
+    if (json_util::get_bool(object, "joinInvite"))
         sv->set_flags(sv->flags() | server::join_invite);
 
     return sv;
@@ -619,7 +620,7 @@
 void server::update() noexcept
 {
     if (state_next_) {
-        log::debug(util::sprintf("server %s: switch state %s -> %s",
+        log::debug(string_util::sprintf("server %s: switch state %s -> %s",
             name_, state_->ident(), state_next_->ident()));
 
         state_ = std::move(state_next_);
@@ -876,7 +877,7 @@
             log::warning() << irc_strerror(irc_errno(*server.session_)) << std::endl;
 
             if (server.recotries_ != 0)
-                log::warning(util::sprintf("server %s: retrying in %hu seconds", server.name_, server.recodelay_));
+                log::warning(string_util::sprintf("server %s: retrying in %hu seconds", server.name_, server.recodelay_));
 
             server.next(std::make_unique<disconnected_state>());
         } else
@@ -889,7 +890,7 @@
 #if !defined(IRCCD_SYSTEM_WINDOWS)
         (void)res_init();
 #endif
-        log::info(util::sprintf("server %s: trying to connect to %s, port %hu", server.name_, server.host_, server.port_));
+        log::info(string_util::sprintf("server %s: trying to connect to %s, port %hu", server.name_, server.host_, server.port_));
 
         if (!connect(server)) {
             log::warning() << "server " << server.name_ << ": disconnected while connecting: ";
@@ -920,7 +921,7 @@
         log::warning() << "server " << server.name_ << ": disconnected" << std::endl;
 
         if (server.recodelay_ > 0)
-            log::warning(util::sprintf("server %s: retrying in %hu seconds", server.name_, server.recodelay_));
+            log::warning(string_util::sprintf("server %s: retrying in %hu seconds", server.name_, server.recodelay_));
 
         server.next(std::make_unique<disconnected_state>());
     } else if (server.timer_.elapsed().wall / 1000000LL >= server.timeout_ * 1000) {
--- a/libirccd/irccd/service.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccd/irccd/service.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -25,6 +25,7 @@
 #include "irccd.hpp"
 #include "logger.hpp"
 #include "service.hpp"
+#include "string_util.hpp"
 #include "system.hpp"
 #include "transport.hpp"
 
@@ -157,7 +158,7 @@
     auto plugin = get(name);
 
     if (!plugin)
-        throw std::invalid_argument(util::sprintf("plugin %s not found", name));
+        throw std::invalid_argument(string_util::sprintf("plugin %s not found", name));
 
     return plugin;
 }
@@ -189,12 +190,12 @@
 
 plugin_config plugin_service::config(const std::string& id)
 {
-    return to_map<plugin_config>(irccd_.config(), util::sprintf("plugin.%s", id));
+    return to_map<plugin_config>(irccd_.config(), string_util::sprintf("plugin.%s", id));
 }
 
 plugin_formats plugin_service::formats(const std::string& id)
 {
-    return to_map<plugin_formats>(irccd_.config(), util::sprintf("format.%s", id));
+    return to_map<plugin_formats>(irccd_.config(), string_util::sprintf("format.%s", id));
 }
 
 plugin_paths plugin_service::paths(const std::string& id)
@@ -202,7 +203,7 @@
     class config cfg(irccd_.config());
 
     auto defaults = to_map<plugin_paths>(cfg, "paths");
-    auto paths = to_map<plugin_paths>(cfg, util::sprintf("paths.%s", id));
+    auto paths = to_map<plugin_paths>(cfg, string_util::sprintf("paths.%s", id));
 
     // Fill defaults paths.
     if (!defaults.count("cache"))
@@ -270,7 +271,7 @@
             add(std::move(plugin));
         }
     } catch (const std::exception& ex) {
-        log::warning(util::sprintf("plugin %s: %s", name, ex.what()));
+        log::warning(string_util::sprintf("plugin %s: %s", name, ex.what()));
     }
 }
 
@@ -342,17 +343,17 @@
 {
     bool result = true;
 
-    log::debug(util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s",
+    log::debug(string_util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s",
         server, channel, origin, plugin, event));
 
     int i = 0;
     for (const auto& rule : rules_) {
         log::debug() << "  candidate "   << i++ << ":\n"
-                     << "    servers: "  << util::join(rule.servers().begin(), rule.servers().end()) << "\n"
-                     << "    channels: " << util::join(rule.channels().begin(), rule.channels().end()) << "\n"
-                     << "    origins: "  << util::join(rule.origins().begin(), rule.origins().end()) << "\n"
-                     << "    plugins: "  << util::join(rule.plugins().begin(), rule.plugins().end()) << "\n"
-                     << "    events: "   << util::join(rule.events().begin(), rule.events().end()) << "\n"
+                     << "    servers: "  << string_util::join(rule.servers().begin(), rule.servers().end()) << "\n"
+                     << "    channels: " << string_util::join(rule.channels().begin(), rule.channels().end()) << "\n"
+                     << "    origins: "  << string_util::join(rule.origins().begin(), rule.origins().end()) << "\n"
+                     << "    plugins: "  << string_util::join(rule.plugins().begin(), rule.plugins().end()) << "\n"
+                     << "    events: "   << string_util::join(rule.events().begin(), rule.events().end()) << "\n"
                      << "    action: "   << ((rule.action() == rule::action_type::accept) ? "accept" : "drop") << std::endl;
 
         if (rule.match(server, channel, origin, plugin, event))
@@ -560,19 +561,19 @@
 
     irccd_.post(event_handler{ev.server->name(), ev.origin, ev.channel,
         [=] (plugin& plugin) -> std::string {
-            return util::parse_message(
+            return string_util::parse_message(
                 ev.message,
                 ev.server->command_char(),
                 plugin.name()
-            ).type == util::message_pack::type::command ? "onCommand" : "onMessage";
+            ).type == string_util::message_pack::type::command ? "onCommand" : "onMessage";
         },
         [=] (plugin& plugin) mutable {
             auto copy = ev;
-            auto pack = util::parse_message(copy.message, copy.server->command_char(), plugin.name());
+            auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name());
 
             copy.message = pack.message;
 
-            if (pack.type == util::message_pack::type::command)
+            if (pack.type == string_util::message_pack::type::command)
                 plugin.on_command(irccd_, copy);
             else
                 plugin.on_message(irccd_, copy);
@@ -632,7 +633,7 @@
 {
     log::debug() << "server " << ev.server->name() << ": event onNames:\n";
     log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  names: " << util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
+    log::debug() << "  names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
 
     auto names = nlohmann::json::array();
 
@@ -742,19 +743,19 @@
 
     irccd_.post(event_handler{ev.server->name(), ev.origin, /* channel */ "",
         [=] (plugin& plugin) -> std::string {
-            return util::parse_message(
+            return string_util::parse_message(
                 ev.message,
                 ev.server->command_char(),
                 plugin.name()
-            ).type == util::message_pack::type::command ? "onQueryCommand" : "onQuery";
+            ).type == string_util::message_pack::type::command ? "onQueryCommand" : "onQuery";
         },
         [=] (plugin& plugin) mutable {
             auto copy = ev;
-            auto pack = util::parse_message(copy.message, copy.server->command_char(), plugin.name());
+            auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name());
 
             copy.message = pack.message;
 
-            if (pack.type == util::message_pack::type::command)
+            if (pack.type == string_util::message_pack::type::command)
                 plugin.on_query_command(irccd_, copy);
             else
                 plugin.on_query(irccd_, copy);
@@ -794,7 +795,7 @@
     log::debug() << "  username: " << ev.whois.user << "\n";
     log::debug() << "  host: " << ev.whois.host << "\n";
     log::debug() << "  realname: " << ev.whois.realname << "\n";
-    log::debug() << "  channels: " << util::join(ev.whois.channels.begin(), ev.whois.channels.end()) << std::endl;
+    log::debug() << "  channels: " << string_util::join(ev.whois.channels.begin(), ev.whois.channels.end()) << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onWhois"           },
@@ -868,7 +869,7 @@
             auto server = ptr.lock();
 
             if (server) {
-                log::info(util::sprintf("server %s: removed", server->name()));
+                log::info(string_util::sprintf("server %s: removed", server->name()));
                 servers_.erase(std::find(servers_.begin(), servers_.end(), server));
             }
         });
@@ -894,7 +895,7 @@
     auto server = get(name);
 
     if (!server)
-        throw std::invalid_argument(util::sprintf("server %s not found", name));
+        throw std::invalid_argument(string_util::sprintf("server %s not found", name));
 
     return server;
 }
--- a/libirccdctl/irccd/client.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/libirccdctl/irccd/client.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -19,7 +19,8 @@
 #include <stdexcept>
 
 #include "client.hpp"
-#include "util.hpp"
+#include "net_util.hpp"
+#include "string_util.hpp"
 
 namespace irccd {
 
@@ -100,7 +101,7 @@
         std::string msg;
 
         do {
-            msg = util::next_network(cnx.m_input);
+            msg = net_util::next_network(cnx.m_input);
 
             if (!msg.empty())
                 parse(cnx, msg);
@@ -147,7 +148,7 @@
     {
         cnt.recv();
 
-        auto msg = util::next_network(cnt.m_input);
+        auto msg = net_util::next_network(cnt.m_input);
 
         if (msg.empty())
             return;
@@ -258,7 +259,7 @@
 
         // Ensure compatibility.
         if (info.major != IRCCD_VERSION_MAJOR || info.minor > IRCCD_VERSION_MINOR)
-            throw std::runtime_error(util::sprintf("server version too recent %d.%d.%d vs %d.%d.%d",
+            throw std::runtime_error(string_util::sprintf("server version too recent %d.%d.%d vs %d.%d.%d",
                 info.major, info.minor, info.patch,
                 IRCCD_VERSION_MAJOR, IRCCD_VERSION_MINOR, IRCCD_VERSION_PATCH
             ));
@@ -274,7 +275,7 @@
 
     void verify(Client &cnx) const
     {
-        auto msg = util::next_network(cnx.m_input);
+        auto msg = net_util::next_network(cnx.m_input);
 
         if (msg.empty())
             return;
--- a/tests/CMakeLists.txt	Tue Oct 31 10:48:06 2017 +0100
+++ b/tests/CMakeLists.txt	Fri Oct 27 21:45:32 2017 +0200
@@ -64,7 +64,7 @@
     add_subdirectory(util)
 
     # Services
-    add_subdirectory(service-plugin)
+    #add_subdirectory(service-plugin)
 
     # JS API
     if (HAVE_JS)
@@ -80,7 +80,7 @@
         add_subdirectory(js-util)
         add_subdirectory(plugin-ask)
         add_subdirectory(plugin-auth)
-        add_subdirectory(plugin-hangman)
+        #add_subdirectory(plugin-hangman)
         add_subdirectory(plugin-history)
         add_subdirectory(plugin-logger)
         add_subdirectory(plugin-plugin)
--- a/tests/js-timer/main.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/tests/js-timer/main.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -22,6 +22,7 @@
 
 #include <irccd/js_plugin_module.hpp>
 #include <irccd/js_timer_module.hpp>
+#include <irccd/net_util.hpp>
 
 #include <js_test.hpp>
 
@@ -41,7 +42,7 @@
     boost::timer::cpu_timer timer;
 
     while (timer.elapsed().wall / 1000000LL < 3000)
-        util::poller::poll(512, f.irccd_);
+        net_util::poll(512, f.irccd_);
 
     BOOST_TEST(duk_get_global_string(f.plugin_->context(), "count"));
     BOOST_TEST(duk_get_int(f.plugin_->context(), -1) == 1);
@@ -54,7 +55,7 @@
     boost::timer::cpu_timer timer;
 
     while (timer.elapsed().wall / 1000000LL < 3000)
-        util::poller::poll(512, f.irccd_);
+        net_util::poll(512, f.irccd_);
 
     BOOST_TEST(duk_get_global_string(f.plugin_->context(), "count"));
     BOOST_TEST(duk_get_int(f.plugin_->context(), -1) >= 5);
--- a/tests/js/main.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/tests/js/main.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -21,6 +21,7 @@
 
 #include <duktape.hpp>
 
+#include <irccd/fs_util.hpp>
 #include <irccd/util.hpp>
 
 namespace irccd {
@@ -57,7 +58,7 @@
         dukx_peval_file(ctx_, SOURCEDIR "/syntax-error.js");
     } catch (const Exception& ex) {
         BOOST_REQUIRE_EQUAL("SyntaxError", ex.name);
-        BOOST_REQUIRE_EQUAL("syntax-error.js", util::fs::base_name(ex.fileName));
+        BOOST_REQUIRE_EQUAL("syntax-error.js", fs_util::base_name(ex.fileName));
         BOOST_REQUIRE_EQUAL(6, ex.lineNumber);
         BOOST_REQUIRE_EQUAL("empty expression not allowed (line 6)", ex.message);
     }
--- a/tests/plugin-hangman/main.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/tests/plugin-hangman/main.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -24,6 +24,7 @@
 #include <irccd/irccd.hpp>
 #include <irccd/server.hpp>
 #include <irccd/service.hpp>
+#include <irccd/string_util.hpp>
 
 #include "plugin-tester.hpp"
 
@@ -46,7 +47,7 @@
 
     void message(std::string target, std::string message) override
     {
-        m_last = util::join({target, message});
+        m_last = string_util::join({target, message});
     }
 };
 
--- a/tests/plugin-plugin/main.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/tests/plugin-plugin/main.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -23,6 +23,7 @@
 #include <irccd/logger.hpp>
 #include <irccd/server.hpp>
 #include <irccd/service.hpp>
+#include <irccd/string_util.hpp>
 
 #include "plugin_test.hpp"
 
@@ -110,7 +111,7 @@
 BOOST_AUTO_TEST_CASE(format_too_long)
 {
     for (int i = 0; i < 100; ++i)
-        irccd_.plugins().add(std::make_shared<plugin>(util::sprintf("plugin-n-%d", i), ""));
+        irccd_.plugins().add(std::make_shared<plugin>(string_util::sprintf("plugin-n-%d", i), ""));
 
     plugin_->on_command(irccd_, {server_, "jean!jean@localhost", "#staff", "list"});
 
--- a/tests/util/main.cpp	Tue Oct 31 10:48:06 2017 +0100
+++ b/tests/util/main.cpp	Fri Oct 27 21:45:32 2017 +0200
@@ -22,6 +22,8 @@
 #include <cstdint>
 
 #include <irccd/util.hpp>
+#include <irccd/fs_util.hpp>
+#include <irccd/string_util.hpp>
 #include <irccd/system.hpp>
 
 namespace std {
@@ -41,98 +43,98 @@
 BOOST_AUTO_TEST_SUITE(format)
 
 /*
- * util::format function
+ * string_util::format function
  * --------------------------------------------------------
  */
 
 BOOST_AUTO_TEST_CASE(nothing)
 {
     std::string expected = "hello world!";
-    std::string result = util::format("hello world!");
+    std::string result = string_util::format("hello world!");
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
 
 BOOST_AUTO_TEST_CASE(escape)
 {
-    util::subst params;
+    string_util::subst params;
 
     params.keywords.emplace("target", "hello");
 
-    BOOST_REQUIRE_EQUAL("$@#", util::format("$@#"));
-    BOOST_REQUIRE_EQUAL(" $ @ # ", util::format(" $ @ # "));
-    BOOST_REQUIRE_EQUAL("#", util::format("#"));
-    BOOST_REQUIRE_EQUAL(" # ", util::format(" # "));
-    BOOST_REQUIRE_EQUAL("#@", util::format("#@"));
-    BOOST_REQUIRE_EQUAL("##", util::format("##"));
-    BOOST_REQUIRE_EQUAL("#!", util::format("#!"));
-    BOOST_REQUIRE_EQUAL("#{target}", util::format("##{target}"));
-    BOOST_REQUIRE_EQUAL("@hello", util::format("@#{target}", params));
-    BOOST_REQUIRE_EQUAL("hello#", util::format("#{target}#", params));
-    BOOST_REQUIRE_THROW(util::format("#{failure"), std::exception);
+    BOOST_REQUIRE_EQUAL("$@#", string_util::format("$@#"));
+    BOOST_REQUIRE_EQUAL(" $ @ # ", string_util::format(" $ @ # "));
+    BOOST_REQUIRE_EQUAL("#", string_util::format("#"));
+    BOOST_REQUIRE_EQUAL(" # ", string_util::format(" # "));
+    BOOST_REQUIRE_EQUAL("#@", string_util::format("#@"));
+    BOOST_REQUIRE_EQUAL("##", string_util::format("##"));
+    BOOST_REQUIRE_EQUAL("#!", string_util::format("#!"));
+    BOOST_REQUIRE_EQUAL("#{target}", string_util::format("##{target}"));
+    BOOST_REQUIRE_EQUAL("@hello", string_util::format("@#{target}", params));
+    BOOST_REQUIRE_EQUAL("hello#", string_util::format("#{target}#", params));
+    BOOST_REQUIRE_THROW(string_util::format("#{failure"), std::exception);
 }
 
 BOOST_AUTO_TEST_CASE(disable_date)
 {
-    util::subst params;
+    string_util::subst params;
 
-    params.flags &= ~(util::subst_flags::date);
+    params.flags &= ~(string_util::subst_flags::date);
 
-    BOOST_REQUIRE_EQUAL("%H:%M", util::format("%H:%M", params));
+    BOOST_REQUIRE_EQUAL("%H:%M", string_util::format("%H:%M", params));
 }
 
 BOOST_AUTO_TEST_CASE(disable_keywords)
 {
-    util::subst params;
+    string_util::subst params;
 
     params.keywords.emplace("target", "hello");
-    params.flags &= ~(util::subst_flags::keywords);
+    params.flags &= ~(string_util::subst_flags::keywords);
 
-    BOOST_REQUIRE_EQUAL("#{target}", util::format("#{target}", params));
+    BOOST_REQUIRE_EQUAL("#{target}", string_util::format("#{target}", params));
 }
 
 BOOST_AUTO_TEST_CASE(disable_env)
 {
-    util::subst params;
+    string_util::subst params;
 
-    params.flags &= ~(util::subst_flags::env);
+    params.flags &= ~(string_util::subst_flags::env);
 
-    BOOST_REQUIRE_EQUAL("${HOME}", util::format("${HOME}", params));
+    BOOST_REQUIRE_EQUAL("${HOME}", string_util::format("${HOME}", params));
 }
 
 BOOST_AUTO_TEST_CASE(keyword_simple)
 {
-    util::subst params;
+    string_util::subst params;
 
     params.keywords.insert({"target", "irccd"});
 
     std::string expected = "hello irccd!";
-    std::string result = util::format("hello #{target}!", params);
+    std::string result = string_util::format("hello #{target}!", params);
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
 
 BOOST_AUTO_TEST_CASE(keyword_multiple)
 {
-    util::subst params;
+    string_util::subst params;
 
     params.keywords.insert({"target", "irccd"});
     params.keywords.insert({"source", "nightmare"});
 
     std::string expected = "hello irccd from nightmare!";
-    std::string result = util::format("hello #{target} from #{source}!", params);
+    std::string result = string_util::format("hello #{target} from #{source}!", params);
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
 
 BOOST_AUTO_TEST_CASE(keyword_adj_twice)
 {
-    util::subst params;
+    string_util::subst params;
 
     params.keywords.insert({"target", "irccd"});
 
     std::string expected = "hello irccdirccd!";
-    std::string result = util::format("hello #{target}#{target}!", params);
+    std::string result = string_util::format("hello #{target}#{target}!", params);
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -140,7 +142,7 @@
 BOOST_AUTO_TEST_CASE(keyword_missing)
 {
     std::string expected = "hello !";
-    std::string result = util::format("hello #{target}!");
+    std::string result = string_util::format("hello #{target}!");
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -151,7 +153,7 @@
 
     if (!home.empty()) {
         std::string expected = "my home is " + home;
-        std::string result = util::format("my home is ${HOME}");
+        std::string result = string_util::format("my home is ${HOME}");
 
         BOOST_REQUIRE_EQUAL(expected, result);
     }
@@ -160,7 +162,7 @@
 BOOST_AUTO_TEST_CASE(env_missing)
 {
     std::string expected = "value is ";
-    std::string result = util::format("value is ${HOPE_THIS_VAR_NOT_EXIST}");
+    std::string result = string_util::format("value is ${HOPE_THIS_VAR_NOT_EXIST}");
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -168,7 +170,7 @@
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * util::split function
+ * string_util::split function
  * --------------------------------------------------------
  */
 
@@ -179,7 +181,7 @@
 BOOST_AUTO_TEST_CASE(simple)
 {
     list expected { "a", "b" };
-    list result = util::split("a;b", ";");
+    list result = string_util::split("a;b", ";");
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -187,7 +189,7 @@
 BOOST_AUTO_TEST_CASE(cut)
 {
     list expected { "msg", "#staff", "foo bar baz" };
-    list result = util::split("msg;#staff;foo bar baz", ";", 3);
+    list result = string_util::split("msg;#staff;foo bar baz", ";", 3);
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -195,7 +197,7 @@
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * util::strip function
+ * string_util::strip function
  * --------------------------------------------------------
  */
 
@@ -204,7 +206,7 @@
 BOOST_AUTO_TEST_CASE(left)
 {
     std::string value = "   123";
-    std::string result = util::strip(value);
+    std::string result = string_util::strip(value);
 
     BOOST_REQUIRE_EQUAL("123", result);
 }
@@ -212,7 +214,7 @@
 BOOST_AUTO_TEST_CASE(right)
 {
     std::string value = "123   ";
-    std::string result = util::strip(value);
+    std::string result = string_util::strip(value);
 
     BOOST_REQUIRE_EQUAL("123", result);
 }
@@ -220,7 +222,7 @@
 BOOST_AUTO_TEST_CASE(both)
 {
     std::string value = "   123   ";
-    std::string result = util::strip(value);
+    std::string result = string_util::strip(value);
 
     BOOST_REQUIRE_EQUAL("123", result);
 }
@@ -228,7 +230,7 @@
 BOOST_AUTO_TEST_CASE(none)
 {
     std::string value = "without";
-    std::string result = util::strip(value);
+    std::string result = string_util::strip(value);
 
     BOOST_REQUIRE_EQUAL("without", result);
 }
@@ -236,7 +238,7 @@
 BOOST_AUTO_TEST_CASE(betweenEmpty)
 {
     std::string value = "one list";
-    std::string result = util::strip(value);
+    std::string result = string_util::strip(value);
 
     BOOST_REQUIRE_EQUAL("one list", result);
 }
@@ -244,7 +246,7 @@
 BOOST_AUTO_TEST_CASE(betweenLeft)
 {
     std::string value = "  space at left";
-    std::string result = util::strip(value);
+    std::string result = string_util::strip(value);
 
     BOOST_REQUIRE_EQUAL("space at left", result);
 }
@@ -252,7 +254,7 @@
 BOOST_AUTO_TEST_CASE(betweenRight)
 {
     std::string value = "space at right  ";
-    std::string result = util::strip(value);
+    std::string result = string_util::strip(value);
 
     BOOST_REQUIRE_EQUAL("space at right", result);
 }
@@ -260,7 +262,7 @@
 BOOST_AUTO_TEST_CASE(betweenBoth)
 {
     std::string value = "  space at both  ";
-    std::string result = util::strip(value);
+    std::string result = string_util::strip(value);
 
     BOOST_REQUIRE_EQUAL("space at both", result);
 }
@@ -268,7 +270,7 @@
 BOOST_AUTO_TEST_CASE(empty)
 {
     std::string value = "    ";
-    std::string result = util::strip(value);
+    std::string result = string_util::strip(value);
 
     BOOST_REQUIRE_EQUAL("", result);
 }
@@ -276,7 +278,7 @@
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * util::join function
+ * string_util::join function
  * --------------------------------------------------------
  */
 
@@ -285,7 +287,7 @@
 BOOST_AUTO_TEST_CASE(empty)
 {
     std::string expected = "";
-    std::string result = util::join<int>({});
+    std::string result = string_util::join<int>({});
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -293,7 +295,7 @@
 BOOST_AUTO_TEST_CASE(one)
 {
     std::string expected = "1";
-    std::string result = util::join({1});
+    std::string result = string_util::join({1});
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -301,7 +303,7 @@
 BOOST_AUTO_TEST_CASE(two)
 {
     std::string expected = "1:2";
-    std::string result = util::join({1, 2});
+    std::string result = string_util::join({1, 2});
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -309,7 +311,7 @@
 BOOST_AUTO_TEST_CASE(delimiterString)
 {
     std::string expected = "1;;2;;3";
-    std::string result = util::join({1, 2, 3}, ";;");
+    std::string result = string_util::join({1, 2, 3}, ";;");
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -317,7 +319,7 @@
 BOOST_AUTO_TEST_CASE(delimiterChar)
 {
     std::string expected = "1@2@3@4";
-    std::string result = util::join({1, 2, 3, 4}, '@');
+    std::string result = string_util::join({1, 2, 3, 4}, '@');
 
     BOOST_REQUIRE_EQUAL(expected, result);
 }
@@ -325,7 +327,7 @@
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * util::is_identifier function
+ * string_util::is_identifier function
  * --------------------------------------------------------
  */
 
@@ -333,26 +335,26 @@
 
 BOOST_AUTO_TEST_CASE(correct)
 {
-    BOOST_REQUIRE(util::is_identifier("localhost"));
-    BOOST_REQUIRE(util::is_identifier("localhost2"));
-    BOOST_REQUIRE(util::is_identifier("localhost2-4_"));
+    BOOST_REQUIRE(string_util::is_identifier("localhost"));
+    BOOST_REQUIRE(string_util::is_identifier("localhost2"));
+    BOOST_REQUIRE(string_util::is_identifier("localhost2-4_"));
 }
 
 BOOST_AUTO_TEST_CASE(incorrect)
 {
-    BOOST_REQUIRE(!util::is_identifier(""));
-    BOOST_REQUIRE(!util::is_identifier("localhost with spaces"));
-    BOOST_REQUIRE(!util::is_identifier("localhost*"));
-    BOOST_REQUIRE(!util::is_identifier("&&"));
-    BOOST_REQUIRE(!util::is_identifier("@'"));
-    BOOST_REQUIRE(!util::is_identifier("##"));
-    BOOST_REQUIRE(!util::is_identifier("===++"));
+    BOOST_REQUIRE(!string_util::is_identifier(""));
+    BOOST_REQUIRE(!string_util::is_identifier("localhost with spaces"));
+    BOOST_REQUIRE(!string_util::is_identifier("localhost*"));
+    BOOST_REQUIRE(!string_util::is_identifier("&&"));
+    BOOST_REQUIRE(!string_util::is_identifier("@'"));
+    BOOST_REQUIRE(!string_util::is_identifier("##"));
+    BOOST_REQUIRE(!string_util::is_identifier("===++"));
 }
 
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * util::is_boolean function
+ * string_util::is_boolean function
  * --------------------------------------------------------
  */
 
@@ -361,39 +363,39 @@
 BOOST_AUTO_TEST_CASE(correct)
 {
     // true
-    BOOST_REQUIRE(util::is_boolean("true"));
-    BOOST_REQUIRE(util::is_boolean("True"));
-    BOOST_REQUIRE(util::is_boolean("TRUE"));
-    BOOST_REQUIRE(util::is_boolean("TruE"));
+    BOOST_REQUIRE(string_util::is_boolean("true"));
+    BOOST_REQUIRE(string_util::is_boolean("True"));
+    BOOST_REQUIRE(string_util::is_boolean("TRUE"));
+    BOOST_REQUIRE(string_util::is_boolean("TruE"));
 
     // yes
-    BOOST_REQUIRE(util::is_boolean("yes"));
-    BOOST_REQUIRE(util::is_boolean("Yes"));
-    BOOST_REQUIRE(util::is_boolean("YES"));
-    BOOST_REQUIRE(util::is_boolean("YeS"));
+    BOOST_REQUIRE(string_util::is_boolean("yes"));
+    BOOST_REQUIRE(string_util::is_boolean("Yes"));
+    BOOST_REQUIRE(string_util::is_boolean("YES"));
+    BOOST_REQUIRE(string_util::is_boolean("YeS"));
 
     // on
-    BOOST_REQUIRE(util::is_boolean("on"));
-    BOOST_REQUIRE(util::is_boolean("On"));
-    BOOST_REQUIRE(util::is_boolean("oN"));
-    BOOST_REQUIRE(util::is_boolean("ON"));
+    BOOST_REQUIRE(string_util::is_boolean("on"));
+    BOOST_REQUIRE(string_util::is_boolean("On"));
+    BOOST_REQUIRE(string_util::is_boolean("oN"));
+    BOOST_REQUIRE(string_util::is_boolean("ON"));
 
     // 1
-    BOOST_REQUIRE(util::is_boolean("1"));
+    BOOST_REQUIRE(string_util::is_boolean("1"));
 }
 
 BOOST_AUTO_TEST_CASE(incorrect)
 {
-    BOOST_REQUIRE(!util::is_boolean("false"));
-    BOOST_REQUIRE(!util::is_boolean("lol"));
-    BOOST_REQUIRE(!util::is_boolean(""));
-    BOOST_REQUIRE(!util::is_boolean("0"));
+    BOOST_REQUIRE(!string_util::is_boolean("false"));
+    BOOST_REQUIRE(!string_util::is_boolean("lol"));
+    BOOST_REQUIRE(!string_util::is_boolean(""));
+    BOOST_REQUIRE(!string_util::is_boolean("0"));
 }
 
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * util::is_number function
+ * string_util::is_number function
  * --------------------------------------------------------
  */
 
@@ -401,21 +403,21 @@
 
 BOOST_AUTO_TEST_CASE(correct)
 {
-    BOOST_REQUIRE(util::is_number("123"));
-    BOOST_REQUIRE(util::is_number("-123"));
-    BOOST_REQUIRE(util::is_number("123.67"));
+    BOOST_REQUIRE(string_util::is_number("123"));
+    BOOST_REQUIRE(string_util::is_number("-123"));
+    BOOST_REQUIRE(string_util::is_number("123.67"));
 }
 
 BOOST_AUTO_TEST_CASE(incorrect)
 {
-    BOOST_REQUIRE(!util::is_number("lol"));
-    BOOST_REQUIRE(!util::is_number("this is not a number"));
+    BOOST_REQUIRE(!string_util::is_number("lol"));
+    BOOST_REQUIRE(!string_util::is_number("this is not a number"));
 }
 
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * util::to_number function
+ * string_util::to_number function
  * ------------------------------------------------------------------
  */
 
@@ -424,44 +426,44 @@
 BOOST_AUTO_TEST_CASE(correct)
 {
     /* unsigned */
-    BOOST_REQUIRE_EQUAL(50u, util::to_number<std::uint8_t>("50"));
-    BOOST_REQUIRE_EQUAL(5000u, util::to_number<std::uint16_t>("5000"));
-    BOOST_REQUIRE_EQUAL(50000u, util::to_number<std::uint32_t>("50000"));
-    BOOST_REQUIRE_EQUAL(500000u, util::to_number<std::uint64_t>("500000"));
+    BOOST_REQUIRE_EQUAL(50u, string_util::to_number<std::uint8_t>("50"));
+    BOOST_REQUIRE_EQUAL(5000u, string_util::to_number<std::uint16_t>("5000"));
+    BOOST_REQUIRE_EQUAL(50000u, string_util::to_number<std::uint32_t>("50000"));
+    BOOST_REQUIRE_EQUAL(500000u, string_util::to_number<std::uint64_t>("500000"));
 
     /* signed */
-    BOOST_REQUIRE_EQUAL(-50, util::to_number<std::int8_t>("-50"));
-    BOOST_REQUIRE_EQUAL(-500, util::to_number<std::int16_t>("-500"));
-    BOOST_REQUIRE_EQUAL(-5000, util::to_number<std::int32_t>("-5000"));
-    BOOST_REQUIRE_EQUAL(-50000, util::to_number<std::int64_t>("-50000"));
+    BOOST_REQUIRE_EQUAL(-50, string_util::to_number<std::int8_t>("-50"));
+    BOOST_REQUIRE_EQUAL(-500, string_util::to_number<std::int16_t>("-500"));
+    BOOST_REQUIRE_EQUAL(-5000, string_util::to_number<std::int32_t>("-5000"));
+    BOOST_REQUIRE_EQUAL(-50000, string_util::to_number<std::int64_t>("-50000"));
 }
 
 BOOST_AUTO_TEST_CASE(incorrect)
 {
     /* unsigned */
-    BOOST_REQUIRE_THROW(util::to_number<std::uint8_t>("300"), std::out_of_range);
-    BOOST_REQUIRE_THROW(util::to_number<std::uint16_t>("80000"), std::out_of_range);
-    BOOST_REQUIRE_THROW(util::to_number<std::uint8_t>("-125"), std::out_of_range);
-    BOOST_REQUIRE_THROW(util::to_number<std::uint16_t>("-25000"), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::uint8_t>("300"), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::uint16_t>("80000"), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::uint8_t>("-125"), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::uint16_t>("-25000"), std::out_of_range);
 
     /* signed */
-    BOOST_REQUIRE_THROW(util::to_number<std::int8_t>("300"), std::out_of_range);
-    BOOST_REQUIRE_THROW(util::to_number<std::int16_t>("80000"), std::out_of_range);
-    BOOST_REQUIRE_THROW(util::to_number<std::int8_t>("-300"), std::out_of_range);
-    BOOST_REQUIRE_THROW(util::to_number<std::int16_t>("-80000"), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::int8_t>("300"), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::int16_t>("80000"), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::int8_t>("-300"), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::int16_t>("-80000"), std::out_of_range);
 
     /* not numbers */
-    BOOST_REQUIRE_THROW(util::to_number<std::uint8_t>("nonono"), std::invalid_argument);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::uint8_t>("nonono"), std::invalid_argument);
 
     /* custom ranges */
-    BOOST_REQUIRE_THROW(util::to_number<std::uint8_t>("50", 0, 10), std::out_of_range);
-    BOOST_REQUIRE_THROW(util::to_number<std::int8_t>("-50", -10, 10), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::uint8_t>("50", 0, 10), std::out_of_range);
+    BOOST_REQUIRE_THROW(string_util::to_number<std::int8_t>("-50", -10, 10), std::out_of_range);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * util::fs::find function (name)
+ * fs_util::find function (name)
  * ------------------------------------------------------------------
  */
 
@@ -469,8 +471,8 @@
 
 BOOST_AUTO_TEST_CASE(not_recursive)
 {
-    auto file1 = util::fs::find(TESTS_BINARY_DIR "/root", "file-1.txt", false);
-    auto file2 = util::fs::find(TESTS_BINARY_DIR "/root", "file-2.txt", false);
+    auto file1 = fs_util::find(TESTS_BINARY_DIR "/root", "file-1.txt", false);
+    auto file2 = fs_util::find(TESTS_BINARY_DIR "/root", "file-2.txt", false);
 
     BOOST_TEST(file1.find("file-1.txt") != std::string::npos);
     BOOST_TEST(file2.empty());
@@ -478,8 +480,8 @@
 
 BOOST_AUTO_TEST_CASE(recursive)
 {
-    auto file1 = util::fs::find(TESTS_BINARY_DIR "/root", "file-1.txt", true);
-    auto file2 = util::fs::find(TESTS_BINARY_DIR "/root", "file-2.txt", true);
+    auto file1 = fs_util::find(TESTS_BINARY_DIR "/root", "file-1.txt", true);
+    auto file2 = fs_util::find(TESTS_BINARY_DIR "/root", "file-2.txt", true);
 
     BOOST_TEST(file1.find("file-1.txt") != std::string::npos);
     BOOST_TEST(file2.find("file-2.txt") != std::string::npos);
@@ -488,7 +490,7 @@
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * util::fs::find function (regex)
+ * fs_util::find function (regex)
  * ------------------------------------------------------------------
  */
 
@@ -498,7 +500,7 @@
 {
     const std::regex regex("file-[12]\\.txt");
 
-    auto file = util::fs::find(TESTS_BINARY_DIR "/root", regex, false);
+    auto file = fs_util::find(TESTS_BINARY_DIR "/root", regex, false);
 
     BOOST_TEST(file.find("file-1.txt") != std::string::npos);
 }
@@ -507,7 +509,7 @@
 {
     const std::regex regex("file-[12]\\.txt");
 
-    auto file = util::fs::find(TESTS_BINARY_DIR "/root/level-a", regex, true);
+    auto file = fs_util::find(TESTS_BINARY_DIR "/root/level-a", regex, true);
 
     BOOST_TEST(file.find("file-2.txt") != std::string::npos);
 }