# HG changeset patch # User David Demelier # Date 1477214726 -7200 # Node ID 8457f5bd2036158f89f771e43bdc428c2097e9a8 # Parent 249ccc1e484097e9a18bf5960f80485aadd61aeb Irccd: add missing bits for tests diff -r 249ccc1e4840 -r 8457f5bd2036 libirccd/CMakeLists.txt --- a/libirccd/CMakeLists.txt Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/CMakeLists.txt Sun Oct 23 11:25:26 2016 +0200 @@ -67,7 +67,6 @@ ${libirccd_SOURCE_DIR}/irccd/cmd-server-part.cpp ${libirccd_SOURCE_DIR}/irccd/cmd-server-reconnect.cpp ${libirccd_SOURCE_DIR}/irccd/cmd-server-topic.cpp - ${libirccd_SOURCE_DIR}/irccd/command.cpp ${libirccd_SOURCE_DIR}/irccd/config.cpp ${libirccd_SOURCE_DIR}/irccd/irccd.cpp ${libirccd_SOURCE_DIR}/irccd/plugin-dynlib.cpp diff -r 249ccc1e4840 -r 8457f5bd2036 libirccd/irccd/command.cpp --- a/libirccd/irccd/command.cpp Sun Oct 23 11:20:29 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,272 +0,0 @@ -/* - * command.cpp -- remote command - * - * Copyright (c) 2013-2016 David Demelier - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include - -#include - -#include "command.hpp" -#include "logger.hpp" -#include "system.hpp" - -using namespace std::string_literals; - -using namespace fmt::literals; - -using json = nlohmann::json; - -namespace irccd { - -namespace { - -/* - * typeName - * ------------------------------------------------------------------ - * - * Convert a JSON value type to string for convenience. - */ -std::string typeName(nlohmann::json::value_t type) noexcept -{ - switch (type) { - case nlohmann::json::value_t::array: - return "array"; - case nlohmann::json::value_t::boolean: - return "bool"; - case nlohmann::json::value_t::number_float: - return "float"; - case nlohmann::json::value_t::number_integer: - return "integer"; - case nlohmann::json::value_t::number_unsigned: - return "unsigned"; - case nlohmann::json::value_t::null: - return "null"; - case nlohmann::json::value_t::object: - return "object"; - case nlohmann::json::value_t::string: - return "string"; - default: - return ""; - } -} - -/* - * typeNameList - * ------------------------------------------------------------------ - * - * Construct a list of names to send a convenient error message if properties - * are invalid, example: string, int or bool expected. - */ - -std::string typeNameList(const std::vector &types) -{ - std::ostringstream oss; - - if (types.size() == 1) - return typeName(types[0]); - - for (std::size_t i = 0; i < types.size(); ++i) { - oss << typeName(types[i]); - - if (i == types.size() - 2) - oss << " or "; - else if (i < types.size() - 1) - oss << ", "; - } - - return oss.str(); -} - -} // !namespace - -/* - * JSON errors - * ------------------------------------------------------------------ - */ - -MissingPropertyError::MissingPropertyError(std::string name, std::vector types) - : m_name(std::move(name)) - , m_types(std::move(types)) -{ - m_message = "missing '" + m_name + "' property (" + typeNameList(m_types) + " expected)"; -} - -InvalidPropertyError::InvalidPropertyError(std::string name, nlohmann::json::value_t expected, nlohmann::json::value_t result) - : m_name(std::move(name)) - , m_expected(expected) - , m_result(result) -{ - m_message += "invalid '" + m_name + "' property "; - m_message += "(" + typeName(expected) + " expected, "; - m_message += "got " + typeName(result) + ")"; -} - -PropertyRangeError::PropertyRangeError(std::string name, std::uint64_t min, std::uint64_t max, std::uint64_t value) - : m_name(std::move(name)) - , m_min(min) - , m_max(max) - , m_value(value) -{ - assert(value < min || value > max); - - m_message += "property '" + m_name + "' is out of range "; - m_message += std::to_string(min) + ".." + std::to_string(max) + ", got " + std::to_string(value); -} - -PropertyError::PropertyError(std::string name, std::string message) - : m_name(std::move(name)) -{ - m_message += "property '" + m_name + "': " + message; -} - -/* - * Command implementation - * ------------------------------------------------------------------ - */ - -std::string Command::usage() const -{ - std::ostringstream oss; - - oss << m_name << " "; - - // Options. - auto optlist = options(); - - if (optlist.size() > 0) { - for (const auto &opt : optlist) { - oss << "["; - - /* - * Long options are too big so only show them in the help - * command usage or only if no short option is available. - */ - if (opt.simpleKey().size() > 0) - oss << "-" << opt.simpleKey(); - else if (opt.longKey().size() > 0) - oss << " --" << opt.longKey(); - - oss << (opt.arg().empty() ? "" : " ") << opt.arg() << "] "; - } - } - - // Arguments. - auto argslist = args(); - - if (argslist.size() > 0) { - for (const auto &arg : argslist) - oss << (arg.required() ? "" : "[") - << arg.name() - << (arg.required() ? "" : "]") << " "; - } - - return oss.str(); -} - -std::string Command::help() const -{ - std::ostringstream oss; - - oss << "usage: " << sys::programName() << " " << m_name; - - // Options summary. - if (options().size() > 0) - oss << " [options...]"; - - // Arguments summary. - if (args().size() > 0) { - oss << " "; - - for (const auto &arg : args()) - oss << (arg.required() ? "" : "[") << arg.name() << (arg.required() ? "" : "]") << " "; - } - - // Description. - oss << "\n\n" << m_description << "\n\n"; - - // Options. - if (options().size() > 0) { - oss << "Options:\n"; - - for (const auto &opt : options()) { - std::ostringstream optoss; - - // Construct the line for the option in a single string to pad it correctly. - optoss << " "; - optoss << (!opt.simpleKey().empty() ? ("-"s + opt.simpleKey() + " ") : " "); - optoss << (!opt.longKey().empty() ? ("--"s + opt.longKey() + " "s) : ""); - optoss << opt.arg(); - - // Add it padded with spaces. - oss << std::left << std::setw(28) << optoss.str(); - oss << opt.description() << "\n"; - } - } - - return oss.str(); -} - -unsigned Command::min() const noexcept -{ - auto list = args(); - - return std::accumulate(list.begin(), list.end(), 0U, [] (unsigned i, const auto &arg) noexcept -> unsigned { - return i + (arg.required() ? 1 : 0); - }); -} - -unsigned Command::max() const noexcept -{ - return (unsigned)args().size(); -} - -nlohmann::json Command::request(Irccdctl &, const CommandRequest &) const -{ - return nlohmann::json::object({}); -} - -nlohmann::json Command::exec(Irccd &, const nlohmann::json &request) const -{ - // Verify that requested properties are present in the request. - for (const auto &prop : properties()) { - auto it = request.find(prop.name()); - - if (it == request.end()) - throw std::invalid_argument("missing '{}' property"_format(prop.name())); - - if (std::find(prop.types().begin(), prop.types().end(), it->type()) == prop.types().end()) { - auto expected = typeNameList(prop.types()); - auto got = typeName(it->type()); - - throw std::invalid_argument("invalid '{}' property ({} expected, got {})"_format(prop.name(), expected, got)); - } - } - - return nlohmann::json::object({}); -} - -void Command::result(Irccdctl &, const nlohmann::json &response) const -{ - auto it = response.find("error"); - - if (it != response.end() && it->is_string()) - log::warning() << "irccdctl: " << it->dump() << std::endl; -} - -} // !irccd diff -r 249ccc1e4840 -r 8457f5bd2036 libirccd/irccd/command.hpp --- a/libirccd/irccd/command.hpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/command.hpp Sun Oct 23 11:25:26 2016 +0200 @@ -35,336 +35,26 @@ class Irccd; class Irccdctl; - -/** - * \brief A JSON property is missing. - */ -class MissingPropertyError : public std::exception { -private: - std::string m_message; - std::string m_name; - std::vector m_types; - -public: - /** - * Constructor. - */ - MissingPropertyError(std::string name, std::vector types); - - /** - * Get human error message. - * - * \return a message - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; +class TransportClient; /** - * \brief A JSON property is invalid + * \brief Server side remote command */ -class InvalidPropertyError : public std::exception { -private: - std::string m_message; - std::string m_name; - - nlohmann::json::value_t m_expected; - nlohmann::json::value_t m_result; - -public: - /** - * Constructor. - * - * \param name the property name - * \param expected the expected type - * \param result the type received - */ - InvalidPropertyError(std::string name, nlohmann::json::value_t expected, nlohmann::json::value_t result); - - /** - * Get human error message. - * - * \return a message - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; - -/** - * \brief Property range error. - */ -class PropertyRangeError : public std::exception { +class Command { private: - std::string m_message; - std::string m_name; - std::uint64_t m_min; - std::uint64_t m_max; - std::uint64_t m_value; - -public: - /** - * Constructor. - * - * \pre value < min || value > max - * \param name the property name - * \param min the minimum value - * \param max the maximum value - * \param value the actual value - */ - PropertyRangeError(std::string name, std::uint64_t min, std::uint64_t max, std::uint64_t value); - - /** - * Get human error message. - * - * \return a message - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; - -/** - * \brief Generic error for JSON properties. - */ -class PropertyError : public std::exception { -private: - std::string m_message; std::string m_name; public: /** - * Constructor. - * - * \param name the property name - * \param message the error message - */ - PropertyError(std::string name, std::string message); - - /** - * Get human error message. - * - * \return a message - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; - - -/** - * \brief Namespace for remote commands. - */ -//namespace command { - -/** - * \brief Command line arguments to irccdctl. - * - * This class contains the resolved arguments from command line that can apply - * to the command. - */ -class CommandRequest { -public: - /** - * The options given by command line. - */ - using Options = std::multimap; - - /** - * Command line arguments in the same order. - */ - using Args = std::vector; - -private: - Options m_options; - Args m_args; - -public: - /** - * Construct the request. + * Construct a command. * - * \param options the options - * \param args the arguments - */ - inline CommandRequest(Options options, Args args) noexcept - : m_options(std::move(options)) - , m_args(std::move(args)) - { - } - - /** - * Get the arguments. - * - * \return the arguments - */ - inline const Args &args() const noexcept - { - return m_args; - } - - /** - * Get the options. - * - * \return the options - */ - inline const Options &options() const noexcept - { - return m_options; - } - - /** - * Get the number of arguments. - * - * \return the number of arguments - */ - inline unsigned length() const noexcept - { - return (unsigned)m_args.size(); - } - - /** - * Check if the request has the given option id. - * - * \param option the option id - * \return true if the option is available - */ - inline bool has(const std::string &option) const noexcept - { - return m_options.count(option) != 0; - } - - /** - * Get the argument at the specified index. - * - * \pre index < length() - * \param index the argument index - * \return the argument + * \pre !name.empty() + * \param name the command name */ - inline const std::string &arg(unsigned index) const noexcept - { - assert(index < m_args.size()); - - return m_args[index]; - } - - /** - * Get the argument or default value if not available. - * - * \param index the index - * \param defaultValue the value if index is out of range - * \return the argument - */ - inline std::string argOr(unsigned index, std::string defaultValue) const noexcept - { - return index < m_args.size() ? m_args[index] : defaultValue; - } - - /** - * Get the given option by its id. - * - * \pre has(key) - * \param key the option id - * \return the option - */ - inline const std::string &option(const std::string &key) const noexcept - { - assert(m_options.count(key) != 0); - - return m_options.find(key)->second; - } - - /** - * Get the given option by its id or defaultValue if not found. - * - * \param key the option id - * \param defaultValue the value replacement - * \return the option - */ - inline std::string optionOr(const std::string &key, std::string defaultValue) const noexcept - { - auto it = m_options.find(key); - - if (it == m_options.end()) - return defaultValue; - - return it->second; - } -}; - -/** - * \brief Invokable command. - * - * A remote command is a invokable command in the irccd daemon. You can register - * dynamically any remote command you like using Application::addCommand. - * - * The remote command will be usable directly from irccdctl without any other - * code. - * - * A remote command can have options and arguments. Options always come first, - * before arguments. - * - * The command workflow is defined as follow: - * - * 1. User wants to invoke a command, request() is called and return a JSON - * object containaing the request, it it send to the daemon. - * - * 2. The daemon receive the request and execute it using exec(). It returns a - * JSON object containint the request result or error if any. - * - * 3. Finally, the command receives the result in result() function and user can - * manipulate it. For convenience, the default implementation shows the error - * if any. - */ -class Command { -public: - /** - * \brief Defines available options for this command. - */ - class Option; - - /** - * \brief Defines available arguments for this command. - */ - class Arg; - - /** - * \brief Defines properties that must be available in the JSON request. - */ - class Property; - -private: - std::string m_name; - std::string m_category; - std::string m_description; - bool m_visible; - -public: - /** - * Create the remote command. - * - * \pre name must not be empty - * \pre category must not be empty - * \param name the command name (e.g. server-list) - * \param category the category (e.g. Server) - * \param description a one line description with no dots, no new line - * \param visible true if the command should be visible without verbosity - */ - inline Command(std::string name, - std::string category, - std::string description, - bool visible = true) noexcept + inline Command(std::string name) noexcept : m_name(std::move(name)) - , m_category(std::move(category)) - , m_description(std::move(description)) - , m_visible(visible) { assert(!m_name.empty()); - assert(!m_category.empty()); } /** @@ -383,325 +73,19 @@ } /** - * Get the command category. - * - * Irccdctl will sort commands by categories. - * - * \return the category - */ - inline const std::string &category() const noexcept - { - return m_category; - } - - /** - * Get the command description. - * - * \return the description - */ - inline const std::string &description() const noexcept - { - return m_description; - } - - /** - * Hide the command in non-verbose mode. - * - * \return true if the command should be visible in non-verbose mode - */ - inline bool visible() const noexcept - { - return m_visible; - } - - /** - * Return the command documentation usage. - * - * \return the usage - */ - IRCCD_EXPORT std::string usage() const; - - /** - * Return the help message. - * - * \return the help message - */ - IRCCD_EXPORT std::string help() const; - - /** - * Get the supported irccdctl options. - * - * \return the options - */ - virtual std::vector