kobodl/cfgparse.cpp
Ville Lindholm dbc223eb84
Initial commit
Import existing source tree; original VCS history is no longer available.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-28 16:35:31 +03:00

1081 lines
18 KiB
C++

/*(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 <string.h>
#include <ctype.h>
#include <stdlib.h>
#include "cfgparse.h"
#define MAX_CONFIG_LINES 1024
/*----------------------------------------------------------
cfg_key_t
----------------------------------------------------------*/
cfg_key_t::cfg_key_t()
{
next = NULL;
name = "<unnamed>";
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 "<key does not exist!>";
if(table[symbol]->description)
return table[symbol]->description;
else
return "<key has no description>";
}
const char *config_parser_t::name(int symbol)
{
if(check_symbol(symbol) < 0)
return "<key does not exist!>";
if(table[symbol]->name)
return table[symbol]->name;
else
return "<key has no name>";
}
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 "";
}