From 4474db15e8601cb8aa1a3a8c3b4ae6d8cecd340a Mon Sep 17 00:00:00 2001 From: Olivier Matz Date: Sun, 12 Aug 2018 15:35:26 +0200 Subject: [PATCH] parse yaml as a lib --- Makefile | 20 +- examples/yaml/parse-yaml.c | 541 +------------------------------------ libecoli_yaml/ecoli_yaml.c | 532 ++++++++++++++++++++++++++++++++++++ libecoli_yaml/ecoli_yaml.h | 25 ++ 4 files changed, 582 insertions(+), 536 deletions(-) create mode 100644 libecoli_yaml/ecoli_yaml.c create mode 100644 libecoli_yaml/ecoli_yaml.h diff --git a/Makefile b/Makefile index bb446cf..7bf0ddb 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright 2016, Olivier MATZ -ECOLI ?= $(abspath ..) +ECOLI ?= $(abspath .) include $(ECOLI)/mk/ecoli-pre.mk # output path with trailing slash @@ -9,7 +9,7 @@ O ?= build/ # XXX -O0 CFLAGS = -g -O0 -Wall -Werror -W -Wextra -fPIC -Wmissing-prototypes -CFLAGS += -I. +CFLAGS += -Ilibecoli -Ilibecoli_yaml # XXX coverage CFLAGS += --coverage @@ -55,16 +55,24 @@ srcs += ecoli_parse.c srcs += ecoli_string.c srcs += ecoli_vec.c -shlib-y-$(O)libecoli.so := $(srcs) +# libs +shlib-y-$(O)libecoli.so := $(addprefix libecoli/,$(srcs)) +cflags-$(O)libecoli_yaml.so = -Ilibecoli_yaml +shlib-y-$(O)libecoli_yaml.so := libecoli_yaml/ecoli_yaml.c + +# tests ldflags-$(O)test = -rdynamic -exe-y-$(O)test = $(srcs) main.c +exe-y-$(O)test = $(addprefix libecoli/,$(srcs)) test/test.c +# examples ldflags-$(O)readline = -lreadline -ltermcap -exe-y-$(O)readline = $(srcs) main-readline.c +exe-y-$(O)readline = $(addprefix libecoli/,$(srcs)) \ + examples/readline/main.c ldflags-$(O)parse-yaml = -lyaml -exe-y-$(O)parse-yaml = $(srcs) parse-yaml.c +exe-y-$(O)parse-yaml = $(addprefix libecoli/,$(srcs)) \ + libecoli_yaml/ecoli_yaml.c examples/yaml/parse-yaml.c include $(ECOLI)/mk/ecoli-post.mk diff --git a/examples/yaml/parse-yaml.c b/examples/yaml/parse-yaml.c index 48bcdc7..d5d8460 100644 --- a/examples/yaml/parse-yaml.c +++ b/examples/yaml/parse-yaml.c @@ -1,552 +1,33 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + #include #include -#include -#include -#include -#include -#include -#include #include -#include - -/* associate a yaml node to a ecoli node */ -struct pair { - const yaml_node_t *ynode; - struct ec_node *enode; -}; - -/* store the ecoli node tree and the associations yaml_node <-> ec_node */ -struct enode_tree { - struct ec_node *root; - struct pair *table; - size_t table_len; -}; - -static struct ec_node * -parse_ec_node(struct enode_tree *tree, - const yaml_document_t *document, const yaml_node_t *ynode); - -static struct ec_config * -parse_ec_config_list(struct enode_tree *tree, - const struct ec_config_schema *schema, - const yaml_document_t *document, const yaml_node_t *ynode); - -static struct ec_config * -parse_ec_config_dict(struct enode_tree *tree, - const struct ec_config_schema *schema, - const yaml_document_t *document, const yaml_node_t *ynode); - -/* XXX to utils.c ? */ -static int -parse_llint(const char *str, int64_t *val) -{ - char *endptr; - int save_errno = errno; - - errno = 0; - *val = strtoll(str, &endptr, 0); - - if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) || - (errno != 0 && *val == 0)) - return -1; - - if (*endptr != 0) { - errno = EINVAL; - return -1; - } - - errno = save_errno; - return 0; -} - -static int -parse_ullint(const char *str, uint64_t *val) -{ - char *endptr; - int save_errno = errno; - - /* since a negative input is silently converted to a positive - * one by strtoull(), first check that it is positive */ - if (strchr(str, '-')) - return -1; - - errno = 0; - *val = strtoull(str, &endptr, 0); - - if ((errno == ERANGE && *val == ULLONG_MAX) || - (errno != 0 && *val == 0)) - return -1; - - if (*endptr != 0) - return -1; - - errno = save_errno; - return 0; -} - -static int -parse_bool(const char *str, bool *val) -{ - if (!strcasecmp(str, "true")) { - *val = true; - return 0; - } else if (!strcasecmp(str, "false")) { - *val = false; - return 0; - } - errno = EINVAL; - return -1; -} - -static int -add_in_table(struct enode_tree *tree, const yaml_node_t *ynode, - struct ec_node *enode) -{ - struct pair *table = NULL; - - table = realloc(tree->table, (tree->table_len + 1) * sizeof(*table)); - if (table == NULL) - return -1; - - ec_node_clone(enode); - table[tree->table_len].ynode = ynode; - table[tree->table_len].enode = enode; - tree->table = table; - tree->table_len++; - - return 0; -} - -static void -free_tree(struct enode_tree *tree) -{ - size_t i; - - if (tree->root != NULL) - ec_node_free(tree->root); - for (i = 0; i < tree->table_len; i++) - ec_node_free(tree->table[i].enode); - free(tree->table); -} - -static struct ec_config * -parse_ec_config(struct enode_tree *tree, - const struct ec_config_schema *schema_elt, - const yaml_document_t *document, const yaml_node_t *ynode) -{ - const struct ec_config_schema *subschema; - struct ec_config *config = NULL; - struct ec_node *enode = NULL; - enum ec_config_type type; - const char *value_str; - uint64_t u64; - int64_t i64; - bool boolean; - - type = ec_config_schema_type(schema_elt); - - switch (type) { - case EC_CONFIG_TYPE_BOOL: - if (ynode->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Boolean should be scalar\n"); - goto fail; - } - value_str = (const char *)ynode->data.scalar.value; - if (parse_bool(value_str, &boolean) < 0) { - fprintf(stderr, "Failed to parse boolean\n"); - goto fail; - } - config = ec_config_bool(boolean); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_INT64: - if (ynode->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Int64 should be scalar\n"); - goto fail; - } - value_str = (const char *)ynode->data.scalar.value; - if (parse_llint(value_str, &i64) < 0) { - fprintf(stderr, "Failed to parse i64\n"); - goto fail; - } - config = ec_config_i64(i64); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_UINT64: - if (ynode->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Uint64 should be scalar\n"); - goto fail; - } - value_str = (const char *)ynode->data.scalar.value; - if (parse_ullint(value_str, &u64) < 0) { - fprintf(stderr, "Failed to parse u64\n"); - goto fail; - } - config = ec_config_u64(u64); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_STRING: - if (ynode->type != YAML_SCALAR_NODE) { - fprintf(stderr, "String should be scalar\n"); - goto fail; - } - value_str = (const char *)ynode->data.scalar.value; - config = ec_config_string(value_str); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_NODE: - enode = parse_ec_node(tree, document, ynode); - if (enode == NULL) - goto fail; - config = ec_config_node(enode); - if (config == NULL) { - fprintf(stderr, "Failed to create config\n"); - goto fail; - } - break; - case EC_CONFIG_TYPE_LIST: - subschema = ec_config_schema_sub(schema_elt); - if (subschema == NULL) { - fprintf(stderr, "List has no subschema\n"); - goto fail; - } - config = parse_ec_config_list(tree, subschema, document, ynode); - if (config == NULL) - goto fail; - break; - case EC_CONFIG_TYPE_DICT: - subschema = ec_config_schema_sub(schema_elt); - if (subschema == NULL) { - fprintf(stderr, "Dict has no subschema\n"); - goto fail; - } - config = parse_ec_config_dict(tree, subschema, document, ynode); - if (config == NULL) - goto fail; - break; - default: - fprintf(stderr, "Invalid config type %d\n", type); - goto fail; - } - - return config; - -fail: - ec_node_free(enode); - ec_config_free(config); - return NULL; -} - -static struct ec_config * -parse_ec_config_list(struct enode_tree *tree, - const struct ec_config_schema *schema, - const yaml_document_t *document, const yaml_node_t *ynode) -{ - struct ec_config *config = NULL, *subconfig = NULL; - const yaml_node_item_t *item; - const yaml_node_t *value; - - (void)tree; - (void)schema; - (void)document; - - if (ynode->type != YAML_SEQUENCE_NODE) { - fprintf(stderr, "Ecoli list config should be a yaml sequence\n"); - goto fail; - } - - config = ec_config_list(); - if (config == NULL) { - fprintf(stderr, "Failed to allocate config\n"); - goto fail; - } - - for (item = ynode->data.sequence.items.start; - item < ynode->data.sequence.items.top; item++) { - value = document->nodes.start + (*item) - 1; // XXX -1 ? - subconfig = parse_ec_config(tree, schema, document, value); - if (subconfig == NULL) - goto fail; - if (ec_config_list_add(config, subconfig) < 0) { - fprintf(stderr, "Failed to add list entry\n"); - goto fail; - } - } - - return config; - -fail: - ec_config_free(config); - return NULL; -} - -static struct ec_config * -parse_ec_config_dict(struct enode_tree *tree, - const struct ec_config_schema *schema, - const yaml_document_t *document, const yaml_node_t *ynode) -{ - const struct ec_config_schema *schema_elt; - struct ec_config *config = NULL, *subconfig = NULL; - const yaml_node_t *key, *value; - const yaml_node_pair_t *pair; - const char *key_str; - - if (ynode->type != YAML_MAPPING_NODE) { - fprintf(stderr, "Ecoli config should be a yaml mapping node\n"); - goto fail; - } - - config = ec_config_dict(); - if (config == NULL) { - fprintf(stderr, "Failed to allocate config\n"); - goto fail; - } - - for (pair = ynode->data.mapping.pairs.start; - pair < ynode->data.mapping.pairs.top; pair++) { - key = document->nodes.start + pair->key - 1; // XXX -1 ? - value = document->nodes.start + pair->value - 1; - key_str = (const char *)key->data.scalar.value; - - if (ec_config_key_is_reserved(key_str)) - continue; - schema_elt = ec_config_schema_lookup(schema, key_str); - if (schema_elt == NULL) { - fprintf(stderr, "No such config %s\n", key_str); - goto fail; - } - subconfig = parse_ec_config(tree, schema_elt, document, value); - if (subconfig == NULL) - goto fail; - if (ec_config_dict_set(config, key_str, subconfig) < 0) { - fprintf(stderr, "Failed to set dict entry\n"); - goto fail; - } - } - - return config; - -fail: - ec_config_free(config); - return NULL; -} - -static struct ec_node * -parse_ec_node(struct enode_tree *tree, - const yaml_document_t *document, const yaml_node_t *ynode) -{ - const struct ec_config_schema *schema; - const struct ec_node_type *type = NULL; - const char *id = NULL, *help = NULL; - struct ec_config *config = NULL; - const yaml_node_t *attrs = NULL; - const yaml_node_t *key, *value; - const yaml_node_pair_t *pair; - const char *key_str, *value_str; - struct ec_node *enode = NULL; - - if (ynode->type != YAML_MAPPING_NODE) { - fprintf(stderr, "Ecoli node should be a yaml mapping node\n"); - goto fail; - } - - for (pair = ynode->data.mapping.pairs.start; - pair < ynode->data.mapping.pairs.top; pair++) { - key = document->nodes.start + pair->key - 1; // XXX -1 ? - value = document->nodes.start + pair->value - 1; - key_str = (const char *)key->data.scalar.value; - value_str = (const char *)value->data.scalar.value; - - if (!strcmp(key_str, "type")) { - if (type != NULL) { - fprintf(stderr, "Duplicate type\n"); - goto fail; - } - if (value->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Type must be a string\n"); - goto fail; - } - type = ec_node_type_lookup(value_str); - if (type == NULL) { - fprintf(stderr, "Cannot find type %s\n", - value_str); - goto fail; - } - } else if (!strcmp(key_str, "attrs")) { - if (attrs != NULL) { - fprintf(stderr, "Duplicate attrs\n"); - goto fail; - } - if (value->type != YAML_MAPPING_NODE) { - fprintf(stderr, "Attrs must be a maping\n"); - goto fail; - } - attrs = value; - } else if (!strcmp(key_str, "id")) { - if (id != NULL) { - fprintf(stderr, "Duplicate id\n"); - goto fail; - } - if (value->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Id must be a scalar\n"); - goto fail; - } - id = value_str; - } else if (!strcmp(key_str, "help")) { - if (help != NULL) { - fprintf(stderr, "Duplicate help\n"); - goto fail; - } - if (value->type != YAML_SCALAR_NODE) { - fprintf(stderr, "Help must be a scalar\n"); - goto fail; - } - help = value_str; - } - } - - /* create the ecoli node */ - if (id == NULL) - id = EC_NO_ID; - enode = ec_node_from_type(type, id); - if (enode == NULL) { - fprintf(stderr, "Cannot create ecoli node\n"); - goto fail; - } - if (add_in_table(tree, ynode, enode) < 0) { - fprintf(stderr, "Cannot add node in table\n"); - goto fail; - } - if (tree->root == NULL) { - ec_node_clone(enode); - tree->root = enode; - } - - /* create its config */ - schema = ec_node_type_schema(type); - if (schema == NULL) { - fprintf(stderr, "No configuration schema for type %s\n", - ec_node_type_name(type)); - goto fail; - } - - config = parse_ec_config_dict(tree, schema, document, ynode); - if (config == NULL) - goto fail; - - if (ec_node_set_config(enode, config) < 0) { - fprintf(stderr, "Failed to set config\n"); - goto fail; - } - - /* add attributes (all as string) */ - //XXX - - return enode; - -fail: - ec_node_free(enode); - ec_config_free(config); - return NULL; -} - -static int -parse_document(struct enode_tree *tree, const yaml_document_t *document) -{ - yaml_node_t *node; - - node = document->nodes.start; - if (parse_ec_node(tree, document, node) == NULL) - return -1; - - return 0; -} - -static int parse_file(struct enode_tree *tree, const char *filename) -{ - FILE *file; - yaml_parser_t parser; - yaml_document_t document; - - file = fopen(filename, "rb"); - if (file == NULL) { - fprintf(stderr, "Failed to open file %s\n", filename); - goto fail_no_doc; - } - - if (yaml_parser_initialize(&parser) == 0) { - fprintf(stderr, "Failed to initialize yaml parser\n"); - goto fail_no_doc; - } - - yaml_parser_set_input_file(&parser, file); - - if (yaml_parser_load(&parser, &document) == 0) { - fprintf(stderr, "Failed to load yaml document\n"); - goto fail_no_doc; - } - - if (yaml_document_get_root_node(&document) == NULL) { - fprintf(stderr, "Incomplete document\n"); //XXX check err - goto fail; - } - - if (parse_document(tree, &document) < 0) { - fprintf(stderr, "Failed to parse document\n"); - goto fail; - } - - yaml_document_delete(&document); - yaml_parser_delete(&parser); - fclose(file); - - return 0; - -fail: - yaml_document_delete(&document); -fail_no_doc: - yaml_parser_delete(&parser); - if (file != NULL) - fclose(file); - - return -1; -} +#include int main(int argc, char *argv[]) { - struct enode_tree tree; - - memset(&tree, 0, sizeof(tree)); + struct ec_node *node = NULL; if (argc != 2) { fprintf(stderr, "Invalid args\n"); goto fail; } - if (parse_file(&tree, argv[1]) < 0) { + node = ec_yaml_import(argv[1]); + if (node == NULL) { fprintf(stderr, "Failed to parse file\n"); goto fail; } - printf("root=%p len=%zd\n", tree.root, tree.table_len); - ec_node_dump(stdout, tree.root); - free_tree(&tree); + ec_node_dump(stdout, node); + ec_node_free(node); return 0; fail: - free_tree(&tree); + ec_node_free(node); return 1; } diff --git a/libecoli_yaml/ecoli_yaml.c b/libecoli_yaml/ecoli_yaml.c new file mode 100644 index 0000000..63af83b --- /dev/null +++ b/libecoli_yaml/ecoli_yaml.c @@ -0,0 +1,532 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* associate a yaml node to a ecoli node */ +struct pair { + const yaml_node_t *ynode; + struct ec_node *enode; +}; + +/* store the associations yaml_node <-> ec_node */ +struct enode_table { + struct pair *pair; + size_t len; +}; + +static struct ec_node * +parse_ec_node(struct ec_node **root, struct enode_table *table, + const yaml_document_t *document, const yaml_node_t *ynode); + +static struct ec_config * +parse_ec_config_list(struct ec_node **root, struct enode_table *table, + const struct ec_config_schema *schema, + const yaml_document_t *document, const yaml_node_t *ynode); + +static struct ec_config * +parse_ec_config_dict(struct ec_node **root, struct enode_table *table, + const struct ec_config_schema *schema, + const yaml_document_t *document, const yaml_node_t *ynode); + +/* XXX to utils.c ? */ +static int +parse_llint(const char *str, int64_t *val) +{ + char *endptr; + int save_errno = errno; + + errno = 0; + *val = strtoll(str, &endptr, 0); + + if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) || + (errno != 0 && *val == 0)) + return -1; + + if (*endptr != 0) { + errno = EINVAL; + return -1; + } + + errno = save_errno; + return 0; +} + +static int +parse_ullint(const char *str, uint64_t *val) +{ + char *endptr; + int save_errno = errno; + + /* since a negative input is silently converted to a positive + * one by strtoull(), first check that it is positive */ + if (strchr(str, '-')) + return -1; + + errno = 0; + *val = strtoull(str, &endptr, 0); + + if ((errno == ERANGE && *val == ULLONG_MAX) || + (errno != 0 && *val == 0)) + return -1; + + if (*endptr != 0) + return -1; + + errno = save_errno; + return 0; +} + +static int +parse_bool(const char *str, bool *val) +{ + if (!strcasecmp(str, "true")) { + *val = true; + return 0; + } else if (!strcasecmp(str, "false")) { + *val = false; + return 0; + } + errno = EINVAL; + return -1; +} + +static int +add_in_table(struct enode_table *table, + const yaml_node_t *ynode, struct ec_node *enode) +{ + struct pair *pair = NULL; + + pair = realloc(table->pair, (table->len + 1) * sizeof(*pair)); + if (pair == NULL) + return -1; + + ec_node_clone(enode); + pair[table->len].ynode = ynode; + pair[table->len].enode = enode; + table->pair = pair; + table->len++; + + return 0; +} + +static void +free_table(struct enode_table *table) +{ + size_t i; + + for (i = 0; i < table->len; i++) + ec_node_free(table->pair[i].enode); + free(table->pair); +} + +static struct ec_config * +parse_ec_config(struct ec_node **root, struct enode_table *table, + const struct ec_config_schema *schema_elt, + const yaml_document_t *document, const yaml_node_t *ynode) +{ + const struct ec_config_schema *subschema; + struct ec_config *config = NULL; + struct ec_node *enode = NULL; + enum ec_config_type type; + const char *value_str; + uint64_t u64; + int64_t i64; + bool boolean; + + type = ec_config_schema_type(schema_elt); + + switch (type) { + case EC_CONFIG_TYPE_BOOL: + if (ynode->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Boolean should be scalar\n"); + goto fail; + } + value_str = (const char *)ynode->data.scalar.value; + if (parse_bool(value_str, &boolean) < 0) { + fprintf(stderr, "Failed to parse boolean\n"); + goto fail; + } + config = ec_config_bool(boolean); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_INT64: + if (ynode->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Int64 should be scalar\n"); + goto fail; + } + value_str = (const char *)ynode->data.scalar.value; + if (parse_llint(value_str, &i64) < 0) { + fprintf(stderr, "Failed to parse i64\n"); + goto fail; + } + config = ec_config_i64(i64); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_UINT64: + if (ynode->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Uint64 should be scalar\n"); + goto fail; + } + value_str = (const char *)ynode->data.scalar.value; + if (parse_ullint(value_str, &u64) < 0) { + fprintf(stderr, "Failed to parse u64\n"); + goto fail; + } + config = ec_config_u64(u64); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_STRING: + if (ynode->type != YAML_SCALAR_NODE) { + fprintf(stderr, "String should be scalar\n"); + goto fail; + } + value_str = (const char *)ynode->data.scalar.value; + config = ec_config_string(value_str); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_NODE: + enode = parse_ec_node(root, table, document, ynode); + if (enode == NULL) + goto fail; + config = ec_config_node(enode); + if (config == NULL) { + fprintf(stderr, "Failed to create config\n"); + goto fail; + } + break; + case EC_CONFIG_TYPE_LIST: + subschema = ec_config_schema_sub(schema_elt); + if (subschema == NULL) { + fprintf(stderr, "List has no subschema\n"); + goto fail; + } + config = parse_ec_config_list(root, table, subschema, document, ynode); + if (config == NULL) + goto fail; + break; + case EC_CONFIG_TYPE_DICT: + subschema = ec_config_schema_sub(schema_elt); + if (subschema == NULL) { + fprintf(stderr, "Dict has no subschema\n"); + goto fail; + } + config = parse_ec_config_dict(root, table, subschema, document, ynode); + if (config == NULL) + goto fail; + break; + default: + fprintf(stderr, "Invalid config type %d\n", type); + goto fail; + } + + return config; + +fail: + ec_node_free(enode); + ec_config_free(config); + return NULL; +} + +static struct ec_config * +parse_ec_config_list(struct ec_node **root, struct enode_table *table, + const struct ec_config_schema *schema, + const yaml_document_t *document, const yaml_node_t *ynode) +{ + struct ec_config *config = NULL, *subconfig = NULL; + const yaml_node_item_t *item; + const yaml_node_t *value; + + if (ynode->type != YAML_SEQUENCE_NODE) { + fprintf(stderr, "Ecoli list config should be a yaml sequence\n"); + goto fail; + } + + config = ec_config_list(); + if (config == NULL) { + fprintf(stderr, "Failed to allocate config\n"); + goto fail; + } + + for (item = ynode->data.sequence.items.start; + item < ynode->data.sequence.items.top; item++) { + value = document->nodes.start + (*item) - 1; // XXX -1 ? + subconfig = parse_ec_config(root, table, schema, document, value); + if (subconfig == NULL) + goto fail; + if (ec_config_list_add(config, subconfig) < 0) { + fprintf(stderr, "Failed to add list entry\n"); + goto fail; + } + } + + return config; + +fail: + ec_config_free(config); + return NULL; +} + +static struct ec_config * +parse_ec_config_dict(struct ec_node **root, struct enode_table *table, + const struct ec_config_schema *schema, + const yaml_document_t *document, const yaml_node_t *ynode) +{ + const struct ec_config_schema *schema_elt; + struct ec_config *config = NULL, *subconfig = NULL; + const yaml_node_t *key, *value; + const yaml_node_pair_t *pair; + const char *key_str; + + if (ynode->type != YAML_MAPPING_NODE) { + fprintf(stderr, "Ecoli config should be a yaml mapping node\n"); + goto fail; + } + + config = ec_config_dict(); + if (config == NULL) { + fprintf(stderr, "Failed to allocate config\n"); + goto fail; + } + + for (pair = ynode->data.mapping.pairs.start; + pair < ynode->data.mapping.pairs.top; pair++) { + key = document->nodes.start + pair->key - 1; // XXX -1 ? + value = document->nodes.start + pair->value - 1; + key_str = (const char *)key->data.scalar.value; + + if (ec_config_key_is_reserved(key_str)) + continue; + schema_elt = ec_config_schema_lookup(schema, key_str); + if (schema_elt == NULL) { + fprintf(stderr, "No such config %s\n", key_str); + goto fail; + } + subconfig = parse_ec_config(root, table, schema_elt, document, value); + if (subconfig == NULL) + goto fail; + if (ec_config_dict_set(config, key_str, subconfig) < 0) { + fprintf(stderr, "Failed to set dict entry\n"); + goto fail; + } + } + + return config; + +fail: + ec_config_free(config); + return NULL; +} + +static struct ec_node * +parse_ec_node(struct ec_node **root, struct enode_table *table, + const yaml_document_t *document, const yaml_node_t *ynode) +{ + const struct ec_config_schema *schema; + const struct ec_node_type *type = NULL; + const char *id = NULL, *help = NULL; + struct ec_config *config = NULL; + const yaml_node_t *attrs = NULL; + const yaml_node_t *key, *value; + const yaml_node_pair_t *pair; + const char *key_str, *value_str; + struct ec_node *enode = NULL; + + if (ynode->type != YAML_MAPPING_NODE) { + fprintf(stderr, "Ecoli node should be a yaml mapping node\n"); + goto fail; + } + + for (pair = ynode->data.mapping.pairs.start; + pair < ynode->data.mapping.pairs.top; pair++) { + key = document->nodes.start + pair->key - 1; // XXX -1 ? + value = document->nodes.start + pair->value - 1; + key_str = (const char *)key->data.scalar.value; + value_str = (const char *)value->data.scalar.value; + + if (!strcmp(key_str, "type")) { + if (type != NULL) { + fprintf(stderr, "Duplicate type\n"); + goto fail; + } + if (value->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Type must be a string\n"); + goto fail; + } + type = ec_node_type_lookup(value_str); + if (type == NULL) { + fprintf(stderr, "Cannot find type %s\n", + value_str); + goto fail; + } + } else if (!strcmp(key_str, "attrs")) { + if (attrs != NULL) { + fprintf(stderr, "Duplicate attrs\n"); + goto fail; + } + if (value->type != YAML_MAPPING_NODE) { + fprintf(stderr, "Attrs must be a maping\n"); + goto fail; + } + attrs = value; + } else if (!strcmp(key_str, "id")) { + if (id != NULL) { + fprintf(stderr, "Duplicate id\n"); + goto fail; + } + if (value->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Id must be a scalar\n"); + goto fail; + } + id = value_str; + } else if (!strcmp(key_str, "help")) { + if (help != NULL) { + fprintf(stderr, "Duplicate help\n"); + goto fail; + } + if (value->type != YAML_SCALAR_NODE) { + fprintf(stderr, "Help must be a scalar\n"); + goto fail; + } + help = value_str; + } + } + + /* create the ecoli node */ + if (id == NULL) + id = EC_NO_ID; + enode = ec_node_from_type(type, id); + if (enode == NULL) { + fprintf(stderr, "Cannot create ecoli node\n"); + goto fail; + } + if (add_in_table(table, ynode, enode) < 0) { + fprintf(stderr, "Cannot add node in table\n"); + goto fail; + } + if (*root == NULL) { + ec_node_clone(enode); + *root = enode; + } + + /* create its config */ + schema = ec_node_type_schema(type); + if (schema == NULL) { + fprintf(stderr, "No configuration schema for type %s\n", + ec_node_type_name(type)); + goto fail; + } + + config = parse_ec_config_dict(root, table, schema, document, ynode); + if (config == NULL) + goto fail; + + if (ec_node_set_config(enode, config) < 0) { + fprintf(stderr, "Failed to set config\n"); + goto fail; + } + + /* add attributes (all as string) */ + //XXX + + return enode; + +fail: + ec_node_free(enode); + ec_config_free(config); + return NULL; +} + +static int +parse_document(struct ec_node **root, struct enode_table *table, + const yaml_document_t *document) +{ + yaml_node_t *node; + + node = document->nodes.start; + if (parse_ec_node(root, table, document, node) == NULL) + return -1; + + return 0; +} + +struct ec_node * +ec_yaml_import(const char *filename) +{ + FILE *file; + yaml_parser_t parser; + yaml_document_t document; + struct ec_node *root = NULL; + struct enode_table table; + + memset(&table, 0, sizeof(table)); + + file = fopen(filename, "rb"); + if (file == NULL) { + fprintf(stderr, "Failed to open file %s\n", filename); + goto fail_no_doc; + } + + if (yaml_parser_initialize(&parser) == 0) { + fprintf(stderr, "Failed to initialize yaml parser\n"); + goto fail_no_doc; + } + + yaml_parser_set_input_file(&parser, file); + + if (yaml_parser_load(&parser, &document) == 0) { + fprintf(stderr, "Failed to load yaml document\n"); + goto fail_no_doc; + } + + if (yaml_document_get_root_node(&document) == NULL) { + fprintf(stderr, "Incomplete document\n"); //XXX check err + goto fail; + } + + if (parse_document(&root, &table, &document) < 0) { + fprintf(stderr, "Failed to parse document\n"); + goto fail; + } + + yaml_document_delete(&document); + yaml_parser_delete(&parser); + fclose(file); + free_table(&table); + + return root; + +fail: + yaml_document_delete(&document); +fail_no_doc: + yaml_parser_delete(&parser); + if (file != NULL) + fclose(file); + ec_node_free(root); + + return NULL; +} diff --git a/libecoli_yaml/ecoli_yaml.h b/libecoli_yaml/ecoli_yaml.h new file mode 100644 index 0000000..a63d837 --- /dev/null +++ b/libecoli_yaml/ecoli_yaml.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2018, Olivier MATZ + */ + +/** + * Interface to import/export ecoli data structures in YAML. + */ + +#ifndef ECOLI_NODE_YAML_ +#define ECOLI_NODE_YAML_ + +struct ec_node; + +/** + * Parse a YAML file and build an ec_node tree from it. + * + * @param filename + * The path to the file to be parsed. + * @return + * The ec_node tree on success, or NULL on error (errno is set). + * The returned node must be freed by the caller with ec_node_free(). + */ +struct ec_node *ec_yaml_import(const char *filename); + +#endif -- 2.39.5