changeset 804:d55a64c6586b

irccdctl: unify CLI output, closes #928 @1h
author David Demelier <markand@malikania.fr>
date Wed, 14 Nov 2018 14:07:05 +0100
parents 14f9e3b03779
children 8025a24bb7c7
files MIGRATING.md irccdctl/cli.cpp libirccd/irccd/daemon/command.cpp tests/src/irccdctl/cli-plugin-info/main.cpp tests/src/irccdctl/cli-rule-add/main.cpp tests/src/irccdctl/cli-rule-edit/main.cpp tests/src/irccdctl/cli-rule-info/main.cpp tests/src/irccdctl/cli-rule-list/main.cpp tests/src/irccdctl/cli-rule-move/main.cpp tests/src/irccdctl/cli-rule-remove/main.cpp tests/src/irccdctl/cli-server-info/main.cpp
diffstat 11 files changed, 308 insertions(+), 295 deletions(-) [+]
line wrap: on
line diff
--- a/MIGRATING.md	Tue Nov 13 20:21:18 2018 +0100
+++ b/MIGRATING.md	Wed Nov 14 14:07:05 2018 +0100
@@ -19,7 +19,8 @@
 - The functions `server-cnotice` and `server-cmode` have been removed, use
   `server-notice` and `server-mode` instead,
 - The option `connect.host` has been renamed to `connect.hostname`,
-- The option `--host` has been renamed to `--hostname`.
+- The option `--host` has been renamed to `--hostname`,
+- The output style has been unified.
 
 ### Plugins
 
--- a/irccdctl/cli.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/irccdctl/cli.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -16,6 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <iomanip>
+#include <ios>
 #include <iostream>
 
 #include <irccd/json_util.hpp>
@@ -29,6 +31,7 @@
 #include "cli.hpp"
 
 using irccd::json_util::deserializer;
+using irccd::json_util::pretty;
 
 namespace irccd::ctl {
 
@@ -37,6 +40,15 @@
 namespace {
 
 template <typename T>
+auto operator<<(std::ostream& out, const std::optional<T>& value) -> std::ostream&
+{
+	if (value)
+		out << pretty(*value);
+
+	return out;
+}
+
+template <typename T>
 auto bind() noexcept -> cli::constructor
 {
 	return [] () noexcept {
@@ -59,114 +71,121 @@
 	return "native";
 }
 
-void onConnect(const nlohmann::json &v)
+auto align(std::string_view topic) -> std::ostream&
 {
-	std::cout << "event:    onConnect\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
+	assert(topic.size() <= 16);
+
+	return std::cout << std::setw(16) << std::left << topic;
 }
 
-void onInvite(const nlohmann::json &v)
+void onConnect(const deserializer& v)
 {
-	std::cout << "event:    onInvite\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "channel:  " << json_util::pretty(v.value("channel", "(unknown)")) << "\n";
+	align("event:")  << "onConnect\n";
+	align("server:") << v.get<std::string>("server") << "\n";
 }
 
-void onJoin(const nlohmann::json &v)
+void onInvite(const deserializer& v)
 {
-	std::cout << "event:    onJoin\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "channel:  " << json_util::pretty(v.value("channel", "(unknown)")) << "\n";
+	align("event:")   << "onInvite\n";
+	align("server:")  << v.get<std::string>("server") << "\n";
+	align("origin:")  << v.get<std::string>("origin") << "\n";
+	align("channel:") << v.get<std::string>("channel") << "\n";
 }
 
-void onKick(const nlohmann::json &v)
+void onJoin(const deserializer& v)
 {
-	std::cout << "event:    onKick\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "channel:  " << json_util::pretty(v.value("channel", "(unknown)")) << "\n";
-	std::cout << "target:   " << json_util::pretty(v.value("target", "(unknown)")) << "\n";
-	std::cout << "reason:   " << json_util::pretty(v.value("reason", "(unknown)")) << "\n";
+	align("event:")   << "onJoin\n";
+	align("server:")  << v.get<std::string>("server") << "\n";
+	align("origin:")  << v.get<std::string>("origin") << "\n";
+	align("channel:") << v.get<std::string>("channel") << "\n";
 }
 
-void onMessage(const nlohmann::json &v)
+void onKick(const deserializer& v)
 {
-	std::cout << "event:    onMessage\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "channel:  " << json_util::pretty(v.value("channel", "(unknown)")) << "\n";
-	std::cout << "message:  " << json_util::pretty(v.value("message", "(unknown)")) << "\n";
+	align("event:")   << "onKick\n";
+	align("server:")  << v.get<std::string>("server") << "\n";
+	align("origin:")  << v.get<std::string>("origin") << "\n";
+	align("channel:") << v.get<std::string>("channel") << "\n";
+	align("target:")  << v.get<std::string>("target") << "\n";
+	align("reason:")  << v.get<std::string>("reason") << "\n";
 }
 
-void onMe(const nlohmann::json &v)
+void onMessage(const deserializer& v)
 {
-	std::cout << "event:    onMe\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "target:   " << json_util::pretty(v.value("target", "(unknown)")) << "\n";
-	std::cout << "message:  " << json_util::pretty(v.value("message", "(unknown)")) << "\n";
+	align("event:")   << "onMessage\n";
+	align("server:")  << v.get<std::string>("server") << "\n";
+	align("origin:")  << v.get<std::string>("origin") << "\n";
+	align("channel:") << v.get<std::string>("channel") << "\n";
+	align("message:") << v.get<std::string>("message") << "\n";
+}
+
+void onMe(const deserializer& v)
+{
+	align("event:")   << "onMe\n";
+	align("server:")  << v.get<std::string>("server") << "\n";
+	align("origin:")  << v.get<std::string>("origin") << "\n";
+	align("target:")  << v.get<std::string>("target") << "\n";
+	align("message:") << v.get<std::string>("message") << "\n";
 }
 
-void onMode(const nlohmann::json &v)
+void onMode(const deserializer& v)
 {
-	std::cout << "event:    onMode\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "mode:     " << json_util::pretty(v.value("mode", "(unknown)")) << "\n";
+	align("event:")  << "onMode\n";
+	align("server:") << v.get<std::string>("server") << "\n";
+	align("origin:") << v.get<std::string>("origin") << "\n";
+	align("mode:")   << v.get<std::string>("mode") << "\n";
 }
 
-void onNames(const nlohmann::json &v)
+void onNames(const deserializer& v)
 {
-	std::cout << "event:    onNames\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "channel:  " << json_util::pretty(v.value("channel", "(unknown)")) << "\n";
-	std::cout << "names:    " << json_util::pretty(v.value("names", "(unknown)")) << "\n";
+	align("event:")   << "onNames\n";
+	align("server:")  << v.get<std::string>("server") << "\n";
+	align("channel:") << v.get<std::string>("channel") << "\n";
+	align("names:")   << v.get<std::string>("names") << "\n";
 }
 
-void onNick(const nlohmann::json &v)
+void onNick(const deserializer& v)
 {
-	std::cout << "event:    onNick\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "nickname: " << json_util::pretty(v.value("nickname", "(unknown)")) << "\n";
+	align("event:")    << "onNick\n";
+	align("server:")   << v.get<std::string>("server") << "\n";
+	align("origin:")   << v.get<std::string>("origin") << "\n";
+	align("nickname:") << v.get<std::string>("nickname") << "\n";
 }
 
-void onNotice(const nlohmann::json &v)
+void onNotice(const deserializer& v)
 {
-	std::cout << "event:    onNotice\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "message:  " << json_util::pretty(v.value("message", "(unknown)")) << "\n";
+	align("event:")   << "onNotice\n";
+	align("server:")  << v.get<std::string>("server") << "\n";
+	align("origin:")  << v.get<std::string>("origin") << "\n";
+	align("message:") << v.get<std::string>("message") << "\n";
 }
 
-void onPart(const nlohmann::json &v)
+void onPart(const deserializer& v)
 {
-	std::cout << "event:    onPart\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "channel:  " << json_util::pretty(v.value("channel", "(unknown)")) << "\n";
-	std::cout << "reason:   " << json_util::pretty(v.value("reason", "(unknown)")) << "\n";
+	align("event:")   << "onPart\n";
+	align("server:")  << v.get<std::string>("server") << "\n";
+	align("origin:")  << v.get<std::string>("origin") << "\n";
+	align("channel:") << v.get<std::string>("channel") << "\n";
+	align("reason:")  << v.get<std::string>("reason") << "\n";
 }
 
-void onTopic(const nlohmann::json &v)
+void onTopic(const deserializer& v)
 {
-	std::cout << "event:    onTopic\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "origin:   " << json_util::pretty(v.value("origin", "(unknown)")) << "\n";
-	std::cout << "channel:  " << json_util::pretty(v.value("channel", "(unknown)")) << "\n";
-	std::cout << "topic:    " << json_util::pretty(v.value("topic", "(unknown)")) << "\n";
+	align("event:")   << "onTopic\n";
+	align("server:")  << v.get<std::string>("server") << "\n";
+	align("origin:")  << v.get<std::string>("origin") << "\n";
+	align("channel:") << v.get<std::string>("channel") << "\n";
+	align("topic:")   << v.get<std::string>("topic") << "\n";
 }
 
-void onWhois(const nlohmann::json &v)
+void onWhois(const deserializer& v)
 {
-	std::cout << "event:    onWhois\n";
-	std::cout << "server:   " << json_util::pretty(v.value("server", "(unknown)")) << "\n";
-	std::cout << "nickname: " << json_util::pretty(v.value("nickname", "(unknown)")) << "\n";
-	std::cout << "username: " << json_util::pretty(v.value("username", "(unknown)")) << "\n";
-	std::cout << "hostname: " << json_util::pretty(v.value("hostname", "(unknown)")) << "\n";
-	std::cout << "realname: " << json_util::pretty(v.value("realname", "(unknown)")) << "\n";
+	align("event:")    << "onWhois\n";
+	align("server:")   << v.get<std::string>("server") << "\n";
+	align("nickname:") << v.get<std::string>("nickname") << "\n";
+	align("username:") << v.get<std::string>("username") << "\n";
+	align("hostname:") << v.get<std::string>("hostname") << "\n";
+	align("realname:") << v.get<std::string>("realname") << "\n";
 }
 
 const std::unordered_map<std::string_view, std::function<void (const nlohmann::json&)>> events{
@@ -188,17 +207,19 @@
 void get_event(ctl::controller& ctl, std::string fmt)
 {
 	ctl.recv([&ctl, fmt] (auto code, auto message) {
+		const deserializer doc(message);
+
 		if (code)
 			throw std::system_error(code);
 
-		const auto event = deserializer(message).get<std::string>("event");
+		const auto event = doc.get<std::string>("event");
 		const auto it = events.find(event ? *event : "");
 
 		if (it != events.end()) {
 			if (fmt == "json")
 				std::cout << message.dump(4) << std::endl;
 			else {
-				it->second(message);
+				it->second(doc);
 				std::cout << std::endl;
 			}
 		}
@@ -315,7 +336,7 @@
 
 	request(ctl, std::move(json), [args] (auto result) {
 		if (result["variables"].is_object())
-			std::cout << json_util::pretty(result["variables"][args[1]]) << std::endl;
+			std::cout << pretty(result["variables"][args[1]]) << std::endl;
 	});
 }
 
@@ -330,7 +351,7 @@
 		const auto variables = result["variables"];
 
 		for (auto v = variables.begin(); v != variables.end(); ++v)
-			std::cout << std::setw(16) << std::left << v.key() << " : " << json_util::pretty(v.value()) << std::endl;
+			std::cout << std::setw(16) << std::left << v.key() << " : " << pretty(v.value()) << std::endl;
 	});
 }
 
@@ -378,15 +399,10 @@
 	request(ctl, json, [] (auto result) {
 		const deserializer doc(result);
 
-		std::cout << std::boolalpha;
-		std::cout << "Author         : "
-		          << doc.get<std::string>("author").value_or("(unknown)") << std::endl;
-		std::cout << "License        : "
-		          << doc.get<std::string>("license").value_or("(unknown)") << std::endl;
-		std::cout << "Summary        : "
-		          << doc.get<std::string>("summary").value_or("(unknown)") << std::endl;
-		std::cout << "Version        : "
-		          << doc.get<std::string>("version").value_or("(unknown)") << std::endl;
+		align("author:")  << doc.get<std::string>("author") << std::endl;
+		align("license:") << doc.get<std::string>("license") << std::endl;
+		align("summary:") << doc.get<std::string>("summary") << std::endl;
+		align("version:") << doc.get<std::string>("version") << std::endl;
 	});
 }
 
@@ -644,13 +660,12 @@
 			return "drop";
 	};
 
-	std::cout << "rule:        " << index << std::endl;
-	std::cout << "servers:     " << unjoin(json["servers"]) << std::endl;
-	std::cout << "channels:    " << unjoin(json["channels"]) << std::endl;
-	std::cout << "plugins:     " << unjoin(json["plugins"]) << std::endl;
-	std::cout << "events:      " << unjoin(json["events"]) << std::endl;
-	std::cout << "action:      " << unstr(json["action"]) << std::endl;
-	std::cout << std::endl;
+	align("rule:") << index << std::endl;
+	align("servers:") << unjoin(json["servers"]) << std::endl;
+	align("channels:") << unjoin(json["channels"]) << std::endl;
+	align("plugins:") << unjoin(json["plugins"]) << std::endl;
+	align("events:") << unjoin(json["events"]) << std::endl;
+	align("action:") << unstr(json["action"]) << std::endl;
 }
 
 auto rule_info_cli::get_name() const noexcept -> std::string_view
@@ -691,10 +706,17 @@
 {
 	request(ctl, {{ "command", "rule-list" }}, [] (auto result) {
 		auto pos = 0;
+		auto length = result["list"].size();
 
-		for (const auto& obj : result["list"])
-			if (obj.is_object())
-				rule_info_cli::print(obj, pos++);
+		for (const auto& obj : result["list"]) {
+			if (!obj.is_object())
+				continue;
+
+			rule_info_cli::print(obj, pos++);
+
+			if (pos < length)
+				std::cout << std::endl;
+		}
 	});
 }
 
@@ -847,24 +869,24 @@
 	});
 
 	request(ctl, std::move(json), [] (auto result) {
-		std::cout << std::boolalpha;
-		std::cout << "Name           : " << json_util::pretty(result["name"]) << std::endl;
-		std::cout << "Hostname       : " << json_util::pretty(result["hostname"]) << std::endl;
-		std::cout << "Port           : " << json_util::pretty(result["port"]) << std::endl;
-		std::cout << "Ipv6           : " << json_util::pretty(result["ipv6"]) << std::endl;
-		std::cout << "SSL            : " << json_util::pretty(result["ssl"]) << std::endl;
-		std::cout << "SSL verified   : " << json_util::pretty(result["sslVerify"]) << std::endl;
-		std::cout << "Channels       : ";
+		const deserializer doc(result);
+
+		align("name:")     << doc.get<std::string>("name") << std::endl;
+		align("hostname:") << doc.get<std::string>("hostname") << std::endl;
+		align("port:")     << doc.get<std::uint64_t>("port") << std::endl;
+		align("nickname:") << doc.get<std::string>("nickname") << std::endl;
+		align("username:") << doc.get<std::string>("username") << std::endl;
+		align("realname:") << doc.get<std::string>("realname") << std::endl;
+		align("ipv4:")     << doc.get<bool>("ipv4") << std::endl;
+		align("ipv6:")     << doc.get<bool>("ipv6") << std::endl;
+		align("ssl:")      << doc.get<bool>("ssl") << std::endl;
+		align("channels:");
 
 		for (const auto& v : result["channels"])
 			if (v.is_string())
 				std::cout << v.template get<std::string>() << " ";
 
 		std::cout << std::endl;
-
-		std::cout << "Nickname       : " << json_util::pretty(result["nickname"]) << std::endl;
-		std::cout << "User name      : " << json_util::pretty(result["username"]) << std::endl;
-		std::cout << "Real name      : " << json_util::pretty(result["realname"]) << std::endl;
 	});
 }
 
--- a/libirccd/irccd/daemon/command.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/libirccd/irccd/daemon/command.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -531,14 +531,9 @@
 	response.push_back({"channels", server->get_channels()});
 
 	// Optional stuff.
-	if ((server->get_options() & server::options::ipv6) == server::options::ipv6)
-		response.push_back({"ipv6", true});
-	if ((server->get_options() & server::options::ipv6) == server::options::ipv6)
-		response.push_back({"ipv6", true});
-	if ((server->get_options() & server::options::ssl) == server::options::ssl)
-		response.push_back({"ssl", true});
-	if ((server->get_options() & server::options::ssl_verify) == server::options::ssl_verify)
-		response.push_back({"sslVerify", true});
+	response.push_back({"ipv4", static_cast<bool>(server->get_options() & server::options::ipv4)});
+	response.push_back({"ipv6", static_cast<bool>(server->get_options() & server::options::ipv6)});
+	response.push_back({"ssl", static_cast<bool>(server->get_options() & server::options::ssl)});
 
 	client.write(response);
 }
--- a/tests/src/irccdctl/cli-plugin-info/main.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/tests/src/irccdctl/cli-plugin-info/main.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -48,10 +48,10 @@
 	BOOST_TEST(!code);
 	BOOST_TEST(out.size() == 4U);
 	BOOST_TEST(err.size() == 0U);
-	BOOST_TEST(out[0] == "Author         : David Demelier <markand@malikania.fr>");
-	BOOST_TEST(out[1] == "License        : ISC");
-	BOOST_TEST(out[2] == "Summary        : mock plugin");
-	BOOST_TEST(out[3] == "Version        : 1.0");
+	BOOST_TEST(out[0] == "author:         David Demelier <markand@malikania.fr>");
+	BOOST_TEST(out[1] == "license:        ISC");
+	BOOST_TEST(out[2] == "summary:        mock plugin");
+	BOOST_TEST(out[3] == "version:        1.0");
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
--- a/tests/src/irccdctl/cli-rule-add/main.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/tests/src/irccdctl/cli-rule-add/main.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -59,14 +59,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s1 s2 ");
-		BOOST_TEST(out[2]  == "channels:    c1 c2 ");
-		BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
-		BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s1 s2 ");
+		BOOST_TEST(out[2]  == "channels:       c1 c2 ");
+		BOOST_TEST(out[3]  == "plugins:        p1 p2 ");
+		BOOST_TEST(out[4]  == "events:         onCommand onMessage ");
+		BOOST_TEST(out[5]  == "action:         drop");
 	}
 }
 
@@ -86,14 +86,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s1 s2 ");
-		BOOST_TEST(out[2]  == "channels:    ");
-		BOOST_TEST(out[3]  == "plugins:     ");
-		BOOST_TEST(out[4]  == "events:      ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s1 s2 ");
+		BOOST_TEST(out[2]  == "channels:       ");
+		BOOST_TEST(out[3]  == "plugins:        ");
+		BOOST_TEST(out[4]  == "events:         ");
+		BOOST_TEST(out[5]  == "action:         drop");
 	}
 }
 
@@ -113,14 +113,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     ");
-		BOOST_TEST(out[2]  == "channels:    c1 c2 ");
-		BOOST_TEST(out[3]  == "plugins:     ");
-		BOOST_TEST(out[4]  == "events:      ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        ");
+		BOOST_TEST(out[2]  == "channels:       c1 c2 ");
+		BOOST_TEST(out[3]  == "plugins:        ");
+		BOOST_TEST(out[4]  == "events:         ");
+		BOOST_TEST(out[5]  == "action:         drop");
 	}
 }
 
@@ -140,14 +140,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     ");
-		BOOST_TEST(out[2]  == "channels:    ");
-		BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
-		BOOST_TEST(out[4]  == "events:      ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        ");
+		BOOST_TEST(out[2]  == "channels:       ");
+		BOOST_TEST(out[3]  == "plugins:        p1 p2 ");
+		BOOST_TEST(out[4]  == "events:         ");
+		BOOST_TEST(out[5]  == "action:         drop");
 	}
 }
 
@@ -167,14 +167,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     ");
-		BOOST_TEST(out[2]  == "channels:    ");
-		BOOST_TEST(out[3]  == "plugins:     ");
-		BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        ");
+		BOOST_TEST(out[2]  == "channels:       ");
+		BOOST_TEST(out[3]  == "plugins:        ");
+		BOOST_TEST(out[4]  == "events:         onCommand onMessage ");
+		BOOST_TEST(out[5]  == "action:         drop");
 	}
 }
 
--- a/tests/src/irccdctl/cli-rule-edit/main.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/tests/src/irccdctl/cli-rule-edit/main.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -65,14 +65,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     ts1 ts2 ");
-		BOOST_TEST(out[2]  == "channels:    c1 c2 ");
-		BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
-		BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        ts1 ts2 ");
+		BOOST_TEST(out[2]  == "channels:       c1 c2 ");
+		BOOST_TEST(out[3]  == "plugins:        p1 p2 ");
+		BOOST_TEST(out[4]  == "events:         onCommand onMessage ");
+		BOOST_TEST(out[5]  == "action:         drop");
 	}
 }
 
@@ -96,14 +96,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s1 s2 ");
-		BOOST_TEST(out[2]  == "channels:    tc1 tc2 ");
-		BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
-		BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s1 s2 ");
+		BOOST_TEST(out[2]  == "channels:       tc1 tc2 ");
+		BOOST_TEST(out[3]  == "plugins:        p1 p2 ");
+		BOOST_TEST(out[4]  == "events:         onCommand onMessage ");
+		BOOST_TEST(out[5]  == "action:         drop");
 	}
 }
 
@@ -127,14 +127,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s1 s2 ");
-		BOOST_TEST(out[2]  == "channels:    c1 c2 ");
-		BOOST_TEST(out[3]  == "plugins:     tp1 tp2 ");
-		BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s1 s2 ");
+		BOOST_TEST(out[2]  == "channels:       c1 c2 ");
+		BOOST_TEST(out[3]  == "plugins:        tp1 tp2 ");
+		BOOST_TEST(out[4]  == "events:         onCommand onMessage ");
+		BOOST_TEST(out[5]  == "action:         drop");
 	}
 }
 
@@ -158,14 +158,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s1 s2 ");
-		BOOST_TEST(out[2]  == "channels:    c1 c2 ");
-		BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
-		BOOST_TEST(out[4]  == "events:      onKick onNickname ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s1 s2 ");
+		BOOST_TEST(out[2]  == "channels:       c1 c2 ");
+		BOOST_TEST(out[3]  == "plugins:        p1 p2 ");
+		BOOST_TEST(out[4]  == "events:         onKick onNickname ");
+		BOOST_TEST(out[5]  == "action:         drop");
 	}
 }
 
@@ -185,14 +185,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s1 s2 ");
-		BOOST_TEST(out[2]  == "channels:    c1 c2 ");
-		BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
-		BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
-		BOOST_TEST(out[5]  == "action:      accept");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s1 s2 ");
+		BOOST_TEST(out[2]  == "channels:       c1 c2 ");
+		BOOST_TEST(out[3]  == "plugins:        p1 p2 ");
+		BOOST_TEST(out[4]  == "events:         onCommand onMessage ");
+		BOOST_TEST(out[5]  == "action:         accept");
 	}
 }
 
@@ -211,14 +211,14 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 7U);
+		BOOST_TEST(out.size() == 6U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s1 s2 ");
-		BOOST_TEST(out[2]  == "channels:    c1 c2 ");
-		BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
-		BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
-		BOOST_TEST(out[5]  == "action:      accept");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s1 s2 ");
+		BOOST_TEST(out[2]  == "channels:       c1 c2 ");
+		BOOST_TEST(out[3]  == "plugins:        p1 p2 ");
+		BOOST_TEST(out[4]  == "events:         onCommand onMessage ");
+		BOOST_TEST(out[5]  == "action:         accept");
 	}
 }
 
--- a/tests/src/irccdctl/cli-rule-info/main.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/tests/src/irccdctl/cli-rule-info/main.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -52,14 +52,14 @@
 	const auto [code, out, err] = exec({ "rule-info", "0" });
 
 	BOOST_TEST(!code);
-	BOOST_TEST(out.size() == 7U);
+	BOOST_TEST(out.size() == 6U);
 	BOOST_TEST(err.size() == 0U);
-	BOOST_TEST(out[0]  == "rule:        0");
-	BOOST_TEST(out[1]  == "servers:     s1 s2 ");
-	BOOST_TEST(out[2]  == "channels:    c1 c2 ");
-	BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
-	BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
-	BOOST_TEST(out[5]  == "action:      drop");
+	BOOST_TEST(out[0]  == "rule:           0");
+	BOOST_TEST(out[1]  == "servers:        s1 s2 ");
+	BOOST_TEST(out[2]  == "channels:       c1 c2 ");
+	BOOST_TEST(out[3]  == "plugins:        p1 p2 ");
+	BOOST_TEST(out[4]  == "events:         onCommand onMessage ");
+	BOOST_TEST(out[5]  == "action:         drop");
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
--- a/tests/src/irccdctl/cli-rule-list/main.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/tests/src/irccdctl/cli-rule-list/main.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -52,14 +52,14 @@
 	const auto [code, out, err] = exec({ "rule-list" });
 
 	BOOST_TEST(!code);
-	BOOST_TEST(out.size() == 7U);
+	BOOST_TEST(out.size() == 6U);
 	BOOST_TEST(err.size() == 0U);
-	BOOST_TEST(out[0]  == "rule:        0");
-	BOOST_TEST(out[1]  == "servers:     s1 s2 ");
-	BOOST_TEST(out[2]  == "channels:    c1 c2 ");
-	BOOST_TEST(out[3]  == "plugins:     p1 p2 ");
-	BOOST_TEST(out[4]  == "events:      onCommand onMessage ");
-	BOOST_TEST(out[5]  == "action:      drop");
+	BOOST_TEST(out[0]  == "rule:           0");
+	BOOST_TEST(out[1]  == "servers:        s1 s2 ");
+	BOOST_TEST(out[2]  == "channels:       c1 c2 ");
+	BOOST_TEST(out[3]  == "plugins:        p1 p2 ");
+	BOOST_TEST(out[4]  == "events:         onCommand onMessage ");
+	BOOST_TEST(out[5]  == "action:         drop");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/irccdctl/cli-rule-move/main.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/tests/src/irccdctl/cli-rule-move/main.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -77,29 +77,28 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 21U);
+		BOOST_TEST(out.size() == 20U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s2 ");
-		BOOST_TEST(out[2]  == "channels:    c2 ");
-		BOOST_TEST(out[3]  == "plugins:     p2 ");
-		BOOST_TEST(out[4]  == "events:      onCommand ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s2 ");
+		BOOST_TEST(out[2]  == "channels:       c2 ");
+		BOOST_TEST(out[3]  == "plugins:        p2 ");
+		BOOST_TEST(out[4]  == "events:         onCommand ");
+		BOOST_TEST(out[5]  == "action:         drop");
 		BOOST_TEST(out[6]  == "");
-		BOOST_TEST(out[7]  == "rule:        1");
-		BOOST_TEST(out[8]  == "servers:     s1 ");
-		BOOST_TEST(out[9]  == "channels:    c1 ");
-		BOOST_TEST(out[10] == "plugins:     p1 ");
-		BOOST_TEST(out[11] == "events:      onTopic ");
-		BOOST_TEST(out[12] == "action:      accept");
+		BOOST_TEST(out[7]  == "rule:           1");
+		BOOST_TEST(out[8]  == "servers:        s1 ");
+		BOOST_TEST(out[9]  == "channels:       c1 ");
+		BOOST_TEST(out[10] == "plugins:        p1 ");
+		BOOST_TEST(out[11] == "events:         onTopic ");
+		BOOST_TEST(out[12] == "action:         accept");
 		BOOST_TEST(out[13] == "");
-		BOOST_TEST(out[14] == "rule:        2");
-		BOOST_TEST(out[15] == "servers:     s3 ");
-		BOOST_TEST(out[16] == "channels:    c3 ");
-		BOOST_TEST(out[17] == "plugins:     p3 ");
-		BOOST_TEST(out[18] == "events:      onMessage ");
-		BOOST_TEST(out[19] == "action:      accept");
-		BOOST_TEST(out[20] == "");
+		BOOST_TEST(out[14] == "rule:           2");
+		BOOST_TEST(out[15] == "servers:        s3 ");
+		BOOST_TEST(out[16] == "channels:       c3 ");
+		BOOST_TEST(out[17] == "plugins:        p3 ");
+		BOOST_TEST(out[18] == "events:         onMessage ");
+		BOOST_TEST(out[19] == "action:         accept");
 	}
 }
 
@@ -119,29 +118,28 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 21U);
+		BOOST_TEST(out.size() == 20U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s3 ");
-		BOOST_TEST(out[2]  == "channels:    c3 ");
-		BOOST_TEST(out[3]  == "plugins:     p3 ");
-		BOOST_TEST(out[4]  == "events:      onMessage ");
-		BOOST_TEST(out[5]  == "action:      accept");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s3 ");
+		BOOST_TEST(out[2]  == "channels:       c3 ");
+		BOOST_TEST(out[3]  == "plugins:        p3 ");
+		BOOST_TEST(out[4]  == "events:         onMessage ");
+		BOOST_TEST(out[5]  == "action:         accept");
 		BOOST_TEST(out[6]  == "");
-		BOOST_TEST(out[7]  == "rule:        1");
-		BOOST_TEST(out[8]  == "servers:     s1 ");
-		BOOST_TEST(out[9]  == "channels:    c1 ");
-		BOOST_TEST(out[10] == "plugins:     p1 ");
-		BOOST_TEST(out[11] == "events:      onTopic ");
-		BOOST_TEST(out[12] == "action:      accept");
+		BOOST_TEST(out[7]  == "rule:           1");
+		BOOST_TEST(out[8]  == "servers:        s1 ");
+		BOOST_TEST(out[9]  == "channels:       c1 ");
+		BOOST_TEST(out[10] == "plugins:        p1 ");
+		BOOST_TEST(out[11] == "events:         onTopic ");
+		BOOST_TEST(out[12] == "action:         accept");
 		BOOST_TEST(out[13] == "");
-		BOOST_TEST(out[14] == "rule:        2");
-		BOOST_TEST(out[15] == "servers:     s2 ");
-		BOOST_TEST(out[16] == "channels:    c2 ");
-		BOOST_TEST(out[17] == "plugins:     p2 ");
-		BOOST_TEST(out[18] == "events:      onCommand ");
-		BOOST_TEST(out[19] == "action:      drop");
-		BOOST_TEST(out[20] == "");
+		BOOST_TEST(out[14] == "rule:           2");
+		BOOST_TEST(out[15] == "servers:        s2 ");
+		BOOST_TEST(out[16] == "channels:       c2 ");
+		BOOST_TEST(out[17] == "plugins:        p2 ");
+		BOOST_TEST(out[18] == "events:         onCommand ");
+		BOOST_TEST(out[19] == "action:         drop");
 	}
 }
 
@@ -161,29 +159,28 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 21U);
+		BOOST_TEST(out.size() == 20U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s1 ");
-		BOOST_TEST(out[2]  == "channels:    c1 ");
-		BOOST_TEST(out[3]  == "plugins:     p1 ");
-		BOOST_TEST(out[4]  == "events:      onTopic ");
-		BOOST_TEST(out[5]  == "action:      accept");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s1 ");
+		BOOST_TEST(out[2]  == "channels:       c1 ");
+		BOOST_TEST(out[3]  == "plugins:        p1 ");
+		BOOST_TEST(out[4]  == "events:         onTopic ");
+		BOOST_TEST(out[5]  == "action:         accept");
 		BOOST_TEST(out[6]  == "");
-		BOOST_TEST(out[7]  == "rule:        1");
-		BOOST_TEST(out[8]  == "servers:     s2 ");
-		BOOST_TEST(out[9]  == "channels:    c2 ");
-		BOOST_TEST(out[10] == "plugins:     p2 ");
-		BOOST_TEST(out[11] == "events:      onCommand ");
-		BOOST_TEST(out[12] == "action:      drop");
+		BOOST_TEST(out[7]  == "rule:           1");
+		BOOST_TEST(out[8]  == "servers:        s2 ");
+		BOOST_TEST(out[9]  == "channels:       c2 ");
+		BOOST_TEST(out[10] == "plugins:        p2 ");
+		BOOST_TEST(out[11] == "events:         onCommand ");
+		BOOST_TEST(out[12] == "action:         drop");
 		BOOST_TEST(out[13] == "");
-		BOOST_TEST(out[14] == "rule:        2");
-		BOOST_TEST(out[15] == "servers:     s3 ");
-		BOOST_TEST(out[16] == "channels:    c3 ");
-		BOOST_TEST(out[17] == "plugins:     p3 ");
-		BOOST_TEST(out[18] == "events:      onMessage ");
-		BOOST_TEST(out[19] == "action:      accept");
-		BOOST_TEST(out[20] == "");
+		BOOST_TEST(out[14] == "rule:           2");
+		BOOST_TEST(out[15] == "servers:        s3 ");
+		BOOST_TEST(out[16] == "channels:       c3 ");
+		BOOST_TEST(out[17] == "plugins:        p3 ");
+		BOOST_TEST(out[18] == "events:         onMessage ");
+		BOOST_TEST(out[19] == "action:         accept");
 	}
 }
 
--- a/tests/src/irccdctl/cli-rule-remove/main.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/tests/src/irccdctl/cli-rule-remove/main.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -77,22 +77,21 @@
 		const auto [code, out, err] = exec({ "rule-list" });
 
 		BOOST_TEST(!code);
-		BOOST_TEST(out.size() == 14U);
+		BOOST_TEST(out.size() == 13U);
 		BOOST_TEST(err.size() == 0U);
-		BOOST_TEST(out[0]  == "rule:        0");
-		BOOST_TEST(out[1]  == "servers:     s2 ");
-		BOOST_TEST(out[2]  == "channels:    c2 ");
-		BOOST_TEST(out[3]  == "plugins:     p2 ");
-		BOOST_TEST(out[4]  == "events:      onCommand ");
-		BOOST_TEST(out[5]  == "action:      drop");
+		BOOST_TEST(out[0]  == "rule:           0");
+		BOOST_TEST(out[1]  == "servers:        s2 ");
+		BOOST_TEST(out[2]  == "channels:       c2 ");
+		BOOST_TEST(out[3]  == "plugins:        p2 ");
+		BOOST_TEST(out[4]  == "events:         onCommand ");
+		BOOST_TEST(out[5]  == "action:         drop");
 		BOOST_TEST(out[6]  == "");
-		BOOST_TEST(out[7]  == "rule:        1");
-		BOOST_TEST(out[8]  == "servers:     s3 ");
-		BOOST_TEST(out[9]  == "channels:    c3 ");
-		BOOST_TEST(out[10] == "plugins:     p3 ");
-		BOOST_TEST(out[11] == "events:      onMessage ");
-		BOOST_TEST(out[12] == "action:      accept");
-		BOOST_TEST(out[13] == "");
+		BOOST_TEST(out[7]  == "rule:           1");
+		BOOST_TEST(out[8]  == "servers:        s3 ");
+		BOOST_TEST(out[9]  == "channels:       c3 ");
+		BOOST_TEST(out[10] == "plugins:        p3 ");
+		BOOST_TEST(out[11] == "events:         onMessage ");
+		BOOST_TEST(out[12] == "action:         accept");
 	}
 }
 
--- a/tests/src/irccdctl/cli-server-info/main.cpp	Tue Nov 13 20:21:18 2018 +0100
+++ b/tests/src/irccdctl/cli-server-info/main.cpp	Wed Nov 14 14:07:05 2018 +0100
@@ -48,17 +48,16 @@
 	BOOST_TEST(!code);
 	BOOST_TEST(out.size() == 10U);
 	BOOST_TEST(err.size() == 0U);
-	BOOST_TEST(out[0] == "Name           : test");
-	BOOST_TEST(out[1] == "Hostname       : localhost");
-	BOOST_TEST(out[2] == "Port           : 6667");
-	// TODO: find appropriate style.
-	//BOOST_TEST(out[3] == "Ipv6           : null");
-	BOOST_TEST(out[4] == "SSL            : null");
-	BOOST_TEST(out[5] == "SSL verified   : null");
-	BOOST_TEST(out[6] == "Channels       : ");
-	BOOST_TEST(out[7] == "Nickname       : francis");
-	BOOST_TEST(out[8] == "User name      : francis");
-	BOOST_TEST(out[9] == "Real name      : IRC Client Daemon");
+	BOOST_TEST(out[0] == "name:           test");
+	BOOST_TEST(out[1] == "hostname:       localhost");
+	BOOST_TEST(out[2] == "port:           6667");
+	BOOST_TEST(out[3] == "nickname:       francis");
+	BOOST_TEST(out[4] == "username:       francis");
+	BOOST_TEST(out[5] == "realname:       IRC Client Daemon");
+	BOOST_TEST(out[6] == "ipv4:           true");
+	BOOST_TEST(out[7] == "ipv6:           true");
+	BOOST_TEST(out[8] == "ssl:            false");
+	BOOST_TEST(out[9] == "channels:       ");
 }
 
 BOOST_AUTO_TEST_SUITE(errors)