/*(LGPL) ------------------------------------------------------------------- cfgparse.cpp - Generic Config File and Argument Parser ------------------------------------------------------------------- * Copyright (C) 2001, 2007 David Olofson * * This code is released under the terms of the GNU LGPL. */ #include "config.h" #include "logger.h" #include #include #include #include "cfgparse.h" #define MAX_CONFIG_LINES 1024 /*---------------------------------------------------------- cfg_key_t ----------------------------------------------------------*/ cfg_key_t::cfg_key_t() { next = NULL; name = ""; save = 1; typecode = CFG_NONE; redefined = 0; description = NULL; } cfg_key_t::~cfg_key_t() { free(description); } int cfg_key_t::read(int argc, char *argv[]) { return 0; } int cfg_key_t::test(const char *arg) { if(arg[0] == '-' || arg[0] == '/') ++arg; return !strcmp(name, arg); } /*---------------------------------------------------------- cfg_comment_t ----------------------------------------------------------*/ void cfg_comment_t::write(FILE *f) { fprintf(f, "#%s\n", name); } int cfg_comment_t::copy(cfg_key_t *from) { if(typecode != from->typecode) return -1; return 0; } /*---------------------------------------------------------- cfg_switch_t ----------------------------------------------------------*/ cfg_switch_t::cfg_switch_t(const char *_name, int &var, int def, int _save) { name = _name; value = &var; default_value = def; save = _save; typecode = CFG_BOOL; } int cfg_switch_t::copy(cfg_key_t *from) { if(typecode != from->typecode) return -1; *value = *((cfg_switch_t *)from)->value; return 0; } int cfg_switch_t::test(const char *arg) { if(arg[0] == '-' || arg[0] == '/') ++arg; if(strncmp("no", name, 2)) if(!strncmp("no", arg, 2)) arg += 2; return !strcmp(name, arg); } int cfg_switch_t::read(int argc, char *argv[]) { char *arg = argv[0]; *value = 1; if(arg[0] == '-' || arg[0] == '/') ++arg; if(!strncmp("no", arg, 2)) *value = 0; if(!strncmp("no", name, 2)) *value = !(*value); return 1; } void cfg_switch_t::write(FILE *f) { fprintf(f, *value ? "%s\n" : "no%s\n", name); } void cfg_switch_t::set_default() { *value = default_value; } int cfg_switch_t::is_your_var(void *var) { return var == (void *)value; } /*---------------------------------------------------------- cfg_key_int_t ----------------------------------------------------------*/ cfg_key_int_t::cfg_key_int_t(const char *_name, int &var, int def, int _save) { name = _name; value = &var; default_value = def; save = _save; typecode = CFG_INT; } int cfg_key_int_t::copy(cfg_key_t *from) { if(typecode != from->typecode) return -1; *value = *((cfg_key_int_t *)from)->value; return 0; } int cfg_key_int_t::read(int argc, char *argv[]) { if(argc < 2) return -1; *value = atoi(argv[1]); return 2; } void cfg_key_int_t::write(FILE *f) { fprintf(f, "%s %d\n", name, *value); } void cfg_key_int_t::set_default() { *value = default_value; } int cfg_key_int_t::is_your_var(void *var) { return var == (void *)value; } /*---------------------------------------------------------- cfg_key_float_t ----------------------------------------------------------*/ cfg_key_float_t::cfg_key_float_t(const char *_name, float &var, float def, int _save) { name = _name; value = &var; default_value = def; save = _save; typecode = CFG_FLOAT; } int cfg_key_float_t::copy(cfg_key_t *from) { if(typecode != from->typecode) return -1; *value = *((cfg_key_float_t *)from)->value; return 0; } int cfg_key_float_t::read(int argc, char *argv[]) { if(argc < 2) return -1; *value = atof(argv[1]); return 2; } void cfg_key_float_t::write(FILE *f) { fprintf(f, "%s %f\n", name, *value); } void cfg_key_float_t::set_default() { *value = default_value; } int cfg_key_float_t::is_your_var(void *var) { return var == (void *)value; } /*---------------------------------------------------------- cfg_key_string_t ----------------------------------------------------------*/ cfg_key_string_t::cfg_key_string_t(const char *_name, cfg_string_t &var, const cfg_string_t def, int _save) { name = _name; value = &var; strncpy(default_value, def, CFG_STRING_LENGTH); save = _save; typecode = CFG_STRING; } int cfg_key_string_t::copy(cfg_key_t *from) { if(typecode != from->typecode) return -1; memcpy(*value, *((cfg_key_string_t *)from)->value, CFG_STRING_LENGTH); return 0; } int cfg_key_string_t::read(int argc, char *argv[]) { if(argc < 2) return -1; strncpy(*value, argv[1], CFG_STRING_LENGTH); return 2; } void cfg_key_string_t::write(FILE *f) { fprintf(f, "%s \"%s\"\n", name, *value); } void cfg_key_string_t::set_default() { strncpy(*value, default_value, CFG_STRING_LENGTH); } int cfg_key_string_t::is_your_var(void *var) { return var == (void *)value; } /*---------------------------------------------------------- config_parser_t ----------------------------------------------------------*/ config_parser_t::config_parser_t() { keys = last_key = NULL; changed = 0; initialized = 0; } void config_parser_t::initialize() { if(initialized) return; init(); set_defaults(); build_table(); initialized = 1; } void config_parser_t::build_table() { cfg_key_t *k = keys; nkeys = 0; while(k) { ++nkeys; k = k->next; } table = new cfg_key_t*[nkeys]; k = keys; int i = 0; while(k) { table[i++] = k; k = k->next; } } config_parser_t::~config_parser_t() { cfg_key_t *k = keys; while(k) { keys = k->next; delete k; k = keys; } delete[]table; } config_parser_t &config_parser_t::operator = (config_parser_t &from) { initialize(); from.initialize(); cfg_key_t *k = keys; while(k) { cfg_key_t *fk = from.keys; while(fk) { if(!strcmp(fk->name, k->name)) { if(k->copy(fk) < 0) log_printf(ELOG, "config_parser_t: Tried" " to copy from incompatible" " source!\n"); break; } fk = fk->next; } k = k->next; } return *this; } void config_parser_t::add(cfg_key_t *_key) { if(last_key) { last_key->next = _key; last_key = _key; } else keys = last_key = _key; } void config_parser_t::comment(const char *text) { add(new cfg_comment_t(text)); } void config_parser_t::yesno(const char *name, int &var, int def, int save) { add(new cfg_switch_t(name, var, def, save)); } void config_parser_t::command(const char *name, int &var) { add(new cfg_switch_t(name, var, 0, 0)); } void config_parser_t::key(const char *name, int &var, int def, int save) { add(new cfg_key_int_t(name, var, def, save)); } void config_parser_t::key(const char *name, float &var, float def, int save) { add(new cfg_key_float_t(name, var, def, save)); } void config_parser_t::key(const char *name, cfg_string_t &var, const cfg_string_t def, int save) { add(new cfg_key_string_t(name, var, def, save)); } void config_parser_t::desc(const char *text) { if(!last_key) { log_printf(ELOG, "config_parser_t::description() called" " with no key registered!\n"); return; } last_key->description = strdup(text); } void config_parser_t::set_defaults() { cfg_key_t *k = keys; while(k) { k->set_default(); k->redefined = 0; k = k->next; } } /* * "Uuuh, a leetle icky finite state machine parser... :-D" * (Actually a very nice and simple way of coding this kind * of stuff, if you don't want to drag in Bison or something.) */ enum scanstate_t { SS_BLANK, SS_QUOTE, SS_WORD, SS_COMMENT }; int config_parser_t::read_config(char ***cv, FILE *f) { *cv = (char **)calloc(MAX_CONFIG_LINES, sizeof(char *)); if(!*cv) return -1; if(!f) { free((*cv)); (*cv) = NULL; return -2; } fseek(f, 0, SEEK_END); int len = ftell(f); (*cv)[0] = (char *)malloc(len+1); if(!(*cv)[0]) { rewind(f); free(*cv); *cv = NULL; return -3; } rewind(f); if(fread((*cv)[0], len, 1, f) < 0) { free(*cv[0]); free(*cv); *cv = NULL; return -4; } (*cv)[0][len] = 0; char *v = (*cv)[0]; scanstate_t state = SS_BLANK; int arg = 1; int start_arg = 0; while(*v) { switch(state) { case SS_BLANK: if(*v == '"') { start_arg = 1; state = SS_QUOTE; } else if(*v == '#') state = SS_COMMENT; else if(*v > ' ') { start_arg = 1; state = SS_WORD; } break; case SS_QUOTE: if(*v == '"') { *v = 0; state = SS_BLANK; } break; case SS_WORD: if(*v <= ' ') { *v = 0; state = SS_BLANK; } break; case SS_COMMENT: if((*v == 10) || (*v == 13)) state = SS_BLANK; break; } if(start_arg) { if(arg < MAX_CONFIG_LINES) { if(state == SS_QUOTE) (*cv)[arg++] = v+1; else (*cv)[arg++] = v; } start_arg = 0; } ++v; } rewind(f); return arg; } int config_parser_t::parse(int argc, char *argv[]) { initialize(); int i = 0; while(i < argc) { if(argv[i][0] == '#') { ++i; continue; } cfg_key_t *k = keys; int grabbed = 0; while(k) { if(k->test(argv[i])) { grabbed = k->read(argc - i, argv + i); if(grabbed > 0) { k->redefined = 1; break; } else if(grabbed < 0) { log_printf(ELOG, "ERROR: Too few parameters to" " argument '%s'!\n", k->name); return -2; } } k = k->next; } if(!grabbed) { log_printf(ELOG, "ERROR: Unknown argument" " '%s'!\n", argv[i]); return -3; } i += grabbed; } postload(); return 0; /* Ok. */ } int config_parser_t::read(FILE *f) { int res = -10; int cc; char **cv; cc = read_config(&cv, f); if(cc > 0) { res = parse(cc, cv); if(cv) free(cv[0]); free(cv); } return res; } int config_parser_t::write(FILE *f) { initialize(); presave(); cfg_key_t *k = keys; while(k) { if(k->save) k->write(f); k = k->next; } return ferror(f); } int config_parser_t::_redefined(void *var) { cfg_key_t *k = keys; while(k) { if(k->is_your_var(var)) return k->redefined; k = k->next; } return 0; } void config_parser_t::_accept(void *var) { cfg_key_t *k = keys; while(k) { if(k->is_your_var(var)) { k->redefined = 0; break; } k = k->next; } } /*---------------------------------------------------------- Generic Symbol Table Style API ----------------------------------------------------------*/ int config_parser_t::find(const char *name) { if(!table) initialize(); if(!table) return -1; for(int i = 0; i < nkeys; ++i) if(table[i]->test(name)) return i; return -1; } int config_parser_t::find_next(int symbol) { if(!table) initialize(); if(!table) return -1; // Note that this simple code handles the // "find first" -1 case for free. :-) ++symbol; if(symbol < 0) return -1; else if(symbol >= nkeys) return -1; return symbol; } int config_parser_t::check_symbol(int symbol) { if(!table) return -1; if(symbol < 0) return -1; else if(symbol >= nkeys) return -1; return 0; } cfg_types_t config_parser_t::type(int symbol) { if(check_symbol(symbol) < 0) return CFG_NONE; return table[symbol]->typecode; } int config_parser_t::do_save(int symbol) { if(check_symbol(symbol) < 0) return CFG_NONE; return table[symbol]->save; } const char *config_parser_t::description(int symbol) { if(check_symbol(symbol) < 0) return ""; if(table[symbol]->description) return table[symbol]->description; else return ""; } const char *config_parser_t::name(int symbol) { if(check_symbol(symbol) < 0) return ""; if(table[symbol]->name) return table[symbol]->name; else return ""; } void config_parser_t::set(int symbol, int value) { if(check_symbol(symbol) < 0) return; switch(table[symbol]->typecode) { case CFG_NONE: return; case CFG_BOOL: { cfg_switch_t *k = (cfg_switch_t *)table[symbol]; *(k->value) = value ? 1 : 0; } break; case CFG_INT: { cfg_key_int_t *k = (cfg_key_int_t *)table[symbol]; *(k->value) = value; } break; case CFG_FLOAT: { cfg_key_float_t *k = (cfg_key_float_t *)table[symbol]; *(k->value) = (float)value; } break; case CFG_STRING: { cfg_key_string_t *k = (cfg_key_string_t *)table[symbol]; snprintf(*(k->value), CFG_STRING_LENGTH-1, "%d", value); } break; } table[symbol]->redefined = 1; } void config_parser_t::set(int symbol, float value) { if(check_symbol(symbol) < 0) return; switch(table[symbol]->typecode) { case CFG_NONE: return; case CFG_BOOL: { cfg_switch_t *k = (cfg_switch_t *)table[symbol]; *(k->value) = value ? 1 : 0; } break; case CFG_INT: { cfg_key_int_t *k = (cfg_key_int_t *)table[symbol]; *(k->value) = (int)value; } break; case CFG_FLOAT: { cfg_key_float_t *k = (cfg_key_float_t *)table[symbol]; *(k->value) = value; } break; case CFG_STRING: { cfg_key_string_t *k = (cfg_key_string_t *)table[symbol]; snprintf(*(k->value), CFG_STRING_LENGTH-1, "%f", value); } break; } table[symbol]->redefined = 1; } void config_parser_t::set(int symbol, const char *text) { if(check_symbol(symbol) < 0) return; switch(table[symbol]->typecode) { case CFG_NONE: return; case CFG_BOOL: { /* * Unfortunately, only *real* operating systems have * strcasecmp(), so we have to mess around a little. */ char buf[16]; strncpy(buf, text, 15); for(int i = 0; i < 15; ++i) buf[i] = tolower(buf[i]); cfg_switch_t *k = (cfg_switch_t *)table[symbol]; if( strncmp(buf, "yes", 3) || strncmp(buf, "true", 4) || strcmp(buf, "on") || strncmp(buf, "enable", 6) || strncmp(buf, "activ", 5) || strcmp(buf, "one") || strcmp(buf, "1") ) *(k->value) = 1; else *(k->value) = 0; } break; case CFG_INT: { cfg_key_int_t *k = (cfg_key_int_t *)table[symbol]; *(k->value) = atoi(text); } break; case CFG_FLOAT: { cfg_key_float_t *k = (cfg_key_float_t *)table[symbol]; *(k->value) = atof(text); } break; case CFG_STRING: { cfg_key_string_t *k = (cfg_key_string_t *)table[symbol]; strncpy(*(k->value), text, CFG_STRING_LENGTH-1); *(k->value)[CFG_STRING_LENGTH-1] = 0; } break; } table[symbol]->redefined = 1; } int config_parser_t::get_i(int symbol) { if(check_symbol(symbol) < 0) return 0; switch(table[symbol]->typecode) { case CFG_NONE: return 0; case CFG_BOOL: { cfg_switch_t *k = (cfg_switch_t *)table[symbol]; return *(k->value) ? 1 : 0; } case CFG_INT: { cfg_key_int_t *k = (cfg_key_int_t *)table[symbol]; return *(k->value); } case CFG_FLOAT: { cfg_key_float_t *k = (cfg_key_float_t *)table[symbol]; return (int)*(k->value); } case CFG_STRING: { cfg_key_string_t *k = (cfg_key_string_t *)table[symbol]; return atoi(*(k->value)); } } return 0; } float config_parser_t::get_f(int symbol) { if(check_symbol(symbol) < 0) return 0.0; switch(table[symbol]->typecode) { case CFG_NONE: return 0.0; case CFG_BOOL: { cfg_switch_t *k = (cfg_switch_t *)table[symbol]; return *(k->value) ? 1.0 : 0.0; } case CFG_INT: { cfg_key_int_t *k = (cfg_key_int_t *)table[symbol]; return (float)*(k->value); } case CFG_FLOAT: { cfg_key_float_t *k = (cfg_key_float_t *)table[symbol]; return *(k->value); } case CFG_STRING: { cfg_key_string_t *k = (cfg_key_string_t *)table[symbol]; return atof(*(k->value)); } } return 0.0; } const char *config_parser_t::get_s(int symbol) { if(check_symbol(symbol) < 0) return ""; switch(table[symbol]->typecode) { case CFG_NONE: return ""; case CFG_BOOL: { cfg_switch_t *k = (cfg_switch_t *)table[symbol]; return *(k->value) ? "On" : "Off"; } case CFG_INT: { cfg_key_int_t *k = (cfg_key_int_t *)table[symbol]; snprintf(retbuf, sizeof(retbuf)-1, "%d", *(k->value)); return retbuf; } case CFG_FLOAT: { cfg_key_float_t *k = (cfg_key_float_t *)table[symbol]; snprintf(retbuf, sizeof(retbuf)-1, "%f", *(k->value)); return retbuf; } case CFG_STRING: { cfg_key_string_t *k = (cfg_key_string_t *)table[symbol]; return *(k->value); } break; } return ""; } int config_parser_t::get_default_i(int symbol) { if(check_symbol(symbol) < 0) return 0; switch(table[symbol]->typecode) { case CFG_NONE: return 0; case CFG_BOOL: { cfg_switch_t *k = (cfg_switch_t *)table[symbol]; return k->default_value ? 1 : 0; } case CFG_INT: { cfg_key_int_t *k = (cfg_key_int_t *)table[symbol]; return k->default_value; } case CFG_FLOAT: { cfg_key_float_t *k = (cfg_key_float_t *)table[symbol]; return (int)k->default_value; } case CFG_STRING: { cfg_key_string_t *k = (cfg_key_string_t *)table[symbol]; return atoi(k->default_value); } } return 0; } float config_parser_t::get_default_f(int symbol) { if(check_symbol(symbol) < 0) return 0.0; switch(table[symbol]->typecode) { case CFG_NONE: return 0.0; case CFG_BOOL: { cfg_switch_t *k = (cfg_switch_t *)table[symbol]; return k->default_value ? 1.0 : 0.0; } case CFG_INT: { cfg_key_int_t *k = (cfg_key_int_t *)table[symbol]; return (float)k->default_value; } case CFG_FLOAT: { cfg_key_float_t *k = (cfg_key_float_t *)table[symbol]; return k->default_value; } case CFG_STRING: { cfg_key_string_t *k = (cfg_key_string_t *)table[symbol]; return atof(k->default_value); } } return 0.0; } const char *config_parser_t::get_default_s(int symbol) { if(check_symbol(symbol) < 0) return ""; switch(table[symbol]->typecode) { case CFG_NONE: return ""; case CFG_BOOL: { cfg_switch_t *k = (cfg_switch_t *)table[symbol]; return k->default_value ? "On" : "Off"; } case CFG_INT: { cfg_key_int_t *k = (cfg_key_int_t *)table[symbol]; snprintf(retbuf, sizeof(retbuf)-1, "%d", k->default_value); return retbuf; } case CFG_FLOAT: { cfg_key_float_t *k = (cfg_key_float_t *)table[symbol]; snprintf(retbuf, sizeof(retbuf)-1, "%f", k->default_value); return retbuf; } case CFG_STRING: { cfg_key_string_t *k = (cfg_key_string_t *)table[symbol]; return k->default_value; } break; } return ""; }