diff options
author | pommicket <pommicket@gmail.com> | 2025-09-14 00:26:39 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2025-09-14 00:26:39 -0400 |
commit | 1406758a5a2e38392e54cb7ba7a00a0f80ee1567 (patch) | |
tree | 4d42477a1b8d8ee3fa7f3251e9e864a9f6a98d2d | |
parent | 64c5d7ab942d8f4589f43909497ae07cc69231d0 (diff) |
Merging configurations
-rw-r--r-- | examples/conf.pom | 19 | ||||
-rw-r--r-- | examples/read_conf.c | 34 | ||||
-rw-r--r-- | pom.c | 220 | ||||
-rw-r--r-- | pom.h | 10 |
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); } @@ -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 = §ions[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 = §ions[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; +} @@ -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`. |