Mercurial > irccd
changeset 588:be1656362297
Irccd: refactor Javascript calls, closes #747
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 05 Dec 2017 22:01:00 +0100 |
parents | 312af09354e0 |
children | 25d4a9f239ec |
files | libirccd-js/CMakeLists.txt libirccd-js/irccd/js/duktape.hpp libirccd-js/irccd/js/duktape_vector.hpp libirccd-js/irccd/js/js_plugin.cpp libirccd-js/irccd/js/js_plugin.hpp libirccd-js/irccd/js/server_jsapi.cpp libirccd-js/irccd/js/util_jsapi.cpp tests/src/file-jsapi/main.cpp |
diffstat | 8 files changed, 287 insertions(+), 310 deletions(-) [+] |
line wrap: on
line diff
--- a/libirccd-js/CMakeLists.txt Tue Dec 05 21:42:55 2017 +0100 +++ b/libirccd-js/CMakeLists.txt Tue Dec 05 22:01:00 2017 +0100 @@ -23,6 +23,7 @@ set( HEADERS ${libirccd-js_SOURCE_DIR}/irccd/js/duktape.hpp + ${libirccd-js_SOURCE_DIR}/irccd/js/duktape_vector.hpp ${libirccd-js_SOURCE_DIR}/irccd/js/directory_jsapi.hpp ${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_jsapi.hpp ${libirccd-js_SOURCE_DIR}/irccd/js/file_jsapi.hpp
--- a/libirccd-js/irccd/js/duktape.hpp Tue Dec 05 21:42:55 2017 +0100 +++ b/libirccd-js/irccd/js/duktape.hpp Tue Dec 05 22:01:00 2017 +0100 @@ -16,8 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef DUKTAPE_HPP -#define DUKTAPE_HPP +#ifndef IRCCD_JS_DUKTAPE_HPP +#define IRCCD_JS_DUKTAPE_HPP /** * \file duktape.hpp @@ -580,6 +580,151 @@ }; /** + * \brief Partial specialization for collections. + * + * Derive from this class to implement type traits for collections. + * + * \see duktape_vector.hpp + */ +template <typename Container> +class dukx_array_type_traits : public std::true_type { +public: + /** + * Push a Javascript array by copying values. + * + * Uses dukx_push for each T values in the collection. + * + * \param ctx the Duktape context + * \param c the container + */ + static void push(duk_context* ctx, const Container& c) + { + using Type = typename Container::value_type; + + duk_push_array(ctx); + + int i = 0; + + for (auto v : c) { + dukx_type_traits<Type>::push(ctx, v); + duk_put_prop_index(ctx, -2, i++); + } + } + + /** + * Get an array from the Javascript array. + * + * \param ctx the Duktape context + * \param index the value index + * \return the container + */ + static Container get(duk_context* ctx, duk_idx_t index) + { + using Type = typename Container::value_type; + using Size = typename Container::size_type; + + Container result; + Size length = duk_get_length(ctx, index); + + for (Size i = 0; i < length; ++i) { + duk_get_prop_index(ctx, index, i); + result.push_back(dukx_type_traits<Type>::get(ctx, -1)); + duk_pop(ctx); + } + + return result; + } + + /** + * Require an array from the Javascript array. + * + * \param ctx the Duktape context + * \param index the value index + * \return the container + */ + static Container require(duk_context* ctx, duk_idx_t index) + { + duk_check_type(ctx, index, DUK_TYPE_OBJECT); + + return get(ctx, index); + } +}; + +/** + * \brief Partial specialization for maps. + * + * Derive from this class to implement type traits for maps. + * + * \see duktape_vector.hpp + */ +template <typename Container> +class dukx_object_type_traits : public std::true_type { +public: + /** + * Push a container by copying values. + * + * Uses dukx_push for each key/value pair in the container. + * + * \param ctx the Duktape context + * \param c the container + */ + static void push(duk_context* ctx, const Container& c) + { + using Type = typename Container::mapped_type; + + duk_push_object(ctx); + + for (const auto& pair : c) { + dukx_type_traits<std::string>::push(ctx, pair.first); + dukx_type_traits<Type>::push(ctx, pair.second); + duk_put_prop(ctx, -3); + } + } + + /** + * Get a object from the Javascript array. + * + * \param ctx the Duktape context + * \param index the value index + * \return the container + */ + static Container get(duk_context* ctx, duk_idx_t index) + { + using Type = typename Container::mapped_type; + + Container result; + + duk_enum(ctx, index, 0); + + while (duk_next(ctx, -1, true)) { + result.emplace( + dukx_type_traits<std::string>::get(ctx, -2), + dukx_type_traits<Type>::get(ctx, -1) + ); + duk_pop_n(ctx, 2); + } + + duk_pop(ctx); + + return result; + } + + /** + * Require a object from the Javascript array. + * + * \param ctx the Duktape context + * \param index the value index + * \return the container + */ + static Container require(duk_context* ctx, duk_idx_t index) + { + duk_check_type(ctx, index, DUK_TYPE_OBJECT); + + return get(ctx, index); + } +}; + +/** * Generic push function. * * This function calls dukx_type_traits<T>::push if specialized. @@ -639,163 +784,6 @@ } /** - * Push a Javascript array to the stack. - * - * This function push the value using duk_push_array and dukx_push for each - * value between the range - * - * \param ctx the Duktape context - * \param it the input iterator - * \param end the end iterator - * \return 1 for convenience - * \warning experimental and may change in the future - */ -template <typename InputIt> -int dukx_push_array(duk_context* ctx, InputIt it, InputIt end) -{ - duk_push_array(ctx); - - for (int i = 0; it != end; ++it) { - dukx_push(ctx, *it); - duk_put_prop_index(ctx, -2, i++); - } - - return 1; -} - -/** - * Overloaded function. - * - * Alias for `dukx_push_array(ctx, values.begin(), values.end());` - * - * \param ctx the Duktape context - * \param values the list of values - * \return 1 for convenience - * \warning experimental and may change in the future - */ -template <typename T> -int dukx_push_array(duk_context* ctx, std::initializer_list<T> values) -{ - return dukx_push_array(ctx, values.begin(), values.end()); -} - -/** - * Push a Javascript object to the stack. - * - * Fhis function push the value using duk_push_object and dukx_push for each - * key/value pair combination in InputIt. - * - * The input iterator must have first and second values (usually `std::pair`). - * - * \param ctx the Duktape context - * \param it the input iterator - * \param end the end iterator - * \return 1 for convenience - * \warning experimental and may change in the future - */ -template <typename InputIt> -int dukx_push_object(duk_context* ctx, InputIt it, InputIt end) -{ - duk_push_object(ctx); - - while (it != end) { - dukx_push(ctx, it->first); - dukx_push(ctx, it++->second); - duk_put_prop(ctx, -3); - } - - return 1; -} - -/** - * Overloaded function. - * - * Alias for `dukx_push_object(ctx, values.begin(), values.end());` - * - * \param ctx the Duktape context - * \param values the list of key/value values - * \return 1 for convenience - * \warning experimental and may change in the future - */ -template <typename T> -int dukx_push_object(duk_context* ctx, std::initializer_list<std::pair<std::string, T>> values) -{ - return dukx_push_object(ctx, values.begin(), values.end()); -} - -/** - * Get values from a Javascript array. - * - * This function uses dukx_get<T> for each value in the Javascript array and - * append them to the output iterator. - * - * \param ctx the Duktape context - * \param index the array index - * \param output the output iterator - * \warning experimental and may change in the future - */ -template <typename T, typename OutputIt> -void dukx_get_array(duk_context* ctx, duk_idx_t index, OutputIt output) -{ - std::size_t length = duk_get_length(ctx, index); - - for (std::size_t i = 0; i < length; ++i) { - duk_get_prop_index(ctx, index, i); - *output++ = dukx_get<T>(ctx, -1); - duk_pop(ctx); - } -} - -/** - * Overloaded function. - * - * Construct a container and return it. - * - * \param ctx the Duktape context - * \param index the array index - * \return the container of values (e.g. `std::vector<int>`) - * \warning experimental and may change in the future - */ -template <typename Container> -Container dukx_get_array(duk_context* ctx, duk_idx_t index) -{ - using T = typename Container::value_type; - - Container result; - - dukx_get_array<T>(ctx, index, std::back_inserter(result)); - - return result; -} - -/** - * Get values from a Javascript object. - * - * \param ctx the Duktape context - * \param index the object index - * \return the container of values (e.g. `std::map<std::string, int>`) - * \warning experimental and may change in the future - */ -template <typename Container> -Container dukx_get_object(duk_context* ctx, duk_idx_t index) -{ - using T = typename Container::mapped_type; - - Container result; - - duk_enum(ctx, index, 0); - - while (duk_next(ctx, -1, true)) { - result.emplace(dukx_get<std::string>(ctx, -2), dukx_get<T>(ctx, -1)); - duk_pop_n(ctx, 2); - } - - duk_pop(ctx); - - return result; -} - -/** * \brief Base ECMAScript error class. * \warning Override the function create for your own exceptions */ @@ -995,4 +983,4 @@ } // !irccd -#endif // !DUKTAPE_HPP +#endif // !IRCCD_JS_DUKTAPE_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-js/irccd/js/duktape_vector.hpp Tue Dec 05 22:01:00 2017 +0100 @@ -0,0 +1,42 @@ +/* + * duktape_vector.hpp -- miscellaneous Duktape extras (std::vector support) + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_JS_DUKTAPE_VECTOR_HPP +#define IRCCD_JS_DUKTAPE_VECTOR_HPP + +/** + * \file duktape_vector.hpp + * \brief Miscellaneous Duktape extras (std::vector support). + */ + +#include <vector> + +#include "duktape.hpp" + +namespace irccd { + +/** + * \brief Specialization for std::vector<T>. + */ +template <typename T> +class dukx_type_traits<std::vector<T>> : public dukx_array_type_traits<std::vector<T>> { +}; + +} // !irccd + +#endif // !IRCCD_JS_DUKTAPE_VECTOR_HPP
--- a/libirccd-js/irccd/js/js_plugin.cpp Tue Dec 05 21:42:55 2017 +0100 +++ b/libirccd-js/irccd/js/js_plugin.cpp Tue Dec 05 22:01:00 2017 +0100 @@ -22,6 +22,7 @@ #include <iterator> #include <stdexcept> +#include "duktape_vector.hpp" #include "irccd.hpp" #include "logger.hpp" #include "js_plugin.hpp" @@ -65,28 +66,35 @@ duk_pop(context_); } -void js_plugin::call(const std::string& name, unsigned nargs) +void js_plugin::push() noexcept { - duk_get_global_string(context_, name.c_str()); +} - if (duk_get_type(context_, -1) == DUK_TYPE_UNDEFINED) - // Function not defined, remove the undefined value and all arguments. - duk_pop_n(context_, nargs + 1); - else { - std::string error; +template <typename Value, typename... Args> +void js_plugin::push(Value&& value, Args&&... args) +{ + dukx_push(context_, std::forward<Value>(value)); + push(std::forward<Args>(args)...); +} - // Call the function and discard the result. - duk_insert(context_, -nargs - 1); +template <typename... Args> +void js_plugin::call(const std::string& func, Args&&... args) +{ + dukx_stack_assert sa(context_); + + duk_get_global_string(context_, func.c_str()); - // TODO: convert into a human readable string. - if (duk_pcall(context_, nargs) != 0) - error = duk_safe_to_string(context_, -1); - + if (duk_get_type(context_, -1) == DUK_TYPE_UNDEFINED) { duk_pop(context_); + return; + } - if (!error.empty()) - throw plugin_error(plugin_error::exec_error, std::move(error)); - } + push(std::forward<Args>(args)...); + + if (duk_pcall(context_, sizeof... (Args)) != 0) + throw plugin_error(plugin_error::exec_error, name(), dukx_stack(context_, -1).stack()); + + duk_pop(context_); } js_plugin::js_plugin(std::string name, std::string path) @@ -153,186 +161,90 @@ duk_pop(context_); } -void js_plugin::on_command(irccd& , const message_event &event) +void js_plugin::on_command(irccd&, const message_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - dukx_push(context_, event.message); - call("onCommand", 4); + call("onCommand", event.server, event.origin, event.channel, event.message); } -void js_plugin::on_connect(irccd& , const connect_event &event) +void js_plugin::on_connect(irccd&, const connect_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - call("onConnect", 1); + call("onConnect", event.server); } -void js_plugin::on_invite(irccd& , const invite_event &event) +void js_plugin::on_invite(irccd&, const invite_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - call("onInvite", 3); + call("onInvite", event.server, event.origin, event.channel); } -void js_plugin::on_join(irccd& , const join_event &event) +void js_plugin::on_join(irccd&, const join_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - call("onJoin", 3); + call("onJoin", event.server, event.origin, event.channel); } -void js_plugin::on_kick(irccd& , const kick_event &event) +void js_plugin::on_kick(irccd&, const kick_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - dukx_push(context_, event.target); - dukx_push(context_, event.reason); - call("onKick", 5); + call("onKick", event.server, event.origin, event.channel, event.target, event.reason); } void js_plugin::on_load(irccd&) { - dukx_stack_assert sa(context_); - - call("onLoad", 0); + call("onLoad"); } -void js_plugin::on_message(irccd& , const message_event &event) +void js_plugin::on_message(irccd&, const message_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - dukx_push(context_, event.message); - call("onMessage", 4); + call("onMessage", event.server, event.origin, event.channel, event.message); } -void js_plugin::on_me(irccd& , const me_event &event) +void js_plugin::on_me(irccd&, const me_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - dukx_push(context_, event.message); - call("onMe", 4); + call("onMe", event.server, event.origin, event.channel, event.message); } -void js_plugin::on_mode(irccd& , const mode_event &event) +void js_plugin::on_mode(irccd&, const mode_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - dukx_push(context_, event.mode); - dukx_push(context_, event.limit); - dukx_push(context_, event.user); - dukx_push(context_, event.mask); - call("onMode", 7); + call("onMode", event.server, event.origin, event.channel, event.mode, + event.limit, event.user, event.mask); } -void js_plugin::on_names(irccd& , const names_event &event) +void js_plugin::on_names(irccd&, const names_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.channel); - dukx_push_array(context_, event.names.begin(), event.names.end()); - - call("onNames", 3); + call("onNames", event.server, event.channel, event.names); } -void js_plugin::on_nick(irccd& , const nick_event &event) +void js_plugin::on_nick(irccd&, const nick_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.nickname); - call("onNick", 3); + call("onNick", event.server, event.origin, event.nickname); } -void js_plugin::on_notice(irccd& , const notice_event &event) +void js_plugin::on_notice(irccd&, const notice_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - dukx_push(context_, event.message); - call("onNotice", 4); + call("onNotice", event.server, event.origin, event.channel, event.message); } -void js_plugin::on_part(irccd& , const part_event &event) +void js_plugin::on_part(irccd&, const part_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - dukx_push(context_, event.reason); - call("onPart", 4); + call("onPart", event.server, event.origin, event.channel, event.reason); } -void js_plugin::on_reload(irccd& ) +void js_plugin::on_reload(irccd&) { - dukx_stack_assert sa(context_); - call("onReload"); } -void js_plugin::on_topic(irccd& , const topic_event &event) +void js_plugin::on_topic(irccd&, const topic_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - dukx_push(context_, event.origin); - dukx_push(context_, event.channel); - dukx_push(context_, event.topic); - call("onTopic", 4); + call("onTopic", event.server, event.origin, event.channel, event.topic); } -void js_plugin::on_unload(irccd& ) +void js_plugin::on_unload(irccd&) { - dukx_stack_assert sa(context_); - call("onUnload"); } -void js_plugin::on_whois(irccd& , const whois_event &event) +void js_plugin::on_whois(irccd&, const whois_event& event) { - dukx_stack_assert sa(context_); - - dukx_push(context_, std::move(event.server)); - duk_push_object(context_); - dukx_push(context_, event.whois.nick); - duk_put_prop_string(context_, -2, "nickname"); - dukx_push(context_, event.whois.user); - duk_put_prop_string(context_, -2, "username"); - dukx_push(context_, event.whois.realname); - duk_put_prop_string(context_, -2, "realname"); - dukx_push(context_, event.whois.host); - duk_put_prop_string(context_, -2, "host"); - dukx_push_array(context_, event.whois.channels.begin(), event.whois.channels.end()); - duk_put_prop_string(context_, -2, "channels"); - - call("onWhois", 2); + call("onWhois", event.server, event.whois); } js_plugin_loader::js_plugin_loader(irccd& irccd) noexcept @@ -359,4 +271,19 @@ return plugin; } +void dukx_type_traits<whois>::push(duk_context* ctx, const whois& whois) +{ + duk_push_object(ctx); + dukx_push(ctx, whois.nick); + duk_put_prop_string(ctx, -2, "nickname"); + dukx_push(ctx, whois.user); + duk_put_prop_string(ctx, -2, "username"); + dukx_push(ctx, whois.realname); + duk_put_prop_string(ctx, -2, "realname"); + dukx_push(ctx, whois.host); + duk_put_prop_string(ctx, -2, "host"); + dukx_push(ctx, whois.channels); + duk_put_prop_string(ctx, -2, "channels"); +} + } // !irccd
--- a/libirccd-js/irccd/js/js_plugin.hpp Tue Dec 05 21:42:55 2017 +0100 +++ b/libirccd-js/irccd/js/js_plugin.hpp Tue Dec 05 22:01:00 2017 +0100 @@ -61,7 +61,17 @@ // Private helpers. std::unordered_map<std::string, std::string> get_table(const std::string&) const; void put_table(const std::string&, const std::unordered_map<std::string, std::string>&); - void call(const std::string&, unsigned = 0); + + /* + * Helpers to call a Javascript function. + */ + void push() noexcept; + + template <typename Value, typename... Args> + void push(Value&& value, Args&&... args); + + template <typename... Args> + void call(const std::string&, Args&&... args); public: /** @@ -272,6 +282,12 @@ const std::string& path) override; }; +template <> +class dukx_type_traits<whois> : public std::true_type { +public: + static void push(duk_context* ctx, const whois& who); +}; + } // !irccd #endif // !IRCCD_PLUGIN_JS_HPP
--- a/libirccd-js/irccd/js/server_jsapi.cpp Tue Dec 05 21:42:55 2017 +0100 +++ b/libirccd-js/irccd/js/server_jsapi.cpp Tue Dec 05 22:01:00 2017 +0100 @@ -23,6 +23,7 @@ #include <irccd.hpp> #include <irccd/server_service.hpp> +#include "duktape_vector.hpp" #include "irccd_jsapi.hpp" #include "js_plugin.hpp" #include "server_jsapi.hpp" @@ -85,7 +86,7 @@ duk_put_prop_string(ctx, -2, "nickname"); dukx_push(ctx, server->username()); duk_put_prop_string(ctx, -2, "username"); - dukx_push_array(ctx, server->channels().begin(), server->channels().end()); + dukx_push(ctx, server->channels()); duk_put_prop_string(ctx, -2, "channels"); return 1;
--- a/libirccd-js/irccd/js/util_jsapi.cpp Tue Dec 05 21:42:55 2017 +0100 +++ b/libirccd-js/irccd/js/util_jsapi.cpp Tue Dec 05 22:01:00 2017 +0100 @@ -20,6 +20,7 @@ #include <irccd/string_util.hpp> +#include "duktape_vector.hpp" #include "js_plugin.hpp" #include "util_jsapi.hpp" @@ -204,7 +205,7 @@ return 1; } - return dukx_push_array(ctx, list.begin(), list.end()); + return dukx_push(ctx, list); } /*
--- a/tests/src/file-jsapi/main.cpp Tue Dec 05 21:42:55 2017 +0100 +++ b/tests/src/file-jsapi/main.cpp Tue Dec 05 22:01:00 2017 +0100 @@ -21,6 +21,7 @@ #include <fstream> +#include <irccd/js/duktape_vector.hpp> #include <irccd/js/file_jsapi.hpp> #include <js_test.hpp> @@ -148,7 +149,7 @@ std::vector<std::string> expected{"a", "b", "c"}; BOOST_TEST(duk_get_global_string(plugin_->context(), "result")); - BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1)); + BOOST_TEST(expected == dukx_get<std::vector<std::string>>(plugin_->context(), -1)); } BOOST_AUTO_TEST_CASE(method_seek1) @@ -279,7 +280,7 @@ std::vector<std::string> expected{"a", "b", "c"}; BOOST_TEST(duk_get_global_string(plugin_->context(), "result")); - BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1)); + BOOST_TEST(expected == dukx_get<std::vector<std::string>>(plugin_->context(), -1)); } BOOST_AUTO_TEST_CASE(method_readline_closed) @@ -299,7 +300,7 @@ std::vector<std::string> expected; BOOST_TEST(duk_get_global_string(plugin_->context(), "result")); - BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1)); + BOOST_TEST(expected == dukx_get<std::vector<std::string>>(plugin_->context(), -1)); } BOOST_AUTO_TEST_SUITE_END()