changeset 711:fc66cc9706a7

CMake: export pkg-config files, closes #874 @1h While here rename libcommon to libirccd-core to avoid collision when installed. For consistency, rename libirccdctl to libirccd-ctl.
author David Demelier <markand@malikania.fr>
date Sat, 07 Jul 2018 15:40:46 +0200
parents a17de53db29b
children e0cd4f544b24
files CMakeLists.txt cmake/export/CMakeLists.txt cmake/export/libirccd-core.pc cmake/export/libirccd-ctl.pc cmake/export/libirccd-js.pc cmake/export/libirccd-test.pc cmake/export/libirccd.pc irccdctl/CMakeLists.txt libcommon/CMakeLists.txt libcommon/irccd/acceptor.hpp libcommon/irccd/config.cpp libcommon/irccd/config.hpp libcommon/irccd/connector.hpp libcommon/irccd/fs_util.hpp libcommon/irccd/ini.cpp libcommon/irccd/ini.hpp libcommon/irccd/ini_util.hpp libcommon/irccd/json_util.hpp libcommon/irccd/options.cpp libcommon/irccd/options.hpp libcommon/irccd/socket_acceptor.hpp libcommon/irccd/socket_connector.hpp libcommon/irccd/socket_stream.hpp libcommon/irccd/stream.hpp libcommon/irccd/string_util.cpp libcommon/irccd/string_util.hpp libcommon/irccd/system.cpp libcommon/irccd/system.hpp libcommon/irccd/tls_acceptor.hpp libcommon/irccd/tls_connector.hpp libcommon/irccd/tls_stream.hpp libcommon/irccd/xdg.hpp libirccd-core/CMakeLists.txt libirccd-core/irccd/acceptor.hpp libirccd-core/irccd/config.cpp libirccd-core/irccd/config.hpp libirccd-core/irccd/connector.hpp libirccd-core/irccd/fs_util.hpp libirccd-core/irccd/ini.cpp libirccd-core/irccd/ini.hpp libirccd-core/irccd/ini_util.hpp libirccd-core/irccd/json_util.hpp libirccd-core/irccd/options.cpp libirccd-core/irccd/options.hpp libirccd-core/irccd/socket_acceptor.hpp libirccd-core/irccd/socket_connector.hpp libirccd-core/irccd/socket_stream.hpp libirccd-core/irccd/stream.hpp libirccd-core/irccd/string_util.cpp libirccd-core/irccd/string_util.hpp libirccd-core/irccd/system.cpp libirccd-core/irccd/system.hpp libirccd-core/irccd/tls_acceptor.hpp libirccd-core/irccd/tls_connector.hpp libirccd-core/irccd/tls_stream.hpp libirccd-core/irccd/xdg.hpp libirccd-ctl/CMakeLists.txt libirccd-ctl/irccd/ctl/controller.cpp libirccd-ctl/irccd/ctl/controller.hpp libirccd-test/CMakeLists.txt libirccd/CMakeLists.txt libirccdctl/CMakeLists.txt libirccdctl/irccd/ctl/controller.cpp libirccdctl/irccd/ctl/controller.hpp tests/src/libirccd/command-plugin-config/CMakeLists.txt tests/src/libirccd/command-plugin-info/CMakeLists.txt tests/src/libirccd/command-plugin-list/CMakeLists.txt tests/src/libirccd/command-plugin-load/CMakeLists.txt tests/src/libirccd/command-plugin-reload/CMakeLists.txt tests/src/libirccd/command-plugin-unload/CMakeLists.txt tests/src/libirccd/command-rule-add/CMakeLists.txt tests/src/libirccd/command-rule-edit/CMakeLists.txt tests/src/libirccd/command-rule-info/CMakeLists.txt tests/src/libirccd/command-rule-list/CMakeLists.txt tests/src/libirccd/command-rule-move/CMakeLists.txt tests/src/libirccd/command-rule-remove/CMakeLists.txt tests/src/libirccd/command-server-connect/CMakeLists.txt tests/src/libirccd/command-server-disconnect/CMakeLists.txt tests/src/libirccd/command-server-info/CMakeLists.txt tests/src/libirccd/command-server-invite/CMakeLists.txt tests/src/libirccd/command-server-join/CMakeLists.txt tests/src/libirccd/command-server-kick/CMakeLists.txt tests/src/libirccd/command-server-list/CMakeLists.txt tests/src/libirccd/command-server-me/CMakeLists.txt tests/src/libirccd/command-server-message/CMakeLists.txt tests/src/libirccd/command-server-mode/CMakeLists.txt tests/src/libirccd/command-server-nick/CMakeLists.txt tests/src/libirccd/command-server-notice/CMakeLists.txt tests/src/libirccd/command-server-part/CMakeLists.txt tests/src/libirccd/command-server-reconnect/CMakeLists.txt tests/src/libirccd/command-server-topic/CMakeLists.txt
diffstat 91 files changed, 5460 insertions(+), 5381 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -28,8 +28,8 @@
 # cmake/IrccdSystem.cmake   - Contains some platforms checks and compile flags.
 # cmake/IrccdVersion.cmake  - Defines the Irccd version and its plugins.
 # cmake/check               - Platform checks in separate files.
+# cmake/export              - CMake and pkg-config exports.
 # cmake/function            - Custom CMake functions.
-# cmake/installer           - Some files for the QtIFW installer.
 # cmake/internal            - Some internal files (e.g. the sysconfig.h)
 # cmake/packages            - Additional find_package modules.
 #
@@ -37,7 +37,8 @@
 #
 # doc                       - The documentation process.
 # extern                    - External libraries.
-# libirccdctl               - The irccdctl library.
+# libirccd-core             - Common code.
+# libirccd-ctl              - The irccdctl library.
 # libirccd-js               - Javascript bindings library.
 # libirccd-test             - Helpers for unit tests.
 # libirccd                  - The irccd library.
@@ -86,9 +87,9 @@
 
 add_subdirectory(extern/json)
 add_subdirectory(doc)
-add_subdirectory(libcommon)
+add_subdirectory(libirccd-core)
 add_subdirectory(libirccd)
-add_subdirectory(libirccdctl)
+add_subdirectory(libirccd-ctl)
 add_subdirectory(libirccd-test)
 
 if (IRCCD_HAVE_JS)
--- a/cmake/export/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/cmake/export/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -18,6 +18,7 @@
 
 include(CMakePackageConfigHelpers)
 
+# CMake export files.
 write_basic_package_version_file(
     ${CMAKE_CURRENT_BINARY_DIR}/irccd-config-version.cmake
     VERSION ${IRCCD_VERSION}
@@ -38,3 +39,26 @@
     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/irccd
     COMPONENT Devel
 )
+
+# pkg-config files.
+if (IRCCD_WITH_PKGCONFIG)
+    # Since javascript is optional, add it only if required.
+    if (IRCCD_HAVE_JS)
+        set(IRCCD_EXTRA_REQUIRES libirccd-js)
+    endif ()
+    if (IRCCD_HAVE_SSL)
+        set(IRCCD_EXTRA_LIBS "-lssl -lcrypto")
+    endif ()
+
+    foreach (pkg libirccd-core libirccd libirccd-ctl libirccd-js libirccd-test)
+        configure_file(
+            ${CMAKE_CURRENT_SOURCE_DIR}/${pkg}.pc
+            ${CMAKE_CURRENT_BINARY_DIR}/${pkg}.pc
+            @ONLY
+        )
+        install(
+            FILES ${CMAKE_CURRENT_BINARY_DIR}/${pkg}.pc
+            DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
+        )
+    endforeach ()
+endif ()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/export/libirccd-core.pc	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,10 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+
+Name: libirccd-core
+Description: irccd (core library)
+Version: @IRCCD_VERSION@
+Cflags: -I${includedir} -I${includedir}/irccd/extern
+Libs: -L${libdir} -lirccd-core -lboost_system -lboost_filesystem -pthread @IRCCD_EXTRA_LIBS@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/export/libirccd-ctl.pc	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+
+Name: libirccd-ctl
+Description: irccd (controller library)
+Version: @IRCCD_VERSION@
+Cflags: -I${includedir} -I${includedir}/irccd/extern
+Libs: -L${libdir} -lirccd-ctl
+Requires: libirccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/export/libirccd-js.pc	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+
+Name: libirccd-js
+Description: irccd (javascript bindings library)
+Version: @IRCCD_VERSION@
+Cflags: -I${includedir}
+Libs: -L${libdir} -lirccd-js
+Requires: libirccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/export/libirccd-test.pc	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+
+Name: libirccd-test
+Description: irccd (javascript bindings library)
+Version: @IRCCD_VERSION@
+Cflags: -I${includedir}
+Libs: -L${libdir} -lirccd-test
+Requires: libirccd @IRCCD_EXTRA_REQUIRES@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/export/libirccd.pc	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+
+Name: libirccd
+Description: irccd (daemon library)
+Version: @IRCCD_VERSION@
+Cflags: -I${includedir} -I${includedir}/irccd/extern
+Libs: -L${libdir} -lirccd
+Requires: libirccd-core
--- a/irccdctl/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/irccdctl/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -91,5 +91,5 @@
     EXPORT
     DESCRIPTION "Irccd controller."
     SOURCES ${SOURCES}
-    LIBRARIES libirccdctl
+    LIBRARIES libirccd-ctl
 )
--- a/libcommon/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-project(libcommon)
-
-find_package(Boost 1.60 REQUIRED QUIET COMPONENTS filesystem system)
-
-set(
-    HEADERS
-    ${libcommon_SOURCE_DIR}/irccd/acceptor.hpp
-    ${libcommon_SOURCE_DIR}/irccd/config.hpp
-    ${libcommon_SOURCE_DIR}/irccd/connector.hpp
-    ${libcommon_SOURCE_DIR}/irccd/fs_util.hpp
-    ${libcommon_SOURCE_DIR}/irccd/ini.hpp
-    ${libcommon_SOURCE_DIR}/irccd/ini_util.hpp
-    ${libcommon_SOURCE_DIR}/irccd/json_util.hpp
-    ${libcommon_SOURCE_DIR}/irccd/options.hpp
-    ${libcommon_SOURCE_DIR}/irccd/socket_acceptor.hpp
-    ${libcommon_SOURCE_DIR}/irccd/socket_connector.hpp
-    ${libcommon_SOURCE_DIR}/irccd/socket_stream.hpp
-    ${libcommon_SOURCE_DIR}/irccd/stream.hpp
-    ${libcommon_SOURCE_DIR}/irccd/string_util.hpp
-    ${libcommon_SOURCE_DIR}/irccd/system.hpp
-    ${libcommon_SOURCE_DIR}/irccd/tls_acceptor.hpp
-    ${libcommon_SOURCE_DIR}/irccd/tls_connector.hpp
-    ${libcommon_SOURCE_DIR}/irccd/tls_stream.hpp
-    ${libcommon_SOURCE_DIR}/irccd/xdg.hpp
-)
-
-set(
-    SOURCES
-    ${libcommon_SOURCE_DIR}/irccd/config.cpp
-    ${libcommon_SOURCE_DIR}/irccd/ini.cpp
-    ${libcommon_SOURCE_DIR}/irccd/options.cpp
-    ${libcommon_SOURCE_DIR}/irccd/string_util.cpp
-    ${libcommon_SOURCE_DIR}/irccd/system.cpp
-)
-
-irccd_define_library(
-    TARGET libcommon
-    EXPORT
-    HEADERS ${HEADERS}
-    HEADERS_DIRECTORY irccd
-    SOURCES
-        ${libcommon_SOURCE_DIR}/CMakeLists.txt
-        ${SOURCES}
-    LIBRARIES
-        libjson
-        Threads::Threads
-        Boost::filesystem
-        Boost::system
-        $<$<BOOL:${IRCCD_HAVE_SSL}>:OpenSSL::Crypto>
-        $<$<BOOL:${IRCCD_HAVE_SSL}>:OpenSSL::SSL>
-        $<$<BOOL:${APPLE}>:resolv>
-        $<$<BOOL:${WIN32}>:mswsock>
-        $<$<BOOL:${WIN32}>:shlwapi>
-        $<$<BOOL:${WIN32}>:ws2_32>
-        $<$<STREQUAL:${CMAKE_SYSTEM_NAME},Linux>:dl>
-    PUBLIC_INCLUDES
-        $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
-        $<BUILD_INTERFACE:${libcommon_SOURCE_DIR}>
-        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
-        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern>
-)
--- a/libcommon/irccd/acceptor.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/*
- * acceptor.hpp -- abstract stream acceptor interface
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_ACCEPTOR_HPP
-#define IRCCD_COMMON_ACCEPTOR_HPP
-
-/**
- * \file acceptor.hpp
- * \brief Abstract stream acceptor interface.
- */
-
-#include <functional>
-#include <memory>
-#include <system_error>
-
-namespace irccd {
-
-namespace io {
-
-class stream;
-
-/**
- * \brief Accept completion handler.
- */
-using accept_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>;
-
-/**
- * \brief Abstract stream acceptor interface.
- *
- * This class is used to wait a new client in an asynchronous manner. Derived
- * classes must implement a non-blocking accept function.
- */
-class acceptor {
-public:
-    /**
-     * Default constructor.
-     */
-    acceptor() = default;
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~acceptor() = default;
-
-    /**
-     * Start asynchronous accept.
-     *
-     * Once the client is accepted, the original acceptor must be kept until it
-     * is destroyed.
-     *
-     * \pre another accept operation must not be running
-     * \pre handler != nullptr
-     * \param handler the handler
-     */
-    virtual void accept(accept_handler handler) = 0;
-};
-
-} // !io
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_ACCEPTOR_HPP
--- a/libcommon/irccd/config.cpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-/*
- * config.cpp -- irccd configuration loader
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <boost/filesystem.hpp>
-
-#include <irccd/system.hpp>
-
-#include "config.hpp"
-
-namespace irccd {
-
-boost::optional<config> config::search(const std::string& name)
-{
-    for (const auto& path : sys::config_filenames(name)) {
-        boost::system::error_code ec;
-
-        if (boost::filesystem::exists(path, ec) && !ec)
-            return config(path);
-    }
-
-    return boost::optional<config>();
-}
-
-} // !irccd
--- a/libcommon/irccd/config.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * config.hpp -- irccd configuration loader
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_CONFIG_HPP
-#define IRCCD_COMMON_CONFIG_HPP
-
-/**
- * \file config.hpp
- * \brief Read .ini configuration file for irccd
- */
-
-#include <boost/optional.hpp>
-
-#include "ini.hpp"
-
-namespace irccd {
-
-/**
- * \brief Read .ini configuration file for irccd
- */
-class config : public ini::document {
-private:
-    std::string path_;
-
-public:
-    /**
-     * Search the configuration file into the standard defined paths.
-     *
-     * \param name the file name
-     * \return the config or empty if not found
-     */
-    static boost::optional<config> search(const std::string& name);
-
-    /**
-     * Load the configuration from the specified path.
-     *
-     * \param path the path
-     */
-    inline config(std::string path = "")
-        : document(path.empty() ? ini::document() : ini::read_file(path))
-        , path_(std::move(path))
-    {
-    }
-
-    /**
-     * Get the path to the configuration file.
-     *
-     * \return the path
-     */
-    inline const std::string& get_path() const noexcept
-    {
-        return path_;
-    }
-};
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_CONFIG_HPP
--- a/libcommon/irccd/connector.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/*
- * connector.hpp -- abstract connection interface
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_CONNECTOR_HPP
-#define IRCCD_COMMON_CONNECTOR_HPP
-
-/**
- * \file connector.hpp
- * \brief Abstract connection interface.
- */
-
-#include <functional>
-#include <memory>
-#include <system_error>
-
-namespace irccd {
-
-namespace io {
-
-class stream;
-
-/**
- * \brief Connect completion handler.
- */
-using connect_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>;
-
-/**
- * \brief Abstract connection interface.
- *
- * This class is used to connect to a stream end point (usually sockets) in an
- * asynchronous manner.
- *
- * Derived class must implement non-blocking connect function.
- */
-class connector {
-public:
-    /**
-     * Default constructor.
-     */
-    connector() = default;
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~connector() = default;
-
-    /**
-     * Start asynchronous connect.
-     *
-     * Once the client is connected, the original acceptor must be kept until it
-     * is destroyed.
-     *
-     * \pre another connect operation must not be running
-     * \pre handler != nullptr
-     * \param handler the handler
-     */
-    virtual void connect(connect_handler handler) = 0;
-};
-
-} // !io
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_CONNECTOR_HPP
--- a/libcommon/irccd/fs_util.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/*
- * fs_util.hpp -- filesystem utilities
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_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 {
-
-/**
- * \brief Filesystem utilities.
- */
-namespace fs_util {
-
-// {{{ base_name
-
-/**
- * Get the base name from a path.
- *
- * Example, base_name("/etc/foo.conf") returns 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();
-}
-
-// }}}
-
-// {{{ dir_name
-
-/**
- * Get the parent directory from a path.
- *
- * Example, dir_name("/etc/foo.conf") returns /etc
- *
- * \param path the path
- * \return the parent directory
- */
-inline std::string dir_name(const std::string& path)
-{
-    return boost::filesystem::path(path).parent_path().string();
-}
-
-// }}}
-
-// {{{ find_if
-
-/**
- * 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 boost::system::system_error on errors
- */
-template <typename Predicate>
-std::string find_if(const std::string& base, bool recursive, Predicate&& predicate)
-{
-    const auto find = [&] (auto it) -> std::string {
-        for (const auto& entry : it)
-            if (predicate(entry))
-                return entry.path().string();
-
-        return "";
-    };
-
-    return recursive
-        ? find(boost::filesystem::recursive_directory_iterator(base))
-        : find(boost::filesystem::directory_iterator(base));
-}
-
-// }}}
-
-// {{{ find
-
-/**
- * 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 boost::system::system_error on 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 boost::system::system_error on 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
--- a/libcommon/irccd/ini.cpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,418 +0,0 @@
-/*
- * ini.cpp -- extended .ini file parser
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cctype>
-#include <cstring>
-#include <iostream>
-#include <iterator>
-#include <fstream>
-#include <sstream>
-#include <stdexcept>
-
-#include <irccd/sysconfig.hpp>
-
-#include <boost/predef.h>
-
-// for PathIsRelative.
-#if BOOST_OS_WINDOWS
-#  include <shlwapi.h>
-#endif
-
-#include "ini.hpp"
-
-namespace irccd {
-
-namespace ini {
-
-namespace {
-
-using stream_iterator = std::istreambuf_iterator<char>;
-using token_iterator = std::vector<token>::const_iterator;
-
-inline bool is_absolute(const std::string& path) noexcept
-{
-#if BOOST_OS_WINDOWS
-    return !PathIsRelative(path.c_str());
-#else
-    return path.size() > 0 && path[0] == '/';
-#endif
-}
-
-inline bool is_quote(char c) noexcept
-{
-    return c == '\'' || c == '"';
-}
-
-inline bool is_space(char c) noexcept
-{
-    // Custom version because std::isspace includes \n as space.
-    return c == ' ' || c == '\t';
-}
-
-inline bool is_list(char c) noexcept
-{
-    return c == '(' || c == ')' || c == ',';
-}
-
-inline bool is_reserved(char c) noexcept
-{
-    return is_list(c) || is_quote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '=';
-}
-
-void analyse_line(int& line, int& column, stream_iterator& it) noexcept
-{
-    assert(*it == '\n');
-
-    ++ line;
-    ++ it;
-    column = 0;
-}
-
-void analyse_comment(int& column, stream_iterator& it, stream_iterator end) noexcept
-{
-    assert(*it == '#');
-
-    while (it != end && *it != '\n') {
-        ++ column;
-        ++ it;
-    }
-}
-
-void analyse_spaces(int& column, stream_iterator& it, stream_iterator end) noexcept
-{
-    assert(is_space(*it));
-
-    while (it != end && is_space(*it)) {
-        ++ column;
-        ++ it;
-    }
-}
-
-void analyse_list(tokens& list, int line, int& column, stream_iterator& it) noexcept
-{
-    assert(is_list(*it));
-
-    switch (*it++) {
-    case '(':
-        list.emplace_back(token::list_begin, line, column++);
-        break;
-    case ')':
-        list.emplace_back(token::list_end, line, column++);
-        break;
-    case ',':
-        list.emplace_back(token::comma, line, column++);
-        break;
-    default:
-        break;
-    }
-}
-
-void analyse_section(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end)
-{
-    assert(*it == '[');
-
-    std::string value;
-    int save = column;
-
-    // Read section name.
-    ++ it;
-    while (it != end && *it != ']') {
-        if (*it == '\n')
-            throw exception(line, column, "section not terminated, missing ']'");
-        if (is_reserved(*it))
-            throw exception(line, column, "section name expected after '[', got '" + std::string(1, *it) + "'");
-
-        ++ column;
-        value += *it++;
-    }
-
-    if (it == end)
-        throw exception(line, column, "section name expected after '[', got <EOF>");
-    if (value.empty())
-        throw exception(line, column, "empty section name");
-
-    // Remove ']'.
-    ++ it;
-
-    list.emplace_back(token::section, line, save, std::move(value));
-}
-
-void analyse_assign(tokens& list, int& line, int& column, stream_iterator& it)
-{
-    assert(*it == '=');
-
-    list.push_back({ token::assign, line, column++ });
-    ++ it;
-}
-
-void analyse_quoted_word(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end)
-{
-    std::string value;
-    int save = column;
-    char quote = *it++;
-
-    while (it != end && *it != quote) {
-        // TODO: escape sequence
-        ++ column;
-        value += *it++;
-    }
-
-    if (it == end)
-        throw exception(line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>");
-
-    // Remove quote.
-    ++ it;
-
-    list.push_back({ token::quoted_word, line, save, std::move(value) });
-}
-
-void analyse_word(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end)
-{
-    assert(!is_reserved(*it));
-
-    std::string value;
-    int save = column;
-
-    while (it != end && !std::isspace(*it) && !is_reserved(*it)) {
-        ++ column;
-        value += *it++;
-    }
-
-    list.push_back({ token::word, line, save, std::move(value) });
-}
-
-void analyse_include(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end)
-{
-    assert(*it == '@');
-
-    std::string include;
-    int save = column;
-
-    // Read include.
-    ++ it;
-    while (it != end && !is_space(*it)) {
-        ++ column;
-        include += *it++;
-    }
-
-    if (include != "include")
-        throw exception(line, column, "expected include after '@' token");
-
-    list.push_back({ token::include, line, save });
-}
-
-void parse_option_value_simple(option& option, token_iterator& it)
-{
-    assert(it->type() == token::word || it->type() == token::quoted_word);
-
-    option.push_back((it++)->value());
-}
-
-void parse_option_value_list(option& option, token_iterator& it, token_iterator end)
-{
-    assert(it->type() == token::list_begin);
-
-    token_iterator save = it++;
-
-    while (it != end && it->type() != token::list_end) {
-        switch (it->type()) {
-        case token::comma:
-            // Previous must be a word.
-            if (it[-1].type() != token::word && it[-1].type() != token::quoted_word)
-                throw exception(it->line(), it->column(), "unexpected comma after '" + it[-1].value() + "'");
-
-            ++ it;
-            break;
-        case token::word:
-        case token::quoted_word:
-            option.push_back((it++)->value());
-            break;
-        default:
-            throw exception(it->line(), it->column(), "unexpected '" + it[-1].value() + "' in list construct");
-            break;
-        }
-    }
-
-    if (it == end)
-        throw exception(save->line(), save->column(), "unterminated list construct");
-
-    // Remove ).
-    ++ it;
-}
-
-void parse_option(section& sc, token_iterator& it, token_iterator end)
-{
-    option option(it->value());
-    token_iterator save(it);
-
-    // No '=' or something else?
-    if (++it == end)
-        throw exception(save->line(), save->column(), "expected '=' assignment, got <EOF>");
-    if (it->type() != token::assign)
-        throw exception(it->line(), it->column(), "expected '=' assignment, got " + it->value());
-
-    // Empty options are allowed so just test for words.
-    if (++it != end) {
-        if (it->type() == token::word || it->type() == token::quoted_word)
-            parse_option_value_simple(option, it);
-        else if (it->type() == token::list_begin)
-            parse_option_value_list(option, it, end);
-    }
-
-    sc.push_back(std::move(option));
-}
-
-void parse_include(document& doc, const std::string& path, token_iterator& it, token_iterator end)
-{
-    token_iterator save(it);
-
-    if (++it == end)
-        throw exception(save->line(), save->column(), "expected file name after '@include' statement, got <EOF>");
-    if (it->type() != token::word && it->type() != token::quoted_word)
-        throw exception(it->line(), it->column(), "expected file name after '@include' statement, got " + it->value());
-
-    std::string value = (it++)->value();
-    std::string file;
-
-    if (!is_absolute(value)) {
-#if BOOST_OS_WINDOWS
-        file = path + "\\" + value;
-#else
-        file = path + "/" + value;
-#endif
-    } else
-        file = value;
-
-    for (const auto& sc : read_file(file))
-        doc.push_back(sc);
-}
-
-void parse_section(document& doc, token_iterator& it, token_iterator end)
-{
-    section sc(it->value());
-
-    // Skip [section].
-    ++ it;
-
-    // Read until next section.
-    while (it != end && it->type() != token::section) {
-        if (it->type() != token::word)
-            throw exception(it->line(), it->column(), "unexpected token '" + it->value() + "' in section definition");
-
-        parse_option(sc, it, end);
-    }
-
-    doc.push_back(std::move(sc));
-}
-
-} // !namespace
-
-tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end)
-{
-    tokens list;
-    int line = 1;
-    int column = 0;
-
-    while (it != end) {
-        if (*it == '\n')
-            analyse_line(line, column, it);
-        else if (*it == '#')
-            analyse_comment(column, it, end);
-        else if (*it == '[')
-            analyse_section(list, line, column, it, end);
-        else if (*it == '=')
-            analyse_assign(list, line, column, it);
-        else if (is_space(*it))
-            analyse_spaces(column, it, end);
-        else if (*it == '@')
-            analyse_include(list, line, column, it, end);
-        else if (is_quote(*it))
-            analyse_quoted_word(list, line, column, it, end);
-        else if (is_list(*it))
-            analyse_list(list, line, column, it);
-        else
-            analyse_word(list, line, column, it, end);
-    }
-
-    return list;
-}
-
-tokens analyse(std::istream& stream)
-{
-    return analyse(std::istreambuf_iterator<char>(stream), {});
-}
-
-document parse(const tokens& tokens, const std::string& path)
-{
-    document doc;
-    token_iterator it = tokens.cbegin();
-    token_iterator end = tokens.cend();
-
-    while (it != end) {
-        switch (it->type()) {
-        case token::include:
-            parse_include(doc, path, it, end);
-            break;
-        case token::section:
-            parse_section(doc, it, end);
-            break;
-        default:
-            throw exception(it->line(), it->column(), "unexpected '" + it->value() + "' on root document");
-        }
-    }
-
-    return doc;
-}
-
-document read_file(const std::string& filename)
-{
-    // Get parent path.
-    auto parent = filename;
-    auto pos = parent.find_last_of("/\\");
-
-    if (pos != std::string::npos)
-        parent.erase(pos);
-    else
-        parent = ".";
-
-    std::ifstream input(filename);
-
-    if (!input)
-        throw exception(0, 0, std::strerror(errno));
-
-    return parse(analyse(input), parent);
-}
-
-document read_string(const std::string& buffer)
-{
-    std::istringstream iss(buffer);
-
-    return parse(analyse(iss));
-}
-
-void dump(const tokens& tokens)
-{
-    for (const token& token: tokens) {
-        // TODO: add better description
-        std::cout << token.line() << ":" << token.column() << ": " << token.value() << std::endl;
-    }
-}
-
-} // !ini
-
-} // !irccd
--- a/libcommon/irccd/ini.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,647 +0,0 @@
-/*
- * ini.hpp -- extended .ini file parser
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_INI_HPP
-#define IRCCD_COMMON_INI_HPP
-
-/**
- * \file ini.hpp
- * \brief Extended .ini file parser.
- * \author David Demelier <markand@malikania.fr>
- * \version 2.0.0
- */
-
-/**
- * \page Ini Ini
- * \brief Extended .ini file parser.
- *
- *   - \subpage ini-syntax
- */
-
-/**
- * \page ini-syntax Syntax
- * \brief File syntax.
- *
- * The syntax is similar to most of `.ini` implementations as:
- *
- *   - a section is delimited by `[name]` can be redefined multiple times,
- *   - an option **must** always be defined in a section,
- *   - empty options must be surrounded by quotes,
- *   - lists can not include trailing commas,
- *   - include statements must always live at the beginning of files
- *     (in no sections),
- *   - comments start with # until the end of line,
- *   - options with spaces **must** use quotes.
- *
- * # Basic file
- *
- * ````ini
- * # This is a comment.
- * [section]
- * option1 = value1
- * option2 = "value 2 with spaces"    # comment is also allowed here
- * ````
- *
- * # Redefinition
- *
- * Sections can be redefined multiple times and are kept the order they are
- * seen.
- *
- * ````ini
- * [section]
- * value = "1"
- *
- * [section]
- * value = "2"
- * ````
- *
- * The ini::document object will contains two ini::section.
- *
- * # Lists
- *
- * Lists are defined using `()` and commas, like values, they may have quotes.
- *
- * ````ini
- * [section]
- * names = ( "x1", "x2" )
- *
- * # This is also allowed.
- * biglist = (
- *   "abc",
- *   "def"
- * )
- * ````
- *
- * # Include statement
- *
- * You can split a file into several pieces, if the include statement contains a
- * relative path, the path will be relative to the current file being parsed.
- *
- * You **must** use the include statement before any section.
- *
- * If the file contains spaces, use quotes.
- *
- * ````ini
- * # main.conf
- * @include "foo.conf"
- *
- * # foo.conf
- * [section]
- * option1 = value1
- * ````
- */
-
-#include "sysconfig.hpp"
-
-#include <algorithm>
-#include <cassert>
-#include <exception>
-#include <stdexcept>
-#include <string>
-#include <vector>
-
-namespace irccd {
-
-/**
- * Namespace for ini related classes.
- */
-namespace ini {
-
-class document;
-
-/**
- * \brief exception in a file.
- */
-class exception : public std::exception {
-private:
-    int line_;
-    int column_;
-    std::string message_;
-
-public:
-    /**
-     * Constructor.
-     *
-     * \param line the line
-     * \param column the column
-     * \param msg the message
-     */
-    inline exception(int line, int column, std::string msg) noexcept
-        : line_(line)
-        , column_(column)
-        , message_(std::move(msg))
-    {
-    }
-
-    /**
-     * Get the line number.
-     *
-     * \return the line
-     */
-    inline int line() const noexcept
-    {
-        return line_;
-    }
-
-    /**
-     * Get the column number.
-     *
-     * \return the column
-     */
-    inline int column() const noexcept
-    {
-        return column_;
-    }
-
-    /**
-     * Return the raw exception message (no line and column shown).
-     *
-     * \return the exception message
-     */
-    const char* what() const noexcept override
-    {
-        return message_.c_str();
-    }
-};
-
-/**
- * \brief Describe a token read in the .ini source.
- *
- * This class can be used when you want to parse a .ini file yourself.
- *
- * \see analyse
- */
-class token {
-public:
-    /**
-     * \brief token type.
-     */
-    enum type {
-        include,                //!< include statement
-        section,                //!< [section]
-        word,                   //!< word without quotes
-        quoted_word,            //!< word with quotes
-        assign,                 //!< = assignment
-        list_begin,             //!< begin of list (
-        list_end,               //!< end of list )
-        comma                   //!< list separation
-    };
-
-private:
-    type type_;
-    int line_;
-    int column_;
-    std::string value_;
-
-public:
-    /**
-     * Construct a token.
-     *
-     * \param type the type
-     * \param line the line
-     * \param column the column
-     * \param value the value
-     */
-    token(type type, int line, int column, std::string value = "") noexcept
-        : type_(type)
-        , line_(line)
-        , column_(column)
-    {
-        switch (type) {
-        case include:
-            value_ = "@include";
-            break;
-        case section:
-        case word:
-        case quoted_word:
-            value_ = value;
-            break;
-        case assign:
-            value_ = "=";
-            break;
-        case list_begin:
-            value_ = "(";
-            break;
-        case list_end:
-            value_ = ")";
-            break;
-        case comma:
-            value_ = ",";
-            break;
-        default:
-            break;
-        }
-    }
-
-    /**
-     * Get the type.
-     *
-     * \return the type
-     */
-    inline type type() const noexcept
-    {
-        return type_;
-    }
-
-    /**
-     * Get the line.
-     *
-     * \return the line
-     */
-    inline int line() const noexcept
-    {
-        return line_;
-    }
-
-    /**
-     * Get the column.
-     *
-     * \return the column
-     */
-    inline int column() const noexcept
-    {
-        return column_;
-    }
-
-    /**
-     * Get the value. For words, quoted words and section, the value is the
-     * content. Otherwise it's the characters parsed.
-     *
-     * \return the value
-     */
-    inline const std::string& value() const noexcept
-    {
-        return value_;
-    }
-};
-
-/**
- * List of tokens in order they are analyzed.
- */
-using tokens = std::vector<token>;
-
-/**
- * \brief option definition.
- */
-class option : public std::vector<std::string> {
-private:
-    std::string key_;
-
-public:
-    /**
-     * Construct an empty option.
-     *
-     * \pre key must not be empty
-     * \param key the key
-     */
-    inline option(std::string key) noexcept
-        : std::vector<std::string>()
-        , key_(std::move(key))
-    {
-        assert(!key_.empty());
-    }
-
-    /**
-     * Construct a single option.
-     *
-     * \pre key must not be empty
-     * \param key the key
-     * \param value the value
-     */
-    inline option(std::string key, std::string value) noexcept
-        : key_(std::move(key))
-    {
-        assert(!key_.empty());
-
-        push_back(std::move(value));
-    }
-
-    /**
-     * Construct a list option.
-     *
-     * \pre key must not be empty
-     * \param key the key
-     * \param values the values
-     */
-    inline option(std::string key, std::vector<std::string> values) noexcept
-        : std::vector<std::string>(std::move(values))
-        , key_(std::move(key))
-    {
-        assert(!key_.empty());
-    }
-
-    /**
-     * Get the option key.
-     *
-     * \return the key
-     */
-    inline const std::string& key() const noexcept
-    {
-        return key_;
-    }
-
-    /**
-     * Get the option value.
-     *
-     * \return the value
-     */
-    inline const std::string& value() const noexcept
-    {
-        static std::string dummy;
-
-        return empty() ? dummy : (*this)[0];
-    }
-};
-
-/**
- * \brief Section that contains one or more options.
- */
-class section : public std::vector<option> {
-private:
-    std::string key_;
-
-public:
-    /**
-     * Construct a section with its name.
-     *
-     * \pre key must not be empty
-     * \param key the key
-     */
-    inline section(std::string key) noexcept
-        : key_(std::move(key))
-    {
-        assert(!key_.empty());
-    }
-
-    /**
-     * Get the section key.
-     *
-     * \return the key
-     */
-    inline const std::string& key() const noexcept
-    {
-        return key_;
-    }
-
-    /**
-     * Check if the section contains a specific option.
-     *
-     * \param key the option key
-     * \return true if the option exists
-     */
-    inline bool contains(const std::string& key) const noexcept
-    {
-        return find(key) != end();
-    }
-
-    /**
-     * Find an option or return an empty one if not found.
-     *
-     * \param key the key
-     * \return the option or empty one if not found
-     */
-    inline option get(const std::string& key) const noexcept
-    {
-        auto it = find(key);
-
-        if (it == end())
-            return option(key);
-
-        return *it;
-    }
-
-    /**
-     * Find an option by key and return an iterator.
-     *
-     * \param key the key
-     * \return the iterator or end() if not found
-     */
-    inline iterator find(const std::string& key) noexcept
-    {
-        return std::find_if(begin(), end(), [&] (const auto& o) {
-            return o.key() == key;
-        });
-    }
-
-    /**
-     * Find an option by key and return an iterator.
-     *
-     * \param key the key
-     * \return the iterator or end() if not found
-     */
-    inline const_iterator find(const std::string& key) const noexcept
-    {
-        return std::find_if(cbegin(), cend(), [&] (const auto& o) {
-            return o.key() == key;
-        });
-    }
-
-    /**
-     * Access an option at the specified key.
-     *
-     * \param key the key
-     * \return the option
-     * \pre contains(key) must return true
-     */
-    inline option& operator[](const std::string& key)
-    {
-        assert(contains(key));
-
-        return *find(key);
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \param key the key
-     * \return the option
-     * \pre contains(key) must return true
-     */
-    inline const option& operator[](const std::string& key) const
-    {
-        assert(contains(key));
-
-        return *find(key);
-    }
-
-    /**
-     * Inherited operators.
-     */
-    using std::vector<option>::operator[];
-};
-
-/**
- * \brief Ini document description.
- * \see read_file
- * \see read_string
- */
-class document : public std::vector<section> {
-public:
-    /**
-     * Check if a document has a specific section.
-     *
-     * \param key the key
-     * \return true if the document contains the section
-     */
-    inline bool contains(const std::string& key) const noexcept
-    {
-        return find(key) != end();
-    }
-
-    /**
-     * Find a section or return an empty one if not found.
-     *
-     * \param key the key
-     * \return the section or empty one if not found
-     */
-    inline section get(const std::string& key) const noexcept
-    {
-        auto it = find(key);
-
-        if (it == end())
-            return section(key);
-
-        return *it;
-    }
-
-    /**
-     * Find a section by key and return an iterator.
-     *
-     * \param key the key
-     * \return the iterator or end() if not found
-     */
-    inline iterator find(const std::string& key) noexcept
-    {
-        return std::find_if(begin(), end(), [&] (const auto& o) {
-            return o.key() == key;
-        });
-    }
-
-    /**
-     * Find a section by key and return an iterator.
-     *
-     * \param key the key
-     * \return the iterator or end() if not found
-     */
-    inline const_iterator find(const std::string& key) const noexcept
-    {
-        return std::find_if(cbegin(), cend(), [&] (const auto& o) {
-            return o.key() == key;
-        });
-    }
-
-    /**
-     * Access a section at the specified key.
-     *
-     * \param key the key
-     * \return the section
-     * \pre contains(key) must return true
-     */
-    inline section& operator[](const std::string& key)
-    {
-        assert(contains(key));
-
-        return *find(key);
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \param key the key
-     * \return the section
-     * \pre contains(key) must return true
-     */
-    inline const section& operator[](const std::string& key) const
-    {
-        assert(contains(key));
-
-        return *find(key);
-    }
-
-    /**
-     * Inherited operators.
-     */
-    using std::vector<section>::operator[];
-};
-
-/**
- * Analyse a stream and detect potential syntax errors. This does not parse the
- * file like including other files in include statement.
- *
- * It does only analysis, for example if an option is defined under no section,
- * this does not trigger an exception while it's invalid.
- *
- * \param it the iterator
- * \param end where to stop
- * \return the list of tokens
- * \throws exception on errors
- */
-tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end);
-
-/**
- * Overloaded function for stream.
- *
- * \param stream the stream
- * \return the list of tokens
- * \throws exception on errors
- */
-tokens analyse(std::istream& stream);
-
-/**
- * Parse the produced tokens.
- *
- * \param tokens the tokens
- * \param path the parent path
- * \return the document
- * \throw exception on errors
- */
-document parse(const tokens& tokens, const std::string& path = ".");
-
-/**
- * Parse a file.
- *
- * \param filename the file name
- * \return the document
- * \throw exception on errors
- */
-document read_file(const std::string& filename);
-
-/**
- * Parse a string.
- *
- * If the string contains include statements, they are relative to the current
- * working directory.
- *
- * \param buffer the buffer
- * \return the document
- * \throw exception on exceptions
- */
-document read_string(const std::string& buffer);
-
-/**
- * Show all tokens and their description.
- *
- * \param tokens the tokens
- */
-void dump(const tokens& tokens);
-
-} // !ini
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_INI_HPP
--- a/libcommon/irccd/ini_util.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/*
- * ini_util.hpp -- ini utilities
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_INI_UTIL_HPP
-#define IRCCD_COMMON_INI_UTIL_HPP
-
-/**
- * \file ini_util.hpp
- * \brief Ini utilities.
- */
-
-#include <boost/optional.hpp>
-
-#include "ini.hpp"
-#include "string_util.hpp"
-
-namespace irccd {
-
-/**
- * \brief Ini utilities.
- */
-namespace ini_util {
-
-/**
- * Get an unsigned integer from the configuration section.
- *
- * \param sc the section
- * \param name the option name
- * \return the value or none if not able to convert
- */
-template <typename Int>
-inline boost::optional<Int> get_uint(const ini::section& sc, const std::string& name) noexcept
-{
-    return string_util::to_uint<Int>(sc.get(name).value());
-}
-
-/**
- * Get an optional string or the default value if not given.
- *
- * \param sc the section
- * \param name the option name
- * \param def the default value
- * \return the value or def if not found
- */
-inline std::string optional_string(const ini::section& sc,
-                                   const std::string& name,
-                                   const std::string& def) noexcept
-{
-    const auto it = sc.find(name);
-
-    if (it == sc.end())
-        return def;
-
-    return it->value();
-}
-
-/**
- * Get an optional unsigned integer from the configuration section.
- *
- * \param sc the section
- * \param name the option name
- * \param def the default value
- * \return the value or none if not able to convert
- */
-template <typename Int>
-inline boost::optional<Int> optional_uint(const ini::section& sc,
-                                          const std::string& name,
-                                          Int def) noexcept
-{
-    const auto it = sc.find(name);
-
-    if (it == sc.end())
-        return def;
-
-    return string_util::to_uint<Int>(it->value());
-}
-
-} // !ini_util
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_INI_UTIL_HPP
--- a/libcommon/irccd/json_util.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,366 +0,0 @@
-/*
- * json_util.hpp -- utilities for JSON
- *
- * Copyright (c) 2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_JSON_UTIL_HPP
-#define IRCCD_JSON_UTIL_HPP
-
-/**
- * \file json_util.hpp
- * \brief Utilities for JSON.
- */
-
-#include <cstdint>
-#include <limits>
-#include <string>
-#include <type_traits>
-
-#include <boost/optional.hpp>
-
-#include <json.hpp>
-
-namespace irccd {
-
-/**
- * \brief Utilities for JSON.
- */
-namespace json_util {
-
-/**
- * \cond JSON_UTIL_HIDDEN_SYMBOLS
- */
-
-namespace detail {
-
-template <typename Int>
-class parser_type_traits_uint : public std::true_type {
-public:
-    static boost::optional<Int> get(const nlohmann::json& value) noexcept
-    {
-        if (!value.is_number_unsigned())
-            return boost::none;
-
-        const auto ret = value.get<std::uint64_t>();
-
-        if (ret > std::numeric_limits<Int>::max())
-            return boost::none;
-
-        return static_cast<Int>(ret);
-    }
-};
-
-template <typename Int>
-class parser_type_traits_int : public std::true_type {
-public:
-    static boost::optional<Int> get(const nlohmann::json& value) noexcept
-    {
-        if (!value.is_number_integer())
-            return boost::none;
-
-        const auto ret = value.get<std::int64_t>();
-
-        if (ret < std::numeric_limits<Int>::min() || ret > std::numeric_limits<Int>::max())
-            return boost::none;
-
-        return static_cast<Int>(ret);
-    }
-};
-
-} // !detail
-
-/**
- * \endcond
- */
-
-/**
- * \brief Describe how to convert a JSON value.
- *
- * This class must be specialized for every type you want to convert from JSON
- * to its native type.
- *
- * You only need to implement the get function with the following signature:
- *
- * ```cpp
- * static boost::optional<T> get(const nlohmann::json& value);
- * ```
- *
- * The implementation should not throw an exception but return a null optional
- * instead.
- *
- * This class is already specialized for the given types:
- *
- * - bool
- * - double
- * - std::uint(8, 16, 32, 64)
- * - std::string
- */
-template <typename T>
-class parser_type_traits : public std::false_type {
-};
-
-/**
- * \brief Specialization for `bool`.
- */
-template <>
-class parser_type_traits<bool> : public std::true_type {
-public:
-    /**
-     * Convert the JSON value to bool.
-     *
-     * \return the bool or none if not a boolean type
-     */
-    static boost::optional<bool> get(const nlohmann::json& value) noexcept
-    {
-        if (!value.is_boolean())
-            return boost::none;
-
-        return value.get<bool>();
-    }
-};
-
-/**
- * \brief Specialization for `double`.
- */
-template <>
-class parser_type_traits<double> : public std::true_type {
-public:
-    /**
-     * Convert the JSON value to bool.
-     *
-     * \return the double or none if not a double type
-     */
-    static boost::optional<double> get(const nlohmann::json& value) noexcept
-    {
-        if (!value.is_number_float())
-            return boost::none;
-
-        return value.get<double>();
-    }
-};
-
-/**
- * \brief Specialization for `std::string`.
- */
-template <>
-class parser_type_traits<std::string> : public std::true_type {
-public:
-    /**
-     * Convert the JSON value to bool.
-     *
-     * \return the string or none if not a string type
-     */
-    static boost::optional<std::string> get(const nlohmann::json& value)
-    {
-        if (!value.is_string())
-            return boost::none;
-
-        return value.get<std::string>();
-    }
-};
-
-/**
- * \brief Specialization for `std::int8_t`.
- */
-template <>
-class parser_type_traits<std::int8_t> : public detail::parser_type_traits_int<std::int8_t> {
-};
-
-/**
- * \brief Specialization for `std::int16_t`.
- */
-template <>
-class parser_type_traits<std::int16_t> : public detail::parser_type_traits_int<std::int16_t> {
-};
-
-/**
- * \brief Specialization for `std::int32_t`.
- */
-template <>
-class parser_type_traits<std::int32_t> : public detail::parser_type_traits_int<std::int32_t> {
-};
-
-/**
- * \brief Specialization for `std::int64_t`.
- */
-template <>
-class parser_type_traits<std::int64_t> : public std::true_type {
-public:
-    /**
-     * Convert the JSON value to std::int64_t.
-     *
-     * \return the int or none if not a int type
-     */
-    static boost::optional<std::int64_t> get(const nlohmann::json& value) noexcept
-    {
-        if (!value.is_number_integer())
-            return boost::none;
-
-        return value.get<std::int64_t>();
-    }
-};
-
-/**
- * \brief Specialization for `std::int8_t`.
- */
-template <>
-class parser_type_traits<std::uint8_t> : public detail::parser_type_traits_uint<std::uint8_t> {
-};
-
-/**
- * \brief Specialization for `std::int16_t`.
- */
-template <>
-class parser_type_traits<std::uint16_t> : public detail::parser_type_traits_uint<std::uint16_t> {
-};
-
-/**
- * \brief Specialization for `std::int32_t`.
- */
-template <>
-class parser_type_traits<std::uint32_t> : public detail::parser_type_traits_uint<std::uint32_t> {
-};
-
-/**
- * \brief Specialization for `std::int64_t`.
- */
-template <>
-class parser_type_traits<std::uint64_t> : public std::true_type {
-public:
-    /**
-     * Convert the JSON value to std::uint64_t.
-     *
-     * \return the int or none if not a int type
-     */
-    static boost::optional<std::uint64_t> get(const nlohmann::json& value) noexcept
-    {
-        if (!value.is_number_unsigned())
-            return boost::none;
-
-        return value.get<std::uint64_t>();
-    }
-};
-
-/**
- * \brief Convenient JSON object parser
- *
- * This class helps destructuring insecure JSON input by returning optional
- * values if they are not present or invalid.
- */
-class document : public nlohmann::json {
-public:
-    /**
-     * Constructor.
-     *
-     * \param object the object
-     */
-    inline document(nlohmann::json object)
-        : nlohmann::json(std::move(object))
-    {
-    }
-
-    /**
-     * Get a value from the document object.
-     *
-     * \param key the property key
-     * \return the value or boost::none if not found or not convertible
-     */
-    template <typename Type>
-    inline boost::optional<Type> get(const std::string& key) const noexcept
-    {
-        static_assert(parser_type_traits<Type>::value, "type not supported");
-
-        const auto it = find(key);
-
-        if (it == end())
-            return boost::none;
-
-        return parser_type_traits<Type>::get(*it);
-    }
-
-    /**
-     * Get an optional value from the document object.
-     *
-     * If the value is undefined, the default value is returned. Otherwise, if
-     * the value is not in the given type, boost::none is returned.
-     *
-     * \param key the property key
-     * \param def the default value if property is undefined
-     * \return the value, boost::none or def
-     */
-    template <typename Type, typename DefaultValue>
-    inline boost::optional<Type> optional(const std::string& key, DefaultValue&& def) const noexcept
-    {
-        static_assert(parser_type_traits<Type>::value, "type not supported");
-
-        const auto it = find(key);
-
-        if (it == end())
-            return boost::optional<Type>(std::forward<DefaultValue>(def));
-
-        return parser_type_traits<Type>::get(*it);
-    }
-};
-
-/**
- * Print the value as human readable.
- *
- * \note This only works on flat objects.
- * \param value the value
- * \param indent the optional indent for objects/arrays
- * \return the string
- */
-inline std::string pretty(const nlohmann::json& value, int indent = 4)
-{
-    switch (value.type()) {
-    case nlohmann::json::value_t::null:
-        return "null";
-    case nlohmann::json::value_t::string:
-        return value.get<std::string>();
-    case nlohmann::json::value_t::boolean:
-        return value.get<bool>() ? "true" : "false";
-    case nlohmann::json::value_t::number_integer:
-        return std::to_string(value.get<std::int64_t>());
-    case nlohmann::json::value_t::number_unsigned:
-        return std::to_string(value.get<std::uint64_t>());
-    case nlohmann::json::value_t::number_float:
-        return std::to_string(value.get<double>());
-    default:
-        return value.dump(indent);
-    }
-}
-
-/**
- * Check if a JSON array contains a specific value in any order.
- *
- * \param array the JSON array
- * \param value the JSON value
- * \return true if value is present
- */
-inline bool contains(const nlohmann::json& array, const nlohmann::json& value) noexcept
-{
-    for (const auto& v : array)
-        if (v == value)
-            return true;
-
-    return false;
-}
-
-} // !json_util
-
-} // !irccd
-
-#endif // !IRCCD_JSON_UTIL_HPP
--- a/libcommon/irccd/options.cpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,191 +0,0 @@
-/*
- * options.cpp -- parse Unix command line options
- *
- * Copyright (c) 2015-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-
-#include "options.hpp"
-
-namespace irccd {
-
-namespace option {
-
-namespace {
-
-using iterator = std::vector<std::string>::iterator;
-using args = std::vector<std::string>;
-
-inline bool is_option(const std::string& arg) noexcept
-{
-    return arg.size() >= 2 && arg[0] == '-';
-}
-
-inline bool is_long_option(const std::string& arg) noexcept
-{
-    assert(is_option(arg));
-
-    return arg.size() >= 3 && arg[1] == '-';
-}
-
-inline bool is_short_simple(const std::string& arg) noexcept
-{
-    assert(is_option(arg) && !is_long_option(arg));
-
-    return arg.size() == 2;
-}
-
-void parse_long_option(result& result, args& args, iterator& it, iterator& end, const options& definition)
-{
-    auto arg = *it++;
-    auto opt = definition.find(arg);
-
-    if (opt == definition.end())
-        throw invalid_option(arg);
-
-    // Need argument?
-    if (opt->second) {
-        if (it == end || is_option(*it))
-            throw missing_value(arg);
-
-        result.insert(std::make_pair(arg, *it++));
-        it = args.erase(args.begin(), it);
-        end = args.end();
-    } else {
-        result.insert(std::make_pair(arg, ""));
-        it = args.erase(args.begin());
-        end = args.end();
-    }
-}
-
-void parse_short_option_simple(result& result, args& args, iterator& it, iterator &end, const options& definition)
-{
-    /*
-     * Here two cases:
-     *
-     * -v (no option)
-     * -c value
-     */
-    auto arg = *it++;
-    auto opt = definition.find(arg);
-
-    if (opt == definition.end())
-        throw invalid_option(arg);
-
-    // Need argument?
-    if (opt->second) {
-        if (it == end || is_option(*it))
-            throw missing_value(arg);
-
-        result.insert(std::make_pair(arg, *it++));
-        it = args.erase(args.begin(), it);
-        end = args.end();
-    } else {
-        result.insert(std::make_pair(arg, ""));
-        it = args.erase(args.begin());
-        end = args.end();
-    }
-}
-
-void parse_short_option_compressed(result& result, args& args, iterator& it, iterator &end, const options& definition)
-{
-    /*
-     * Here multiple scenarios:
-     *
-     * 1. -abc (-a -b -c if all are simple boolean arguments)
-     * 2. -vc foo.conf (-v -c foo.conf if -c is argument dependant)
-     * 3. -vcfoo.conf (-v -c foo.conf also)
-     */
-    auto value = it->substr(1);
-    auto len = value.length();
-    int toremove = 1;
-
-    for (std::size_t i = 0; i < len; ++i) {
-        auto arg = std::string{'-'} + value[i];
-        auto opt = definition.find(arg);
-
-        if (opt == definition.end())
-            throw invalid_option(arg);
-
-        if (opt->second) {
-            if (i == (len - 1)) {
-                // End of string, get the next argument (see 2.).
-                if (++it == end || is_option(*it))
-                    throw missing_value(arg);
-
-                result.insert(std::make_pair(arg, *it));
-                toremove += 1;
-            } else {
-                result.insert(std::make_pair(arg, value.substr(i + 1)));
-                i = len;
-            }
-        } else
-            result.insert(std::make_pair(arg, ""));
-    }
-
-    it = args.erase(args.begin(), args.begin() + toremove);
-    end = args.end();
-}
-
-void parse_short_option(result& result, args& args, iterator& it, iterator &end, const options& definition)
-{
-    if (is_short_simple(*it))
-        parse_short_option_simple(result, args, it, end, definition);
-    else
-        parse_short_option_compressed(result, args, it, end, definition);
-}
-
-} // !namespace
-
-result read(std::vector<std::string>& args, const options& definition)
-{
-    result result;
-
-    auto it = args.begin();
-    auto end = args.end();
-
-    while (it != end) {
-        if (!is_option(*it))
-            break;
-
-        if (is_long_option(*it))
-            parse_long_option(result, args, it, end, definition);
-        else
-            parse_short_option(result, args, it, end, definition);
-    }
-
-    return result;
-}
-
-result read(int& argc, char**& argv, const options& definition)
-{
-    std::vector<std::string> args;
-
-    for (int i = 0; i < argc; ++i)
-        args.push_back(argv[i]);
-
-    auto before = args.size();
-    auto result = read(args, definition);
-
-    argc -= before - args.size();
-    argv += before - args.size();
-
-    return result;
-}
-
-} // !option
-
-} // !irccd
--- a/libcommon/irccd/options.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +0,0 @@
-/*
- * options.hpp -- parse Unix command line options
- *
- * Copyright (c) 2015-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_OPTIONS_HPP
-#define IRCCD_COMMON_OPTIONS_HPP
-
-/**
- * \file options.hpp
- * \brief Basic Unix options parser.
- */
-
-#include <exception>
-#include <map>
-#include <string>
-#include <utility>
-#include <vector>
-
-namespace irccd {
-
-/**
- * Namespace for options parsing.
- */
-namespace option {
-
-/**
- * \brief This exception is thrown when an invalid option has been found.
- */
-class invalid_option : public std::exception {
-private:
-    std::string message_;
-    std::string name_;
-
-public:
-    /**
-     * Construct the exception.
-     *
-     * \param name the argument missing
-     */
-    inline invalid_option(std::string name)
-        : name_(std::move(name))
-    {
-        message_ = std::string("invalid option: ") + name_;
-    }
-
-    /**
-     * Get the option name.
-     *
-     * \return the name
-     */
-    inline const std::string& name() const noexcept
-    {
-        return name_;
-    }
-
-    /**
-     * Get the error message.
-     *
-     * \return the error message
-     */
-    const char* what() const noexcept override
-    {
-        return message_.c_str();
-    }
-};
-
-/**
- * \brief This exception is thrown when an option requires a value and no value
- * has been given.
- */
-class missing_value : public std::exception {
-private:
-    std::string message_;
-    std::string name_;
-
-public:
-    /**
-     * Construct the exception.
-     *
-     * \param name the option that requires a value
-     */
-    inline missing_value(std::string name)
-        : name_(std::move(name))
-    {
-        message_ = std::string("missing argument for: ") + name_;
-    }
-
-    /**
-     * Get the option name.
-     *
-     * \return the name
-     */
-    inline const std::string& name() const noexcept
-    {
-        return name_;
-    }
-
-    /**
-     * Get the error message.
-     *
-     * \return the error message
-     */
-    const char* what() const noexcept override
-    {
-        return message_.c_str();
-    }
-};
-
-/**
- * Packed multimap of options.
- */
-using result = std::multimap<std::string, std::string>;
-
-/**
- * Define the allowed options.
- */
-using options = std::map<std::string, bool>;
-
-/**
- * Extract the command line options and return a result.
- *
- * \param args the arguments
- * \param definition
- * \warning the arguments vector is modified in place to remove parsed options
- * \throw missing_value
- * \throw invalid_option
- */
-result read(std::vector<std::string>& args, const options& definition);
-
-/**
- * Overloaded function for usage with main() arguments.
- *
- * \param argc the number of arguments
- * \param argv the argument vector
- * \param definition
- * \note don't forget to remove the first argv[0] argument
- * \warning the argc and argv are modified in place to remove parsed options
- * \throw missing_value
- * \throw invalid_option
- */
-result read(int& argc, char**& argv, const options& definition);
-
-} // !option
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_OPTIONS_HPP
--- a/libcommon/irccd/socket_acceptor.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-/*
- * socket_acceptor.hpp -- socket stream acceptor interface
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
-#define IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
-
-/**
- * \file socket_acceptor.hpp
- * \brief Socket stream acceptor interface.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#include "acceptor.hpp"
-#include "socket_stream.hpp"
-
-namespace irccd {
-
-namespace io {
-
-/**
- * \brief Socket stream acceptor interface.
- * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
- */
-template <typename Protocol>
-class socket_acceptor : public acceptor {
-public:
-    /**
-     * Convenient endpoint alias.
-     */
-    using endpoint = typename Protocol::endpoint;
-
-    /**
-     * Convenient acceptor alias.
-     */
-    using acceptor = typename Protocol::acceptor;
-
-    /**
-     * Convenient socket alias.
-     */
-    using socket = typename Protocol::socket;
-
-private:
-    acceptor acceptor_;
-
-#if !defined(NDEBUG)
-    bool is_accepting_{false};
-#endif
-
-protected:
-    /**
-     * Helper to accept on the real underlying socket.
-     *
-     * \param socket the real socket
-     * \param handler the handler
-     */
-    template <typename Socket, typename Handler>
-    void do_accept(Socket& socket, Handler handler);
-
-public:
-    /**
-     * Construct the socket_acceptor.
-     *
-     * \pre acceptor must be ready (is_open() returns true)
-     * \param acceptor the Boost.Asio acceptor
-     */
-    inline socket_acceptor(acceptor acceptor) noexcept
-        : acceptor_(std::move(acceptor))
-    {
-        assert(acceptor_.is_open());
-    }
-
-    /**
-     * Get the underlying acceptor.
-     *
-     * \return the acceptor
-     */
-    inline const acceptor& get_acceptor() const noexcept
-    {
-        return acceptor_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the acceptor
-     */
-    inline acceptor& get_acceptor() noexcept
-    {
-        return acceptor_;
-    }
-
-    /**
-     * \copydoc acceptor::accept
-     */
-    void accept(accept_handler handler) override;
-};
-
-template <typename Protocol>
-template <typename Socket, typename Handler>
-void socket_acceptor<Protocol>::do_accept(Socket& socket, Handler handler)
-{
-#if !defined(NDEBUG)
-    assert(!is_accepting_);
-
-    is_accepting_ = true;
-#endif
-
-    acceptor_.async_accept(socket, [this, handler] (auto code) {
-#if !defined(NDEBUG)
-        is_accepting_ = false;
-#endif
-        handler(detail::convert(code));
-    });
-}
-
-template <typename Protocol>
-void socket_acceptor<Protocol>::accept(accept_handler handler)
-{
-    assert(handler);
-
-    const auto client = std::make_shared<socket_stream<socket>>(acceptor_.get_io_service());
-
-    do_accept(client->get_socket(), [this, client, handler] (auto code) {
-        handler(std::move(code), code ? nullptr : std::move(client));
-    });
-}
-
-/**
- * Convenient TCP/IP acceptor type.
- */
-using ip_acceptor = socket_acceptor<boost::asio::ip::tcp>;
-
-#if !BOOST_OS_WINDOWS
-
-/**
- * Convenient Unix acceptor type.
- */
-using local_acceptor = socket_acceptor<boost::asio::local::stream_protocol>;
-
-#endif
-
-} // !io
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
--- a/libcommon/irccd/socket_connector.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +0,0 @@
-/*
- * socket_connector.hpp -- socket connection interface
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_SOCKET_CONNECTOR_HPP
-#define IRCCD_COMMON_SOCKET_CONNECTOR_HPP
-
-/**
- * \file socket_connector.hpp
- * \brief Socket connection interface.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#include <vector>
-
-#include "connector.hpp"
-#include "socket_stream.hpp"
-
-namespace irccd {
-
-namespace io {
-
-/**
- * \brief Socket connection interface.
- * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
- */
-template <typename Protocol>
-class socket_connector : public connector {
-public:
-    /**
-     * Convenient endpoint alias.
-     */
-    using endpoint = typename Protocol::endpoint;
-
-    /**
-     * Convenient socket alias.
-     */
-    using socket = typename Protocol::socket;
-
-private:
-    boost::asio::io_service& service_;
-    std::vector<endpoint> endpoints_;
-
-#if !defined(NDEBUG)
-    bool is_connecting_{false};
-#endif
-
-protected:
-    /**
-     * Start trying to connect to all endpoints.
-     *
-     * \param socket the underlying socket
-     * \param handler handler with `void f(std::error_code)` signature
-     */
-    template <typename Socket, typename Handler>
-    void do_connect(Socket& socket, Handler handler);
-
-public:
-    /**
-     * Construct the socket connector with only one endpoint.
-     *
-     * \param service the service
-     * \param endpoint the unique endpoint
-     */
-    inline socket_connector(boost::asio::io_service& service, endpoint endpoint) noexcept
-        : service_(service)
-        , endpoints_{std::move(endpoint)}
-    {
-    }
-
-    /**
-     * Construct the socket connection.
-     *
-     * \param service the service
-     * \param eps the endpoints
-     */
-    inline socket_connector(boost::asio::io_service& service, std::vector<endpoint> eps) noexcept
-        : service_(service)
-        , endpoints_(std::move(eps))
-    {
-    }
-
-    /**
-     * Get the underlying I/O service.
-     *
-     * \return the I/O service
-     */
-    inline const boost::asio::io_service& get_io_service() const noexcept
-    {
-        return service_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the I/O service
-     */
-    inline boost::asio::io_service& get_io_service() noexcept
-    {
-        return service_;
-    }
-
-    /**
-     * \copydoc connector::connect
-     */
-    void connect(connect_handler handler);
-};
-
-template <typename Protocol>
-template <typename Socket, typename Handler>
-void socket_connector<Protocol>::do_connect(Socket& socket, Handler handler)
-{
-#if !defined(NDEBUG)
-    assert(!is_connecting_);
-
-    is_connecting_ = true;
-#endif
-
-    boost::asio::async_connect(socket, endpoints_.begin(), endpoints_.end(), [this, handler] (auto code, auto ep) {
-#if !defined(NDEBUG)
-        is_connecting_ = false;
-#endif
-
-        if (ep == endpoints_.end())
-            handler(make_error_code(std::errc::host_unreachable));
-        else
-            handler(detail::convert(code));
-    });
-}
-
-template <typename Protocol>
-void socket_connector<Protocol>::connect(connect_handler handler)
-{
-    assert(handler);
-
-    const auto stream = std::make_shared<socket_stream<socket>>(service_);
-
-    do_connect(stream->get_socket(), [handler, stream] (auto code) {
-        handler(code, code ? nullptr : std::move(stream));
-    });
-}
-
-/**
- * Convenient TCP/IP connector type.
- */
-using ip_connector = socket_connector<boost::asio::ip::tcp>;
-
-#if !BOOST_OS_WINDOWS
-
-/**
- * Convenient Unix conncetor type.
- */
-using local_connector = socket_connector<boost::asio::local::stream_protocol>;
-
-#endif
-
-} // !io
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_SOCKET_CONNECTOR_HPP
--- a/libcommon/irccd/socket_stream.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,246 +0,0 @@
-/*
- * socket_stream.hpp -- socket stream interface
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_SOCKET_STREAM_HPP
-#define IRCCD_COMMON_SOCKET_STREAM_HPP
-
-/**
- * \file socket_stream.hpp
- * \brief Socket stream interface.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#include <cstddef>
-#include <cassert>
-#include <string>
-#include <utility>
-
-#include <boost/asio.hpp>
-#include <boost/predef/os.h>
-
-#include "stream.hpp"
-
-namespace irccd {
-
-namespace io {
-
-/**
- * \cond HIDDEN_SYMBOLS
- */
-
-namespace detail {
-
-/**
- * Convert boost::system::error_code to std.
- *
- * \param code the error code
- * \return the std::error_code
- */
-inline std::error_code convert(boost::system::error_code code) noexcept
-{
-    return std::error_code(code.value(), std::system_category());
-}
-
-} // !detail
-
-/**
- * \endcond
- */
-
-/**
- * \brief Socket implementation interface.
- * \tparam Socket the Boost.Asio compatible socket.
- *
- * This class reimplements stream for Boost.Asio sockets.
- */
-template <typename Socket>
-class socket_stream : public stream {
-private:
-    Socket socket_;
-    boost::asio::streambuf input_;
-    std::string output_;
-
-#if !defined(NDEBUG)
-    bool is_receiving_{false};
-    bool is_sending_{false};
-#endif
-
-    void handle_read(boost::system::error_code, std::size_t, read_handler);
-    void handle_write(boost::system::error_code, std::size_t, write_handler);
-
-public:
-    /**
-     * Create the socket stream.
-     *
-     * \param args the Socket constructor arguments
-     */
-    template <typename... Args>
-    inline socket_stream(Args&&... args)
-        : socket_(std::forward<Args>(args)...)
-    {
-    }
-
-    /**
-     * Get the underlying socket.
-     *
-     * \return the socket
-     */
-    inline const Socket& get_socket() const noexcept
-    {
-        return socket_;
-    }
-
-    /**
-     * Overloaded function
-     *
-     * \return the socket
-     */
-    inline Socket& get_socket() noexcept
-    {
-        return socket_;
-    }
-
-    /**
-     * \copydoc stream::read
-     */
-    void read(read_handler handler) override;
-
-    /**
-     * \copydoc stream::write
-     */
-    void write(const nlohmann::json& json, write_handler handler) override;
-};
-
-template <typename Socket>
-void socket_stream<Socket>::handle_read(boost::system::error_code code,
-                                        std::size_t xfer,
-                                        read_handler handler)
-{
-#if !defined(NDEBUG)
-    is_receiving_ = false;
-#endif
-
-    if (xfer == 0U) {
-        handler(make_error_code(std::errc::not_connected), nullptr);
-        return;
-    }
-    if (code) {
-        handler(detail::convert(code), nullptr);
-        return;
-    }
-
-    // 1. Convert the buffer safely.
-    std::string buffer;
-
-    try {
-        buffer = std::string(
-            boost::asio::buffers_begin(input_.data()),
-            boost::asio::buffers_begin(input_.data()) + xfer - /* \r\n\r\n */ 4
-        );
-
-        input_.consume(xfer);
-    } catch (const std::bad_alloc&) {
-        handler(make_error_code(std::errc::not_enough_memory), nullptr);
-        return;
-    }
-
-    // 2. Convert to JSON.
-    nlohmann::json doc;
-
-    try {
-        doc = nlohmann::json::parse(buffer);
-    } catch (const std::exception&) {
-        handler(make_error_code(std::errc::invalid_argument), nullptr);
-        return;
-    }
-
-    if (!doc.is_object())
-        handler(make_error_code(std::errc::invalid_argument), nullptr);
-    else
-        handler(std::error_code(), std::move(doc));
-}
-
-template <typename Socket>
-void socket_stream<Socket>::handle_write(boost::system::error_code code,
-                                         std::size_t xfer,
-                                         write_handler handler)
-{
-#if !defined(NDEBUG)
-    is_sending_ = false;
-#endif
-
-    if (xfer == 0)
-        handler(make_error_code(std::errc::not_connected));
-    else
-        handler(detail::convert(code));
-}
-
-template <typename Socket>
-void socket_stream<Socket>::read(read_handler handler)
-{
-#if !defined(NDEBUG)
-    assert(!is_receiving_);
-    assert(handler);
-
-    is_receiving_ = true;
-#endif
-
-    boost::asio::async_read_until(get_socket(), input_, "\r\n\r\n", [this, handler] (auto code, auto xfer) {
-        handle_read(code, xfer, std::move(handler));
-    });
-}
-
-template <typename Socket>
-void socket_stream<Socket>::write(const nlohmann::json& json, write_handler handler)
-{
-#if !defined(NDEBUG)
-    assert(!is_sending_);
-    assert(handler);
-
-    is_sending_ = true;
-#endif
-
-    output_ = json.dump(0) + "\r\n\r\n";
-
-    const auto buffer = boost::asio::buffer(output_.data(), output_.size());
-
-    boost::asio::async_write(get_socket(), buffer, [this, handler] (auto code, auto xfer) {
-        handle_write(code, xfer, std::move(handler));
-    });
-}
-
-/**
- * Convenient TCP/IP stream type.
- */
-using ip_stream = socket_stream<boost::asio::ip::tcp::socket>;
-
-#if !BOOST_OS_WINDOWS
-
-/**
- * Convenient Unix stream type.
- */
-using local_stream = socket_stream<boost::asio::local::stream_protocol::socket>;
-
-#endif
-
-} // !io
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_SOCKET_STREAM_HPP
--- a/libcommon/irccd/stream.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/*
- * stream.hpp -- abstract stream interface
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_STREAM_HPP
-#define IRCCD_COMMON_STREAM_HPP
-
-/**
- * \file stream.hpp
- * \brief Abstract stream interface.
- */
-
-#include <functional>
-#include <system_error>
-
-#include "json.hpp"
-
-namespace irccd {
-
-namespace io {
-
-/**
- * \brief Read completion handler.
- */
-using read_handler = std::function<void (std::error_code, nlohmann::json)>;
-
-/**
- * \brief Write completion handler.
- */
-using write_handler = std::function<void (std::error_code)>;
-
-/**
- * \brief Abstract stream interface
- *
- * Abstract I/O interface that allows reading/writing from a stream in an
- * asynchronous manner.
- *
- * The derived classes must implement non-blocking read and write operations.
- */
-class stream {
-public:
-    /**
-     * Default constructor.
-     */
-    stream() = default;
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~stream() = default;
-
-    /**
-     * Start asynchronous read.
-     *
-     * \pre another read operation must not be running
-     * \pre handler != nullptr
-     * \param handler the handler
-     */
-    virtual void read(read_handler handler) = 0;
-
-    /**
-     * Start asynchronous write.
-     *
-     * \pre json.is_object()
-     * \pre another write operation must not be running
-     * \pre handler != nullptr
-     * \param json the JSON message
-     * \param handler the handler
-     */
-    virtual void write(const nlohmann::json& json, write_handler handler) = 0;
-};
-
-} // !io
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_STREAM_HPP
--- a/libcommon/irccd/string_util.cpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,446 +0,0 @@
-/*
- * string_util.cpp -- string utilities
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <boost/predef/os.h>
-
-#include "sysconfig.hpp"
-
-#if defined(HAVE_POPEN)
-#   include <array>
-#   include <cerrno>
-#   include <cstring>
-#   include <functional>
-#   include <memory>
-#endif
-
-#include <cassert>
-#include <iomanip>
-
-#include "string_util.hpp"
-
-using namespace std::string_literals;
-
-namespace irccd {
-
-namespace string_util {
-
-// {{{ subst
-
-namespace {
-
-const std::unordered_map<std::string, int> irc_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> irc_attributes{
-    { "bold",       '\x02'  },
-    { "italic",     '\x09'  },
-    { "strike",     '\x13'  },
-    { "reset",      '\x0f'  },
-    { "underline",  '\x15'  },
-    { "underline2", '\x1f'  },
-    { "reverse",    '\x16'  }
-};
-
-const std::unordered_map<std::string, unsigned> shell_colors{
-    { "black",      30  },
-    { "red",        31  },
-    { "green",      32  },
-    { "orange",     33  },
-    { "blue",       34  },
-    { "purple",     35  },
-    { "cyan",       36  },
-    { "white",      37  },
-    { "default",    39  },
-};
-
-const std::unordered_map<std::string, unsigned> shell_attributes{
-    { "bold",       1   },
-    { "dim",        2   },
-    { "underline",  4   },
-    { "blink",      5   },
-    { "reverse",    7   },
-    { "hidden",     8   }
-};
-
-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_irc_attrs(const std::string& content)
-{
-    auto list = split(content, ",");
-
-    // @{} means reset.
-    if (list.empty())
-        return std::string(1, irc_attributes.at("reset"));
-
-    std::ostringstream oss;
-
-    // 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 = irc_colors.find(foreground);
-        if (it != irc_colors.end())
-            oss << it->second;
-
-        // Background.
-        if (list.size() >= 2 && (it = irc_colors.find(list[1])) != irc_colors.end())
-            oss << "," << it->second;
-
-        // Attributes.
-        for (std::size_t i = 2; i < list.size(); ++i) {
-            auto attribute = irc_attributes.find(list[i]);
-
-            if (attribute != irc_attributes.end())
-                oss << attribute->second;
-        }
-    }
-
-    return oss.str();
-}
-
-std::string subst_shell_attrs(const std::string& content)
-{
-#if !BOOST_OS_WINDOWS
-    auto list = split(content, ",");
-
-    if (list.empty())
-        return "\033[0m";
-    if (list.size() > 3)
-        return "";
-
-    std::vector<std::string> seq;
-
-    /*
-     * Shell sequence looks like this:
-     *
-     * ^[[attributes;foreground;backgroundm
-     */
-    if (list.size() >= 3) {
-        const auto it = shell_attributes.find(list[2]);
-
-        if (it != shell_attributes.end())
-            seq.push_back(std::to_string(it->second));
-        else
-            return "";
-    }
-    if (list.size() >= 1) {
-        const auto it = shell_colors.find(list[0]);
-
-        if (it != shell_colors.end())
-            seq.push_back(std::to_string(it->second));
-        else
-            return "";
-    }
-    if (list.size() >= 2) {
-        const auto it = shell_colors.find(list[1]);
-
-        if (it != shell_colors.end())
-            seq.push_back(std::to_string(it->second + 10));
-        else
-            return "";
-    }
-
-    std::ostringstream oss;
-
-    oss << "\033[";
-    oss << string_util::join(seq, ';');
-    oss << "m";
-
-    return oss.str();
-#else
-    return "";
-#endif
-}
-
-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_irc_attrs(content);
-        else if ((params.flags & subst_flags::shell_attrs) == subst_flags::shell_attrs)
-            value = subst_shell_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();
-}
-
-// }}}
-
-// {{{ strip
-
-std::string strip(std::string str) noexcept
-{
-    const auto test = [] (auto 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;
-}
-
-// }}}
-
-// {{{ split
-
-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;
-}
-
-// }}}
-
-// {{{ is_boolean
-
-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";
-}
-
-// }}}
-
-} // !string_util
-
-} // !util
--- a/libcommon/irccd/string_util.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,411 +0,0 @@
-/*
- * string_util.hpp -- string utilities
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_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 <stdexcept>
-#include <string>
-#include <type_traits>
-#include <unordered_map>
-
-#include <boost/format.hpp>
-#include <boost/optional.hpp>
-
-namespace irccd {
-
-/**
- * \file string_util.hpp
- * \brief String utilities.
- */
-namespace string_util {
-
-// {{{ subst
-
-/**
- * \brief Disable or enable some features.
- */
-enum class subst_flags : unsigned {
-    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
-    shell_attrs = (1 << 5)      //!< shell attributes
-};
-
-/**
- * \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
- *   or shell 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.
- */
-std::string format(std::string text, const subst& params = {});
-
-// }}}
-
-// {{{ strip
-
-/**
- * Remove leading and trailing spaces.
- *
- * \param str the string
- * \return the removed white spaces
- */
-std::string strip(std::string str) noexcept;
-
-// }}}
-
-// {{{ split
-
-/**
- * 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
- */
-std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max = -1);
-
-// }}}
-
-// {{{ join
-
-/**
- * Join values by a separator and return a string.
- *
- * \param first the first iterator
- * \param last the last iterator
- * \param delim the optional delimiter
- * \return the string
- */
-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();
-}
-
-/**
- * Overloaded function that takes a container.
- *
- * \param c the container
- * \param delim the optional delimiter
- * \return the string
- */
-template <typename Container, typename DelimType = char>
-std::string join(const Container& c, DelimType delim = ':')
-{
-    return join(c.begin(), c.end(), delim);
-}
-
-/**
- * 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);
-}
-
-// }}}
-
-// {{{ is_identifier
-
-/**
- * Check if a string is a valid irccd identifier.
- *
- * \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-_]+"));
-}
-
-// }}}
-
-// {{{ is_boolean
-
-/**
- * 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
- */
-bool is_boolean(std::string value) noexcept;
-
-// }}}
-
-// {{{ sprintf
-
-/**
- * \cond HIDDEN_SYMBOLS
- */
-
-namespace detail {
-
-inline void sprintf(boost::format&)
-{
-}
-
-template <typename Arg, typename... Args>
-inline 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();
-}
-
-// }}}
-
-// {{{ to_int
-
-/**
- * Convert the given string into a signed integer.
- *
- * \param str the string to convert
- * \param min the minimum value allowed
- * \param max the maximum value allowed
- * \return the value or boost::none if not convertible
- */
-template <typename T = int>
-boost::optional<T> to_int(const std::string& str,
-                          T min = std::numeric_limits<T>::min(),
-                          T max = std::numeric_limits<T>::max()) noexcept
-{
-    static_assert(std::is_signed<T>::value, "must be signed");
-
-    char* end;
-    auto v = std::strtoll(str.c_str(), &end, 10);
-
-    if (*end != '\0' || v < min || v > max)
-        return boost::none;
-
-    return static_cast<T>(v);
-}
-
-// }}}
-
-// {{{ to_uint
-
-/**
- * Convert the given string into a unsigned integer.
- *
- * \note invalid numbers are valid as well
- * \param str the string to convert
- * \param min the minimum value allowed
- * \param max the maximum value allowed
- * \return the value or boost::none if not convertible
- */
-template <typename T = unsigned>
-boost::optional<T> to_uint(const std::string& str,
-                           T min = std::numeric_limits<T>::min(),
-                           T max = std::numeric_limits<T>::max()) noexcept
-{
-    static_assert(std::is_unsigned<T>::value, "must be unsigned");
-
-    char* end;
-    auto v = std::strtoull(str.c_str(), &end, 10);
-
-    if (*end != '\0' || v < min || v > max)
-        return boost::none;
-
-    return static_cast<T>(v);
-}
-
-// }}}
-
-} // !string_util
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_STRING_UTIL_HPP
--- a/libcommon/irccd/system.cpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,502 +0,0 @@
-/*
- * system.cpp -- platform dependent functions for system inspection
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-#include <cerrno>
-#include <cstdlib>
-#include <cstring>
-#include <ctime>
-#include <stdexcept>
-#include <string>
-
-#include <boost/dll.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/predef/os.h>
-
-#include "sysconfig.hpp"
-
-#if BOOST_OS_WINDOWS
-#   include <sys/timeb.h>
-#   include <shlobj.h>
-#else
-#   include <sys/utsname.h>
-#   include <sys/types.h>
-#   include <sys/param.h>
-#   include <sys/time.h>
-#   include <unistd.h>
-#endif
-
-#if BOOST_OS_LINUX
-#   include <sys/sysinfo.h>
-#endif
-
-#if BOOST_OS_MACOS
-#   include <sys/sysctl.h>
-#   include <libproc.h>
-#endif
-
-#if defined(HAVE_GETLOGIN)
-#   include <unistd.h>
-#endif
-
-#include "system.hpp"
-#include "string_util.hpp"
-#include "xdg.hpp"
-
-namespace irccd {
-
-namespace sys {
-
-namespace {
-
-// {{{ base_directory
-
-/*
- * base_directory
- * ------------------------------------------------------------------
- *
- * Get the base program directory.
- *
- * If irccd has been compiled with relative paths, the base directory is
- * evaluated by climbing the `bindir' directory from the executable path.
- *
- * Otherwise, use the installation prefix.
- */
-boost::filesystem::path base_directory()
-{
-    static const boost::filesystem::path bindir(CMAKE_INSTALL_BINDIR);
-    static const boost::filesystem::path prefix(CMAKE_INSTALL_PREFIX);
-
-    boost::filesystem::path path(".");
-
-    if (bindir.is_relative()) {
-        try {
-            path = boost::dll::program_location();
-            path = path.parent_path();
-        } catch (...) {
-            path = ".";
-        }
-
-        // Compute relative base directory.
-        for (auto len = std::distance(bindir.begin(), bindir.end()); len > 0; len--)
-            path = path.parent_path();
-        if (path.empty())
-            path = ".";
-    } else
-        path = prefix;
-
-    return path;
-}
-
-// }}}
-
-// {{{ system_directory
-
-/*
- * system_directory
- * ------------------------------------------------------------------
- *
- * Compute the system directory path for the given component.
- *
- * Referenced by:
- *   - cachedir,
- *   - datadir,
- *   - sysconfigdir,
- *   - plugindir.
- */
-boost::filesystem::path system_directory(const std::string& component)
-{
-    boost::filesystem::path path(component);
-
-    if (path.is_relative())
-        path = base_directory() / component;
-
-    return path.string();
-}
-
-// }}}
-
-// {{{ user_config_directory
-
-/*
- * user_config_directory
- * ------------------------------------------------------------------
- *
- * Get user configuration directory.
- *
- * Referenced by:   config_filenames.
- * Requires:
- *   - Windows:
- *     - <shlobj.h>
- */
-boost::filesystem::path user_config_directory()
-{
-    boost::filesystem::path path;
-
-#if BOOST_OS_WINDOWS
-    char folder[MAX_PATH] = {0};
-
-    if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK) {
-        path /= folder;
-        path /= "\\irccd\\config";
-    } else
-        path = ".";
-#else
-    try {
-        path = xdg().config_home();
-    } catch (...) {
-        path = sys::env("HOME");
-        path /= ".config";
-    }
-
-    path /= "irccd";
-#endif
-
-    return path;
-}
-
-// }}}
-
-// {{{ user_plugin_directory
-
-/*
- * user_plugin_directory
- * ------------------------------------------------------------------
- *
- * Referenced by:   plugin_filenames.
- * Requires:
- *   - Windows:
- *     - <shlobj.h>
- *
- * Like add user_config_directory but for plugins.
- */
-boost::filesystem::path user_plugin_directory()
-{
-    boost::filesystem::path path;
-
-#if BOOST_OS_WINDOWS
-    char folder[MAX_PATH] = {0};
-
-    if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK) {
-        path /= folder;
-        path /= "\\irccd\\share";
-    }
-#else
-    try {
-        path = xdg().data_home();
-    } catch (...) {
-        path = sys::env("HOME");
-        path /= ".local/share";
-    }
-
-    path /= "irccd";
-#endif
-
-    return path / "plugins";
-}
-
-// }}}
-
-} // !namespace
-
-// {{{ set_program_name
-
-void set_program_name(std::string name) noexcept
-{
-#if defined(HAVE_SETPROGNAME)
-    static std::string save = name;
-
-    setprogname(save.c_str());
-#else
-    (void)name;
-#endif
-}
-
-// }}}
-
-// {{{ name
-
-std::string name()
-{
-#if BOOST_OS_LINUX
-    return "Linux";
-#elif BOOST_OS_WINDOWS
-    return "Windows";
-#elif BOOST_OS_BSD_FREE
-    return "FreeBSD";
-#elif BOOST_OS_BSD_DRAGONFLY
-    return "DragonFlyBSD";
-#elif BOOST_OS_BSD_OPEN
-    return "OpenBSD";
-#elif BOOST_OS_BSD_NET
-    return "NetBSD";
-#elif BOOST_OS_MACOS
-    return "macOS";
-#elif BOOST_OS_ANDROID
-    return "Android";
-#elif BOOST_OS_AIX
-    return "Aix";
-#elif BOOST_OS_HAIKU
-    return "Haiku";
-#elif BOOST_OS_IOS
-    return "iOS";
-#elif BOOST_OS_SOLARIS
-    return "Solaris";
-#else
-    return "Unknown";
-#endif
-}
-
-// }}}
-
-// {{{ version
-
-/*
- * Requires:
- *   - Windows:
- *     - <windows.h>
- *   - Others:
- *     - <sys/utsname.h>
- */
-std::string version()
-{
-#if BOOST_OS_WINDOWS
-    const auto version = GetVersion();
-    const auto major = (DWORD)(LOBYTE(LOWORD(version)));
-    const auto minor = (DWORD)(HIBYTE(LOWORD(version)));
-
-    return std::to_string(major) + "." + std::to_string(minor);
-#else
-    struct utsname uts;
-
-    if (::uname(&uts) < 0)
-        throw std::runtime_error(std::strerror(errno));
-
-    return std::string(uts.release);
-#endif
-}
-
-// }}}
-
-// {{{ uptime
-
-/*
- * Requires:
- *   - Windows:
- *     - <windows.h>
- *   - Linux:
- *     - <sys/sysinfo.h>
- *   - Mac:
- *     - <sys/types.h>
- *     - <sys/sysctl.h>
- *   - Others:
- *     - <ctime>
- */
-std::uint64_t uptime()
-{
-#if BOOST_OS_WINDOWS
-    return ::GetTickCount64() / 1000;
-#elif BOOST_OS_LINUX
-    struct sysinfo info;
-
-    if (sysinfo(&info) < 0)
-        throw std::runtime_error(std::strerror(errno));
-
-    return info.uptime;
-#elif BOOST_OS_MACOS
-    struct timeval boottime;
-    size_t length = sizeof (boottime);
-    int mib[2] = { CTL_KERN, KERN_BOOTTIME };
-
-    if (sysctl(mib, 2, &boottime, &length, nullptr, 0) < 0)
-        throw std::runtime_error(std::strerror(errno));
-
-    time_t bsec = boottime.tv_sec, csec = time(nullptr);
-
-    return difftime(csec, bsec);
-#else
-    struct timespec ts;
-
-    if (clock_gettime(CLOCK_UPTIME, &ts) < 0)
-        throw std::runtime_error(std::strerror(errno));
-
-    return ts.tv_sec;
-#endif
-}
-
-// }}}
-
-// {{{ ticks
-
-/*
- * Requires:
- *   - Windows:
- *     - <sys/timeb.h>
- *   - Others:
- *     - <sys/times.h>
- */
-std::uint64_t ticks()
-{
-#if BOOST_OS_WINDOWS
-    _timeb tp;
-
-    _ftime(&tp);
-
-    return tp.time * 1000LL + tp.millitm;
-#else
-    struct timeval tp;
-
-    gettimeofday(&tp, NULL);
-
-    return tp.tv_sec * 1000LL + tp.tv_usec / 1000;
-#endif
-}
-
-// }}}
-
-// {{{ home
-
-/*
- * Requires:
- *   - Windows:
- *     - <shlobj.h>
- */
-std::string home()
-{
-#if BOOST_OS_WINDOWS
-    char path[MAX_PATH];
-
-    if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path) != S_OK)
-        return "";
-
-    return std::string(path);
-#else
-    return env("HOME");
-#endif
-}
-
-// }}}
-
-// {{{ env
-
-/*
- * Requires:
- *   - <cstdlib>
- */
-std::string env(const std::string& var)
-{
-    const auto value = std::getenv(var.c_str());
-
-    if (value == nullptr)
-        return "";
-
-    return value;
-}
-
-// }}}
-
-// {{{ cachedir
-
-boost::filesystem::path cachedir()
-{
-    return system_directory(CMAKE_INSTALL_LOCALSTATEDIR) / "cache/irccd";
-}
-
-// }}}
-
-// {{{ datadir
-
-boost::filesystem::path datadir()
-{
-    return system_directory(CMAKE_INSTALL_DATADIR);
-}
-
-// }}}
-
-// {{{ sysconfdir
-
-boost::filesystem::path sysconfdir()
-{
-    return system_directory(CMAKE_INSTALL_SYSCONFDIR) / "irccd";
-}
-
-// }}}
-
-// {{{ plugindir
-
-boost::filesystem::path plugindir()
-{
-    return system_directory(CMAKE_INSTALL_LIBDIR) / "irccd";
-}
-
-// }}}
-
-// {{{ username
-
-/*
- * Requires:
- *   - <unistd.h>
- */
-std::string username()
-{
-#if defined(HAVE_GETLOGIN)
-    auto v = getlogin();
-
-    if (v)
-        return v;
-#endif
-
-    return "";
-}
-
-// }}}
-
-// {{{ config_filenames
-
-std::vector<std::string> config_filenames(std::string file)
-{
-    return {
-        (user_config_directory() / file).string(),
-        (sysconfdir() / file).string()
-    };
-}
-
-// }}}
-
-// {{{ plugin_filenames
-
-std::vector<std::string> plugin_filenames(const std::string& name,
-                                          const std::vector<std::string>& extensions)
-{
-    assert(!extensions.empty());
-
-    std::vector<std::string> result;
-
-    for (const auto& ext : extensions)
-        result.push_back((user_plugin_directory() / (name + ext)).string());
-    for (const auto& ext : extensions)
-        result.push_back((plugindir() / (name + ext)).string());
-
-    return result;
-}
-
-// }}}
-
-} // !sys
-
-} // !irccd
--- a/libcommon/irccd/system.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-/*
- * system.hpp -- platform dependent functions for system inspection
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_SYSTEM_HPP
-#define IRCCD_COMMON_SYSTEM_HPP
-
-/**
- * \file system.hpp
- * \brief System dependant functions
- */
-
-#include <cstdint>
-#include <string>
-#include <vector>
-
-#include <boost/filesystem.hpp>
-
-#include "sysconfig.hpp"
-
-namespace irccd {
-
-/**
- * \brief Namespace for system functions.
- */
-namespace sys {
-
-/**
- * Set the program name, needed for some functions or some systems.
- *
- * \param name the program name
- */
-void set_program_name(std::string name) noexcept;
-
-/**
- * Get the system name.
- *
- * \return the name
- */
-std::string name();
-
-/**
- * Get the system version.
- *
- * \return the version
- */
-std::string version();
-
-/**
- * Get the number of seconds elapsed since the boottime.
- *
- * \return the number of seconds
- */
-std::uint64_t uptime();
-
-/**
- * Get the milliseconds elapsed since the application
- * startup.
- *
- * \return the milliseconds
- */
-std::uint64_t ticks();
-
-/**
- * Get an environment variable.
- *
- * \return the value or empty string
- */
-std::string env(const std::string& var);
-
-/**
- * Get home directory usually /home/foo
- *
- * \return the home directory
- */
-std::string home();
-
-/**
- * Get the cache directory as specified as compile time option
- * CMAKE_INSTALL_LOCALSTATEDIR, if the value is absolute, it is returned as-is.
- *
- * If the component is relative, it is evaluated using the binary executable
- * path.
- *
- * \return the evaluated cache directory.
- * \see datadir
- * \see configdir
- */
-boost::filesystem::path cachedir();
-
-/**
- * Like cachedir but for CMAKE_INSTALL_DATADIR.
- *
- * \return the evaluated data directory.
- * \see cachedir
- * \see datadir
- */
-boost::filesystem::path datadir();
-
-/**
- * Like cachedir but for CMAKE_INSTALL_SYSCONFDIR.
- *
- * \return the evaluated config directory.
- * \see cachedir
- * \see datadir
- * \note use config_filenames for irccd.conf, irccdctl.conf files
- */
- boost::filesystem::path sysconfdir();
-
-/**
- * Like cachedir but for CMAKE_INSTALL_LIBDIR.
- *
- * \return the evaluated system plugin directory.
- * \see cachedir
- * \see datadir
- */
-boost::filesystem::path plugindir();
-
-/**
- * Get user account login or empty if not available.
- *
- * \return the user account name
- */
-std::string username();
-
-/**
- * Construct a list of paths to read configuration files from.
- *
- * This function does not test the presence of the files as a condition race
- * may occur.
- *
- * The caller is responsible of opening files for each path.
- *
- * \param file the filename to append for convenience
- * \return the list of paths to check in order
- */
-std::vector<std::string> config_filenames(std::string file);
-
-/**
- * Construct a list of paths for reading plugins.
- *
- * \param name the plugin id (without extension)
- * \param extensions the list of extensions supported
- */
-std::vector<std::string> plugin_filenames(const std::string& name,
-                                          const std::vector<std::string>& extensions);
-
-} // !sys
-
-} // !irccd
-
-#endif // !IRCCD_COMMON_SYSTEM_HPP
--- a/libcommon/irccd/tls_acceptor.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/*
- * tls_acceptor.hpp -- TLS/SSL acceptors
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_TLS_ACCEPTOR_HPP
-#define IRCCD_COMMON_TLS_ACCEPTOR_HPP
-
-/**
- * \file tls_acceptor.hpp
- * \brief TLS/SSL acceptors.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#if defined(IRCCD_HAVE_SSL)
-
-#include "socket_acceptor.hpp"
-#include "tls_stream.hpp"
-
-namespace irccd {
-
-namespace io {
-
-/**
- * \brief TLS/SSL acceptors.
- * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
- */
-template <typename Protocol = boost::asio::ip::tcp>
-class tls_acceptor : public socket_acceptor<Protocol> {
-private:
-    using socket = typename Protocol::socket;
-
-    boost::asio::ssl::context context_;
-
-public:
-    /**
-     * Construct a secure layer transport server.
-     *
-     * \param context the SSL context
-     * \param args the socket_acceptor arguments
-     */
-    template <typename... Args>
-    inline tls_acceptor(boost::asio::ssl::context context, Args&&... args)
-        : socket_acceptor<Protocol>(std::forward<Args>(args)...)
-        , context_(std::move(context))
-    {
-    }
-
-    /**
-     * \copydoc acceptor::accept
-     */
-    void accept(accept_handler handler) override;
-};
-
-template <typename Protocol>
-void tls_acceptor<Protocol>::accept(accept_handler handler)
-{
-    assert(handler);
-
-    auto client = std::make_shared<tls_stream<socket>>(this->get_acceptor().get_io_service(), this->context_);
-
-    socket_acceptor<Protocol>::do_accept(client->get_socket().lowest_layer(), [handler, client] (auto code) {
-        using boost::asio::ssl::stream_base;
-
-        if (code) {
-            handler(code, nullptr);
-            return;
-        }
-
-        client->get_socket().async_handshake(stream_base::server, [handler, client] (auto code) {
-            handler(detail::convert(code), code ? nullptr : std::move(client));
-        });
-    });
-}
-
-} // !io
-
-} // !irccd
-
-#endif // !IRCCD_HAVE_SSL
-
-#endif // !IRCCD_COMMON_TLS_ACCEPTOR_HPP
--- a/libcommon/irccd/tls_connector.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-/*
- * tls_connector.hpp -- TLS/SSL connectors
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_TLS_CONNECTOR_HPP
-#define IRCCD_COMMON_TLS_CONNECTOR_HPP
-
-/**
- * \file tls_connector.hpp
- * \brief TLS/SSL connectors.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#if defined(IRCCD_HAVE_SSL)
-
-#include "socket_connector.hpp"
-#include "tls_stream.hpp"
-
-namespace irccd {
-
-namespace io {
-
-/**
- * \brief TLS/SSL connectors.
- * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
- */
-template <typename Protocol = boost::asio::ip::tcp>
-class tls_connector : public socket_connector<Protocol> {
-private:
-    boost::asio::ssl::context context_;
-
-public:
-    /**
-     * Construct a secure layer transport server.
-     *
-     * \param context the SSL context
-     * \param args the arguments to socket_connector<Socket> constructor
-     */
-    template <typename... Args>
-    inline tls_connector(boost::asio::ssl::context context, Args&&... args)
-        : socket_connector<Protocol>(std::forward<Args>(args)...)
-        , context_(std::move(context))
-    {
-    }
-
-    /**
-     * \copydoc socket_connector::connect
-     */
-    void connect(connect_handler handler) override;
-};
-
-template <typename Protocol>
-void tls_connector<Protocol>::connect(connect_handler handler)
-{
-    using boost::asio::ssl::stream_base;
-    using socket = typename Protocol::socket;
-
-    assert(handler);
-
-    const auto stream = std::make_shared<tls_stream<socket>>(this->get_io_service(), context_);
-
-    socket_connector<Protocol>::do_connect(stream->get_socket().lowest_layer(), [this, handler, stream] (auto code) {
-        if (code) {
-            handler(code, nullptr);
-            return;
-        }
-
-        stream->get_socket().async_handshake(stream_base::client, [handler, stream] (auto code) {
-            handler(detail::convert(code), code ? nullptr : std::move(stream));
-        });
-    });
-}
-
-} // !io
-
-} // !irccd
-
-#endif // !IRCCD_HAVE_SSL
-
-#endif // !IRCCD_COMMON_TLS_CONNECTOR_HPP
--- a/libcommon/irccd/tls_stream.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * tls_stream.hpp -- TLS/SSL streams
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMON_TLS_STREAM_HPP
-#define IRCCD_COMMON_TLS_STREAM_HPP
-
-/**
- * \file tls_stream.hpp
- * \brief TLS/SSL streams.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#if defined(IRCCD_HAVE_SSL)
-
-#include <boost/asio/ssl.hpp>
-
-#include "socket_stream.hpp"
-
-namespace irccd {
-
-namespace io {
-
-/**
- * \brief TLS/SSL streams.
- * \tparam Socket the Boost.Asio compatible socket.
- */
-template <typename Socket = boost::asio::ip::tcp::socket>
-class tls_stream : public socket_stream<boost::asio::ssl::stream<Socket>> {
-public:
-    /**
-     * Constructor.
-     *
-     * \param args the arguments to boost::asio::ssl::stream<Socket>
-     */
-    template <typename... Args>
-    inline tls_stream(Args&&... args)
-        : socket_stream<boost::asio::ssl::stream<Socket>>(std::forward<Args>(args)...)
-    {
-    }
-};
-
-} // !io
-
-} // !irccd
-
-#endif // !IRCCD_HAVE_SSL
-
-#endif // !IRCCD_COMMON_TLS_STREAM_HPP
--- a/libcommon/irccd/xdg.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/*
- * xdg.hpp -- XDG directory specifications
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_XDG_HPP
-#define IRCCD_XDG_HPP
-
-/**
- * \file xdg.hpp
- * \brief XDG directory specifications.
- * \author David Demelier <markand@malikana.fr>
- */
-
-#include <cstdlib>
-#include <sstream>
-#include <stdexcept>
-#include <string>
-#include <vector>
-
-namespace irccd {
-
-/**
- * \brief XDG directory specifications.
- *
- * Read and get XDG directories.
- *
- * This file should compiles on Windows to facilitate portability but its
- * functions must not be used.
- */
-class xdg {
-private:
-    std::string config_home_;
-    std::string data_home_;
-    std::string cache_home_;
-    std::string runtime_dir_;
-    std::vector<std::string> config_dirs_;
-    std::vector<std::string> data_dirs_;
-
-    inline bool is_absolute(const std::string& path) const noexcept
-    {
-        return path.length() > 0 && path[0] == '/';
-    }
-
-    std::vector<std::string> split(const std::string& arg) const
-    {
-        std::stringstream iss(arg);
-        std::string item;
-        std::vector<std::string> elems;
-
-        while (std::getline(iss, item, ':')) {
-            if (is_absolute(item))
-                elems.push_back(item);
-        }
-
-        return elems;
-    }
-
-    std::string env_or_home(const std::string& var, const std::string& repl) const
-    {
-        auto value = std::getenv(var.c_str());
-
-        if (value == nullptr || !is_absolute(value)) {
-            auto home = std::getenv("HOME");
-
-            if (home == nullptr)
-                throw std::runtime_error("could not get home directory");
-
-            return std::string(home) + "/" + repl;
-        }
-
-        return value;
-    }
-
-    std::vector<std::string> list_or_defaults(const std::string& var,
-                                              const std::vector<std::string>& list) const
-    {
-        auto value = std::getenv(var.c_str());
-
-        if (!value)
-            return list;
-
-        // No valid item at all? Use defaults.
-        auto result = split(value);
-
-        return (result.size() == 0) ? list : result;
-    }
-
-public:
-    /**
-     * Open an xdg instance and load directories.
-     *
-     * \throw std::runtime_error on failures
-     */
-    xdg()
-    {
-        config_home_    = env_or_home("XDG_CONFIG_HOME", ".config");
-        data_home_      = env_or_home("XDG_DATA_HOME", ".local/share");
-        cache_home_     = env_or_home("XDG_CACHE_HOME", ".cache");
-
-        config_dirs_    = list_or_defaults("XDG_CONFIG_DIRS", { "/etc/xdg" });
-        data_dirs_      = list_or_defaults("XDG_DATA_DIRS", { "/usr/local/share", "/usr/share" });
-
-        /*
-         * Runtime directory is a special case and does not have a replacement,
-         * the application should manage this by itself.
-         */
-        auto runtime = std::getenv("XDG_RUNTIME_DIR");
-
-        if (runtime && is_absolute(runtime))
-            runtime_dir_ = runtime;
-    }
-
-    /**
-     * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config
-     *
-     * \return the config directory
-     */
-    inline const std::string& config_home() const noexcept
-    {
-        return config_home_;
-    }
-
-    /**
-     * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share
-     *
-     * \return the data directory
-     */
-    inline const std::string& data_home() const noexcept
-    {
-        return data_home_;
-    }
-
-    /**
-     * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache
-     *
-     * \return the cache directory
-     */
-    inline const std::string& cache_home() const noexcept
-    {
-        return cache_home_;
-    }
-
-    /**
-     * Get the runtime directory.
-     *
-     * There is no replacement for XDG_RUNTIME_DIR, if it is not set, an empty
-     * value is returned and the user is responsible of using something else.
-     *
-     * \return the runtime directory
-     */
-    inline const std::string& runtime_dir() const noexcept
-    {
-        return runtime_dir_;
-    }
-
-    /**
-     * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" }
-     *
-     * \return the list of config directories
-     */
-    inline const std::vector<std::string>& config_dirs() const noexcept
-    {
-        return config_dirs_;
-    }
-
-    /**
-     * Get the data directories. ${XDG_DATA_DIRS} or { "/usr/local/share",
-     * "/usr/share" }
-     *
-     * \return the list of data directories
-     */
-    inline const std::vector<std::string>& data_dirs() const noexcept
-    {
-        return data_dirs_;
-    }
-};
-
-} // !irccd
-
-#endif // !IRCCD_XDG_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,79 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(libirccd-core)
+
+find_package(Boost 1.60 REQUIRED QUIET COMPONENTS filesystem system)
+
+set(
+    HEADERS
+    ${libirccd-core_SOURCE_DIR}/irccd/acceptor.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/config.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/connector.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/fs_util.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/ini.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/ini_util.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/json_util.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/options.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/socket_acceptor.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/socket_connector.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/socket_stream.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/stream.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/string_util.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/system.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/tls_acceptor.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/tls_connector.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/tls_stream.hpp
+    ${libirccd-core_SOURCE_DIR}/irccd/xdg.hpp
+)
+
+set(
+    SOURCES
+    ${libirccd-core_SOURCE_DIR}/irccd/config.cpp
+    ${libirccd-core_SOURCE_DIR}/irccd/ini.cpp
+    ${libirccd-core_SOURCE_DIR}/irccd/options.cpp
+    ${libirccd-core_SOURCE_DIR}/irccd/string_util.cpp
+    ${libirccd-core_SOURCE_DIR}/irccd/system.cpp
+)
+
+irccd_define_library(
+    TARGET libirccd-core
+    EXPORT
+    HEADERS ${HEADERS}
+    HEADERS_DIRECTORY irccd
+    SOURCES
+        ${libirccd-core_SOURCE_DIR}/CMakeLists.txt
+        ${SOURCES}
+    LIBRARIES
+        libjson
+        Threads::Threads
+        Boost::filesystem
+        Boost::system
+        $<$<BOOL:${IRCCD_HAVE_SSL}>:OpenSSL::Crypto>
+        $<$<BOOL:${IRCCD_HAVE_SSL}>:OpenSSL::SSL>
+        $<$<BOOL:${APPLE}>:resolv>
+        $<$<BOOL:${WIN32}>:mswsock>
+        $<$<BOOL:${WIN32}>:shlwapi>
+        $<$<BOOL:${WIN32}>:ws2_32>
+        $<$<STREQUAL:${CMAKE_SYSTEM_NAME},Linux>:dl>
+    PUBLIC_INCLUDES
+        $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
+        $<BUILD_INTERFACE:${libirccd-core_SOURCE_DIR}>
+        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern>
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/acceptor.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,77 @@
+/*
+ * acceptor.hpp -- abstract stream acceptor interface
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_ACCEPTOR_HPP
+#define IRCCD_COMMON_ACCEPTOR_HPP
+
+/**
+ * \file acceptor.hpp
+ * \brief Abstract stream acceptor interface.
+ */
+
+#include <functional>
+#include <memory>
+#include <system_error>
+
+namespace irccd {
+
+namespace io {
+
+class stream;
+
+/**
+ * \brief Accept completion handler.
+ */
+using accept_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>;
+
+/**
+ * \brief Abstract stream acceptor interface.
+ *
+ * This class is used to wait a new client in an asynchronous manner. Derived
+ * classes must implement a non-blocking accept function.
+ */
+class acceptor {
+public:
+    /**
+     * Default constructor.
+     */
+    acceptor() = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~acceptor() = default;
+
+    /**
+     * Start asynchronous accept.
+     *
+     * Once the client is accepted, the original acceptor must be kept until it
+     * is destroyed.
+     *
+     * \pre another accept operation must not be running
+     * \pre handler != nullptr
+     * \param handler the handler
+     */
+    virtual void accept(accept_handler handler) = 0;
+};
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_ACCEPTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/config.cpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,39 @@
+/*
+ * config.cpp -- irccd configuration loader
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <boost/filesystem.hpp>
+
+#include <irccd/system.hpp>
+
+#include "config.hpp"
+
+namespace irccd {
+
+boost::optional<config> config::search(const std::string& name)
+{
+    for (const auto& path : sys::config_filenames(name)) {
+        boost::system::error_code ec;
+
+        if (boost::filesystem::exists(path, ec) && !ec)
+            return config(path);
+    }
+
+    return boost::optional<config>();
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/config.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,73 @@
+/*
+ * config.hpp -- irccd configuration loader
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_CONFIG_HPP
+#define IRCCD_COMMON_CONFIG_HPP
+
+/**
+ * \file config.hpp
+ * \brief Read .ini configuration file for irccd
+ */
+
+#include <boost/optional.hpp>
+
+#include "ini.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Read .ini configuration file for irccd
+ */
+class config : public ini::document {
+private:
+    std::string path_;
+
+public:
+    /**
+     * Search the configuration file into the standard defined paths.
+     *
+     * \param name the file name
+     * \return the config or empty if not found
+     */
+    static boost::optional<config> search(const std::string& name);
+
+    /**
+     * Load the configuration from the specified path.
+     *
+     * \param path the path
+     */
+    inline config(std::string path = "")
+        : document(path.empty() ? ini::document() : ini::read_file(path))
+        , path_(std::move(path))
+    {
+    }
+
+    /**
+     * Get the path to the configuration file.
+     *
+     * \return the path
+     */
+    inline const std::string& get_path() const noexcept
+    {
+        return path_;
+    }
+};
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_CONFIG_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/connector.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,79 @@
+/*
+ * connector.hpp -- abstract connection interface
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_CONNECTOR_HPP
+#define IRCCD_COMMON_CONNECTOR_HPP
+
+/**
+ * \file connector.hpp
+ * \brief Abstract connection interface.
+ */
+
+#include <functional>
+#include <memory>
+#include <system_error>
+
+namespace irccd {
+
+namespace io {
+
+class stream;
+
+/**
+ * \brief Connect completion handler.
+ */
+using connect_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>;
+
+/**
+ * \brief Abstract connection interface.
+ *
+ * This class is used to connect to a stream end point (usually sockets) in an
+ * asynchronous manner.
+ *
+ * Derived class must implement non-blocking connect function.
+ */
+class connector {
+public:
+    /**
+     * Default constructor.
+     */
+    connector() = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~connector() = default;
+
+    /**
+     * Start asynchronous connect.
+     *
+     * Once the client is connected, the original acceptor must be kept until it
+     * is destroyed.
+     *
+     * \pre another connect operation must not be running
+     * \pre handler != nullptr
+     * \param handler the handler
+     */
+    virtual void connect(connect_handler handler) = 0;
+};
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_CONNECTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/fs_util.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,149 @@
+/*
+ * fs_util.hpp -- filesystem utilities
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_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 {
+
+/**
+ * \brief Filesystem utilities.
+ */
+namespace fs_util {
+
+// {{{ base_name
+
+/**
+ * Get the base name from a path.
+ *
+ * Example, base_name("/etc/foo.conf") returns 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();
+}
+
+// }}}
+
+// {{{ dir_name
+
+/**
+ * Get the parent directory from a path.
+ *
+ * Example, dir_name("/etc/foo.conf") returns /etc
+ *
+ * \param path the path
+ * \return the parent directory
+ */
+inline std::string dir_name(const std::string& path)
+{
+    return boost::filesystem::path(path).parent_path().string();
+}
+
+// }}}
+
+// {{{ find_if
+
+/**
+ * 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 boost::system::system_error on errors
+ */
+template <typename Predicate>
+std::string find_if(const std::string& base, bool recursive, Predicate&& predicate)
+{
+    const auto find = [&] (auto it) -> std::string {
+        for (const auto& entry : it)
+            if (predicate(entry))
+                return entry.path().string();
+
+        return "";
+    };
+
+    return recursive
+        ? find(boost::filesystem::recursive_directory_iterator(base))
+        : find(boost::filesystem::directory_iterator(base));
+}
+
+// }}}
+
+// {{{ find
+
+/**
+ * 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 boost::system::system_error on 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 boost::system::system_error on 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/libirccd-core/irccd/ini.cpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,418 @@
+/*
+ * ini.cpp -- extended .ini file parser
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cctype>
+#include <cstring>
+#include <iostream>
+#include <iterator>
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+
+#include <irccd/sysconfig.hpp>
+
+#include <boost/predef.h>
+
+// for PathIsRelative.
+#if BOOST_OS_WINDOWS
+#  include <shlwapi.h>
+#endif
+
+#include "ini.hpp"
+
+namespace irccd {
+
+namespace ini {
+
+namespace {
+
+using stream_iterator = std::istreambuf_iterator<char>;
+using token_iterator = std::vector<token>::const_iterator;
+
+inline bool is_absolute(const std::string& path) noexcept
+{
+#if BOOST_OS_WINDOWS
+    return !PathIsRelative(path.c_str());
+#else
+    return path.size() > 0 && path[0] == '/';
+#endif
+}
+
+inline bool is_quote(char c) noexcept
+{
+    return c == '\'' || c == '"';
+}
+
+inline bool is_space(char c) noexcept
+{
+    // Custom version because std::isspace includes \n as space.
+    return c == ' ' || c == '\t';
+}
+
+inline bool is_list(char c) noexcept
+{
+    return c == '(' || c == ')' || c == ',';
+}
+
+inline bool is_reserved(char c) noexcept
+{
+    return is_list(c) || is_quote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '=';
+}
+
+void analyse_line(int& line, int& column, stream_iterator& it) noexcept
+{
+    assert(*it == '\n');
+
+    ++ line;
+    ++ it;
+    column = 0;
+}
+
+void analyse_comment(int& column, stream_iterator& it, stream_iterator end) noexcept
+{
+    assert(*it == '#');
+
+    while (it != end && *it != '\n') {
+        ++ column;
+        ++ it;
+    }
+}
+
+void analyse_spaces(int& column, stream_iterator& it, stream_iterator end) noexcept
+{
+    assert(is_space(*it));
+
+    while (it != end && is_space(*it)) {
+        ++ column;
+        ++ it;
+    }
+}
+
+void analyse_list(tokens& list, int line, int& column, stream_iterator& it) noexcept
+{
+    assert(is_list(*it));
+
+    switch (*it++) {
+    case '(':
+        list.emplace_back(token::list_begin, line, column++);
+        break;
+    case ')':
+        list.emplace_back(token::list_end, line, column++);
+        break;
+    case ',':
+        list.emplace_back(token::comma, line, column++);
+        break;
+    default:
+        break;
+    }
+}
+
+void analyse_section(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end)
+{
+    assert(*it == '[');
+
+    std::string value;
+    int save = column;
+
+    // Read section name.
+    ++ it;
+    while (it != end && *it != ']') {
+        if (*it == '\n')
+            throw exception(line, column, "section not terminated, missing ']'");
+        if (is_reserved(*it))
+            throw exception(line, column, "section name expected after '[', got '" + std::string(1, *it) + "'");
+
+        ++ column;
+        value += *it++;
+    }
+
+    if (it == end)
+        throw exception(line, column, "section name expected after '[', got <EOF>");
+    if (value.empty())
+        throw exception(line, column, "empty section name");
+
+    // Remove ']'.
+    ++ it;
+
+    list.emplace_back(token::section, line, save, std::move(value));
+}
+
+void analyse_assign(tokens& list, int& line, int& column, stream_iterator& it)
+{
+    assert(*it == '=');
+
+    list.push_back({ token::assign, line, column++ });
+    ++ it;
+}
+
+void analyse_quoted_word(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end)
+{
+    std::string value;
+    int save = column;
+    char quote = *it++;
+
+    while (it != end && *it != quote) {
+        // TODO: escape sequence
+        ++ column;
+        value += *it++;
+    }
+
+    if (it == end)
+        throw exception(line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>");
+
+    // Remove quote.
+    ++ it;
+
+    list.push_back({ token::quoted_word, line, save, std::move(value) });
+}
+
+void analyse_word(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end)
+{
+    assert(!is_reserved(*it));
+
+    std::string value;
+    int save = column;
+
+    while (it != end && !std::isspace(*it) && !is_reserved(*it)) {
+        ++ column;
+        value += *it++;
+    }
+
+    list.push_back({ token::word, line, save, std::move(value) });
+}
+
+void analyse_include(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end)
+{
+    assert(*it == '@');
+
+    std::string include;
+    int save = column;
+
+    // Read include.
+    ++ it;
+    while (it != end && !is_space(*it)) {
+        ++ column;
+        include += *it++;
+    }
+
+    if (include != "include")
+        throw exception(line, column, "expected include after '@' token");
+
+    list.push_back({ token::include, line, save });
+}
+
+void parse_option_value_simple(option& option, token_iterator& it)
+{
+    assert(it->type() == token::word || it->type() == token::quoted_word);
+
+    option.push_back((it++)->value());
+}
+
+void parse_option_value_list(option& option, token_iterator& it, token_iterator end)
+{
+    assert(it->type() == token::list_begin);
+
+    token_iterator save = it++;
+
+    while (it != end && it->type() != token::list_end) {
+        switch (it->type()) {
+        case token::comma:
+            // Previous must be a word.
+            if (it[-1].type() != token::word && it[-1].type() != token::quoted_word)
+                throw exception(it->line(), it->column(), "unexpected comma after '" + it[-1].value() + "'");
+
+            ++ it;
+            break;
+        case token::word:
+        case token::quoted_word:
+            option.push_back((it++)->value());
+            break;
+        default:
+            throw exception(it->line(), it->column(), "unexpected '" + it[-1].value() + "' in list construct");
+            break;
+        }
+    }
+
+    if (it == end)
+        throw exception(save->line(), save->column(), "unterminated list construct");
+
+    // Remove ).
+    ++ it;
+}
+
+void parse_option(section& sc, token_iterator& it, token_iterator end)
+{
+    option option(it->value());
+    token_iterator save(it);
+
+    // No '=' or something else?
+    if (++it == end)
+        throw exception(save->line(), save->column(), "expected '=' assignment, got <EOF>");
+    if (it->type() != token::assign)
+        throw exception(it->line(), it->column(), "expected '=' assignment, got " + it->value());
+
+    // Empty options are allowed so just test for words.
+    if (++it != end) {
+        if (it->type() == token::word || it->type() == token::quoted_word)
+            parse_option_value_simple(option, it);
+        else if (it->type() == token::list_begin)
+            parse_option_value_list(option, it, end);
+    }
+
+    sc.push_back(std::move(option));
+}
+
+void parse_include(document& doc, const std::string& path, token_iterator& it, token_iterator end)
+{
+    token_iterator save(it);
+
+    if (++it == end)
+        throw exception(save->line(), save->column(), "expected file name after '@include' statement, got <EOF>");
+    if (it->type() != token::word && it->type() != token::quoted_word)
+        throw exception(it->line(), it->column(), "expected file name after '@include' statement, got " + it->value());
+
+    std::string value = (it++)->value();
+    std::string file;
+
+    if (!is_absolute(value)) {
+#if BOOST_OS_WINDOWS
+        file = path + "\\" + value;
+#else
+        file = path + "/" + value;
+#endif
+    } else
+        file = value;
+
+    for (const auto& sc : read_file(file))
+        doc.push_back(sc);
+}
+
+void parse_section(document& doc, token_iterator& it, token_iterator end)
+{
+    section sc(it->value());
+
+    // Skip [section].
+    ++ it;
+
+    // Read until next section.
+    while (it != end && it->type() != token::section) {
+        if (it->type() != token::word)
+            throw exception(it->line(), it->column(), "unexpected token '" + it->value() + "' in section definition");
+
+        parse_option(sc, it, end);
+    }
+
+    doc.push_back(std::move(sc));
+}
+
+} // !namespace
+
+tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end)
+{
+    tokens list;
+    int line = 1;
+    int column = 0;
+
+    while (it != end) {
+        if (*it == '\n')
+            analyse_line(line, column, it);
+        else if (*it == '#')
+            analyse_comment(column, it, end);
+        else if (*it == '[')
+            analyse_section(list, line, column, it, end);
+        else if (*it == '=')
+            analyse_assign(list, line, column, it);
+        else if (is_space(*it))
+            analyse_spaces(column, it, end);
+        else if (*it == '@')
+            analyse_include(list, line, column, it, end);
+        else if (is_quote(*it))
+            analyse_quoted_word(list, line, column, it, end);
+        else if (is_list(*it))
+            analyse_list(list, line, column, it);
+        else
+            analyse_word(list, line, column, it, end);
+    }
+
+    return list;
+}
+
+tokens analyse(std::istream& stream)
+{
+    return analyse(std::istreambuf_iterator<char>(stream), {});
+}
+
+document parse(const tokens& tokens, const std::string& path)
+{
+    document doc;
+    token_iterator it = tokens.cbegin();
+    token_iterator end = tokens.cend();
+
+    while (it != end) {
+        switch (it->type()) {
+        case token::include:
+            parse_include(doc, path, it, end);
+            break;
+        case token::section:
+            parse_section(doc, it, end);
+            break;
+        default:
+            throw exception(it->line(), it->column(), "unexpected '" + it->value() + "' on root document");
+        }
+    }
+
+    return doc;
+}
+
+document read_file(const std::string& filename)
+{
+    // Get parent path.
+    auto parent = filename;
+    auto pos = parent.find_last_of("/\\");
+
+    if (pos != std::string::npos)
+        parent.erase(pos);
+    else
+        parent = ".";
+
+    std::ifstream input(filename);
+
+    if (!input)
+        throw exception(0, 0, std::strerror(errno));
+
+    return parse(analyse(input), parent);
+}
+
+document read_string(const std::string& buffer)
+{
+    std::istringstream iss(buffer);
+
+    return parse(analyse(iss));
+}
+
+void dump(const tokens& tokens)
+{
+    for (const token& token: tokens) {
+        // TODO: add better description
+        std::cout << token.line() << ":" << token.column() << ": " << token.value() << std::endl;
+    }
+}
+
+} // !ini
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/ini.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,647 @@
+/*
+ * ini.hpp -- extended .ini file parser
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_INI_HPP
+#define IRCCD_COMMON_INI_HPP
+
+/**
+ * \file ini.hpp
+ * \brief Extended .ini file parser.
+ * \author David Demelier <markand@malikania.fr>
+ * \version 2.0.0
+ */
+
+/**
+ * \page Ini Ini
+ * \brief Extended .ini file parser.
+ *
+ *   - \subpage ini-syntax
+ */
+
+/**
+ * \page ini-syntax Syntax
+ * \brief File syntax.
+ *
+ * The syntax is similar to most of `.ini` implementations as:
+ *
+ *   - a section is delimited by `[name]` can be redefined multiple times,
+ *   - an option **must** always be defined in a section,
+ *   - empty options must be surrounded by quotes,
+ *   - lists can not include trailing commas,
+ *   - include statements must always live at the beginning of files
+ *     (in no sections),
+ *   - comments start with # until the end of line,
+ *   - options with spaces **must** use quotes.
+ *
+ * # Basic file
+ *
+ * ````ini
+ * # This is a comment.
+ * [section]
+ * option1 = value1
+ * option2 = "value 2 with spaces"    # comment is also allowed here
+ * ````
+ *
+ * # Redefinition
+ *
+ * Sections can be redefined multiple times and are kept the order they are
+ * seen.
+ *
+ * ````ini
+ * [section]
+ * value = "1"
+ *
+ * [section]
+ * value = "2"
+ * ````
+ *
+ * The ini::document object will contains two ini::section.
+ *
+ * # Lists
+ *
+ * Lists are defined using `()` and commas, like values, they may have quotes.
+ *
+ * ````ini
+ * [section]
+ * names = ( "x1", "x2" )
+ *
+ * # This is also allowed.
+ * biglist = (
+ *   "abc",
+ *   "def"
+ * )
+ * ````
+ *
+ * # Include statement
+ *
+ * You can split a file into several pieces, if the include statement contains a
+ * relative path, the path will be relative to the current file being parsed.
+ *
+ * You **must** use the include statement before any section.
+ *
+ * If the file contains spaces, use quotes.
+ *
+ * ````ini
+ * # main.conf
+ * @include "foo.conf"
+ *
+ * # foo.conf
+ * [section]
+ * option1 = value1
+ * ````
+ */
+
+#include "sysconfig.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <exception>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+namespace irccd {
+
+/**
+ * Namespace for ini related classes.
+ */
+namespace ini {
+
+class document;
+
+/**
+ * \brief exception in a file.
+ */
+class exception : public std::exception {
+private:
+    int line_;
+    int column_;
+    std::string message_;
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param line the line
+     * \param column the column
+     * \param msg the message
+     */
+    inline exception(int line, int column, std::string msg) noexcept
+        : line_(line)
+        , column_(column)
+        , message_(std::move(msg))
+    {
+    }
+
+    /**
+     * Get the line number.
+     *
+     * \return the line
+     */
+    inline int line() const noexcept
+    {
+        return line_;
+    }
+
+    /**
+     * Get the column number.
+     *
+     * \return the column
+     */
+    inline int column() const noexcept
+    {
+        return column_;
+    }
+
+    /**
+     * Return the raw exception message (no line and column shown).
+     *
+     * \return the exception message
+     */
+    const char* what() const noexcept override
+    {
+        return message_.c_str();
+    }
+};
+
+/**
+ * \brief Describe a token read in the .ini source.
+ *
+ * This class can be used when you want to parse a .ini file yourself.
+ *
+ * \see analyse
+ */
+class token {
+public:
+    /**
+     * \brief token type.
+     */
+    enum type {
+        include,                //!< include statement
+        section,                //!< [section]
+        word,                   //!< word without quotes
+        quoted_word,            //!< word with quotes
+        assign,                 //!< = assignment
+        list_begin,             //!< begin of list (
+        list_end,               //!< end of list )
+        comma                   //!< list separation
+    };
+
+private:
+    type type_;
+    int line_;
+    int column_;
+    std::string value_;
+
+public:
+    /**
+     * Construct a token.
+     *
+     * \param type the type
+     * \param line the line
+     * \param column the column
+     * \param value the value
+     */
+    token(type type, int line, int column, std::string value = "") noexcept
+        : type_(type)
+        , line_(line)
+        , column_(column)
+    {
+        switch (type) {
+        case include:
+            value_ = "@include";
+            break;
+        case section:
+        case word:
+        case quoted_word:
+            value_ = value;
+            break;
+        case assign:
+            value_ = "=";
+            break;
+        case list_begin:
+            value_ = "(";
+            break;
+        case list_end:
+            value_ = ")";
+            break;
+        case comma:
+            value_ = ",";
+            break;
+        default:
+            break;
+        }
+    }
+
+    /**
+     * Get the type.
+     *
+     * \return the type
+     */
+    inline type type() const noexcept
+    {
+        return type_;
+    }
+
+    /**
+     * Get the line.
+     *
+     * \return the line
+     */
+    inline int line() const noexcept
+    {
+        return line_;
+    }
+
+    /**
+     * Get the column.
+     *
+     * \return the column
+     */
+    inline int column() const noexcept
+    {
+        return column_;
+    }
+
+    /**
+     * Get the value. For words, quoted words and section, the value is the
+     * content. Otherwise it's the characters parsed.
+     *
+     * \return the value
+     */
+    inline const std::string& value() const noexcept
+    {
+        return value_;
+    }
+};
+
+/**
+ * List of tokens in order they are analyzed.
+ */
+using tokens = std::vector<token>;
+
+/**
+ * \brief option definition.
+ */
+class option : public std::vector<std::string> {
+private:
+    std::string key_;
+
+public:
+    /**
+     * Construct an empty option.
+     *
+     * \pre key must not be empty
+     * \param key the key
+     */
+    inline option(std::string key) noexcept
+        : std::vector<std::string>()
+        , key_(std::move(key))
+    {
+        assert(!key_.empty());
+    }
+
+    /**
+     * Construct a single option.
+     *
+     * \pre key must not be empty
+     * \param key the key
+     * \param value the value
+     */
+    inline option(std::string key, std::string value) noexcept
+        : key_(std::move(key))
+    {
+        assert(!key_.empty());
+
+        push_back(std::move(value));
+    }
+
+    /**
+     * Construct a list option.
+     *
+     * \pre key must not be empty
+     * \param key the key
+     * \param values the values
+     */
+    inline option(std::string key, std::vector<std::string> values) noexcept
+        : std::vector<std::string>(std::move(values))
+        , key_(std::move(key))
+    {
+        assert(!key_.empty());
+    }
+
+    /**
+     * Get the option key.
+     *
+     * \return the key
+     */
+    inline const std::string& key() const noexcept
+    {
+        return key_;
+    }
+
+    /**
+     * Get the option value.
+     *
+     * \return the value
+     */
+    inline const std::string& value() const noexcept
+    {
+        static std::string dummy;
+
+        return empty() ? dummy : (*this)[0];
+    }
+};
+
+/**
+ * \brief Section that contains one or more options.
+ */
+class section : public std::vector<option> {
+private:
+    std::string key_;
+
+public:
+    /**
+     * Construct a section with its name.
+     *
+     * \pre key must not be empty
+     * \param key the key
+     */
+    inline section(std::string key) noexcept
+        : key_(std::move(key))
+    {
+        assert(!key_.empty());
+    }
+
+    /**
+     * Get the section key.
+     *
+     * \return the key
+     */
+    inline const std::string& key() const noexcept
+    {
+        return key_;
+    }
+
+    /**
+     * Check if the section contains a specific option.
+     *
+     * \param key the option key
+     * \return true if the option exists
+     */
+    inline bool contains(const std::string& key) const noexcept
+    {
+        return find(key) != end();
+    }
+
+    /**
+     * Find an option or return an empty one if not found.
+     *
+     * \param key the key
+     * \return the option or empty one if not found
+     */
+    inline option get(const std::string& key) const noexcept
+    {
+        auto it = find(key);
+
+        if (it == end())
+            return option(key);
+
+        return *it;
+    }
+
+    /**
+     * Find an option by key and return an iterator.
+     *
+     * \param key the key
+     * \return the iterator or end() if not found
+     */
+    inline iterator find(const std::string& key) noexcept
+    {
+        return std::find_if(begin(), end(), [&] (const auto& o) {
+            return o.key() == key;
+        });
+    }
+
+    /**
+     * Find an option by key and return an iterator.
+     *
+     * \param key the key
+     * \return the iterator or end() if not found
+     */
+    inline const_iterator find(const std::string& key) const noexcept
+    {
+        return std::find_if(cbegin(), cend(), [&] (const auto& o) {
+            return o.key() == key;
+        });
+    }
+
+    /**
+     * Access an option at the specified key.
+     *
+     * \param key the key
+     * \return the option
+     * \pre contains(key) must return true
+     */
+    inline option& operator[](const std::string& key)
+    {
+        assert(contains(key));
+
+        return *find(key);
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \param key the key
+     * \return the option
+     * \pre contains(key) must return true
+     */
+    inline const option& operator[](const std::string& key) const
+    {
+        assert(contains(key));
+
+        return *find(key);
+    }
+
+    /**
+     * Inherited operators.
+     */
+    using std::vector<option>::operator[];
+};
+
+/**
+ * \brief Ini document description.
+ * \see read_file
+ * \see read_string
+ */
+class document : public std::vector<section> {
+public:
+    /**
+     * Check if a document has a specific section.
+     *
+     * \param key the key
+     * \return true if the document contains the section
+     */
+    inline bool contains(const std::string& key) const noexcept
+    {
+        return find(key) != end();
+    }
+
+    /**
+     * Find a section or return an empty one if not found.
+     *
+     * \param key the key
+     * \return the section or empty one if not found
+     */
+    inline section get(const std::string& key) const noexcept
+    {
+        auto it = find(key);
+
+        if (it == end())
+            return section(key);
+
+        return *it;
+    }
+
+    /**
+     * Find a section by key and return an iterator.
+     *
+     * \param key the key
+     * \return the iterator or end() if not found
+     */
+    inline iterator find(const std::string& key) noexcept
+    {
+        return std::find_if(begin(), end(), [&] (const auto& o) {
+            return o.key() == key;
+        });
+    }
+
+    /**
+     * Find a section by key and return an iterator.
+     *
+     * \param key the key
+     * \return the iterator or end() if not found
+     */
+    inline const_iterator find(const std::string& key) const noexcept
+    {
+        return std::find_if(cbegin(), cend(), [&] (const auto& o) {
+            return o.key() == key;
+        });
+    }
+
+    /**
+     * Access a section at the specified key.
+     *
+     * \param key the key
+     * \return the section
+     * \pre contains(key) must return true
+     */
+    inline section& operator[](const std::string& key)
+    {
+        assert(contains(key));
+
+        return *find(key);
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \param key the key
+     * \return the section
+     * \pre contains(key) must return true
+     */
+    inline const section& operator[](const std::string& key) const
+    {
+        assert(contains(key));
+
+        return *find(key);
+    }
+
+    /**
+     * Inherited operators.
+     */
+    using std::vector<section>::operator[];
+};
+
+/**
+ * Analyse a stream and detect potential syntax errors. This does not parse the
+ * file like including other files in include statement.
+ *
+ * It does only analysis, for example if an option is defined under no section,
+ * this does not trigger an exception while it's invalid.
+ *
+ * \param it the iterator
+ * \param end where to stop
+ * \return the list of tokens
+ * \throws exception on errors
+ */
+tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end);
+
+/**
+ * Overloaded function for stream.
+ *
+ * \param stream the stream
+ * \return the list of tokens
+ * \throws exception on errors
+ */
+tokens analyse(std::istream& stream);
+
+/**
+ * Parse the produced tokens.
+ *
+ * \param tokens the tokens
+ * \param path the parent path
+ * \return the document
+ * \throw exception on errors
+ */
+document parse(const tokens& tokens, const std::string& path = ".");
+
+/**
+ * Parse a file.
+ *
+ * \param filename the file name
+ * \return the document
+ * \throw exception on errors
+ */
+document read_file(const std::string& filename);
+
+/**
+ * Parse a string.
+ *
+ * If the string contains include statements, they are relative to the current
+ * working directory.
+ *
+ * \param buffer the buffer
+ * \return the document
+ * \throw exception on exceptions
+ */
+document read_string(const std::string& buffer);
+
+/**
+ * Show all tokens and their description.
+ *
+ * \param tokens the tokens
+ */
+void dump(const tokens& tokens);
+
+} // !ini
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_INI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/ini_util.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,97 @@
+/*
+ * ini_util.hpp -- ini utilities
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_INI_UTIL_HPP
+#define IRCCD_COMMON_INI_UTIL_HPP
+
+/**
+ * \file ini_util.hpp
+ * \brief Ini utilities.
+ */
+
+#include <boost/optional.hpp>
+
+#include "ini.hpp"
+#include "string_util.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Ini utilities.
+ */
+namespace ini_util {
+
+/**
+ * Get an unsigned integer from the configuration section.
+ *
+ * \param sc the section
+ * \param name the option name
+ * \return the value or none if not able to convert
+ */
+template <typename Int>
+inline boost::optional<Int> get_uint(const ini::section& sc, const std::string& name) noexcept
+{
+    return string_util::to_uint<Int>(sc.get(name).value());
+}
+
+/**
+ * Get an optional string or the default value if not given.
+ *
+ * \param sc the section
+ * \param name the option name
+ * \param def the default value
+ * \return the value or def if not found
+ */
+inline std::string optional_string(const ini::section& sc,
+                                   const std::string& name,
+                                   const std::string& def) noexcept
+{
+    const auto it = sc.find(name);
+
+    if (it == sc.end())
+        return def;
+
+    return it->value();
+}
+
+/**
+ * Get an optional unsigned integer from the configuration section.
+ *
+ * \param sc the section
+ * \param name the option name
+ * \param def the default value
+ * \return the value or none if not able to convert
+ */
+template <typename Int>
+inline boost::optional<Int> optional_uint(const ini::section& sc,
+                                          const std::string& name,
+                                          Int def) noexcept
+{
+    const auto it = sc.find(name);
+
+    if (it == sc.end())
+        return def;
+
+    return string_util::to_uint<Int>(it->value());
+}
+
+} // !ini_util
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_INI_UTIL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/json_util.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,366 @@
+/*
+ * json_util.hpp -- utilities for JSON
+ *
+ * Copyright (c) 2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_JSON_UTIL_HPP
+#define IRCCD_JSON_UTIL_HPP
+
+/**
+ * \file json_util.hpp
+ * \brief Utilities for JSON.
+ */
+
+#include <cstdint>
+#include <limits>
+#include <string>
+#include <type_traits>
+
+#include <boost/optional.hpp>
+
+#include <json.hpp>
+
+namespace irccd {
+
+/**
+ * \brief Utilities for JSON.
+ */
+namespace json_util {
+
+/**
+ * \cond JSON_UTIL_HIDDEN_SYMBOLS
+ */
+
+namespace detail {
+
+template <typename Int>
+class parser_type_traits_uint : public std::true_type {
+public:
+    static boost::optional<Int> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_unsigned())
+            return boost::none;
+
+        const auto ret = value.get<std::uint64_t>();
+
+        if (ret > std::numeric_limits<Int>::max())
+            return boost::none;
+
+        return static_cast<Int>(ret);
+    }
+};
+
+template <typename Int>
+class parser_type_traits_int : public std::true_type {
+public:
+    static boost::optional<Int> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_integer())
+            return boost::none;
+
+        const auto ret = value.get<std::int64_t>();
+
+        if (ret < std::numeric_limits<Int>::min() || ret > std::numeric_limits<Int>::max())
+            return boost::none;
+
+        return static_cast<Int>(ret);
+    }
+};
+
+} // !detail
+
+/**
+ * \endcond
+ */
+
+/**
+ * \brief Describe how to convert a JSON value.
+ *
+ * This class must be specialized for every type you want to convert from JSON
+ * to its native type.
+ *
+ * You only need to implement the get function with the following signature:
+ *
+ * ```cpp
+ * static boost::optional<T> get(const nlohmann::json& value);
+ * ```
+ *
+ * The implementation should not throw an exception but return a null optional
+ * instead.
+ *
+ * This class is already specialized for the given types:
+ *
+ * - bool
+ * - double
+ * - std::uint(8, 16, 32, 64)
+ * - std::string
+ */
+template <typename T>
+class parser_type_traits : public std::false_type {
+};
+
+/**
+ * \brief Specialization for `bool`.
+ */
+template <>
+class parser_type_traits<bool> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to bool.
+     *
+     * \return the bool or none if not a boolean type
+     */
+    static boost::optional<bool> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_boolean())
+            return boost::none;
+
+        return value.get<bool>();
+    }
+};
+
+/**
+ * \brief Specialization for `double`.
+ */
+template <>
+class parser_type_traits<double> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to bool.
+     *
+     * \return the double or none if not a double type
+     */
+    static boost::optional<double> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_float())
+            return boost::none;
+
+        return value.get<double>();
+    }
+};
+
+/**
+ * \brief Specialization for `std::string`.
+ */
+template <>
+class parser_type_traits<std::string> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to bool.
+     *
+     * \return the string or none if not a string type
+     */
+    static boost::optional<std::string> get(const nlohmann::json& value)
+    {
+        if (!value.is_string())
+            return boost::none;
+
+        return value.get<std::string>();
+    }
+};
+
+/**
+ * \brief Specialization for `std::int8_t`.
+ */
+template <>
+class parser_type_traits<std::int8_t> : public detail::parser_type_traits_int<std::int8_t> {
+};
+
+/**
+ * \brief Specialization for `std::int16_t`.
+ */
+template <>
+class parser_type_traits<std::int16_t> : public detail::parser_type_traits_int<std::int16_t> {
+};
+
+/**
+ * \brief Specialization for `std::int32_t`.
+ */
+template <>
+class parser_type_traits<std::int32_t> : public detail::parser_type_traits_int<std::int32_t> {
+};
+
+/**
+ * \brief Specialization for `std::int64_t`.
+ */
+template <>
+class parser_type_traits<std::int64_t> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to std::int64_t.
+     *
+     * \return the int or none if not a int type
+     */
+    static boost::optional<std::int64_t> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_integer())
+            return boost::none;
+
+        return value.get<std::int64_t>();
+    }
+};
+
+/**
+ * \brief Specialization for `std::int8_t`.
+ */
+template <>
+class parser_type_traits<std::uint8_t> : public detail::parser_type_traits_uint<std::uint8_t> {
+};
+
+/**
+ * \brief Specialization for `std::int16_t`.
+ */
+template <>
+class parser_type_traits<std::uint16_t> : public detail::parser_type_traits_uint<std::uint16_t> {
+};
+
+/**
+ * \brief Specialization for `std::int32_t`.
+ */
+template <>
+class parser_type_traits<std::uint32_t> : public detail::parser_type_traits_uint<std::uint32_t> {
+};
+
+/**
+ * \brief Specialization for `std::int64_t`.
+ */
+template <>
+class parser_type_traits<std::uint64_t> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to std::uint64_t.
+     *
+     * \return the int or none if not a int type
+     */
+    static boost::optional<std::uint64_t> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_unsigned())
+            return boost::none;
+
+        return value.get<std::uint64_t>();
+    }
+};
+
+/**
+ * \brief Convenient JSON object parser
+ *
+ * This class helps destructuring insecure JSON input by returning optional
+ * values if they are not present or invalid.
+ */
+class document : public nlohmann::json {
+public:
+    /**
+     * Constructor.
+     *
+     * \param object the object
+     */
+    inline document(nlohmann::json object)
+        : nlohmann::json(std::move(object))
+    {
+    }
+
+    /**
+     * Get a value from the document object.
+     *
+     * \param key the property key
+     * \return the value or boost::none if not found or not convertible
+     */
+    template <typename Type>
+    inline boost::optional<Type> get(const std::string& key) const noexcept
+    {
+        static_assert(parser_type_traits<Type>::value, "type not supported");
+
+        const auto it = find(key);
+
+        if (it == end())
+            return boost::none;
+
+        return parser_type_traits<Type>::get(*it);
+    }
+
+    /**
+     * Get an optional value from the document object.
+     *
+     * If the value is undefined, the default value is returned. Otherwise, if
+     * the value is not in the given type, boost::none is returned.
+     *
+     * \param key the property key
+     * \param def the default value if property is undefined
+     * \return the value, boost::none or def
+     */
+    template <typename Type, typename DefaultValue>
+    inline boost::optional<Type> optional(const std::string& key, DefaultValue&& def) const noexcept
+    {
+        static_assert(parser_type_traits<Type>::value, "type not supported");
+
+        const auto it = find(key);
+
+        if (it == end())
+            return boost::optional<Type>(std::forward<DefaultValue>(def));
+
+        return parser_type_traits<Type>::get(*it);
+    }
+};
+
+/**
+ * Print the value as human readable.
+ *
+ * \note This only works on flat objects.
+ * \param value the value
+ * \param indent the optional indent for objects/arrays
+ * \return the string
+ */
+inline std::string pretty(const nlohmann::json& value, int indent = 4)
+{
+    switch (value.type()) {
+    case nlohmann::json::value_t::null:
+        return "null";
+    case nlohmann::json::value_t::string:
+        return value.get<std::string>();
+    case nlohmann::json::value_t::boolean:
+        return value.get<bool>() ? "true" : "false";
+    case nlohmann::json::value_t::number_integer:
+        return std::to_string(value.get<std::int64_t>());
+    case nlohmann::json::value_t::number_unsigned:
+        return std::to_string(value.get<std::uint64_t>());
+    case nlohmann::json::value_t::number_float:
+        return std::to_string(value.get<double>());
+    default:
+        return value.dump(indent);
+    }
+}
+
+/**
+ * Check if a JSON array contains a specific value in any order.
+ *
+ * \param array the JSON array
+ * \param value the JSON value
+ * \return true if value is present
+ */
+inline bool contains(const nlohmann::json& array, const nlohmann::json& value) noexcept
+{
+    for (const auto& v : array)
+        if (v == value)
+            return true;
+
+    return false;
+}
+
+} // !json_util
+
+} // !irccd
+
+#endif // !IRCCD_JSON_UTIL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/options.cpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,191 @@
+/*
+ * options.cpp -- parse Unix command line options
+ *
+ * Copyright (c) 2015-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include "options.hpp"
+
+namespace irccd {
+
+namespace option {
+
+namespace {
+
+using iterator = std::vector<std::string>::iterator;
+using args = std::vector<std::string>;
+
+inline bool is_option(const std::string& arg) noexcept
+{
+    return arg.size() >= 2 && arg[0] == '-';
+}
+
+inline bool is_long_option(const std::string& arg) noexcept
+{
+    assert(is_option(arg));
+
+    return arg.size() >= 3 && arg[1] == '-';
+}
+
+inline bool is_short_simple(const std::string& arg) noexcept
+{
+    assert(is_option(arg) && !is_long_option(arg));
+
+    return arg.size() == 2;
+}
+
+void parse_long_option(result& result, args& args, iterator& it, iterator& end, const options& definition)
+{
+    auto arg = *it++;
+    auto opt = definition.find(arg);
+
+    if (opt == definition.end())
+        throw invalid_option(arg);
+
+    // Need argument?
+    if (opt->second) {
+        if (it == end || is_option(*it))
+            throw missing_value(arg);
+
+        result.insert(std::make_pair(arg, *it++));
+        it = args.erase(args.begin(), it);
+        end = args.end();
+    } else {
+        result.insert(std::make_pair(arg, ""));
+        it = args.erase(args.begin());
+        end = args.end();
+    }
+}
+
+void parse_short_option_simple(result& result, args& args, iterator& it, iterator &end, const options& definition)
+{
+    /*
+     * Here two cases:
+     *
+     * -v (no option)
+     * -c value
+     */
+    auto arg = *it++;
+    auto opt = definition.find(arg);
+
+    if (opt == definition.end())
+        throw invalid_option(arg);
+
+    // Need argument?
+    if (opt->second) {
+        if (it == end || is_option(*it))
+            throw missing_value(arg);
+
+        result.insert(std::make_pair(arg, *it++));
+        it = args.erase(args.begin(), it);
+        end = args.end();
+    } else {
+        result.insert(std::make_pair(arg, ""));
+        it = args.erase(args.begin());
+        end = args.end();
+    }
+}
+
+void parse_short_option_compressed(result& result, args& args, iterator& it, iterator &end, const options& definition)
+{
+    /*
+     * Here multiple scenarios:
+     *
+     * 1. -abc (-a -b -c if all are simple boolean arguments)
+     * 2. -vc foo.conf (-v -c foo.conf if -c is argument dependant)
+     * 3. -vcfoo.conf (-v -c foo.conf also)
+     */
+    auto value = it->substr(1);
+    auto len = value.length();
+    int toremove = 1;
+
+    for (std::size_t i = 0; i < len; ++i) {
+        auto arg = std::string{'-'} + value[i];
+        auto opt = definition.find(arg);
+
+        if (opt == definition.end())
+            throw invalid_option(arg);
+
+        if (opt->second) {
+            if (i == (len - 1)) {
+                // End of string, get the next argument (see 2.).
+                if (++it == end || is_option(*it))
+                    throw missing_value(arg);
+
+                result.insert(std::make_pair(arg, *it));
+                toremove += 1;
+            } else {
+                result.insert(std::make_pair(arg, value.substr(i + 1)));
+                i = len;
+            }
+        } else
+            result.insert(std::make_pair(arg, ""));
+    }
+
+    it = args.erase(args.begin(), args.begin() + toremove);
+    end = args.end();
+}
+
+void parse_short_option(result& result, args& args, iterator& it, iterator &end, const options& definition)
+{
+    if (is_short_simple(*it))
+        parse_short_option_simple(result, args, it, end, definition);
+    else
+        parse_short_option_compressed(result, args, it, end, definition);
+}
+
+} // !namespace
+
+result read(std::vector<std::string>& args, const options& definition)
+{
+    result result;
+
+    auto it = args.begin();
+    auto end = args.end();
+
+    while (it != end) {
+        if (!is_option(*it))
+            break;
+
+        if (is_long_option(*it))
+            parse_long_option(result, args, it, end, definition);
+        else
+            parse_short_option(result, args, it, end, definition);
+    }
+
+    return result;
+}
+
+result read(int& argc, char**& argv, const options& definition)
+{
+    std::vector<std::string> args;
+
+    for (int i = 0; i < argc; ++i)
+        args.push_back(argv[i]);
+
+    auto before = args.size();
+    auto result = read(args, definition);
+
+    argc -= before - args.size();
+    argv += before - args.size();
+
+    return result;
+}
+
+} // !option
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/options.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,161 @@
+/*
+ * options.hpp -- parse Unix command line options
+ *
+ * Copyright (c) 2015-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_OPTIONS_HPP
+#define IRCCD_COMMON_OPTIONS_HPP
+
+/**
+ * \file options.hpp
+ * \brief Basic Unix options parser.
+ */
+
+#include <exception>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace irccd {
+
+/**
+ * Namespace for options parsing.
+ */
+namespace option {
+
+/**
+ * \brief This exception is thrown when an invalid option has been found.
+ */
+class invalid_option : public std::exception {
+private:
+    std::string message_;
+    std::string name_;
+
+public:
+    /**
+     * Construct the exception.
+     *
+     * \param name the argument missing
+     */
+    inline invalid_option(std::string name)
+        : name_(std::move(name))
+    {
+        message_ = std::string("invalid option: ") + name_;
+    }
+
+    /**
+     * Get the option name.
+     *
+     * \return the name
+     */
+    inline const std::string& name() const noexcept
+    {
+        return name_;
+    }
+
+    /**
+     * Get the error message.
+     *
+     * \return the error message
+     */
+    const char* what() const noexcept override
+    {
+        return message_.c_str();
+    }
+};
+
+/**
+ * \brief This exception is thrown when an option requires a value and no value
+ * has been given.
+ */
+class missing_value : public std::exception {
+private:
+    std::string message_;
+    std::string name_;
+
+public:
+    /**
+     * Construct the exception.
+     *
+     * \param name the option that requires a value
+     */
+    inline missing_value(std::string name)
+        : name_(std::move(name))
+    {
+        message_ = std::string("missing argument for: ") + name_;
+    }
+
+    /**
+     * Get the option name.
+     *
+     * \return the name
+     */
+    inline const std::string& name() const noexcept
+    {
+        return name_;
+    }
+
+    /**
+     * Get the error message.
+     *
+     * \return the error message
+     */
+    const char* what() const noexcept override
+    {
+        return message_.c_str();
+    }
+};
+
+/**
+ * Packed multimap of options.
+ */
+using result = std::multimap<std::string, std::string>;
+
+/**
+ * Define the allowed options.
+ */
+using options = std::map<std::string, bool>;
+
+/**
+ * Extract the command line options and return a result.
+ *
+ * \param args the arguments
+ * \param definition
+ * \warning the arguments vector is modified in place to remove parsed options
+ * \throw missing_value
+ * \throw invalid_option
+ */
+result read(std::vector<std::string>& args, const options& definition);
+
+/**
+ * Overloaded function for usage with main() arguments.
+ *
+ * \param argc the number of arguments
+ * \param argv the argument vector
+ * \param definition
+ * \note don't forget to remove the first argv[0] argument
+ * \warning the argc and argv are modified in place to remove parsed options
+ * \throw missing_value
+ * \throw invalid_option
+ */
+result read(int& argc, char**& argv, const options& definition);
+
+} // !option
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_OPTIONS_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/socket_acceptor.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,162 @@
+/*
+ * socket_acceptor.hpp -- socket stream acceptor interface
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
+#define IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
+
+/**
+ * \file socket_acceptor.hpp
+ * \brief Socket stream acceptor interface.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include "acceptor.hpp"
+#include "socket_stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief Socket stream acceptor interface.
+ * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
+ */
+template <typename Protocol>
+class socket_acceptor : public acceptor {
+public:
+    /**
+     * Convenient endpoint alias.
+     */
+    using endpoint = typename Protocol::endpoint;
+
+    /**
+     * Convenient acceptor alias.
+     */
+    using acceptor = typename Protocol::acceptor;
+
+    /**
+     * Convenient socket alias.
+     */
+    using socket = typename Protocol::socket;
+
+private:
+    acceptor acceptor_;
+
+#if !defined(NDEBUG)
+    bool is_accepting_{false};
+#endif
+
+protected:
+    /**
+     * Helper to accept on the real underlying socket.
+     *
+     * \param socket the real socket
+     * \param handler the handler
+     */
+    template <typename Socket, typename Handler>
+    void do_accept(Socket& socket, Handler handler);
+
+public:
+    /**
+     * Construct the socket_acceptor.
+     *
+     * \pre acceptor must be ready (is_open() returns true)
+     * \param acceptor the Boost.Asio acceptor
+     */
+    inline socket_acceptor(acceptor acceptor) noexcept
+        : acceptor_(std::move(acceptor))
+    {
+        assert(acceptor_.is_open());
+    }
+
+    /**
+     * Get the underlying acceptor.
+     *
+     * \return the acceptor
+     */
+    inline const acceptor& get_acceptor() const noexcept
+    {
+        return acceptor_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the acceptor
+     */
+    inline acceptor& get_acceptor() noexcept
+    {
+        return acceptor_;
+    }
+
+    /**
+     * \copydoc acceptor::accept
+     */
+    void accept(accept_handler handler) override;
+};
+
+template <typename Protocol>
+template <typename Socket, typename Handler>
+void socket_acceptor<Protocol>::do_accept(Socket& socket, Handler handler)
+{
+#if !defined(NDEBUG)
+    assert(!is_accepting_);
+
+    is_accepting_ = true;
+#endif
+
+    acceptor_.async_accept(socket, [this, handler] (auto code) {
+#if !defined(NDEBUG)
+        is_accepting_ = false;
+#endif
+        handler(detail::convert(code));
+    });
+}
+
+template <typename Protocol>
+void socket_acceptor<Protocol>::accept(accept_handler handler)
+{
+    assert(handler);
+
+    const auto client = std::make_shared<socket_stream<socket>>(acceptor_.get_io_service());
+
+    do_accept(client->get_socket(), [this, client, handler] (auto code) {
+        handler(std::move(code), code ? nullptr : std::move(client));
+    });
+}
+
+/**
+ * Convenient TCP/IP acceptor type.
+ */
+using ip_acceptor = socket_acceptor<boost::asio::ip::tcp>;
+
+#if !BOOST_OS_WINDOWS
+
+/**
+ * Convenient Unix acceptor type.
+ */
+using local_acceptor = socket_acceptor<boost::asio::local::stream_protocol>;
+
+#endif
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/socket_connector.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,176 @@
+/*
+ * socket_connector.hpp -- socket connection interface
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_SOCKET_CONNECTOR_HPP
+#define IRCCD_COMMON_SOCKET_CONNECTOR_HPP
+
+/**
+ * \file socket_connector.hpp
+ * \brief Socket connection interface.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include <vector>
+
+#include "connector.hpp"
+#include "socket_stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief Socket connection interface.
+ * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
+ */
+template <typename Protocol>
+class socket_connector : public connector {
+public:
+    /**
+     * Convenient endpoint alias.
+     */
+    using endpoint = typename Protocol::endpoint;
+
+    /**
+     * Convenient socket alias.
+     */
+    using socket = typename Protocol::socket;
+
+private:
+    boost::asio::io_service& service_;
+    std::vector<endpoint> endpoints_;
+
+#if !defined(NDEBUG)
+    bool is_connecting_{false};
+#endif
+
+protected:
+    /**
+     * Start trying to connect to all endpoints.
+     *
+     * \param socket the underlying socket
+     * \param handler handler with `void f(std::error_code)` signature
+     */
+    template <typename Socket, typename Handler>
+    void do_connect(Socket& socket, Handler handler);
+
+public:
+    /**
+     * Construct the socket connector with only one endpoint.
+     *
+     * \param service the service
+     * \param endpoint the unique endpoint
+     */
+    inline socket_connector(boost::asio::io_service& service, endpoint endpoint) noexcept
+        : service_(service)
+        , endpoints_{std::move(endpoint)}
+    {
+    }
+
+    /**
+     * Construct the socket connection.
+     *
+     * \param service the service
+     * \param eps the endpoints
+     */
+    inline socket_connector(boost::asio::io_service& service, std::vector<endpoint> eps) noexcept
+        : service_(service)
+        , endpoints_(std::move(eps))
+    {
+    }
+
+    /**
+     * Get the underlying I/O service.
+     *
+     * \return the I/O service
+     */
+    inline const boost::asio::io_service& get_io_service() const noexcept
+    {
+        return service_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the I/O service
+     */
+    inline boost::asio::io_service& get_io_service() noexcept
+    {
+        return service_;
+    }
+
+    /**
+     * \copydoc connector::connect
+     */
+    void connect(connect_handler handler);
+};
+
+template <typename Protocol>
+template <typename Socket, typename Handler>
+void socket_connector<Protocol>::do_connect(Socket& socket, Handler handler)
+{
+#if !defined(NDEBUG)
+    assert(!is_connecting_);
+
+    is_connecting_ = true;
+#endif
+
+    boost::asio::async_connect(socket, endpoints_.begin(), endpoints_.end(), [this, handler] (auto code, auto ep) {
+#if !defined(NDEBUG)
+        is_connecting_ = false;
+#endif
+
+        if (ep == endpoints_.end())
+            handler(make_error_code(std::errc::host_unreachable));
+        else
+            handler(detail::convert(code));
+    });
+}
+
+template <typename Protocol>
+void socket_connector<Protocol>::connect(connect_handler handler)
+{
+    assert(handler);
+
+    const auto stream = std::make_shared<socket_stream<socket>>(service_);
+
+    do_connect(stream->get_socket(), [handler, stream] (auto code) {
+        handler(code, code ? nullptr : std::move(stream));
+    });
+}
+
+/**
+ * Convenient TCP/IP connector type.
+ */
+using ip_connector = socket_connector<boost::asio::ip::tcp>;
+
+#if !BOOST_OS_WINDOWS
+
+/**
+ * Convenient Unix conncetor type.
+ */
+using local_connector = socket_connector<boost::asio::local::stream_protocol>;
+
+#endif
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_SOCKET_CONNECTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/socket_stream.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,246 @@
+/*
+ * socket_stream.hpp -- socket stream interface
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_SOCKET_STREAM_HPP
+#define IRCCD_COMMON_SOCKET_STREAM_HPP
+
+/**
+ * \file socket_stream.hpp
+ * \brief Socket stream interface.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include <cstddef>
+#include <cassert>
+#include <string>
+#include <utility>
+
+#include <boost/asio.hpp>
+#include <boost/predef/os.h>
+
+#include "stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \cond HIDDEN_SYMBOLS
+ */
+
+namespace detail {
+
+/**
+ * Convert boost::system::error_code to std.
+ *
+ * \param code the error code
+ * \return the std::error_code
+ */
+inline std::error_code convert(boost::system::error_code code) noexcept
+{
+    return std::error_code(code.value(), std::system_category());
+}
+
+} // !detail
+
+/**
+ * \endcond
+ */
+
+/**
+ * \brief Socket implementation interface.
+ * \tparam Socket the Boost.Asio compatible socket.
+ *
+ * This class reimplements stream for Boost.Asio sockets.
+ */
+template <typename Socket>
+class socket_stream : public stream {
+private:
+    Socket socket_;
+    boost::asio::streambuf input_;
+    std::string output_;
+
+#if !defined(NDEBUG)
+    bool is_receiving_{false};
+    bool is_sending_{false};
+#endif
+
+    void handle_read(boost::system::error_code, std::size_t, read_handler);
+    void handle_write(boost::system::error_code, std::size_t, write_handler);
+
+public:
+    /**
+     * Create the socket stream.
+     *
+     * \param args the Socket constructor arguments
+     */
+    template <typename... Args>
+    inline socket_stream(Args&&... args)
+        : socket_(std::forward<Args>(args)...)
+    {
+    }
+
+    /**
+     * Get the underlying socket.
+     *
+     * \return the socket
+     */
+    inline const Socket& get_socket() const noexcept
+    {
+        return socket_;
+    }
+
+    /**
+     * Overloaded function
+     *
+     * \return the socket
+     */
+    inline Socket& get_socket() noexcept
+    {
+        return socket_;
+    }
+
+    /**
+     * \copydoc stream::read
+     */
+    void read(read_handler handler) override;
+
+    /**
+     * \copydoc stream::write
+     */
+    void write(const nlohmann::json& json, write_handler handler) override;
+};
+
+template <typename Socket>
+void socket_stream<Socket>::handle_read(boost::system::error_code code,
+                                        std::size_t xfer,
+                                        read_handler handler)
+{
+#if !defined(NDEBUG)
+    is_receiving_ = false;
+#endif
+
+    if (xfer == 0U) {
+        handler(make_error_code(std::errc::not_connected), nullptr);
+        return;
+    }
+    if (code) {
+        handler(detail::convert(code), nullptr);
+        return;
+    }
+
+    // 1. Convert the buffer safely.
+    std::string buffer;
+
+    try {
+        buffer = std::string(
+            boost::asio::buffers_begin(input_.data()),
+            boost::asio::buffers_begin(input_.data()) + xfer - /* \r\n\r\n */ 4
+        );
+
+        input_.consume(xfer);
+    } catch (const std::bad_alloc&) {
+        handler(make_error_code(std::errc::not_enough_memory), nullptr);
+        return;
+    }
+
+    // 2. Convert to JSON.
+    nlohmann::json doc;
+
+    try {
+        doc = nlohmann::json::parse(buffer);
+    } catch (const std::exception&) {
+        handler(make_error_code(std::errc::invalid_argument), nullptr);
+        return;
+    }
+
+    if (!doc.is_object())
+        handler(make_error_code(std::errc::invalid_argument), nullptr);
+    else
+        handler(std::error_code(), std::move(doc));
+}
+
+template <typename Socket>
+void socket_stream<Socket>::handle_write(boost::system::error_code code,
+                                         std::size_t xfer,
+                                         write_handler handler)
+{
+#if !defined(NDEBUG)
+    is_sending_ = false;
+#endif
+
+    if (xfer == 0)
+        handler(make_error_code(std::errc::not_connected));
+    else
+        handler(detail::convert(code));
+}
+
+template <typename Socket>
+void socket_stream<Socket>::read(read_handler handler)
+{
+#if !defined(NDEBUG)
+    assert(!is_receiving_);
+    assert(handler);
+
+    is_receiving_ = true;
+#endif
+
+    boost::asio::async_read_until(get_socket(), input_, "\r\n\r\n", [this, handler] (auto code, auto xfer) {
+        handle_read(code, xfer, std::move(handler));
+    });
+}
+
+template <typename Socket>
+void socket_stream<Socket>::write(const nlohmann::json& json, write_handler handler)
+{
+#if !defined(NDEBUG)
+    assert(!is_sending_);
+    assert(handler);
+
+    is_sending_ = true;
+#endif
+
+    output_ = json.dump(0) + "\r\n\r\n";
+
+    const auto buffer = boost::asio::buffer(output_.data(), output_.size());
+
+    boost::asio::async_write(get_socket(), buffer, [this, handler] (auto code, auto xfer) {
+        handle_write(code, xfer, std::move(handler));
+    });
+}
+
+/**
+ * Convenient TCP/IP stream type.
+ */
+using ip_stream = socket_stream<boost::asio::ip::tcp::socket>;
+
+#if !BOOST_OS_WINDOWS
+
+/**
+ * Convenient Unix stream type.
+ */
+using local_stream = socket_stream<boost::asio::local::stream_protocol::socket>;
+
+#endif
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_SOCKET_STREAM_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/stream.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,91 @@
+/*
+ * stream.hpp -- abstract stream interface
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_STREAM_HPP
+#define IRCCD_COMMON_STREAM_HPP
+
+/**
+ * \file stream.hpp
+ * \brief Abstract stream interface.
+ */
+
+#include <functional>
+#include <system_error>
+
+#include "json.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief Read completion handler.
+ */
+using read_handler = std::function<void (std::error_code, nlohmann::json)>;
+
+/**
+ * \brief Write completion handler.
+ */
+using write_handler = std::function<void (std::error_code)>;
+
+/**
+ * \brief Abstract stream interface
+ *
+ * Abstract I/O interface that allows reading/writing from a stream in an
+ * asynchronous manner.
+ *
+ * The derived classes must implement non-blocking read and write operations.
+ */
+class stream {
+public:
+    /**
+     * Default constructor.
+     */
+    stream() = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~stream() = default;
+
+    /**
+     * Start asynchronous read.
+     *
+     * \pre another read operation must not be running
+     * \pre handler != nullptr
+     * \param handler the handler
+     */
+    virtual void read(read_handler handler) = 0;
+
+    /**
+     * Start asynchronous write.
+     *
+     * \pre json.is_object()
+     * \pre another write operation must not be running
+     * \pre handler != nullptr
+     * \param json the JSON message
+     * \param handler the handler
+     */
+    virtual void write(const nlohmann::json& json, write_handler handler) = 0;
+};
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_STREAM_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/string_util.cpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,446 @@
+/*
+ * string_util.cpp -- string utilities
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <boost/predef/os.h>
+
+#include "sysconfig.hpp"
+
+#if defined(HAVE_POPEN)
+#   include <array>
+#   include <cerrno>
+#   include <cstring>
+#   include <functional>
+#   include <memory>
+#endif
+
+#include <cassert>
+#include <iomanip>
+
+#include "string_util.hpp"
+
+using namespace std::string_literals;
+
+namespace irccd {
+
+namespace string_util {
+
+// {{{ subst
+
+namespace {
+
+const std::unordered_map<std::string, int> irc_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> irc_attributes{
+    { "bold",       '\x02'  },
+    { "italic",     '\x09'  },
+    { "strike",     '\x13'  },
+    { "reset",      '\x0f'  },
+    { "underline",  '\x15'  },
+    { "underline2", '\x1f'  },
+    { "reverse",    '\x16'  }
+};
+
+const std::unordered_map<std::string, unsigned> shell_colors{
+    { "black",      30  },
+    { "red",        31  },
+    { "green",      32  },
+    { "orange",     33  },
+    { "blue",       34  },
+    { "purple",     35  },
+    { "cyan",       36  },
+    { "white",      37  },
+    { "default",    39  },
+};
+
+const std::unordered_map<std::string, unsigned> shell_attributes{
+    { "bold",       1   },
+    { "dim",        2   },
+    { "underline",  4   },
+    { "blink",      5   },
+    { "reverse",    7   },
+    { "hidden",     8   }
+};
+
+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_irc_attrs(const std::string& content)
+{
+    auto list = split(content, ",");
+
+    // @{} means reset.
+    if (list.empty())
+        return std::string(1, irc_attributes.at("reset"));
+
+    std::ostringstream oss;
+
+    // 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 = irc_colors.find(foreground);
+        if (it != irc_colors.end())
+            oss << it->second;
+
+        // Background.
+        if (list.size() >= 2 && (it = irc_colors.find(list[1])) != irc_colors.end())
+            oss << "," << it->second;
+
+        // Attributes.
+        for (std::size_t i = 2; i < list.size(); ++i) {
+            auto attribute = irc_attributes.find(list[i]);
+
+            if (attribute != irc_attributes.end())
+                oss << attribute->second;
+        }
+    }
+
+    return oss.str();
+}
+
+std::string subst_shell_attrs(const std::string& content)
+{
+#if !BOOST_OS_WINDOWS
+    auto list = split(content, ",");
+
+    if (list.empty())
+        return "\033[0m";
+    if (list.size() > 3)
+        return "";
+
+    std::vector<std::string> seq;
+
+    /*
+     * Shell sequence looks like this:
+     *
+     * ^[[attributes;foreground;backgroundm
+     */
+    if (list.size() >= 3) {
+        const auto it = shell_attributes.find(list[2]);
+
+        if (it != shell_attributes.end())
+            seq.push_back(std::to_string(it->second));
+        else
+            return "";
+    }
+    if (list.size() >= 1) {
+        const auto it = shell_colors.find(list[0]);
+
+        if (it != shell_colors.end())
+            seq.push_back(std::to_string(it->second));
+        else
+            return "";
+    }
+    if (list.size() >= 2) {
+        const auto it = shell_colors.find(list[1]);
+
+        if (it != shell_colors.end())
+            seq.push_back(std::to_string(it->second + 10));
+        else
+            return "";
+    }
+
+    std::ostringstream oss;
+
+    oss << "\033[";
+    oss << string_util::join(seq, ';');
+    oss << "m";
+
+    return oss.str();
+#else
+    return "";
+#endif
+}
+
+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_irc_attrs(content);
+        else if ((params.flags & subst_flags::shell_attrs) == subst_flags::shell_attrs)
+            value = subst_shell_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();
+}
+
+// }}}
+
+// {{{ strip
+
+std::string strip(std::string str) noexcept
+{
+    const auto test = [] (auto 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;
+}
+
+// }}}
+
+// {{{ split
+
+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;
+}
+
+// }}}
+
+// {{{ is_boolean
+
+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";
+}
+
+// }}}
+
+} // !string_util
+
+} // !util
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/string_util.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,411 @@
+/*
+ * string_util.hpp -- string utilities
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_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 <stdexcept>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+
+#include <boost/format.hpp>
+#include <boost/optional.hpp>
+
+namespace irccd {
+
+/**
+ * \file string_util.hpp
+ * \brief String utilities.
+ */
+namespace string_util {
+
+// {{{ subst
+
+/**
+ * \brief Disable or enable some features.
+ */
+enum class subst_flags : unsigned {
+    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
+    shell_attrs = (1 << 5)      //!< shell attributes
+};
+
+/**
+ * \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
+ *   or shell 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.
+ */
+std::string format(std::string text, const subst& params = {});
+
+// }}}
+
+// {{{ strip
+
+/**
+ * Remove leading and trailing spaces.
+ *
+ * \param str the string
+ * \return the removed white spaces
+ */
+std::string strip(std::string str) noexcept;
+
+// }}}
+
+// {{{ split
+
+/**
+ * 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
+ */
+std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max = -1);
+
+// }}}
+
+// {{{ join
+
+/**
+ * Join values by a separator and return a string.
+ *
+ * \param first the first iterator
+ * \param last the last iterator
+ * \param delim the optional delimiter
+ * \return the string
+ */
+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();
+}
+
+/**
+ * Overloaded function that takes a container.
+ *
+ * \param c the container
+ * \param delim the optional delimiter
+ * \return the string
+ */
+template <typename Container, typename DelimType = char>
+std::string join(const Container& c, DelimType delim = ':')
+{
+    return join(c.begin(), c.end(), delim);
+}
+
+/**
+ * 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);
+}
+
+// }}}
+
+// {{{ is_identifier
+
+/**
+ * Check if a string is a valid irccd identifier.
+ *
+ * \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-_]+"));
+}
+
+// }}}
+
+// {{{ is_boolean
+
+/**
+ * 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
+ */
+bool is_boolean(std::string value) noexcept;
+
+// }}}
+
+// {{{ sprintf
+
+/**
+ * \cond HIDDEN_SYMBOLS
+ */
+
+namespace detail {
+
+inline void sprintf(boost::format&)
+{
+}
+
+template <typename Arg, typename... Args>
+inline 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();
+}
+
+// }}}
+
+// {{{ to_int
+
+/**
+ * Convert the given string into a signed integer.
+ *
+ * \param str the string to convert
+ * \param min the minimum value allowed
+ * \param max the maximum value allowed
+ * \return the value or boost::none if not convertible
+ */
+template <typename T = int>
+boost::optional<T> to_int(const std::string& str,
+                          T min = std::numeric_limits<T>::min(),
+                          T max = std::numeric_limits<T>::max()) noexcept
+{
+    static_assert(std::is_signed<T>::value, "must be signed");
+
+    char* end;
+    auto v = std::strtoll(str.c_str(), &end, 10);
+
+    if (*end != '\0' || v < min || v > max)
+        return boost::none;
+
+    return static_cast<T>(v);
+}
+
+// }}}
+
+// {{{ to_uint
+
+/**
+ * Convert the given string into a unsigned integer.
+ *
+ * \note invalid numbers are valid as well
+ * \param str the string to convert
+ * \param min the minimum value allowed
+ * \param max the maximum value allowed
+ * \return the value or boost::none if not convertible
+ */
+template <typename T = unsigned>
+boost::optional<T> to_uint(const std::string& str,
+                           T min = std::numeric_limits<T>::min(),
+                           T max = std::numeric_limits<T>::max()) noexcept
+{
+    static_assert(std::is_unsigned<T>::value, "must be unsigned");
+
+    char* end;
+    auto v = std::strtoull(str.c_str(), &end, 10);
+
+    if (*end != '\0' || v < min || v > max)
+        return boost::none;
+
+    return static_cast<T>(v);
+}
+
+// }}}
+
+} // !string_util
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_STRING_UTIL_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/system.cpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,502 @@
+/*
+ * system.cpp -- platform dependent functions for system inspection
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <stdexcept>
+#include <string>
+
+#include <boost/dll.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/predef/os.h>
+
+#include "sysconfig.hpp"
+
+#if BOOST_OS_WINDOWS
+#   include <sys/timeb.h>
+#   include <shlobj.h>
+#else
+#   include <sys/utsname.h>
+#   include <sys/types.h>
+#   include <sys/param.h>
+#   include <sys/time.h>
+#   include <unistd.h>
+#endif
+
+#if BOOST_OS_LINUX
+#   include <sys/sysinfo.h>
+#endif
+
+#if BOOST_OS_MACOS
+#   include <sys/sysctl.h>
+#   include <libproc.h>
+#endif
+
+#if defined(HAVE_GETLOGIN)
+#   include <unistd.h>
+#endif
+
+#include "system.hpp"
+#include "string_util.hpp"
+#include "xdg.hpp"
+
+namespace irccd {
+
+namespace sys {
+
+namespace {
+
+// {{{ base_directory
+
+/*
+ * base_directory
+ * ------------------------------------------------------------------
+ *
+ * Get the base program directory.
+ *
+ * If irccd has been compiled with relative paths, the base directory is
+ * evaluated by climbing the `bindir' directory from the executable path.
+ *
+ * Otherwise, use the installation prefix.
+ */
+boost::filesystem::path base_directory()
+{
+    static const boost::filesystem::path bindir(CMAKE_INSTALL_BINDIR);
+    static const boost::filesystem::path prefix(CMAKE_INSTALL_PREFIX);
+
+    boost::filesystem::path path(".");
+
+    if (bindir.is_relative()) {
+        try {
+            path = boost::dll::program_location();
+            path = path.parent_path();
+        } catch (...) {
+            path = ".";
+        }
+
+        // Compute relative base directory.
+        for (auto len = std::distance(bindir.begin(), bindir.end()); len > 0; len--)
+            path = path.parent_path();
+        if (path.empty())
+            path = ".";
+    } else
+        path = prefix;
+
+    return path;
+}
+
+// }}}
+
+// {{{ system_directory
+
+/*
+ * system_directory
+ * ------------------------------------------------------------------
+ *
+ * Compute the system directory path for the given component.
+ *
+ * Referenced by:
+ *   - cachedir,
+ *   - datadir,
+ *   - sysconfigdir,
+ *   - plugindir.
+ */
+boost::filesystem::path system_directory(const std::string& component)
+{
+    boost::filesystem::path path(component);
+
+    if (path.is_relative())
+        path = base_directory() / component;
+
+    return path.string();
+}
+
+// }}}
+
+// {{{ user_config_directory
+
+/*
+ * user_config_directory
+ * ------------------------------------------------------------------
+ *
+ * Get user configuration directory.
+ *
+ * Referenced by:   config_filenames.
+ * Requires:
+ *   - Windows:
+ *     - <shlobj.h>
+ */
+boost::filesystem::path user_config_directory()
+{
+    boost::filesystem::path path;
+
+#if BOOST_OS_WINDOWS
+    char folder[MAX_PATH] = {0};
+
+    if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK) {
+        path /= folder;
+        path /= "\\irccd\\config";
+    } else
+        path = ".";
+#else
+    try {
+        path = xdg().config_home();
+    } catch (...) {
+        path = sys::env("HOME");
+        path /= ".config";
+    }
+
+    path /= "irccd";
+#endif
+
+    return path;
+}
+
+// }}}
+
+// {{{ user_plugin_directory
+
+/*
+ * user_plugin_directory
+ * ------------------------------------------------------------------
+ *
+ * Referenced by:   plugin_filenames.
+ * Requires:
+ *   - Windows:
+ *     - <shlobj.h>
+ *
+ * Like add user_config_directory but for plugins.
+ */
+boost::filesystem::path user_plugin_directory()
+{
+    boost::filesystem::path path;
+
+#if BOOST_OS_WINDOWS
+    char folder[MAX_PATH] = {0};
+
+    if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK) {
+        path /= folder;
+        path /= "\\irccd\\share";
+    }
+#else
+    try {
+        path = xdg().data_home();
+    } catch (...) {
+        path = sys::env("HOME");
+        path /= ".local/share";
+    }
+
+    path /= "irccd";
+#endif
+
+    return path / "plugins";
+}
+
+// }}}
+
+} // !namespace
+
+// {{{ set_program_name
+
+void set_program_name(std::string name) noexcept
+{
+#if defined(HAVE_SETPROGNAME)
+    static std::string save = name;
+
+    setprogname(save.c_str());
+#else
+    (void)name;
+#endif
+}
+
+// }}}
+
+// {{{ name
+
+std::string name()
+{
+#if BOOST_OS_LINUX
+    return "Linux";
+#elif BOOST_OS_WINDOWS
+    return "Windows";
+#elif BOOST_OS_BSD_FREE
+    return "FreeBSD";
+#elif BOOST_OS_BSD_DRAGONFLY
+    return "DragonFlyBSD";
+#elif BOOST_OS_BSD_OPEN
+    return "OpenBSD";
+#elif BOOST_OS_BSD_NET
+    return "NetBSD";
+#elif BOOST_OS_MACOS
+    return "macOS";
+#elif BOOST_OS_ANDROID
+    return "Android";
+#elif BOOST_OS_AIX
+    return "Aix";
+#elif BOOST_OS_HAIKU
+    return "Haiku";
+#elif BOOST_OS_IOS
+    return "iOS";
+#elif BOOST_OS_SOLARIS
+    return "Solaris";
+#else
+    return "Unknown";
+#endif
+}
+
+// }}}
+
+// {{{ version
+
+/*
+ * Requires:
+ *   - Windows:
+ *     - <windows.h>
+ *   - Others:
+ *     - <sys/utsname.h>
+ */
+std::string version()
+{
+#if BOOST_OS_WINDOWS
+    const auto version = GetVersion();
+    const auto major = (DWORD)(LOBYTE(LOWORD(version)));
+    const auto minor = (DWORD)(HIBYTE(LOWORD(version)));
+
+    return std::to_string(major) + "." + std::to_string(minor);
+#else
+    struct utsname uts;
+
+    if (::uname(&uts) < 0)
+        throw std::runtime_error(std::strerror(errno));
+
+    return std::string(uts.release);
+#endif
+}
+
+// }}}
+
+// {{{ uptime
+
+/*
+ * Requires:
+ *   - Windows:
+ *     - <windows.h>
+ *   - Linux:
+ *     - <sys/sysinfo.h>
+ *   - Mac:
+ *     - <sys/types.h>
+ *     - <sys/sysctl.h>
+ *   - Others:
+ *     - <ctime>
+ */
+std::uint64_t uptime()
+{
+#if BOOST_OS_WINDOWS
+    return ::GetTickCount64() / 1000;
+#elif BOOST_OS_LINUX
+    struct sysinfo info;
+
+    if (sysinfo(&info) < 0)
+        throw std::runtime_error(std::strerror(errno));
+
+    return info.uptime;
+#elif BOOST_OS_MACOS
+    struct timeval boottime;
+    size_t length = sizeof (boottime);
+    int mib[2] = { CTL_KERN, KERN_BOOTTIME };
+
+    if (sysctl(mib, 2, &boottime, &length, nullptr, 0) < 0)
+        throw std::runtime_error(std::strerror(errno));
+
+    time_t bsec = boottime.tv_sec, csec = time(nullptr);
+
+    return difftime(csec, bsec);
+#else
+    struct timespec ts;
+
+    if (clock_gettime(CLOCK_UPTIME, &ts) < 0)
+        throw std::runtime_error(std::strerror(errno));
+
+    return ts.tv_sec;
+#endif
+}
+
+// }}}
+
+// {{{ ticks
+
+/*
+ * Requires:
+ *   - Windows:
+ *     - <sys/timeb.h>
+ *   - Others:
+ *     - <sys/times.h>
+ */
+std::uint64_t ticks()
+{
+#if BOOST_OS_WINDOWS
+    _timeb tp;
+
+    _ftime(&tp);
+
+    return tp.time * 1000LL + tp.millitm;
+#else
+    struct timeval tp;
+
+    gettimeofday(&tp, NULL);
+
+    return tp.tv_sec * 1000LL + tp.tv_usec / 1000;
+#endif
+}
+
+// }}}
+
+// {{{ home
+
+/*
+ * Requires:
+ *   - Windows:
+ *     - <shlobj.h>
+ */
+std::string home()
+{
+#if BOOST_OS_WINDOWS
+    char path[MAX_PATH];
+
+    if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path) != S_OK)
+        return "";
+
+    return std::string(path);
+#else
+    return env("HOME");
+#endif
+}
+
+// }}}
+
+// {{{ env
+
+/*
+ * Requires:
+ *   - <cstdlib>
+ */
+std::string env(const std::string& var)
+{
+    const auto value = std::getenv(var.c_str());
+
+    if (value == nullptr)
+        return "";
+
+    return value;
+}
+
+// }}}
+
+// {{{ cachedir
+
+boost::filesystem::path cachedir()
+{
+    return system_directory(CMAKE_INSTALL_LOCALSTATEDIR) / "cache/irccd";
+}
+
+// }}}
+
+// {{{ datadir
+
+boost::filesystem::path datadir()
+{
+    return system_directory(CMAKE_INSTALL_DATADIR);
+}
+
+// }}}
+
+// {{{ sysconfdir
+
+boost::filesystem::path sysconfdir()
+{
+    return system_directory(CMAKE_INSTALL_SYSCONFDIR) / "irccd";
+}
+
+// }}}
+
+// {{{ plugindir
+
+boost::filesystem::path plugindir()
+{
+    return system_directory(CMAKE_INSTALL_LIBDIR) / "irccd";
+}
+
+// }}}
+
+// {{{ username
+
+/*
+ * Requires:
+ *   - <unistd.h>
+ */
+std::string username()
+{
+#if defined(HAVE_GETLOGIN)
+    auto v = getlogin();
+
+    if (v)
+        return v;
+#endif
+
+    return "";
+}
+
+// }}}
+
+// {{{ config_filenames
+
+std::vector<std::string> config_filenames(std::string file)
+{
+    return {
+        (user_config_directory() / file).string(),
+        (sysconfdir() / file).string()
+    };
+}
+
+// }}}
+
+// {{{ plugin_filenames
+
+std::vector<std::string> plugin_filenames(const std::string& name,
+                                          const std::vector<std::string>& extensions)
+{
+    assert(!extensions.empty());
+
+    std::vector<std::string> result;
+
+    for (const auto& ext : extensions)
+        result.push_back((user_plugin_directory() / (name + ext)).string());
+    for (const auto& ext : extensions)
+        result.push_back((plugindir() / (name + ext)).string());
+
+    return result;
+}
+
+// }}}
+
+} // !sys
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/system.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,166 @@
+/*
+ * system.hpp -- platform dependent functions for system inspection
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_SYSTEM_HPP
+#define IRCCD_COMMON_SYSTEM_HPP
+
+/**
+ * \file system.hpp
+ * \brief System dependant functions
+ */
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <boost/filesystem.hpp>
+
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Namespace for system functions.
+ */
+namespace sys {
+
+/**
+ * Set the program name, needed for some functions or some systems.
+ *
+ * \param name the program name
+ */
+void set_program_name(std::string name) noexcept;
+
+/**
+ * Get the system name.
+ *
+ * \return the name
+ */
+std::string name();
+
+/**
+ * Get the system version.
+ *
+ * \return the version
+ */
+std::string version();
+
+/**
+ * Get the number of seconds elapsed since the boottime.
+ *
+ * \return the number of seconds
+ */
+std::uint64_t uptime();
+
+/**
+ * Get the milliseconds elapsed since the application
+ * startup.
+ *
+ * \return the milliseconds
+ */
+std::uint64_t ticks();
+
+/**
+ * Get an environment variable.
+ *
+ * \return the value or empty string
+ */
+std::string env(const std::string& var);
+
+/**
+ * Get home directory usually /home/foo
+ *
+ * \return the home directory
+ */
+std::string home();
+
+/**
+ * Get the cache directory as specified as compile time option
+ * CMAKE_INSTALL_LOCALSTATEDIR, if the value is absolute, it is returned as-is.
+ *
+ * If the component is relative, it is evaluated using the binary executable
+ * path.
+ *
+ * \return the evaluated cache directory.
+ * \see datadir
+ * \see configdir
+ */
+boost::filesystem::path cachedir();
+
+/**
+ * Like cachedir but for CMAKE_INSTALL_DATADIR.
+ *
+ * \return the evaluated data directory.
+ * \see cachedir
+ * \see datadir
+ */
+boost::filesystem::path datadir();
+
+/**
+ * Like cachedir but for CMAKE_INSTALL_SYSCONFDIR.
+ *
+ * \return the evaluated config directory.
+ * \see cachedir
+ * \see datadir
+ * \note use config_filenames for irccd.conf, irccdctl.conf files
+ */
+ boost::filesystem::path sysconfdir();
+
+/**
+ * Like cachedir but for CMAKE_INSTALL_LIBDIR.
+ *
+ * \return the evaluated system plugin directory.
+ * \see cachedir
+ * \see datadir
+ */
+boost::filesystem::path plugindir();
+
+/**
+ * Get user account login or empty if not available.
+ *
+ * \return the user account name
+ */
+std::string username();
+
+/**
+ * Construct a list of paths to read configuration files from.
+ *
+ * This function does not test the presence of the files as a condition race
+ * may occur.
+ *
+ * The caller is responsible of opening files for each path.
+ *
+ * \param file the filename to append for convenience
+ * \return the list of paths to check in order
+ */
+std::vector<std::string> config_filenames(std::string file);
+
+/**
+ * Construct a list of paths for reading plugins.
+ *
+ * \param name the plugin id (without extension)
+ * \param extensions the list of extensions supported
+ */
+std::vector<std::string> plugin_filenames(const std::string& name,
+                                          const std::vector<std::string>& extensions);
+
+} // !sys
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_SYSTEM_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/tls_acceptor.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,96 @@
+/*
+ * tls_acceptor.hpp -- TLS/SSL acceptors
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_TLS_ACCEPTOR_HPP
+#define IRCCD_COMMON_TLS_ACCEPTOR_HPP
+
+/**
+ * \file tls_acceptor.hpp
+ * \brief TLS/SSL acceptors.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#if defined(IRCCD_HAVE_SSL)
+
+#include "socket_acceptor.hpp"
+#include "tls_stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief TLS/SSL acceptors.
+ * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
+ */
+template <typename Protocol = boost::asio::ip::tcp>
+class tls_acceptor : public socket_acceptor<Protocol> {
+private:
+    using socket = typename Protocol::socket;
+
+    boost::asio::ssl::context context_;
+
+public:
+    /**
+     * Construct a secure layer transport server.
+     *
+     * \param context the SSL context
+     * \param args the socket_acceptor arguments
+     */
+    template <typename... Args>
+    inline tls_acceptor(boost::asio::ssl::context context, Args&&... args)
+        : socket_acceptor<Protocol>(std::forward<Args>(args)...)
+        , context_(std::move(context))
+    {
+    }
+
+    /**
+     * \copydoc acceptor::accept
+     */
+    void accept(accept_handler handler) override;
+};
+
+template <typename Protocol>
+void tls_acceptor<Protocol>::accept(accept_handler handler)
+{
+    assert(handler);
+
+    auto client = std::make_shared<tls_stream<socket>>(this->get_acceptor().get_io_service(), this->context_);
+
+    socket_acceptor<Protocol>::do_accept(client->get_socket().lowest_layer(), [handler, client] (auto code) {
+        using boost::asio::ssl::stream_base;
+
+        if (code) {
+            handler(code, nullptr);
+            return;
+        }
+
+        client->get_socket().async_handshake(stream_base::server, [handler, client] (auto code) {
+            handler(detail::convert(code), code ? nullptr : std::move(client));
+        });
+    });
+}
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_HAVE_SSL
+
+#endif // !IRCCD_COMMON_TLS_ACCEPTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/tls_connector.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,95 @@
+/*
+ * tls_connector.hpp -- TLS/SSL connectors
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_TLS_CONNECTOR_HPP
+#define IRCCD_COMMON_TLS_CONNECTOR_HPP
+
+/**
+ * \file tls_connector.hpp
+ * \brief TLS/SSL connectors.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#if defined(IRCCD_HAVE_SSL)
+
+#include "socket_connector.hpp"
+#include "tls_stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief TLS/SSL connectors.
+ * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp)
+ */
+template <typename Protocol = boost::asio::ip::tcp>
+class tls_connector : public socket_connector<Protocol> {
+private:
+    boost::asio::ssl::context context_;
+
+public:
+    /**
+     * Construct a secure layer transport server.
+     *
+     * \param context the SSL context
+     * \param args the arguments to socket_connector<Socket> constructor
+     */
+    template <typename... Args>
+    inline tls_connector(boost::asio::ssl::context context, Args&&... args)
+        : socket_connector<Protocol>(std::forward<Args>(args)...)
+        , context_(std::move(context))
+    {
+    }
+
+    /**
+     * \copydoc socket_connector::connect
+     */
+    void connect(connect_handler handler) override;
+};
+
+template <typename Protocol>
+void tls_connector<Protocol>::connect(connect_handler handler)
+{
+    using boost::asio::ssl::stream_base;
+    using socket = typename Protocol::socket;
+
+    assert(handler);
+
+    const auto stream = std::make_shared<tls_stream<socket>>(this->get_io_service(), context_);
+
+    socket_connector<Protocol>::do_connect(stream->get_socket().lowest_layer(), [this, handler, stream] (auto code) {
+        if (code) {
+            handler(code, nullptr);
+            return;
+        }
+
+        stream->get_socket().async_handshake(stream_base::client, [handler, stream] (auto code) {
+            handler(detail::convert(code), code ? nullptr : std::move(stream));
+        });
+    });
+}
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_HAVE_SSL
+
+#endif // !IRCCD_COMMON_TLS_CONNECTOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/tls_stream.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,64 @@
+/*
+ * tls_stream.hpp -- TLS/SSL streams
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_TLS_STREAM_HPP
+#define IRCCD_COMMON_TLS_STREAM_HPP
+
+/**
+ * \file tls_stream.hpp
+ * \brief TLS/SSL streams.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#if defined(IRCCD_HAVE_SSL)
+
+#include <boost/asio/ssl.hpp>
+
+#include "socket_stream.hpp"
+
+namespace irccd {
+
+namespace io {
+
+/**
+ * \brief TLS/SSL streams.
+ * \tparam Socket the Boost.Asio compatible socket.
+ */
+template <typename Socket = boost::asio::ip::tcp::socket>
+class tls_stream : public socket_stream<boost::asio::ssl::stream<Socket>> {
+public:
+    /**
+     * Constructor.
+     *
+     * \param args the arguments to boost::asio::ssl::stream<Socket>
+     */
+    template <typename... Args>
+    inline tls_stream(Args&&... args)
+        : socket_stream<boost::asio::ssl::stream<Socket>>(std::forward<Args>(args)...)
+    {
+    }
+};
+
+} // !io
+
+} // !irccd
+
+#endif // !IRCCD_HAVE_SSL
+
+#endif // !IRCCD_COMMON_TLS_STREAM_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-core/irccd/xdg.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,194 @@
+/*
+ * xdg.hpp -- XDG directory specifications
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_XDG_HPP
+#define IRCCD_XDG_HPP
+
+/**
+ * \file xdg.hpp
+ * \brief XDG directory specifications.
+ * \author David Demelier <markand@malikana.fr>
+ */
+
+#include <cstdlib>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+namespace irccd {
+
+/**
+ * \brief XDG directory specifications.
+ *
+ * Read and get XDG directories.
+ *
+ * This file should compiles on Windows to facilitate portability but its
+ * functions must not be used.
+ */
+class xdg {
+private:
+    std::string config_home_;
+    std::string data_home_;
+    std::string cache_home_;
+    std::string runtime_dir_;
+    std::vector<std::string> config_dirs_;
+    std::vector<std::string> data_dirs_;
+
+    inline bool is_absolute(const std::string& path) const noexcept
+    {
+        return path.length() > 0 && path[0] == '/';
+    }
+
+    std::vector<std::string> split(const std::string& arg) const
+    {
+        std::stringstream iss(arg);
+        std::string item;
+        std::vector<std::string> elems;
+
+        while (std::getline(iss, item, ':')) {
+            if (is_absolute(item))
+                elems.push_back(item);
+        }
+
+        return elems;
+    }
+
+    std::string env_or_home(const std::string& var, const std::string& repl) const
+    {
+        auto value = std::getenv(var.c_str());
+
+        if (value == nullptr || !is_absolute(value)) {
+            auto home = std::getenv("HOME");
+
+            if (home == nullptr)
+                throw std::runtime_error("could not get home directory");
+
+            return std::string(home) + "/" + repl;
+        }
+
+        return value;
+    }
+
+    std::vector<std::string> list_or_defaults(const std::string& var,
+                                              const std::vector<std::string>& list) const
+    {
+        auto value = std::getenv(var.c_str());
+
+        if (!value)
+            return list;
+
+        // No valid item at all? Use defaults.
+        auto result = split(value);
+
+        return (result.size() == 0) ? list : result;
+    }
+
+public:
+    /**
+     * Open an xdg instance and load directories.
+     *
+     * \throw std::runtime_error on failures
+     */
+    xdg()
+    {
+        config_home_    = env_or_home("XDG_CONFIG_HOME", ".config");
+        data_home_      = env_or_home("XDG_DATA_HOME", ".local/share");
+        cache_home_     = env_or_home("XDG_CACHE_HOME", ".cache");
+
+        config_dirs_    = list_or_defaults("XDG_CONFIG_DIRS", { "/etc/xdg" });
+        data_dirs_      = list_or_defaults("XDG_DATA_DIRS", { "/usr/local/share", "/usr/share" });
+
+        /*
+         * Runtime directory is a special case and does not have a replacement,
+         * the application should manage this by itself.
+         */
+        auto runtime = std::getenv("XDG_RUNTIME_DIR");
+
+        if (runtime && is_absolute(runtime))
+            runtime_dir_ = runtime;
+    }
+
+    /**
+     * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config
+     *
+     * \return the config directory
+     */
+    inline const std::string& config_home() const noexcept
+    {
+        return config_home_;
+    }
+
+    /**
+     * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share
+     *
+     * \return the data directory
+     */
+    inline const std::string& data_home() const noexcept
+    {
+        return data_home_;
+    }
+
+    /**
+     * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache
+     *
+     * \return the cache directory
+     */
+    inline const std::string& cache_home() const noexcept
+    {
+        return cache_home_;
+    }
+
+    /**
+     * Get the runtime directory.
+     *
+     * There is no replacement for XDG_RUNTIME_DIR, if it is not set, an empty
+     * value is returned and the user is responsible of using something else.
+     *
+     * \return the runtime directory
+     */
+    inline const std::string& runtime_dir() const noexcept
+    {
+        return runtime_dir_;
+    }
+
+    /**
+     * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" }
+     *
+     * \return the list of config directories
+     */
+    inline const std::vector<std::string>& config_dirs() const noexcept
+    {
+        return config_dirs_;
+    }
+
+    /**
+     * Get the data directories. ${XDG_DATA_DIRS} or { "/usr/local/share",
+     * "/usr/share" }
+     *
+     * \return the list of data directories
+     */
+    inline const std::vector<std::string>& data_dirs() const noexcept
+    {
+        return data_dirs_;
+    }
+};
+
+} // !irccd
+
+#endif // !IRCCD_XDG_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-ctl/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,43 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(libirccd-ctl)
+
+set(
+    HEADERS
+    ${libirccd-ctl_SOURCE_DIR}/irccd/ctl/controller.hpp
+)
+
+set(
+    SOURCES
+    ${libirccd-ctl_SOURCE_DIR}/irccd/ctl/controller.cpp
+)
+
+irccd_define_library(
+    TARGET libirccd-ctl
+    EXPORT
+    HEADERS ${HEADERS}
+    HEADERS_DIRECTORY irccd/ctl
+    SOURCES
+        ${libirccd-ctl_SOURCE_DIR}/CMakeLists.txt
+        ${SOURCES}
+    LIBRARIES
+        libirccd
+    PUBLIC_INCLUDES
+        $<BUILD_INTERFACE:${libirccd-ctl_SOURCE_DIR}>
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-ctl/irccd/ctl/controller.cpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,143 @@
+/*
+ * controller.cpp -- main irccdctl interface
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include <irccd/sysconfig.hpp>
+#include <irccd/json_util.hpp>
+
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server.hpp>
+#include <irccd/daemon/plugin.hpp>
+#include <irccd/daemon/rule.hpp>
+
+#include "controller.hpp"
+
+namespace irccd {
+
+namespace ctl {
+
+void controller::authenticate(connect_handler handler, nlohmann::json info)
+{
+    const auto cmd = nlohmann::json::object({
+        { "command",    "auth"      },
+        { "password",   password_   }
+    });
+
+    write(cmd, [handler, info, this] (auto code) {
+        if (code) {
+            handler(std::move(code), nullptr);
+            return;
+        }
+
+        read([handler, info] (auto code, auto) {
+            handler(std::move(code), std::move(info));
+        });
+    });
+}
+
+void controller::verify(connect_handler handler)
+{
+    read([handler, this] (auto code, auto message) {
+        if (code) {
+            handler(std::move(code), std::move(message));
+            return;
+        }
+
+        const json_util::document doc(message);
+        const auto program = doc.get<std::string>("program");
+        const auto major = doc.get<int>("major");
+
+        if (!program && *program != "irccd")
+            handler(irccd_error::not_irccd, std::move(message));
+        else if (major && *major != IRCCD_VERSION_MAJOR)
+            handler(irccd_error::incompatible_version, std::move(message));
+        else {
+            if (!password_.empty())
+                authenticate(std::move(handler), message);
+            else
+                handler(code, std::move(message));
+        }
+    });
+}
+
+void controller::connect(connect_handler handler)
+{
+    assert(handler);
+
+    connector_->connect([handler, this] (auto code, auto stream) {
+        if (code)
+            handler(std::move(code), nullptr);
+        else {
+            stream_ = std::move(stream);
+            verify(std::move(handler));
+        }
+    });
+}
+
+void controller::read(io::read_handler handler)
+{
+    assert(handler);
+    assert(stream_);
+
+    auto stream = stream_;
+
+    stream_->read([this, handler, stream] (auto code, auto msg) {
+        if (code) {
+            stream_ = nullptr;
+            handler(std::move(code), std::move(msg));
+            return;
+        }
+
+        const json_util::document doc(msg);
+        const auto e = doc.get<int>("error");
+        const auto c = doc.get<std::string>("errorCategory");
+
+        if (e && c) {
+            if (*c == "irccd")
+                code = make_error_code(static_cast<irccd_error::error>(*e));
+            else if (*c == "server")
+                code = make_error_code(static_cast<server_error::error>(*e));
+            else if (*c == "plugin")
+                code = make_error_code(static_cast<plugin_error::error>(*e));
+            else if (*c == "rule")
+                code = make_error_code(static_cast<rule_error::error>(*e));
+        }
+
+        handler(std::move(code), std::move(msg));
+    });
+}
+
+void controller::write(nlohmann::json message, io::write_handler handler)
+{
+    assert(message.is_object());
+    assert(stream_);
+
+    auto stream = stream_;
+
+    stream_->write(std::move(message), [this, stream, handler] (auto code) {
+        if (code)
+            stream_ = nullptr;
+        if (handler)
+            handler(std::move(code));
+    });
+}
+
+} // !ctl
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-ctl/irccd/ctl/controller.hpp	Sat Jul 07 15:40:46 2018 +0200
@@ -0,0 +1,136 @@
+/*
+ * controller.hpp -- main irccdctl interface
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_CTL_CONTROLLER_HPP
+#define IRCCD_CTL_CONTROLLER_HPP
+
+/**
+ * \file controller.hpp
+ * \brief Main irccdctl interface.
+ */
+
+#include <cassert>
+
+#include <irccd/connector.hpp>
+#include <irccd/stream.hpp>
+
+namespace irccd {
+
+namespace ctl {
+
+/**
+ * \brief Main irccdctl interface.
+ *
+ * This class is an easy frontend to issue commands to irccd, it uses an
+ * independant connection to perform the requests.
+ *
+ * This class is responsible of doing initial connection, performing checks and
+ * optional authentication.
+ *
+ * It is implemented in mind that connection are asynchronous even though this
+ * is not necessary.
+ */
+class controller {
+public:
+    /**
+     * Connection completion handler.
+     *
+     * This callback is called when connection has been completed or failed. In
+     * both case, the error code is set and the JSON object may contain the
+     * irccd program information.
+     */
+    using connect_handler = std::function<void (std::error_code, nlohmann::json)>;
+
+private:
+    std::unique_ptr<io::connector> connector_;
+    std::shared_ptr<io::stream> stream_;
+    std::string password_;
+
+    void authenticate(connect_handler, nlohmann::json);
+    void verify(connect_handler);
+
+public:
+    /**
+     * Construct the controller with its connection.
+     *
+     * \pre connector != nullptr
+     * \
+     * \note no connect attempt is done
+     */
+    inline controller(std::unique_ptr<io::connector> connector) noexcept
+        : connector_(std::move(connector))
+    {
+        assert(connector_);
+    }
+
+    /**
+     * Get the optional password set.
+     *
+     * \return the password
+     */
+    inline const std::string& get_password() const noexcept
+    {
+        return password_;
+    }
+
+    /**
+     * Set an optional password.
+     *
+     * An empty password means no authentication (default).
+     *
+     * \param password the password
+     * \note this must be called before connect
+     */
+    inline void set_password(std::string password) noexcept
+    {
+        password_ = std::move(password);
+    }
+
+    /**
+     * Attempt to connect to the irccd daemon.
+     *
+     * \pre handler != nullptr
+     * \param handler the handler
+     */
+    void connect(connect_handler handler);
+
+    /**
+     * Queue a receive operation, if receive operations are already running, it
+     * is queued and ran once ready.
+     *
+     * \pre handler != nullptr
+     * \param handler the recv handler
+     */
+    void read(io::read_handler handler);
+
+    /**
+     * Queue a send operation, if receive operations are already running, it is
+     * queued and ran once ready.
+     *
+     * \pre message.is_object()
+     * \param message the JSON message
+     * \param handler the optional completion handler
+     */
+    void write(nlohmann::json message, io::write_handler handler = nullptr);
+};
+
+} // !ctl
+
+} // !irccd
+
+#endif // !IRCCD_CTL_CONTROLLER_HPP
--- a/libirccd-test/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/libirccd-test/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -53,7 +53,7 @@
     LIBRARIES
         ${LIBRARIES}
         libirccd
-        libirccdctl
+        libirccd-ctl
     PUBLIC_INCLUDES
         $<BUILD_INTERFACE:${libirccd-test_SOURCE_DIR}>
     FLAGS
--- a/libirccd/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/libirccd/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -123,7 +123,7 @@
         ${libirccd_SOURCE_DIR}/CMakeLists.txt
         ${SOURCES}
     LIBRARIES
-        libcommon
+        libirccd-core
     PUBLIC_INCLUDES
         $<BUILD_INTERFACE:${libirccd_SOURCE_DIR}>
 )
--- a/libirccdctl/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-project(libirccdctl)
-
-set(
-    HEADERS
-    ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.hpp
-)
-
-set(
-    SOURCES
-    ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.cpp
-)
-
-irccd_define_library(
-    TARGET libirccdctl
-    EXPORT
-    HEADERS ${HEADERS}
-    HEADERS_DIRECTORY irccd/ctl
-    SOURCES
-        ${libirccdctl_SOURCE_DIR}/CMakeLists.txt
-        ${SOURCES}
-    LIBRARIES
-        libirccd
-    PUBLIC_INCLUDES
-        $<BUILD_INTERFACE:${libirccdctl_SOURCE_DIR}>
-)
--- a/libirccdctl/irccd/ctl/controller.cpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-/*
- * controller.cpp -- main irccdctl interface
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-
-#include <irccd/sysconfig.hpp>
-#include <irccd/json_util.hpp>
-
-#include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server.hpp>
-#include <irccd/daemon/plugin.hpp>
-#include <irccd/daemon/rule.hpp>
-
-#include "controller.hpp"
-
-namespace irccd {
-
-namespace ctl {
-
-void controller::authenticate(connect_handler handler, nlohmann::json info)
-{
-    const auto cmd = nlohmann::json::object({
-        { "command",    "auth"      },
-        { "password",   password_   }
-    });
-
-    write(cmd, [handler, info, this] (auto code) {
-        if (code) {
-            handler(std::move(code), nullptr);
-            return;
-        }
-
-        read([handler, info] (auto code, auto) {
-            handler(std::move(code), std::move(info));
-        });
-    });
-}
-
-void controller::verify(connect_handler handler)
-{
-    read([handler, this] (auto code, auto message) {
-        if (code) {
-            handler(std::move(code), std::move(message));
-            return;
-        }
-
-        const json_util::document doc(message);
-        const auto program = doc.get<std::string>("program");
-        const auto major = doc.get<int>("major");
-
-        if (!program && *program != "irccd")
-            handler(irccd_error::not_irccd, std::move(message));
-        else if (major && *major != IRCCD_VERSION_MAJOR)
-            handler(irccd_error::incompatible_version, std::move(message));
-        else {
-            if (!password_.empty())
-                authenticate(std::move(handler), message);
-            else
-                handler(code, std::move(message));
-        }
-    });
-}
-
-void controller::connect(connect_handler handler)
-{
-    assert(handler);
-
-    connector_->connect([handler, this] (auto code, auto stream) {
-        if (code)
-            handler(std::move(code), nullptr);
-        else {
-            stream_ = std::move(stream);
-            verify(std::move(handler));
-        }
-    });
-}
-
-void controller::read(io::read_handler handler)
-{
-    assert(handler);
-    assert(stream_);
-
-    auto stream = stream_;
-
-    stream_->read([this, handler, stream] (auto code, auto msg) {
-        if (code) {
-            stream_ = nullptr;
-            handler(std::move(code), std::move(msg));
-            return;
-        }
-
-        const json_util::document doc(msg);
-        const auto e = doc.get<int>("error");
-        const auto c = doc.get<std::string>("errorCategory");
-
-        if (e && c) {
-            if (*c == "irccd")
-                code = make_error_code(static_cast<irccd_error::error>(*e));
-            else if (*c == "server")
-                code = make_error_code(static_cast<server_error::error>(*e));
-            else if (*c == "plugin")
-                code = make_error_code(static_cast<plugin_error::error>(*e));
-            else if (*c == "rule")
-                code = make_error_code(static_cast<rule_error::error>(*e));
-        }
-
-        handler(std::move(code), std::move(msg));
-    });
-}
-
-void controller::write(nlohmann::json message, io::write_handler handler)
-{
-    assert(message.is_object());
-    assert(stream_);
-
-    auto stream = stream_;
-
-    stream_->write(std::move(message), [this, stream, handler] (auto code) {
-        if (code)
-            stream_ = nullptr;
-        if (handler)
-            handler(std::move(code));
-    });
-}
-
-} // !ctl
-
-} // !irccd
--- a/libirccdctl/irccd/ctl/controller.hpp	Sat Jul 07 14:03:04 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +0,0 @@
-/*
- * controller.hpp -- main irccdctl interface
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_CTL_CONTROLLER_HPP
-#define IRCCD_CTL_CONTROLLER_HPP
-
-/**
- * \file controller.hpp
- * \brief Main irccdctl interface.
- */
-
-#include <cassert>
-
-#include <irccd/connector.hpp>
-#include <irccd/stream.hpp>
-
-namespace irccd {
-
-namespace ctl {
-
-/**
- * \brief Main irccdctl interface.
- *
- * This class is an easy frontend to issue commands to irccd, it uses an
- * independant connection to perform the requests.
- *
- * This class is responsible of doing initial connection, performing checks and
- * optional authentication.
- *
- * It is implemented in mind that connection are asynchronous even though this
- * is not necessary.
- */
-class controller {
-public:
-    /**
-     * Connection completion handler.
-     *
-     * This callback is called when connection has been completed or failed. In
-     * both case, the error code is set and the JSON object may contain the
-     * irccd program information.
-     */
-    using connect_handler = std::function<void (std::error_code, nlohmann::json)>;
-
-private:
-    std::unique_ptr<io::connector> connector_;
-    std::shared_ptr<io::stream> stream_;
-    std::string password_;
-
-    void authenticate(connect_handler, nlohmann::json);
-    void verify(connect_handler);
-
-public:
-    /**
-     * Construct the controller with its connection.
-     *
-     * \pre connector != nullptr
-     * \
-     * \note no connect attempt is done
-     */
-    inline controller(std::unique_ptr<io::connector> connector) noexcept
-        : connector_(std::move(connector))
-    {
-        assert(connector_);
-    }
-
-    /**
-     * Get the optional password set.
-     *
-     * \return the password
-     */
-    inline const std::string& get_password() const noexcept
-    {
-        return password_;
-    }
-
-    /**
-     * Set an optional password.
-     *
-     * An empty password means no authentication (default).
-     *
-     * \param password the password
-     * \note this must be called before connect
-     */
-    inline void set_password(std::string password) noexcept
-    {
-        password_ = std::move(password);
-    }
-
-    /**
-     * Attempt to connect to the irccd daemon.
-     *
-     * \pre handler != nullptr
-     * \param handler the handler
-     */
-    void connect(connect_handler handler);
-
-    /**
-     * Queue a receive operation, if receive operations are already running, it
-     * is queued and ran once ready.
-     *
-     * \pre handler != nullptr
-     * \param handler the recv handler
-     */
-    void read(io::read_handler handler);
-
-    /**
-     * Queue a send operation, if receive operations are already running, it is
-     * queued and ran once ready.
-     *
-     * \pre message.is_object()
-     * \param message the JSON message
-     * \param handler the optional completion handler
-     */
-    void write(nlohmann::json message, io::write_handler handler = nullptr);
-};
-
-} // !ctl
-
-} // !irccd
-
-#endif // !IRCCD_CTL_CONTROLLER_HPP
--- a/tests/src/libirccd/command-plugin-config/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-plugin-config/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-plugin-config
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-plugin-info/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-plugin-info/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-plugin-info
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-plugin-list/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-plugin-list/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-plugin-list
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-plugin-load/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-plugin-load/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-plugin-load
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-plugin-reload/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-plugin-reload/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-plugin-reload
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-plugin-unload/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-plugin-unload/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-plugin-unload
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-rule-add/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-rule-add/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-rule-add
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-rule-edit/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-rule-edit/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-rule-edit
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-rule-info/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-rule-info/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-rule-info
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-rule-list/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-rule-list/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-rule-list
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-rule-move/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-rule-move/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-rule-move
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-rule-remove/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-rule-remove/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-rule-remove
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-connect/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-connect/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-connect
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-disconnect/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-disconnect/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-disconnect
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-info/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-info/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-info
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-invite/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-invite/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-invite
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-join/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-join/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-join
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-kick/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-kick/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-kick
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-list/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-list/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-list
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-me/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-me/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-me
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-message/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-message/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-message
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-mode/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-mode/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-mode
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-nick/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-nick/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-nick
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-notice/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-notice/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-notice
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-part/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-part/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-part
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-reconnect/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-reconnect/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-reconnect
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )
--- a/tests/src/libirccd/command-server-topic/CMakeLists.txt	Sat Jul 07 14:03:04 2018 +0200
+++ b/tests/src/libirccd/command-server-topic/CMakeLists.txt	Sat Jul 07 15:40:46 2018 +0200
@@ -19,5 +19,5 @@
 irccd_define_test(
     NAME command-server-topic
     SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
+    LIBRARIES libirccd libirccd-ctl
 )