[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,34 +25,31 @@ enum OptionType
OPTION_TYPE_NONE = 0, OPTION_TYPE_NONE = 0,
OPTION_TYPE_INT, OPTION_TYPE_INT,
OPTION_TYPE_STRING, OPTION_TYPE_STRING,
OPTION_TYPE_BOOL OPTION_TYPE_BOOL,
OPTION_TYPE_CUSTOM
}; };
struct OptionState; struct Option;
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 * module;
const char * name; const char * name;
const char * description; const char * description;
struct OptionValue value;
bool (*validator)(struct OptionValue * value, const char ** error); 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)(); void (*printHelp)();
}; };
@ -60,7 +57,7 @@ struct Option
bool option_register(struct Option options[]); bool option_register(struct Option options[]);
// lookup the value of an option // lookup the value of an option
struct OptionValue * option_get (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); int option_get_int (const char * module, const char * name);
const char * option_get_string(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); bool option_get_bool (const char * module, const char * name);

View File

@ -52,10 +52,51 @@ static struct State state =
.gCount = 0 .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[]) bool option_register(struct Option options[])
{ {
int new = 0; 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; ++new;
state.options = realloc( state.options = realloc(
@ -63,14 +104,58 @@ bool option_register(struct Option options[])
sizeof(struct Option) * (state.oCount + new) 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]; struct Option * o = &state.options[state.oCount + i];
memcpy(o, &options[i], sizeof(struct Option)); 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 // ensure the string is locally allocated
if (o->value.type == OPTION_TYPE_STRING) if (o->type == OPTION_TYPE_STRING)
o->value.v.x_string = strdup(o->value.v.x_string); o->value.x_string = strdup(o->value.x_string);
// add the option to the correct group for help printout // add the option to the correct group for help printout
bool found = false; bool found = false;
@ -121,8 +206,8 @@ void option_free()
for(int i = 0; i < state.oCount; ++i) for(int i = 0; i < state.oCount; ++i)
{ {
struct Option * o = &state.options[i]; struct Option * o = &state.options[i];
if (o->value.type == OPTION_TYPE_STRING) if (o->type == OPTION_TYPE_STRING)
free(o->value.v.x_string); free(o->value.x_string);
} }
free(state.options); free(state.options);
state.options = NULL; state.options = NULL;
@ -133,33 +218,9 @@ void option_free()
state.gCount = 0; 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) return opt->parser(opt, value);
{
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;
} }
bool option_parse(int argc, char * argv[]) bool option_parse(int argc, char * argv[])
@ -203,7 +264,7 @@ bool option_parse(int argc, char * argv[])
continue; continue;
} }
if (!option_set(&o->value, value)) if (!option_set(o, value))
{ {
DEBUG_ERROR("Failed to set the option value"); DEBUG_ERROR("Failed to set the option value");
free(arg); free(arg);
@ -306,7 +367,7 @@ bool option_load(const char * filename)
case '\n': case '\n':
if (name) if (name)
{ {
struct OptionValue * o = option_get(module, name); struct Option * o = option_get(module, name);
if (!o) if (!o)
DEBUG_WARN("Ignored unknown option %s:%s", module, name); DEBUG_WARN("Ignored unknown option %s:%s", module, name);
else else
@ -396,7 +457,7 @@ bool option_validate()
struct Option * o = &state.options[i]; struct Option * o = &state.options[i];
const char * error = NULL; const char * error = NULL;
if (o->validator) 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); 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) for(int i = 0; i < state.groups[g].count; ++i)
{ {
struct Option * o = state.groups[g].options[i]; struct Option * o = state.groups[g].options[i];
printf(" %s:%-*s - %s [", o->module, state.groups[g].pad, o->name, o->description); char * value = o->toString(o);
printf(" %s:%-*s - %s [%s]\n", o->module, state.groups[g].pad, o->name, o->description, value);
switch(o->value.type) free(value);
{
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;
}
} }
printf("\n"); 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) for(int i = 0; i < state.oCount; ++i)
{ {
struct Option * o = &state.options[i]; struct Option * o = &state.options[i];
if ((strcmp(o->module, module) == 0) && (strcmp(o->name, name) == 0)) if ((strcmp(o->module, module) == 0) && (strcmp(o->name, name) == 0))
return &o->value; return o;
} }
return NULL; return NULL;
} }
int option_get_int(const char * module, const char * name) 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) if (!o)
return -1; return -1;
assert(o->type == OPTION_TYPE_INT); 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) 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) if (!o)
return NULL; return NULL;
assert(o->type == OPTION_TYPE_STRING); 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) 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) if (!o)
return false; return false;
assert(o->type == OPTION_TYPE_BOOL); assert(o->type == OPTION_TYPE_BOOL);
return o->v.x_bool; return o->value.x_bool;
} }