modify config to support options with multiple params/values
authorTero Marttila <terom@fixme.fi>
Wed, 08 Apr 2009 01:28:46 +0300
changeset 120 576bab0a1c5a
parent 119 64f50072db9e
child 121 4682ebbc5644
modify config to support options with multiple params/values
src/config.c
src/config.h
src/error.h
src/lua_objs.c
src/nexus.c
--- a/src/config.c	Tue Apr 07 21:09:25 2009 +0300
+++ b/src/config.c	Wed Apr 08 01:28:46 2009 +0300
@@ -8,7 +8,7 @@
     const struct config_option *option;
 
     // find the matching config opt
-    for (option = options; option->name && option->type; option++) {
+    for (option = options; option->name; option++) {
         if (strcmp(option->name, name) == 0)
             return option;
     }
@@ -27,20 +27,22 @@
     const char *network, *channel;
     struct irc_chan *chan;
 
+    // XXX: wrong error code
+
     // parse required args
     if ((network = strsep(&raw_value, "/")) == NULL)
-        JUMP_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid <network> for CONFIG_IRC_CHAN value");
+        JUMP_SET_ERROR_STR(err, ERR_CONFIG_VALUE, "invalid <network> for CONFIG_IRC_CHAN value");
 
     if ((channel = strsep(&raw_value, "/")) == NULL)
-        JUMP_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid <channel> for CONFIG_IRC_CHAN value");
+        JUMP_SET_ERROR_STR(err, ERR_CONFIG_VALUE, "invalid <channel> for CONFIG_IRC_CHAN value");
     
     // extraneous stuff?
     if (raw_value)
-        JUMP_SET_ERROR_STR(err, ERR_MODULE_CONF, "trailing data for CONFIG_IRC_CHAN value");
+        JUMP_SET_ERROR_STR(err, ERR_CONFIG_VALUE, "trailing data for CONFIG_IRC_CHAN value");
     
     // get the channel?
     if ((chan = irc_client_get_chan(nexus->client, network, channel)) == NULL)
-        JUMP_SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown network/channel name for CONFIG_IRC_CHAN value");
+        JUMP_SET_ERROR_STR(err, ERR_CONFIG_VALUE, "unknown network/channel name for CONFIG_IRC_CHAN value");
     
     // ok
     return chan;
@@ -51,8 +53,15 @@
 
 err_t config_parse (const struct config_option *option, struct nexus *nexus, struct config_value *value, char *raw_value, struct error_info *err)
 {
+    // use the first param
+    const struct config_param *param = &option->params[0];
+
+    // must have exactly one param
+    if (!param->type || (param + 1)->type)
+        return SET_ERROR(err, ERR_CONFIG_PARAMS);
+
     // parse the value 
-    switch (option->type) {
+    switch (param->type) {
         case CONFIG_STRING:
             // simple!
             value->string = raw_value;
@@ -70,32 +79,66 @@
     }
 
     // copy the type
-    value->type = option->type;
+    value->type = param->type;
 
     // ok
     return SUCCESS; 
 }
 
-err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value *value, struct error_info *err)
+err_t config_check_param (const struct config_param *param, const struct config_value *value, struct error_info *err)
 {
+    // no value given?
+    if (!value || !value->type)
+        RETURN_SET_ERROR_STR(err, ERR_CONFIG_REQUIRED, param->name);
+
     // wrong type?
-    if (option->type != value->type)
+    if (param->type != value->type)
         // XXX: info about type names
         return SET_ERROR(err, ERR_CONFIG_TYPE);
 
-    // null?
-    if (!value)
-        RETURN_SET_ERROR_STR(err, ERR_CONFIG_REQUIRED, option->name);
-    
-    // call the handler
-    switch (option->type) {
-        case CONFIG_STRING:     return option->func.string(ctx, value->string, err);
-        case CONFIG_IRC_CHAN:   return option->func.irc_chan(ctx, value->irc_chan, err);
-        default:                NOT_REACHED();
-    }
+    // ok
+    return SUCCESS;
 }
 
-err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value *value, struct error_info *err)
+err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value values[], struct error_info *err)
+{
+    const struct config_param *param;
+    const struct config_value *value;
+
+    // handle each param
+    for (param = option->params, value = values; param->name && param->type; param++, value++) {
+        // check the given value
+        if (config_check_param(param, value, err))
+            goto error;
+        
+        if (param->is_handler) {
+            err_t tmp;
+            
+            // invoke the handler
+            switch (param->type) {
+                case CONFIG_STRING:     tmp = param->func.string(ctx, value->string, err); break;
+                case CONFIG_IRC_CHAN:   tmp = param->func.irc_chan(ctx, value->irc_chan, err); break;
+                default:                NOT_REACHED();
+            }
+            
+            // abort on errors
+            if (tmp)
+                goto error;
+        }
+    }
+
+    // the option's handler?
+    if (option->func && option->func(ctx, values, err))
+        goto error;
+
+    // ok
+    return SUCCESS;
+
+error:
+    return ERROR_CODE(err);
+}
+
+err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value values[], struct error_info *err)
 {
     const struct config_option *option;
 
@@ -104,37 +147,46 @@
         return ERROR_CODE(err);
     
     // apply it    
-    return config_apply_opt(option, ctx, value, err);
+    return config_apply_opt(option, ctx, values, err);
 }
 
 err_t config_apply_string (const struct config_option *options, void *ctx, const char *name, char *value, struct error_info *err)
 {
-    struct config_value conf_value = { CONFIG_STRING, { .string = value } };
+    struct config_value conf_value[] = {
+        CONFIG_VALUE_STRING(value),
+        CONFIG_VALUE_END,
+    };
 
-    return config_apply(options, ctx, name, &conf_value, err);
+    return config_apply(options, ctx, name, conf_value, err);
 }
 
 err_t config_apply_irc_chan (const struct config_option *options, void *ctx, const char *name, struct irc_chan *value, struct error_info *err)
 {
-    struct config_value conf_value = { CONFIG_STRING, { .irc_chan = value } };
+    struct config_value conf_value[] = {
+        CONFIG_VALUE_IRC_CHAN(value),
+        CONFIG_VALUE_END,
+    };
 
-    return config_apply(options, ctx, name, &conf_value, err);
+    return config_apply(options, ctx, name, conf_value, err);
 }
 
 err_t config_apply_raw (const struct config_option *options, struct nexus *nexus, void *ctx, const char *name, char *raw_value, struct error_info *err)
 {
     const struct config_option *option;
-    struct config_value value;
+    struct config_value value[2] = {
+        CONFIG_VALUE_END,
+        CONFIG_VALUE_END,
+    };
 
     // no matching option found?
     if (!(option = config_lookup(options, name, err)))
         return ERROR_CODE(err);
     
     // parse it
-    if (config_parse(option, nexus, &value, raw_value, err))
+    if (config_parse(option, nexus, &value[0], raw_value, err))
         return ERROR_CODE(err);
     
     // apply it
-    return config_apply_opt(option, ctx, &value, err);
+    return config_apply_opt(option, ctx, value, err);
 }
 
--- a/src/config.h	Tue Apr 07 21:09:25 2009 +0300
+++ b/src/config.h	Wed Apr 08 01:28:46 2009 +0300
@@ -7,6 +7,7 @@
  * Support for module configuration parameters
  */
 #include "error.h"
+#include <stdbool.h>
 
 /** 
  * Different types of configuration parameters
@@ -39,36 +40,84 @@
 };
 
 /**
- * A single configuration option, with a name, type, handler function, etc.
+ * Structure to define a single parameter for an option
  */
-struct config_option {
-    /** The name of the config option */
+struct config_param {
+    /** The name of the arg */
     const char *name;
 
-    /** The type of the value expected */
+    /** The type */
     enum config_type type;
-    
-    /** The value handler func, by type */
+
+    /** Description */
+    const char *description;
+
+    /** Optional value handler function, by type */
     union {
         err_t (*string) (void *ctx, char *value, struct error_info *err);
         err_t (*irc_chan) (void *ctx, struct irc_chan *chan, struct error_info *err);
     } func;
 
-    /** The value description */
-    const char *description;
+    /** Use handler function? */
+    bool is_handler;
+};
+
+/**
+ * The maximum number of parameters that a single option can have, although this includes the terminating NULL
+ */
+#define CONFIG_PARAMS_MAX (15 + 1)
+
+/**
+ * The maximum number of values that an option can access, including the terminating NULL
+ */
+#define CONFIG_VALUES_MAX CONFIG_PARAMS_MAX
+
+/**
+ * A more complicated configuration option that can have multiple parameters
+ */
+struct config_option {
+    /** The name of the config option */
+    const char *name;
+
+    /** The list of parameters */
+    const struct config_param params[CONFIG_PARAMS_MAX];
+    
+    /** The handler function */
+    err_t (*func) (void *ctx, const struct config_value values[], struct error_info *err);
 
     /** Help text */
     const char *help;
 };
 
+#define CONFIG_PARAM(name, type, desc) \
+    {   name, type, desc, { NULL }, false }
+
+#define CONFIG_PARAM_HANDLER(name, type, desc, _func_name_, func) \
+    {   name, type, desc, {._func_name_ = func }, true }
+
+#define CONFIG_OPT(name, params, func, help) \
+    {   name, params, func, help }
+
 #define CONFIG_OPT_STRING(name, func, desc, help) \
-    {   name, CONFIG_STRING, { .string = func }, desc, help }
+    {   name, { CONFIG_PARAM_HANDLER(name, CONFIG_STRING, desc, string, func) }, NULL, help }
 
 #define CONFIG_OPT_IRC_CHAN(name, func, desc, help) \
-    {   name, CONFIG_IRC_CHAN, { .irc_chan = func }, desc, help }
+    {   name, { CONFIG_PARAM_HANDLER(name, CONFIG_IRC_CHAN, desc, irc_chan, func) }, NULL, help }
 
 #define CONFIG_OPT_END \
-    {   NULL,   CONFIG_INVALID, { NULL }, NULL, NULL }
+    {   NULL,   {}, NULL, NULL }
+
+#define CONFIG_VALUE(type, _name_, value) \
+    {   (type), { ._name_ = (value) } }
+
+#define CONFIG_VALUE_STRING(str_value) \
+    CONFIG_VALUE(CONFIG_STRING, string, str_value)
+
+#define CONFIG_VALUE_IRC_CHAN(irc_chan_value) \
+    CONFIG_VALUE(CONFIG_IRC_CHAN, irc_chan, irc_chan_value)
+
+#define CONFIG_VALUE_END \
+    {   0, { NULL } }
 
 /**
  * Lookup a config option by name.
@@ -83,7 +132,7 @@
 // XXX: move this into nexus
 #include "nexus.h"
 /**
- * Parse a raw value into a suitable configuration value, based on the config option type.
+ * Parse a raw value into a suitable configuration value for a single-param option, based on the config option type.
  *
  * Since this needs to access the application state, you need to pass in the nexus as an argument.
  *
@@ -99,27 +148,27 @@
 err_t config_parse (const struct config_option *option, struct nexus *nexus, struct config_value *value, char *raw_value, struct error_info *err);
 
 /**
- * Apply a parsed configuration value to the given option.
+ * Apply a list of parsed configuration values to the given config_option struct.
  *
  * The config option handlers take a context argument; the value of this depends on the implementor of the config_option.
  *
  * @param option the option to apply
  * @param ctx the context pointer for the option handler
- * @param value the parsed value
+ * @param values the NULL-terminated array of parsed values
  * @param err returned error info
  */
-err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value *value, struct error_info *err);
+err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value values[], struct error_info *err);
 
 /**
- * Apply a parsed configuration value for the named config opt.
+ * Apply a list of parsed configuration values for the named config opt.
  *
  * @param options a CONFIG_OPT_END-terminated array of config_option's
  * @param ctx the context pointer for the option handler
  * @param name the config_option::name to look up
- * @param value the parsed value
+ * @param values the NULL-termianted array of parsed values
  * @param err returned error info
  */
-err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value *value, struct error_info *err);
+err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value values[], struct error_info *err);
 
 /**
  * Apply a string value for the named config opt.
--- a/src/error.h	Tue Apr 07 21:09:25 2009 +0300
+++ b/src/error.h	Wed Apr 08 01:28:46 2009 +0300
@@ -90,9 +90,11 @@
 
     /** config errors */
     _ERR_CONFIG     = 0x000b00,
-    ERR_CONFIG_NAME,
-    ERR_CONFIG_TYPE,
-    ERR_CONFIG_REQUIRED,
+    ERR_CONFIG_NAME,        ///< unknown option name
+    ERR_CONFIG_TYPE,        ///< invalid value type for parameter
+    ERR_CONFIG_REQUIRED,    ///< missing value for required parameter
+    ERR_CONFIG_VALUE,       ///< invalid value
+    ERR_CONFIG_PARAMS,      ///< invalid number of parameters
     
     /** lua errors */
     _ERR_LUA        = 0x000c00,
--- a/src/lua_objs.c	Tue Apr 07 21:09:25 2009 +0300
+++ b/src/lua_objs.c	Wed Apr 08 01:28:46 2009 +0300
@@ -132,7 +132,10 @@
 {
     struct lua_module *lua_module = lua_obj_get_obj(L, __func__, "spbot.module");
     const struct config_option *option;
-    struct config_value value;
+    struct config_value values[2] = {
+        CONFIG_VALUE_END,
+        CONFIG_VALUE_END
+    }, *value = &values[0];
     struct error_info err;
 
     // XXX: come up with some better way...
@@ -170,8 +173,8 @@
             struct lua_chan *lua_chan = lua_touserdata(L, 3);
 
             // build the value
-            value.type = CONFIG_IRC_CHAN;
-            value.irc_chan = lua_chan->chan;
+            value->type = CONFIG_IRC_CHAN;
+            value->irc_chan = lua_chan->chan;
             
             break;
         
@@ -181,7 +184,7 @@
     }
     
     // apply it
-    if (module_conf(lua_module->module, option, &value, &err))
+    if (module_conf(lua_module->module, option, values, &err))
         return luaL_error(L, "module_conf: %s/%s: %s", module_name(lua_module->module), option->name, error_msg(&err));
    
     // ok
--- a/src/nexus.c	Tue Apr 07 21:09:25 2009 +0300
+++ b/src/nexus.c	Wed Apr 08 01:28:46 2009 +0300
@@ -83,8 +83,8 @@
             if (module->desc->config_options) {
                 const struct config_option *opt;
 
-                for (opt = module->desc->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);
+                for (opt = module->desc->config_options; opt->name; opt++) {
+                    printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, "xxx", opt->help);
                 }
 
             } else {