add config module and modify irc_log/nexus to use it
authorTero Marttila <terom@fixme.fi>
Fri, 27 Mar 2009 17:22:43 +0200
changeset 83 c8e2dac08207
parent 82 bc767e01648d
child 84 2791bb73bbee
add config module and modify irc_log/nexus to use it
src/CMakeLists.txt
src/config.c
src/config.h
src/error.c
src/error.h
src/irc_chan.c
src/irc_log.c
src/module.h
src/nexus.c
--- a/src/CMakeLists.txt	Fri Mar 27 01:01:34 2009 +0200
+++ b/src/CMakeLists.txt	Fri Mar 27 17:22:43 2009 +0200
@@ -12,7 +12,7 @@
 set (SOCK_SOURCES sock.c sock_tcp.c sock_gnutls.c sock_test.c line_proto.c)
 set (IRC_SOURCES irc_line.c irc_conn.c irc_net.c irc_chan.c chain.c irc_cmd.c irc_proto.c irc_client.c irc_user.c)
 
-set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES} signals.c module.c)
+set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES} signals.c module.c config.c)
 set (TEST_SOURCES test.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES})
 set (IRC_LOG_SOURCES irc_log.c)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/config.c	Fri Mar 27 17:22:43 2009 +0200
@@ -0,0 +1,21 @@
+#include "config.h"
+
+#include <string.h>
+
+err_t config_apply (struct config_option *options, void *ctx, const char *name, char *value, struct error_info *err)
+{
+    struct config_option *option;
+
+    // find the matching config opt
+    for (option = options; option->name && option->type; option++) {
+        if (strcmp(option->name, name) == 0)
+            break;
+    }
+
+    // no matching option found?
+    if (!option)
+        RETURN_SET_ERROR_STR(err, ERR_CONFIG_NAME, name);
+
+    // call the handler
+    return option->func(ctx, value, err);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/config.h	Fri Mar 27 17:22:43 2009 +0200
@@ -0,0 +1,43 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+/**
+ * Support for module configuration parameters
+ */
+#include "error.h"
+
+/** 
+ * Different types of configuration parameters
+ */
+enum config_type {
+    CONFIG_INVALID,
+
+    /** A plain NUL-terminated string */
+    CONFIG_STRING,
+};
+
+struct config_option {
+    /** The name of the config option */
+    const char *name;
+
+    /** The type of the value, XXX: unused */
+    enum config_type type;
+    
+    /** The value handler func */
+    err_t (*func) (void *ctx, char *value, struct error_info *err);
+
+    /** The value description */
+    const char *description;
+
+    /** Help text */
+    const char *help;
+};
+
+/**
+ * Apply a configuration name/value to an array of config_option's.
+ *
+ * This finds the appropriate config_option and calls its func.
+ */
+err_t config_apply (struct config_option *options, void *ctx, const char *name, char *value, struct error_info *err);
+
+#endif
--- a/src/error.c	Fri Mar 27 01:01:34 2009 +0200
+++ b/src/error.c	Fri Mar 27 17:22:43 2009 +0200
@@ -19,6 +19,7 @@
     {   ERR_EVENT_ADD,                      "event_add",                                ERR_EXTRA_NONE      },
     {   ERR_EVSQL_NEW_PQ,                   "evsql_new_pq",                             ERR_EXTRA_NONE      },
     {   ERR_EVSQL_QUERY_EXEC,               "evsql_query_exec",                         ERR_EXTRA_NONE      },
+    {   ERR_CONFIG_NAME,                    "no matching config name",                  ERR_EXTRA_STR       },
     {   ERR_CMD_OPT,                        "argv",                                     ERR_EXTRA_STR       },
     {   _ERR_INVALID,                       NULL,                                       0                   }
 
--- a/src/error.h	Fri Mar 27 01:01:34 2009 +0200
+++ b/src/error.h	Fri Mar 27 17:22:43 2009 +0200
@@ -86,9 +86,14 @@
     /** @see module_error_code */
     _ERR_MODULE     = 0x000a00,
 
+    /** config errors */
+    _ERR_CONFIG     = 0x000b00,
+    ERR_CONFIG_NAME,
+
     /** General errors */
     _ERR_GENERAL    = 0xffff00,
     ERR_CMD_OPT,
+
 };
 
 /**
--- a/src/irc_chan.c	Fri Mar 27 01:01:34 2009 +0200
+++ b/src/irc_chan.c	Fri Mar 27 17:22:43 2009 +0200
@@ -158,7 +158,6 @@
     strcpy(names, arg_names);
 
     // iterate over each name
-    // XXX: nickflags
     while ((nickname = strsep(&names, " "))) {
         // skip empty token at end
         if (strlen(nickname) == 0)
--- a/src/irc_log.c	Fri Mar 27 01:01:34 2009 +0200
+++ b/src/irc_log.c	Fri Mar 27 17:22:43 2009 +0200
@@ -1,5 +1,6 @@
 #include "module.h"
 #include "irc_chan.h"
+#include "config.h"
 #include "error.h"
 #include "log.h"
 
@@ -398,8 +399,10 @@
  *
  * Fails if ctx->db is already set.
  */
-static err_t irc_log_conf_db_info (struct irc_log_ctx *ctx, char *value, struct error_info *err)
+static err_t irc_log_conf_db_info (void *_ctx, char *value, struct error_info *err)
 {
+    struct irc_log_ctx *ctx = _ctx;
+
     log_info("connect to database: %s", value);
 
     // already connected?
@@ -422,8 +425,9 @@
  * Fails if the value is invalid, we don't have a database connected, the channel doesn't exist, adding our command
  * handlers/callbacks fails, or sending the initial INSERT-OPEN query fails.
  */
-static err_t irc_log_conf_channel (struct irc_log_ctx *ctx, char *value, struct error_info *err)
+static err_t irc_log_conf_channel (void *_ctx, char *value, struct error_info *err)
 {
+    struct irc_log_ctx *ctx = _ctx;
     const char *network, *channel;
     struct irc_chan *chan;
     
@@ -455,6 +459,15 @@
 }
 
 /**
+ * Our configuration options
+ */
+struct config_option irc_log_config_options[] = {
+    {   "db_info",  CONFIG_STRING,  &irc_log_conf_db_info,  "[<key>=<value> [...]]",    "set database connection info, see libpq docs"  },
+    {   "channel",  CONFIG_STRING,  &irc_log_conf_channel,  "<network>:<channel>",      "log the given channel"                         },
+    {   NULL,       0,              NULL,                   NULL,                       NULL                                            }
+};
+
+/**
  * Set a config option
  */
 static err_t irc_log_conf (void *_ctx, const char *name, char *value, struct error_info *err)
@@ -465,19 +478,8 @@
     if (ctx->unloading)
         RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "module is being unloaded");
     
-    // apply the config setting
-    if (strcmp(name, "db_info") == 0) {
-        return irc_log_conf_db_info(ctx, value, err);
-
-    } else if (strcmp(name, "channel") == 0) {
-        return irc_log_conf_channel(ctx, value, err);
-
-    } else {
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown configuration option");
-    }
-
-    // ok
-    return SUCCESS;
+    // apply it
+    return config_apply(irc_log_config_options, ctx, name, value, err);
 }
 
 /**
@@ -504,8 +506,9 @@
  * The module function table
  */
 struct module_funcs irc_log_funcs = {
-    .init       = &irc_log_init,
-    .conf       = &irc_log_conf,
-    .unload     = &irc_log_unload,
+    .init               = &irc_log_init,
+    .conf               = &irc_log_conf,
+    .config_options     = irc_log_config_options,
+    .unload             = &irc_log_unload,
 };
 
--- a/src/module.h	Fri Mar 27 01:01:34 2009 +0200
+++ b/src/module.h	Fri Mar 27 17:22:43 2009 +0200
@@ -6,11 +6,12 @@
  *
  * Dynamically loadable modules for use with nexus.
  *
- * The modules are loaded using dlopen(), and hence should be standard dynamic libraries. Module initialization happens
- * using a module_init_func_t named "<name>_init", which should return some kind of context pointer, which can later be
- * used to perform other operations on the module.
+ * The modules are loaded using dlopen(), and hence should be standard dynamic libraries. Modules are then "loaded" by
+ * resolving a `struct module_funcs` symbol called '<modname>_funcs', and then using the init func to construct a
+ * "context", which is then further manipulated by various other module functions.
  */
 #include "nexus.h"
+#include "config.h"
 #include "error.h"
 
 #include <sys/queue.h>
@@ -29,6 +30,8 @@
 /**
  * A module's behaviour is defined as a set of function pointers, which is dynamically resolved from the module DSO,
  * using the <mod_name>_funcs symbol.
+ *
+ * XXX: this also includes non-functions now...
  */
 struct module_funcs {
     /**
@@ -57,6 +60,12 @@
     err_t (*conf) (void *ctx, const char *name, char *value, struct error_info *err);
 
     /**
+     * Optionally, a module may also provide some kind of list of available configuration parameters, by setting this
+     * to a pointer to an array of those.
+     */
+    const struct config_option *config_options;
+
+    /**
      * Unload the module, removing all handlers/callbacks added to the nexus' irc_client. This does not have to act
      * immediately, and will still have access to all irc_* resources it had earlier, and may perform I/O to unload
      * cleanly. But the unloading should complete reasonably quickly, after which all event handlers added by the
--- a/src/nexus.c	Fri Mar 27 01:01:34 2009 +0200
+++ b/src/nexus.c	Fri Mar 27 17:22:43 2009 +0200
@@ -43,9 +43,11 @@
 };
 
 /**
- * Display --help output on stdout
+ * Display --help output on stdout.
+ *
+ * If nexus is given, --config options for loaded modules are also listed
  */
-static void usage (const char *exe) 
+static void usage (struct nexus *nexus, const char *exe)
 {
     printf("Usage: %s [OPTIONS]\n", exe);
     printf("\n");
@@ -55,6 +57,29 @@
     printf(" --module / -m          add a module using '<name>:<path>' format\n");
     printf(" --config / -C          add a module configuration option using '<mod_name>:<name>[:<value>]' format\n");
     printf(" --debug / -d           set logging level to DEBUG\n");
+
+    if (nexus && !TAILQ_EMPTY(&nexus->modules->list)) {
+        struct module *module;
+
+        printf("\n");
+        printf("Module configuration options\n");
+
+        TAILQ_FOREACH(module, &nexus->modules->list, modules_list) {
+            printf("\n");
+            printf("%s:\n", module->info.name);
+
+            if (module->funcs->config_options) {
+                const struct config_option *opt;
+
+                for (opt = module->funcs->config_options; opt->name && opt->type; opt++) {
+                    printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, opt->description, opt->help);
+                }
+
+            } else {
+                printf("\t???\n");
+            }
+        }
+    }
 }
 
 /**
@@ -218,7 +243,7 @@
     while ((opt = getopt_long(argc, argv, "hn:c:m:C:", options, &option_index)) != -1) {
         switch (opt) {
             case OPT_HELP:
-                usage(argv[0]);
+                usage(nexus, argv[0]);
 
                 // XXX: return instead
                 exit(EXIT_SUCCESS);
@@ -252,7 +277,7 @@
                 break;
 
             case '?':
-                usage(argv[0]);
+                usage(nexus, argv[0]);
                 return SET_ERROR(err, ERR_CMD_OPT);
         }
     }