[common] rework option API to allow for custom types

This commit is contained in:
Geoffrey McRae 2019-05-11 20:58:49 +10:00
parent 5b199d8f25
commit 538a6dc08e
3 changed files with 129 additions and 89 deletions

View File

@ -1 +1 @@
a12-180-g51ddb62126+1
a12-181-g5b199d8f25+1

View File

@ -25,45 +25,42 @@ enum OptionType
OPTION_TYPE_NONE = 0,
OPTION_TYPE_INT,
OPTION_TYPE_STRING,
OPTION_TYPE_BOOL
OPTION_TYPE_BOOL,
OPTION_TYPE_CUSTOM
};
struct OptionState;
struct OptionValue
{
enum OptionType type;
union
{
int x_int;
char * x_string;
bool x_bool;
}
v;
// internal state
struct OptionState * state;
};
struct Option;
struct Option
{
const char * module;
const char * name;
const char * description;
struct OptionValue value;
bool (*validator)(struct OptionValue * value, const char ** error);
void (*printHelp)();
enum OptionType type;
union
{
int x_int;
char * x_string;
bool x_bool;
void * x_custom;
}
value;
bool (*parser )(struct Option * opt, const char * str);
bool (*validator)(struct Option * opt, const char ** error);
char * (*toString )(struct Option * opt);
void (*printHelp)();
};
// register an NULL terminated array of options
bool option_register(struct Option options[]);
// lookup the value of an option
struct OptionValue * option_get (const char * module, const char * name);
int option_get_int (const char * module, const char * name);
const char * option_get_string(const char * module, const char * name);
bool option_get_bool (const char * module, const char * name);
struct Option * option_get (const char * module, const char * name);
int option_get_int (const char * module, const char * name);
const char * option_get_string(const char * module, const char * name);
bool option_get_bool (const char * module, const char * name);
// called by the main application to parse the command line arguments
bool option_parse(int argc, char * argv[]);

View File

@ -52,10 +52,51 @@ static struct State state =
.gCount = 0
};
static bool int_parser(struct Option * opt, const char * str)
{
opt->value.x_int = atol(str);
return true;
}
static bool bool_parser(struct Option * opt, const char * str)
{
opt->value.x_bool =
strcmp(str, "1" ) == 0 ||
strcmp(str, "on" ) == 0 ||
strcmp(str, "yes" ) == 0 ||
strcmp(str, "true") == 0;
return true;
}
static bool string_parser(struct Option * opt, const char * str)
{
free(opt->value.x_string);
opt->value.x_string = strdup(str);
return true;
}
static char * int_toString(struct Option * opt)
{
int len = snprintf(NULL, 0, "%d", opt->value.x_int);
char * ret = malloc(len + 1);
sprintf(ret, "%d", opt->value.x_int);
return ret;
}
static char * bool_toString(struct Option * opt)
{
return strdup(opt->value.x_bool ? "yes" : "no");
}
static char * string_toString(struct Option * opt)
{
return strdup(opt->value.x_string);
}
bool option_register(struct Option options[])
{
int new = 0;
for(int i = 0; options[i].value.type != OPTION_TYPE_NONE; ++i)
for(int i = 0; options[i].type != OPTION_TYPE_NONE; ++i)
++new;
state.options = realloc(
@ -63,14 +104,58 @@ bool option_register(struct Option options[])
sizeof(struct Option) * (state.oCount + new)
);
for(int i = 0; options[i].value.type != OPTION_TYPE_NONE; ++i)
for(int i = 0; options[i].type != OPTION_TYPE_NONE; ++i)
{
struct Option * o = &state.options[state.oCount + i];
memcpy(o, &options[i], sizeof(struct Option));
if (!o->parser)
{
switch(o->type)
{
case OPTION_TYPE_INT:
o->parser = int_parser;
break;
case OPTION_TYPE_STRING:
o->parser = string_parser;
break;
case OPTION_TYPE_BOOL:
o->parser = bool_parser;
break;
default:
DEBUG_ERROR("BUG: Non int/string/bool option types must have a parser");
continue;
}
}
if (!o->toString)
{
switch(o->type)
{
case OPTION_TYPE_INT:
o->toString = int_toString;
break;
case OPTION_TYPE_STRING:
o->toString = string_toString;
break;
case OPTION_TYPE_BOOL:
o->toString = bool_toString;
break;
default:
DEBUG_ERROR("BUG: Non int/string/bool option types must implement toString");
continue;
}
}
// ensure the string is locally allocated
if (o->value.type == OPTION_TYPE_STRING)
o->value.v.x_string = strdup(o->value.v.x_string);
if (o->type == OPTION_TYPE_STRING)
o->value.x_string = strdup(o->value.x_string);
// add the option to the correct group for help printout
bool found = false;
@ -121,8 +206,8 @@ void option_free()
for(int i = 0; i < state.oCount; ++i)
{
struct Option * o = &state.options[i];
if (o->value.type == OPTION_TYPE_STRING)
free(o->value.v.x_string);
if (o->type == OPTION_TYPE_STRING)
free(o->value.x_string);
}
free(state.options);
state.options = NULL;
@ -133,33 +218,9 @@ void option_free()
state.gCount = 0;
}
static bool option_set(struct OptionValue * v, const char * value)
static bool option_set(struct Option * opt, const char * value)
{
switch(v->type)
{
case OPTION_TYPE_INT:
v->v.x_int = atol(value);
break;
case OPTION_TYPE_STRING:
free(v->v.x_string);
v->v.x_string = strdup(value);
break;
case OPTION_TYPE_BOOL:
v->v.x_bool =
strcmp(value, "1" ) == 0 ||
strcmp(value, "yes" ) == 0 ||
strcmp(value, "true") == 0 ||
strcmp(value, "on" ) == 0;
break;
default:
DEBUG_ERROR("BUG: Invalid option type, this should never happen");
return false;
}
return true;
return opt->parser(opt, value);
}
bool option_parse(int argc, char * argv[])
@ -203,7 +264,7 @@ bool option_parse(int argc, char * argv[])
continue;
}
if (!option_set(&o->value, value))
if (!option_set(o, value))
{
DEBUG_ERROR("Failed to set the option value");
free(arg);
@ -306,7 +367,7 @@ bool option_load(const char * filename)
case '\n':
if (name)
{
struct OptionValue * o = option_get(module, name);
struct Option * o = option_get(module, name);
if (!o)
DEBUG_WARN("Ignored unknown option %s:%s", module, name);
else
@ -396,7 +457,7 @@ bool option_validate()
struct Option * o = &state.options[i];
const char * error = NULL;
if (o->validator)
if (!o->validator(&o->value, &error))
if (!o->validator(o, &error))
{
printf("\nInvalid value provided to the option: %s:%s\n", o->module, o->name);
@ -430,66 +491,48 @@ void option_print()
for(int i = 0; i < state.groups[g].count; ++i)
{
struct Option * o = state.groups[g].options[i];
printf(" %s:%-*s - %s [", o->module, state.groups[g].pad, o->name, o->description);
switch(o->value.type)
{
case OPTION_TYPE_INT:
printf("%d]\n", o->value.v.x_int);
break;
case OPTION_TYPE_STRING:
printf("%s]\n", o->value.v.x_string);
break;
case OPTION_TYPE_BOOL:
printf("%s]\n", o->value.v.x_bool ? "yes" : "no");
break;
default:
DEBUG_ERROR("BUG: Invalid option type, this should never happen");
assert(false);
break;
}
char * value = o->toString(o);
printf(" %s:%-*s - %s [%s]\n", o->module, state.groups[g].pad, o->name, o->description, value);
free(value);
}
printf("\n");
}
}
struct OptionValue * option_get(const char * module, const char * name)
struct Option * option_get(const char * module, const char * name)
{
for(int i = 0; i < state.oCount; ++i)
{
struct Option * o = &state.options[i];
if ((strcmp(o->module, module) == 0) && (strcmp(o->name, name) == 0))
return &o->value;
return o;
}
return NULL;
}
int option_get_int(const char * module, const char * name)
{
struct OptionValue * o = option_get(module, name);
struct Option * o = option_get(module, name);
if (!o)
return -1;
assert(o->type == OPTION_TYPE_INT);
return o->v.x_int;
return o->value.x_int;
}
const char * option_get_string(const char * module, const char * name)
{
struct OptionValue * o = option_get(module, name);
struct Option * o = option_get(module, name);
if (!o)
return NULL;
assert(o->type == OPTION_TYPE_STRING);
return o->v.x_string;
return o->value.x_string;
}
bool option_get_bool(const char * module, const char * name)
{
struct OptionValue * o = option_get(module, name);
struct Option * o = option_get(module, name);
if (!o)
return false;
assert(o->type == OPTION_TYPE_BOOL);
return o->v.x_bool;
return o->value.x_bool;
}