view libirccd-js/irccd/mod-plugin.cpp @ 292:671612cbc721

Irccd: split lib into libirccd-js, #564
author David Demelier <markand@malikania.fr>
date Wed, 05 Oct 2016 20:32:27 +0200
parents
children 45065955ba2d
line wrap: on
line source

/*
 * js-plugin.cpp -- Irccd.Plugin API
 *
 * Copyright (c) 2013-2016 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.
 */

#include "irccd.hpp"
#include "plugin-js.hpp"
#include "service-plugin.hpp"
#include "mod-irccd.hpp"
#include "mod-plugin.hpp"

namespace irccd {

namespace {

const char PluginGlobal[] = "\xff""\xff""irccd-plugin-ptr";

/*
 * wrap
 * ------------------------------------------------------------------
 *
 * Wrap function for these functions because they all takes the same arguments.
 *
 * - load,
 * - reload,
 * - unload.
 */
template <typename Func>
duk_idx_t wrap(duk_context *ctx, int nret, Func &&func)
{
    std::string name = duk_require_string(ctx, 0);

    try {
        func(dukx_get_irccd(ctx), name);
    } catch (const std::out_of_range &ex) {
        dukx_throw(ctx, ReferenceError(ex.what()));
    } catch (const std::exception &ex) {
        dukx_throw(ctx, Error(ex.what()));
    }

    return nret;
}

/*
 * set
 * ------------------------------------------------------------------
 *
 * This setter is used to replace the Irccd.Plugin.(config|format) property when
 * the plugin assign a new one.
 *
 * Because the plugin configuration always has higher priority, when a new
 * object is assigned to 'config' or to the 'format' property, the plugin
 * configuration is merged to the assigned one, adding or replacing any values.
 *
 * Example:
 *
 * Plugin 'xyz' does:
 *
 * Irccd.Plugin.config = {
 *      mode: "simple",
 *      level: "123"
 * };
 *
 * The user configuration is:
 *
 * [plugin.xyz]
 * mode = "hard"
 * path = "/var"
 *
 * The final user table looks like this:
 *
 * Irccd.Plugin.config = {
 *      mode: "hard",
 *      level: "123",
 *      path: "/var"
 */
duk_ret_t set(duk_context *ctx, const char *name)
{
    if (!duk_is_object(ctx, 0))
        duk_error(ctx, DUK_ERR_TYPE_ERROR, "'%s' property must be object", name);

    // Merge old table with new one.
    duk_get_global_string(ctx, name);
    duk_enum(ctx, -1, 0);

    while (duk_next(ctx, -1, true))
        duk_put_prop(ctx, 0);

    // Pop enum and old table.
    duk_pop_2(ctx);

    // Replace the old table with the new assigned one.
    duk_put_global_string(ctx, name);

    return 0;
}

/*
 * get
 * ------------------------------------------------------------------
 *
 * Get the Irccd.Plugin.(config|format) property.
 */
duk_ret_t get(duk_context *ctx, const char *name)
{
    duk_get_global_string(ctx, name);

    return 1;
}

/*
 * setConfig
 * ------------------------------------------------------------------
 *
 * Wrap setter for Irccd.Plugin.config property.
 */
duk_ret_t setConfig(duk_context *ctx)
{
    return set(ctx, JsPlugin::ConfigProperty);
}

/*
 * getConfig
 * ------------------------------------------------------------------
 *
 * Wrap getter for Irccd.Plugin.config property.
 */
duk_ret_t getConfig(duk_context *ctx)
{
    return get(ctx, JsPlugin::ConfigProperty);
}

/*
 * setFormat
 * ------------------------------------------------------------------
 *
 * Wrap setter for Irccd.Plugin.format property.
 */
duk_ret_t setFormat(duk_context *ctx)
{
    return set(ctx, JsPlugin::FormatProperty);
}

/*
 * getFormat
 * ------------------------------------------------------------------
 *
 * Wrap getter for Irccd.Plugin.format property.
 */
duk_ret_t getFormat(duk_context *ctx)
{
    return get(ctx, JsPlugin::FormatProperty);
}

/*
 * Function: Irccd.Plugin.info([name])
 * ------------------------------------------------------------------
 *
 * Get information about a plugin.
 *
 * The returned object as the following properties:
 *
 * - name: (string) the plugin identifier,
 * - author: (string) the author,
 * - license: (string) the license,
 * - summary: (string) a short description,
 * - version: (string) the version
 *
 * Arguments:
 *   - name, the plugin identifier, if not specified the current plugin is
 *     selected.
 * Returns:
 *   The plugin information or undefined if the plugin was not found.
 */
duk_idx_t info(duk_context *ctx)
{
    std::shared_ptr<Plugin> plugin;

    if (duk_get_top(ctx) >= 1)
        plugin = dukx_get_irccd(ctx).plugins().get(duk_require_string(ctx, 0));
    else
        plugin = dukx_get_plugin(ctx);

    if (!plugin)
        return 0;

    duk_push_object(ctx);
    dukx_push_std_string(ctx, plugin->name());
    duk_put_prop_string(ctx, -2, "name");
    dukx_push_std_string(ctx, plugin->author());
    duk_put_prop_string(ctx, -2, "author");
    dukx_push_std_string(ctx, plugin->license());
    duk_put_prop_string(ctx, -2, "license");
    dukx_push_std_string(ctx, plugin->summary());
    duk_put_prop_string(ctx, -2, "summary");
    dukx_push_std_string(ctx, plugin->version());
    duk_put_prop_string(ctx, -2, "version");

    return 1;
}

/*
 * Function: Irccd.Plugin.list()
 * ------------------------------------------------------------------
 *
 * Get the list of plugins, the array returned contains all plugin names.
 *
 * Returns:
 *   The list of all plugin names.
 */
duk_idx_t list(duk_context *ctx)
{
    dukx_push_array(ctx, dukx_get_irccd(ctx).plugins().list(), [] (auto ctx, auto plugin) {
        dukx_push_std_string(ctx, plugin->name());
    });

    return 1;
}

/*
 * Function: Irccd.Plugin.load(name)
 * ------------------------------------------------------------------
 *
 * Load a plugin by name. This function will search through the standard
 * directories.
 *
 * Arguments:
 *   - name, the plugin identifier.
 * Throws:
 *   - Error on errors,
 *   - ReferenceError if the plugin was not found.
 */
duk_idx_t load(duk_context *ctx)
{
    return wrap(ctx, 0, [&] (Irccd &irccd, const std::string &name) {
        irccd.plugins().load(name);
    });
}

/*
 * Function: Irccd.Plugin.reload(name)
 * ------------------------------------------------------------------
 *
 * Reload a plugin by name.
 *
 * Arguments:
 *   - name, the plugin identifier.
 * Throws:
 *   - Error on errors,
 *   - ReferenceError if the plugin was not found.
 */
duk_idx_t reload(duk_context *ctx)
{
    return wrap(ctx, 0, [&] (Irccd &irccd, const std::string &name) {
        irccd.plugins().reload(name);
    });
}

/*
 * Function: Irccd.Plugin.unload(name)
 * ------------------------------------------------------------------
 *
 * Unload a plugin by name.
 *
 * Arguments:
 *   - name, the plugin identifier.
 * Throws:
 *   - Error on errors,
 *   - ReferenceError if the plugin was not found.
 */
duk_idx_t unload(duk_context *ctx)
{
    return wrap(ctx, 0, [&] (Irccd &irccd, const std::string &name) {
        irccd.plugins().unload(name);
    });
}

const duk_function_list_entry functions[] = {
    { "info",   info,       DUK_VARARGS     },
    { "list",   list,       0               },
    { "load",   load,       1               },
    { "reload", reload,     1               },
    { "unload", unload,     1               },
    { nullptr,  nullptr,    0               }
};

} // !namespace

PluginModule::PluginModule() noexcept
    : Module("Irccd.Plugin")
{
}

void PluginModule::load(Irccd &, const std::shared_ptr<JsPlugin> &plugin)
{
    StackAssert sa(plugin->context());

    duk_push_pointer(plugin->context(), new std::shared_ptr<JsPlugin>(plugin));
    duk_put_global_string(plugin->context(), PluginGlobal);
    duk_get_global_string(plugin->context(), "Irccd");
    duk_push_object(plugin->context());
    duk_put_function_list(plugin->context(), -1, functions);

    // 'config' property.
    duk_push_string(plugin->context(), "config");
    duk_push_c_function(plugin->context(), getConfig, 0);
    duk_push_c_function(plugin->context(), setConfig, 1);
    duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);

    // 'format' property.
    duk_push_string(plugin->context(), "format");
    duk_push_c_function(plugin->context(), getFormat, 0);
    duk_push_c_function(plugin->context(), setFormat, 1);
    duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);

    duk_put_prop_string(plugin->context(), -2, "Plugin");
    duk_pop(plugin->context());
}

void PluginModule::unload(Irccd &, const std::shared_ptr<JsPlugin> &plugin)
{
    StackAssert sa(plugin->context());

    duk_push_global_object(plugin->context());
    duk_get_prop_string(plugin->context(), -1, PluginGlobal);
    delete static_cast<std::shared_ptr<JsPlugin> *>(duk_to_pointer(plugin->context(), -1));
    duk_pop(plugin->context());
    duk_del_prop_string(plugin->context(), -1, PluginGlobal);
    duk_pop(plugin->context());
}

std::shared_ptr<JsPlugin> dukx_get_plugin(duk_context *ctx)
{
    StackAssert sa(ctx);

    duk_get_global_string(ctx, PluginGlobal);
    auto plugin = static_cast<std::shared_ptr<JsPlugin> *>(duk_to_pointer(ctx, -1));
    duk_pop(ctx);

    return *plugin;
}

} // !irccd