changeset 318:8457f5bd2036

Irccd: add missing bits for tests
author David Demelier <markand@malikania.fr>
date Sun, 23 Oct 2016 11:25:26 +0200
parents 249ccc1e4840
children 04e83ea7147b
files libirccd/CMakeLists.txt libirccd/irccd/command.cpp libirccd/irccd/command.hpp libirccd/irccd/irccd.cpp libirccd/irccd/irccd.hpp libirccd/irccd/server.hpp libirccd/irccd/service-command.cpp libirccd/irccd/service-command.hpp libirccd/irccd/service-transport.cpp libirccd/irccd/transport.cpp libirccd/irccd/transport.hpp libirccdctl/CMakeLists.txt libirccdctl/irccd/irccdctl.cpp libirccdctl/irccd/irccdctl.hpp
diffstat 14 files changed, 116 insertions(+), 1700 deletions(-) [+]
line wrap: on
line diff
--- a/libirccd/CMakeLists.txt	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/CMakeLists.txt	Sun Oct 23 11:25:26 2016 +0200
@@ -67,7 +67,6 @@
     ${libirccd_SOURCE_DIR}/irccd/cmd-server-part.cpp
     ${libirccd_SOURCE_DIR}/irccd/cmd-server-reconnect.cpp
     ${libirccd_SOURCE_DIR}/irccd/cmd-server-topic.cpp
-    ${libirccd_SOURCE_DIR}/irccd/command.cpp
     ${libirccd_SOURCE_DIR}/irccd/config.cpp
     ${libirccd_SOURCE_DIR}/irccd/irccd.cpp
     ${libirccd_SOURCE_DIR}/irccd/plugin-dynlib.cpp
--- a/libirccd/irccd/command.cpp	Sun Oct 23 11:20:29 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,272 +0,0 @@
-/*
- * command.cpp -- remote command
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <iomanip>
-#include <numeric>
-#include <sstream>
-
-#include <format.h>
-
-#include "command.hpp"
-#include "logger.hpp"
-#include "system.hpp"
-
-using namespace std::string_literals;
-
-using namespace fmt::literals;
-
-using json = nlohmann::json;
-
-namespace irccd {
-
-namespace {
-
-/*
- * typeName
- * ------------------------------------------------------------------
- *
- * Convert a JSON value type to string for convenience.
- */
-std::string typeName(nlohmann::json::value_t type) noexcept
-{
-    switch (type) {
-    case nlohmann::json::value_t::array:
-        return "array";
-    case nlohmann::json::value_t::boolean:
-        return "bool";
-    case nlohmann::json::value_t::number_float:
-        return "float";
-    case nlohmann::json::value_t::number_integer:
-        return "integer";
-    case nlohmann::json::value_t::number_unsigned:
-        return "unsigned";
-    case nlohmann::json::value_t::null:
-        return "null";
-    case nlohmann::json::value_t::object:
-        return "object";
-    case nlohmann::json::value_t::string:
-        return "string";
-    default:
-        return "";
-    }
-}
-
-/*
- * typeNameList
- * ------------------------------------------------------------------
- *
- * Construct a list of names to send a convenient error message if properties
- * are invalid, example: string, int or bool expected.
- */
-
-std::string typeNameList(const std::vector<json::value_t> &types)
-{
-    std::ostringstream oss;
-
-    if (types.size() == 1)
-        return typeName(types[0]);
-
-    for (std::size_t i = 0; i < types.size(); ++i) {
-        oss << typeName(types[i]);
-
-        if (i == types.size() - 2)
-            oss << " or ";
-        else if (i < types.size() - 1)
-            oss << ", ";
-    }
-
-    return oss.str();
-}
-
-} // !namespace
-
-/*
- * JSON errors
- * ------------------------------------------------------------------
- */
-
-MissingPropertyError::MissingPropertyError(std::string name, std::vector<nlohmann::json::value_t> types)
-    : m_name(std::move(name))
-    , m_types(std::move(types))
-{
-    m_message = "missing '" + m_name + "' property (" + typeNameList(m_types) + " expected)";
-}
-
-InvalidPropertyError::InvalidPropertyError(std::string name, nlohmann::json::value_t expected, nlohmann::json::value_t result)
-    : m_name(std::move(name))
-    , m_expected(expected)
-    , m_result(result)
-{
-    m_message += "invalid '" + m_name + "' property ";
-    m_message += "(" + typeName(expected) + " expected, ";
-    m_message += "got " + typeName(result) + ")";
-}
-
-PropertyRangeError::PropertyRangeError(std::string name, std::uint64_t min, std::uint64_t max, std::uint64_t value)
-    : m_name(std::move(name))
-    , m_min(min)
-    , m_max(max)
-    , m_value(value)
-{
-    assert(value < min || value > max);
-
-    m_message += "property '" + m_name + "' is out of range ";
-    m_message += std::to_string(min) + ".." + std::to_string(max) + ", got " + std::to_string(value);
-}
-
-PropertyError::PropertyError(std::string name, std::string message)
-    : m_name(std::move(name))
-{
-    m_message += "property '" + m_name + "': " + message;
-}
-
-/*
- * Command implementation
- * ------------------------------------------------------------------
- */
-
-std::string Command::usage() const
-{
-    std::ostringstream oss;
-
-    oss << m_name << " ";
-
-    // Options.
-    auto optlist = options();
-
-    if (optlist.size() > 0) {
-        for (const auto &opt : optlist) {
-            oss << "[";
-
-            /*
-             * Long options are too big so only show them in the help
-             * command usage or only if no short option is available.
-             */
-            if (opt.simpleKey().size() > 0)
-                oss << "-" << opt.simpleKey();
-            else if (opt.longKey().size() > 0)
-                oss << " --" << opt.longKey();
-
-            oss << (opt.arg().empty() ? "" : " ") << opt.arg() << "] ";
-        }
-    }
-
-    // Arguments.
-    auto argslist = args();
-
-    if (argslist.size() > 0) {
-        for (const auto &arg : argslist)
-            oss << (arg.required() ? "" : "[")
-                << arg.name()
-                << (arg.required() ? "" : "]") << " ";
-    }
-
-    return oss.str();
-}
-
-std::string Command::help() const
-{
-    std::ostringstream oss;
-
-    oss << "usage: " << sys::programName() << " " << m_name;
-
-    // Options summary.
-    if (options().size() > 0)
-        oss << " [options...]";
-
-    // Arguments summary.
-    if (args().size() > 0) {
-        oss << " ";
-
-        for (const auto &arg : args())
-            oss << (arg.required() ? "" : "[") << arg.name() << (arg.required() ? "" : "]") << " ";
-    }
-
-    // Description.
-    oss << "\n\n" << m_description << "\n\n";
-
-    // Options.
-    if (options().size() > 0) {
-        oss << "Options:\n";
-
-        for (const auto &opt : options()) {
-            std::ostringstream optoss;
-
-            // Construct the line for the option in a single string to pad it correctly.
-            optoss << "  ";
-            optoss << (!opt.simpleKey().empty() ? ("-"s + opt.simpleKey() + " ") : "   ");
-            optoss << (!opt.longKey().empty() ? ("--"s + opt.longKey() + " "s) : "");
-            optoss << opt.arg();
-
-            // Add it padded with spaces.
-            oss << std::left << std::setw(28) << optoss.str();
-            oss << opt.description() << "\n";
-        }
-    }
-
-    return oss.str();
-}
-
-unsigned Command::min() const noexcept
-{
-    auto list = args();
-
-    return std::accumulate(list.begin(), list.end(), 0U, [] (unsigned i, const auto &arg) noexcept -> unsigned {
-        return i + (arg.required() ? 1 : 0);
-    });
-}
-
-unsigned Command::max() const noexcept
-{
-    return (unsigned)args().size();
-}
-
-nlohmann::json Command::request(Irccdctl &, const CommandRequest &) const
-{
-    return nlohmann::json::object({});
-}
-
-nlohmann::json Command::exec(Irccd &, const nlohmann::json &request) const
-{
-    // Verify that requested properties are present in the request.
-    for (const auto &prop : properties()) {
-        auto it = request.find(prop.name());
-
-        if (it == request.end())
-            throw std::invalid_argument("missing '{}' property"_format(prop.name()));
-
-        if (std::find(prop.types().begin(), prop.types().end(), it->type()) == prop.types().end()) {
-            auto expected = typeNameList(prop.types());
-            auto got = typeName(it->type());
-
-            throw std::invalid_argument("invalid '{}' property ({} expected, got {})"_format(prop.name(), expected, got));
-        }
-    }
-
-    return nlohmann::json::object({});
-}
-
-void Command::result(Irccdctl &, const nlohmann::json &response) const
-{
-    auto it = response.find("error");
-
-    if (it != response.end() && it->is_string())
-        log::warning() << "irccdctl: " << it->dump() << std::endl;
-}
-
-} // !irccd
--- a/libirccd/irccd/command.hpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/irccd/command.hpp	Sun Oct 23 11:25:26 2016 +0200
@@ -35,336 +35,26 @@
 
 class Irccd;
 class Irccdctl;
-
-/**
- * \brief A JSON property is missing.
- */
-class MissingPropertyError : public std::exception {
-private:
-    std::string m_message;
-    std::string m_name;
-    std::vector<nlohmann::json::value_t> m_types;
-
-public:
-    /**
-     * Constructor.
-     */
-    MissingPropertyError(std::string name, std::vector<nlohmann::json::value_t> types);
-
-    /**
-     * Get human error message.
-     *
-     * \return a message
-     */
-    const char *what() const noexcept override
-    {
-        return m_message.c_str();
-    }
-};
+class TransportClient;
 
 /**
- * \brief A JSON property is invalid
+ * \brief Server side remote command
  */
-class InvalidPropertyError : public std::exception {
-private:
-    std::string m_message;
-    std::string m_name;
-
-    nlohmann::json::value_t m_expected;
-    nlohmann::json::value_t m_result;
-
-public:
-    /**
-     * Constructor.
-     *
-     * \param name the property name
-     * \param expected the expected type
-     * \param result the type received
-     */
-    InvalidPropertyError(std::string name, nlohmann::json::value_t expected, nlohmann::json::value_t result);
-
-    /**
-     * Get human error message.
-     *
-     * \return a message
-     */
-    const char *what() const noexcept override
-    {
-        return m_message.c_str();
-    }
-};
-
-/**
- * \brief Property range error.
- */
-class PropertyRangeError : public std::exception {
+class Command {
 private:
-    std::string m_message;
-    std::string m_name;
-    std::uint64_t m_min;
-    std::uint64_t m_max;
-    std::uint64_t m_value;
-
-public:
-    /**
-     * Constructor.
-     *
-     * \pre value < min || value > max
-     * \param name the property name
-     * \param min the minimum value
-     * \param max the maximum value
-     * \param value the actual value
-     */
-    PropertyRangeError(std::string name, std::uint64_t min, std::uint64_t max, std::uint64_t value);
-
-    /**
-     * Get human error message.
-     *
-     * \return a message
-     */
-    const char *what() const noexcept override
-    {
-        return m_message.c_str();
-    }
-};
-
-/**
- * \brief Generic error for JSON properties.
- */
-class PropertyError : public std::exception {
-private:
-    std::string m_message;
     std::string m_name;
 
 public:
     /**
-     * Constructor.
-     *
-     * \param name the property name
-     * \param message the error message
-     */
-    PropertyError(std::string name, std::string message);
-
-    /**
-     * Get human error message.
-     *
-     * \return a message
-     */
-    const char *what() const noexcept override
-    {
-        return m_message.c_str();
-    }
-};
-
-
-/**
- * \brief Namespace for remote commands.
- */
-//namespace command {
-
-/**
- * \brief Command line arguments to irccdctl.
- *
- * This class contains the resolved arguments from command line that can apply
- * to the command.
- */
-class CommandRequest {
-public:
-    /**
-     * The options given by command line.
-     */
-    using Options = std::multimap<std::string, std::string>;
-
-    /**
-     * Command line arguments in the same order.
-     */
-    using Args = std::vector<std::string>;
-
-private:
-    Options m_options;
-    Args m_args;
-
-public:
-    /**
-     * Construct the request.
+     * Construct a command.
      *
-     * \param options the options
-     * \param args the arguments
-     */
-    inline CommandRequest(Options options, Args args) noexcept
-        : m_options(std::move(options))
-        , m_args(std::move(args))
-    {
-    }
-
-    /**
-     * Get the arguments.
-     *
-     * \return the arguments
-     */
-    inline const Args &args() const noexcept
-    {
-        return m_args;
-    }
-
-    /**
-     * Get the options.
-     *
-     * \return the options
-     */
-    inline const Options &options() const noexcept
-    {
-        return m_options;
-    }
-
-    /**
-     * Get the number of arguments.
-     *
-     * \return the number of arguments
-     */
-    inline unsigned length() const noexcept
-    {
-        return (unsigned)m_args.size();
-    }
-
-    /**
-     * Check if the request has the given option id.
-     *
-     * \param option the option id
-     * \return true if the option is available
-     */
-    inline bool has(const std::string &option) const noexcept
-    {
-        return m_options.count(option) != 0;
-    }
-
-    /**
-     * Get the argument at the specified index.
-     *
-     * \pre index < length()
-     * \param index the argument index
-     * \return the argument
+     * \pre !name.empty()
+     * \param name the command name
      */
-    inline const std::string &arg(unsigned index) const noexcept
-    {
-        assert(index < m_args.size());
-
-        return m_args[index];
-    }
-
-    /**
-     * Get the argument or default value if not available.
-     *
-     * \param index the index
-     * \param defaultValue the value if index is out of range
-     * \return the argument
-     */
-    inline std::string argOr(unsigned index, std::string defaultValue) const noexcept
-    {
-        return index < m_args.size() ? m_args[index] : defaultValue;
-    }
-
-    /**
-     * Get the given option by its id.
-     *
-     * \pre has(key)
-     * \param key the option id
-     * \return the option
-     */
-    inline const std::string &option(const std::string &key) const noexcept
-    {
-        assert(m_options.count(key) != 0);
-
-        return m_options.find(key)->second;
-    }
-
-    /**
-     * Get the given option by its id or defaultValue if not found.
-     *
-     * \param key the option id
-     * \param defaultValue the value replacement
-     * \return the option
-     */
-    inline std::string optionOr(const std::string &key, std::string defaultValue) const noexcept
-    {
-        auto it = m_options.find(key);
-
-        if (it == m_options.end())
-            return defaultValue;
-
-        return it->second;
-    }
-};
-
-/**
- * \brief Invokable command.
- *
- * A remote command is a invokable command in the irccd daemon. You can register
- * dynamically any remote command you like using Application::addCommand.
- *
- * The remote command will be usable directly from irccdctl without any other
- * code.
- *
- * A remote command can have options and arguments. Options always come first,
- * before arguments.
- *
- * The command workflow is defined as follow:
- *
- * 1. User wants to invoke a command, request() is called and return a JSON
- *    object containaing the request, it it send to the daemon.
- *
- * 2. The daemon receive the request and execute it using exec(). It returns a
- *    JSON object containint the request result or error if any.
- *
- * 3. Finally, the command receives the result in result() function and user can
- *    manipulate it. For convenience, the default implementation shows the error
- *    if any.
- */
-class Command {
-public:
-    /**
-     * \brief Defines available options for this command.
-     */
-    class Option;
-
-    /**
-     * \brief Defines available arguments for this command.
-     */
-    class Arg;
-
-    /**
-     * \brief Defines properties that must be available in the JSON request.
-     */
-    class Property;
-
-private:
-    std::string m_name;
-    std::string m_category;
-    std::string m_description;
-    bool m_visible;
-
-public:
-    /**
-     * Create the remote command.
-     *
-     * \pre name must not be empty
-     * \pre category must not be empty
-     * \param name the command name (e.g. server-list)
-     * \param category the category (e.g. Server)
-     * \param description a one line description with no dots, no new line
-     * \param visible true if the command should be visible without verbosity
-     */
-    inline Command(std::string name,
-                   std::string category,
-                   std::string description,
-                   bool visible = true) noexcept
+    inline Command(std::string name) noexcept
         : m_name(std::move(name))
-        , m_category(std::move(category))
-        , m_description(std::move(description))
-        , m_visible(visible)
     {
         assert(!m_name.empty());
-        assert(!m_category.empty());
     }
 
     /**
@@ -383,325 +73,19 @@
     }
 
     /**
-     * Get the command category.
-     *
-     * Irccdctl will sort commands by categories.
-     *
-     * \return the category
-     */
-    inline const std::string &category() const noexcept
-    {
-        return m_category;
-    }
-
-    /**
-     * Get the command description.
-     *
-     * \return the description
-     */
-    inline const std::string &description() const noexcept
-    {
-        return m_description;
-    }
-
-    /**
-     * Hide the command in non-verbose mode.
-     *
-     * \return true if the command should be visible in non-verbose mode
-     */
-    inline bool visible() const noexcept
-    {
-        return m_visible;
-    }
-
-    /**
-     * Return the command documentation usage.
-     *
-     * \return the usage
-     */
-    IRCCD_EXPORT std::string usage() const;
-
-    /**
-     * Return the help message.
-     *
-     * \return the help message
-     */
-    IRCCD_EXPORT std::string help() const;
-
-    /**
-     * Get the supported irccdctl options.
-     *
-     * \return the options
-     */
-    virtual std::vector<Option> options() const
-    {
-        return {};
-    }
-
-    /**
-     * Get the supported arguments.
-     *
-     * \return the arguments
-     */
-    virtual std::vector<Arg> args() const
-    {
-        return {};
-    }
-
-    /**
-     * Get the properties required in the JSON request.
-     *
-     * Default implementation returns empty list.
+     * Execute the command.
      *
-     * \return the required properties
-     * \note Put only **required** properties
-     */
-    virtual std::vector<Property> properties() const
-    {
-        return {};
-    }
-
-    /**
-     * Get the minimum number of arguments required.
-     *
-     * \return the minimum
-     */
-    IRCCD_EXPORT unsigned min() const noexcept;
-
-    /**
-     * Get the maximum number of arguments required.
-     *
-     * \return the maximum
-     */
-    IRCCD_EXPORT unsigned max() const noexcept;
-
-    /**
-     * Prepare a JSON request to the daemon.
-     *
-     * If the command is local and does not need to send anything to irccd's
-     * instance, return a null JSON value.
-     *
-     * The default implementation just send the command name with no arguments.
-     *
-     * \param irccdctl the irccdctl instance
-     * \param args the command line arguments and options
-     * \return the JSON object to send to the daemon
-     * \post the returned JSON value must be an object
-     */
-    IRCCD_EXPORT virtual nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const;
-
-    /**
-     * Execute the command in the daemon.
-     *
-     * The user can return an object with any properties to forward to the
-     * client. Irccd will automatically add the command name and the appropriate
-     * status code.
-     *
-     * The default return an empty object which indicates success.
-     *
-     * If any exception is thrown from this function, it is forwarded to the
-     * client as error status.
-     *
-     * \param irccd the instance
-     * \param request the JSON request
-     * \return the response
-     */
-    IRCCD_EXPORT virtual nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const;
-
-    /**
-     * What to do when receiving the response from irccd.
-     *
-     * This default implementation just check for an error string and shows it
-     * if any.
-     *
-     * \param irccdctl the irccdctl instance
-     * \param response the JSON response
-     */
-    IRCCD_EXPORT virtual void result(Irccdctl &irccdctl, const nlohmann::json &response) const;
-};
-
-/**
- * \brief Option description for a command.
- */
-class Command::Option {
-private:
-    std::string m_id;
-    std::string m_simple;
-    std::string m_long;
-    std::string m_arg;
-    std::string m_description;
-
-public:
-    /**
-     * Constructor an option description.
-     *
-     * Simple and long keys must not start with '-' or '--', they will be added
-     * automatically.
-     *
-     * If arg is not empty, the option takes an argument.
+     * If the command throw an exception, the error is sent to the client so be
+     * careful about sensitive information.
      *
-     * \pre id must not be empty
-     * \pre at least simpleKey or longKey must not be empty
-     * \pre description must not be empty
-     * \param id the option id
-     * \param simpleKey the key the option key
-     * \param longKey the long option name
-     * \param arg the argument name if needed
-     * \param description the description
-     */
-    inline Option(std::string id,
-              std::string simpleKey,
-              std::string longKey,
-              std::string arg,
-              std::string description) noexcept
-        : m_id(std::move(id))
-        , m_simple(std::move(simpleKey))
-        , m_long(std::move(longKey))
-        , m_arg(std::move(arg))
-        , m_description(std::move(description))
-    {
-        assert(!m_id.empty());
-        assert(!m_simple.empty() || !m_long.empty());
-        assert(!m_description.empty());
-    }
-
-    /**
-     * Get the id.
+     * The implementation should use client.success() or client.error() to send
+     * some data.
      *
-     * \return the id
-     */
-    inline const std::string &id() const noexcept
-    {
-        return m_id;
-    }
-
-    /**
-     * Get the option key.
-     *
-     * \return the key
-     */
-    inline const std::string &simpleKey() const noexcept
-    {
-        return m_simple;
-    }
-
-    /**
-     * Get the long option.
-     *
-     * \return the long option
-     */
-    inline const std::string &longKey() const noexcept
-    {
-        return m_long;
-    }
-
-    /**
-     * Get the option description.
-     *
-     * \return the description
-     */
-    inline const std::string &description() const noexcept
-    {
-        return m_description;
-    }
-
-    /**
-     * Get the option argument name.
-     *
-     * \return the argument name if any
+     * \param irccd the irccd instance
+     * \param client the client
+     * \param args the client arguments
      */
-    inline const std::string &arg() const noexcept
-    {
-        return m_arg;
-    }
-};
-
-/**
- * \brief Argument description for command.
- */
-class Command::Arg {
-private:
-    std::string m_name;
-    bool m_required;
-
-public:
-    /**
-     * Construct an argument.
-     *
-     * \param name the name
-     * \param required true if the argument is required
-     */
-    inline Arg(std::string name, bool required) noexcept
-        : m_name(std::move(name))
-        , m_required(required)
-    {
-    }
-
-    /**
-     * Get the argument name.
-     *
-     * \return the name
-     */
-    inline const std::string &name() const noexcept
-    {
-        return m_name;
-    }
-
-    /**
-     * Tells if the argument is required.
-     *
-     * \return true if required
-     */
-    inline bool required() const noexcept
-    {
-        return m_required;
-    }
-};
-
-/**
- * \brief Property description for JSON request.
- */
-class Command::Property {
-private:
-    std::string m_name;
-    std::vector<nlohmann::json::value_t> m_types;
-
-public:
-    /**
-     * Construct the property description.
-     *
-     * \pre !name.empty()
-     * \pre types.size() >= 1
-     * \param name the name
-     * \param types the json types allowed
-     */
-    inline Property(std::string name, std::vector<nlohmann::json::value_t> types = { nlohmann::json::value_t::string }) noexcept
-        : m_name(std::move(name))
-        , m_types(std::move(types))
-    {
-        assert(!m_name.empty());
-        assert(m_types.size() >= 1);
-    }
-
-    /**
-     * Get the property name.
-     *
-     * \return the name
-     */
-    inline const std::string &name() const noexcept
-    {
-        return m_name;
-    }
-
-    /**
-     * Get the property types.
-     *
-     * \return the types
-     */
-    inline const std::vector<nlohmann::json::value_t> &types() const noexcept
-    {
-        return m_types;
-    }
+    IRCCD_EXPORT virtual void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) = 0;
 };
 
 } // !irccd
--- a/libirccd/irccd/irccd.cpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/irccd/irccd.cpp	Sun Oct 23 11:25:26 2016 +0200
@@ -53,10 +53,8 @@
 
 void Irccd::run()
 {
-    while (m_running) {
+    while (m_running)
         util::poller::poll(250, *m_interruptService, *m_servers, *m_transports);
-        dispatch();
-    }
 }
 
 void Irccd::prepare(fd_set &in, fd_set &out, net::Handle &max)
@@ -67,10 +65,7 @@
 void Irccd::sync(fd_set &in, fd_set &out)
 {
     util::poller::sync(in, out, *m_interruptService, *m_servers, *m_transports);
-}
 
-void Irccd::dispatch()
-{
     /*
      * Make a copy because the events can add other events while we are
      * iterating it. Also lock because the timers may alter these events too.
--- a/libirccd/irccd/irccd.hpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/irccd/irccd.hpp	Sun Oct 23 11:25:26 2016 +0200
@@ -154,16 +154,11 @@
     IRCCD_EXPORT void post(std::function<void (Irccd &)> ev) noexcept;
 
     /**
-     * Loop forever by calling poll() and dispatch() indefinitely.
+     * Loop forever by calling prepare and sync indefinitely.
      */
     IRCCD_EXPORT void run();
 
     /**
-     * Dispatch the pending events, usually after calling poll().
-     */
-    IRCCD_EXPORT void dispatch();
-
-    /**
      * Request to stop, usually from a signal.
      */
     IRCCD_EXPORT void stop();
--- a/libirccd/irccd/server.hpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/irccd/server.hpp	Sun Oct 23 11:25:26 2016 +0200
@@ -633,7 +633,7 @@
      *
      * \param nickname the nickname
      */
-    IRCCD_EXPORT void setNickname(std::string nickname);
+    IRCCD_EXPORT virtual void setNickname(std::string nickname);
 
     /**
      * Get the username.
@@ -816,7 +816,7 @@
     /**
      * Asks for a reconnection.
      */
-    IRCCD_EXPORT void reconnect() noexcept;
+    IRCCD_EXPORT virtual void reconnect() noexcept;
 
     /**
      * Prepare the IRC session.
--- a/libirccd/irccd/service-command.cpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/irccd/service-command.cpp	Sun Oct 23 11:25:26 2016 +0200
@@ -36,4 +36,16 @@
     return it == m_commands.end() ? nullptr : *it;
 }
 
+void CommandService::add(std::shared_ptr<Command> command)
+{
+    auto it = std::find_if(m_commands.begin(), m_commands.end(), [&] (const auto &cmd) {
+        return cmd->name() == command->name();
+    });
+
+    if (it != m_commands.end())
+        *it = std::move(command);
+    else
+        m_commands.push_back(std::move(command));
+}
+
 } // !irccd
--- a/libirccd/irccd/service-command.hpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/irccd/service-command.hpp	Sun Oct 23 11:25:26 2016 +0200
@@ -66,6 +66,14 @@
      * \return the command or empty one if not found
      */
     IRCCD_EXPORT std::shared_ptr<Command> find(const std::string &name) const noexcept;
+
+    /**
+     * Add a command or replace existing one.
+     *
+     * \pre command != nullptr
+     * \param command the command name
+     */
+    IRCCD_EXPORT void add(std::shared_ptr<Command> command);
 };
 
 } // !irccd
--- a/libirccd/irccd/service-transport.cpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/irccd/service-transport.cpp	Sun Oct 23 11:25:26 2016 +0200
@@ -37,7 +37,6 @@
         if (!tc)
             return;
 
-        // 1. Check if the Json object is valid.
         auto name = object.find("command");
         if (name == object.end() || !name->is_string()) {
             // TODO: send error.
@@ -45,36 +44,17 @@
             return;
         }
 
-        // 2. Search for a command
         auto cmd = m_irccd.commands().find(*name);
 
-        if (!cmd) {
-            // TODO: send error again.
-            log::warning("command does not exists");
-            return;
+        if (!cmd)
+            tc->error(*name, "command does not exist");
+        else {
+            try {
+                cmd->exec(m_irccd, *tc, object);
+            } catch (const std::exception &ex) {
+                tc->error(cmd->name(), ex.what());
+            }
         }
-
-        // 3. Try to execute it.
-        auto response = nlohmann::json::object({});
-
-        try {
-            response = cmd->exec(m_irccd, object);
-
-            // Adjust if command has returned something else.
-            if (!response.is_object())
-                response = nlohmann::json::object({});
-
-            response.push_back({"status", true});
-        } catch (const std::exception &ex) {
-            response.push_back({"status", false});
-            response.push_back({"error", ex.what()});
-        }
-
-        // 4. Store the command name result.
-        response.push_back({"response", *name});
-
-        // 5. Send the result.
-        tc->send(response);
     });
 }
 
--- a/libirccd/irccd/transport.cpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/irccd/transport.cpp	Sun Oct 23 11:25:26 2016 +0200
@@ -232,6 +232,35 @@
     m_output += "\r\n\r\n";
 }
 
+void TransportClient::success(const std::string &cmd, nlohmann::json extra)
+{
+    assert(extra.is_object() || extra.is_null());
+
+    if (!extra.is_object())
+        extra = nlohmann::json::object();
+
+    extra["command"] = cmd;
+    extra["status"] = true;
+
+    m_output += extra.dump();
+    m_output += "\r\n\r\n";
+}
+
+void TransportClient::error(const std::string &cmd, const std::string &error, nlohmann::json extra)
+{
+    assert(extra.is_object() || extra.is_null());
+
+    if (!extra.is_object())
+        extra = nlohmann::json::object();
+
+    extra["command"] = cmd;
+    extra["status"] = false;
+    extra["error"] = error;
+
+    m_output += extra.dump();
+    m_output += "\r\n\r\n";
+}
+
 /*
  * TransportClientTls
  * ------------------------------------------------------------------
--- a/libirccd/irccd/transport.hpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccd/irccd/transport.hpp	Sun Oct 23 11:25:26 2016 +0200
@@ -155,6 +155,27 @@
      * \copydoc Service::sync
      */
     IRCCD_EXPORT virtual void sync(fd_set &in, fd_set &out);
+
+    /**
+     * Send a successful command to the client with optional extra data
+     *
+     * \pre extra must be null or object
+     * \param cmd the command name
+     * \param extra the optional extra data
+     */
+    IRCCD_EXPORT void success(const std::string &cmd, nlohmann::json extra = nullptr);
+
+    /**
+     * Send an error status to the client.
+     *
+     * \pre extra must be null or object
+     * \param cmd the command name
+     * \param error the error string
+     * \param extra the optional extra data
+     */
+    IRCCD_EXPORT void error(const std::string &cmd,
+                            const std::string &error,
+                            nlohmann::json extra = nullptr);
 };
 
 /*
--- a/libirccdctl/CMakeLists.txt	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccdctl/CMakeLists.txt	Sun Oct 23 11:25:26 2016 +0200
@@ -19,4 +19,5 @@
         ${HEADERS}
         ${SOURCES}
     LIBRARIES libcommon
+    PUBLIC_INCLUDES ${libirccdctl_SOURCE_DIR}/irccd
 )
--- a/libirccdctl/irccd/irccdctl.cpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccdctl/irccd/irccdctl.cpp	Sun Oct 23 11:25:26 2016 +0200
@@ -16,631 +16,4 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <format.h>
-
-#include "command.hpp"
-#include "client.hpp"
-#include "elapsed-timer.hpp"
-#include "fs.hpp"
-#include "ini.hpp"
-#include "irccdctl.hpp"
-#include "logger.hpp"
-#include "options.hpp"
-#include "path.hpp"
-#include "system.hpp"
-#include "util.hpp"
-
-using namespace std::string_literals;
-
-using namespace fmt::literals;
-
-namespace irccd {
-
-void Irccdctl::usage() const
-{
-    bool first = true;
-
-    for (const auto &cmd : m_commandService.commands()) {
-        log::warning() << (first ? "usage: " : "       ") << sys::programName() << " "
-                       << cmd->usage() << std::endl;
-        first = false;
-    }
-
-    std::exit(1);
-}
-
-void Irccdctl::help() const
-{
-    log::warning() << "usage: " << sys::programName() << " [options...] <command> [command-options...] [command-args...]\n\n";
-    log::warning() << "General options:\n";
-    log::warning() << "\t-c, --config file\tspecify the configuration file\n";
-    log::warning() << "\t--help\t\t\tshow this help\n";
-    log::warning() << "\t-t, --type type\t\tspecify connection type\n";
-    log::warning() << "\t-v, --verbose\t\tbe verbose\n\n";
-    log::warning() << "Available options for type ip and ipv6 (-t, --type):\n";
-    log::warning() << "\t-h, --host address\tconnect to the specified address\n";
-    log::warning() << "\t-p, --port port\t\tuse the specified port number\n\n";
-    log::warning() << "Available options for type unix (-t, --type):\n";
-    log::warning() << "\t-P, --path file\t\tconnect to the specified socket file\n\n";
-    log::warning() << "Available commands:\n";
-
-    for (const auto &cmd : m_commandService.commands())
-        log::warning() << "\t" << std::left << std::setw(32)
-                       << cmd->name() << cmd->description() << std::endl;
-
-    log::warning() << "\nFor more information on a command, type " << sys::programName() << " help <command>" << std::endl;
-
-    std::exit(1);
-}
-
-/*
- * Configuration file parsing.
- * -------------------------------------------------------------------
- */
-
-/*
- * readConnectIp
- * -------------------------------------------------------------------
- *
- * Extract IP connection information from the config file.
- *
- * [connect]
- * type = "ip"
- * host = "ip or hostname"
- * port = "port number or service"
- * domain = "ipv4 or ipv6" (Optional, default: ipv4)
- * ssl = true | false
- */
-void Irccdctl::readConnectIp(const ini::Section &sc)
-{
-    ini::Section::const_iterator it;
-
-    std::string host, port;
-
-    if ((it = sc.find("host")) == sc.end())
-        throw std::invalid_argument("missing host parameter");
-
-    host = it->value();
-
-    if ((it = sc.find("port")) == sc.end())
-        throw std::invalid_argument("missing port parameter");
-
-    port = it->value();
-
-    int domain = AF_INET;
-
-    if ((it = sc.find("domain")) != sc.end()) {
-        if (it->value() == "ipv6")
-            domain = AF_INET6;
-        else if (it->value() == "ipv4")
-            domain = AF_INET;
-        else
-            throw std::invalid_argument("invalid domain: " + it->value());
-    }
-
-    m_address = net::resolveOne(host, port, domain, SOCK_STREAM);
-
-    if ((it = sc.find("ssl")) != sc.end() && util::isBoolean(it->value()))
-        m_connection = std::make_unique<TlsClient>();
-    else
-        m_connection = std::make_unique<Client>();
-}
-
-/*
- * readConnectLocal
- * -------------------------------------------------------------------
- *
- * Extract local connection for Unix.
- *
- * [connect]
- * type = "unix"
- * path = "path to socket file"
- */
-void Irccdctl::readConnectLocal(const ini::Section &sc)
-{
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-    auto it = sc.find("path");
-
-    if (it == sc.end())
-        throw std::invalid_argument("missing path parameter");
-
-    m_address = net::local::create(it->value());
-    m_connection = std::make_unique<Client>();
-#else
-    (void)sc;
-
-    throw std::invalid_argument("unix connection not supported on Windows");
-#endif
-}
-
-/*
- * readConnect
- * -------------------------------------------------------------------
- *
- * Generic function for reading the [connect] section.
- */
-void Irccdctl::readConnect(const ini::Section &sc)
-{
-    auto it = sc.find("type");
-
-    if (it == sc.end())
-        throw std::invalid_argument("missing type parameter");
-
-    if (it->value() == "ip")
-        readConnectIp(sc);
-    else if (it->value() == "unix")
-        readConnectLocal(sc);
-    else
-        throw std::invalid_argument("invalid type given: " + it->value());
-
-    auto password = sc.find("password");
-
-    if (password != sc.end())
-        m_connection->setPassword(password->value());
-}
-
-/*
- * readGeneral
- * -------------------------------------------------------------------
- *
- * Read the general section.
- *
- * [general]
- * verbose = true
- */
-void Irccdctl::readGeneral(const ini::Section &sc)
-{
-    auto verbose = sc.find("verbose");
-
-    if (verbose != sc.end())
-        log::setVerbose(util::isBoolean(verbose->value()));
-}
-
-/*
- * readAliases
- * -------------------------------------------------------------------
- *
- * Read aliases for irccdctl.
- *
- * [alias]
- * name = ( "command", "arg1, "...", "argn" )
- */
-void Irccdctl::readAliases(const ini::Section &sc)
-{
-    for (const auto &option : sc) {
-        // This is the alias name.
-        Alias alias(option.key());
-
-        // Iterate over the list of commands to execute for this alias.
-        for (const auto &repl : option) {
-            // This is the alias split string.
-            auto list = util::split(repl, " \t");
-
-            if (list.size() < 1)
-                throw std::invalid_argument("alias require at least one argument");
-
-            // First argument is the command/alias to execute.
-            auto command = list[0];
-
-            // Remove command name and puts arguments.
-            alias.push_back({std::move(command), std::vector<AliasArg>(list.begin() + 1, list.end())});
-        }
-
-        m_aliases.emplace(option.key(), std::move(alias));
-    }
-}
-
-void Irccdctl::read(const std::string &path)
-{
-    try {
-        ini::Document doc = ini::readFile(path);
-        ini::Document::const_iterator it;
-
-        if (!m_connection && (it = doc.find("connect")) != doc.end())
-            readConnect(*it);
-        if ((it = doc.find("general")) != doc.end())
-            readGeneral(*it);
-        if ((it = doc.find("alias")) != doc.end())
-            readAliases(*it);
-    } catch (const std::exception &ex) {
-        log::warning() << path << ": " << ex.what() << std::endl;
-    }
-}
-
-/*
- * Command line parsing.
- * -------------------------------------------------------------------
- */
-
-/*
- * parseConnectIp
- * ------------------------------------------------------------------
- *
- * Parse internet connection from command line.
- *
- * -t ip | ipv6
- * -h host or ip
- * -p port
- */
-void Irccdctl::parseConnectIp(const option::Result &options)
-{
-    option::Result::const_iterator it;
-
-    // Host (-h or --host).
-    std::string host;
-
-    if ((it = options.find("-h")) == options.end() && (it = options.find("--host")) == options.end())
-        throw std::invalid_argument("missing host argument (-h or --host)");
-
-    host = it->second;
-
-    // Port (-p or --port).
-    std::string port;
-
-    if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end())
-        throw std::invalid_argument("missing port argument (-p or --port)");
-
-    port = it->second;
-
-    // Domain
-    int domain = AF_INET;
-
-    if ((it = options.find("-t")) != options.end())
-        domain = it->second == "ipv6" ? AF_INET6 : AF_INET;
-    else if ((it = options.find("--type")) != options.end())
-        domain = it->second == "ipv6" ? AF_INET6: AF_INET;
-
-    m_address = net::resolveOne(host, port, domain, SOCK_STREAM);
-    m_connection = std::make_unique<Client>();
-}
-
-/*
- * parseConnectLocal
- * ------------------------------------------------------------------
- *
- * Parse local connection.
- *
- * -P file
- */
-void Irccdctl::parseConnectLocal(const option::Result &options)
-{
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-    option::Result::const_iterator it;
-
-    if ((it = options.find("-P")) == options.end() && (it = options.find("--path")) == options.end())
-        throw std::invalid_argument("missing path parameter (-P or --path)");
-
-    m_address = net::local::create(it->second, false);
-    m_connection = std::make_unique<Client>();
-#else
-    (void)options;
-
-    throw std::invalid_argument("unix connection not supported on Windows");
-#endif
-}
-
-/*
- * parseConnect
- * ------------------------------------------------------------------
- *
- * Generic parsing of command line option for connection.
- */
-void Irccdctl::parseConnect(const option::Result &options)
-{
-    assert(options.count("-t") > 0 || options.count("--type") > 0);
-
-    auto it = options.find("-t");
-
-    if (it == options.end())
-        it = options.find("--type");
-    if (it->second == "ip" || it->second == "ipv6")
-        return parseConnectIp(options);
-    if (it->second == "unix")
-        return parseConnectLocal(options);
-
-    throw std::invalid_argument("invalid type given: " + it->second);
-}
-
-option::Result Irccdctl::parse(int &argc, char **&argv)
-{
-    // 1. Parse command line options.
-    option::Options def{
-        { "-c",         true    },
-        { "--config",   true    },
-        { "-h",         true    },
-        { "--help",     false   },
-        { "--host",     true    },
-        { "-p",         true    },
-        { "--port",     true    },
-        { "-P",         true    },
-        { "--path",     true    },
-        { "-t",         true    },
-        { "--type",     true    },
-        { "-v",         false   },
-        { "--verbose",  false   }
-    };
-
-    option::Result result;
-
-    try {
-        result = option::read(argc, argv, def);
-
-        if (result.count("--help") != 0) {
-            usage();
-            // NOTREACHED
-        }
-
-        if (result.count("-v") != 0 || result.count("--verbose") != 0)
-            log::setVerbose(true);
-    } catch (const std::exception &ex) {
-        log::warning("{}: {}"_format(sys::programName(), ex.what()));
-        usage();
-    }
-
-    return result;
-}
-
-nlohmann::json Irccdctl::waitMessage(const std::string id)
-{
-    ElapsedTimer timer;
-
-    while (m_messages.empty() && m_connection->isConnected() && timer.elapsed() < m_timeout)
-        util::poller::poll(250, *m_connection);
-
-    if (m_messages.empty())
-        return nlohmann::json();
-
-    nlohmann::json value;
-
-    if (id == "") {
-        value = m_messages[0];
-        m_messages.erase(m_messages.begin());
-    } else {
-        auto it = std::find_if(m_messages.begin(), m_messages.end(), [&] (const auto &v) {
-            auto rt = v.find("response");
-
-            if (v.count("error") > 0 || (rt != v.end() && rt->is_string() && *rt == id))
-                return true;
-
-            return false;
-        });
-
-        // Remove the previous messages.
-        if (it != m_messages.end()) {
-            value = *it;
-            m_messages.erase(m_messages.begin(), it + 1);
-        }
-    }
-
-    auto error = value.find("error");
-
-    if (error != value.end() && error->is_string())
-        throw std::runtime_error(error->template get<std::string>());
-
-    return value;
-}
-
-nlohmann::json Irccdctl::waitEvent()
-{
-    ElapsedTimer timer;
-
-    while (m_events.empty() && m_connection->isConnected() && timer.elapsed() < m_timeout)
-        util::poller::poll(250, *m_connection);
-
-    if (m_events.empty())
-        return nullptr;
-
-    auto first = m_events.front();
-    m_events.erase(m_events.begin());
-
-    return first;
-}
-
-nlohmann::json Irccdctl::exec(const Command &cmd, std::vector<std::string> args)
-{
-    // 1. Build options from command line arguments.
-    option::Options def;
-
-    for (const auto &opt : cmd.options()) {
-        // parser::read needs '-' and '--' so add them.
-        if (!opt.simpleKey().empty())
-            def.emplace("-"s + opt.simpleKey(), !opt.arg().empty());
-        if (!opt.longKey().empty())
-            def.emplace("--"s + opt.longKey(), !opt.arg().empty());
-    }
-
-    // 2. Parse them, remove them from args (in parser::read) and build the map with id.
-    CommandRequest::Options requestOptions;
-
-    for (const auto &pair : option::read(args, def)) {
-        auto options = cmd.options();
-        auto it = std::find_if(options.begin(), options.end(), [&] (const auto &opt) {
-            return ("-"s + opt.simpleKey()) == pair.first || ("--"s + opt.longKey()) == pair.first;
-        });
-
-        requestOptions.emplace(it->id(), pair.second);
-    }
-
-    // 3. Check number of arguments.
-    if (args.size() < cmd.min())
-        throw std::runtime_error("too few arguments");
-
-    /*
-     * 4. Construct the request, if the returned value is not an object, do not
-     * send anything (e.g. help).
-     */
-    auto request = cmd.request(*this, CommandRequest(std::move(requestOptions), std::move(args)));
-
-    if (!request.is_object())
-        throw std::invalid_argument("command has returned invalid request");
-
-    request.push_back({"command", cmd.name()});
-
-    // 5. Send the command.
-    m_connection->request(request);
-
-    // 6. Returns the response.
-    return waitMessage(cmd.name());
-}
-
-std::vector<nlohmann::json> Irccdctl::exec(const Alias &alias, std::vector<std::string> argsCopy)
-{
-    std::vector<nlohmann::json> values;
-
-    for (const AliasCommand &cmd : alias) {
-        std::vector<std::string> args(argsCopy);
-        std::vector<std::string> cmdArgs;
-        std::vector<std::string>::size_type toremove = 0;
-
-        // 1. Append command name before.
-        cmdArgs.push_back(cmd.command());
-
-        for (const auto &arg : cmd.args()) {
-            if (arg.isPlaceholder()) {
-                if (args.size() < arg.index() + 1)
-                    throw std::invalid_argument("missing argument for placeholder %" + std::to_string(arg.index()));
-
-                cmdArgs.push_back(args[arg.index()]);
-
-                if (arg.index() + 1 > toremove)
-                    toremove = arg.index() + 1;
-            } else
-                cmdArgs.push_back(arg.value());
-        }
-
-        assert(toremove <= args.size());
-
-        // 2. Remove the arguments that been placed in placeholders.
-        args.erase(args.begin(), args.begin() + toremove);
-
-        // 3. Now append the rest of arguments.
-        std::copy(args.begin(), args.end(), std::back_inserter(cmdArgs));
-
-        // 4. Finally try to execute.
-        auto response = exec(cmdArgs);
-
-        values.insert(values.end(), response.begin(), response.end());
-    }
-
-    return values;
-}
-
-std::vector<nlohmann::json> Irccdctl::exec(std::vector<std::string> args)
-{
-    assert(args.size() > 0);
-
-    auto name = args[0];
-    auto alias = m_aliases.find(name);
-
-    // Remove name.
-    args.erase(args.begin());
-
-    std::vector<nlohmann::json> values;
-
-    if (alias != m_aliases.end()) {
-        auto response = exec(alias->second, args);
-
-        values.insert(values.end(), response.begin(), response.end());
-    } else {
-        auto cmd = m_commandService.find(name);
-
-        if (cmd)
-            values.push_back(exec(*cmd, args));
-        else
-            throw std::invalid_argument("no alias or command named " + name);
-    }
-
-    return values;
-}
-
-void Irccdctl::run(int argc, char **argv)
-{
-    // 1. Read command line arguments.
-    auto result = parse(argc, argv);
-
-    /*
-     * 2. Open optional config by command line or by searching it
-     *
-     * The connection to irccd is searched in the following order :
-     *
-     * 1. From the command line if specified
-     * 2. From the configuration file specified by -c
-     * 3. From the configuration file searched through directories
-     */
-    try {
-        if (result.count("-t") > 0 || result.count("--type") > 0)
-            parseConnect(result);
-
-        auto it = result.find("-c");
-
-        if (it != result.end() || (it = result.find("--config")) != result.end())
-            read(it->second);
-        else {
-            for (const std::string &dir : path::list(path::PathConfig)) {
-                std::string path = dir + "irccdctl.conf";
-
-                if (fs::exists(path)) {
-                    read(path);
-                    break;
-                }
-            }
-        }
-    } catch (const std::exception &ex) {
-        log::warning() << sys::programName() << ": " << ex.what() << std::endl;
-        std::exit(1);
-    }
-
-    if (argc <= 0) {
-        usage();
-        // NOTREACHED
-    }
-
-    // Help does not require connection.
-    if (std::strcmp(argv[0], "help") != 0) {
-        if (!m_connection) {
-            log::warning("{}: no connection specified"_format(sys::programName()));
-            std::exit(1);
-        }
-
-        m_connection->onDisconnect.connect([this] (auto reason) {
-            log::warning() << "connection lost to irccd: " << reason << std::endl;
-        });
-        m_connection->onConnect.connect([this] (auto info) {
-            log::info() << "connected to irccd "
-                        << info.major << "."
-                        << info.minor << "."
-                        << info.patch << std::endl;
-        });
-        m_connection->onEvent.connect([this] (auto msg) {
-            m_events.push_back(std::move(msg));
-        });
-        m_connection->onMessage.connect([this] (auto msg) {
-            m_messages.push_back(std::move(msg));
-        });
-
-        m_connection->connect(m_address);
-    } else if (argc == 1)
-        help();
-        // NOTREACHED
-
-    // Build a vector of arguments.
-    std::vector<std::string> args;
-
-    for (int i = 0; i < argc; ++i)
-        args.push_back(argv[i]);
-
-    auto commands = exec(args);
-
-    for (const auto &r : commands) {
-        auto name = r.find("response");
-
-        if (name == r.end() || !name->is_string())
-            log::warning() << "unknown irccd response with no response" << std::endl;
-
-        auto it = m_commandService.find(*name);
-
-        it->result(*this, r);
-    }
-}
-
-} // !irccd
+// empty for the moment.
--- a/libirccdctl/irccd/irccdctl.hpp	Sun Oct 23 11:20:29 2016 +0200
+++ b/libirccdctl/irccd/irccdctl.hpp	Sun Oct 23 11:25:26 2016 +0200
@@ -19,153 +19,44 @@
 #ifndef IRCCD_IRCCDCTL_HPP
 #define IRCCD_IRCCDCTL_HPP
 
-/**
- * \file irccdctl.hpp
- * \brief Base class for irccdctl front end.
- */
-
-#include <map>
 #include <memory>
-#include <string>
-#include <vector>
 
 #include "client.hpp"
-#include "alias.hpp"
-#include "options.hpp"
-#include "service-command.hpp"
-
-#include <json.hpp>
 
 namespace irccd {
 
-class Client;
-
-namespace ini {
-
-class Document;
-class Section;
-
-} // !ini
-
 /**
- * \brief Main irccdctl class.
+ * \brief Transient class for connecting to irccd
  */
 class Irccdctl {
 private:
-    // Commands.
-    CommandService m_commandService;
-
-    // Connection handler.
-    std::unique_ptr<Client> m_connection;
-    std::uint32_t m_timeout{30000};
-    net::Address m_address;
-
-    // Aliases.
-    std::map<std::string, Alias> m_aliases;
-
-    // Incoming data.
-    std::vector<nlohmann::json> m_events;
-    std::vector<nlohmann::json> m_messages;
-
-    void usage() const;
-    void help() const;
-
-    // Parse configuration file.
-    void readConnectIp(const ini::Section &sc);
-    void readConnectLocal(const ini::Section &sc);
-    void readConnect(const ini::Section &sc);
-    void readGeneral(const ini::Section &sc);
-    void readAliases(const ini::Section &sc);
-    void read(const std::string &path);
-
-    // Parse command line options.
-    void parseConnectIp(const option::Result &options);
-    void parseConnectLocal(const option::Result &options);
-    void parseConnect(const option::Result &options);
-    option::Result parse(int &argc, char **&argv);
+    std::unique_ptr<Client> m_client;
 
 public:
-    /**
-     * Get the command service.
-     *
-     * \return the command service
-     */
-    inline CommandService &commandService() noexcept
+    inline Irccdctl(std::unique_ptr<Client> client) noexcept
+        : m_client(std::move(client))
     {
-        return m_commandService;
     }
 
-    /**
-     * Get the client connection to irccd.
-     *
-     * \return the connection
-     */
-    inline const Client &client() const noexcept
+    inline Client &client() noexcept
     {
-        return *m_connection;
+        return *m_client;
     }
 
-    /**
-     * Get the client connection to irccd.
-     *
-     * \return the connection
-     */
-    inline Client &client() noexcept
+    inline const Client &client() const noexcept
     {
-        return *m_connection;
+        return *m_client;
     }
 
-    /**
-     * Get the next message response with the given id.
-     *
-     * If the response id is not provided, get the next incoming message.
-     *
-     * Otherwise, if the id is provided, all other previous messages will be
-     * discarded.
-     *
-     * \param id the response id (e.g. server-message)
-     * \return the next message
-     * \warning this may skip previous events
-     */
-    IRCCD_EXPORT nlohmann::json waitMessage(const std::string id = "");
-
-    /**
-     * Get the next pending even within the internal timeout.
-     *
-     * \return the next event or empty if not available
-     */
-    IRCCD_EXPORT nlohmann::json waitEvent();
+    inline void prepare(fd_set &in, fd_set &out, net::Handle &max)
+    {
+        m_client->prepare(in, out, max);
+    }
 
-    /**
-     * Execute the given command and wait for its result.
-     *
-     * \param cmd the command
-     * \param args the arguments
-     */
-    IRCCD_EXPORT nlohmann::json exec(const Command &cmd, std::vector<std::string> args);
-
-    /**
-     * Execute the given alias.
-     *
-     * \param alias the alias
-     * \param args the arguments
-     */
-    IRCCD_EXPORT std::vector<nlohmann::json> exec(const Alias &alias, std::vector<std::string> args);
-
-    /**
-     * Resolve the command line arguments.
-     *
-     * \param args the main arguments
-     */
-    IRCCD_EXPORT std::vector<nlohmann::json> exec(std::vector<std::string> args);
-
-    /**
-     * Run the irccdctl front end.
-     *
-     * \param argc the number of arguments
-     * \param argv the arguments
-     */
-    IRCCD_EXPORT void run(int argc, char **argv);
+    inline void sync(fd_set &in, fd_set &out)
+    {
+        m_client->sync(in, out);
+    }
 };
 
 } // !irccd