forked from external/yambar
yml: detect (and report to user) duplicate keys in dictionaries
This commit is contained in:
parent
de34d734a0
commit
305058deab
1 changed files with 151 additions and 10 deletions
161
yml.c
161
yml.c
|
@ -10,6 +10,12 @@
|
||||||
|
|
||||||
#include "tllist.h"
|
#include "tllist.h"
|
||||||
|
|
||||||
|
enum yml_error {
|
||||||
|
YML_ERR_NONE,
|
||||||
|
YML_ERR_DUPLICATE_KEY,
|
||||||
|
YML_ERR_UNKNOWN,
|
||||||
|
};
|
||||||
|
|
||||||
enum node_type {
|
enum node_type {
|
||||||
ROOT,
|
ROOT,
|
||||||
SCALAR,
|
SCALAR,
|
||||||
|
@ -87,7 +93,34 @@ clone_node(struct yml_node *parent, const struct yml_node *node)
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
|
node_equal(const struct yml_node *a, const struct yml_node *b)
|
||||||
|
{
|
||||||
|
if (a->type != b->type)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (a->type != SCALAR) {
|
||||||
|
/* TODO... */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strcmp(a->scalar.value, b->scalar.value) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
dict_has_key(const struct yml_node *node, const struct yml_node *key)
|
||||||
|
{
|
||||||
|
assert(node->type == DICT);
|
||||||
|
|
||||||
|
tll_foreach(node->dict.pairs, pair) {
|
||||||
|
if (node_equal(pair->item.key, key))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum yml_error
|
||||||
add_node(struct yml_node *parent, struct yml_node *new_node)
|
add_node(struct yml_node *parent, struct yml_node *new_node)
|
||||||
{
|
{
|
||||||
switch (parent->type) {
|
switch (parent->type) {
|
||||||
|
@ -99,6 +132,9 @@ add_node(struct yml_node *parent, struct yml_node *new_node)
|
||||||
|
|
||||||
case DICT:
|
case DICT:
|
||||||
if (!parent->dict.next_is_value) {
|
if (!parent->dict.next_is_value) {
|
||||||
|
if (dict_has_key(parent, new_node))
|
||||||
|
return YML_ERR_DUPLICATE_KEY;
|
||||||
|
|
||||||
tll_push_back(parent->dict.pairs, (struct dict_pair){.key = new_node});
|
tll_push_back(parent->dict.pairs, (struct dict_pair){.key = new_node});
|
||||||
parent->dict.next_is_value = true;
|
parent->dict.next_is_value = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -115,8 +151,10 @@ add_node(struct yml_node *parent, struct yml_node *new_node)
|
||||||
|
|
||||||
case SCALAR:
|
case SCALAR:
|
||||||
assert(false);
|
assert(false);
|
||||||
break;
|
return YML_ERR_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return YML_ERR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -214,6 +252,58 @@ post_process(struct yml_node *node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
format_error(enum yml_error err,
|
||||||
|
const struct yml_node *parent,
|
||||||
|
const struct yml_node *node)
|
||||||
|
{
|
||||||
|
static char err_str[512];
|
||||||
|
|
||||||
|
switch (err) {
|
||||||
|
case YML_ERR_NONE:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case YML_ERR_DUPLICATE_KEY: {
|
||||||
|
/* Find parent's key (i.e its name) */
|
||||||
|
if (parent->parent != NULL &&
|
||||||
|
parent->parent->type == DICT &&
|
||||||
|
node->type == SCALAR)
|
||||||
|
{
|
||||||
|
tll_foreach(parent->parent->dict.pairs, pair) {
|
||||||
|
if (pair->item.value != parent)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pair->item.key->type != SCALAR)
|
||||||
|
break;
|
||||||
|
|
||||||
|
assert(pair->item.key->type == SCALAR);
|
||||||
|
assert(node->type == SCALAR);
|
||||||
|
|
||||||
|
snprintf(err_str, sizeof(err_str),
|
||||||
|
"%s: duplicate key: '%s'",
|
||||||
|
pair->item.key->scalar.value,
|
||||||
|
node->scalar.value);
|
||||||
|
return err_str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node->type == SCALAR) {
|
||||||
|
snprintf(err_str, sizeof(err_str),
|
||||||
|
"duplicate key: %s", node->scalar.value);
|
||||||
|
} else
|
||||||
|
snprintf(err_str, sizeof(err_str), "duplicate key");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case YML_ERR_UNKNOWN:
|
||||||
|
snprintf(err_str, sizeof(err_str), "unknown error");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err_str;
|
||||||
|
}
|
||||||
|
|
||||||
struct yml_node *
|
struct yml_node *
|
||||||
yml_load(FILE *yml, char **error)
|
yml_load(FILE *yml, char **error)
|
||||||
{
|
{
|
||||||
|
@ -235,6 +325,8 @@ yml_load(FILE *yml, char **error)
|
||||||
|
|
||||||
struct yml_node *n = root;
|
struct yml_node *n = root;
|
||||||
|
|
||||||
|
const char *error_str = NULL;
|
||||||
|
|
||||||
while (!done) {
|
while (!done) {
|
||||||
yaml_event_t event;
|
yaml_event_t event;
|
||||||
if (!yaml_parser_parse(&yaml, &event)) {
|
if (!yaml_parser_parse(&yaml, &event)) {
|
||||||
|
@ -252,12 +344,9 @@ yml_load(FILE *yml, char **error)
|
||||||
yaml.problem_mark.column,
|
yaml.problem_mark.column,
|
||||||
yaml.problem,
|
yaml.problem,
|
||||||
yaml.context != NULL ? yaml.context : "");
|
yaml.context != NULL ? yaml.context : "");
|
||||||
(*error)[cnt] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yml_destroy(root);
|
goto err;
|
||||||
yaml_parser_delete(&yaml);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
|
@ -297,7 +386,14 @@ yml_load(FILE *yml, char **error)
|
||||||
|
|
||||||
struct yml_node *clone = clone_node(NULL, map->node);
|
struct yml_node *clone = clone_node(NULL, map->node);
|
||||||
assert(clone != NULL);
|
assert(clone != NULL);
|
||||||
add_node(n, clone);
|
|
||||||
|
enum yml_error err = add_node(n, clone);
|
||||||
|
if (err != YML_ERR_NONE) {
|
||||||
|
error_str = format_error(err, n, clone);
|
||||||
|
yml_destroy(clone);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -309,7 +405,13 @@ yml_load(FILE *yml, char **error)
|
||||||
new_scalar->type = SCALAR;
|
new_scalar->type = SCALAR;
|
||||||
new_scalar->scalar.value = strndup(
|
new_scalar->scalar.value = strndup(
|
||||||
(const char*)event.data.scalar.value, event.data.scalar.length);
|
(const char*)event.data.scalar.value, event.data.scalar.length);
|
||||||
add_node(n, new_scalar);
|
|
||||||
|
enum yml_error err = add_node(n, new_scalar);
|
||||||
|
if (err != YML_ERR_NONE) {
|
||||||
|
error_str = format_error(err, n, new_scalar);
|
||||||
|
yml_destroy(new_scalar);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.data.scalar.anchor != NULL) {
|
if (event.data.scalar.anchor != NULL) {
|
||||||
const char *anchor = (const char *)event.data.scalar.anchor;
|
const char *anchor = (const char *)event.data.scalar.anchor;
|
||||||
|
@ -324,7 +426,14 @@ yml_load(FILE *yml, char **error)
|
||||||
indent += 2;
|
indent += 2;
|
||||||
struct yml_node *new_list = calloc(1, sizeof(*new_list));
|
struct yml_node *new_list = calloc(1, sizeof(*new_list));
|
||||||
new_list->type = LIST;
|
new_list->type = LIST;
|
||||||
add_node(n, new_list);
|
|
||||||
|
enum yml_error err = add_node(n, new_list);
|
||||||
|
if (err != YML_ERR_NONE) {
|
||||||
|
error_str = format_error(err, n, new_list);
|
||||||
|
yml_destroy(new_list);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
n = new_list;
|
n = new_list;
|
||||||
|
|
||||||
if (event.data.sequence_start.anchor != NULL) {
|
if (event.data.sequence_start.anchor != NULL) {
|
||||||
|
@ -348,7 +457,14 @@ yml_load(FILE *yml, char **error)
|
||||||
|
|
||||||
struct yml_node *new_dict = calloc(1, sizeof(*new_dict));
|
struct yml_node *new_dict = calloc(1, sizeof(*new_dict));
|
||||||
new_dict->type = DICT;
|
new_dict->type = DICT;
|
||||||
add_node(n, new_dict);
|
|
||||||
|
enum yml_error err = add_node(n, new_dict);
|
||||||
|
if (err != YML_ERR_NONE) {
|
||||||
|
error_str = format_error(err, n, new_dict);
|
||||||
|
yml_destroy(new_dict);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
n = new_dict;
|
n = new_dict;
|
||||||
|
|
||||||
if (event.data.mapping_start.anchor != NULL) {
|
if (event.data.mapping_start.anchor != NULL) {
|
||||||
|
@ -375,6 +491,31 @@ yml_load(FILE *yml, char **error)
|
||||||
post_process(root);
|
post_process(root);
|
||||||
//print_node(root);
|
//print_node(root);
|
||||||
return root;
|
return root;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (error_str != NULL) {
|
||||||
|
int cnt = snprintf(
|
||||||
|
NULL, 0, "%zu:%zu: %s",
|
||||||
|
yaml.mark.line + 1,
|
||||||
|
yaml.mark.column,
|
||||||
|
error_str);
|
||||||
|
*error = malloc(cnt + 1);
|
||||||
|
snprintf(
|
||||||
|
*error, cnt + 1, "%zu:%zu: %s",
|
||||||
|
yaml.mark.line + 1,
|
||||||
|
yaml.mark.column,
|
||||||
|
error_str);
|
||||||
|
} else {
|
||||||
|
int cnt = snprintf(NULL, 0, "%zu:%zu: unknown error",
|
||||||
|
yaml.mark.line + 1, yaml.mark.column);
|
||||||
|
*error = malloc(cnt + 1);
|
||||||
|
snprintf(*error, cnt + 1, "%zu:%zu: unknown error",
|
||||||
|
yaml.mark.line + 1, yaml.mark.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
yml_destroy(root);
|
||||||
|
yaml_parser_delete(&yaml);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
Loading…
Add table
Reference in a new issue