Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Pancake2/PancakeConfiguration.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
601 lines (472 sloc)
18.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "PancakeConfiguration.h" | |
#include "PancakeLogger.h" | |
PancakeConfigurationStructure *PancakeConfiguration; | |
static PancakeConfigurationScope *rootScope; | |
static Byte *configurationTypeNames[] = { | |
"none", | |
"group", | |
"integer", | |
"64-bit integer", | |
"float", | |
"string", | |
"boolean (true/false)", | |
"array", | |
"list" | |
}; | |
void PancakeConfigurationInitialize() { | |
PancakeConfiguration = PancakeAllocate(sizeof(PancakeConfigurationStructure)); | |
PancakeConfiguration->wrapper = PancakeAllocate(sizeof(config_t)); | |
PancakeConfiguration->groups = NULL; | |
PancakeConfiguration->settings = NULL; | |
} | |
STATIC UByte PancakeConfigurationCheckValue(PancakeConfigurationScope *scope, PancakeConfigurationGroup *parent, config_setting_t *configSetting) { | |
switch(configSetting->type) { | |
case CONFIG_TYPE_GROUP: { | |
PancakeConfigurationGroup *group = NULL; | |
UInt16 i; | |
config_setting_t *setting; | |
if(configSetting->name != NULL) { | |
HASH_FIND(hh, parent ? parent->children : PancakeConfiguration->groups, configSetting->name, strlen(configSetting->name), group); | |
// Fail if group does not exist | |
if(group == NULL) { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to parse configuration: Unknown group %s in %s on line %i", configSetting->name, configSetting->file, configSetting->line); | |
return 0; | |
} | |
// Call hook if available | |
if(group->hook && !group->hook(PANCAKE_CONFIGURATION_INIT, configSetting, &scope)) { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to parse configuration: Hook failed for %s in %s on line %i", configSetting->name, configSetting->file, configSetting->line); | |
return 0; | |
} | |
} | |
// Iterate through settings in group | |
for(i = 0, setting = config_setting_get_elem(configSetting, i); | |
setting != NULL; | |
i++, setting = config_setting_get_elem(configSetting, i)) { | |
if(!PancakeConfigurationCheckValue(scope, group, setting)) { | |
return 0; | |
} | |
} | |
} break; | |
default: { | |
PancakeConfigurationSetting *setting; | |
PancakeConfigurationScopeValue *scopedValue; | |
HASH_FIND(hh, parent ? parent->settings : PancakeConfiguration->settings, configSetting->name, strlen(configSetting->name), setting); | |
// Fail if setting does not exist | |
if(setting == NULL) { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to parse configuration: Unknown setting %s in %s on line %i", configSetting->name, configSetting->file, configSetting->line); | |
return 0; | |
} | |
// Resolve copy of setting | |
if(setting->type == CONFIG_TYPE_COPY) { | |
setting = ((PancakeConfigurationSettingCopy*) setting)->setting; | |
} | |
// Check setting type | |
if(setting->type != configSetting->type && configSetting->type != CONFIG_TYPE_ANY) { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to parse configuration: Bad value for %s: expected %s, got %s in %s on line %i", configSetting->name, configurationTypeNames[setting->type], configurationTypeNames[configSetting->type], configSetting->file, configSetting->line); | |
return 0; | |
} | |
// Call hook if available | |
if(setting->hook && !setting->hook(PANCAKE_CONFIGURATION_INIT, configSetting, &scope)) { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to parse configuration: Hook failed for %s in %s on line %i", configSetting->name, configSetting->file, configSetting->line); | |
return 0; | |
} | |
// Iterate through list | |
if(configSetting->type == CONFIG_TYPE_LIST && setting->listGroup) { | |
UInt16 i; | |
config_setting_t *childSetting; | |
for(i = 0, childSetting = config_setting_get_elem(configSetting, i); | |
childSetting != NULL; | |
i++, childSetting = config_setting_get_elem(configSetting, i)) { | |
config_setting_t *groupChildSetting; | |
UInt16 i2; | |
// Issue error when value is not a group | |
if(childSetting->type != CONFIG_TYPE_GROUP) { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to parse configuration: Bad value for %s in %s on line %i", configSetting->name, childSetting->file, childSetting->line); | |
return 0; | |
} | |
// Call hook if available | |
if(setting->listGroup->hook && !setting->listGroup->hook(PANCAKE_CONFIGURATION_INIT, childSetting, &scope)) { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to parse configuration: Hook failed for %s in %s on line %i", configSetting->name, childSetting->file, childSetting->line); | |
return 0; | |
} | |
for(i2 = 0, groupChildSetting = config_setting_get_elem(childSetting, i2); | |
groupChildSetting != NULL; | |
i2++, groupChildSetting = config_setting_get_elem(childSetting, i2)) { | |
if(!PancakeConfigurationCheckValue(scope, setting->listGroup, groupChildSetting)) { | |
return 0; | |
} | |
} | |
} | |
} | |
if(setting->valuePtr != NULL) { | |
// Add value to scope | |
scopedValue = PancakeAllocate(sizeof(PancakeConfigurationScopeValue)); | |
scopedValue->setting = setting; | |
scopedValue->value = configSetting->value; | |
DL_APPEND(scope->values, scopedValue); | |
if(!scope->isRootScope) { | |
setting->haveScopedValue = 1; | |
} | |
} | |
setting->haveValue = 1; | |
} break; | |
} | |
return 1; | |
} | |
STATIC void PancakeConfigurationLoadGroupDefaultValues(PancakeConfigurationGroup *parent) { | |
PancakeConfigurationSetting *setting; | |
PancakeConfigurationGroup *group; | |
// Fetch default values of settings in group | |
for(setting = parent ? parent->settings : PancakeConfiguration->settings; | |
setting != NULL; | |
setting = setting->hh.next) { | |
// Ignore copies | |
if(setting->type == CONFIG_TYPE_COPY) { | |
continue; | |
} | |
if((!setting->haveValue || setting->haveScopedValue) && setting->valuePtr != NULL) { | |
PancakeConfigurationScopeValue *value; | |
// Make sure not to add a value twice to root scope | |
DL_FOREACH(rootScope->values, value) { | |
if(value->setting == setting) { | |
goto skip; | |
} | |
} | |
value = PancakeAllocate(sizeof(PancakeConfigurationScopeValue)); | |
value->setting = setting; | |
value->value = setting->defaultValue; | |
DL_APPEND(rootScope->values, value); | |
skip:; | |
} | |
} | |
// Fetch nested groups | |
for(group = parent ? parent->children : PancakeConfiguration->groups; | |
group != NULL; | |
group = group->hh.next) { | |
PancakeConfigurationLoadGroupDefaultValues(group); | |
} | |
} | |
UByte PancakeConfigurationLoad() { | |
PancakeConfigurationScopeValue *value, *tmp; | |
config_init(PancakeConfiguration->wrapper); | |
if(!config_read_file(PancakeConfiguration->wrapper, PANCAKE_CONFIG_PATH)) { | |
const Byte *file = config_error_file(PancakeConfiguration->wrapper); | |
const Byte *text = config_error_text(PancakeConfiguration->wrapper); | |
Int32 line = config_error_line(PancakeConfiguration->wrapper); | |
if(file != NULL) { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to parse configuration: %s in %s on line %i", text, file, line); | |
} else { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to parse configuration: %s", text); | |
} | |
return 0; | |
} | |
// Create root configuration scope | |
rootScope = PancakeConfigurationAddScope(); | |
rootScope->isRootScope = 1; | |
// Check the configuration | |
if(!PancakeConfigurationCheckValue(rootScope, NULL, config_root_setting(PancakeConfiguration->wrapper))) { | |
return 0; | |
} | |
// Load default values into root scope where necessary | |
PancakeConfigurationLoadGroupDefaultValues(NULL); | |
// Load root scope | |
PancakeConfigurationActivateScope(rootScope); | |
// Remove unnecessary elements from root scope to speed up unscoping | |
DL_FOREACH_SAFE(rootScope->values, value, tmp) { | |
if(!value->setting->haveValue || !value->setting->haveScopedValue) { | |
DL_DELETE(rootScope->values, value); | |
PancakeFree(value); | |
} | |
} | |
return 1; | |
} | |
STATIC void PancakeConfigurationDestroyValue(PancakeConfigurationGroup *parent, config_setting_t *configSetting) { | |
switch(configSetting->type) { | |
case CONFIG_TYPE_GROUP: { | |
PancakeConfigurationGroup *group = NULL; | |
UInt16 i; | |
config_setting_t *setting; | |
if(configSetting->name != NULL) { | |
HASH_FIND(hh, parent ? parent->children : PancakeConfiguration->groups, configSetting->name, strlen(configSetting->name), group); | |
} | |
// Iterate through settings in group | |
for(i = 0, setting = config_setting_get_elem(configSetting, i); | |
setting != NULL; | |
i++, setting = config_setting_get_elem(configSetting, i)) { | |
PancakeConfigurationDestroyValue(group, setting); | |
} | |
// Call destruction hook if available | |
if(group && group->hook) { | |
group->hook(PANCAKE_CONFIGURATION_DTOR, configSetting, NULL); | |
} | |
} break; | |
default: { | |
PancakeConfigurationSetting *setting; | |
HASH_FIND(hh, parent ? parent->settings : PancakeConfiguration->settings, configSetting->name, strlen(configSetting->name), setting); | |
// Resolve copy of setting | |
if(setting->type == CONFIG_TYPE_COPY) { | |
setting = ((PancakeConfigurationSettingCopy*) setting)->setting; | |
} | |
// Destroy list group | |
if(setting->type == CONFIG_TYPE_LIST && setting->listGroup) { | |
UInt16 i; | |
config_setting_t *childSetting; | |
for(i = 0, childSetting = config_setting_get_elem(configSetting, i); | |
childSetting != NULL; | |
i++, childSetting = config_setting_get_elem(configSetting, i)) { | |
config_setting_t *groupChildSetting; | |
UInt16 i2; | |
for(i2 = 0, groupChildSetting = config_setting_get_elem(childSetting, i2); | |
groupChildSetting != NULL; | |
i2++, groupChildSetting = config_setting_get_elem(childSetting, i2)) { | |
PancakeConfigurationDestroyValue(setting->listGroup, groupChildSetting); | |
} | |
// Call hook if available | |
if(setting->listGroup->hook) { | |
setting->listGroup->hook(PANCAKE_CONFIGURATION_DTOR, childSetting, NULL); | |
} | |
} | |
} | |
// Call destruction hook if available | |
if(setting->hook) { | |
setting->hook(PANCAKE_CONFIGURATION_DTOR, configSetting, NULL); | |
} | |
} break; | |
} | |
} | |
void PancakeConfigurationUnload() { | |
PancakeConfigurationDestroyValue(NULL, config_root_setting(PancakeConfiguration->wrapper)); | |
config_destroy(PancakeConfiguration->wrapper); | |
PancakeConfigurationDestroyScope(rootScope); | |
} | |
STATIC void PancakeConfigurationDestroyGroup(PancakeConfigurationGroup *group) { | |
PancakeConfigurationSetting *setting, *tmp; | |
PancakeConfigurationGroup *child, *tmp2; | |
if(group->isCopy) { | |
return; | |
} | |
HASH_ITER(hh, group->settings, setting, tmp) { | |
HASH_DEL(group->settings, setting); | |
if(setting->type != CONFIG_TYPE_COPY && setting->listGroup) { | |
PancakeConfigurationDestroyGroup(setting->listGroup); | |
PancakeFree(setting->listGroup); | |
} | |
PancakeFree(setting); | |
} | |
HASH_ITER(hh, group->children, child, tmp2) { | |
HASH_DEL(group->children, child); | |
PancakeConfigurationDestroyGroup(child); | |
PancakeFree(child); | |
} | |
} | |
void PancakeConfigurationDestroy() { | |
PancakeConfigurationGroup *group, *tmp; | |
PancakeConfigurationSetting *setting, *tmp2; | |
HASH_ITER(hh, PancakeConfiguration->groups, group, tmp) { | |
PancakeConfigurationDestroyGroup(group); | |
HASH_DEL(PancakeConfiguration->groups, group); | |
PancakeFree(group); | |
} | |
HASH_ITER(hh, PancakeConfiguration->settings, setting, tmp2) { | |
HASH_DEL(PancakeConfiguration->settings, setting); | |
if(setting->type != CONFIG_TYPE_COPY && setting->listGroup) { | |
PancakeConfigurationDestroyGroup(setting->listGroup); | |
PancakeFree(setting->listGroup); | |
} | |
PancakeFree(setting); | |
} | |
PancakeFree(PancakeConfiguration->wrapper); | |
PancakeFree(PancakeConfiguration); | |
} | |
PANCAKE_API PancakeConfigurationGroup *PancakeConfigurationAddGroup(PancakeConfigurationGroup *parent, String name, PancakeConfigurationHook hook) { | |
PancakeConfigurationGroup *group = PancakeAllocate(sizeof(PancakeConfigurationGroup)); | |
group->name = name; | |
group->settings = NULL; | |
group->children = NULL; | |
group->hook = hook; | |
group->isCopy = 0; | |
if(parent == NULL) { | |
HASH_ADD_KEYPTR(hh, PancakeConfiguration->groups, name.value, name.length, group); | |
} else { | |
HASH_ADD_KEYPTR(hh, parent->children, name.value, name.length, group); | |
} | |
return group; | |
} | |
PANCAKE_API void PancakeConfigurationAddGroupToGroup(PancakeConfigurationGroup *parent, PancakeConfigurationGroup *child) { | |
// We must copy the group in order to have it in two groups | |
PancakeConfigurationGroup *copy = PancakeConfigurationAddGroup(parent, child->name, child->hook); | |
copy->children = child->children; | |
copy->settings = child->settings; | |
copy->isCopy = 1; | |
} | |
PANCAKE_API void PancakeConfigurationAddSettingToGroup(PancakeConfigurationGroup *parent, PancakeConfigurationSetting *child) { | |
PancakeConfigurationSettingCopy *copy = PancakeAllocate(sizeof(PancakeConfigurationSettingCopy)); | |
copy->type = CONFIG_TYPE_COPY; | |
copy->setting = child; | |
if(parent == NULL) { | |
HASH_ADD_KEYPTR(hh, PancakeConfiguration->settings, child->name.value, child->name.length, (PancakeConfigurationSetting*) copy); | |
} else { | |
HASH_ADD_KEYPTR(hh, parent->settings, child->name.value, child->name.length, (PancakeConfigurationSetting*) copy); | |
} | |
} | |
PANCAKE_API PancakeConfigurationGroup *PancakeConfigurationLookupGroup(PancakeConfigurationGroup *parent, String name) { | |
for(parent = parent ? parent->children : PancakeConfiguration->groups; parent != NULL; parent = parent->hh.next) { | |
PancakeConfigurationGroup *group; | |
if(name.length == parent->name.length && !strcmp(name.value, parent->name.value)) { | |
return parent; | |
} | |
if(group = PancakeConfigurationLookupGroup(parent, name)) { | |
return group; | |
} | |
} | |
return NULL; | |
} | |
PANCAKE_API PancakeConfigurationSetting *PancakeConfigurationLookupSetting(PancakeConfigurationGroup *parent, String name) { | |
PancakeConfigurationSetting *setting; | |
HASH_FIND(hh, parent ? parent->settings : PancakeConfiguration->settings, name.value, name.length, setting); | |
return setting; | |
} | |
PANCAKE_API PancakeConfigurationSetting *PancakeConfigurationAddSetting(PancakeConfigurationGroup *group, String name, UByte type, void *valuePtr, UInt8 valueSize, config_value_t defaultValue, PancakeConfigurationHook hook) { | |
PancakeConfigurationSetting *setting = PancakeAllocate(sizeof(PancakeConfigurationSetting)); | |
setting->name = name; | |
setting->type = type; | |
setting->hook = hook; | |
setting->valuePtr = valuePtr; | |
setting->valueSize = valueSize; | |
setting->defaultValue = defaultValue; | |
setting->haveScopedValue = 0; | |
setting->haveValue = 0; | |
setting->listGroup = NULL; | |
if(group == NULL) { | |
HASH_ADD_KEYPTR(hh, PancakeConfiguration->settings, name.value, name.length, setting); | |
} else { | |
HASH_ADD_KEYPTR(hh, group->settings, name.value, name.length, setting); | |
} | |
return setting; | |
} | |
PANCAKE_API PancakeConfigurationGroup *PancakeConfigurationListGroup(PancakeConfigurationSetting *setting, PancakeConfigurationHook hook) { | |
PancakeConfigurationGroup *group = PancakeAllocate(sizeof(PancakeConfigurationGroup)); | |
group->name = (String) {}; | |
group->settings = NULL; | |
group->children = NULL; | |
group->hook = hook; | |
group->isCopy = 0; | |
setting->listGroup = group; | |
return group; | |
} | |
/* Configuration scope functions */ | |
PANCAKE_API PancakeConfigurationScope *PancakeConfigurationAddScope() { | |
PancakeConfigurationScope *scope = PancakeAllocate(sizeof(PancakeConfigurationScope)); | |
scope->values = NULL; | |
scope->isRootScope = 0; | |
scope->data = NULL; | |
return scope; | |
} | |
PANCAKE_API extern inline void PancakeConfigurationActivateScope(PancakeConfigurationScope *scope) { | |
PancakeConfigurationScopeValue *value; | |
// Copy scope values into configuration structures | |
DL_FOREACH(scope->values, value) { | |
PancakeAssert(value->setting->valuePtr != NULL); | |
memcpy(value->setting->valuePtr, &value->value, value->setting->valueSize); | |
} | |
} | |
PANCAKE_API extern inline void PancakeConfigurationUnscope() { | |
PancakeConfigurationScopeValue *value; | |
// Reset configuration by setting values from root scope | |
DL_FOREACH(rootScope->values, value) { | |
PancakeAssert(value->setting->valuePtr != NULL); | |
memcpy(value->setting->valuePtr, &value->value, value->setting->valueSize); | |
} | |
} | |
PANCAKE_API void PancakeConfigurationDestroyScope(PancakeConfigurationScope *scope) { | |
PancakeConfigurationScopeValue *value, *tmp; | |
// Destroy list and free memory | |
DL_FOREACH_SAFE(scope->values, value, tmp) { | |
DL_DELETE(scope->values, value); | |
PancakeFree(value); | |
} | |
PancakeFree(scope); | |
} | |
PANCAKE_API extern inline void PancakeConfigurationInitializeScopeGroup(PancakeConfigurationScopeGroup *group) { | |
group->scopes = NULL; | |
group->numScopes = 0; | |
} | |
PANCAKE_API extern inline void PancakeConfigurationScopeGroupAddScope(PancakeConfigurationScopeGroup *group, PancakeConfigurationScope *scope) { | |
group->numScopes++; | |
group->scopes = PancakeReallocate(group->scopes, sizeof(PancakeConfigurationScope*) * group->numScopes); | |
group->scopes[group->numScopes - 1] = scope; | |
} | |
PANCAKE_API extern inline void PancakeConfigurationActivateScopeGroup(PancakeConfigurationScopeGroup *group) { | |
UInt16 i; | |
for(i = 0; i < group->numScopes; i++) { | |
PancakeConfigurationActivateScope(group->scopes[i]); | |
} | |
} | |
PANCAKE_API extern inline void PancakeConfigurationDestroyScopeGroup(PancakeConfigurationScopeGroup *group) { | |
if(group->scopes) { | |
PancakeFree(group->scopes); | |
} | |
} | |
/* Built-in configuration hooks */ | |
UByte PancakeConfigurationFile(UByte step, config_setting_t *setting, PancakeConfigurationScope **scope) { | |
switch(step) { | |
case PANCAKE_CONFIGURATION_INIT: { | |
struct stat filestat; | |
FILE *stream; | |
PancakeAssert(setting->type == CONFIG_TYPE_STRING); | |
if(stat(setting->value.sval, &filestat) == -1) { | |
if(errno != ENOENT) { // Ignore errors when file does not exist as we will simply create it | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Stat on %s failed: %s", setting->value.sval, strerror(errno)); | |
return 0; | |
} | |
} else if(!S_ISREG(filestat.st_mode)) { // Log file MUST be a regular file | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "%s is not a regular file", setting->value.sval); | |
return 0; | |
} | |
stream = fopen(setting->value.sval, "a"); | |
if(stream == NULL) { | |
PancakeLoggerFormat(PANCAKE_LOGGER_ERROR, 0, "Failed to open %s: %s", setting->value.sval, strerror(errno)); | |
return 0; | |
} | |
// Set special type | |
setting->type = CONFIG_TYPE_FILE; | |
free(setting->value.sval); | |
setting->value.sval = (char*) stream; | |
} break; | |
case PANCAKE_CONFIGURATION_DTOR: | |
// Reset type to make library happy and close file stream | |
if(setting->type == CONFIG_TYPE_FILE) { | |
fclose((FILE*) setting->value.sval); | |
setting->type = CONFIG_TYPE_NONE; | |
} | |
break; | |
} | |
return 1; | |
} | |
UByte PancakeConfigurationString(UByte step, config_setting_t *setting, PancakeConfigurationScope **scope) { | |
String *str; | |
if(step == PANCAKE_CONFIGURATION_INIT) { | |
UInt32 length = strlen(setting->value.sval); | |
// Optimize if string is empty | |
if(!length) { | |
free(setting->value.sval); | |
setting->value.sval = NULL; | |
setting->type = CONFIG_TYPE_NONE; | |
return 1; | |
} | |
str = PancakeAllocate(sizeof(String)); | |
str->value = setting->value.sval; | |
str->length = length; | |
setting->type = CONFIG_TYPE_SPECIAL; | |
setting->value.sval = (Byte*) str; | |
} else { | |
if(setting->type == CONFIG_TYPE_SPECIAL) { | |
str = (String*) setting->value.sval; | |
free(str->value); | |
PancakeFree(str); | |
// Make library happy | |
setting->type = CONFIG_TYPE_NONE; | |
} | |
} | |
return 1; | |
} |