degesch: enable configuration in Lua plugins
This commit is contained in:
parent
b7dd384048
commit
04f87b7587
317
degesch.c
317
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
|
// The servers are loaded later when we can create buffers for them
|
||||||
config_register_module (config, "servers", NULL, NULL);
|
config_register_module (config, "servers", NULL, NULL);
|
||||||
config_register_module (config, "aliases", 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, "behaviour", load_config_behaviour, ctx);
|
||||||
config_register_module (config, "attributes", load_config_attributes, 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;
|
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
|
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 ---------------------------------------------------------------------
|
// --- Lua ---------------------------------------------------------------------
|
||||||
|
|
||||||
// Each plugin has its own Lua state object, so that a/ they don't disturb each
|
// 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 plugin super; ///< The structure we're deriving
|
||||||
struct app_context *ctx; ///< Application context
|
struct app_context *ctx; ///< Application context
|
||||||
lua_State *L; ///< Lua state
|
lua_State *L; ///< Lua state
|
||||||
|
|
||||||
|
struct lua_schema_item *schemas; ///< Registered schema items
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -7819,11 +7844,299 @@ lua_plugin_hook_timer (lua_State *L)
|
||||||
return 1;
|
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[] =
|
static luaL_Reg lua_plugin_library[] =
|
||||||
{
|
{
|
||||||
{ "hook_input", lua_plugin_hook_input },
|
{ "hook_input", lua_plugin_hook_input },
|
||||||
{ "hook_irc", lua_plugin_hook_irc },
|
{ "hook_irc", lua_plugin_hook_irc },
|
||||||
{ "hook_timer", lua_plugin_hook_timer },
|
{ "hook_timer", lua_plugin_hook_timer },
|
||||||
|
{ "setup_config", lua_plugin_setup_config },
|
||||||
{ NULL, NULL },
|
{ 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_HOOK_METATABLE, lua_hook_table);
|
||||||
lua_plugin_create_meta (L, XLUA_BUFFER_METATABLE, lua_buffer_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_SERVER_METATABLE, lua_server_table);
|
||||||
|
lua_plugin_create_meta (L, XLUA_SCHEMA_METATABLE, lua_schema_item_table);
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
if (!(ret = luaL_loadfile (L, filename))
|
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, "");
|
||||||
log_global_indent (ctx, "Option \"#s\":", name);
|
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, " Type: #s", config_item_type_name (schema->type));
|
||||||
log_global_indent (ctx, " Default: #s",
|
log_global_indent (ctx, " Default: #s",
|
||||||
schema->default_ ? schema->default_ : "null");
|
schema->default_ ? schema->default_ : "null");
|
||||||
|
|
2
liberty
2
liberty
|
@ -1 +1 @@
|
||||||
Subproject commit 8b2e41ed8ffac0494763495896c6a80a9e9db543
|
Subproject commit f6d74544f82ce8186e73a6ba268c2bc56b3ce5c7
|
Loading…
Reference in New Issue