summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-14 00:26:39 -0400
committerpommicket <pommicket@gmail.com>2025-09-14 00:26:39 -0400
commit1406758a5a2e38392e54cb7ba7a00a0f80ee1567 (patch)
tree4d42477a1b8d8ee3fa7f3251e9e864a9f6a98d2d
parent64c5d7ab942d8f4589f43909497ae07cc69231d0 (diff)
Merging configurations
-rw-r--r--examples/conf.pom19
-rw-r--r--examples/read_conf.c34
-rw-r--r--pom.c220
-rw-r--r--pom.h10
4 files changed, 190 insertions, 93 deletions
diff --git a/examples/conf.pom b/examples/conf.pom
index ba10cca..27037d9 100644
--- a/examples/conf.pom
+++ b/examples/conf.pom
@@ -1,17 +1,4 @@
-[0oo.eeee°]
- bar = 10
-# testing
-
-Hello = 5
-
- # here's another comment
-[jibjabjobdsfajkha]
- win = yes
-
[number]
- one = 1
- two = 2
- three = "é
- yippee"
-[]
-thing=yup
+one = 1
+two = 2
+three = 3
diff --git a/examples/read_conf.c b/examples/read_conf.c
index f153c15..45a6520 100644
--- a/examples/read_conf.c
+++ b/examples/read_conf.c
@@ -12,26 +12,24 @@ int main(int argc, char **argv) {
free(error);
return EXIT_FAILURE;
}
- printf("number.one: %s\n", pom_conf_get(conf, "number.one"));
- printf("number.tow: %s\n", pom_conf_get_or_default(conf, "number.tow", "(none)"));
- printf("has thing? %d\n", pom_conf_has(conf, "thing"));
- const char *file; uint64_t line;
- if (pom_conf_location(conf, "thing", &file, &line)) {
- printf("\tthing location: %s:%" PRIu64 "\n", file,line);
+ pom_conf *conf2 = pom_load_string("<inline>", "foo=bar\r\n[j.number]\n"
+ "one = I\n"
+ "five = V\n", &error);
+ if (!conf2) {
+ pom_conf_free(conf);
+ pom_error_print(error);
+ free(error);
+ return EXIT_FAILURE;
}
- const pom_item *item;
pom_item_iter *iter = NULL;
- while ((item = pom_conf_next_item(pom_conf_section(conf, "number"), &iter))) {
- printf("Number: %s, Value: %s\n", item->key, item->value);
- }
- pom_unread_key_iter *unread = NULL;
- const char *key;
- while ((key = pom_conf_next_unread_key(conf, &unread))) {
- printf("Unrecognized key: %s\n", key);
- }
- pom_key_iter *keys = NULL;
- while ((key = pom_conf_next_key(conf, &keys))) {
- printf("Top-level key: %s\n", key);
+ const pom_item *item;
+ pom_conf_merge(conf,pom_conf_section(conf2,"j"));
+ while ((item = pom_conf_next_item(conf, &iter))) {
+ printf("Key: %s, Value: %s\n", item->key, item->value);
+ const char *file; uint64_t line;
+ pom_conf_location(conf,item->key,&file,&line);
+ printf(" Defined at %s:%" PRIu64 "\n",file,line);
}
pom_conf_free(conf);
+ pom_conf_free(conf2);
}
diff --git a/pom.c b/pom.c
index 5846226..7fc754f 100644
--- a/pom.c
+++ b/pom.c
@@ -1,3 +1,10 @@
+/*
+TODO:
+- error_to_string
+- conf_copy
+- conf_print
+- typed get functions
+*/
#include "pom.h"
#include <stdio.h> // still needed for sprintf, even if POM_NO_STDIO is defined.
@@ -717,7 +724,7 @@ parse_line(struct parser *parser) {
strip_trailing_accepted_spaces(line);
size_t len = strlen(line);
if (line[len-1] != ']') {
- parser_error(parser, "Missing ] to match [");
+ parser_error(parser, "Line starting with [ must end with ]");
return;
}
line += 1;
@@ -732,7 +739,6 @@ parse_line(struct parser *parser) {
check_valid_key(parser, current_section);
return;
}
- printf("%s|%s\n",parser->current_section.array,line);
size_t equals_idx;
for (size_t i = 0; ; i++) {
if (line[i] == '=') {
@@ -793,7 +799,8 @@ set_error(pom_error **error, pom_error *e) {
}
}
-static void conf_free_list_append(struct main_conf *conf, struct to_free *mem) {
+static void
+conf_free_list_append(struct main_conf *conf, struct to_free *mem) {
mem->next = NULL;
if (conf->to_free_tail) {
conf->to_free_tail->next = mem;
@@ -803,7 +810,8 @@ static void conf_free_list_append(struct main_conf *conf, struct to_free *mem) {
}
}
-static void *conf_calloc(struct main_conf *conf, size_t nmemb, size_t sz) {
+static void *
+conf_calloc(struct main_conf *conf, size_t nmemb, size_t sz) {
if (nmemb > SIZE_MAX / (2*sz)) return NULL;
struct to_free *mem = calloc(1, sizeof(struct to_free) + nmemb * sz);
if (!mem) return NULL;
@@ -811,12 +819,18 @@ static void *conf_calloc(struct main_conf *conf, size_t nmemb, size_t sz) {
return &mem->data[0];
}
-static void conf_free(struct main_conf *conf) {
- if (!conf) return;
+static void
+conf_free_inner(struct main_conf *conf) {
for (struct to_free *next, *f = conf->to_free_head; f; f = next) {
next = f->next;
free(f);
}
+}
+
+static void
+conf_free(struct main_conf *conf) {
+ if (!conf) return;
+ conf_free_inner(conf);
free(conf);
}
@@ -885,6 +899,66 @@ conf_binary_search_sections(const pom_conf *conf, const char *key, char nxt_char
return lo;
}
+// set up root->sections
+static POM__MUST_USE_L bool conf_create_sections(pom_conf *root) POM__MUST_USE_R;
+static bool
+conf_create_sections(pom_conf *root) {
+ assert(root->prefix_len == 0);
+ assert(root->sections == NULL);
+ struct main_conf *conf = root->main;
+ size_t sections_count = 0;
+ size_t items_count = root->items_count;
+ struct conf_item *items = root->items;
+ for (size_t i = 0; i < items_count; i++) {
+ struct conf_item *item = &items[i];
+ for (const char *p = item->key; p; ) {
+ const char *dot = strchr(p, '.');
+ if (!dot) break;
+ sections_count += i == 0
+ || strncmp(item->key, items[i-1].key, dot + 1 - item->key) != 0;
+ p = dot + 1;
+ }
+ }
+ struct conf_section *sections = conf_calloc(conf, sections_count, sizeof *sections);
+ if (!sections) return false;
+ root->sections = sections;
+ root->sections_count = sections_count;
+ struct conf_section *section = sections;
+ for (size_t i = 0; i < items_count; i++) {
+ struct conf_item *item = &items[i];
+ for (const char *p = item->key, *dot; p; p = dot + 1) {
+ dot = strchr(p, '.');
+ if (!dot) break;
+ size_t key_len = dot - item->key;
+ if (i && strncmp(item->key, items[i-1].key, key_len + 1) == 0)
+ continue; // section was already created
+ // create section
+ char *section_key = conf_calloc(conf, key_len + 1, 1);
+ if (!section_key)
+ return false;
+ section->key = section_key;
+ memcpy(section_key, item->key, key_len);
+ section_key[key_len] = 0;
+ // Note: + (...) is to not include key foo.bar in section(conf, "foo.bar")
+ size_t i_start = i + (item->key[key_len] == 0);
+ size_t i_end = conf_binary_search(root, section_key, '.' + 1, NULL);
+ section->conf.items = items + i_start;
+ section->conf.items_count = i_end - i_start;
+ section->conf.prefix_len = strlen(section_key) + 1/* dot */;
+ section->conf.main = conf;
+ section++;
+ }
+ }
+ assert(section == sections + sections_count);
+ for (size_t i = 0; i < sections_count; i++) {
+ section = &sections[i];
+ // set up sub-sections.
+ section->conf.sections = section;
+ section->conf.sections_count = conf_binary_search_sections(root, section->key, '.' + 1, NULL) - i;
+ }
+ return true;
+}
+
pom_conf *
parser_finish(struct parser *parser) {
if (parser->out_of_memory || parser->errors.count) {
@@ -956,56 +1030,8 @@ parser_finish(struct parser *parser) {
root->prefix_len = 0;
root->items = items;
root->items_count = items_count;
- size_t sections_count = 0;
- for (size_t i = 0; i < items_count; i++) {
- struct conf_item *item = &items[i];
- for (const char *p = item->key; p; ) {
- const char *dot = strchr(p, '.');
- if (!dot) break;
- sections_count += i == 0
- || strncmp(item->key, items[i-1].key, dot + 1 - item->key) != 0;
- p = dot + 1;
- }
- }
- struct conf_section *sections = conf_calloc(conf, sections_count, sizeof *sections);
- if (!sections) goto out_of_memory;
- root->sections = sections;
- root->sections_count = sections_count;
- struct conf_section *section = sections;
- for (size_t i = 0; i < items_count; i++) {
- struct conf_item *item = &items[i];
- for (const char *p = item->key, *dot; p; p = dot + 1) {
- dot = strchr(p, '.');
- if (!dot) break;
- size_t key_len = dot - item->key;
- if (i && strncmp(item->key, items[i-1].key, key_len + 1) == 0)
- continue; // section was already created
- // create section
- char *section_key = conf_calloc(conf, key_len + 1, 1);
- if (!section_key) {
- conf_free(conf);
- return NULL;
- }
- section->key = section_key;
- memcpy(section_key, item->key, key_len);
- section_key[key_len] = 0;
- // Note: + (...) is to not include key foo.bar in section(conf, "foo.bar")
- size_t i_start = i + (item->key[key_len] == 0);
- size_t i_end = conf_binary_search(root, section_key, '.' + 1, NULL);
- section->conf.items = items + i_start;
- section->conf.items_count = i_end - i_start;
- section->conf.prefix_len = strlen(section_key) + 1/* dot */;
- section->conf.main = conf;
- section++;
- }
- }
- assert(section == sections + sections_count);
- for (size_t i = 0; i < sections_count; i++) {
- section = &sections[i];
- // set up sub-sections.
- section->conf.sections = section;
- section->conf.sections_count = conf_binary_search_sections(root, section->key, '.' + 1, NULL) - i;
- }
+ if (!conf_create_sections(root))
+ goto out_of_memory;
return root;
}
@@ -1304,3 +1330,85 @@ pom_conf_section(const pom_conf *conf, const char *key) {
else
return &conf->main->empty_section;
}
+
+bool
+pom_conf_merge(pom_conf *conf, const pom_conf *other) {
+ check_conf(conf);
+ check_conf(other);
+ assert(conf->prefix_len == 0);
+ size_t max_items = conf->items_count + other->items_count;
+ struct conf_item *new_items = conf_calloc(conf->main, max_items, sizeof *new_items);
+ if (!new_items)
+ return false;
+ size_t new_string_data_len = 0;
+ for (size_t i = 0; i < other->items_count; i++) {
+ const struct conf_item *item = &other->items[i];
+ new_string_data_len +=
+ strlen(item->key) + 1 - other->prefix_len
+ + strlen(item->value) + 1;
+ if (i == 0 || item->file != item[-1].file) {
+ new_string_data_len += strlen(item->file) + 1;
+ }
+ }
+ char *new_string_data = conf_calloc(conf->main, 1, new_string_data_len);
+ if (!new_string_data)
+ return false;
+ char *string = new_string_data;
+ size_t i_conf = 0;
+ size_t i_other = 0;
+ size_t i_new_items = 0;
+ const char *prev_file_other = NULL, *prev_file_new = NULL;
+ while (i_conf < conf->items_count || i_other < other->items_count) {
+ const struct conf_item *item_conf = &conf->items[i_conf];
+ const struct conf_item *item_other = &other->items[i_other];
+ int cmp = i_conf >= conf->items_count ? 1
+ : i_other >= other->items_count ? -1
+ : strcmp(item_conf->key + conf->prefix_len, item_other->key + other->prefix_len);
+ struct conf_item *new_item = &new_items[i_new_items++];
+ if (cmp < 0) {
+ // select item from `conf`.
+ i_conf++;
+ new_item->key = item_conf->key;
+ new_item->value = item_conf->value;
+ new_item->line = item_conf->line;
+ new_item->file = item_conf->file;
+ atomic_init(&new_item->read, atomic_load_explicit(&item_conf->read, memory_order_relaxed));
+ } else {
+ if (cmp == 0)
+ i_conf++; // skip item with same key in `conf`.
+ // select item from `other`.
+ i_other++;
+ // copy all the fields, since they might have to outlive `other`.
+ new_item->key = string;
+ strcpy(string, item_other->key + other->prefix_len);
+ string += strlen(string) + 1;
+ new_item->value = string;
+ strcpy(string, item_other->value);
+ string += strlen(string) + 1;
+ new_item->line = item_other->line;
+ if (item_other->file == prev_file_other) {
+ new_item->file = prev_file_new;
+ } else {
+ // if this item's file is the same as the last one from `other`,
+ // just use the same pointer.
+ // this isn't perfect since keys from different files can be interleaved,
+ // but it's good enough.
+ new_item->file = string;
+ prev_file_new = string;
+ prev_file_other = item_other->file;
+ strcpy(string, item_other->file);
+ string += strlen(string) + 1;
+ }
+ atomic_init(&new_item->read, atomic_load_explicit(&item_other->read, memory_order_relaxed));
+ }
+ }
+ assert(string <= new_string_data + new_string_data_len);
+ pom_conf new_conf = {0};
+ new_conf.main = conf->main;
+ new_conf.items = new_items;
+ new_conf.items_count = i_new_items;
+ if (!conf_create_sections(&new_conf))
+ return false;
+ *conf = new_conf;
+ return true;
+}
diff --git a/pom.h b/pom.h
index 4473562..147720a 100644
--- a/pom.h
+++ b/pom.h
@@ -14,9 +14,11 @@
/// have certainly read their keys before calling it).
///
/// If C11 atomics are not available, you can almost certainly still get away
-/// with sharing configurations across threads.
+/// with sharing configurations across threads, as long as you use proper
+/// synchronization for \ref pom_conf_next_unread_key.
/// (Essentially, libpom may end up writing the same value to the same address
-/// from separate threads, which will likely never be an issue on any real machine.)
+/// from separate threads, which is *technically* undefined behaviour, but will
+/// likely never be an issue on any real machine.)
/// Even if you are extremely paranoid, you can still use
/// distinct configurations in different threads without worry.
@@ -425,7 +427,9 @@ POM__MUST_USE_R;
/// is called, so if you are repeatedly calling this function (unlikely, but who knows),
/// you should make a copy of `conf` after each call (with \ref pom_conf_copy),
/// then free the old value of `conf`.
-void
+///
+/// Returns `false` on out-of-memory, in which case `conf` is unchanged.
+bool
pom_conf_merge(pom_conf *conf, const pom_conf *other);
/// Get all unread keys in `conf`.