/*
- * Copyright (C) 2010-2014 Tobias Brunner
+ * Copyright (C) 2010-2018 Tobias Brunner
* Copyright (C) 2008 Martin Willi
* HSR Hochschule fuer Technik Rapperswil
*
/**
* Print a format key, but consume already processed arguments
+ * Note that key and start point into the same string
*/
static bool print_key(char *buf, int len, char *start, char *key, va_list args)
{
char *pos = start;
bool res;
+ if (!args)
+ {
+ return snprintf(buf, len, "%s", key) < len;
+ }
+
va_copy(copy, args);
while (TRUE)
{
return res;
}
+/**
+ * Check if the given section is contained in the given array.
+ */
+static bool has_section(array_t *array, section_t *section)
+{
+ section_t *current;
+ int i;
+
+ for (i = 0; i < array_count(array); i++)
+ {
+ array_get(array, i, ¤t);
+ if (current == section)
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
/**
* Find a section by a given key, using buffered key, reusable buffer.
* If "ensure" is TRUE, the sections are created if they don't exist.
}
/**
- * Find all sections via a given key considering fallbacks, using buffered key,
+ * Forward declaration
+ */
+static array_t *find_sections(private_settings_t *this, section_t *section,
+ char *key, va_list args, array_t **sections);
+
+/**
+ * Resolve the given reference. Not thread-safe.
+ */
+static void resolve_reference(private_settings_t *this, section_ref_t *ref,
+ array_t **sections)
+{
+ find_sections(this, this->top, ref->name, NULL, sections);
+}
+
+/**
+ * Find all sections via a given key considering references, using buffered key,
* reusable buffer.
*/
-static void find_sections_buffered(section_t *section, char *start, char *key,
- va_list args, char *buf, int len, array_t **sections)
+static void find_sections_buffered(private_settings_t *this, section_t *section,
+ char *start, char *key, va_list args,
+ char *buf, int len, bool ignore_refs,
+ array_t **sections)
{
- section_t *found = NULL, *fallback;
+ section_t *found = NULL, *reference;
+ array_t *references;
+ section_ref_t *ref;
char *pos;
- int i;
+ int i, j;
if (!section)
{
return;
}
if (pos)
- { /* restore so we can follow fallbacks */
+ { /* restore so we can follow references */
*pos = '.';
}
if (!strlen(buf))
{
if (pos)
{
- find_sections_buffered(found, start, pos+1, args, buf, len,
- sections);
+ find_sections_buffered(this, found, start, pos+1, args, buf, len,
+ FALSE, sections);
}
- else
+ else if (!has_section(*sections, found))
{
+ /* ignore if already added to avoid loops */
array_insert_create(sections, ARRAY_TAIL, found);
- for (i = 0; i < array_count(found->fallbacks); i++)
+ /* add all sections that are referenced here (also resolves
+ * references in parent sections of the referenced section) */
+ for (i = 0; i < array_count(found->references); i++)
{
- array_get(found->fallbacks, i, &fallback);
- array_insert_create(sections, ARRAY_TAIL, fallback);
+ array_get(found->references, i, &ref);
+ resolve_reference(this, ref, sections);
}
}
}
- if (section->fallbacks)
+ if (!ignore_refs && section != found && section->references)
{
- for (i = 0; i < array_count(section->fallbacks); i++)
+ /* find matching sub-sections relative to the referenced sections */
+ for (i = 0; i < array_count(section->references); i++)
{
- array_get(section->fallbacks, i, &fallback);
- find_sections_buffered(fallback, start, key, args, buf, len,
- sections);
+ array_get(section->references, i, &ref);
+ references = NULL;
+ resolve_reference(this, ref, &references);
+ for (j = 0; j < array_count(references); j++)
+ {
+ array_get(references, j, &reference);
+ /* ignore references in this referenced section, they were
+ * resolved via resolve_reference() */
+ find_sections_buffered(this, reference, start, key, args,
+ buf, len, TRUE, sections);
+ }
+ array_destroy(references);
}
}
}
}
/**
- * Find a section by a given key with its fallbacks (not thread-safe!).
- * Sections are returned in depth-first order (array is allocated). NULL is
- * returned if no sections are found.
+ * Find a section by a given key with resolved references (not thread-safe!).
+ * The array is allocated. NULL is returned if no sections are found.
*/
static array_t *find_sections(private_settings_t *this, section_t *section,
- char *key, va_list args)
+ char *key, va_list args, array_t **sections)
{
char buf[128], keybuf[512];
- array_t *sections = NULL;
if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
{
return NULL;
}
- find_sections_buffered(section, keybuf, keybuf, args, buf,
- sizeof(buf), §ions);
- return sections;
-}
-
-/**
- * Check if the given fallback section already exists
- */
-static bool fallback_exists(section_t *section, section_t *fallback)
-{
- if (section == fallback)
- {
- return TRUE;
- }
- else if (section->fallbacks)
- {
- section_t *existing;
- int i;
-
- for (i = 0; i < array_count(section->fallbacks); i++)
- {
- array_get(section->fallbacks, i, &existing);
- if (existing == fallback)
- {
- return TRUE;
- }
- }
- }
- return FALSE;
-}
-
-/**
- * Ensure that the section with the given key exists and add the given fallback
- * section (thread-safe).
- */
-static void add_fallback_to_section(private_settings_t *this,
- section_t *section, const char *key, va_list args,
- section_t *fallback)
-{
- char buf[128], keybuf[512];
- section_t *found;
-
- if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
- {
- return;
- }
- this->lock->write_lock(this->lock);
- found = find_section_buffered(section, keybuf, keybuf, args, buf,
- sizeof(buf), TRUE);
- if (!fallback_exists(found, fallback))
- {
- /* to ensure sections referred to as fallback are not purged, we create
- * the array there too */
- if (!fallback->fallbacks)
- {
- fallback->fallbacks = array_create(0, 0);
- }
- array_insert_create(&found->fallbacks, ARRAY_TAIL, fallback);
- }
- this->lock->unlock(this->lock);
+ find_sections_buffered(this, section, keybuf, keybuf, args, buf,
+ sizeof(buf), FALSE, sections);
+ return *sections;
}
/**
* Find the key/value pair for a key, using buffered key, reusable buffer
- * If "ensure" is TRUE, the sections (and key/value pair) are created if they
- * don't exist.
- * Fallbacks are only considered if "ensure" is FALSE.
+ * There are two modes: 1. To find a key at an exact location and create the
+ * sections (and key/value pair) if necessary, don't pass an array for sections.
+ * 2. To find a key and follow references pass a pointer to an array to store
+ * visited sections. NULL is returned in this case if the key is not found.
*/
-static kv_t *find_value_buffered(section_t *section, char *start, char *key,
- va_list args, char *buf, int len, bool ensure)
+static kv_t *find_value_buffered(private_settings_t *this, section_t *section,
+ char *start, char *key, va_list args,
+ char *buf, int len, bool ignore_refs,
+ array_t **sections)
{
- int i;
- char *pos;
- kv_t *kv = NULL;
section_t *found = NULL;
+ kv_t *kv = NULL;
+ section_ref_t *ref;
+ array_t *references;
+ char *pos;
+ int i, j;
- if (section == NULL)
+ if (!section)
{
return NULL;
}
-
pos = strchr(key, '.');
if (pos)
{
{
return NULL;
}
- /* restore so we can retry for fallbacks */
+ /* restore so we can follow references */
*pos = '.';
if (!strlen(buf))
{
else if (array_bsearch(section->sections, buf, settings_section_find,
&found) == -1)
{
- if (ensure)
+ if (!sections)
{
found = settings_section_create(strdup(buf));
settings_section_add(section, found, NULL);
}
if (found)
{
- kv = find_value_buffered(found, start, pos+1, args, buf, len,
- ensure);
- }
- if (!kv && !ensure && section->fallbacks)
- {
- for (i = 0; !kv && i < array_count(section->fallbacks); i++)
- {
- array_get(section->fallbacks, i, &found);
- kv = find_value_buffered(found, start, key, args, buf, len,
- ensure);
- }
+ kv = find_value_buffered(this, found, start, pos+1, args, buf, len,
+ FALSE, sections);
}
}
else
{
+ if (sections)
+ {
+ array_insert_create(sections, ARRAY_TAIL, section);
+ }
if (!print_key(buf, len, start, key, args))
{
return NULL;
}
if (array_bsearch(section->kv, buf, settings_kv_find, &kv) == -1)
{
- if (ensure)
+ if (!sections)
{
kv = settings_kv_create(strdup(buf), NULL);
settings_kv_add(section, kv, NULL);
}
- else if (section->fallbacks)
+ }
+ }
+ if (!kv && !ignore_refs && sections && section->references)
+ {
+ /* find key relative to the referenced sections */
+ for (i = 0; !kv && i < array_count(section->references); i++)
+ {
+ array_get(section->references, i, &ref);
+ references = NULL;
+ resolve_reference(this, ref, &references);
+ for (j = 0; !kv && j < array_count(references); j++)
{
- for (i = 0; !kv && i < array_count(section->fallbacks); i++)
+ array_get(references, j, &found);
+ /* ignore if already added to avoid loops */
+ if (!has_section(*sections, found))
{
- array_get(section->fallbacks, i, &found);
- kv = find_value_buffered(found, start, key, args, buf, len,
- ensure);
+ /* ignore references in this referenced section, they were
+ * resolved via resolve_reference() */
+ kv = find_value_buffered(this, found, start, key, args,
+ buf, len, TRUE, sections);
}
}
+ array_destroy(references);
}
}
return kv;
}
+/**
+ * Remove the key/value pair for a key, using buffered key, reusable buffer
+ */
+static void remove_value_buffered(private_settings_t *this, section_t *section,
+ char *start, char *key, va_list args,
+ char *buf, int len)
+{
+ section_t *found = NULL;
+ kv_t *kv = NULL, *ordered = NULL;
+ char *pos;
+ int idx, i;
+
+ if (!section)
+ {
+ return;
+ }
+ pos = strchr(key, '.');
+ if (pos)
+ {
+ *pos = '\0';
+ pos++;
+ }
+ if (!print_key(buf, len, start, key, args))
+ {
+ return;
+ }
+ if (!strlen(buf))
+ {
+ found = section;
+ }
+ if (pos)
+ {
+ if (array_bsearch(section->sections, buf, settings_section_find,
+ &found) != -1)
+ {
+ remove_value_buffered(this, found, start, pos, args, buf, len);
+ }
+ }
+ else
+ {
+ idx = array_bsearch(section->kv, buf, settings_kv_find, &kv);
+ if (idx != -1)
+ {
+ array_remove(section->kv, idx, NULL);
+ for (i = 0; i < array_count(section->kv_order); i++)
+ {
+ array_get(section->kv_order, i, &ordered);
+ if (kv == ordered)
+ {
+ array_remove(section->kv_order, i, NULL);
+ settings_kv_destroy(kv, this->contents);
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Described in header
+ */
+void settings_remove_value(settings_t *settings, char *key, ...)
+{
+ private_settings_t *this = (private_settings_t*)settings;
+ char buf[128], keybuf[512];
+ va_list args;
+
+ if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
+ {
+ return;
+ }
+ va_start(args, key);
+
+ this->lock->read_lock(this->lock);
+ remove_value_buffered(this, this->top, keybuf, keybuf, args, buf,
+ sizeof(buf));
+ this->lock->unlock(this->lock);
+
+ va_end(args);
+}
+
/**
* Find the string value for a key (thread-safe).
*/
char *key, va_list args)
{
char buf[128], keybuf[512], *value = NULL;
+ array_t *sections = NULL;
kv_t *kv;
if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
return NULL;
}
this->lock->read_lock(this->lock);
- kv = find_value_buffered(section, keybuf, keybuf, args, buf, sizeof(buf),
- FALSE);
+ kv = find_value_buffered(this, section, keybuf, keybuf, args,
+ buf, sizeof(buf), FALSE, §ions);
if (kv)
{
value = kv->value;
}
this->lock->unlock(this->lock);
+ array_destroy(sections);
return value;
}
return;
}
this->lock->write_lock(this->lock);
- kv = find_value_buffered(section, keybuf, keybuf, args, buf, sizeof(buf),
- TRUE);
+ kv = find_value_buffered(this, section, keybuf, keybuf, args,
+ buf, sizeof(buf), FALSE, NULL);
if (kv)
{
settings_kv_set(kv, strdupnull(value), this->contents);
private_settings_t *this, char *key, ...)
{
enumerator_data_t *data;
- array_t *sections;
+ array_t *sections = NULL;
va_list args;
this->lock->read_lock(this->lock);
va_start(args, key);
- sections = find_sections(this, this->top, key, args);
+ sections = find_sections(this, this->top, key, args, §ions);
va_end(args);
if (!sections)
while (orig->enumerate(orig, &kv))
{
- if (seen->get(seen, kv->key) || !kv->value)
+ if (seen->get(seen, kv->key))
+ {
+ continue;
+ }
+ seen->put(seen, kv->key, kv->key);
+ if (!kv->value)
{
continue;
}
*key = kv->key;
*value = kv->value;
- seen->put(seen, kv->key, kv->key);
return TRUE;
}
return FALSE;
private_settings_t *this, char *key, ...)
{
enumerator_data_t *data;
- array_t *sections;
+ array_t *sections = NULL;
va_list args;
this->lock->read_lock(this->lock);
va_start(args, key);
- sections = find_sections(this, this->top, key, args);
+ sections = find_sections(this, this->top, key, args, §ions);
va_end(args);
if (!sections)
{
section_t *section;
va_list args;
+ char buf[512];
- /* find/create the fallback */
+ /* find/create the section */
va_start(args, fallback);
- section = ensure_section(this, this->top, fallback, args);
+ section = ensure_section(this, this->top, key, args);
va_end(args);
va_start(args, fallback);
- add_fallback_to_section(this, this->top, key, args, section);
+ if (vsnprintf(buf, sizeof(buf), fallback, args) < sizeof(buf))
+ {
+ this->lock->write_lock(this->lock);
+ settings_reference_add(section, strdup(buf), TRUE);
+ this->lock->unlock(this->lock);
+ }
va_end(args);
}
/*
- * Copyright (C) 2010 Tobias Brunner
+ * Copyright (C) 2010-2018 Tobias Brunner
* Copyright (C) 2008 Martin Willi
* HSR Hochschule fuer Technik Rapperswil
*
* 'section-one.two' will result in a lookup for the same section/key
* in 'section-two'.
*
- * @note Lookups are depth-first and currently strictly top-down.
- * For instance, if app.sec had lib1.sec as fallback and lib1 had lib2 as
- * fallback the keys/sections in lib2.sec would not be considered. But if
- * app had lib3 as fallback the contents of lib3.sec would (as app is passed
- * during the initial lookup). In the last example the order during
- * enumerations would be app.sec, lib1.sec, lib3.sec.
- *
* @note Additional arguments will be applied to both section format
- * strings so they must be compatible.
+ * strings so they must be compatible. And they are evaluated immediately,
+ * so arguments can't contain dots.
*
* @param section section for which a fallback is configured, printf style
* @param fallback fallback section, printf style
*/
settings_t *settings_create_string(char *settings);
+/**
+ * Remove the given key/value.
+ *
+ * Compared to setting a key to NULL, which makes it appear to be unset (i.e.
+ * default values will apply) this removes the given key (if found) and
+ * references/fallbacks will apply when looking for that key. This is mainly
+ * usefuls for the unit tests.
+ *
+ * @param settings settings to remove key/value from
+ * @param key key including sections, printf style format
+ * @param ... argument list for key
+ */
+void settings_remove_value(settings_t *settings, char *key, ...);
+
#endif /** SETTINGS_H_ @}*/
%{
/*
- * Copyright (C) 2014 Tobias Brunner
+ * Copyright (C) 2014-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
/* type of our extra data */
%option extra-type="parser_helper_t*"
+/* state used to scan names */
+%x nam
/* state used to scan values */
%x val
/* state used to scan include file patterns */
/* state used to scan quoted strings */
%x str
+/* pattern for section/key names */
+NAME [^#{}:,="\r\n\t ]
+
%%
[\t ]*#[^\r\n]* /* eat comments */
[\t\r ]+ /* eat whitespace */
-\n|#.*\n return NEWLINE; /* also eats comments at the end of a line */
+\n|#.*\n /* eat newlines and comments at the end of a line */
"{" |
-"}" return yytext[0];
+"}" |
+"," return yytext[0];
+
+":" return REFS;
"=" {
yy_push_state(val, yyscanner);
return STRING_ERROR;
}
-[^#{}="\r\n\t ]+ {
- yylval->s = strdup(yytext);
- return NAME;
+{NAME} {
+ yyextra->string_init(yyextra);
+ yyextra->string_add(yyextra, yytext);
+ yy_push_state(nam, yyscanner);
+}
+
+<nam>{
+ "::" {
+ yyextra->string_add(yyextra, yytext+1);
+ }
+
+ {NAME}+ {
+ yyextra->string_add(yyextra, yytext);
+ }
+
+ <<EOF>> |
+ .|[\r\n] {
+ if (*yytext)
+ {
+ switch (yytext[0])
+ {
+ case '\n':
+ /* put the newline back to fix the line numbers */
+ unput('\n');
+ yy_set_bol(0);
+ break;
+ default:
+ /* these are parsed outside of this start condition */
+ unput(yytext[0]);
+ break;
+ }
+ }
+ yy_pop_state(yyscanner);
+ yylval->s = yyextra->string_get(yyextra);
+ return NAME;
+ }
}
<val>{
\r /* just ignore these */
[\t ]+
<<EOF>> |
- [#}\n] {
+ [#}\n] {
if (*yytext)
{
switch (yytext[0])
}
}
yy_pop_state(yyscanner);
+ return NEWLINE;
}
- "\"" {
+ "\"" {
yyextra->string_init(yyextra);
yy_push_state(str, yyscanner);
}
/* same as above, but allow more characters */
- [^#}"\r\n\t ]+ {
+ [^#}"\r\n\t ]+ {
yylval->s = strdup(yytext);
return NAME;
}
%{
/*
- * Copyright (C) 2014 Tobias Brunner
+ * Copyright (C) 2014-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
static section_t *pop_section(parser_helper_t *ctx);
static void add_section(parser_helper_t *ctx, section_t *section);
static void add_setting(parser_helper_t *ctx, kv_t *kv);
+static void add_references(parser_helper_t *ctx, array_t *references);
/**
* Make sure to call lexer with the proper context
char *s;
struct section_t *sec;
struct kv_t *kv;
+ array_t *refs;
}
%token <s> NAME STRING
+%token REFS ":"
%token NEWLINE STRING_ERROR
/* ...and other symbols */
%type <s> value valuepart
%type <sec> section_start section
%type <kv> setting
+%type <refs> references
/* properly destroy string tokens that are strdup()ed on error */
%destructor { free($$); } NAME STRING value valuepart
/* properly destroy parse results on error */
%destructor { pop_section(ctx); settings_section_destroy($$, NULL); } section_start section
%destructor { settings_kv_destroy($$, NULL); } setting
+%destructor { array_destroy_function($$, (void*)free, NULL); } references
/* there are two shift/reduce conflicts because of the "NAME = NAME" and
* "NAME {" ambiguity, and the "NAME =" rule) */
$$ = push_section(ctx, $NAME);
}
|
- NAME NEWLINE '{'
+ NAME ":" references '{'
{
$$ = push_section(ctx, $NAME);
+ add_references(ctx, $references);
+ array_destroy($references);
+ }
+ ;
+
+references:
+ NAME
+ {
+ $$ = array_create(0, 0);
+ array_insert($$, ARRAY_TAIL, $1);
+ }
+ | references ',' NAME
+ {
+ array_insert($1, ARRAY_TAIL, $3);
+ $$ = $1;
}
;
settings_kv_add(section, kv, NULL);
}
+/**
+ * Adds the given references to the section on top of the stack
+ */
+static void add_references(parser_helper_t *ctx, array_t *references)
+{
+ array_t *sections = (array_t*)ctx->context;
+ section_t *section;
+ enumerator_t *refs;
+ char *ref;
+
+ array_get(sections, ARRAY_TAIL, §ion);
+
+ refs = array_create_enumerator(references);
+ while (refs->enumerate(refs, &ref))
+ {
+ settings_reference_add(section, ref, FALSE);
+ array_remove_at(references, refs);
+ }
+ refs->destroy(refs);
+}
+
/**
* Parse the given file and add all sections and key/value pairs to the
* given section.
/*
- * Copyright (C) 2010-2014 Tobias Brunner
+ * Copyright (C) 2010-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
settings_kv_destroy(kv, contents);
}
+static void ref_destroy(section_ref_t *ref, int idx, void *ctx)
+{
+ free(ref->name);
+ free(ref);
+}
+
/*
* Described in header
*/
array_destroy(this->sections_order);
array_destroy_function(this->kv, (void*)kv_destroy, contents);
array_destroy(this->kv_order);
- array_destroy(this->fallbacks);
+ array_destroy_function(this->references, (void*)ref_destroy, NULL);
free(this->name);
free(this);
}
}
}
+/*
+ * Described in header
+ */
+void settings_reference_add(section_t *section, char *name, bool permanent)
+{
+ section_ref_t *ref;
+ int i;
+
+ for (i = 0; i < array_count(section->references); i++)
+ {
+ array_get(section->references, i, &ref);
+ if (ref->permanent && !permanent)
+ { /* add it before any permanent references */
+ break;
+ }
+ if (ref->permanent == permanent && streq(name, ref->name))
+ {
+ free(name);
+ return;
+ }
+ }
+
+ INIT(ref,
+ .name = name,
+ .permanent = permanent,
+ );
+ array_insert_create(§ion->references, i, ref);
+}
+
/*
* Add a section to the given parent, optionally remove settings/subsections
* not found when extending an existing section
static bool section_purge(section_t *this, array_t *contents)
{
section_t *current;
+ section_ref_t *ref;
int i, idx;
array_destroy_function(this->kv, (void*)kv_destroy, contents);
this->kv = NULL;
array_destroy(this->kv_order);
this->kv_order = NULL;
- /* we ensure sections used as fallback, or configured with fallbacks (or
- * having any such subsections) are not removed */
+ /* remove non-permanent references */
+ for (i = array_count(this->references) - 1; i >= 0; i--)
+ {
+ array_get(this->references, i, &ref);
+ if (!ref->permanent)
+ {
+ array_remove(this->references, i, NULL);
+ ref_destroy(ref, 0, NULL);
+ }
+ }
+ if (!array_count(this->references))
+ {
+ array_destroy(this->references);
+ this->references = NULL;
+ }
for (i = array_count(this->sections_order) - 1; i >= 0; i--)
{
array_get(this->sections_order, i, ¤t);
settings_section_destroy(current, contents);
}
}
- return !this->fallbacks && !array_count(this->sections);
+ /* we ensure sections configured with permanent references (or having any
+ * such subsections) are not removed */
+ return !this->references && !array_count(this->sections);
}
/*
{
enumerator_t *enumerator;
section_t *section;
+ section_ref_t *ref;
kv_t *kv;
array_t *sections = NULL, *kvs = NULL;
int idx;
if (purge)
- { /* remove sections and settings in base not found in extension, the
- * others are removed too (from the _order list) so they can be inserted
- * in the order found in extension */
+ { /* remove sections, settings in base not found in extension, the others
+ * are removed too (from the _order list) so they can be inserted in the
+ * order found in extension, non-permanent references are removed */
enumerator = array_create_enumerator(base->sections_order);
while (enumerator->enumerate(enumerator, (void**)§ion))
{
array_sort(kvs, settings_kv_sort, NULL);
}
}
+
+ enumerator = array_create_enumerator(base->references);
+ while (enumerator->enumerate(enumerator, (void**)&ref))
+ {
+ if (ref->permanent)
+ { /* permanent references are ignored */
+ continue;
+ }
+ array_remove_at(base->references, enumerator);
+ ref_destroy(ref, 0, NULL);
+ }
+ enumerator->destroy(enumerator);
}
while (array_remove(extension->sections_order, 0, §ion))
array_remove(extension->kv, idx, NULL);
settings_kv_add(base, kv, contents);
}
+
+ while (array_remove(extension->references, 0, &ref))
+ {
+ if (ref->permanent)
+ { /* ignore permanent references in the extension */
+ continue;
+ }
+ settings_reference_add(base, strdup(ref->name), FALSE);
+ ref_destroy(ref, 0, NULL);
+ }
array_destroy(sections);
array_destroy(kvs);
}
/*
- * Copyright (C) 2010-2014 Tobias Brunner
+ * Copyright (C) 2010-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
#define SETTINGS_TYPES_H_
typedef struct kv_t kv_t;
+typedef struct section_ref_t section_ref_t;
typedef struct section_t section_t;
#include "collections/array.h"
char *value;
};
+/**
+ * Section reference.
+ */
+struct section_ref_t {
+
+ /**
+ * Name of the referenced section.
+ */
+ char *name;
+
+ /**
+ * TRUE for permanent references that were added programmatically via
+ * add_fallback() and are not removed during reloads/purges.
+ */
+ bool permanent;
+};
+
/**
* Section containing subsections and key value pairs.
*/
char *name;
/**
- * Fallback sections, as section_t.
+ * Referenced sections, as section_ref_t.
*/
- array_t *fallbacks;
+ array_t *references;
/**
* Subsections, as section_t.
*/
void settings_kv_add(section_t *section, kv_t *kv, array_t *contents);
+/**
+ * Add a reference to another section.
+ *
+ * @param section section to which to add the reference
+ * @param name name of the referenced section (adopted)
+ * @param permanent whether the reference is not removed during reloads
+ */
+void settings_reference_add(section_t *section, char *name, bool permanent);
+
/**
* Create a section with the given name.
*
}
END_TEST
+START_TEST(test_fallback_resolution)
+{
+ linked_list_t *keys, *values;
+
+ settings->destroy(settings);
+ create_settings(chunk_from_str(
+ "base {\n"
+ " sub {\n"
+ " key1 = val1\n"
+ " key2 = val2\n"
+ " key5 = val5\n"
+ " subsub {\n"
+ " subkey1 = subval1\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ "other {\n"
+ " sub {\n"
+ " key3 = val3\n"
+ " key4 = val4\n"
+ " }\n"
+ "}\n"
+ "main {\n"
+ " sub {\n"
+ " key4=\n"
+ " key5 = \n"
+ " }\n"
+ "}"));
+
+ settings->add_fallback(settings, "other", "base");
+ settings->add_fallback(settings, "main.sub", "other.sub");
+
+ verify_string("val1", "main.sub.key1");
+ verify_string("val3", "main.sub.key3");
+ verify_null("main.sub.key4");
+ verify_null("main.sub.key5");
+ verify_string("subval1", "main.sub.subsub.subkey1");
+
+ keys = linked_list_create_with_items("key3", "key1", "key2", NULL);
+ values = linked_list_create_with_items("val3", "val1", "val2", NULL);
+ verify_key_values(keys, values, "main.sub");
+}
+END_TEST
+
START_TEST(test_add_fallback_printf)
{
settings->add_fallback(settings, "%s.sub1", "sub", "main");
}
END_TEST
+START_TEST(test_references)
+{
+ linked_list_t *keys, *values;
+
+ create_settings(chunk_from_str(
+ "main {\n"
+ " sub1 {\n"
+ " key1 = sub1val1\n"
+ " key2 = sub1val2\n"
+ " key4 = sub1val4\n"
+ " subsub {\n"
+ " subkey1 = sub1subsubval1\n"
+ " subkey2 = sub1subsubval2\n"
+ " }\n"
+ " subsub1 {\n"
+ " subkey1 = sub1subsub1val1\n"
+ " }\n"
+ " }\n"
+ " sub2 : main.sub1 {\n"
+ " key2 = sub2val2\n"
+ " key3 = sub2val3\n"
+ " key4 =\n"
+ " subsub {\n"
+ " subkey1 = sub2subsubval1\n"
+ " subkey3 = sub2subsubval3\n"
+ " }\n"
+ " }\n"
+ "}"));
+
+ verify_string("sub1val1", "main.sub2.key1");
+ verify_string("sub2val2", "main.sub2.key2");
+ verify_string("sub2val3", "main.sub2.key3");
+ verify_null("main.sub2.key4");
+ verify_string("sub2subsubval1", "main.sub2.subsub.subkey1");
+ verify_string("sub1subsubval2", "main.sub2.subsub.subkey2");
+ verify_string("sub2subsubval3", "main.sub2.subsub.subkey3");
+ verify_string("sub1subsub1val1", "main.sub2.subsub1.subkey1");
+
+ keys = linked_list_create_with_items("subsub", "subsub1", NULL);
+ verify_sections(keys, "main.sub2");
+
+ keys = linked_list_create_with_items("key2", "key3", "key1", NULL);
+ values = linked_list_create_with_items("sub2val2", "sub2val3", "sub1val1", NULL);
+ verify_key_values(keys, values, "main.sub2");
+
+ keys = linked_list_create_with_items("subkey1", "subkey3", "subkey2", NULL);
+ values = linked_list_create_with_items("sub2subsubval1", "sub2subsubval3", "sub1subsubval2", NULL);
+ verify_key_values(keys, values, "main.sub2.subsub");
+}
+END_TEST
+
+START_TEST(test_references_templates)
+{
+ create_settings(chunk_from_str(
+ "sub-def {\n"
+ " key1 = sub1val1\n"
+ " key2 = sub1val2\n"
+ " subsub {\n"
+ " subkey1 = sub1subsubval1\n"
+ " }\n"
+ "}\n"
+ "subsub-def {\n"
+ " subkey1 = sub1subval1\n"
+ " subkey2 = sub1subval1\n"
+ "}\n"
+ "main {\n"
+ " sub1 : sub-def {\n"
+ " key1 = mainsub1val1\n"
+ " subsub : subsub-def {\n"
+ " subkey1 = mainsub1subval1\n"
+ " }\n"
+ " subsub1 {\n"
+ " subkey1 = mainsub1sub1val1\n"
+ " }\n"
+ " }\n"
+ " sub2 : sub-def {\n"
+ " key2 = mainsub2val2\n"
+ " key3 = mainsub2val3\n"
+ " subsub {\n"
+ " subkey3 = mainsub2subsubval3\n"
+ " }\n"
+ " }\n"
+ "}"));
+
+ verify_string("mainsub1val1", "main.sub1.key1");
+ verify_string("sub1val2", "main.sub1.key2");
+ verify_string("mainsub1subval1", "main.sub1.subsub.subkey1");
+ verify_string("sub1subval1", "main.sub1.subsub.subkey2");
+ verify_string("mainsub1sub1val1", "main.sub1.subsub1.subkey1");
+ verify_string("sub1val1", "main.sub2.key1");
+ verify_string("mainsub2val2", "main.sub2.key2");
+ verify_string("mainsub2val3", "main.sub2.key3");
+ verify_string("sub1subsubval1", "main.sub2.subsub.subkey1");
+ verify_null("main.sub2.subsub.subkey2");
+ verify_string("mainsub2subsubval3", "main.sub2.subsub.subkey3");
+}
+END_TEST
+
+START_TEST(test_references_order)
+{
+ linked_list_t *keys, *values;
+
+ create_settings(chunk_from_str(
+ "main {\n"
+ " sub1 {\n"
+ " key1 = sub1val1\n"
+ " key2 = sub1val2\n"
+ " subsub1 {\n"
+ " }\n"
+ " }\n"
+ " sub2 {\n"
+ " key2 = sub2val2\n"
+ " key3 = sub2val3\n"
+ " subsub2 {\n"
+ " }\n"
+ " }\n"
+ " sub3 : main.sub1, main.sub2 {\n"
+ " key3 = sub3val3\n"
+ " }\n"
+ " sub4 : main.sub2, main.sub1 {\n"
+ " key3 = sub4val3\n"
+ " }\n"
+ "}"));
+
+ verify_string("sub1val2", "main.sub3.key2");
+ verify_string("sub3val3", "main.sub3.key3");
+ verify_string("sub2val2", "main.sub4.key2");
+ verify_string("sub4val3", "main.sub4.key3");
+
+ /* the order of referenced keys/subsections depends on the reference
+ * statement's order */
+ keys = linked_list_create_with_items("subsub1", "subsub2", NULL);
+ verify_sections(keys, "main.sub3");
+
+ keys = linked_list_create_with_items("subsub2", "subsub1", NULL);
+ verify_sections(keys, "main.sub4");
+
+ /* local keys are always enumerated first */
+ keys = linked_list_create_with_items("key3", "key1", "key2", NULL);
+ values = linked_list_create_with_items("sub3val3", "sub1val1", "sub1val2", NULL);
+ verify_key_values(keys, values, "main.sub3");
+
+ keys = linked_list_create_with_items("key3", "key2", "key1", NULL);
+ values = linked_list_create_with_items("sub4val3", "sub2val2", "sub1val1", NULL);
+ verify_key_values(keys, values, "main.sub4");
+}
+END_TEST
+
+START_TEST(test_references_resolution)
+{
+ linked_list_t *keys, *values;
+
+ create_settings(chunk_from_str(
+ "sec-a {\n"
+ " sub1 {\n"
+ " a1 = val-a1\n"
+ " key = sec-a-val1\n"
+ " sub-a {\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ "sec-b : sec-a {\n"
+ " sub1 {\n"
+ " b1 = val-b1\n"
+ " key = sec-b-val1\n"
+ " sub-b1 {\n"
+ " }\n"
+ " }\n"
+ " sub2 {\n"
+ " b2 = val-b2\n"
+ " key = sec-b-val2\n"
+ " sub-b2 {\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ "sec-c : sec-b {\n"
+ " sub2 : sec-b.sub1 {\n"
+ " c2 = val-c2\n"
+ " key = sec-c-val2\n"
+ " sub-c2 {\n"
+ " }\n"
+ " }\n"
+ "}"));
+
+ verify_string("sec-c-val2", "sec-c.sub2.key");
+ settings_remove_value(settings, "sec-c.sub2.key");
+ verify_string("sec-b-val1", "sec-c.sub2.key");
+ settings_remove_value(settings, "sec-b.sub1.key");
+ verify_string("sec-a-val1", "sec-c.sub2.key");
+ settings_remove_value(settings, "sec-a.sub1.key");
+ verify_string("sec-b-val2", "sec-c.sub2.key");
+ settings_remove_value(settings, "sec-b.sub2.key");
+ verify_null("sec-c.sub2.key");
+
+ keys = linked_list_create_with_items("sub-c2", "sub-b1", "sub-a", "sub-b2", NULL);
+ verify_sections(keys, "sec-c.sub2");
+
+ keys = linked_list_create_with_items("c2", "b1", "a1", "b2", NULL);
+ values = linked_list_create_with_items("val-c2", "val-b1", "val-a1", "val-b2", NULL);
+ verify_key_values(keys, values, "sec-c.sub2");
+}
+END_TEST
+
+START_TEST(test_references_fallback)
+{
+ linked_list_t *keys, *values;
+
+#define test_references_fallback_base_settings \
+ "lib {\n" \
+ " key1 = libval1\n" \
+ " keylib = libval\n" \
+ " sub {\n" \
+ " key1 = libsubval1\n" \
+ " }\n" \
+ " libsub {\n" \
+ " }\n" \
+ "}\n" \
+ "other {\n" \
+ " key1 = otherval1\n" \
+ " keyother = otherval\n" \
+ " sub {\n" \
+ " key1 = othersubval1\n" \
+ " }\n" \
+ " othersub {\n" \
+ " }\n" \
+ "}\n"
+
+ create_settings(chunk_from_str(
+ test_references_fallback_base_settings "app : other {}"));
+
+ /* references have precedence over fallbacks */
+ settings->add_fallback(settings, "app", "lib");
+ verify_string("otherval1", "app.key1");
+ verify_string("libval", "app.keylib");
+ verify_string("othersubval1", "app.sub.key1");
+
+ keys = linked_list_create_with_items("sub", "othersub", "libsub", NULL);
+ verify_sections(keys, "app");
+
+ keys = linked_list_create_with_items("key1", "keyother", "keylib", NULL);
+ values = linked_list_create_with_items("otherval1", "otherval", "libval", NULL);
+ verify_key_values(keys, values, "app");
+
+ /* fallbacks are unaffected when reloading configs with references */
+ ck_assert(settings->load_string_section(settings,
+ test_references_fallback_base_settings "app {}", FALSE, ""));
+ verify_string("libval1", "app.key1");
+ verify_string("libval", "app.keylib");
+ verify_string("libsubval1", "app.sub.key1");
+
+ ck_assert(settings->load_string_section(settings,
+ test_references_fallback_base_settings "app : other {}", FALSE, ""));
+ verify_string("otherval1", "app.key1");
+ verify_string("libval", "app.keylib");
+ verify_string("othersubval1", "app.sub.key1");
+}
+END_TEST
+
START_SETUP(setup_string_config)
{
create_settings(chunk_from_str(
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(settings->load_files(settings, path, FALSE));
verify_string("a setting with = and { character", "equals");
+
+ contents = chunk_from_str(
+ "ref { key = value }\nvalid:ref {}");
+ ck_assert(chunk_write(contents, path, 0022, TRUE));
+ ck_assert(settings->load_files(settings, path, FALSE));
+ verify_string("value", "valid.key");
+
+ contents = chunk_from_str(
+ "ref { key = value }\nvalid\n:\nref {}");
+ ck_assert(chunk_write(contents, path, 0022, TRUE));
+ ck_assert(settings->load_files(settings, path, FALSE));
+ verify_string("value", "valid.key");
+
+ contents = chunk_from_str(
+ "ref { key = value }\nother { key1 = value1 }\nvalid\n:\nref\n\t,\nother {}");
+ ck_assert(chunk_write(contents, path, 0022, TRUE));
+ ck_assert(settings->load_files(settings, path, FALSE));
+ verify_string("value", "valid.key");
+ verify_string("value1", "valid.key1");
+
+ contents = chunk_from_str(
+ "c::\\Logfiles\\charon.log { dmn = 1 }");
+ ck_assert(chunk_write(contents, path, 0022, TRUE));
+ ck_assert(settings->load_files(settings, path, FALSE));
+ verify_string("1", "%s.dmn", "c:\\Logfiles\\charon.log");
+
+ contents = chunk_from_str(
+ "section { c::\\Logfiles\\charon.log = 1 }");
+ ck_assert(chunk_write(contents, path, 0022, TRUE));
+ ck_assert(settings->load_files(settings, path, FALSE));
+ verify_string("1", "section.%s", "c:\\Logfiles\\charon.log");
}
END_TEST
"\"unexpected\" = string");
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(!settings->load_files(settings, path, FALSE));
+
+ contents = chunk_from_str(
+ "incorrect :: ref {}");
+ ck_assert(chunk_write(contents, path, 0022, TRUE));
+ ck_assert(!settings->load_files(settings, path, FALSE));
}
END_TEST
tc = tcase_create("fallback");
tcase_add_checked_fixture(tc, setup_fallback_config, teardown_config);
tcase_add_test(tc, test_add_fallback);
+ tcase_add_test(tc, test_fallback_resolution);
tcase_add_test(tc, test_add_fallback_printf);
suite_add_tcase(s, tc);
+ tc = tcase_create("references");
+ tcase_add_checked_fixture(tc, NULL, teardown_config);
+ tcase_add_test(tc, test_references);
+ tcase_add_test(tc, test_references_templates);
+ tcase_add_test(tc, test_references_order);
+ tcase_add_test(tc, test_references_resolution);
+ tcase_add_test(tc, test_references_fallback);
+ suite_add_tcase(s, tc);
+
tc = tcase_create("strings");
tcase_add_checked_fixture(tc, setup_string_config, teardown_config);
tcase_add_test(tc, test_strings);