degesch: enable configuration in Lua plugins
This commit is contained in:
parent
b7dd384048
commit
04f87b7587
325
degesch.c
325
degesch.c
@ -1939,6 +1939,7 @@ register_config_modules (struct app_context *ctx)
|
||||
// The servers are loaded later when we can create buffers for them
|
||||
config_register_module (config, "servers", NULL, NULL);
|
||||
config_register_module (config, "aliases", NULL, NULL);
|
||||
config_register_module (config, "plugins", NULL, NULL);
|
||||
config_register_module (config, "behaviour", load_config_behaviour, ctx);
|
||||
config_register_module (config, "attributes", load_config_attributes, ctx);
|
||||
}
|
||||
@ -2004,6 +2005,12 @@ get_aliases_config (struct app_context *ctx)
|
||||
return &config_item_get (ctx->config.root, "aliases", NULL)->value.object;
|
||||
}
|
||||
|
||||
static struct str_map *
|
||||
get_plugins_config (struct app_context *ctx)
|
||||
{
|
||||
return &config_item_get (ctx->config.root, "plugins", NULL)->value.object;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
@ -7290,6 +7297,22 @@ server_rename (struct app_context *ctx, struct server *s, const char *new_name)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Plugins -----------------------------------------------------------------
|
||||
|
||||
/// Returns the basename of the plugin's name without any extensions,
|
||||
/// or NULL if the name isn't suitable (starts with a dot)
|
||||
static char *
|
||||
plugin_config_name (struct plugin *self)
|
||||
{
|
||||
const char *begin = self->name;
|
||||
for (const char *p = begin; *p; )
|
||||
if (*p++ == '/')
|
||||
begin = p;
|
||||
|
||||
size_t len = strcspn (begin, ".");
|
||||
return len ? xstrndup (begin, len) : NULL;
|
||||
}
|
||||
|
||||
// --- Lua ---------------------------------------------------------------------
|
||||
|
||||
// Each plugin has its own Lua state object, so that a/ they don't disturb each
|
||||
@ -7304,6 +7327,8 @@ struct lua_plugin
|
||||
struct plugin super; ///< The structure we're deriving
|
||||
struct app_context *ctx; ///< Application context
|
||||
lua_State *L; ///< Lua state
|
||||
|
||||
struct lua_schema_item *schemas; ///< Registered schema items
|
||||
};
|
||||
|
||||
static void
|
||||
@ -7819,12 +7844,300 @@ lua_plugin_hook_timer (lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
#define XLUA_SCHEMA_METATABLE "schema" ///< Identifier for the Lua metatable
|
||||
|
||||
struct lua_schema_item
|
||||
{
|
||||
LIST_HEADER (struct lua_schema_item)
|
||||
|
||||
struct lua_plugin *plugin; ///< The plugin we belong to
|
||||
struct config_item *item; ///< The item managed by the schema
|
||||
struct config_schema schema; ///< Schema itself
|
||||
|
||||
int ref_validate; ///< Reference to "validate" callback
|
||||
int ref_on_change; ///< Reference to "on_change" callback
|
||||
};
|
||||
|
||||
static void
|
||||
lua_schema_item_discard (struct lua_schema_item *self)
|
||||
{
|
||||
if (self->item)
|
||||
{
|
||||
self->item->schema = NULL;
|
||||
self->item->user_data = NULL;
|
||||
self->item = NULL;
|
||||
LIST_UNLINK (self->plugin->schemas, self);
|
||||
}
|
||||
|
||||
// Now that we've disconnected from the item, allow garbage collection
|
||||
lua_cache_invalidate (self->plugin->L, self);
|
||||
}
|
||||
|
||||
static int
|
||||
lua_schema_item_gc (lua_State *L)
|
||||
{
|
||||
struct lua_schema_item *self =
|
||||
luaL_checkudata (L, 1, XLUA_SCHEMA_METATABLE);
|
||||
lua_schema_item_discard (self);
|
||||
|
||||
free ((char *) self->schema.name);
|
||||
free ((char *) self->schema.comment);
|
||||
free ((char *) self->schema.default_);
|
||||
|
||||
luaL_unref (L, LUA_REGISTRYINDEX, self->ref_validate);
|
||||
luaL_unref (L, LUA_REGISTRYINDEX, self->ref_on_change);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static luaL_Reg lua_schema_item_table[] =
|
||||
{
|
||||
{ "__gc", lua_schema_item_gc },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
/// Unfortunately this has the same problem as JSON libraries in that Lua
|
||||
/// cannot store null values in containers (it has no distinct "undefined" type)
|
||||
static void
|
||||
lua_plugin_push_config_item (lua_State *L, const struct config_item *item)
|
||||
{
|
||||
switch (item->type)
|
||||
{
|
||||
case CONFIG_ITEM_NULL:
|
||||
lua_pushnil (L);
|
||||
break;
|
||||
case CONFIG_ITEM_OBJECT:
|
||||
{
|
||||
lua_createtable (L, 0, item->value.object.len);
|
||||
|
||||
struct str_map_iter iter;
|
||||
str_map_iter_init (&iter, &item->value.object);
|
||||
struct config_item *child;
|
||||
while ((child = str_map_iter_next (&iter)))
|
||||
{
|
||||
lua_plugin_push_config_item (L, child);
|
||||
lua_setfield (L, -2, iter.link->key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONFIG_ITEM_BOOLEAN:
|
||||
lua_pushboolean (L, item->value.boolean);
|
||||
break;
|
||||
case CONFIG_ITEM_INTEGER:
|
||||
lua_pushinteger (L, item->value.integer);
|
||||
break;
|
||||
case CONFIG_ITEM_STRING:
|
||||
case CONFIG_ITEM_STRING_ARRAY:
|
||||
lua_pushlstring (L, item->value.string.str, item->value.string.len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
lua_schema_item_validate (const struct config_item *item, struct error **e)
|
||||
{
|
||||
struct lua_schema_item *self = item->user_data;
|
||||
if (self->ref_validate == LUA_REFNIL)
|
||||
return true;
|
||||
|
||||
struct lua_plugin *plugin = self->plugin;
|
||||
lua_State *L = plugin->L;
|
||||
|
||||
lua_pushcfunction (L, lua_plugin_error_handler);
|
||||
lua_rawgeti (L, LUA_REGISTRYINDEX, self->ref_validate);
|
||||
lua_plugin_push_config_item (L, item);
|
||||
|
||||
// The callback can make use of error("...", 0) to produce nice messages
|
||||
if (lua_pcall (L, 1, 0, -3))
|
||||
{
|
||||
(void) lua_plugin_process_error (plugin, lua_tostring (L, -1), e);
|
||||
lua_pop (L, 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
lua_schema_item_on_change (struct config_item *item)
|
||||
{
|
||||
struct lua_schema_item *self = item->user_data;
|
||||
if (self->ref_on_change == LUA_REFNIL)
|
||||
return;
|
||||
|
||||
struct lua_plugin *plugin = self->plugin;
|
||||
lua_State *L = plugin->L;
|
||||
|
||||
lua_pushcfunction (L, lua_plugin_error_handler);
|
||||
lua_rawgeti (L, LUA_REGISTRYINDEX, self->ref_on_change);
|
||||
lua_plugin_push_config_item (L, item);
|
||||
|
||||
if (lua_pcall (L, 1, 0, -3))
|
||||
{
|
||||
struct error *e = NULL;
|
||||
(void) lua_plugin_process_error (plugin, lua_tostring (L, -1), &e);
|
||||
lua_pop (L, 1);
|
||||
|
||||
log_global_error (plugin->ctx, "Lua: plugin \"#s\": #s: #s",
|
||||
plugin->super.name, "schema on_change", e->message);
|
||||
error_free (e);
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static int
|
||||
lua_plugin_decode_config_item_type (const char *type)
|
||||
{
|
||||
if (!strcmp (type, "null")) return CONFIG_ITEM_NULL;
|
||||
if (!strcmp (type, "object")) return CONFIG_ITEM_OBJECT;
|
||||
if (!strcmp (type, "boolean")) return CONFIG_ITEM_BOOLEAN;
|
||||
if (!strcmp (type, "integer")) return CONFIG_ITEM_INTEGER;
|
||||
if (!strcmp (type, "string")) return CONFIG_ITEM_STRING;
|
||||
if (!strcmp (type, "string_array")) return CONFIG_ITEM_STRING_ARRAY;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool
|
||||
lua_plugin_check_field (lua_State *L, int idx, const char *name,
|
||||
int expected, bool optional)
|
||||
{
|
||||
int found = lua_getfield (L, idx, name);
|
||||
if (found == expected)
|
||||
return true;
|
||||
if (optional && found == LUA_TNIL)
|
||||
return false;
|
||||
|
||||
const char *message = optional
|
||||
? "invalid field \"%s\" (found: %s, expected: %s or nil)"
|
||||
: "invalid or missing field \"%s\" (found: %s, expected: %s)";
|
||||
return luaL_error (L, message, name,
|
||||
lua_typename (L, found), lua_typename (L, expected));
|
||||
}
|
||||
|
||||
static int
|
||||
lua_plugin_add_config_schema (struct lua_plugin *plugin,
|
||||
struct config_item *subtree, const char *name)
|
||||
{
|
||||
struct config_item *item = str_map_find (&subtree->value.object, name);
|
||||
lua_State *L = plugin->L;
|
||||
|
||||
// This should only ever happen because of a conflict with another plugin;
|
||||
// this is the price we pay for simplicity
|
||||
if (item && item->schema)
|
||||
{
|
||||
struct lua_schema_item *owner = item->user_data;
|
||||
return luaL_error (L, "conflicting schema item: %s (owned by: %s)",
|
||||
name, owner->plugin->super.name);
|
||||
}
|
||||
|
||||
// Create and initialize a full userdata wrapper for the schema item
|
||||
struct lua_schema_item *self = lua_newuserdata (L, sizeof *self);
|
||||
luaL_setmetatable (L, XLUA_SCHEMA_METATABLE);
|
||||
memset (self, 0, sizeof *self);
|
||||
|
||||
self->plugin = plugin;
|
||||
self->ref_on_change = LUA_REFNIL;
|
||||
self->ref_validate = LUA_REFNIL;
|
||||
|
||||
struct config_schema *schema = &self->schema;
|
||||
schema->name = xstrdup (name);
|
||||
schema->comment = NULL;
|
||||
schema->default_ = NULL;
|
||||
schema->type = CONFIG_ITEM_NULL;
|
||||
|
||||
// Try to update the defaults with values provided by the plugin
|
||||
int values = lua_absindex (L, -2);
|
||||
(void) lua_plugin_check_field (L, values, "type", LUA_TSTRING, false);
|
||||
int item_type = schema->type =
|
||||
lua_plugin_decode_config_item_type (lua_tostring (L, -1));
|
||||
if (item_type == -1)
|
||||
return luaL_error (L, "invalid type of schema item");
|
||||
|
||||
if (lua_plugin_check_field (L, values, "comment", LUA_TSTRING, true))
|
||||
schema->comment = xstrdup (lua_tostring (L, -1));
|
||||
if (lua_plugin_check_field (L, values, "default", LUA_TSTRING, true))
|
||||
schema->default_ = xstrdup (lua_tostring (L, -1));
|
||||
if (lua_plugin_check_field (L, values, "on_change", LUA_TFUNCTION, true))
|
||||
self->ref_on_change = luaL_ref (L, -1);
|
||||
if (lua_plugin_check_field (L, values, "validate", LUA_TFUNCTION, true))
|
||||
self->ref_validate = luaL_ref (L, -1);
|
||||
|
||||
lua_pop (L, 5);
|
||||
|
||||
// Try to install the created schema item into our configuration
|
||||
struct error *warning = NULL, *e = NULL;
|
||||
item = config_schema_initialize_item
|
||||
(&self->schema, subtree, self, &warning, &e);
|
||||
|
||||
if (warning)
|
||||
{
|
||||
log_global_error (plugin->ctx, "Lua: plugin \"#s\": #s",
|
||||
plugin->super.name, warning->message);
|
||||
error_free (warning);
|
||||
}
|
||||
if (e)
|
||||
{
|
||||
const char *error = lua_pushstring (L, e->message);
|
||||
error_free (e);
|
||||
return luaL_error (L, "%s", error);
|
||||
}
|
||||
|
||||
self->item = item;
|
||||
LIST_PREPEND (plugin->schemas, self);
|
||||
|
||||
// On the stack there should be the schema table and the resulting object;
|
||||
// we need to make sure Lua doesn't GC the second and get rid of them both
|
||||
lua_cache_store (L, self, -1);
|
||||
lua_pop (L, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_plugin_setup_config (lua_State *L)
|
||||
{
|
||||
struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1));
|
||||
luaL_checktype (L, 1, LUA_TTABLE);
|
||||
|
||||
struct app_context *ctx = plugin->ctx;
|
||||
char *config_name = plugin_config_name (&plugin->super);
|
||||
if (!config_name)
|
||||
return luaL_error (L, "unsuitable plugin name");
|
||||
|
||||
struct str_map *plugins = get_plugins_config (ctx);
|
||||
struct config_item *subtree = str_map_find (plugins, config_name);
|
||||
if (!subtree || subtree->type != CONFIG_ITEM_OBJECT)
|
||||
str_map_set (plugins, config_name, (subtree = config_item_object ()));
|
||||
free (config_name);
|
||||
|
||||
LIST_FOR_EACH (struct lua_schema_item, iter, plugin->schemas)
|
||||
lua_schema_item_discard (iter);
|
||||
|
||||
// Load all schema items and apply them to the plugin's subtree
|
||||
lua_pushnil (L);
|
||||
while (lua_next (L, 1))
|
||||
{
|
||||
if (lua_type (L, -2) != LUA_TSTRING
|
||||
|| lua_type (L, -1) != LUA_TTABLE)
|
||||
return luaL_error (L, "%s: %s -> %s", "invalid types",
|
||||
lua_typename (L, -2), lua_typename (L, -1));
|
||||
lua_plugin_add_config_schema (plugin, subtree, lua_tostring (L, -2));
|
||||
}
|
||||
|
||||
// Let the plugin read out configuration via on_change callbacks
|
||||
config_schema_call_changed (subtree);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static luaL_Reg lua_plugin_library[] =
|
||||
{
|
||||
{ "hook_input", lua_plugin_hook_input },
|
||||
{ "hook_irc", lua_plugin_hook_irc },
|
||||
{ "hook_timer", lua_plugin_hook_timer },
|
||||
{ NULL, NULL },
|
||||
{ "hook_input", lua_plugin_hook_input },
|
||||
{ "hook_irc", lua_plugin_hook_irc },
|
||||
{ "hook_timer", lua_plugin_hook_timer },
|
||||
{ "setup_config", lua_plugin_setup_config },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@ -7921,6 +8234,7 @@ lua_plugin_load (struct app_context *ctx, const char *filename,
|
||||
lua_plugin_create_meta (L, XLUA_HOOK_METATABLE, lua_hook_table);
|
||||
lua_plugin_create_meta (L, XLUA_BUFFER_METATABLE, lua_buffer_table);
|
||||
lua_plugin_create_meta (L, XLUA_SERVER_METATABLE, lua_server_table);
|
||||
lua_plugin_create_meta (L, XLUA_SCHEMA_METATABLE, lua_schema_item_table);
|
||||
|
||||
int ret;
|
||||
if (!(ret = luaL_loadfile (L, filename))
|
||||
@ -9351,7 +9665,8 @@ try_handle_command_help_option (struct app_context *ctx, const char *name)
|
||||
|
||||
log_global_indent (ctx, "");
|
||||
log_global_indent (ctx, "Option \"#s\":", name);
|
||||
log_global_indent (ctx, " Description: #s", schema->comment);
|
||||
log_global_indent (ctx, " Description: #s",
|
||||
schema->comment ? schema->comment : "(none)");
|
||||
log_global_indent (ctx, " Type: #s", config_item_type_name (schema->type));
|
||||
log_global_indent (ctx, " Default: #s",
|
||||
schema->default_ ? schema->default_ : "null");
|
||||
|
2
liberty
2
liberty
@ -1 +1 @@
|
||||
Subproject commit 8b2e41ed8ffac0494763495896c6a80a9e9db543
|
||||
Subproject commit f6d74544f82ce8186e73a6ba268c2bc56b3ce5c7
|
Loading…
Reference in New Issue
Block a user