Mercurial > irccd
changeset 123:c7fee63ccf92
Irccd: new configuration mechanism, #397
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 10 May 2016 21:27:40 +0200 |
parents | 2aecbd638b1c |
children | 0895acad4072 |
files | irccd/main.cpp lib/irccd/cmd-plugin-list.cpp lib/irccd/cmd-server-list.cpp lib/irccd/cmd-server-reconnect.cpp lib/irccd/config.cpp lib/irccd/config.hpp lib/irccd/fs.cpp lib/irccd/ini.cpp lib/irccd/ini.hpp lib/irccd/irccd.cpp lib/irccd/irccd.hpp lib/irccd/irccdctl.cpp lib/irccd/js-plugin.cpp lib/irccd/js-server.cpp lib/irccd/plugin.cpp lib/irccd/plugin.hpp lib/irccd/server-event.cpp |
diffstat | 17 files changed, 1163 insertions(+), 922 deletions(-) [+] |
line wrap: on
line diff
--- a/irccd/main.cpp Wed May 04 13:38:02 2016 +0200 +++ b/irccd/main.cpp Tue May 10 21:27:40 2016 +0200 @@ -16,24 +16,51 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <irccd/sysconfig.hpp> + +#if defined(HAVE_GETPID) +# include <sys/types.h> +# include <unistd.h> +# include <cerrno> +# include <cstring> +# include <fstream> +#endif + +#if defined(HAVE_DAEMON) +# include <cstdlib> +#endif + #include <csignal> -#include <irccd/sysconfig.hpp> +#include <format.h> #include <irccd/logger.hpp> #include <irccd/options.hpp> #include <irccd/path.hpp> #include <irccd/system.hpp> - #include <irccd/config.hpp> #include <irccd/irccd.hpp> +using namespace fmt::literals; + using namespace irccd; namespace { std::unique_ptr<Irccd> instance; +void usage() +{ + log::warning() << "usage: " << sys::programName() << " [options...]\n\n"; + log::warning() << "Available options:\n"; + log::warning() << " -c, --config file specify the configuration file\n"; + log::warning() << " -f, --foreground do not run as a daemon\n"; + log::warning() << " --help show this help\n"; + log::warning() << " -p, --plugin name load a specific plugin\n"; + log::warning() << " -v, --verbose be verbose" << std::endl; + std::exit(1); +} + void stop(int) { instance->stop(); @@ -41,16 +68,15 @@ void init(int &argc, char **&argv) { - // MOVE THIS IN Application - - /* Needed for some components */ + // Needed for some components. sys::setProgramName("irccd"); path::setApplicationPath(argv[0]); - /* Default logging to console */ + // Default logging to console. log::setVerbose(false); log::setInterface(std::make_unique<log::Console>()); + // Register some signals. signal(SIGINT, stop); signal(SIGTERM, stop); @@ -58,25 +84,9 @@ ++ argv; } -void usage() +parser::Result parse(int &argc, char **&argv) { - log::warning() << "usage: " << sys::programName() << " [options...]\n\n"; - log::warning() << "Available options:\n"; - log::warning() << " -c, --config file specify the configuration file\n"; - log::warning() << " -f, --foreground do not run as a daemon\n"; - log::warning() << " --help show this help\n"; - log::warning() << " -p, --plugin name load a specific plugin\n"; - log::warning() << " -v, --verbose be verbose" << std::endl; - std::exit(1); -} - -} // !namespace - -int main(int argc, char **argv) -{ - init(argc, argv); - - /* Parse command line options */ + // Parse command line options. parser::Result result; try { @@ -99,7 +109,7 @@ usage(); // NOTREACHED } - + if (pair.first == "-v" || pair.first == "--verbose") { log::setVerbose(true); } @@ -109,8 +119,153 @@ usage(); } + return result; +} + +Config open(const parser::Result &result) +{ + auto it = result.find("-c"); + + if (it != result.end() || (it = result.find("--config")) != result.end()) { + try { + return Config(it->second); + } catch (const std::exception &ex) { + throw std::runtime_error("{}: {}"_format(it->second, ex.what())); + } + } + + return Config::find(); +} + +void loadPid(const std::string &path) +{ + if (path.empty()) { + return; + } + + try { +#if defined(HAVE_GETPID) + std::ofstream out(path, std::ofstream::trunc); + + if (!out) { + throw std::runtime_error("irccd: could not open pidfile {}: {}"_format(path, std::strerror(errno))); + } + + log::debug() << "irccd: pid written in " << path << std::endl; + out << getpid() << std::endl; +#else + throw std::runtime_error("irccd: pidfile option not supported on this platform"); +#endif + } catch (const std::exception &ex) { + log::warning() << "irccd: " << ex.what() << std::endl; + } +} + +void loadGid(const std::string gid) +{ + try { + if (!gid.empty()) { +#if defined(HAVE_SETGID) + sys::setGid(gid); +#else + throw std::runtime_error("irccd: gid option not supported on this platform"); +#endif + } + } catch (const std::exception &ex) { + log::warning() << "irccd: " << ex.what() << std::endl; + } +} + +void loadUid(const std::string &uid) +{ + try { + if (!uid.empty()) { +#if defined(HAVE_SETUID) + sys::setUid(uid); +#else + throw std::runtime_error("irccd: uid option not supported on this platform"); +#endif + } + } catch (const std::exception &ex) { + log::warning() << "irccd: " << ex.what() << std::endl; + } +} + +void loadForeground(bool foreground, const parser::Result &options) +{ + try { +#if defined(HAVE_DAEMON) + if (options.count("-f") == 0 && options.count("--foreground") == 0 && !foreground) { + daemon(1, 0); + } +#endif + } catch (const std::exception &ex) { + log::warning() << "irccd: " << ex.what() << std::endl; + } +} + +void load(const Config &config, const parser::Result &options) +{ + /* + * Order matters, please be careful when changing this. + * + * 1. Open logs as early as possible to use the defined outputs on any loading errors. + */ + + // [logs] and [format] sections. + config.loadLogs(); + config.loadFormats(); + + // Show message here to use the formats. + log::info() << "irccd: using " << config.path() << std::endl; + + // [general] section. + loadPid(config.pidfile()); + loadGid(config.gid()); + loadUid(config.uid()); + loadForeground(config.isForeground(), options); + + // [transport] + for (const auto &transport : config.loadTransports()) { + instance->addTransport(transport); + } + + // [server] section. + for (const auto &server : config.loadServers()) { + instance->addServer(server); + } + + // [rule] section. + for (const auto &rule : config.loadRules()) { + instance->addRule(rule); + } + + // [plugin] section. + for (const auto &plugin : config.loadPlugins()) { + instance->addPlugin(plugin); + } +} + +} // !namespace + +int main(int argc, char **argv) +{ + init(argc, argv); + + parser::Result options = parse(argc, argv); + + // Find configuration file. instance = std::make_unique<Irccd>(); - instance->load(Config{result}); + + try { + Config cfg = open(options); + + load(cfg, options); + } catch (const std::exception &ex) { + log::warning() << "irccd: " << ex.what() << std::endl; + return 1; + } + instance->run(); return 0;
--- a/lib/irccd/cmd-plugin-list.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/cmd-plugin-list.cpp Tue May 10 21:27:40 2016 +0200 @@ -43,7 +43,7 @@ json::Value list = json::array({}); for (const auto &plugin : irccd.plugins()) { - list.append(plugin.first); + list.append(plugin->name()); } response.insert("list", std::move(list)); @@ -68,4 +68,4 @@ } // !command -} +} // !irccd
--- a/lib/irccd/cmd-server-list.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/cmd-server-list.cpp Tue May 10 21:27:40 2016 +0200 @@ -40,8 +40,8 @@ auto json = json::object({}); auto list = json::array({}); - for (const auto &pair : irccd.servers()) { - list.append(pair.first); + for (const auto &server : irccd.servers()) { + list.append(server->info().name); } json.insert("list", std::move(list));
--- a/lib/irccd/cmd-server-reconnect.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/cmd-server-reconnect.cpp Tue May 10 21:27:40 2016 +0200 @@ -50,8 +50,8 @@ if (server != request.end() && server->isString()) { irccd.requireServer(server->toString())->reconnect(); } else { - for (auto &pair : irccd.servers()) { - pair.second->reconnect(); + for (auto &server : irccd.servers()) { + server->reconnect(); } }
--- a/lib/irccd/config.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/config.cpp Tue May 10 21:27:40 2016 +0200 @@ -16,31 +16,20 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "sysconfig.hpp" +#include <cassert> -#if defined(HAVE_GETPID) -# include <sys/types.h> -# include <unistd.h> -# include <cerrno> -# include <cstring> -# include <fstream> -#endif - -#if defined(HAVE_DAEMON) -# include <cstdlib> -#endif +#include <format.h> #include "config.hpp" -#include "ini.hpp" -#include "logger.hpp" +#include "fs.hpp" +#include "irccd.hpp" #include "path.hpp" -#include "sockets.hpp" -#include "system.hpp" +#include "rule.hpp" +#include "server.hpp" +#include "sysconfig.hpp" #include "util.hpp" -#include "irccd.hpp" -using namespace std; -using namespace std::string_literals; +using namespace fmt::literals; namespace irccd { @@ -83,101 +72,74 @@ } }; -} // !namespace - -void Config::loadGeneral(const ini::Document &config) const +std::string get(const ini::Document &doc, const std::string §ion, const std::string &key) { - ini::Document::const_iterator sc = config.find("general"); - ini::Section::const_iterator it; - -#if defined(HAVE_GETPID) - if (sc != config.end()) { - it = sc->find("pidfile"); - - if (it != sc->end() && !it->value().empty()) { - std::string path = it->value(); - std::ofstream out(path, std::ofstream::trunc); + auto its = doc.find(section); - if (!out) { - log::warning() << "irccd: could not open pidfile " << path << ": " << std::strerror(errno) << std::endl; - } else { - log::debug() << "irccd: pid written in " << path << std::endl; - out << getpid(); - } - } + if (its == doc.end()) { + return ""; } -#endif -#if defined(HAVE_DAEMON) - /* CLI priority is higher */ - bool daemonize = m_options.count("-f") == 0 && m_options.count("--foreground") == 0; + auto ito = its->find(key); - if (daemonize && sc != config.end()) { - it = sc->find("foreground"); - - if (it != sc->end()) { - daemonize = !util::isBoolean(it->value()); - } + if (ito == its->end()) { + return ""; } - if (daemonize) { - daemon(1, 0); - } -#endif - - if (sc != config.end()) { - try { -#if defined(HAVE_SETGID) - if ((it = sc->find("gid")) != sc->end()) { - sys::setGid(it->value()); - } -#endif -#if defined(HAVE_SETUID) - if ((it = sc->find("uid")) != sc->end()) { - sys::setUid(it->value()); - } -#endif - } catch (const std::exception &ex) { - log::warning() << "irccd: could not set " << it->key() << ": " << ex.what() << std::endl; - } - } + return ito->value(); } -void Config::loadFormats(const ini::Document &config) const +ServerIdentity loadIdentity(const ini::Section &sc) { - ini::Document::const_iterator sc = config.find("format"); + assert(sc.key() == "identity"); + assert(sc.contains("name") && util::isIdentifierValid(sc["name"].value())); + + ServerIdentity identity; + + // Mandatory stuff. + identity.name = sc["name"].value(); + + // Optional stuff. + ini::Section::const_iterator it; - if (sc == config.end()) { - return; + if ((it = sc.find("username")) != sc.end()) { + identity.username = it->value(); + } + if ((it = sc.find("realname")) != sc.end()) { + identity.realname = it->value(); + } + if ((it = sc.find("nickname")) != sc.end()) { + identity.nickname = it->value(); + } + if ((it = sc.find("ctcp-version")) != sc.end()) { + identity.ctcpversion = it->value(); } - ini::Section::const_iterator it; - std::unique_ptr<IrccdLogFilter> filter = std::make_unique<IrccdLogFilter>(); + return identity; +} - if ((it = sc->find("debug")) != sc->cend()) { - filter->m_debug = it->value(); - } - if ((it = sc->find("info")) != sc->cend()) { - filter->m_info = it->value(); - } - if ((it = sc->find("warning")) != sc->cend()) { - filter->m_warning = it->value(); +PluginConfig loadPluginConfig(const ini::Section &sc) +{ + PluginConfig config; + + for (const auto &option : sc) { + config.emplace(option.key(), option.value()); } - log::setFilter(std::move(filter)); + return config; } -void Config::loadLogFile(const ini::Section &sc) const +std::unique_ptr<log::Interface> loadLogFile(const ini::Section &sc) { /* * TODO: improve that with CMake options. */ #if defined(IRCCD_SYSTEM_WINDOWS) - string normal = "log.txt"; - string errors = "errors.txt"; + std::string normal = "log.txt"; + std::string errors = "errors.txt"; #else - string normal = "/var/log/irccd/log.txt"; - string errors = "/var/log/irccd/errors.txt"; + std::string normal = "/var/log/irccd/log.txt"; + std::string errors = "/var/log/irccd/errors.txt"; #endif ini::Section::const_iterator it; @@ -189,177 +151,228 @@ errors = it->value(); } - log::setInterface(make_unique<log::File>(move(normal), move(errors))); + return std::make_unique<log::File>(std::move(normal), std::move(errors)); } -void Config::loadLogSyslog() const +std::unique_ptr<log::Interface> loadLogSyslog() { #if defined(HAVE_SYSLOG) - log::setInterface(make_unique<log::Syslog>()); + return std::make_unique<log::Syslog>(); #else - log::warning() << "irccd: syslog is not available on this platform" << endl; + throw std::runtime_error("logs: syslog is not available on this platform"); #endif // !HAVE_SYSLOG } -void Config::loadLogs(const ini::Document &config) const +std::shared_ptr<TransportServer> loadTransportIp(const ini::Section &sc) { - ini::Document::const_iterator sc = config.find("logs"); + assert(sc.key() == "transport"); - if (sc == config.end()) { - return; - } - + std::shared_ptr<TransportServer> transport; ini::Section::const_iterator it; - if ((it = sc->find("verbose")) != sc->end() && m_options.count("-v") == 0 && m_options.count("--verbose")) { - log::setVerbose(util::isBoolean(it->value())); + // Port + int port; + + if ((it = sc.find("port")) == sc.cend()) { + throw std::invalid_argument("transport: missing 'port' parameter"); + } + + try { + port = util::toNumber<std::uint16_t>(it->value()); + } catch (const std::exception &) { + throw std::invalid_argument("transport: invalid port number: {}"_format(it->value())); } - if ((it = sc->find("type")) != sc->end()) { - /* Console is the default, no test case */ - if (it->value() == "file") { - loadLogFile(*sc); - } else if (it->value() == "syslog") { - loadLogSyslog(); - } else { - log::warning() << "irccd: unknown log type: " << it->value() << std::endl; + + // Address + std::string address = "*"; + + if ((it = sc.find("address")) != sc.end()) { + address = it->value(); + } + + // Domain + bool ipv6 = true; + bool ipv4 = true; + + if ((it = sc.find("domain")) != sc.end()) { + ipv6 = false; + ipv4 = false; + + for (const auto &v : *it) { + if (v == "ipv4") { + ipv4 = true; + } + if (v == "ipv6") { + ipv6 = true; + } } } + + if (ipv6) { + transport = std::make_shared<TransportServerIp>(AF_INET6, move(address), port, !ipv4); + } else if (ipv4) { + transport = std::make_shared<TransportServerIp>(AF_INET, move(address), port); + } else { + throw std::invalid_argument("transport: domain must at least have ipv4 or ipv6"); + } + + return transport; } -void Config::loadPlugins(Irccd &irccd, const ini::Section &sc) const +std::shared_ptr<TransportServer> loadTransportUnix(const ini::Section &sc) { -#if defined(WITH_JS) - for (const ini::Option &option : sc) { - try { - if (option.value().empty()) { - irccd.loadPlugin(option.key(), option.key(), true); - } else { - irccd.loadPlugin(option.key(), option.value(), false); - } - } catch (const std::exception &ex) { - log::warning() << "plugin " << option.key() << ": " << ex.what() << std::endl; - } + assert(sc.key() == "transport"); + +#if !defined(IRCCD_SYSTEM_WINDOWS) + ini::Section::const_iterator it = sc.find("path"); + + if (it == sc.end()) { + throw std::invalid_argument("transport: missing 'path' parameter"); } + + return std::make_shared<TransportServerUnix>(it->value()); #else - (void)irccd; (void)sc; + + throw std::invalid_argument("transport: unix transport not supported on on this platform"); #endif } -void Config::loadPluginConfig(Irccd &irccd, const ini::Section &sc, string name) const +std::shared_ptr<TransportServer> loadTransport(const ini::Section &sc) { -#if defined(WITH_JS) - PluginConfig config; + assert(sc.key() == "transport"); - for (const ini::Option &option : sc) { - config.emplace(option.key(), option.value()); + std::shared_ptr<TransportServer> transport; + ini::Section::const_iterator it = sc.find("type"); + + if (it == sc.end()) { + throw std::invalid_argument("transport: missing 'type' parameter"); } - irccd.addPluginConfig(std::move(name), std::move(config)); -#else - (void)irccd; - (void)sc; - (void)name; -#endif + if (it->value() == "ip") { + transport = loadTransportIp(sc); + } else if (it->value() == "unix") { + transport = loadTransportUnix(sc); + } else { + throw std::invalid_argument("transport: invalid type given: {}"_format(it->value())); + } + + return transport; } -void Config::loadPlugins(Irccd &irccd, const ini::Document &config) const +Rule loadRule(const ini::Section &sc) { -#if defined(WITH_JS) - std::regex regex("^plugin\\.([A-Za-z0-9-_]+)$"); - std::smatch match; + assert(sc.key() == "rule"); + + // Simple converter from std::vector to std::unordered_set + auto toSet = [] (const std::vector<std::string> &v) -> std::unordered_set<std::string> { + return std::unordered_set<std::string>(v.begin(), v.end()); + }; + + RuleSet servers, channels, origins, plugins, events; + RuleAction action = RuleAction::Accept; + + // Get the sets + ini::Section::const_iterator it; - /* - * Load plugin configurations before we load plugins since we use them - * when we load the plugin itself. - */ - for (const ini::Section §ion : config) { - if (regex_match(section.key(), match, regex)) { - loadPluginConfig(irccd, section, match[1]); - } + if ((it = sc.find("servers")) != sc.end()) { + servers = toSet(*it); + } + if ((it = sc.find("channels")) != sc.end()) { + channels = toSet(*it); + } + if ((it = sc.find("origins")) != sc.end()) { + origins = toSet(*it); + } + if ((it = sc.find("plugins")) != sc.end()) { + plugins = toSet(*it); + } + if ((it = sc.find("channels")) != sc.end()) { + channels = toSet(*it); } - ini::Document::const_iterator it = config.find("plugins"); - - if (it != config.end()) { - loadPlugins(irccd, *it); + // Get the action + if ((it = sc.find("action")) == sc.end()) { + throw std::invalid_argument("rule: missing 'action'' parameter"); } -#else - (void)irccd; - (void)config; - log::warning() << "irccd: JavaScript disabled, ignoring plugins" << std::endl; -#endif + if (it->value() == "drop") { + action = RuleAction::Drop; + } else if (it->value() == "accept") { + action = RuleAction::Accept; + } else { + throw std::invalid_argument("rule: invalid action given: {}"_format(it->value())); + } + + return Rule(std::move(servers), + std::move(channels), + std::move(origins), + std::move(plugins), + std::move(events), + action); } -void Config::loadServer(Irccd &irccd, const ini::Section &sc) const +std::shared_ptr<Server> loadServer(const ini::Section &sc, const Config &config) { + assert(sc.key() == "server"); + ServerInfo info; ServerIdentity identity; ServerSettings settings; - /* Name */ + // Name ini::Section::const_iterator it; if ((it = sc.find("name")) == sc.end()) { - throw std::invalid_argument("server: missing name"); + throw std::invalid_argument("server: missing 'name' parameter"); } else if (!util::isIdentifierValid(it->value())) { - throw std::invalid_argument("server " + it->value() + ": name is not valid"); - } else if (irccd.hasServer(it->value())) { - throw std::invalid_argument("server " + it->value() + ": already exists"); + throw std::invalid_argument("server: invalid identifier: {}"_format(it->value())); } info.name = it->value(); - /* Host */ + // Host if ((it = sc.find("host")) == sc.end()) { - throw std::invalid_argument("server " + info.name + ": missing host"); + throw std::invalid_argument("server {}: missing host"_format(it->value())); } info.host = it->value(); - /* Optional identity */ + // Optional identity if ((it = sc.find("identity")) != sc.end()) { - identity = irccd.findIdentity(it->value()); + identity = config.findIdentity(it->value()); } - /* Optional port */ + // Optional port if ((it = sc.find("port")) != sc.end()) { try { - info.port = std::stoi(it->value()); + info.port = util::toNumber<std::uint16_t>(it->value()); } catch (const std::exception &) { - throw std::invalid_argument("server " + info.name + ": invalid port number: " + it->value()); + throw std::invalid_argument("server {}: invalid number for {}: {}"_format(info.name, it->key(), it->value())); } } - /* Optional password */ + // Optional password if ((it = sc.find("password")) != sc.end()) { info.password = it->value(); } - /* Optional flags */ + // Optional flags if ((it = sc.find("ipv6")) != sc.end() && util::isBoolean(it->value())) { info.flags |= ServerInfo::Ipv6; } if ((it = sc.find("ssl")) != sc.end()) { if (util::isBoolean(it->value())) { -#if defined(WITH_SSL) info.flags |= ServerInfo::Ssl; -#else - throw std::invalid_argument("server " + info.name + ": ssl is disabled"); -#endif } } if ((it = sc.find("ssl-verify")) != sc.end()) { if (util::isBoolean(it->value())) { -#if defined(WITH_SSL) info.flags |= ServerInfo::SslVerify; -#else - throw std::invalid_argument("server " + info.name + ": ssl is disabled"); -#endif } } - /* Options */ + + // Options if ((it = sc.find("auto-rejoin")) != sc.end() && util::isBoolean(it->value())) { settings.flags |= ServerSettings::AutoRejoin; } @@ -367,7 +380,7 @@ settings.flags |= ServerSettings::JoinInvite; } - /* Channels */ + // Channels if ((it = sc.find("channels")) != sc.end()) { for (const std::string &s : *it) { ServerChannel channel; @@ -386,297 +399,246 @@ settings.command = it->value(); } - /* Reconnect */ + // Reconnect and ping timeout try { if ((it = sc.find("reconnect-tries")) != sc.end()) { - settings.reconnectTries = std::stoi(it->value()); + settings.reconnectTries = util::toNumber<std::int8_t>(it->value()); } if ((it = sc.find("reconnect-timeout")) != sc.end()) { - settings.reconnectDelay = std::stoi(it->value()); + settings.reconnectDelay = util::toNumber<std::uint16_t>(it->value()); } if ((it = sc.find("ping-timeout")) != sc.end()) { - settings.pingTimeout = std::stoi(it->value()); + settings.pingTimeout = util::toNumber<std::uint16_t>(it->value()); } } catch (const std::exception &) { - throw std::invalid_argument("server " + info.name + ": invalid number for " + it->key() + ": " + it->value()); + log::warning("server {}: invalid number for {}: {}"_format(info.name, it->key(), it->value())); } - irccd.addServer(std::make_shared<Server>(std::move(info), std::move(identity), std::move(settings))); -} - -void Config::loadServers(Irccd &irccd, const ini::Document &config) const -{ - for (const ini::Section §ion : config) { - if (section.key() == "server") { - try { - loadServer(irccd, section); - } catch (const exception &ex) { - log::warning() << ex.what() << endl; - } - } - } + return std::make_shared<Server>(std::move(info), std::move(identity), std::move(settings)); } -void Config::loadIdentity(Irccd &irccd, const ini::Section &sc) const +} // !namespace + +Config Config::find() { - ServerIdentity identity; - ini::Section::const_iterator it; + for (const auto &path : path::list(path::PathConfig)) { + std::string fullpath = path + "irccd.conf"; - if ((it = sc.find("name")) == sc.end()) { - throw invalid_argument("missing name"); - } else if (!util::isIdentifierValid(it->value())) { - throw invalid_argument("identity name not valid"); + if (!fs::isReadable(fullpath)) { + continue; + } + + try { + return Config(fullpath); + } catch (const std::exception &ex) { + throw std::runtime_error("{}: {}"_format(fullpath, ex.what())); + } } - identity.name = it->value(); - - /* Optional stuff */ - if ((it = sc.find("username")) != sc.end()) { - identity.username = it->value(); - } - if ((it = sc.find("realname")) != sc.end()) { - identity.realname = it->value(); - } - if ((it = sc.find("nickname")) != sc.end()) { - identity.nickname = it->value(); - } - if ((it = sc.find("ctcp-version")) != sc.end()) { - identity.ctcpversion = it->value(); - } - - log::debug() << "identity " << identity.name << ": "; - log::debug() << "nickname=" << identity.nickname << ", username=" << identity.username << ", "; - log::debug() << "realname=" << identity.realname << ", ctcp-version=" << identity.ctcpversion << endl; - - irccd.addIdentity(move(identity)); + throw std::runtime_error("no configuration file found"); } -void Config::loadIdentities(Irccd &irccd, const ini::Document &config) const +ServerIdentity Config::findIdentity(const std::string &name) const { - for (const ini::Section §ion : config) { - if (section.key() == "identity") { - try { - loadIdentity(irccd, section); - } catch (const exception &ex) { - log::warning() << "identity: " << ex.what() << endl; - } + assert(util::isIdentifierValid(name)); + + for (const auto §ion : m_document) { + if (section.key() != "identity") { + continue; + } + + auto it = section.find("name"); + + if (it == section.end()) { + log::warning("identity: missing 'name' property"); + continue; } + if (!util::isIdentifierValid(it->value())) { + log::warning("identity: invalid identifier: {}"_format(it->value())); + continue; + } + if (it->value() != name) { + continue; + } + + return loadIdentity(section); } + + return ServerIdentity(); } -void Config::loadRule(Irccd &irccd, const ini::Section &sc) const +PluginConfig Config::findPluginConfig(const std::string &name) const +{ + assert(util::isIdentifierValid(name)); + + std::string fullname = std::string("plugin.") + name; + + for (const auto §ion : m_document) { + if (section.key() != fullname) { + continue; + } + + return loadPluginConfig(section); + } + + return PluginConfig(); +} + +bool Config::isVerbose() const noexcept +{ + return util::isBoolean(get(m_document, "logs", "verbose")); +} + +bool Config::isForeground() const noexcept { - /* Simple converter from std::vector to std::unordered_set */ - auto toSet = [] (const std::vector<std::string> &v) -> std::unordered_set<std::string> { - return std::unordered_set<std::string>(v.begin(), v.end()); - }; + return util::isBoolean(get(m_document, "general", "foreground")); +} + +std::string Config::pidfile() const +{ + return get(m_document, "general", "pidfile"); +} + +std::string Config::uid() const +{ + return get(m_document, "general", "uid"); +} - RuleSet servers, channels, origins, plugins, events; - RuleAction action = RuleAction::Accept; +std::string Config::gid() const +{ + return get(m_document, "general", "gid"); +} - /* Get the sets */ +void Config::loadLogs() const +{ + ini::Document::const_iterator sc = m_document.find("logs"); + + if (sc == m_document.end()) { + return; + } + ini::Section::const_iterator it; - if ((it = sc.find("servers")) != sc.end()) { - servers = toSet(*it); - } - if ((it = sc.find("channels")) != sc.end()) { - channels = toSet(*it); - } - if ((it = sc.find("origins")) != sc.end()) { - origins = toSet(*it); - } - if ((it = sc.find("plugins")) != sc.end()) { - plugins = toSet(*it); - } - if ((it = sc.find("channels")) != sc.end()) { - channels = toSet(*it); - } + if ((it = sc->find("type")) != sc->end()) { + std::unique_ptr<log::Interface> iface; - /* Get the action */ - if ((it = sc.find("action")) == sc.end()) { - throw std::invalid_argument("missing action parameter"); - } - if (it->value() == "drop") { - action = RuleAction::Drop; - } else if (it->value() == "accept") { - action = RuleAction::Accept; - } else { - throw std::invalid_argument("invalid action given: " + it->value()); - } + /* Console is the default, no test case */ + if (it->value() == "file") { + iface = loadLogFile(*sc); + } else if (it->value() == "syslog") { + iface = loadLogSyslog(); + } else { + throw std::runtime_error("logs: unknown log type: {}"_format(it->value())); + } - irccd.addRule(Rule(move(servers), move(channels), move(origins), move(plugins), move(events), action)); -} - -void Config::loadRules(Irccd &irccd, const ini::Document &config) const -{ - for (const ini::Section &sc : config) { - if (sc.key() == "rule") { - try { - loadRule(irccd, sc); - } catch (const std::exception &ex) { - log::warning() << "rule: " << ex.what() << std::endl; - } + if (iface) { + log::setInterface(std::move(iface)); } } } -void Config::loadTransportIp(Irccd &irccd, const ini::Section &sc) const +void Config::loadFormats() const { - bool ipv6 = true; - bool ipv4 = true; + ini::Document::const_iterator sc = m_document.find("format"); + if (sc == m_document.end()) { + return; + } + + std::unique_ptr<IrccdLogFilter> filter = std::make_unique<IrccdLogFilter>(); ini::Section::const_iterator it; - /* Port */ - int port; - - if ((it = sc.find("port")) == sc.end()) { - throw invalid_argument("missing port"); + if ((it = sc->find("debug")) != sc->cend()) { + filter->m_debug = it->value(); } - - try { - port = stoi(it->value()); - } catch (const std::exception &) { - throw std::invalid_argument("invalid port number: " + it->value()); + if ((it = sc->find("info")) != sc->cend()) { + filter->m_info = it->value(); + } + if ((it = sc->find("warning")) != sc->cend()) { + filter->m_warning = it->value(); } - /* Address*/ - std::string address = "*"; + log::setFilter(std::move(filter)); +} - if ((it = sc.find("address")) != sc.end()) { - address = it->value(); +std::vector<std::shared_ptr<TransportServer>> Config::loadTransports() const +{ + std::vector<std::shared_ptr<TransportServer>> transports; + + for (const auto §ion : m_document) { + if (section.key() == "transport") { + transports.push_back(loadTransport(section)); + } } - /* Domain */ - if ((it = sc.find("domain")) != sc.end()) { - ipv6 = false; - ipv4 = false; + return transports; +} - for (const string &v : *it) { - if (v == "ipv4") { - ipv4 = true; - } - if (v == "ipv6") { - ipv6 = true; - } +std::vector<Rule> Config::loadRules() const +{ + std::vector<Rule> rules; + + for (const auto §ion : m_document) { + if (section.key() == "rule") { + rules.push_back(loadRule(section)); } } - if (ipv6) { - irccd.addTransport(std::make_shared<TransportServerIp>(AF_INET6, move(address), port, !ipv4)); - } else if (ipv4) { - irccd.addTransport(std::make_shared<TransportServerIp>(AF_INET, move(address), port)); - } else { - throw std::invalid_argument("domain must at least have ipv4 or ipv6"); - } -} - -void Config::loadTransportUnix(Irccd &irccd, const ini::Section &sc) const -{ -#if !defined(IRCCD_SYSTEM_WINDOWS) - /* Path */ - ini::Section::const_iterator it = sc.find("path"); - - if (it == sc.end()) { - throw std::invalid_argument("missing path parameter"); - } else { - string path = sc["path"].value(); - - irccd.addTransport(std::make_shared<TransportServerUnix>(move(path))); - } -#else - (void)irccd; - (void)sc; - - throw std::invalid_argument("local transport not supported on on this platform"); -#endif + return rules; } -void Config::loadTransports(Irccd &irccd, const ini::Document &config) const +std::vector<std::shared_ptr<Server>> Config::loadServers() const { - for (const ini::Section &sc : config) { - if (sc.key() == "transport") { - try { - ini::Section::const_iterator it = sc.find("type"); - - if (it == sc.end()) { - log::warning() << "transport: missing type parameter" << std::endl; - } else if (it->value() == "ip") { - loadTransportIp(irccd, sc); - } else if (it->value() == "unix") { - loadTransportUnix(irccd, sc); - } else { - log::warning() << "transport: invalid type given: " << std::endl; - } - } catch (const net::Error &error) { - log::warning() << "transport: " << error.function() << ": " << error.what() << std::endl; - } catch (const exception &ex) { - log::warning() << "transport: error: " << ex.what() << endl; - } - } - } -} - -bool Config::openConfig(Irccd &irccd, const string &path) const -{ - try { - /* - * Order matters, take care when you change this. - */ - ini::Document config(ini::File{path}); + std::vector<std::shared_ptr<Server>> servers; - loadGeneral(config); - loadLogs(config); - loadFormats(config); - loadIdentities(irccd, config); - loadServers(irccd, config); - loadRules(irccd, config); - loadPlugins(irccd, config); - loadTransports(irccd, config); - } catch (const ini::Error &ex) { - log::warning() << sys::programName() << ": " << path << ":" << ex.line() << ":" << ex.column() << ": " << ex.what() << std::endl; - return false; - } catch (const std::exception &ex) { - log::warning() << sys::programName() << ": " << path << ": " << ex.what() << std::endl; - return false; - } - - return true; -} + for (const auto §ion : m_document) { + if (section.key() != "server") { + continue; + } -Config::Config(parser::Result options) noexcept - : m_options(move(options)) -{ -} - -void Config::load(Irccd &irccd) -{ - auto it = m_options.find("-c"); - auto found = false; - - if (it != m_options.end()) { - found = openConfig(irccd, it->second); - } else if ((it = m_options.find("--config")) != m_options.end()) { - found = openConfig(irccd, it->second); - } else { - /* Search for a configuration file */ - for (const string &path : path::list(path::PathConfig)) { - string fullpath = path + "irccd.conf"; - - log::info() << "irccd: trying " << fullpath << endl; - - if (openConfig(irccd, fullpath)) { - found = true; - break; - } + try { + servers.push_back(loadServer(section, *this)); + } catch (const std::exception &ex) { + log::warning(ex.what()); } } - if (!found) { - log::warning() << "irccd: no configuration file could be found, exiting" << endl; - exit(1); + return servers; +} + +std::vector<std::shared_ptr<Plugin>> Config::loadPlugins() const +{ + std::vector<std::shared_ptr<Plugin>> plugins; + + // Plugins are defined in only one section. + auto it = m_document.find("plugins"); + + if (it == m_document.end()) { + return plugins; } + + for (const auto &option : *it) { + std::string name = option.key(); + std::string path = option.value(); + + try { + log::info("plugin {}: loading"_format(name)); + + if (path.empty()) { + plugins.push_back(Plugin::find(name, findPluginConfig(name))); + } else { + log::info("plugin {}: trying {}"_format(name, path)); + plugins.push_back(std::make_shared<Plugin>(name, path, findPluginConfig(name))); + } + } catch (const duk::ErrorInfo &ex) { + log::warning("plugin {}: {}"_format(option.key(), ex.what())); + log::warning("plugin {}: {}"_format(option.key(), ex.stack)); + } catch (const std::exception &ex) { + log::warning("plugin {}: {}"_format(option.key(), ex.what())); + } + } + + return plugins; } } // !irccd
--- a/lib/irccd/config.hpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/config.hpp Tue May 10 21:27:40 2016 +0200 @@ -24,18 +24,21 @@ * \brief Read .ini configuration file for irccd */ -#include "options.hpp" +#include <memory> +#include <string> +#include <vector> + +#include "ini.hpp" +#include "plugin.hpp" namespace irccd { -namespace ini { +class Rule; -class Document; -class Section; +class Server; +class ServerIdentity; -} // !ini - -class Irccd; +class TransportServer; /** * \class Config @@ -43,42 +46,127 @@ */ class Config { private: - parser::Result m_options; - - void loadGeneral(const ini::Document &config) const; - void loadFormats(const ini::Document &config) const; - void loadLogFile(const ini::Section &sc) const; - void loadLogSyslog() const; - void loadLogs(const ini::Document &config) const; - void loadPlugins(Irccd &irccd, const ini::Section &sc) const; - void loadPluginConfig(Irccd &irccd, const ini::Section &sc, std::string name) const; - void loadPlugins(Irccd &irccd, const ini::Document &config) const; - void loadServer(Irccd &irccd, const ini::Section &sc) const; - void loadServers(Irccd &irccd, const ini::Document &config) const; - void loadIdentity(Irccd &irccd, const ini::Section &sc) const; - void loadIdentities(Irccd &irccd, const ini::Document &config) const; - void loadRule(Irccd &irccd, const ini::Section &sc) const; - void loadRules(Irccd &irccd, const ini::Document &config) const; - void loadTransportIp(Irccd &irccd, const ini::Section &sc) const; - void loadTransportUnix(Irccd &irccd, const ini::Section &sc) const; - void loadTransports(Irccd &irccd, const ini::Document &config) const; - bool openConfig(Irccd &irccd, const std::string &path) const; + std::string m_path; + ini::Document m_document; public: /** - * Construct the configuration file loader. If path is empty, then the configuration file is searched through - * the standard directories. + * Search the configuration file into the standard defined paths. + * + * \return the config + * \throw std::exception on errors or if no config could be found + */ + static Config find(); + + /** + * Load the configuration from the specified path. + * + * \param path the path + */ + inline Config(std::string path) + : m_path(std::move(path)) + , m_document(ini::readFile(m_path)) + { + } + + /** + * Get the path to the configuration file. * - * \param options the option parsed at command line + * \return the path + */ + inline const std::string &path() const noexcept + { + return m_path; + } + + /** + * Find an entity if defined in the configuration file. + * + * \pre util::isValidIdentifier(name) + * \return default identity if cannot be found */ - Config(parser::Result options) noexcept; + ServerIdentity findIdentity(const std::string &name) const; + + /** + * Find a plugin configuration if defined in the configuration file. + * + * \pre util::isValidIdentifier(name) + * \return the configuration or empty if not found + */ + PluginConfig findPluginConfig(const std::string &name) const; + + /** + * Get the path to the pidfile. + * + * \return the path or empty if not defined + */ + std::string pidfile() const; /** - * Load the config into irccd. + * Get the uid. + * + * \return the uid or empty one if no one is set + */ + std::string uid() const; + + /** + * Get the gid. + * + * \return the gid or empty one if no one is set + */ + std::string gid() const; + + /** + * Check if verbosity is enabled. + * + * \return true if verbosity was requested + */ + bool isVerbose() const noexcept; + + /** + * Check if foreground is specified (= no daemonize). * - * \param irccd the irccd instance + * \return true if foreground was requested + */ + bool isForeground() const noexcept; + + /** + * Load logging interface. + */ + void loadLogs() const; + + /** + * Load formats for logging. + */ + void loadFormats() const; + + /** + * Load transports. + * + * \return the set of transports */ - void load(Irccd &irccd); + std::vector<std::shared_ptr<TransportServer>> loadTransports() const; + + /** + * Load rules. + * + * \return the rules + */ + std::vector<Rule> loadRules() const; + + /** + * Get the list of servers defined. + * + * \return the list of servers + */ + std::vector<std::shared_ptr<Server>> loadServers() const; + + /** + * Get the list of defined plugins. + * + * \return the list of plugins + */ + std::vector<std::shared_ptr<Plugin>> loadPlugins() const; }; } // !irccd
--- a/lib/irccd/fs.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/fs.cpp Tue May 10 21:27:40 2016 +0200 @@ -67,11 +67,14 @@ bool can(const std::string &path, const std::string &mode) { auto fp = std::fopen(path.c_str(), mode.c_str()); - auto result = fp != nullptr; + + if (fp == nullptr) { + return false; + } std::fclose(fp); - return result; + return true; } #if defined(_WIN32)
--- a/lib/irccd/ini.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/ini.cpp Tue May 10 21:27:40 2016 +0200 @@ -1,5 +1,5 @@ /* - * ini.cpp -- .ini file parsing + * ini.cpp -- extended .ini file parser * * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> * @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <cassert> #include <cctype> #include <cstring> #include <iostream> @@ -25,16 +24,18 @@ #include <sstream> #include <stdexcept> +/* for PathIsRelative */ #if defined(_WIN32) -# include <Shlwapi.h> // for PathIsRelative +# include <Shlwapi.h> #endif #include "ini.hpp" +namespace irccd { + namespace { -using namespace irccd; -using namespace irccd::ini; +using namespace ini; using StreamIterator = std::istreambuf_iterator<char>; using TokenIterator = std::vector<Token>::const_iterator; @@ -69,7 +70,7 @@ return isList(c) || isQuote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '='; } -void analyzeLine(int &line, int &column, StreamIterator &it) noexcept +void analyseLine(int &line, int &column, StreamIterator &it) noexcept { assert(*it == '\n'); @@ -78,7 +79,7 @@ column = 0; } -void analyzeComment(int &column, StreamIterator &it, StreamIterator end) noexcept +void analyseComment(int &column, StreamIterator &it, StreamIterator end) noexcept { assert(*it == '#'); @@ -88,7 +89,7 @@ } } -void analyzeSpaces(int &column, StreamIterator &it, StreamIterator end) noexcept +void analyseSpaces(int &column, StreamIterator &it, StreamIterator end) noexcept { assert(isSpace(*it)); @@ -98,7 +99,7 @@ } } -void analyzeList(Tokens &list, int line, int &column, StreamIterator &it) noexcept +void analyseList(Tokens &list, int line, int &column, StreamIterator &it) noexcept { assert(isList(*it)); @@ -117,7 +118,7 @@ } } -void analyzeSection(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +void analyseSection(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) { assert(*it == '['); @@ -127,16 +128,23 @@ /* Read section name */ ++ it; while (it != end && *it != ']') { - if (*it == '\n') + if (*it == '\n') { throw Error(line, column, "section not terminated, missing ']'"); - if (isReserved(*it)) + } + if (isReserved(*it)) { throw Error(line, column, "section name expected after '[', got '" + std::string(1, *it) + "'"); + } + ++ column; value += *it++; } - if (it == end) + if (it == end) { throw Error(line, column, "section name expected after '[', got <EOF>"); + } + if (value.empty()) { + throw Error(line, column, "empty section name"); + } /* Remove ']' */ ++ it; @@ -144,7 +152,7 @@ list.emplace_back(Token::Section, line, save, std::move(value)); } -void analyzeAssign(Tokens &list, int &line, int &column, StreamIterator &it) +void analyseAssign(Tokens &list, int &line, int &column, StreamIterator &it) { assert(*it == '='); @@ -152,7 +160,7 @@ ++ it; } -void analyzeQuotedWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +void analyseQuotedWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) { std::string value; int save = column; @@ -164,8 +172,9 @@ value += *it++; } - if (it == end) + if (it == end) { throw Error(line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>"); + } /* Remove quote */ ++ it; @@ -173,7 +182,7 @@ list.push_back({ Token::QuotedWord, line, save, std::move(value) }); } -void analyzeWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +void analyseWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) { assert(!isReserved(*it)); @@ -188,7 +197,7 @@ list.push_back({ Token::Word, line, save, std::move(value) }); } -void analyzeInclude(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +void analyseInclude(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) { assert(*it == '@'); @@ -202,42 +211,13 @@ include += *it++; } - if (include != "include") + if (include != "include") { throw Error(line, column, "expected include after '@' token"); + } list.push_back({ Token::Include, line, save }); } -Tokens analyze(StreamIterator &it, StreamIterator end) -{ - Tokens list; - int line = 1; - int column = 0; - - while (it != end) { - if (*it == '\n') - analyzeLine(line, column, it); - else if (*it == '#') - analyzeComment(column, it, end); - else if (*it == '[') - analyzeSection(list, line, column, it, end); - else if (*it == '=') - analyzeAssign(list, line, column, it); - else if (isSpace(*it)) - analyzeSpaces(column, it, end); - else if (*it == '@') - analyzeInclude(list, line, column, it, end); - else if (isQuote(*it)) - analyzeQuotedWord(list, line, column, it, end); - else if (isList(*it)) - analyzeList(list, line, column, it); - else - analyzeWord(list, line, column, it, end); - } - - return list; -} - void parseOptionValueSimple(Option &option, TokenIterator &it) { assert(it->type() == Token::Word || it->type() == Token::QuotedWord); @@ -270,8 +250,9 @@ } } - if (it == end) + if (it == end) { throw Error(save->line(), save->column(), "unterminated list construct"); + } /* Remove ) */ ++ it; @@ -284,50 +265,53 @@ TokenIterator save = it; /* No '=' or something else? */ - if (++it == end) + if (++it == end) { throw Error(save->line(), save->column(), "expected '=' assignment, got <EOF>"); - if (it->type() != Token::Assign) + } + if (it->type() != Token::Assign) { throw Error(it->line(), it->column(), "expected '=' assignment, got " + it->value()); + } /* Empty options are allowed so just test for words */ if (++it != end) { - if (it->type() == Token::Word || it->type() == Token::QuotedWord) + if (it->type() == Token::Word || it->type() == Token::QuotedWord) { parseOptionValueSimple(option, it); - else if (it->type() == Token::ListBegin) + } else if (it->type() == Token::ListBegin) { parseOptionValueList(option, it, end); + } } sc.push_back(std::move(option)); } -void parseInclude(Document &doc, TokenIterator &it, TokenIterator end) +void parseInclude(Document &doc, const std::string &path, TokenIterator &it, TokenIterator end) { TokenIterator save = it; - if (++it == end) + if (++it == end) { throw Error(save->line(), save->column(), "expected file name after '@include' statement, got <EOF>"); - if (it->type() != Token::Word && it->type() != Token::QuotedWord) + } + + if (it->type() != Token::Word && it->type() != Token::QuotedWord) { throw Error(it->line(), it->column(), "expected file name after '@include' statement, got " + it->value()); - if (doc.path().empty()) - throw Error(it->line(), it->column(), "'@include' statement invalid with buffer documents"); + } std::string value = (it++)->value(); std::string file; if (!isAbsolute(value)) { #if defined(_WIN32) - file = doc.path() + "\\" + value; + file = path + "\\" + value; #else - file = doc.path() + "/" + value; + file = path + "/" + value; #endif } else { file = value; } - Document child(File{file}); - - for (const auto &sc : child) + for (const auto &sc : readFile(file)) { doc.push_back(sc); + } } void parseSection(Document &doc, TokenIterator &it, TokenIterator end) @@ -339,8 +323,9 @@ /* Read until next section */ while (it != end && it->type() != Token::Section) { - if (it->type() != Token::Word) + if (it->type() != Token::Word) { throw Error(it->line(), it->column(), "unexpected token '" + it->value() + "' in section definition"); + } parseOption(sc, it, end); } @@ -348,16 +333,56 @@ doc.push_back(std::move(sc)); } -void parse(Document &doc, const Tokens &tokens) +} // !namespace + +namespace ini { + +Tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end) { + Tokens list; + int line = 1; + int column = 0; + + while (it != end) { + if (*it == '\n') { + analyseLine(line, column, it); + } else if (*it == '#') { + analyseComment(column, it, end); + } else if (*it == '[') { + analyseSection(list, line, column, it, end); + } else if (*it == '=') { + analyseAssign(list, line, column, it); + } else if (isSpace(*it)) { + analyseSpaces(column, it, end); + } else if (*it == '@') { + analyseInclude(list, line, column, it, end); + } else if (isQuote(*it)) { + analyseQuotedWord(list, line, column, it, end); + } else if (isList(*it)) { + analyseList(list, line, column, it); + } else { + analyseWord(list, line, column, it, end); + } + } + + return list; +} + +Tokens analyse(std::istream &stream) +{ + return analyse(std::istreambuf_iterator<char>(stream), {}); +} + +Document parse(const Tokens &tokens, const std::string &path) +{ + Document doc; TokenIterator it = tokens.cbegin(); TokenIterator end = tokens.cend(); while (it != end) { - /* Just ignore this */ switch (it->type()) { case Token::Include: - parseInclude(doc, it, end); + parseInclude(doc, path, it, end); break; case Token::Section: parseSection(doc, it, end); @@ -366,60 +391,44 @@ throw Error(it->line(), it->column(), "unexpected '" + it->value() + "' on root document"); } } -} -} // !namespace - -namespace irccd { - -namespace ini { - -Tokens Document::analyze(const File &file) -{ - std::fstream stream(file.path); - - if (!stream) - throw std::runtime_error(std::strerror(errno)); - - std::istreambuf_iterator<char> it(stream); - std::istreambuf_iterator<char> end; - - return ::analyze(it, end); + return doc; } -Tokens Document::analyze(const Buffer &buffer) +Document readFile(const std::string &filename) { - std::istringstream stream(buffer.text); - std::istreambuf_iterator<char> it(stream); - std::istreambuf_iterator<char> end; + /* Get parent path */ + auto parent = filename; + auto pos = parent.find_last_of("/\\"); - return ::analyze(it, end); + if (pos != std::string::npos) { + parent.erase(pos); + } else { + parent = "."; + } + + std::ifstream input(filename); + + if (!input) { + throw Error(0, 0, std::strerror(errno)); + } + + return parse(analyse(input), parent); } -Document::Document(const File &file) - : m_path(file.path) +Document readString(const std::string &buffer) { - /* Update path */ - auto pos = m_path.find_last_of("/\\"); + std::istringstream iss(buffer); - if (pos != std::string::npos) - m_path.erase(pos); - else - m_path = "."; - - parse(*this, analyze(file)); + return parse(analyse(iss)); } -Document::Document(const Buffer &buffer) +void dump(const Tokens &tokens) { - parse(*this, analyze(buffer)); -} - -void Document::dump(const Tokens &tokens) -{ - for (const Token &token: tokens) + for (const Token &token: tokens) { // TODO: add better description std::cout << token.line() << ":" << token.column() << ": " << token.value() << std::endl; + } } } // !ini
--- a/lib/irccd/ini.hpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/ini.hpp Tue May 10 21:27:40 2016 +0200 @@ -1,5 +1,5 @@ /* - * ini.hpp -- .ini file parsing + * ini.hpp -- extended .ini file parser * * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> * @@ -21,10 +21,90 @@ /** * \file ini.hpp - * \brief Configuration file parser. + * \brief Extended .ini file parser. + * \author David Demelier <markand@malikania.fr> + */ + +/** + * \page Ini Ini + * \brief Extended .ini file parser. + * + * - \subpage ini-syntax + */ + +/** + * \page ini-syntax Syntax + * \brief File syntax. + * + * The syntax is similar to most of `.ini` implementations as: + * + * - a section is delimited by `[name]` can be redefined multiple times, + * - an option **must** always be defined in a section, + * - empty options must be surrounded by quotes, + * - lists can not includes trailing commas, + * - include statement must always be at the beginning of files (in no sections), + * - comments starts with # until the end of line, + * - options with spaces **must** use quotes. + * + * # Basic file + * + * ````ini + * # This is a comment. + * [section] + * option1 = value1 + * option2 = "value 2 with spaces" # comment is also allowed here + * ```` + * + * # Redefinition + * + * Sections can be redefined multiple times and are kept the order they are seen. + * + * ````ini + * [section] + * value = "1" + * + * [section] + * value = "2" + * ```` + * + * The ini::Document object will contains two ini::Section. + * + * # Lists + * + * Lists are defined using `()` and commas, like values, they may have quotes. + * + * ````ini + * [section] + * names = ( "x1", "x2" ) + * + * # This is also allowed + * biglist = ( + * "abc", + * "def" + * ) + * ```` + * + * # Include statement + * + * You can split a file into several pieces, if the include statement contains a relative path, the path will be relative + * to the current file being parsed. + * + * You **must** use the include statement before any section. + * + * If the file contains spaces, use quotes. + * + * ````ini + * # main.conf + * @include "foo.conf" + * + * # foo.conf + * [section] + * option1 = value1 + * ```` */ #include <algorithm> +#include <cassert> #include <exception> #include <stdexcept> #include <string> @@ -41,7 +121,7 @@ /** * \class Error - * \brief Error in a file + * \brief Error in a file. */ class Error : public std::exception { private: @@ -53,14 +133,14 @@ /** * Constructor. * - * \param l the line - * \param c the column - * \param m the message + * \param line the line + * \param column the column + * \param msg the message */ - inline Error(int l, int c, std::string m) noexcept - : m_line(l) - , m_column(c) - , m_message(std::move(m)) + inline Error(int line, int column, std::string msg) noexcept + : m_line(line) + , m_column(column) + , m_message(std::move(msg)) { } @@ -97,16 +177,16 @@ /** * \class Token - * \brief Describe a token read in the .ini source + * \brief Describe a token read in the .ini source. * * This class can be used when you want to parse a .ini file yourself. * - * \see Document::analyze + * \see analyze */ class Token { public: /** - * \brief Token type + * \brief Token type. */ enum Type { Include, //!< include statement @@ -224,30 +304,35 @@ /** * Construct an empty option. * + * \pre key must not be empty * \param key the key - * \param value the value */ inline Option(std::string key) noexcept : std::vector<std::string>() , m_key(std::move(key)) { + assert(!m_key.empty()); } /** * Construct a single option. * + * \pre key must not be empty * \param key the key * \param value the value */ inline Option(std::string key, std::string value) noexcept : m_key(std::move(key)) { + assert(!m_key.empty()); + push_back(std::move(value)); } /** * Construct a list option. * + * \pre key must not be empty * \param key the key * \param values the values */ @@ -255,6 +340,7 @@ : std::vector<std::string>(std::move(values)) , m_key(std::move(key)) { + assert(!m_key.empty()); } /** @@ -292,11 +378,13 @@ /** * Construct a section with its name. * + * \pre key must not be empty * \param key the key */ inline Section(std::string key) noexcept : m_key(std::move(key)) { + assert(!m_key.empty()); } /** @@ -321,30 +409,6 @@ } /** - * Access an option at the specified key. - * - * \param key the key - * \return the option - * \throw std::out_of_range if the key does not exist - */ - inline Option &operator[](const std::string &key) - { - return *find(key); - } - - /** - * Access an option at the specified key. - * - * \param key the key - * \return the option - * \throw std::out_of_range if the key does not exist - */ - inline const Option &operator[](const std::string &key) const - { - return *find(key); - } - - /** * Find an option by key and return an iterator. * * \param key the key @@ -371,104 +435,52 @@ } /** + * Access an option at the specified key. + * + * \param key the key + * \return the option + * \pre contains(key) must return true + */ + inline Option &operator[](const std::string &key) + { + assert(contains(key)); + + return *find(key); + } + + /** + * Overloaded function. + * + * \param key the key + * \return the option + * \pre contains(key) must return true + */ + inline const Option &operator[](const std::string &key) const + { + assert(contains(key)); + + return *find(key); + } + + /** * Inherited operators. */ using std::vector<Option>::operator[]; }; /** - * \class File - * \brief Source for reading .ini files. - */ -class File { -public: - /** - * Path to the file. - */ - std::string path; -}; - -/** - * \class Buffer - * \brief Source for reading ini from text. - * \note the include statement is not supported with buffers. - */ -class Buffer { -public: - /** - * The ini content. - */ - std::string text; -}; - -/** * \class Document - * \brief Ini config file loader + * \brief Ini document description. + * \see readFile + * \see readString */ class Document : public std::vector<Section> { -private: - std::string m_path; - public: /** - * Analyze a file and extract tokens. If the function succeeds, that does not mean the content is valid, - * it just means that there are no syntax error. - * - * For example, this class does not allow adding options under no sections and this function will not - * detect that issue. - * - * \param file the file to read - * \return the list of tokens - * \throws Error on errors - */ - static Tokens analyze(const File &file); - - /** - * Overloaded function for buffers. - * - * \param buffer the buffer to read - * \return the list of tokens - * \throws Error on errors - */ - static Tokens analyze(const Buffer &buffer); - - /** - * Show all tokens and their description. - * - * \param tokens the tokens - */ - static void dump(const Tokens &tokens); - - /** - * Construct a document from a file. - * - * \param file the file to read - * \throws Error on errors - */ - Document(const File &file); - - /** - * Overloaded constructor for buffers. - * - * \param buffer the buffer to read - * \throws Error on errors - */ - Document(const Buffer &buffer); - - /** - * Get the current document path, only useful when constructed from File source. - * - * \return the path - */ - inline const std::string &path() const noexcept - { - return m_path; - } - - /** * Check if a document has a specific section. * * \param key the key + * \return true if the document contains the section */ inline bool contains(const std::string &key) const noexcept { @@ -476,30 +488,6 @@ } /** - * Access a section at the specified key. - * - * \param key the key - * \return the section - * \throw std::out_of_range if the key does not exist - */ - inline Section &operator[](const std::string &key) - { - return *find(key); - } - - /** - * Access a section at the specified key. - * - * \param key the key - * \return the section - * \throw std::out_of_range if the key does not exist - */ - inline const Section &operator[](const std::string &key) const - { - return *find(key); - } - - /** * Find a section by key and return an iterator. * * \param key the key @@ -526,11 +514,99 @@ } /** + * Access a section at the specified key. + * + * \param key the key + * \return the section + * \pre contains(key) must return true + */ + inline Section &operator[](const std::string &key) + { + assert(contains(key)); + + return *find(key); + } + + /** + * Overloaded function. + * + * \param key the key + * \return the section + * \pre contains(key) must return true + */ + inline const Section &operator[](const std::string &key) const + { + assert(contains(key)); + + return *find(key); + } + + /** * Inherited operators. */ using std::vector<Section>::operator[]; }; +/** + * Analyse a stream and detect potential syntax errors. This does not parse the file like including other + * files in include statement. + * + * It does only analysis, for example if an option is defined under no section, this does not trigger an + * error while it's invalid. + * + * \param it the iterator + * \param end where to stop + * \return the list of tokens + * \throws Error on errors + */ +Tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end); + +/** + * Overloaded function for stream. + * + * \param stream the stream + * \return the list of tokens + * \throws Error on errors + */ +Tokens analyse(std::istream &stream); + +/** + * Parse the produced tokens. + * + * \param tokens the tokens + * \param path the parent path + * \return the document + * \throw Error on errors + */ +Document parse(const Tokens &tokens, const std::string &path = "."); + +/** + * Parse a file. + * + * \param filename the file name + * \return the document + * \throw Error on errors + */ +Document readFile(const std::string &filename); + +/** + * Parse a string. + * + * If the string contains include statements, they are relative to the current working directory. + * + * \param buffer the buffer + * \return the document + * \throw Error on errors + */ +Document readString(const std::string &buffer); + +/** + * Show all tokens and their description. + * + * \param tokens the tokens + */ +void dump(const Tokens &tokens); + } // !ini } // !irccd
--- a/lib/irccd/irccd.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/irccd.cpp Tue May 10 21:27:40 2016 +0200 @@ -625,7 +625,7 @@ auto tc = ptr.lock(); if (tc) { - m_lookupTransportClients.erase(tc->handle()); + m_transportClients.erase(std::find(m_transportClients.begin(), m_transportClients.end(), tc)); } }); } @@ -643,21 +643,21 @@ void Irccd::processTransportClients(fd_set &input, fd_set &output) { - for (auto &pair : m_lookupTransportClients) { - pair.second->sync(input, output); + for (auto &client : m_transportClients) { + client->sync(input, output); } } void Irccd::processTransportServers(fd_set &input) { - for (auto &pair : m_lookupTransportServers) { - if (!FD_ISSET(pair.second->handle(), &input)) { + for (auto &transport : m_transportServers) { + if (!FD_ISSET(transport->handle(), &input)) { continue; } log::debug("transport: new client connected"); - std::shared_ptr<TransportClient> client = pair.second->accept(); + std::shared_ptr<TransportClient> client = transport->accept(); std::weak_ptr<TransportClient> ptr(client); /* Send some information */ @@ -682,14 +682,14 @@ client->onDie.connect(std::bind(&Irccd::handleTransportDie, this, ptr)); /* Register it */ - m_lookupTransportClients.emplace(client->handle(), move(client)); + m_transportClients.push_back(std::move(client)); } } void Irccd::processServers(fd_set &input, fd_set &output) { - for (auto &pair : m_servers) { - pair.second->sync(input, output); + for (auto &server : m_servers) { + server->sync(input, output); } } @@ -736,7 +736,9 @@ void Irccd::addServer(shared_ptr<Server> server) noexcept { +#if 0 assert(m_servers.count(server->info().name) == 0); +#endif std::weak_ptr<Server> ptr(server); @@ -762,50 +764,54 @@ if (server) { log::info(fmt::format("server {}: removed", server->info().name)); - m_servers.erase(server->info().name); + m_servers.erase(std::find(m_servers.begin(), m_servers.end(), server)); } }); }); - m_servers.emplace(server->info().name, move(server)); + m_servers.push_back(std::move(server)); } std::shared_ptr<Server> Irccd::getServer(const std::string &name) const noexcept { - auto it = m_servers.find(name); + auto it = std::find_if(m_servers.begin(), m_servers.end(), [&] (const auto &server) { + return server->info().name == name; + }); if (it == m_servers.end()) { return nullptr; } - return it->second; + return *it; } std::shared_ptr<Server> Irccd::requireServer(const std::string &name) const { - auto it = m_servers.find(name); + auto server = getServer(name); - if (it == m_servers.end()) { - throw std::invalid_argument("server " + name + " not found"); + if (!server) { + throw std::invalid_argument("server {} not found"_format(name)); } - return it->second; + return server; } void Irccd::removeServer(const std::string &name) { - auto it = m_servers.find(name); + auto it = std::find_if(m_servers.begin(), m_servers.end(), [&] (const auto &server) { + return server->info().name == name; + }); if (it != m_servers.end()) { - it->second->disconnect(); + (*it)->disconnect(); m_servers.erase(it); } } void Irccd::clearServers() noexcept { - for (auto &pair : m_servers) { - pair.second->disconnect(); + for (auto &server : m_servers) { + server->disconnect(); } m_servers.clear(); @@ -813,14 +819,14 @@ void Irccd::addTransport(std::shared_ptr<TransportServer> ts) { - m_lookupTransportServers.emplace(ts->handle(), ts); + m_transportServers.push_back(std::move(ts)); } void Irccd::broadcast(std::string data) { - /* Asynchronous send */ - for (auto &pair : m_lookupTransportClients) { - pair.second->send(data); + // Asynchronous send. + for (auto &client : m_transportClients) { + client->send(data); } } @@ -828,24 +834,26 @@ std::shared_ptr<Plugin> Irccd::getPlugin(const std::string &name) const noexcept { - auto it = m_plugins.find(name); + auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) { + return plugin->name() == name; + }); if (it == m_plugins.end()) { return nullptr; } - return it->second; + return *it; } std::shared_ptr<Plugin> Irccd::requirePlugin(const std::string &name) const { - auto it = m_plugins.find(name); + auto plugin = getPlugin(name); - if (it == m_plugins.end()) { - throw std::out_of_range(std::string("plugin ") + name + " not found"); + if (!plugin) { + throw std::invalid_argument("plugin {} not found"_format(name)); } - return it->second; + return plugin; } void Irccd::addPlugin(std::shared_ptr<Plugin> plugin) @@ -861,15 +869,20 @@ /* Initial load now */ try { plugin->onLoad(); - m_plugins.insert({plugin->name(), plugin}); + m_plugins.push_back(std::move(plugin)); } catch (const std::exception &ex) { - log::warning(fmt::format("plugin {}: {}", plugin->name(), ex.what())); + log::warning("plugin {}: {}"_format(plugin->name(), ex.what())); } } void Irccd::loadPlugin(std::string name, const std::string &source, bool find) { - if (m_plugins.count(name) > 0) { + // TODO: change with Plugin::find + auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) { + return plugin->name() == name; + }); + + if (it != m_plugins.end()) { throw std::invalid_argument("plugin already loaded"); } @@ -891,7 +904,7 @@ log::info() << " from " << path << std::endl; try { - plugin = std::make_shared<Plugin>(name, path, m_pluginConf[name]); + plugin = std::make_shared<Plugin>(name, path /*, m_pluginConf[name] */); break; } catch (const std::exception &ex) { log::info(fmt::format(" error: {}", ex.what())); @@ -916,11 +929,13 @@ void Irccd::unloadPlugin(const std::string &name) { - auto plugin = getPlugin(name); + auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) { + return plugin->name() == name; + }); - if (plugin) { - plugin->onUnload(); - m_plugins.erase(name); + if (it != m_plugins.end()) { + (*it)->onUnload(); + m_plugins.erase(it); } } @@ -999,22 +1014,23 @@ FD_SET(m_socketServer.handle(), &setinput); /* 2. Add servers */ - for (auto &pair : m_servers) { - pair.second->update(); - pair.second->prepare(setinput, setoutput, max); + for (auto &server : m_servers) { + server->update(); + server->prepare(setinput, setoutput, max); } /* 3. Add transports clients */ - for (auto &pair : m_lookupTransportClients) { - set(setinput, pair.first); + for (auto &client : m_transportClients) { + set(setinput, client->handle()); - if (pair.second->hasOutput()) - set(setoutput, pair.first); + if (client->hasOutput()) { + set(setoutput, client->handle()); + } } /* 4. Add transport servers */ - for (auto &pair : m_lookupTransportServers) { - set(setinput, pair.first); + for (auto &transport : m_transportServers) { + set(setinput, transport->handle()); } /* 5. Do the selection */ @@ -1045,7 +1061,7 @@ * 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. */ - std::vector<Event> copy; + std::vector<std::function<void (Irccd &)>> copy; { std::lock_guard<mutex> lock(m_mutex);
--- a/lib/irccd/irccd.hpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/irccd.hpp Tue May 10 21:27:40 2016 +0200 @@ -24,6 +24,7 @@ * \brief Base class for irccd front end. */ +#include <algorithm> #include <atomic> #include <cassert> #include <condition_variable> @@ -52,50 +53,6 @@ class TransportCommand; /** - * Event to execute after the poll. - */ -using Event = std::function<void (Irccd &)>; - -/** - * List of events. - */ -using Events = std::vector<Event>; - -/** - * Map of identities. - */ -using Identities = std::unordered_map<std::string, ServerIdentity>; - -/** - * List of rules. - */ -using Rules = std::vector<Rule>; - -/** - * Map of servers. - */ -using Servers = std::unordered_map<std::string, std::shared_ptr<Server>>; - -/** - * Map of transport command handlers. - */ -using TransportCommands = std::unordered_map<std::string, std::unique_ptr<TransportCommand>>; - -#if defined(WITH_JS) - -/** - * Map of plugins. - */ -using Plugins = std::unordered_map<std::string, std::shared_ptr<Plugin>>; - -/** - * Map of plugin configurations. - */ -using PluginConfigs = std::unordered_map<std::string, PluginConfig>; - -#endif - -/** * \class Irccd * \brief Irccd main instance * @@ -111,40 +68,29 @@ */ class Irccd : public Application { private: - template <typename T> - using LookupTable = std::unordered_map<net::Handle, std::shared_ptr<T>>; - - /* Main loop */ + // Main loop stuff. std::atomic<bool> m_running{true}; + std::mutex m_mutex; + std::vector<std::function<void (Irccd &)>> m_events; - /* Mutex for post() */ - std::mutex m_mutex; - - /* IPC */ + // IPC. net::SocketTcp<net::address::Ip> m_socketServer; net::SocketTcp<net::address::Ip> m_socketClient; - /* Event loop */ - Events m_events; + // Servers. + std::vector<std::shared_ptr<Server>> m_servers; - /* Servers */ - Servers m_servers; - - /* Optional JavaScript plugins */ + // Optional plugins. #if defined(WITH_JS) - Plugins m_plugins; - PluginConfigs m_pluginConf; + std::vector<std::shared_ptr<Plugin>> m_plugins; #endif - /* Identities */ - Identities m_identities; + // Rules. + std::vector<Rule> m_rules; - /* Rules */ - Rules m_rules; - - /* Lookup tables */ - LookupTable<TransportClient> m_lookupTransportClients; - LookupTable<TransportServer> m_lookupTransportServers; + // Transports. + std::vector<std::shared_ptr<TransportClient>> m_transportClients; + std::vector<std::shared_ptr<TransportServer>> m_transportServers; /* * Server slots @@ -207,17 +153,6 @@ Irccd(); /** - * Load a configuration into irccd. Added as convenience to allow expressions like `irccd.load(Config{"foo"})`. - * - * \param config the configuration loader - */ - template <typename T> - inline void load(T &&config) - { - config.load(*this); - } - - /** * Add an event to the queue. This will immediately signals the event loop to interrupt itself to dispatch * the pending events. * @@ -227,37 +162,6 @@ void post(std::function<void (Irccd &)> ev) noexcept; /* - * Identity management - * ---------------------------------------------------------- - * - * Functions to get or add new identities. - */ - - /** - * Add an identity. - * - * \param identity the identity - * \note If the identity already exists, it is overriden - */ - inline void addIdentity(ServerIdentity identity) noexcept - { - m_identities.emplace(identity.name, std::move(identity)); - } - - /** - * Get an identity, if not found, the default one is used. - * - * \param name the identity name - * \return the identity or default one - */ - inline ServerIdentity findIdentity(const std::string &name) const noexcept - { - auto it = m_identities.find(name); - - return it == m_identities.end() ? ServerIdentity() : it->second; - } - - /* * Server management * ---------------------------------------------------------- * @@ -274,7 +178,9 @@ */ inline bool hasServer(const std::string &name) const noexcept { - return m_servers.count(name) > 0; + return std::count_if(m_servers.cbegin(), m_servers.end(), [&] (const auto &sv) { + return sv->info().name == name; + }) > 0; } /** @@ -303,11 +209,11 @@ std::shared_ptr<Server> requireServer(const std::string &name) const; /** - * Get the map of loaded servers. + * Get the list of servers * * \return the servers */ - inline const Servers &servers() const noexcept + inline const std::vector<std::shared_ptr<Server>> &servers() const noexcept { return m_servers; } @@ -365,7 +271,9 @@ */ inline bool hasPlugin(const std::string &name) const noexcept { - return m_plugins.count(name) > 0; + return std::count_if(m_plugins.cbegin(), m_plugins.cend(), [&] (const auto &plugin) { + return plugin->name() == name; + }) > 0; } /** @@ -386,17 +294,6 @@ std::shared_ptr<Plugin> requirePlugin(const std::string &name) const; /** - * Add plugin configuration for the specified plugin. - * - * \param name - * \param config - */ - inline void addPluginConfig(std::string name, PluginConfig config) - { - m_pluginConf.emplace(std::move(name), std::move(config)); - } - - /** * Add a loaded plugin. * * Plugins signals will be connected to the irccd main loop. The onLoad function will also be called and the @@ -437,7 +334,7 @@ * * \return the map of plugins */ - inline const Plugins &plugins() const noexcept + inline const std::vector<std::shared_ptr<Plugin>> &plugins() const noexcept { return m_plugins; }
--- a/lib/irccd/irccdctl.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/irccdctl.cpp Tue May 10 21:27:40 2016 +0200 @@ -250,7 +250,7 @@ void Irccdctl::read(const std::string &path, const parser::Result &options) { - ini::Document doc(ini::File{path}); + ini::Document doc = ini::readFile(path); ini::Document::const_iterator it = doc.find("connect"); /* Do not try to read [connect] if specified at command line */
--- a/lib/irccd/js-plugin.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/js-plugin.cpp Tue May 10 21:27:40 2016 +0200 @@ -103,8 +103,8 @@ duk::push(ctx, duk::Array{}); int i = 0; - for (const auto &pair : duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->plugins()) { - duk::putProperty(ctx, -1, i++, pair.first); + for (const auto &plugin : duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->plugins()) { + duk::putProperty(ctx, -1, i++, plugin->name()); } return 1;
--- a/lib/irccd/js-server.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/js-server.cpp Tue May 10 21:27:40 2016 +0200 @@ -468,8 +468,8 @@ { duk::push(ctx, duk::Object{}); - for (const auto &pair : duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->servers()) { - duk::putProperty(ctx, -1, pair.first, duk::Shared<Server>{pair.second}); + for (const auto &server : duk::getGlobal<duk::RawPointer<Irccd>>(ctx, "\xff""\xff""irccd")->servers()) { + duk::putProperty(ctx, -1, server->info().name, duk::Shared<Server>{server}); } return 1;
--- a/lib/irccd/plugin.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/plugin.cpp Tue May 10 21:27:40 2016 +0200 @@ -18,6 +18,8 @@ #include <stdexcept> +#include <format.h> + #include "sysconfig.hpp" #if defined(HAVE_STAT) @@ -46,9 +48,11 @@ using namespace std; +using namespace fmt::literals; + namespace irccd { -void Plugin::call(const string &name, unsigned nargs) +void Plugin::call(const std::string &name, unsigned nargs) { duk::getGlobal<void>(m_context, name); @@ -148,6 +152,29 @@ duk::pop(m_context, 2); } +std::shared_ptr<Plugin> Plugin::find(const std::string &name, const PluginConfig &config) +{ + log::info("plugin {}: searching {}.js"_format(name, name)); + + for (const auto &path : path::list(path::PathPlugins)) { + std::string fullpath = path + name + ".js"; + + if (!fs::isReadable(fullpath)) { + continue; + } + + log::info("plugin {}: trying {}"_format(name, fullpath)); + + try { + return std::make_shared<Plugin>(name, fullpath, config); + } catch (const std::exception &ex) { + throw std::runtime_error("{}: {}"_format(fullpath, ex.what())); + } + } + + throw std::runtime_error("no suitable plugin found"); +} + Plugin::Plugin(std::string name, std::string path, const PluginConfig &config) : m_name(std::move(name)) , m_path(std::move(path))
--- a/lib/irccd/plugin.hpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/plugin.hpp Tue May 10 21:27:40 2016 +0200 @@ -107,6 +107,14 @@ public: /** + * Find plugin in standard paths. + * + * \param name the plugin name + * \param config the optional configuration + */ + static std::shared_ptr<Plugin> find(const std::string &name, const PluginConfig &config = PluginConfig()); + + /** * Constructor. * * \param name the plugin id
--- a/lib/irccd/server-event.cpp Wed May 04 13:38:02 2016 +0200 +++ b/lib/irccd/server-event.cpp Tue May 10 21:27:40 2016 +0200 @@ -37,9 +37,9 @@ void ServerEvent::operator()(Irccd &irccd) const { - for (auto &pair : irccd.plugins()) { - auto name = m_plugin_function_name(*pair.second); - auto allowed = Rule::solve(irccd.rules(), m_server, m_target, m_origin, pair.first, name); + for (auto &plugin : irccd.plugins()) { + auto eventname = m_plugin_function_name(*plugin); + auto allowed = Rule::solve(irccd.rules(), m_server, m_target, m_origin, plugin->name(), eventname); if (!allowed) { log::debug() << "rule: event skipped on match" << std::endl; @@ -49,9 +49,9 @@ } try { - m_plugin_exec(*pair.second); + m_plugin_exec(*plugin); } catch (const duk::ErrorInfo &info) { - log::warning() << "plugin " << pair.second->name() << ": error: " << info.what() << std::endl; + log::warning() << "plugin " << plugin->name() << ": error: " << info.what() << std::endl; if (!info.fileName.empty()) { log::warning() << " " << info.fileName << ":" << info.lineNumber << std::endl;