Mercurial > irccd
changeset 520:defacef00c82
Irccd: load plugin configuration dynamically, closes #724
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 31 Oct 2017 10:48:06 +0100 |
parents | 63faa8087c46 |
children | e03521cf207b |
files | libcommon/irccd/ini.cpp libcommon/irccd/ini.hpp libirccd/irccd/config.cpp libirccd/irccd/config.hpp libirccd/irccd/service.cpp libirccd/irccd/service.hpp |
diffstat | 6 files changed, 185 insertions(+), 356 deletions(-) [+] |
line wrap: on
line diff
--- a/libcommon/irccd/ini.cpp Thu Oct 26 20:55:20 2017 +0200 +++ b/libcommon/irccd/ini.cpp Tue Oct 31 10:48:06 2017 +0100 @@ -128,23 +128,19 @@ // Read section name. ++ it; while (it != end && *it != ']') { - if (*it == '\n') { + if (*it == '\n') throw exception(line, column, "section not terminated, missing ']'"); - } - if (is_reserved(*it)) { + if (is_reserved(*it)) throw exception(line, column, "section name expected after '[', got '" + std::string(1, *it) + "'"); - } ++ column; value += *it++; } - if (it == end) { + if (it == end) throw exception(line, column, "section name expected after '[', got <EOF>"); - } - if (value.empty()) { + if (value.empty()) throw exception(line, column, "empty section name"); - } // Remove ']'. ++ it; @@ -172,9 +168,8 @@ value += *it++; } - if (it == end) { + if (it == end) throw exception(line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>"); - } // Remove quote. ++ it; @@ -211,9 +206,8 @@ include += *it++; } - if (include != "include") { + if (include != "include") throw exception(line, column, "expected include after '@' token"); - } list.push_back({ token::include, line, save }); } @@ -235,9 +229,8 @@ switch (it->type()) { case token::comma: // Previous must be a word. - if (it[-1].type() != token::word && it[-1].type() != token::quoted_word) { + if (it[-1].type() != token::word && it[-1].type() != token::quoted_word) throw exception(it->line(), it->column(), "unexpected comma after '" + it[-1].value() + "'"); - } ++ it; break; @@ -251,9 +244,8 @@ } } - if (it == end) { + if (it == end) throw exception(save->line(), save->column(), "unterminated list construct"); - } // Remove ). ++ it; @@ -265,20 +257,17 @@ token_iterator save(it); // No '=' or something else? - if (++it == end) { + if (++it == end) throw exception(save->line(), save->column(), "expected '=' assignment, got <EOF>"); - } - if (it->type() != token::assign) { + if (it->type() != token::assign) throw exception(it->line(), it->column(), "expected '=' assignment, got " + it->value()); - } // Empty options are allowed so just test for words. if (++it != end) { - if (it->type() == token::word || it->type() == token::quoted_word) { + if (it->type() == token::word || it->type() == token::quoted_word) parse_option_value_simple(option, it); - } else if (it->type() == token::list_begin) { + else if (it->type() == token::list_begin) parse_option_value_list(option, it, end); - } } sc.push_back(std::move(option)); @@ -288,12 +277,10 @@ { token_iterator save(it); - if (++it == end) { + if (++it == end) throw exception(save->line(), save->column(), "expected file name after '@include' statement, got <EOF>"); - } - if (it->type() != token::word && it->type() != token::quoted_word) { + if (it->type() != token::word && it->type() != token::quoted_word) throw exception(it->line(), it->column(), "expected file name after '@include' statement, got " + it->value()); - } std::string value = (it++)->value(); std::string file; @@ -304,13 +291,11 @@ #else file = path + "/" + value; #endif - } else { + } else file = value; - } - for (const auto& sc : read_file(file)) { + for (const auto& sc : read_file(file)) doc.push_back(sc); - } } void parse_section(document& doc, token_iterator& it, token_iterator end) @@ -322,9 +307,8 @@ // Read until next section. while (it != end && it->type() != token::section) { - if (it->type() != token::word) { + if (it->type() != token::word) throw exception(it->line(), it->column(), "unexpected token '" + it->value() + "' in section definition"); - } parse_option(sc, it, end); } @@ -341,25 +325,24 @@ int column = 0; while (it != end) { - if (*it == '\n') { + if (*it == '\n') analyse_line(line, column, it); - } else if (*it == '#') { + else if (*it == '#') analyse_comment(column, it, end); - } else if (*it == '[') { + else if (*it == '[') analyse_section(list, line, column, it, end); - } else if (*it == '=') { + else if (*it == '=') analyse_assign(list, line, column, it); - } else if (is_space(*it)) { + else if (is_space(*it)) analyse_spaces(column, it, end); - } else if (*it == '@') { + else if (*it == '@') analyse_include(list, line, column, it, end); - } else if (is_quote(*it)) { + else if (is_quote(*it)) analyse_quoted_word(list, line, column, it, end); - } else if (is_list(*it)) { + else if (is_list(*it)) analyse_list(list, line, column, it); - } else { + else analyse_word(list, line, column, it, end); - } } return list; @@ -398,17 +381,15 @@ auto parent = filename; auto pos = parent.find_last_of("/\\"); - if (pos != std::string::npos) { + if (pos != std::string::npos) parent.erase(pos); - } else { + else parent = "."; - } std::ifstream input(filename); - if (!input) { + if (!input) throw exception(0, 0, std::strerror(errno)); - } return parse(analyse(input), parent); }
--- a/libcommon/irccd/ini.hpp Thu Oct 26 20:55:20 2017 +0200 +++ b/libcommon/irccd/ini.hpp Tue Oct 31 10:48:06 2017 +0100 @@ -16,20 +16,26 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef INI_HPP -#define INI_HPP +#ifndef IRCCD_INI_HPP +#define IRCCD_INI_HPP /** * \file ini.hpp * \brief Extended .ini file parser. * \author David Demelier <markand@malikania.fr> - * \version 1.0.0 + * \version 2.0.0 */ /** * \page Ini Ini * \brief Extended .ini file parser. * + * ## Export macros + * + * You must define `INI_DLL` globally and `INI_BUILDING_DLL` when compiling the + * library if you want a DLL, alternatively you can provide your own + * `` macro instead. + * * - \subpage ini-syntax */ @@ -42,10 +48,10 @@ * - 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 + * - lists can not include trailing commas, + * - include statements must always live at the beginning of files * (in no sections), - * - comments starts with # until the end of line, + * - comments start with # until the end of line, * - options with spaces **must** use quotes. * * # Basic file @@ -70,7 +76,7 @@ * value = "2" * ```` * - * The ini::document object will contains two ini::Section. + * The ini::document object will contains two ini::section. * * # Lists * @@ -80,7 +86,7 @@ * [section] * names = ( "x1", "x2" ) * - * # This is also allowed + * # This is also allowed. * biglist = ( * "abc", * "def" @@ -106,32 +112,6 @@ * ```` */ -/** - * \cond INI_HIDDEN_SYMBOLS - */ - -#if !defined(IRCCD_EXPORT) -# if defined(INI_DLL) -# if defined(_WIN32) -# if defined(INI_BUILDING_DLL) -# define IRCCD_EXPORT __declspec(dllexport) -# else -# define IRCCD_EXPORT __declspec(dllimport) -# endif -# else -# define IRCCD_EXPORT -# endif -# else -# define IRCCD_EXPORT -# endif -#endif - -/** - * \endcond - */ - -#include <sysconfig.hpp> - #include <algorithm> #include <cassert> #include <exception> @@ -153,9 +133,9 @@ */ class exception : public std::exception { private: - int m_line; //!< line number - int m_column; //!< line column - std::string m_message; //!< exception message + int line_; + int column_; + std::string message_; public: /** @@ -166,9 +146,9 @@ * \param msg the message */ inline exception(int line, int column, std::string msg) noexcept - : m_line(line) - , m_column(column) - , m_message(std::move(msg)) + : line_(line) + , column_(column) + , message_(std::move(msg)) { } @@ -179,7 +159,7 @@ */ inline int line() const noexcept { - return m_line; + return line_; } /** @@ -189,7 +169,7 @@ */ inline int column() const noexcept { - return m_column; + return column_; } /** @@ -199,7 +179,7 @@ */ const char* what() const noexcept override { - return m_message.c_str(); + return message_.c_str(); } }; @@ -208,7 +188,7 @@ * * This class can be used when you want to parse a .ini file yourself. * - * \see analyze + * \see analyse */ class token { public: @@ -227,10 +207,10 @@ }; private: - type m_type; - int m_line; - int m_column; - std::string m_value; + type type_; + int line_; + int column_; + std::string value_; public: /** @@ -242,30 +222,30 @@ * \param value the value */ token(type type, int line, int column, std::string value = "") noexcept - : m_type(type) - , m_line(line) - , m_column(column) + : type_(type) + , line_(line) + , column_(column) { switch (type) { case include: - m_value = "@include"; + value_ = "@include"; break; case section: case word: case quoted_word: - m_value = value; + value_ = value; break; case assign: - m_value = "="; + value_ = "="; break; case list_begin: - m_value = "("; + value_ = "("; break; case list_end: - m_value = ")"; + value_ = ")"; break; case comma: - m_value = ","; + value_ = ","; break; default: break; @@ -279,7 +259,7 @@ */ inline type type() const noexcept { - return m_type; + return type_; } /** @@ -289,7 +269,7 @@ */ inline int line() const noexcept { - return m_line; + return line_; } /** @@ -299,7 +279,7 @@ */ inline int column() const noexcept { - return m_column; + return column_; } /** @@ -310,7 +290,7 @@ */ inline const std::string& value() const noexcept { - return m_value; + return value_; } }; @@ -324,7 +304,7 @@ */ class option : public std::vector<std::string> { private: - std::string m_key; + std::string key_; public: /** @@ -335,9 +315,9 @@ */ inline option(std::string key) noexcept : std::vector<std::string>() - , m_key(std::move(key)) + , key_(std::move(key)) { - assert(!m_key.empty()); + assert(!key_.empty()); } /** @@ -348,9 +328,9 @@ * \param value the value */ inline option(std::string key, std::string value) noexcept - : m_key(std::move(key)) + : key_(std::move(key)) { - assert(!m_key.empty()); + assert(!key_.empty()); push_back(std::move(value)); } @@ -364,9 +344,9 @@ */ inline option(std::string key, std::vector<std::string> values) noexcept : std::vector<std::string>(std::move(values)) - , m_key(std::move(key)) + , key_(std::move(key)) { - assert(!m_key.empty()); + assert(!key_.empty()); } /** @@ -376,7 +356,7 @@ */ inline const std::string& key() const noexcept { - return m_key; + return key_; } /** @@ -397,7 +377,7 @@ */ class section : public std::vector<option> { private: - std::string m_key; + std::string key_; public: /** @@ -407,9 +387,9 @@ * \param key the key */ inline section(std::string key) noexcept - : m_key(std::move(key)) + : key_(std::move(key)) { - assert(!m_key.empty()); + assert(!key_.empty()); } /** @@ -419,7 +399,7 @@ */ inline const std::string& key() const noexcept { - return m_key; + return key_; } /** @@ -434,6 +414,22 @@ } /** + * Find an option or return an empty one if not found. + * + * \param key the key + * \return the option or empty one if not found + */ + inline option get(const std::string& key) const noexcept + { + auto it = find(key); + + if (it == end()) + return option(key); + + return *it; + } + + /** * Find an option by key and return an iterator. * * \param key the key @@ -495,8 +491,8 @@ /** * \brief Ini document description. - * \see readFile - * \see readString + * \see read_file + * \see read_string */ class document : public std::vector<section> { public: @@ -512,6 +508,22 @@ } /** + * Find a section or return an empty one if not found. + * + * \param key the key + * \return the section or empty one if not found + */ + inline section get(const std::string& key) const noexcept + { + auto it = find(key); + + if (it == end()) + return section(key); + + return *it; + } + + /** * Find a section by key and return an iterator. * * \param key the key @@ -583,7 +595,7 @@ * \return the list of tokens * \throws exception on errors */ -IRCCD_EXPORT tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end); + tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end); /** * Overloaded function for stream. @@ -592,7 +604,7 @@ * \return the list of tokens * \throws exception on errors */ -IRCCD_EXPORT tokens analyse(std::istream& stream); + tokens analyse(std::istream& stream); /** * Parse the produced tokens. @@ -602,7 +614,7 @@ * \return the document * \throw exception on errors */ -IRCCD_EXPORT document parse(const tokens& tokens, const std::string& path = "."); + document parse(const tokens& tokens, const std::string& path = "."); /** * Parse a file. @@ -611,7 +623,7 @@ * \return the document * \throw exception on errors */ -IRCCD_EXPORT document read_file(const std::string& filename); + document read_file(const std::string& filename); /** * Parse a string. @@ -623,17 +635,17 @@ * \return the document * \throw exception on exceptions */ -IRCCD_EXPORT document read_string(const std::string& buffer); + document read_string(const std::string& buffer); /** * Show all tokens and their description. * * \param tokens the tokens */ -IRCCD_EXPORT void dump(const tokens& tokens); + void dump(const tokens& tokens); } // !ini } // !irccd -#endif // !INI_HPP +#endif // !IRCCD_INI_HPP
--- a/libirccd/irccd/config.cpp Thu Oct 26 20:55:20 2017 +0200 +++ b/libirccd/irccd/config.cpp Tue Oct 31 10:48:06 2017 +0100 @@ -87,26 +87,6 @@ return ito->value(); } -plugin_config load_plugin_config(const ini::section& sc) -{ - plugin_config config; - - for (const auto& option : sc) - config.emplace(option.key(), option.value()); - - return config; -} - -plugin_paths read_paths(const ini::section& sc) -{ - plugin_paths paths; - - for (const auto& opt : sc) - paths.emplace(opt.key(), opt.value()); - - return paths; -} - std::unique_ptr<log::logger> load_log_file(const ini::section& sc) { /* @@ -422,51 +402,6 @@ server.set_ctcp_version(it->value()); } -plugin_config config::find_plugin_config(const std::string& name) const -{ - assert(util::is_identifier(name)); - - std::string fullname = std::string("plugin.") + name; - - for (const auto& section : document_) { - if (section.key() != fullname) - continue; - - return load_plugin_config(section); - } - - return plugin_config(); -} - -plugin_formats config::find_plugin_formats(const std::string& name) const -{ - assert(util::is_identifier(name)); - - auto section = document_.find(std::string("format.") + name); - - if (section == document_.end()) - return plugin_formats(); - - plugin_formats formats; - - for (const auto& opt : *section) - formats.emplace(opt.key(), opt.value()); - - return formats; -} - -plugin_paths config::find_plugin_paths(const std::string& name) const -{ - assert(util::is_identifier(name)); - - auto section = document_.find(std::string("paths.") + name); - - if (section == document_.end()) - return plugin_paths(); - - return read_paths(*section); -} - bool config::is_verbose() const noexcept { return util::is_boolean(get(document_, "logs", "verbose")); @@ -577,36 +512,18 @@ return servers; } -plugin_paths config::load_paths() const -{ - auto section = document_.find("paths"); - - if (section == document_.end()) - return {}; - - return read_paths(*section); -} - void config::load_plugins(irccd& irccd) const { auto it = document_.find("plugins"); - irccd.plugins().set_paths(load_paths()); - - if (it != document_.end()) { - for (const auto& option : *it) { - if (!util::is_identifier(option.key())) - continue; + if (it == document_.end()) + return; - auto paths = find_plugin_paths(option.key()); - - if (!paths.empty()) - irccd.plugins().set_paths(std::move(paths)); + for (const auto& option : *it) { + if (!util::is_identifier(option.key())) + continue; - irccd.plugins().set_config(option.key(), find_plugin_config(option.key())); - irccd.plugins().set_formats(option.key(), find_plugin_formats(option.key())); - irccd.plugins().load(option.key(), option.value()); - } + irccd.plugins().load(option.key(), option.value()); } }
--- a/libirccd/irccd/config.hpp Thu Oct 26 20:55:20 2017 +0200 +++ b/libirccd/irccd/config.hpp Tue Oct 31 10:48:06 2017 +0100 @@ -68,6 +68,16 @@ } /** + * Get the underlying document. + * + * \return the document + */ + inline const ini::document& doc() const noexcept + { + return document_; + } + + /** * Get the path to the configuration file. * * \return the path @@ -87,30 +97,6 @@ */ void load_server_identity(server& server, 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 - */ - plugin_config find_plugin_config(const std::string& name) const; - - /** - * Find plugin formats if defined. - * - * \pre util::isValidIdentifier(name) - * \return the formats or empty one if not found - */ - plugin_formats find_plugin_formats(const std::string& name) const; - - /** - * Find plugin paths if defined. - * - * \pre util::isValidIdentifier(name) - * \param name the plugin name - */ - plugin_paths find_plugin_paths(const std::string& name) const; - /** * Get the path to the pidfile. * @@ -178,13 +164,6 @@ std::vector<std::shared_ptr<server>> load_servers() const; /** - * Load default paths for plugins. - * - * \return the map of paths - */ - plugin_paths load_paths() const; - - /** * Get the list of defined plugins. * * \param irccd the irccd instance
--- a/libirccd/irccd/service.cpp Thu Oct 26 20:55:20 2017 +0200 +++ b/libirccd/irccd/service.cpp Tue Oct 31 10:48:06 2017 +0100 @@ -21,6 +21,7 @@ #include <functional> #include <stdexcept> +#include "config.hpp" #include "irccd.hpp" #include "logger.hpp" #include "service.hpp" @@ -124,9 +125,6 @@ plugin_service::plugin_service(irccd& irccd) noexcept : irccd_(irccd) { - default_paths_.emplace("cache", sys::cachedir()); - default_paths_.emplace("data", sys::datadir()); - default_paths_.emplace("config", sys::sysconfigdir()); } plugin_service::~plugin_service() @@ -174,68 +172,55 @@ loaders_.push_back(std::move(loader)); } -void plugin_service::set_config(const std::string& name, plugin_config config) +namespace { + +template <typename Map> +Map to_map(const config& conf, const std::string& section) { - config_.emplace(name, std::move(config)); + Map ret; + + for (const auto& opt : conf.doc().get(section)) + ret.emplace(opt.key(), opt.value()); + + return ret; } -plugin_config plugin_service::config(const std::string& name) const -{ - auto it = config_.find(name); +} // !namespace - if (it != config_.end()) - return it->second; - - return plugin_config(); +plugin_config plugin_service::config(const std::string& id) +{ + return to_map<plugin_config>(irccd_.config(), util::sprintf("plugin.%s", id)); } -void plugin_service::set_formats(const std::string& name, plugin_formats formats) -{ - formats_.emplace(name, std::move(formats)); -} - -plugin_formats plugin_service::formats(const std::string& name) const +plugin_formats plugin_service::formats(const std::string& id) { - auto it = formats_.find(name); - - if (it != formats_.end()) - return it->second; - - return plugin_formats(); + return to_map<plugin_formats>(irccd_.config(), util::sprintf("format.%s", id)); } -const plugin_paths& plugin_service::paths() const noexcept -{ - return default_paths_; -} - -plugin_paths plugin_service::paths(const std::string& name) const +plugin_paths plugin_service::paths(const std::string& id) { - auto result = default_paths_; - auto overriden = paths_.find(name); + class config cfg(irccd_.config()); - // For all default paths, append the plugin name. - for (auto& pair : result) - pair.second += "/plugin/"s + name; + auto defaults = to_map<plugin_paths>(cfg, "paths"); + auto paths = to_map<plugin_paths>(cfg, util::sprintf("paths.%s", id)); - // Now, mere overriden paths. - if (overriden != paths_.end()) - for (const auto& pair : overriden->second) - result[pair.first] = pair.second; - - return result; -} + // Fill defaults paths. + if (!defaults.count("cache")) + defaults.emplace("cache", sys::cachedir() + "/plugin/" + id); + if (!defaults.count("data")) + paths.emplace("data", sys::datadir() + "/plugin/" + id); + if (!defaults.count("config")) + paths.emplace("config", sys::sysconfigdir() + "/plugin/" + id); -void plugin_service::set_paths(plugin_paths paths) -{ - // If the paths is empty or not complete, do not erase default items. - for (const auto& pair : paths) - default_paths_[pair.first] = pair.second; -} + // Now fill missing fields. + if (!paths.count("cache")) + paths.emplace("cache", defaults["cache"]); + if (!paths.count("data")) + paths.emplace("data", defaults["data"]); + if (!paths.count("config")) + paths.emplace("config", defaults["config"]); -void plugin_service::set_paths(const std::string& name, plugin_paths paths) -{ - paths_.emplace(name, std::move(paths)); + return paths; } std::shared_ptr<plugin> plugin_service::open(const std::string& id, @@ -277,8 +262,8 @@ plugin = open(name, std::move(path)); if (plugin) { - plugin->set_config(config_[name]); - plugin->set_formats(formats_[name]); + plugin->set_config(config(name)); + plugin->set_formats(formats(name)); plugin->set_paths(paths(name)); plugin->on_load(irccd_);
--- a/libirccd/irccd/service.hpp Thu Oct 26 20:55:20 2017 +0200 +++ b/libirccd/irccd/service.hpp Tue Oct 31 10:48:06 2017 +0100 @@ -138,12 +138,8 @@ class plugin_service { private: irccd& irccd_; - plugin_paths default_paths_; std::vector<std::shared_ptr<plugin>> plugins_; std::vector<std::unique_ptr<plugin_loader>> loaders_; - std::unordered_map<std::string, plugin_config> config_; - std::unordered_map<std::string, plugin_formats> formats_; - std::unordered_map<std::string, plugin_paths> paths_; public: /** @@ -210,68 +206,27 @@ void add_loader(std::unique_ptr<plugin_loader> loader); /** - * Configure a plugin. + * Get the configuration for the specified plugin. * - * If the plugin is already loaded, its configuration is updated. - * - * \param name the plugin name - * \param config the new configuration + * \return the configuration */ - void set_config(const std::string& name, plugin_config config); - - /** - * Get a configuration for a plugin. - * - * \param name the plugin name - * \return the configuration or default one if not found - */ - plugin_config config(const std::string& name) const; + plugin_config config(const std::string& id); /** - * Add formatting for a plugin. + * Get the formats for the specified plugin. * - * \param name the plugin name - * \param formats the formats - */ - void set_formats(const std::string& name, plugin_formats formats); - - /** - * Get formats for a plugin. - * - * \param name the plugin name * \return the formats */ - plugin_formats formats(const std::string& name) const; - - /** - * Get the default paths for plugins. - * - * \return the paths - */ - const plugin_paths& paths() const noexcept; + plugin_formats formats(const std::string& id); /** * Get the paths for the specified plugin. * - * \param name the plugin + * If none is defined, return the default ones. + * * \return the paths */ - plugin_paths paths(const std::string& name) const; - - /** - * Set default paths. - * - * \param paths the default paths (for all plugins) - */ - void set_paths(plugin_paths paths); - - /** - * Override paths for the specified plugin. - * - * \param name the plugin name - * \param paths the paths - */ - void set_paths(const std::string& name, plugin_paths paths); + plugin_paths paths(const std::string& id); /** * Generic function for opening the plugin at the given path.