changeset 348:24b1709093e7

Irccdctl: resurrect
author David Demelier <markand@malikania.fr>
date Sun, 13 Nov 2016 14:16:58 +0100
parents ec43b9ac3df7
children 56475d6c1c30
files irccd/main.cpp irccdctl/CMakeLists.txt irccdctl/alias.cpp irccdctl/alias.hpp irccdctl/cli.cpp irccdctl/main.cpp
diffstat 6 files changed, 783 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Sun Nov 13 10:41:28 2016 +0100
+++ b/irccd/main.cpp	Sun Nov 13 14:16:58 2016 +0100
@@ -275,6 +275,8 @@
 
     instance = std::make_unique<Irccd>();
     instance->commands().add(std::make_unique<command::PluginConfigCommand>());
+    instance->commands().add(std::make_unique<command::PluginInfoCommand>());
+    instance->commands().add(std::make_unique<command::PluginListCommand>());
     instance->commands().add(std::make_unique<command::PluginReloadCommand>());
     instance->commands().add(std::make_unique<command::PluginUnloadCommand>());
     instance->commands().add(std::make_unique<command::ServerChannelModeCommand>());
--- a/irccdctl/CMakeLists.txt	Sun Nov 13 10:41:28 2016 +0100
+++ b/irccdctl/CMakeLists.txt	Sun Nov 13 14:16:58 2016 +0100
@@ -20,6 +20,8 @@
 set(
     SOURCES
     ${irccdctl_SOURCE_DIR}/CMakeLists.txt
+    ${irccdctl_SOURCE_DIR}/alias.cpp
+    ${irccdctl_SOURCE_DIR}/alias.hpp
     ${irccdctl_SOURCE_DIR}/cli.cpp
     ${irccdctl_SOURCE_DIR}/cli.hpp
     ${irccdctl_SOURCE_DIR}/main.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/alias.cpp	Sun Nov 13 14:16:58 2016 +0100
@@ -0,0 +1,60 @@
+/*
+ * alias.cpp -- create irccdctl aliases
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+#include <regex>
+
+#include "alias.hpp"
+
+namespace irccd {
+
+AliasArg::AliasArg(std::string value)
+{
+    assert(!value.empty());
+
+    if ((m_isPlaceholder = std::regex_match(value, std::regex("^%\\d+$")))) 
+        m_value = value.substr(1);
+    else
+        m_value = std::move(value);
+}
+
+unsigned AliasArg::index() const noexcept
+{
+    assert(isPlaceholder());
+
+    return std::stoi(m_value);
+}
+
+const std::string &AliasArg::value() const noexcept
+{
+    assert(!isPlaceholder());
+
+    return m_value;
+}
+
+std::ostream &operator<<(std::ostream &out, const AliasArg &arg)
+{
+    if (arg.m_isPlaceholder)
+        out << "%" << arg.m_value;
+    else
+        out << arg.m_value;
+
+    return out;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccdctl/alias.hpp	Sun Nov 13 14:16:58 2016 +0100
@@ -0,0 +1,174 @@
+/*
+ * alias.hpp -- create irccdctl aliases
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_ALIAS_HPP
+#define IRCCD_ALIAS_HPP
+
+/**
+ * \file alias.hpp
+ * \brief Create irccdctl aliases.
+ */
+
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+/**
+ * \class AliasArg
+ * \brief Describe an alias argument.
+ *
+ * When the user specify arguments, it can precise an applied argument or a
+ * placeholder that will be substituted during command line invocation.
+ *
+ * Placeholders are placed using %n where n is an integer starting from 0.
+ */
+class AliasArg {
+private:
+    std::string m_value;
+    bool m_isPlaceholder;
+
+public:
+    /**
+     * Construct an argument.
+     *
+     * \pre value must not be empty
+     * \param value the value
+     */
+    IRCCD_EXPORT AliasArg(std::string value);
+
+    /**
+     * Check if the argument is a placeholder.
+     *
+     * \return true if the argument is a placeholder
+     */
+    inline bool isPlaceholder() const noexcept
+    {
+        return m_isPlaceholder;
+    }
+
+    /**
+     * Get the placeholder index (e.g %0 returns 0)
+     *
+     * \pre isPlaceholder() must return true
+     * \return the position
+     */
+    IRCCD_EXPORT unsigned index() const noexcept;
+
+    /**
+     * Get the real value.
+     *
+     * \pre isPlaceholder() must return false
+     * \return the value
+     */
+    IRCCD_EXPORT const std::string &value() const noexcept;
+
+    /**
+     * Output the alias to the stream.
+     *
+     * \param out the output stream
+     * \return out
+     */
+    IRCCD_EXPORT friend std::ostream &operator<<(std::ostream &out, const AliasArg &);
+};
+
+/**
+ * \class AliasCommand
+ * \brief Describe a user-provided alias command.
+ *
+ * An alias command is just a command with a set of applied or placeholders
+ * arguments.
+ */
+class AliasCommand {
+private:
+    std::string m_command;
+    std::vector<AliasArg> m_args;
+
+public:
+    /**
+     * Create an alias command.
+     *
+     * \param command the command
+     * \param args the arguments
+     */
+    inline AliasCommand(std::string command, std::vector<AliasArg> args = {}) noexcept
+        : m_command(std::move(command))
+        , m_args(std::move(args))
+    {
+    }
+
+    /**
+     * Get the command to execute.
+     *
+     * \return the command name
+     */
+    inline const std::string &command() const noexcept
+    {
+        return m_command;
+    }
+
+    /**
+     * Get the arguments.
+     *
+     * \return the arguments
+     */
+    inline const std::vector<AliasArg> &args() const noexcept
+    {
+        return m_args;
+    }
+};
+
+/**
+ * \class Alias
+ * \brief A set of commands to execute with their arguments.
+ *
+ * An alias is a composition of AliasCommand, typically, the user is able to set
+ * an alias that execute a list of specified commands in order they are defined.
+ */
+class Alias : public std::vector<AliasCommand> {
+private:
+    std::string m_name;
+
+public:
+    /**
+     * Create an alias.
+     *
+     * \param name the alias name
+     */
+    inline Alias(std::string name) noexcept
+        : m_name(std::move(name))
+    {
+    }
+
+    /**
+     * Get the alias name.
+     *
+     * \return the name
+     */
+    inline const std::string &name() const noexcept
+    {
+        return m_name;
+    }
+};
+
+} // !irccd
+
+#endif // !IRCCD_ALIAS_HPP
--- a/irccdctl/cli.cpp	Sun Nov 13 10:41:28 2016 +0100
+++ b/irccdctl/cli.cpp	Sun Nov 13 14:16:58 2016 +0100
@@ -71,8 +71,6 @@
 
     irccdctl.client().onMessage.disconnect(id);
 
-    std::cout << msg << std::endl;
-
     if (!msg.is_object())
         throw std::runtime_error("no response received");
     if (util::json::getString(msg, "command") != m_name)
--- a/irccdctl/main.cpp	Sun Nov 13 10:41:28 2016 +0100
+++ b/irccdctl/main.cpp	Sun Nov 13 14:16:58 2016 +0100
@@ -16,21 +16,559 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <unordered_map>
+
+#include <format.h>
+
+#include "alias.hpp"
+#include "cli.hpp"
 #include "client.hpp"
+#include "elapsed-timer.hpp"
+#include "fs.hpp"
+#include "ini.hpp"
 #include "irccdctl.hpp"
-#include "cli.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;
 using namespace irccd;
 
-int main(int, char **)
+namespace {
+
+std::vector<std::unique_ptr<Cli>> commands;
+std::unordered_map<std::string, Alias> aliases;
+std::unique_ptr<Client> client;
+std::unique_ptr<Irccdctl> irccdctl;
+net::Address address;
+
+void usage()
+{
+    bool first = true;
+
+    for (const auto &cmd : commands) {
+        log::warning() << (first ? "usage: " : "       ") << sys::programName() << " "
+                       << cmd->usage() << std::endl;
+        first = false;
+    }
+
+    std::exit(1);
+}
+
+void help()
+{
+    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 : commands)
+        log::warning() << "\t" << std::left << std::setw(32)
+                       << cmd->name() << cmd->summary() << 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 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());
+        }
+    }
+
+    address = net::resolveOne(host, port, domain, SOCK_STREAM);
+
+    if ((it = sc.find("ssl")) != sc.end() && util::isBoolean(it->value()))
+        client = std::make_unique<TlsClient>();
+    else
+        client = std::make_unique<Client>();
+}
+
+/*
+ * readConnectLocal
+ * -------------------------------------------------------------------
+ *
+ * Extract local connection for Unix.
+ *
+ * [connect]
+ * type = "unix"
+ * path = "path to socket file"
+ */
+void 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");
+
+    address = net::local::create(it->value());
+    client = 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 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())
+        client->setPassword(password->value());
+}
+
+/*
+ * readGeneral
+ * -------------------------------------------------------------------
+ *
+ * Read the general section.
+ *
+ * [general]
+ * verbose = true
+ */
+void 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 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())});
+        }
+
+        aliases.emplace(option.key(), std::move(alias));
+    }
+}
+
+void read(const std::string &path)
 {
     try {
-        Irccdctl ctl(std::make_unique<Client>());
-        ctl.client().connect(net::local::create("/tmp/irccd.sock"));
+        ini::Document doc = ini::readFile(path);
+        ini::Document::const_iterator it;
 
-        cli::PluginConfigCli command;
-        command.exec(ctl, {"ask", "disable_if", "ok"});
+        if (!client && (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) {
-        std::cerr << ex.what() << std::endl;
+        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 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;
+    }
+
+    address = net::resolveOne(host, port, domain, SOCK_STREAM);
+    client  = std::make_unique<Client>();
+}
+
+/*
+ * parseConnectLocal
+ * ------------------------------------------------------------------
+ *
+ * Parse local connection.
+ *
+ * -P file
+ */
+void 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)");
+
+    address = net::local::create(it->second, false);
+    client = 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 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 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;
+}
+
+void exec(std::vector<std::string>);
+
+void 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.
+        exec(cmdArgs);
+    }
+}
+
+void exec(std::vector<std::string> args)
+{
+    assert(args.size() > 0);
+
+    auto name = args[0];
+    auto alias = aliases.find(name);
+
+    // Remove name.
+    args.erase(args.begin());
+
+    if (alias != aliases.end()) {
+        exec(alias->second, args);
+    } else {
+        auto cmd = std::find_if(commands.begin(), commands.end(), [&] (auto &it) {
+            return it->name() == name;
+        });
+
+        if (cmd != commands.end()) {
+            (*cmd)->exec(*irccdctl, args);
+        } else {
+            throw std::invalid_argument("no alias or command named " + name);
+        }
+    }
+}
+
+void init(int &argc, char **&argv)
+{
+    sys::setProgramName("irccdctl");
+    net::init();
+
+    --argc;
+    ++argv;
+
+    commands.push_back(std::make_unique<cli::PluginConfigCli>());
+    commands.push_back(std::make_unique<cli::PluginInfoCli>());
+    commands.push_back(std::make_unique<cli::PluginListCli>());
+    commands.push_back(std::make_unique<cli::PluginLoadCli>());
+    commands.push_back(std::make_unique<cli::PluginReloadCli>());
+    commands.push_back(std::make_unique<cli::PluginUnloadCli>());
+    commands.push_back(std::make_unique<cli::ServerChannelMode>());
+    commands.push_back(std::make_unique<cli::ServerChannelNoticeCli>());
+    commands.push_back(std::make_unique<cli::ServerConnectCli>());
+    commands.push_back(std::make_unique<cli::ServerDisconnectCli>());
+    commands.push_back(std::make_unique<cli::ServerInfoCli>());
+    commands.push_back(std::make_unique<cli::ServerInviteCli>());
+    commands.push_back(std::make_unique<cli::ServerJoinCli>());
+    commands.push_back(std::make_unique<cli::ServerKickCli>());
+    commands.push_back(std::make_unique<cli::ServerListCli>());
+    commands.push_back(std::make_unique<cli::ServerMeCli>());
+    commands.push_back(std::make_unique<cli::ServerMessageCli>());
+    commands.push_back(std::make_unique<cli::ServerModeCli>());
+    commands.push_back(std::make_unique<cli::ServerNickCli>());
+    commands.push_back(std::make_unique<cli::ServerNoticeCli>());
+    commands.push_back(std::make_unique<cli::ServerPartCli>());
+    commands.push_back(std::make_unique<cli::ServerReconnectCli>());
+}
+
+} // !namespace
+
+int main(int argc, char **argv)
+{
+    init(argc, 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
+    }
+    if (std::strcmp(argv[0], "help") == 0) {
+        help();
+        // NOTREACHED
+    }
+
+    if (!client) {
+        log::warning("{}: no connection specified"_format(sys::programName()));
+        std::exit(1);
+    }
+
+    irccdctl = std::make_unique<Irccdctl>(std::move(client));
+    irccdctl->client().onDisconnect.connect([&] (auto reason) {
+        log::warning() << "connection lost to irccd: " << reason << std::endl;
+    });
+    irccdctl->client().onConnect.connect([&] (auto info) {
+        log::info() << "connected to irccd "
+                    << info.major << "."
+                    << info.minor << "."
+                    << info.patch << std::endl;
+    });
+    irccdctl->client().connect(address);
+
+    // Build a vector of arguments.
+    std::vector<std::string> args;
+
+    for (int i = 0; i < argc; ++i)
+        args.push_back(argv[i]);
+
+    try {
+        exec(args);
+    } catch (const std::exception &ex) {
+        std::cerr << sys::programName() << ": unrecoverable error: " << ex.what() << std::endl;
+    }
+}