src/spbot/config.c
author Tero Marttila <terom@fixme.fi>
Wed, 27 May 2009 23:57:48 +0300
branchnew-lib-errors
changeset 217 7728d6ec3abf
parent 158 src/config.c@b5a5df4f4421
permissions -rw-r--r--
nexus.c compiles
#include "config.h"

#include <string.h>
#include <assert.h>

const struct error_list config_errors = ERROR_LIST("config",
    ERROR_TYPE_STRING(  ERR_CONFIG_NAME,        "unknown config option"         ),
    ERROR_TYPE(         ERR_CONFIG_TYPE,        "invalid config type"           ),
    ERROR_TYPE_STRING(  ERR_CONFIG_REQUIRED,    "missing required value"        ),
    ERROR_TYPE_STRING(  ERR_CONFIG_VALUE,       "invalid value"                 ),
    ERROR_TYPE(         ERR_CONFIG_PARAMS,      "invalid number of paramters"   )
);

const struct config_option* config_lookup (const struct config_option *options, const char *name, error_t *err)
{
    const struct config_option *option;

    // find the matching config opt
    for (option = options; option->name; option++) {
        if (strcmp(option->name, name) == 0)
            return option;
    }

    // not found
    SET_ERROR_STR(err, &config_errors, ERR_CONFIG_NAME, name);

    return NULL;
}

int config_params_count (const struct config_option *option)
{
    const struct config_param *param;
    int count = 0;

    // handle each param
    for (param = option->params; param->name && param->type; param++)
        count++;

    return count;
}

/**
 * Parse a raw value for a CONFIG_IRC_CHAN into an irc_chan
 */
static struct irc_chan* config_parse_irc_chan (struct nexus *nexus, char *raw_value, error_t *err)
{
    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, &config_errors, ERR_CONFIG_VALUE, "invalid <network> for CONFIG_IRC_CHAN value");

    if ((channel = strsep(&raw_value, "/")) == NULL)
        JUMP_SET_ERROR_STR(err, &config_errors, ERR_CONFIG_VALUE, "invalid <channel> for CONFIG_IRC_CHAN value");
    
    // extraneous stuff?
    if (raw_value)
        JUMP_SET_ERROR_STR(err, &config_errors, 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, &config_errors, ERR_CONFIG_VALUE, "unknown network/channel name for CONFIG_IRC_CHAN value");
    
    // ok
    return chan;

error:
    return NULL;    
}

err_t config_parse_param (const struct config_param *param, struct nexus *nexus, struct config_value *value, char *raw_value, error_t *err)
{
    // parse the value 
    switch (param->type) {
        case CONFIG_INVALID:
            return SET_ERROR_STR(err, &config_errors, ERR_CONFIG_TYPE, "invalid value for invalid type (too many values?)");

        case CONFIG_STRING:
            // simple!
            value->string = raw_value;
            break;

        case CONFIG_IRC_CHAN:
            // parse the value
            if (!(value->irc_chan = config_parse_irc_chan(nexus, raw_value, err)))
                return error_code(err);

            break;
        
        case CONFIG_USER:
            // fail
            return SET_ERROR_STR(err, &config_errors, ERR_CONFIG_TYPE, "user type can't be parsed");

        default:
            NOT_REACHED(param->type);
    }

    // copy the type
    value->type = param->type;

    // ok
    return SUCCESS; 
}

err_t config_parse (const struct config_option *option, struct nexus *nexus, struct config_value *value, char *raw_value, error_t *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, &config_errors, ERR_CONFIG_PARAMS);
    
    // parse it
    return config_parse_param(param, nexus, value, raw_value, err);
}

err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value values[], error_t *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++) {
        // no value given, or value given as NULL?
        if (!value->type || value->type == CONFIG_NULL) {
            if (param->optional) {
                // optional, so just ignore the value 
                
            } else {
                // required
                JUMP_SET_ERROR_STR(err, &config_errors, ERR_CONFIG_REQUIRED, param->name);

            }

        } else if (param->type != value->type) {
            // invalid type, XXX: also report correct type name?
            JUMP_SET_ERROR(err, &config_errors, ERR_CONFIG_TYPE);

        } else if (param->is_handler) {
            // only applicable for non-optional args
            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(param->type);
            }
            
            // abort on errors
            if (tmp)
                goto error;
        }
        
        // the values list is NULL-terminated, and optional params can be left off
        if (value->type)
            value++;
    }

    // the option's handler?
    if (option->func && option->func(ctx, option, 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[], error_t *err)
{
    const struct config_option *option;

    // no matching option found?
    if (!(option = config_lookup(options, name, err)))
        return error_code(err);
    
    // apply it    
    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, error_t *err)
{
    struct config_value conf_value[] = {
        CONFIG_VALUE_STRING(value),
        CONFIG_VALUE_END,
    };

    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, error_t *err)
{
    struct config_value conf_value[] = {
        CONFIG_VALUE_IRC_CHAN(value),
        CONFIG_VALUE_END,
    };

    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, error_t *err)
{
    const struct config_option *option;
    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[0], raw_value, err))
        return error_code(err);
    
    // apply it
    return config_apply_opt(option, ctx, value, err);
}

const struct config_value* config_get_value (const struct config_option *option, const struct config_value values[], const char *name)
{
    const struct config_param *param;
    const struct config_value *value;
    
    // go through the (param, value) pairs
    for (param = option->params, value = values; param->name && param->type && value->type; param++, value++) {
        if (strcmp(param->name, name) == 0) {
            // not NULL?
            if (value->type != CONFIG_NULL)
                return value;

            else
                return NULL;
        }
    }

    // not found
    return NULL;
}

const char* config_get_string (const struct config_option *option, const struct config_value values[], const char *name)
{
    const struct config_value *value;

    return (value = config_get_value(option, values, name)) ? value->string : NULL;
}

struct irc_chan* config_get_irc_chan (const struct config_option *option, const struct config_value values[], const char *name)
{
    const struct config_value *value;

    return (value = config_get_value(option, values, name)) ? value->irc_chan : NULL;
}

void* config_get_user (const struct config_option *option, const struct config_value values[], const char *name, const struct config_user_type *user_type)
{
    const struct config_value *value;

    return ((value = config_get_value(option, values, name)) && value->user.type == user_type) ? value->user.ptr : NULL;
}