add meson support
authorOlivier Matz <zer0@droids-corp.org>
Mon, 11 Feb 2019 14:39:07 +0000 (15:39 +0100)
committerOlivier Matz <zer0@droids-corp.org>
Wed, 13 Feb 2019 15:05:01 +0000 (16:05 +0100)
185 files changed:
Makefile [deleted file]
examples/meson.build [new file with mode: 0644]
examples/parse-yaml/meson.build [new file with mode: 0644]
examples/parse-yaml/parse-yaml.c [new file with mode: 0644]
examples/parse-yaml/test.yaml [new file with mode: 0644]
examples/readline/meson.build [new file with mode: 0644]
examples/yaml/parse-yaml.c [deleted file]
examples/yaml/test.yaml [deleted file]
include/ecoli_assert.h [new file with mode: 0644]
include/ecoli_complete.h [new file with mode: 0644]
include/ecoli_config.h [new file with mode: 0644]
include/ecoli_editline.h [new file with mode: 0644]
include/ecoli_init.h [new file with mode: 0644]
include/ecoli_keyval.h [new file with mode: 0644]
include/ecoli_log.h [new file with mode: 0644]
include/ecoli_malloc.h [new file with mode: 0644]
include/ecoli_murmurhash.h [new file with mode: 0644]
include/ecoli_node.h [new file with mode: 0644]
include/ecoli_node_any.h [new file with mode: 0644]
include/ecoli_node_cmd.h [new file with mode: 0644]
include/ecoli_node_dynamic.h [new file with mode: 0644]
include/ecoli_node_empty.h [new file with mode: 0644]
include/ecoli_node_expr.h [new file with mode: 0644]
include/ecoli_node_file.h [new file with mode: 0644]
include/ecoli_node_helper.h [new file with mode: 0644]
include/ecoli_node_int.h [new file with mode: 0644]
include/ecoli_node_many.h [new file with mode: 0644]
include/ecoli_node_none.h [new file with mode: 0644]
include/ecoli_node_once.h [new file with mode: 0644]
include/ecoli_node_option.h [new file with mode: 0644]
include/ecoli_node_or.h [new file with mode: 0644]
include/ecoli_node_re.h [new file with mode: 0644]
include/ecoli_node_re_lex.h [new file with mode: 0644]
include/ecoli_node_seq.h [new file with mode: 0644]
include/ecoli_node_sh_lex.h [new file with mode: 0644]
include/ecoli_node_space.h [new file with mode: 0644]
include/ecoli_node_str.h [new file with mode: 0644]
include/ecoli_node_subset.h [new file with mode: 0644]
include/ecoli_parse.h [new file with mode: 0644]
include/ecoli_string.h [new file with mode: 0644]
include/ecoli_strvec.h [new file with mode: 0644]
include/ecoli_test.h [new file with mode: 0644]
include/ecoli_utils.h [new file with mode: 0644]
include/ecoli_vec.h [new file with mode: 0644]
include/ecoli_yaml.h [new file with mode: 0644]
include/meson.build [new file with mode: 0644]
libecoli/ecoli_assert.c [deleted file]
libecoli/ecoli_assert.h [deleted file]
libecoli/ecoli_complete.c [deleted file]
libecoli/ecoli_complete.h [deleted file]
libecoli/ecoli_config.c [deleted file]
libecoli/ecoli_config.h [deleted file]
libecoli/ecoli_init.c [deleted file]
libecoli/ecoli_init.h [deleted file]
libecoli/ecoli_keyval.c [deleted file]
libecoli/ecoli_keyval.h [deleted file]
libecoli/ecoli_log.c [deleted file]
libecoli/ecoli_log.h [deleted file]
libecoli/ecoli_malloc.c [deleted file]
libecoli/ecoli_malloc.h [deleted file]
libecoli/ecoli_murmurhash.c [deleted file]
libecoli/ecoli_murmurhash.h [deleted file]
libecoli/ecoli_node.c [deleted file]
libecoli/ecoli_node.h [deleted file]
libecoli/ecoli_node_any.c [deleted file]
libecoli/ecoli_node_any.h [deleted file]
libecoli/ecoli_node_cmd.c [deleted file]
libecoli/ecoli_node_cmd.h [deleted file]
libecoli/ecoli_node_dynamic.c [deleted file]
libecoli/ecoli_node_dynamic.h [deleted file]
libecoli/ecoli_node_empty.c [deleted file]
libecoli/ecoli_node_empty.h [deleted file]
libecoli/ecoli_node_expr.c [deleted file]
libecoli/ecoli_node_expr.h [deleted file]
libecoli/ecoli_node_expr_test.c [deleted file]
libecoli/ecoli_node_file.c [deleted file]
libecoli/ecoli_node_file.h [deleted file]
libecoli/ecoli_node_helper.c [deleted file]
libecoli/ecoli_node_helper.h [deleted file]
libecoli/ecoli_node_int.c [deleted file]
libecoli/ecoli_node_int.h [deleted file]
libecoli/ecoli_node_many.c [deleted file]
libecoli/ecoli_node_many.h [deleted file]
libecoli/ecoli_node_none.c [deleted file]
libecoli/ecoli_node_none.h [deleted file]
libecoli/ecoli_node_once.c [deleted file]
libecoli/ecoli_node_once.h [deleted file]
libecoli/ecoli_node_option.c [deleted file]
libecoli/ecoli_node_option.h [deleted file]
libecoli/ecoli_node_or.c [deleted file]
libecoli/ecoli_node_or.h [deleted file]
libecoli/ecoli_node_re.c [deleted file]
libecoli/ecoli_node_re.h [deleted file]
libecoli/ecoli_node_re_lex.c [deleted file]
libecoli/ecoli_node_re_lex.h [deleted file]
libecoli/ecoli_node_seq.c [deleted file]
libecoli/ecoli_node_seq.h [deleted file]
libecoli/ecoli_node_sh_lex.c [deleted file]
libecoli/ecoli_node_sh_lex.h [deleted file]
libecoli/ecoli_node_space.c [deleted file]
libecoli/ecoli_node_space.h [deleted file]
libecoli/ecoli_node_str.c [deleted file]
libecoli/ecoli_node_str.h [deleted file]
libecoli/ecoli_node_subset.c [deleted file]
libecoli/ecoli_node_subset.h [deleted file]
libecoli/ecoli_parse.c [deleted file]
libecoli/ecoli_parse.h [deleted file]
libecoli/ecoli_string.c [deleted file]
libecoli/ecoli_string.h [deleted file]
libecoli/ecoli_strvec.c [deleted file]
libecoli/ecoli_strvec.h [deleted file]
libecoli/ecoli_test.c [deleted file]
libecoli/ecoli_test.h [deleted file]
libecoli/ecoli_utils.h [deleted file]
libecoli/ecoli_vec.c [deleted file]
libecoli/ecoli_vec.h [deleted file]
libecoli_editline/ecoli_editline.c [deleted file]
libecoli_editline/ecoli_editline.h [deleted file]
libecoli_editline/editline.c [deleted file]
libecoli_yaml/ecoli_yaml.c [deleted file]
libecoli_yaml/ecoli_yaml.h [deleted file]
meson.build [new file with mode: 0644]
mk/ecoli-ar-rules.mk [deleted file]
mk/ecoli-ar-vars.mk [deleted file]
mk/ecoli-clean-rules.mk [deleted file]
mk/ecoli-clean-vars.mk [deleted file]
mk/ecoli-copy-rules.mk [deleted file]
mk/ecoli-copy-vars.mk [deleted file]
mk/ecoli-exe-rules.mk [deleted file]
mk/ecoli-exe-vars.mk [deleted file]
mk/ecoli-obj-rules.mk [deleted file]
mk/ecoli-obj-vars.mk [deleted file]
mk/ecoli-objcopy-rules.mk [deleted file]
mk/ecoli-objcopy-vars.mk [deleted file]
mk/ecoli-post.mk [deleted file]
mk/ecoli-pre.mk [deleted file]
mk/ecoli-shlib-rules.mk [deleted file]
mk/ecoli-shlib-vars.mk [deleted file]
mk/ecoli-slink-rules.mk [deleted file]
mk/ecoli-slink-vars.mk [deleted file]
mk/ecoli-subdir-rules.mk [deleted file]
mk/ecoli-subdir-vars.mk [deleted file]
mk/ecoli-tools.mk [deleted file]
mk/ecoli-vars.mk [deleted file]
src/ecoli_assert.c [new file with mode: 0644]
src/ecoli_complete.c [new file with mode: 0644]
src/ecoli_config.c [new file with mode: 0644]
src/ecoli_editline.c [new file with mode: 0644]
src/ecoli_init.c [new file with mode: 0644]
src/ecoli_keyval.c [new file with mode: 0644]
src/ecoli_log.c [new file with mode: 0644]
src/ecoli_malloc.c [new file with mode: 0644]
src/ecoli_murmurhash.c [new file with mode: 0644]
src/ecoli_node.c [new file with mode: 0644]
src/ecoli_node_any.c [new file with mode: 0644]
src/ecoli_node_cmd.c [new file with mode: 0644]
src/ecoli_node_dynamic.c [new file with mode: 0644]
src/ecoli_node_empty.c [new file with mode: 0644]
src/ecoli_node_expr.c [new file with mode: 0644]
src/ecoli_node_expr_test.c [new file with mode: 0644]
src/ecoli_node_file.c [new file with mode: 0644]
src/ecoli_node_helper.c [new file with mode: 0644]
src/ecoli_node_int.c [new file with mode: 0644]
src/ecoli_node_many.c [new file with mode: 0644]
src/ecoli_node_none.c [new file with mode: 0644]
src/ecoli_node_once.c [new file with mode: 0644]
src/ecoli_node_option.c [new file with mode: 0644]
src/ecoli_node_or.c [new file with mode: 0644]
src/ecoli_node_re.c [new file with mode: 0644]
src/ecoli_node_re_lex.c [new file with mode: 0644]
src/ecoli_node_seq.c [new file with mode: 0644]
src/ecoli_node_sh_lex.c [new file with mode: 0644]
src/ecoli_node_space.c [new file with mode: 0644]
src/ecoli_node_str.c [new file with mode: 0644]
src/ecoli_node_subset.c [new file with mode: 0644]
src/ecoli_parse.c [new file with mode: 0644]
src/ecoli_string.c [new file with mode: 0644]
src/ecoli_strvec.c [new file with mode: 0644]
src/ecoli_test.c [new file with mode: 0644]
src/ecoli_vec.c [new file with mode: 0644]
src/ecoli_yaml.c [new file with mode: 0644]
src/editline.c [new file with mode: 0644]
src/meson.build [new file with mode: 0644]
test/meson.build [new file with mode: 0644]
todo.txt

diff --git a/Makefile b/Makefile
deleted file mode 100644 (file)
index 8ccd7e2..0000000
--- a/Makefile
+++ /dev/null
@@ -1,87 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
-
-ECOLI ?= $(abspath .)
-include $(ECOLI)/mk/ecoli-pre.mk
-
-# output path with trailing slash
-O ?= build/
-
-# XXX -O0
-CFLAGS  = -g -O0 -Wall -Werror -W -Wextra -fPIC -Wmissing-prototypes
-CFLAGS += -Ilibecoli -Ilibecoli_yaml -Ilibecoli_editline
-
-# XXX coverage
-CFLAGS += --coverage
-LDFLAGS += --coverage
-#  rm -rf build; rm -rf result; make && ./build/test
-#  lcov -d build -c -t build/test -o test.info && genhtml -o result test.info
-
-
-srcs :=
-srcs += ecoli_assert.c
-srcs += ecoli_complete.c
-srcs += ecoli_config.c
-srcs += ecoli_keyval.c
-srcs += ecoli_init.c
-srcs += ecoli_log.c
-srcs += ecoli_malloc.c
-srcs += ecoli_murmurhash.c
-srcs += ecoli_strvec.c
-srcs += ecoli_test.c
-srcs += ecoli_node.c
-srcs += ecoli_node_any.c
-srcs += ecoli_node_cmd.c
-srcs += ecoli_node_empty.c
-srcs += ecoli_node_expr.c
-srcs += ecoli_node_expr_test.c
-srcs += ecoli_node_dynamic.c
-srcs += ecoli_node_file.c
-srcs += ecoli_node_helper.c
-srcs += ecoli_node_int.c
-srcs += ecoli_node_many.c
-srcs += ecoli_node_none.c
-srcs += ecoli_node_once.c
-srcs += ecoli_node_option.c
-srcs += ecoli_node_or.c
-srcs += ecoli_node_re.c
-srcs += ecoli_node_re_lex.c
-srcs += ecoli_node_seq.c
-srcs += ecoli_node_sh_lex.c
-srcs += ecoli_node_space.c
-srcs += ecoli_node_str.c
-srcs += ecoli_node_subset.c
-srcs += ecoli_parse.c
-srcs += ecoli_string.c
-srcs += ecoli_vec.c
-
-# 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
-
-cflags-$(O)libecoli_editline.so = -Ilibecoli_editline
-shlib-y-$(O)libecoli_editline.so := libecoli_editline/ecoli_editline.c
-
-# tests
-ldflags-$(O)test = -rdynamic
-exe-y-$(O)test = $(addprefix libecoli/,$(srcs)) test/test.c
-
-# examples
-ldflags-$(O)readline = -lreadline -ltermcap
-exe-y-$(O)readline = $(addprefix libecoli/,$(srcs)) \
-       examples/readline/main.c
-
-ldflags-$(O)parse-yaml = -lyaml -ledit
-exe-y-$(O)parse-yaml = $(addprefix libecoli/,$(srcs)) \
-       libecoli_yaml/ecoli_yaml.c libecoli_editline/ecoli_editline.c\
-       examples/yaml/parse-yaml.c
-
-include $(ECOLI)/mk/ecoli-post.mk
-
-all: _ecoli_all
-
-clean: _ecoli_clean
-
-.PHONY: clean all
diff --git a/examples/meson.build b/examples/meson.build
new file mode 100644 (file)
index 0000000..1435bfe
--- /dev/null
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+
+subdir('readline')
+subdir('parse-yaml')
diff --git a/examples/parse-yaml/meson.build b/examples/parse-yaml/meson.build
new file mode 100644 (file)
index 0000000..118fdc3
--- /dev/null
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+
+inc = include_directories('../../include')
+
+parse_yaml_sources = [
+       'parse-yaml.c',
+]
+
+ecoli_parse_yaml = executable(
+       'ecoli-parse-yaml',
+       parse_yaml_sources,
+       include_directories : inc,
+       link_with : libecoli,
+       dependencies: [yaml_dep, edit_dep])
diff --git a/examples/parse-yaml/parse-yaml.c b/examples/parse-yaml/parse-yaml.c
new file mode 100644 (file)
index 0000000..218fe78
--- /dev/null
@@ -0,0 +1,310 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+
+#include <ecoli_init.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_yaml.h>
+#include <ecoli_editline.h>
+#include <ecoli_node_sh_lex.h>
+
+static char *input_file;
+static char *output_file;
+static bool complete;
+
+static const char short_options[] =
+       "h"  /* help */
+       "i:" /* input-file */
+       "o:" /* output-file */
+       "c"  /* complete */
+       ;
+
+#define OPT_HELP "help"
+#define OPT_INPUT_FILE "input-file"
+#define OPT_OUTPUT_FILE "output-file"
+#define OPT_COMPLETE "complete"
+
+static const struct option long_options[] = {
+       {OPT_HELP, 0, NULL, 'h'},
+       {OPT_INPUT_FILE, 1, NULL, 'i'},
+       {OPT_OUTPUT_FILE, 1, NULL, 'o'},
+       {OPT_COMPLETE, 0, NULL, 'c'},
+       {NULL, 0, NULL, 0}
+};
+
+static void usage(const char *prgname)
+{
+       fprintf(stderr, "%s -o <file.sh> -i <file.yaml>\n"
+               "  -h\n"
+               "  --"OPT_HELP"\n"
+               "      Show this help.\n"
+               "  -i <input-file>\n"
+               "  --"OPT_INPUT_FILE"=<file>\n"
+               "      Set the yaml input file describing the grammar.\n"
+               "  -o <output-file>\n"
+               "  --"OPT_OUTPUT_FILE"=<file>\n"
+               "      Set the output file.\n"
+               "  -c\n"
+               "  --"OPT_COMPLETE"\n"
+               "      Output the completion list."
+               , prgname);
+}
+
+static int parse_args(int argc, char **argv)
+{
+       int ret, opt;
+
+       while ((opt = getopt_long(argc, argv, short_options,
+                               long_options, NULL)) != EOF) {
+
+               switch (opt) {
+               case 'h': /* help */
+                       usage(argv[0]);
+                       exit(0);
+
+               case 'i': /* input-file */
+                       input_file = strdup(optarg);
+                       break;
+
+               case 'o': /* output-file */
+                       output_file = strdup(optarg);
+                       break;
+
+               case 'c': /* complete */
+                       complete = 1;
+                       break;
+
+               default:
+                       usage(argv[0]);
+                       return -1;
+               }
+
+       }
+
+       if (input_file == NULL) {
+               fprintf(stderr, "No input file\n");
+               usage(argv[0]);
+               return -1;
+       }
+       if (output_file == NULL) {
+               fprintf(stderr, "No output file\n");
+               usage(argv[0]);
+               return -1;
+       }
+
+       ret = optind - 1;
+       optind = 1;
+
+       return ret;
+}
+
+static int
+__dump_as_shell(FILE *f, const struct ec_parse *parse, size_t *seq)
+{
+       const struct ec_node *node = ec_parse_get_node(parse);
+       struct ec_parse *child;
+       size_t cur_seq, i, len;
+       const char *s;
+
+       (*seq)++;
+       cur_seq = *seq;
+
+       // XXX protect strings
+
+
+       fprintf(f, "ec_node%zu_id='%s'\n", cur_seq, ec_node_id(node));
+       fprintf(f, "ec_node%zu_type='%s'\n", cur_seq,
+               ec_node_type_name(ec_node_type(node)));
+
+       len = ec_strvec_len(ec_parse_strvec(parse));
+       fprintf(f, "ec_node%zu_strvec_len=%zu\n", cur_seq, len);
+       for (i = 0; i < len; i++) {
+               s = ec_strvec_val(ec_parse_strvec(parse), i);
+               fprintf(f, "ec_node%zu_str%zu='%s'\n", cur_seq, i, s);
+       }
+
+       if (ec_parse_get_first_child(parse) != NULL) {
+               fprintf(f, "ec_node%zu_first_child='ec_node%zu'\n",
+                       cur_seq, cur_seq + 1);
+       }
+
+       EC_PARSE_FOREACH_CHILD(child, parse) {
+               fprintf(f, "ec_node%zu_parent='ec_node%zu'\n",
+                       *seq + 1, cur_seq);
+               __dump_as_shell(f, child, seq);
+       }
+
+       if (ec_parse_get_next(parse) != NULL) {
+               fprintf(f, "ec_node%zu_next='ec_node%zu'\n",
+                       cur_seq, *seq + 1);
+       }
+
+       return 0;
+}
+
+static int
+dump_as_shell(const struct ec_parse *parse)
+{
+       FILE *f;
+       size_t seq = 0;
+       int ret;
+
+       f = fopen(output_file, "w");
+       if (f == NULL)
+               return -1;
+
+       ret = __dump_as_shell(f, parse, &seq);
+
+       fclose(f);
+
+       return ret;
+}
+
+static int
+interact(struct ec_node *node)
+{
+       struct ec_editline *editline = NULL;
+       struct ec_parse *parse = NULL;
+       struct ec_node *shlex = NULL;
+       char *line = NULL;
+
+       shlex = ec_node_sh_lex(EC_NO_ID, ec_node_clone(node)); //XXX
+       if (shlex == NULL) {
+               fprintf(stderr, "Failed to add lexer node\n");
+               goto fail;
+       }
+
+       editline = ec_editline("ecoli", stdin, stdout, stderr, 0);
+       if (editline == NULL) {
+               fprintf(stderr, "Failed to initialize editline\n");
+               goto fail;
+       }
+
+       parse = ec_editline_parse(editline, shlex);
+       if (parse == NULL)
+               goto fail;
+
+       if (!ec_parse_matches(parse))
+               goto fail;
+
+       //ec_parse_dump(stdout, parse);
+
+       if (dump_as_shell(parse) < 0) {
+               fprintf(stderr, "Failed to dump the parsed result\n");
+               goto fail;
+       }
+
+       ec_parse_free(parse);
+       ec_editline_free(editline);
+       ec_node_free(shlex);
+       return 0;
+
+fail:
+       ec_parse_free(parse);
+       ec_editline_free(editline);
+       free(line);
+       ec_node_free(shlex);
+       return -1;
+}
+
+static int
+complete_words(const struct ec_node *node, int argc, char *argv[])
+{
+       struct ec_comp *comp = NULL;
+       struct ec_strvec *strvec = NULL;
+       struct ec_comp_iter *iter = NULL;
+       struct ec_comp_item *item = NULL;
+       size_t count;
+
+       if (argc <= 1)
+               goto fail;
+       strvec = ec_strvec_from_array((const char * const *)&argv[1],
+                               argc - 1);
+       if (strvec == NULL)
+               goto fail;
+
+       comp = ec_node_complete_strvec(node, strvec);
+       if (comp == NULL)
+               goto fail;
+
+       count = ec_comp_count(comp, EC_COMP_UNKNOWN | EC_COMP_FULL |
+                       EC_COMP_PARTIAL);
+
+       iter = ec_comp_iter(comp,
+               EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL);
+       if (iter == NULL)
+               goto fail;
+
+       while ((item = ec_comp_iter_next(iter)) != NULL) {
+
+               /* only one match, display it fully */
+               if (count == 1) {
+                       printf("%s\n", ec_comp_item_get_str(item));
+                       break;
+               }
+
+               /* else show the 'display' part only */
+               printf("%s\n", ec_comp_item_get_display(item));
+       }
+
+       ec_comp_iter_free(iter);
+       ec_comp_free(comp);
+       ec_strvec_free(strvec);
+       return 0;
+
+fail:
+       ec_comp_free(comp);
+       ec_strvec_free(strvec);
+       return -1;
+}
+
+int
+main(int argc, char *argv[])
+{
+       struct ec_node *node = NULL;
+       int ret;
+
+       ret = parse_args(argc, argv);
+       if (ret < 0)
+               goto fail;
+
+       argc -= ret;
+       argv += ret;
+
+       if (ec_init() < 0) {
+               fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
+               return 1;
+       }
+
+       node = ec_yaml_import(input_file);
+       if (node == NULL) {
+               fprintf(stderr, "Failed to parse file\n");
+               goto fail;
+       }
+       //ec_node_dump(stdout, node);
+
+       if (complete) {
+               if (complete_words(node, argc, argv) < 0)
+                       goto fail;
+       } else {
+               if (interact(node) < 0)
+                       goto fail;
+       }
+
+       ec_node_free(node);
+
+       return 0;
+
+fail:
+       ec_node_free(node);
+       return 1;
+}
diff --git a/examples/parse-yaml/test.yaml b/examples/parse-yaml/test.yaml
new file mode 100644 (file)
index 0000000..5642766
--- /dev/null
@@ -0,0 +1,32 @@
+type: or
+children:
+- type: seq
+  id: hello
+  help: Say hello to someone
+  children:
+  - type: str
+    string: hello
+  - type: or
+    id: name
+    help: Name of the person to greet
+    children:
+    - type: str
+      string: john
+    - type: str
+      string: mike
+- type: seq
+  id: goodbye
+  help: Say good bye to someone
+  children:
+  - type: str
+    string: good
+  - type: str
+    string: bye
+  - type: or
+    id: name
+    help: Name of the person to greet
+    children:
+    - type: str
+      string: mary
+    - type: str
+      string: jessica
diff --git a/examples/readline/meson.build b/examples/readline/meson.build
new file mode 100644 (file)
index 0000000..513c85c
--- /dev/null
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+
+inc = include_directories('../../include')
+
+readline_sources = [
+       'main.c',
+]
+
+c_compiler = meson.get_compiler('c')
+readline = c_compiler.find_library('readline', required: false)
+if readline.found()
+       ecoli_readline = executable(
+               'ecoli-readline',
+               readline_sources,
+               include_directories : inc,
+               link_with : libecoli,
+               dependencies: readline)
+endif
diff --git a/examples/yaml/parse-yaml.c b/examples/yaml/parse-yaml.c
deleted file mode 100644 (file)
index 218fe78..0000000
+++ /dev/null
@@ -1,310 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <getopt.h>
-#include <errno.h>
-
-#include <ecoli_init.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_yaml.h>
-#include <ecoli_editline.h>
-#include <ecoli_node_sh_lex.h>
-
-static char *input_file;
-static char *output_file;
-static bool complete;
-
-static const char short_options[] =
-       "h"  /* help */
-       "i:" /* input-file */
-       "o:" /* output-file */
-       "c"  /* complete */
-       ;
-
-#define OPT_HELP "help"
-#define OPT_INPUT_FILE "input-file"
-#define OPT_OUTPUT_FILE "output-file"
-#define OPT_COMPLETE "complete"
-
-static const struct option long_options[] = {
-       {OPT_HELP, 0, NULL, 'h'},
-       {OPT_INPUT_FILE, 1, NULL, 'i'},
-       {OPT_OUTPUT_FILE, 1, NULL, 'o'},
-       {OPT_COMPLETE, 0, NULL, 'c'},
-       {NULL, 0, NULL, 0}
-};
-
-static void usage(const char *prgname)
-{
-       fprintf(stderr, "%s -o <file.sh> -i <file.yaml>\n"
-               "  -h\n"
-               "  --"OPT_HELP"\n"
-               "      Show this help.\n"
-               "  -i <input-file>\n"
-               "  --"OPT_INPUT_FILE"=<file>\n"
-               "      Set the yaml input file describing the grammar.\n"
-               "  -o <output-file>\n"
-               "  --"OPT_OUTPUT_FILE"=<file>\n"
-               "      Set the output file.\n"
-               "  -c\n"
-               "  --"OPT_COMPLETE"\n"
-               "      Output the completion list."
-               , prgname);
-}
-
-static int parse_args(int argc, char **argv)
-{
-       int ret, opt;
-
-       while ((opt = getopt_long(argc, argv, short_options,
-                               long_options, NULL)) != EOF) {
-
-               switch (opt) {
-               case 'h': /* help */
-                       usage(argv[0]);
-                       exit(0);
-
-               case 'i': /* input-file */
-                       input_file = strdup(optarg);
-                       break;
-
-               case 'o': /* output-file */
-                       output_file = strdup(optarg);
-                       break;
-
-               case 'c': /* complete */
-                       complete = 1;
-                       break;
-
-               default:
-                       usage(argv[0]);
-                       return -1;
-               }
-
-       }
-
-       if (input_file == NULL) {
-               fprintf(stderr, "No input file\n");
-               usage(argv[0]);
-               return -1;
-       }
-       if (output_file == NULL) {
-               fprintf(stderr, "No output file\n");
-               usage(argv[0]);
-               return -1;
-       }
-
-       ret = optind - 1;
-       optind = 1;
-
-       return ret;
-}
-
-static int
-__dump_as_shell(FILE *f, const struct ec_parse *parse, size_t *seq)
-{
-       const struct ec_node *node = ec_parse_get_node(parse);
-       struct ec_parse *child;
-       size_t cur_seq, i, len;
-       const char *s;
-
-       (*seq)++;
-       cur_seq = *seq;
-
-       // XXX protect strings
-
-
-       fprintf(f, "ec_node%zu_id='%s'\n", cur_seq, ec_node_id(node));
-       fprintf(f, "ec_node%zu_type='%s'\n", cur_seq,
-               ec_node_type_name(ec_node_type(node)));
-
-       len = ec_strvec_len(ec_parse_strvec(parse));
-       fprintf(f, "ec_node%zu_strvec_len=%zu\n", cur_seq, len);
-       for (i = 0; i < len; i++) {
-               s = ec_strvec_val(ec_parse_strvec(parse), i);
-               fprintf(f, "ec_node%zu_str%zu='%s'\n", cur_seq, i, s);
-       }
-
-       if (ec_parse_get_first_child(parse) != NULL) {
-               fprintf(f, "ec_node%zu_first_child='ec_node%zu'\n",
-                       cur_seq, cur_seq + 1);
-       }
-
-       EC_PARSE_FOREACH_CHILD(child, parse) {
-               fprintf(f, "ec_node%zu_parent='ec_node%zu'\n",
-                       *seq + 1, cur_seq);
-               __dump_as_shell(f, child, seq);
-       }
-
-       if (ec_parse_get_next(parse) != NULL) {
-               fprintf(f, "ec_node%zu_next='ec_node%zu'\n",
-                       cur_seq, *seq + 1);
-       }
-
-       return 0;
-}
-
-static int
-dump_as_shell(const struct ec_parse *parse)
-{
-       FILE *f;
-       size_t seq = 0;
-       int ret;
-
-       f = fopen(output_file, "w");
-       if (f == NULL)
-               return -1;
-
-       ret = __dump_as_shell(f, parse, &seq);
-
-       fclose(f);
-
-       return ret;
-}
-
-static int
-interact(struct ec_node *node)
-{
-       struct ec_editline *editline = NULL;
-       struct ec_parse *parse = NULL;
-       struct ec_node *shlex = NULL;
-       char *line = NULL;
-
-       shlex = ec_node_sh_lex(EC_NO_ID, ec_node_clone(node)); //XXX
-       if (shlex == NULL) {
-               fprintf(stderr, "Failed to add lexer node\n");
-               goto fail;
-       }
-
-       editline = ec_editline("ecoli", stdin, stdout, stderr, 0);
-       if (editline == NULL) {
-               fprintf(stderr, "Failed to initialize editline\n");
-               goto fail;
-       }
-
-       parse = ec_editline_parse(editline, shlex);
-       if (parse == NULL)
-               goto fail;
-
-       if (!ec_parse_matches(parse))
-               goto fail;
-
-       //ec_parse_dump(stdout, parse);
-
-       if (dump_as_shell(parse) < 0) {
-               fprintf(stderr, "Failed to dump the parsed result\n");
-               goto fail;
-       }
-
-       ec_parse_free(parse);
-       ec_editline_free(editline);
-       ec_node_free(shlex);
-       return 0;
-
-fail:
-       ec_parse_free(parse);
-       ec_editline_free(editline);
-       free(line);
-       ec_node_free(shlex);
-       return -1;
-}
-
-static int
-complete_words(const struct ec_node *node, int argc, char *argv[])
-{
-       struct ec_comp *comp = NULL;
-       struct ec_strvec *strvec = NULL;
-       struct ec_comp_iter *iter = NULL;
-       struct ec_comp_item *item = NULL;
-       size_t count;
-
-       if (argc <= 1)
-               goto fail;
-       strvec = ec_strvec_from_array((const char * const *)&argv[1],
-                               argc - 1);
-       if (strvec == NULL)
-               goto fail;
-
-       comp = ec_node_complete_strvec(node, strvec);
-       if (comp == NULL)
-               goto fail;
-
-       count = ec_comp_count(comp, EC_COMP_UNKNOWN | EC_COMP_FULL |
-                       EC_COMP_PARTIAL);
-
-       iter = ec_comp_iter(comp,
-               EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL);
-       if (iter == NULL)
-               goto fail;
-
-       while ((item = ec_comp_iter_next(iter)) != NULL) {
-
-               /* only one match, display it fully */
-               if (count == 1) {
-                       printf("%s\n", ec_comp_item_get_str(item));
-                       break;
-               }
-
-               /* else show the 'display' part only */
-               printf("%s\n", ec_comp_item_get_display(item));
-       }
-
-       ec_comp_iter_free(iter);
-       ec_comp_free(comp);
-       ec_strvec_free(strvec);
-       return 0;
-
-fail:
-       ec_comp_free(comp);
-       ec_strvec_free(strvec);
-       return -1;
-}
-
-int
-main(int argc, char *argv[])
-{
-       struct ec_node *node = NULL;
-       int ret;
-
-       ret = parse_args(argc, argv);
-       if (ret < 0)
-               goto fail;
-
-       argc -= ret;
-       argv += ret;
-
-       if (ec_init() < 0) {
-               fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
-               return 1;
-       }
-
-       node = ec_yaml_import(input_file);
-       if (node == NULL) {
-               fprintf(stderr, "Failed to parse file\n");
-               goto fail;
-       }
-       //ec_node_dump(stdout, node);
-
-       if (complete) {
-               if (complete_words(node, argc, argv) < 0)
-                       goto fail;
-       } else {
-               if (interact(node) < 0)
-                       goto fail;
-       }
-
-       ec_node_free(node);
-
-       return 0;
-
-fail:
-       ec_node_free(node);
-       return 1;
-}
diff --git a/examples/yaml/test.yaml b/examples/yaml/test.yaml
deleted file mode 100644 (file)
index 5642766..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-type: or
-children:
-- type: seq
-  id: hello
-  help: Say hello to someone
-  children:
-  - type: str
-    string: hello
-  - type: or
-    id: name
-    help: Name of the person to greet
-    children:
-    - type: str
-      string: john
-    - type: str
-      string: mike
-- type: seq
-  id: goodbye
-  help: Say good bye to someone
-  children:
-  - type: str
-    string: good
-  - type: str
-    string: bye
-  - type: or
-    id: name
-    help: Name of the person to greet
-    children:
-    - type: str
-      string: mary
-    - type: str
-      string: jessica
diff --git a/include/ecoli_assert.h b/include/ecoli_assert.h
new file mode 100644 (file)
index 0000000..fcd2186
--- /dev/null
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Assert API
+ *
+ * Helpers to check at runtime if a condition is true, or otherwise
+ * either abort (exit program) or return an error.
+ */
+
+#ifndef ECOLI_ASSERT_
+#define ECOLI_ASSERT_
+
+#include <stdbool.h>
+
+/**
+ * Abort if the condition is false.
+ *
+ * If expression is false this macro will prints an error message to
+ * standard error and terminates the program by calling abort(3).
+ *
+ * @param expr
+ *   The expression to be checked.
+ * @param args
+ *   The format string, optionally followed by other arguments.
+ */
+#define ec_assert_print(expr, args...) \
+       __ec_assert_print(expr, #expr, args)
+
+/* internal */
+void __ec_assert_print(bool expr, const char *expr_str,
+               const char *format, ...);
+
+/**
+ * Check a condition or return.
+ *
+ * If the condition is true, do nothing. If it is false, set
+ * errno and return the specified value.
+ *
+ * @param cond
+ *   The condition to test.
+ * @param ret
+ *   The value to return.
+ * @param err
+ *   The errno to set.
+ */
+#define EC_CHECK_ARG(cond, ret, err) do {                              \
+               if (!(cond)) {                                          \
+                       errno = err;                                    \
+                       return ret;                                     \
+               }                                                       \
+       } while(0)
+
+#endif
diff --git a/include/ecoli_complete.h b/include/ecoli_complete.h
new file mode 100644 (file)
index 0000000..dee4123
--- /dev/null
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * API for generating completions item on a node.
+ *
+ * This file provide helpers to list and manipulate the possible
+ * completions for a given input.
+ *
+ * XXX comp vs item
+ */
+
+#ifndef ECOLI_COMPLETE_
+#define ECOLI_COMPLETE_
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+struct ec_node;
+
+enum ec_comp_type { /* XXX should be a define */
+       EC_COMP_UNKNOWN = 0x1,
+       EC_COMP_FULL = 0x2,
+       EC_COMP_PARTIAL = 0x4,
+       EC_COMP_ALL = 0x7,
+};
+
+struct ec_comp_item;
+
+TAILQ_HEAD(ec_comp_item_list, ec_comp_item);
+
+struct ec_comp_group {
+       TAILQ_ENTRY(ec_comp_group) next;
+       const struct ec_node *node;
+       struct ec_comp_item_list items;
+       struct ec_parse *state;
+       struct ec_keyval *attrs;
+};
+
+TAILQ_HEAD(ec_comp_group_list, ec_comp_group);
+
+struct ec_comp {
+       unsigned count;
+       unsigned count_full;
+       unsigned count_partial;
+       unsigned count_unknown;
+       struct ec_parse *cur_state;
+       struct ec_comp_group *cur_group;
+       struct ec_comp_group_list groups;
+       struct ec_keyval *attrs;
+};
+
+/*
+ * return a comp object filled with items
+ * return NULL on error (nomem?)
+ */
+struct ec_comp *ec_node_complete(const struct ec_node *node,
+       const char *str);
+struct ec_comp *ec_node_complete_strvec(const struct ec_node *node,
+       const struct ec_strvec *strvec);
+
+/* internal: used by nodes */
+int ec_node_complete_child(const struct ec_node *node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec);
+
+/**
+ * Create a completion object (list of completion items).
+ *
+ *
+ */
+struct ec_comp *ec_comp(struct ec_parse *state);
+
+/**
+ * Free a completion object and all its items.
+ *
+ *
+ */
+void ec_comp_free(struct ec_comp *comp);
+
+/**
+ *
+ *
+ *
+ */
+void ec_comp_dump(FILE *out,
+       const struct ec_comp *comp);
+
+/**
+ * Merge items contained in 'from' into 'to'
+ *
+ * The 'from' comp struct is freed.
+ */
+int ec_comp_merge(struct ec_comp *to,
+               struct ec_comp *from);
+
+struct ec_parse *ec_comp_get_state(struct ec_comp *comp);
+
+/* shortcut for ec_comp_item() + ec_comp_item_add() */
+int ec_comp_add_item(struct ec_comp *comp,
+                       const struct ec_node *node,
+                       struct ec_comp_item **p_item,
+                       enum ec_comp_type type,
+                       const char *start, const char *full);
+
+/**
+ *
+ */
+int ec_comp_item_set_str(struct ec_comp_item *item,
+                       const char *str);
+
+/**
+ * Get the string value of a completion item.
+ *
+ *
+ */
+const char *
+ec_comp_item_get_str(const struct ec_comp_item *item);
+
+/**
+ * Get the display string value of a completion item.
+ *
+ *
+ */
+const char *
+ec_comp_item_get_display(const struct ec_comp_item *item);
+
+/**
+ * Get the completion string value of a completion item.
+ *
+ *
+ */
+const char *
+ec_comp_item_get_completion(const struct ec_comp_item *item);
+
+/**
+ * Get the group of a completion item.
+ *
+ *
+ */
+const struct ec_comp_group *
+ec_comp_item_get_grp(const struct ec_comp_item *item);
+
+/**
+ * Get the type of a completion item.
+ *
+ *
+ */
+enum ec_comp_type
+ec_comp_item_get_type(const struct ec_comp_item *item);
+
+/**
+ * Get the node associated to a completion item.
+ *
+ *
+ */
+const struct ec_node *
+ec_comp_item_get_node(const struct ec_comp_item *item);
+
+/**
+ * Set the display value of an item.
+ *
+ *
+ */
+int ec_comp_item_set_display(struct ec_comp_item *item,
+                               const char *display);
+
+/**
+ * Set the completion value of an item.
+ *
+ *
+ */
+int ec_comp_item_set_completion(struct ec_comp_item *item,
+                               const char *completion);
+
+/**
+ *
+ *
+ *
+ */
+int
+ec_node_complete_unknown(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec);
+
+/**
+ *
+ *
+ *
+ */
+unsigned int ec_comp_count(
+       const struct ec_comp *comp,
+       enum ec_comp_type flags);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_comp_iter {
+       enum ec_comp_type type;
+       const struct ec_comp *comp;
+       struct ec_comp_group *cur_node;
+       struct ec_comp_item *cur_match;
+};
+
+/**
+ *
+ *
+ *
+ */
+struct ec_comp_iter *
+ec_comp_iter(const struct ec_comp *comp,
+       enum ec_comp_type type);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_comp_item *ec_comp_iter_next(
+       struct ec_comp_iter *iter);
+
+/**
+ *
+ *
+ *
+ */
+void ec_comp_iter_free(struct ec_comp_iter *iter);
+
+
+#endif
diff --git a/include/ecoli_config.h b/include/ecoli_config.h
new file mode 100644 (file)
index 0000000..7aa427f
--- /dev/null
@@ -0,0 +1,404 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_CONFIG_
+#define ECOLI_CONFIG_
+
+#include <sys/queue.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#ifndef EC_COUNT_OF //XXX
+#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
+               ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+#endif
+
+struct ec_config;
+struct ec_keyval;
+
+/**
+ * The type identifier for a config value.
+ */
+enum ec_config_type {
+       EC_CONFIG_TYPE_NONE = 0,
+       EC_CONFIG_TYPE_BOOL,
+       EC_CONFIG_TYPE_INT64,
+       EC_CONFIG_TYPE_UINT64,
+       EC_CONFIG_TYPE_STRING,
+       EC_CONFIG_TYPE_NODE,
+       EC_CONFIG_TYPE_LIST,
+       EC_CONFIG_TYPE_DICT,
+};
+
+/**
+ * Structure describing the format of a configuration value.
+ *
+ * This structure is used in a const array which is referenced by a
+ * struct ec_config. Each entry of the array represents a key/value
+ * storage of the configuration dictionary.
+ */
+struct ec_config_schema {
+       const char *key;          /**< The key string (NULL for list elts). */
+       const char *desc;         /**< A description of the value. */
+       enum ec_config_type type; /**< Type of the value */
+       /* XXX flags: mandatory */
+       /* XXX default */
+
+       /** If type is dict or list, the schema of the dict or list
+        * elements. Else must be NULL. */
+       const struct ec_config_schema *subschema;
+};
+
+TAILQ_HEAD(ec_config_list, ec_config);
+
+/**
+ * Structure storing the configuration data.
+ */
+struct ec_config {
+       /** type of value stored in the union */
+       enum ec_config_type type;
+
+       union {
+               bool boolean;   /** Boolean value */
+               int64_t i64;    /** Signed integer value */
+               uint64_t u64;   /** Unsigned integer value */
+               char *string;   /** String value */
+               struct ec_node *node;       /** Node value */
+               struct ec_keyval *dict;     /** Hash table value */
+               struct ec_config_list list; /** List value */
+       };
+
+       /**
+        * Next in list, only valid if type is list.
+        */
+       TAILQ_ENTRY(ec_config) next;
+};
+
+/* schema */
+
+/**
+ * Validate a configuration schema array.
+ *
+ * @param schema
+ *   Pointer to the first element of the schema array. The array
+ *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
+ * @return
+ *   0 if the schema is valid, or -1 on error (errno is set).
+ */
+int ec_config_schema_validate(const struct ec_config_schema *schema);
+
+/**
+ * Dump a configuration schema array.
+ *
+ * @param out
+ *   Output stream on which the dump will be sent.
+ * @param schema
+ *   Pointer to the first element of the schema array. The array
+ *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
+ */
+void ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema);
+
+/**
+ * Find a schema entry matching the key.
+ *
+ * Browse the schema array and lookup for the given key.
+ *
+ * @param schema
+ *   Pointer to the first element of the schema array. The array
+ *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
+ * @return
+ *   The schema entry if it matches a key, or NULL if not found.
+ */
+const struct ec_config_schema *
+ec_config_schema_lookup(const struct ec_config_schema *schema,
+                       const char *key);
+
+/**
+ * Get the type of a schema entry.
+ *
+ * @param schema_elt
+ *   Pointer to an element of the schema array.
+ * @return
+ *   The type of the schema entry.
+ */
+enum ec_config_type
+ec_config_schema_type(const struct ec_config_schema *schema_elt);
+
+/**
+ * Get the subschema of a schema entry.
+ *
+ * @param schema_elt
+ *   Pointer to an element of the schema array.
+ * @return
+ *   The subschema if any, or NULL.
+ */
+const struct ec_config_schema *
+ec_config_schema_sub(const struct ec_config_schema *schema_elt);
+
+/**
+ * Check if a key name is reserved in a config dict.
+ *
+ * Some key names are reserved and should not be used in configs.
+ *
+ * @param name
+ *   The name of the key to test.
+ * @return
+ *   True if the key name is reserved and must not be used, else false.
+ */
+bool ec_config_key_is_reserved(const char *name);
+
+/**
+ * Array of reserved key names.
+ */
+extern const char *ec_config_reserved_keys[];
+
+
+/* config */
+
+/**
+ * Get the type of the configuration.
+ *
+ * @param config
+ *   The configuration.
+ * @return
+ *   The configuration type.
+ */
+enum ec_config_type ec_config_get_type(const struct ec_config *config);
+
+/**
+ * Create a boolean configuration value.
+ *
+ * @param boolean
+ *   The boolean value to be set.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_bool(bool boolean);
+
+/**
+ * Create a signed integer configuration value.
+ *
+ * @param i64
+ *   The signed integer value to be set.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_i64(int64_t i64);
+
+/**
+ * Create an unsigned configuration value.
+ *
+ * @param u64
+ *   The unsigned integer value to be set.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_u64(uint64_t u64);
+
+/**
+ * Create a string configuration value.
+ *
+ * @param string
+ *   The string value to be set. The string is copied into the
+ *   configuration object.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_string(const char *string);
+
+/**
+ * Create a node configuration value.
+ *
+ * @param node
+ *   The node pointer to be set. The node is "consumed" by
+ *   the function and should not be used by the caller, even
+ *   on error. The caller can use ec_node_clone() to keep a
+ *   reference on the node.
+ * @return
+ *   The configuration object, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_node(struct ec_node *node);
+
+/**
+ * Create a hash table configuration value.
+ *
+ * @return
+ *   A configuration object containing an empty hash table, or NULL on
+ *   error (errno is set).
+ */
+struct ec_config *ec_config_dict(void);
+
+/**
+ * Create a list configuration value.
+ *
+ * @return
+ *   The configuration object containing an empty list, or NULL on
+ *   error (errno is set).
+ */
+struct ec_config *ec_config_list(void);
+
+/**
+ * Add a config object into a list.
+ *
+ * @param list
+ *   The list configuration in which the value will be added.
+ * @param value
+ *   The value configuration to add in the list. The value object
+ *   will be freed when freeing the list object. On error, the
+ *   value object is also freed.
+ * @return
+ *   0 on success, else -1 (errno is set).
+ */
+int ec_config_list_add(struct ec_config *list, struct ec_config *value);
+
+/**
+ * Remove an element from a list.
+ *
+ * The element is freed and should not be accessed.
+ *
+ * @param list
+ *   The list configuration.
+ * @param config
+ *   The element to remove from the list.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_config_list_del(struct ec_config *list, struct ec_config *config);
+
+/**
+ * Count the number of elements in a list or dict.
+ *
+ * @param config
+ *   The configuration that must be a list or a dict.
+ * @return
+ *   The number of elements, or -1 on error (errno is set).
+ */
+ssize_t ec_config_count(const struct ec_config *config);
+
+/**
+ * Validate a configuration.
+ *
+ * @param dict
+ *   A hash table configuration to validate.
+ * @param schema
+ *   Pointer to the first element of the schema array. The array
+ *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_config_validate(const struct ec_config *dict,
+               const struct ec_config_schema *schema);
+
+/**
+ * Set a value in a hash table configuration
+ *
+ * @param dict
+ *   A hash table configuration to validate.
+ * @param key
+ *   The key to update.
+ * @param value
+ *   The value to set. The value object will be freed when freeing the
+ *   dict object. On error, the value object is also freed.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_config_dict_set(struct ec_config *dict, const char *key,
+               struct ec_config *value);
+
+/**
+ * Remove an element from a hash table configuration.
+ *
+ * The element is freed and should not be accessed.
+ *
+ * @param dict
+ *   A hash table configuration to validate.
+ * @param key
+ *   The key of the configuration to delete.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_config_dict_del(struct ec_config *config, const char *key);
+
+/**
+ * Compare two configurations.
+ */
+int ec_config_cmp(const struct ec_config *config1,
+               const struct ec_config *config2);
+
+/**
+ * Get configuration value.
+ */
+struct ec_config *ec_config_dict_get(const struct ec_config *config,
+                               const char *key);
+
+/**
+ * Get the first element of a list.
+ *
+ * Example of use:
+ * for (config = ec_config_list_iter(list);
+ *     config != NULL;
+ *     config = ec_config_list_next(list, config)) {
+ *             ...
+ * }
+ *
+ * @param list
+ *   The list configuration to iterate.
+ * @return
+ *   The first configuration element, or NULL on error (errno is set).
+ */
+struct ec_config *ec_config_list_first(struct ec_config *list);
+
+/**
+ * Get next element in list.
+ *
+ * @param list
+ *   The list configuration beeing iterated.
+ * @param config
+ *   The current configuration element.
+ * @return
+ *   The next configuration element, or NULL if there is no more element.
+ */
+struct ec_config *
+ec_config_list_next(struct ec_config *list, struct ec_config *config);
+
+/**
+ * Free a configuration.
+ *
+ * @param config
+ *   The element to free.
+ */
+void ec_config_free(struct ec_config *config);
+
+/**
+ * Compare two configurations.
+ *
+ * @return
+ *   0 if the configurations are equal, else -1.
+ */
+int ec_config_cmp(const struct ec_config *value1,
+               const struct ec_config *value2);
+
+/**
+ * Duplicate a configuration.
+ *
+ * @param config
+ *   The configuration to duplicate.
+ * @return
+ *   The duplicated configuration, or NULL on error (errno is set).
+ */
+struct ec_config *
+ec_config_dup(const struct ec_config *config);
+
+/**
+ * Dump a configuration.
+ *
+ * @param out
+ *   Output stream on which the dump will be sent.
+ * @param config
+ *   The configuration to dump.
+ */
+void ec_config_dump(FILE *out, const struct ec_config *config);
+
+#endif
diff --git a/include/ecoli_editline.h b/include/ecoli_editline.h
new file mode 100644 (file)
index 0000000..58824d2
--- /dev/null
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Helpers that can be used to associate an editline instance with
+ * an ecoli node tree.
+ *
+ * XXX support saved history
+ * XXX support multiline edition
+ * XXX set prompt
+ */
+
+#ifndef ECOLI_EDITLINE_
+#define ECOLI_EDITLINE_
+
+#include <histedit.h>
+
+struct ec_editline;
+struct ec_node;
+struct ec_parse;
+struct ec_comp;
+
+struct ec_editline_help {
+       char *desc;
+       char *help;
+};
+
+/**
+ * Default history size.
+ */
+#define EC_EDITLINE_HISTORY_SIZE 128
+
+/**
+ * Ask the terminal to not send signals (STOP, SUSPEND, XXX). The
+ * ctrl-c, ctrl-z will be interpreted as standard characters. An
+ * action can be associated to these characters with:
+ *
+ *     static int cb(EditLine *editline, int c) {
+ *     {
+ *             see editline documentation for details
+ *     }
+ *
+ *     if (el_set(el, EL_ADDFN, "ed-foobar", "Help string about foobar", cb))
+ *             handle_error;
+ *     if (el_set(el, EL_BIND, "^C", "ed-break", NULL))
+ *             handle_error;
+ *
+ * The default behavior (without this flag) is to let the signal pass: ctrl-c
+ * will stop program and ctrl-z will suspend it.
+ */
+#define EC_EDITLINE_DISABLE_SIGNALS 0x01
+
+/**
+ * Disable history. The default behavior creates an history with
+ * EC_EDITLINE_HISTORY_SIZE entries. To change this value, use
+ * ec_editline_set_history().
+ */
+#define EC_EDITLINE_DISABLE_HISTORY 0x02
+
+/**
+ * Disable completion. The default behavior is to complete when
+ * '?' or '<tab>' is hit. You can register your own callback with:
+ *
+ *     if (el_set(el, EL_ADDFN, "ed-complete", "Complete buffer", callback))
+ *             handle_error;
+ *     if (el_set(el, EL_BIND, "^I", "ed-complete", NULL))
+ *             handle_error;
+ *
+ * The default used callback is ec_editline_complete().
+ */
+#define EC_EDITLINE_DISABLE_COMPLETION 0x04
+
+typedef int (*ec_editline_cmpl_t)(struct ec_editline *editline, int c);
+
+/**
+ * Create an editline instance with default behavior.
+ *
+ * XXX Wrapper to editline's el_init() 
+ *
+ * It 
+ */
+struct ec_editline *
+ec_editline(const char *name, FILE *f_in, FILE *f_out, FILE *f_err,
+       unsigned int flags);
+
+/**
+ * Free an editline instance allocated with ec_editline().
+ */
+void ec_editline_free(struct ec_editline *editline);
+
+/**
+ * Return the editline instance attached to the ec_editline object.
+ */
+EditLine *ec_editline_get_el(struct ec_editline *editline);
+
+// XXX public?
+const struct ec_node *ec_editline_get_node(struct ec_editline *editline);
+void ec_editline_set_node(struct ec_editline *editline,
+                       const struct ec_node *node);
+
+//XXX get history, get_...
+
+/**
+ * Change the history size.
+ *
+ * The default behavior is to have an history whose size
+ * is EC_EDITLINE_HISTORY_SIZE. This can be changed with this
+ * function.
+ *
+ * @param editline
+ *   The pointer to the ec_editline structure.
+ * @param hist_size
+ *   The desired size of the history.
+ * @return
+ *   0 on success, or -1 on error (errno is set).
+ */
+int ec_editline_set_history(struct ec_editline *editline,
+       size_t hist_size);
+
+int
+ec_editline_print_cols(struct ec_editline *editline,
+               char const * const *matches, size_t n);
+
+void ec_editline_free_completions(char **matches, size_t len);
+ssize_t
+ec_editline_get_completions(const struct ec_comp *cmpl, char ***matches_out);
+char *
+ec_editline_append_chars(const struct ec_comp *cmpl);
+
+ssize_t
+ec_editline_get_helps(const struct ec_editline *editline, const char *line,
+       const char *full_line, struct ec_editline_help **helps_out);
+int
+ec_editline_print_helps(struct ec_editline *editline,
+                       const struct ec_editline_help *helps, size_t n);
+void
+ec_editline_free_helps(struct ec_editline_help *helps, size_t len);
+
+int
+ec_editline_set_prompt(struct ec_editline *editline, const char *prompt);
+
+
+
+
+/**
+ * Get a line.
+ *
+ * The returned line must be freed by the caller using ec_free().
+ */
+char *ec_editline_gets(struct ec_editline *editline);
+
+/**
+ * Get a line (managing completion) and parse it with passed node
+ * XXX find a better name?
+ */
+struct ec_parse *
+ec_editline_parse(struct ec_editline *editline, const struct ec_node *node);
+
+int
+ec_editline_complete(EditLine *el, int c);
+
+#endif
diff --git a/include/ecoli_init.h b/include/ecoli_init.h
new file mode 100644 (file)
index 0000000..4e8bc1e
--- /dev/null
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Register initialization routines.
+ */
+
+#ifndef ECOLI_INIT_
+#define ECOLI_INIT_
+
+#include <sys/queue.h>
+
+#include <ecoli_log.h>
+#include <ecoli_node.h>
+
+#define EC_INIT_REGISTER(t)                                            \
+       static void ec_init_init_##t(void);                             \
+       static void __attribute__((constructor, used))                  \
+       ec_init_init_##t(void)                                          \
+       {                                                               \
+                ec_init_register(&t);                                  \
+       }
+
+/**
+ * Type of init function. Return 0 on success, -1 on error.
+ */
+typedef int (ec_init_t)(void);
+
+TAILQ_HEAD(ec_init_list, ec_init);
+
+/**
+ * A structure describing a test case.
+ */
+struct ec_init {
+       TAILQ_ENTRY(ec_init) next;  /**< Next in list. */
+       ec_init_t *init;            /**< Init function. */
+       unsigned int priority;      /**< Priority (0=first, 99=last) */
+};
+
+/**
+ * Register an initialization function.
+ *
+ * @param init
+ *   A pointer to a ec_init structure to be registered.
+ */
+void ec_init_register(struct ec_init *test);
+
+/**
+ * Initialize ecoli library
+ *
+ * Must be called before any other function from libecoli, except
+ * ec_malloc_register().
+ *
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_init(void);
+
+#endif
diff --git a/include/ecoli_keyval.h b/include/ecoli_keyval.h
new file mode 100644 (file)
index 0000000..a3e9400
--- /dev/null
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Simple hash table API
+ *
+ * This file provides functions to store objects in hash tables, using strings
+ * as keys.
+ */
+
+#ifndef ECOLI_KEYVAL_
+#define ECOLI_KEYVAL_
+
+#include <stdio.h>
+#include <stdbool.h>
+
+typedef void (*ec_keyval_elt_free_t)(void *);
+
+struct ec_keyval;
+struct ec_keyval_iter;
+
+/**
+ * Create a hash table.
+ *
+ * @return
+ *   The hash table, or NULL on error (errno is set).
+ */
+struct ec_keyval *ec_keyval(void);
+
+/**
+ * Get a value from the hash table.
+ *
+ * @param keyval
+ *   The hash table.
+ * @param key
+ *   The key string.
+ * @return
+ *   The element if it is found, or NULL on error (errno is set).
+ *   In case of success but the element is NULL, errno is set to 0.
+ */
+void *ec_keyval_get(const struct ec_keyval *keyval, const char *key);
+
+/**
+ * Check if the hash table contains this key.
+ *
+ * @param keyval
+ *   The hash table.
+ * @param key
+ *   The key string.
+ * @return
+ *   true if it contains the key, else false.
+ */
+bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key);
+
+/**
+ * Delete an object from the hash table.
+ *
+ * @param keyval
+ *   The hash table.
+ * @param key
+ *   The key string.
+ * @return
+ *   0 on success, or -1 on error (errno is set).
+ */
+int ec_keyval_del(struct ec_keyval *keyval, const char *key);
+
+/**
+ * Add/replace an object in the hash table.
+ *
+ * @param keyval
+ *   The hash table.
+ * @param key
+ *   The key string.
+ * @param val
+ *   The pointer to be saved in the hash table.
+ * @param free_cb
+ *   An optional pointer to a destructor function called when an
+ *   object is destroyed (ec_keyval_del() or ec_keyval_free()).
+ * @return
+ *   0 on success, or -1 on error (errno is set).
+ *   On error, the passed value is freed (free_cb(val) is called).
+ */
+int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val,
+       ec_keyval_elt_free_t free_cb);
+
+/**
+ * Free a hash table an all its objects.
+ *
+ * @param keyval
+ *   The hash table.
+ */
+void ec_keyval_free(struct ec_keyval *keyval);
+
+/**
+ * Get the length of a hash table.
+ *
+ * @param keyval
+ *   The hash table.
+ * @return
+ *   The length of the hash table.
+ */
+size_t ec_keyval_len(const struct ec_keyval *keyval);
+
+/**
+ * Duplicate a hash table
+ *
+ * A reference counter is shared between the clones of
+ * hash tables so that the objects are freed only when
+ * the last reference is destroyed.
+ *
+ * @param keyval
+ *   The hash table.
+ * @return
+ *   The duplicated hash table, or NULL on error (errno is set).
+ */
+struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval);
+
+/**
+ * Dump a hash table.
+ *
+ * @param out
+ *   The stream where the dump is sent.
+ * @param keyval
+ *   The hash table.
+ */
+void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval);
+
+/**
+ * Iterate the elements in the hash table.
+ *
+ * The typical usage is as below:
+ *
+ *     // dump elements
+ *     for (iter = ec_keyval_iter(keyval);
+ *          ec_keyval_iter_valid(iter);
+ *          ec_keyval_iter_next(iter)) {
+ *             printf("  %s: %p\n",
+ *                     ec_keyval_iter_get_key(iter),
+ *                     ec_keyval_iter_get_val(iter));
+ *     }
+ *     ec_keyval_iter_free(iter);
+ *
+ * @param keyval
+ *   The hash table.
+ * @return
+ *   An iterator, or NULL on error (errno is set).
+ */
+struct ec_keyval_iter *
+ec_keyval_iter(const struct ec_keyval *keyval);
+
+/**
+ * Make the iterator point to the next element in the hash table.
+ *
+ * @param iter
+ *   The hash table iterator.
+ */
+void ec_keyval_iter_next(struct ec_keyval_iter *iter);
+
+/**
+ * Free the iterator.
+ *
+ * @param iter
+ *   The hash table iterator.
+ */
+void ec_keyval_iter_free(struct ec_keyval_iter *iter);
+
+/**
+ * Check if the iterator points to a valid element.
+ *
+ * @param iter
+ *   The hash table iterator.
+ * @return
+ *   true if the element is valid, else false.
+ */
+bool
+ec_keyval_iter_valid(const struct ec_keyval_iter *iter);
+
+/**
+ * Get the key of the current element.
+ *
+ * @param iter
+ *   The hash table iterator.
+ * @return
+ *   The current element key, or NULL if the iterator points to an
+ *   invalid element.
+ */
+const char *
+ec_keyval_iter_get_key(const struct ec_keyval_iter *iter);
+
+/**
+ * Get the value of the current element.
+ *
+ * @param iter
+ *   The hash table iterator.
+ * @return
+ *   The current element value, or NULL if the iterator points to an
+ *   invalid element.
+ */
+void *
+ec_keyval_iter_get_val(const struct ec_keyval_iter *iter);
+
+
+#endif
diff --git a/include/ecoli_log.h b/include/ecoli_log.h
new file mode 100644 (file)
index 0000000..2414dc0
--- /dev/null
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Logging API
+ *
+ * This file provide logging helpers:
+ * - logging functions, supporting printf-like format
+ * - several debug level (similar to syslog)
+ * - named log types
+ * - redirection of log to a user functions (default logs nothing)
+ */
+
+#ifndef ECOLI_LOG_
+#define ECOLI_LOG_
+
+#include <stdarg.h>
+
+#include <ecoli_assert.h>
+
+enum ec_log_level {
+       EC_LOG_EMERG   = 0,  /* system is unusable               */
+       EC_LOG_ALERT   = 1,  /* action must be taken immediately */
+       EC_LOG_CRIT    = 2,  /* critical conditions              */
+       EC_LOG_ERR     = 3,  /* error conditions                 */
+       EC_LOG_WARNING = 4,  /* warning conditions               */
+       EC_LOG_NOTICE  = 5,  /* normal but significant condition */
+       EC_LOG_INFO    = 6,  /* informational                    */
+       EC_LOG_DEBUG   = 7,  /* debug-level messages             */
+};
+
+/**
+ * Register a log type.
+ *
+ * This macro defines a function that will be called at startup (using
+ * the "constructor" attribute). This function registers the named type
+ * passed as argument, and sets a static global variable
+ * "ec_log_local_type". This variable is used as the default log type
+ * for this file when using EC_LOG() or EC_VLOG().
+ *
+ * This macro can be present several times in a file. In this case, the
+ * local log type is set to the last registered type.
+ *
+ * On error, the function aborts.
+ *
+ * @param name
+ *   The name of the log to be registered.
+ */
+#define EC_LOG_TYPE_REGISTER(name)                                     \
+       static int name##_log_type;                                     \
+       static int ec_log_local_type;                                   \
+       __attribute__((constructor, used))                              \
+       static void ec_log_register_##name(void)                        \
+       {                                                               \
+               ec_log_local_type = ec_log_type_register(#name);        \
+               ec_assert_print(ec_log_local_type >= 0,                 \
+                               "cannot register log type.\n");         \
+               name##_log_type = ec_log_local_type;                    \
+       }
+
+/**
+ * User log function type.
+ *
+ * It is advised that a user-defined log function drops all messages
+ * that are at least as critical as ec_log_level_get(), as done by the
+ * default handler.
+ *
+ * @param type
+ *   The log type identifier.
+ * @param level
+ *   The log level.
+ * @param opaque
+ *   The opaque pointer that was passed to ec_log_fct_register().
+ * @param str
+ *   The string to log.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+typedef int (*ec_log_t)(int type, enum ec_log_level level, void *opaque,
+                       const char *str);
+
+/**
+ * Register a user log function.
+ *
+ * @param usr_log
+ *   Function pointer that will be invoked for each log call.
+ *   If the parameter is NULL, ec_log_default_cb() is used.
+ * @param opaque
+ *   Opaque pointer passed to the log function.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_log_fct_register(ec_log_t usr_log, void *opaque);
+
+/**
+ * Register a named log type.
+ *
+ * Register a new log type, which is identified by its name. The
+ * function returns a log identifier associated to the log name. If the
+ * name is already registered, the function just returns its identifier.
+ *
+ * @param name
+ *   The name of the log type.
+ * @return
+ *   The log type identifier on success (positive or zero), -1 on
+ *   error (errno is set).
+ */
+int ec_log_type_register(const char *name);
+
+/**
+ * Return the log name associated to the log type identifier.
+ *
+ * @param type
+ *   The log type identifier.
+ * @return
+ *   The name associated to the log type, or "unknown". It always return
+ *   a valid string (never NULL).
+ */
+const char *ec_log_name(int type);
+
+/**
+ * Log a formatted string.
+ *
+ * @param type
+ *   The log type identifier.
+ * @param level
+ *   The log level.
+ * @param format
+ *   The format string, followed by optional arguments.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_log(int type, enum ec_log_level level, const char *format, ...)
+       __attribute__((format(__printf__, 3, 4)));
+
+/**
+ * Log a formatted string.
+ *
+ * @param type
+ *   The log type identifier.
+ * @param level
+ *   The log level.
+ * @param format
+ *   The format string.
+ * @param ap
+ *   The list of arguments.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap);
+
+/**
+ * Log a formatted string using the local log type.
+ *
+ * This macro requires that a log type is previously register with
+ * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type"
+ * variable.
+ *
+ * @param level
+ *   The log level.
+ * @param format
+ *   The format string, followed by optional arguments.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+#define EC_LOG(level, args...) ec_log(ec_log_local_type, level, args)
+
+/**
+ * Log a formatted string using the local log type.
+ *
+ * This macro requires that a log type is previously register with
+ * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type"
+ * variable.
+ *
+ * @param level
+ *   The log level.
+ * @param format
+ *   The format string.
+ * @param ap
+ *   The list of arguments.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+#define EC_VLOG(level, fmt, ap) ec_vlog(ec_log_local_type, level, fmt, ap)
+
+/**
+ * Default log handler.
+ *
+ * This is the default log function that is used by the library. By
+ * default, it prints all logs whose level is WARNING or more critical.
+ * This level can be changed with ec_log_level_set().
+ *
+ * @param type
+ *   The log type identifier.
+ * @param level
+ *   The log level.
+ * @param opaque
+ *   Unused.
+ * @param str
+ *   The string to be logged.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_log_default_cb(int type, enum ec_log_level level, void *opaque,
+               const char *str);
+
+/**
+ * Set the global log level.
+ *
+ * This level is used by the default log handler, ec_log_default_cb().
+ * All messages that are at least as critical as the default level are
+ * displayed.
+ *
+ * It is advised
+ *
+ * @param level
+ *   The log level to be set.
+ * @return
+ *   0 on success, -1 on error.
+ */
+int ec_log_level_set(enum ec_log_level level);
+
+/**
+ * Get the global log level.
+ *
+ * This level is used by the default log handler, ec_log_default_cb().
+ * All messages that are at least as critical as the default level are
+ * displayed.
+ *
+ * @param level
+ *   The log level to be set.
+ * @return
+ *   0 on success, -1 on error.
+ */
+enum ec_log_level ec_log_level_get(void);
+
+#endif
diff --git a/include/ecoli_malloc.h b/include/ecoli_malloc.h
new file mode 100644 (file)
index 0000000..42cbf87
--- /dev/null
@@ -0,0 +1,255 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Interface to configure the allocator used by libecoli.
+ * By default, the standard allocation functions from libc are used.
+ */
+
+#ifndef ECOLI_MALLOC_
+#define ECOLI_MALLOC_
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * Function type of malloc, passed to ec_malloc_register().
+ *
+ * The API is the same than malloc(), excepted the file and line
+ * arguments.
+ *
+ * @param size
+ *   The size of the memory area to allocate.
+ * @param file
+ *   The path to the file that invoked the malloc.
+ * @param line
+ *   The line in the file that invoked the malloc.
+ * @return
+ *   A pointer to the allocated memory area, or NULL on error (errno
+ *   is set).
+ */
+typedef void *(*ec_malloc_t)(size_t size, const char *file, unsigned int line);
+
+/**
+ * Function type of free, passed to ec_malloc_register().
+ *
+ * The API is the same than free(), excepted the file and line
+ * arguments.
+ *
+ * @param ptr
+ *   The pointer to the memory area to be freed.
+ * @param file
+ *   The path to the file that invoked the malloc.
+ * @param line
+ *   The line in the file that invoked the malloc.
+ */
+typedef void (*ec_free_t)(void *ptr, const char *file, unsigned int line);
+
+/**
+ * Function type of realloc, passed to ec_malloc_register().
+ *
+ * The API is the same than realloc(), excepted the file and line
+ * arguments.
+ *
+ * @param ptr
+ *   The pointer to the memory area to be reallocated.
+ * @param file
+ *   The path to the file that invoked the malloc.
+ * @param line
+ *   The line in the file that invoked the malloc.
+ * @return
+ *   A pointer to the allocated memory area, or NULL on error (errno
+ *   is set).
+ */
+typedef void *(*ec_realloc_t)(void *ptr, size_t size, const char *file,
+       unsigned int line);
+
+/**
+ * Register allocation functions.
+ *
+ * This function can be use to register another allocator
+ * to be used by libecoli. By default, ec_malloc(), ec_free() and
+ * ec_realloc() use the standard libc allocator. Another handler
+ * can be used for debug purposes or when running in a specific
+ * environment.
+ *
+ * This function must be called before ec_init().
+ *
+ * @param usr_malloc
+ *   A user-defined malloc function.
+ * @param usr_free
+ *   A user-defined free function.
+ * @param usr_realloc
+ *   A user-defined realloc function.
+ * @return
+ *   0 on success, or -1 on error (errno is set).
+ */
+int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free,
+       ec_realloc_t usr_realloc);
+
+struct ec_malloc_handler {
+       ec_malloc_t malloc;
+       ec_free_t free;
+       ec_realloc_t realloc;
+};
+
+extern struct ec_malloc_handler ec_malloc_handler;
+
+/**
+ * Allocate a memory area.
+ *
+ * Like malloc(), ec_malloc() allocates size bytes and returns a pointer
+ * to the allocated memory. The memory is not initialized. The memory is
+ * freed with ec_free().
+ *
+ * @param size
+ *   The size of the area to allocate in bytes.
+ * @return
+ *   The pointer to the allocated memory, or NULL on error (errno is set).
+ */
+#define ec_malloc(size) ({                                             \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.malloc == NULL)                           \
+               ret_ = malloc(size);                                    \
+       else                                                            \
+               ret_ = __ec_malloc(size, __FILE__, __LINE__);           \
+       ret_;                                                           \
+       })
+
+/**
+ * Ecoli malloc function.
+ *
+ * Use this function when the macro ec_malloc() cannot be used,
+ * for instance when it is passed as a callback pointer.
+ */
+void *ec_malloc_func(size_t size);
+
+/**
+ * Free a memory area.
+ *
+ * Like free(), ec_free() frees the area pointed by ptr, which must have
+ * been returned by a previous call to ec_malloc() or any other
+ * allocation function of this file.
+ *
+ * @param ptr
+ *   The pointer to the memory area.
+ */
+#define ec_free(ptr) ({                                                        \
+       if (ec_malloc_handler.free == NULL)                             \
+               free(ptr);                                              \
+       else                                                            \
+               __ec_free(ptr, __FILE__, __LINE__);                     \
+       })
+
+/**
+ * Ecoli free function.
+ *
+ * Use this function when the macro ec_free() cannot be used,
+ * for instance when it is passed as a callback pointer.
+ */
+void ec_free_func(void *ptr);
+
+/**
+ * Resize an allocated memory area.
+ *
+ * @param ptr
+ *   The pointer to the previously allocated memory area, or NULL.
+ * @param size
+ *   The new size of the memory area.
+ * @return
+ *   A pointer to the newly allocated memory, or NULL if the request
+ *   fails. In that case, the original area is left untouched.
+ */
+#define ec_realloc(ptr, size) ({                                       \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.realloc == NULL)                          \
+               ret_ = realloc(ptr, size);                              \
+       else                                                            \
+               ret_ = __ec_realloc(ptr, size, __FILE__, __LINE__);     \
+       ret_;                                                           \
+       })
+
+/**
+ * Ecoli realloc function.
+ *
+ * Use this function when the macro ec_realloc() cannot be used,
+ * for instance when it is passed as a callback pointer.
+ */
+void ec_realloc_func(void *ptr, size_t size);
+
+/**
+ * Allocate and initialize an array of elements.
+ *
+ * @param n
+ *   The number of elements.
+ * @param size
+ *   The size of each element.
+ * @return
+ *   The pointer to the allocated memory, or NULL on error (errno is set).
+ */
+#define ec_calloc(n, size) ({                                          \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.malloc == NULL)                           \
+               ret_ = calloc(n, size);                                 \
+       else                                                            \
+               ret_ = __ec_calloc(n, size, __FILE__, __LINE__);        \
+       ret_;                                                           \
+       })
+
+/**
+ * Duplicate a string.
+ *
+ * Memory for the new string is obtained with ec_malloc(), and can be
+ * freed with ec_free().
+ *
+ * @param s
+ *   The string to be duplicated.
+ * @return
+ *   The pointer to the duplicated string, or NULL on error (errno is set).
+ */
+#define ec_strdup(s) ({                                                        \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.malloc == NULL)                           \
+               ret_ = strdup(s);                                       \
+       else                                                            \
+               ret_ = __ec_strdup(s, __FILE__, __LINE__);              \
+       ret_;                                                           \
+       })
+
+/**
+ * Duplicate at most n bytes of a string.
+ *
+ * This function is similar to ec_strdup(), except that it copies at
+ * most n bytes.  If s is longer than n, only n bytes are copied, and a
+ * terminating null byte ('\0') is added.
+ *
+ * @param s
+ *   The string to be duplicated.
+ * @param n
+ *   The maximum length of the new string.
+ * @return
+ *   The pointer to the duplicated string, or NULL on error (errno is set).
+ */
+#define ec_strndup(s, n) ({                                            \
+       void *ret_;                                                     \
+       if (ec_malloc_handler.malloc == NULL)                           \
+               ret_ = strndup(s, n);                                   \
+       else                                                            \
+               ret_ = __ec_strndup(s, n, __FILE__, __LINE__);          \
+       ret_;                                                           \
+       })
+
+/* internal */
+void *__ec_malloc(size_t size, const char *file, unsigned int line);
+void __ec_free(void *ptr, const char *file, unsigned int line);
+void *__ec_calloc(size_t nmemb, size_t size, const char *file,
+       unsigned int line);
+void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line);
+char *__ec_strdup(const char *s, const char *file, unsigned int line);
+char *__ec_strndup(const char *s, size_t n, const char *file,
+       unsigned int line);
+
+
+#endif
diff --git a/include/ecoli_murmurhash.h b/include/ecoli_murmurhash.h
new file mode 100644 (file)
index 0000000..6b76d34
--- /dev/null
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * MurmurHash3 is a hash implementation that was written by Austin Appleby, and
+ * is placed in the public domain. The author hereby disclaims copyright to this
+ * source code.
+ */
+
+#ifndef ECOLI_MURMURHASH_H_
+#define ECOLI_MURMURHASH_H_
+
+#include <stdint.h>
+
+/** Hash rotation */
+static inline uint32_t ec_murmurhash_rotl32(uint32_t x, int8_t r)
+{
+       return (x << r) | (x >> (32 - r));
+}
+
+/** Add 32-bit to the hash */
+static inline uint32_t ec_murmurhash3_add32(uint32_t h, uint32_t data)
+{
+       data *= 0xcc9e2d51;
+       data = ec_murmurhash_rotl32(data, 15);
+       data *= 0x1b873593;
+       h ^= data;
+       return h;
+}
+
+/** Intermediate mix */
+static inline uint32_t ec_murmurhash3_mix32(uint32_t h)
+{
+       h = ec_murmurhash_rotl32(h,13);
+       h = h * 5 +0xe6546b64;
+       return h;
+}
+
+/** Final mix: force all bits of a hash block to avalanche */
+static inline uint32_t ec_murmurhash3_fmix32(uint32_t h)
+{
+       h ^= h >> 16;
+       h *= 0x85ebca6b;
+       h ^= h >> 13;
+       h *= 0xc2b2ae35;
+       h ^= h >> 16;
+
+       return h;
+}
+
+/**
+ * Calculate a 32-bit murmurhash3
+ *
+ * @param key
+ *   The key (the unaligned variable-length array of bytes).
+ * @param len
+ *   The length of the key, counting by bytes.
+ * @param seed
+ *   Can be any 4-byte value initialization value.
+ * @return
+ *   A 32-bit hash.
+ */
+uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed);
+
+#endif /* ECOLI_MURMURHASH_H_ */
diff --git a/include/ecoli_node.h b/include/ecoli_node.h
new file mode 100644 (file)
index 0000000..45f3e74
--- /dev/null
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Interface to manage the ecoli nodes.
+ *
+ * A node is a main structure of the ecoli library, used to define how
+ * to match and complete the input tokens. A node is a generic object
+ * that implements:
+ * - a parse(node, input) method: check if an input matches
+ * - a complete(node, input) method: return possible completions for
+ *   a given input
+ * - some other methods to initialize, free, ...
+ *
+ * One basic example is the string node (ec_node_str). A node
+ * ec_node_str("foo") will match any token list starting with "foo",
+ * for example:
+ * - ["foo"]
+ * - ["foo", "bar", ...]
+ * But will not match:
+ * - []
+ * - ["bar", ...]
+ *
+ * A node ec_node_str("foo") will complete with "foo" if the input
+ * contains one token, with the same beginning than "foo":
+ * - [""]
+ * - ["f"]
+ * - ["fo"]
+ * - ["foo"]
+ * But it will not complete:
+ * - []
+ * - ["bar"]
+ * - ["f", ""]
+ * - ["", "f"]
+ *
+ * A node can have child nodes. For instance, a sequence node
+ * ec_node_seq(ec_node_str("foo"), ec_node_str("bar")) will match
+ * ["foo", "bar"].
+ */
+
+#ifndef ECOLI_NODE_
+#define ECOLI_NODE_
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#define EC_NO_ID "no-id"
+
+#define EC_NODE_ENDLIST ((void *)1)
+
+struct ec_node;
+struct ec_parse;
+struct ec_comp;
+struct ec_strvec;
+struct ec_keyval;
+struct ec_config;
+struct ec_config_schema;
+
+#define EC_NODE_TYPE_REGISTER(t)                                       \
+       static void ec_node_init_##t(void);                             \
+       static void __attribute__((constructor, used))                  \
+       ec_node_init_##t(void)                                          \
+       {                                                               \
+               if (ec_node_type_register(&t) < 0)                      \
+                       fprintf(stderr,                                 \
+                               "cannot register node type %s\n",       \
+                               t.name);                                \
+       }
+
+TAILQ_HEAD(ec_node_type_list, ec_node_type);
+
+typedef int (*ec_node_set_config_t)(struct ec_node *node,
+                               const struct ec_config *config);
+typedef int (*ec_node_parse_t)(const struct ec_node *node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec);
+typedef int (*ec_node_complete_t)(const struct ec_node *node,
+                               struct ec_comp *comp_state,
+                               const struct ec_strvec *strvec);
+typedef const char * (*ec_node_desc_t)(const struct ec_node *);
+typedef int (*ec_node_init_priv_t)(struct ec_node *);
+typedef void (*ec_node_free_priv_t)(struct ec_node *);
+typedef size_t (*ec_node_get_children_count_t)(const struct ec_node *);
+typedef int (*ec_node_get_child_t)(const struct ec_node *,
+       size_t i, struct ec_node **child, unsigned int *refs);
+
+/**
+ * A structure describing a node type.
+ */
+struct ec_node_type {
+       TAILQ_ENTRY(ec_node_type) next;  /**< Next in list. */
+       const char *name;                /**< Node type name. */
+       /** Configuration schema array, must be terminated by a sentinel
+        *  (.type = EC_CONFIG_TYPE_NONE). */
+       const struct ec_config_schema *schema;
+       ec_node_set_config_t set_config; /* validate/ack a config change */
+       ec_node_parse_t parse;
+       ec_node_complete_t complete;
+       ec_node_desc_t desc;
+       size_t size;
+       ec_node_init_priv_t init_priv;
+       ec_node_free_priv_t free_priv;
+       ec_node_get_children_count_t get_children_count;
+       ec_node_get_child_t get_child;
+};
+
+/**
+ * Register a node type.
+ *
+ * @param type
+ *   A pointer to a ec_test structure describing the test
+ *   to be registered.
+ * @return
+ *   0 on success, negative value on error.
+ */
+int ec_node_type_register(struct ec_node_type *type);
+
+/**
+ * Lookup node type by name
+ *
+ * @param name
+ *   The name of the node type to search.
+ * @return
+ *   The node type if found, or NULL on error.
+ */
+const struct ec_node_type *ec_node_type_lookup(const char *name);
+
+/**
+ * Dump registered log types
+ */
+void ec_node_type_dump(FILE *out);
+
+/**
+ * Get the config schema of a node type.
+ */
+const struct ec_config_schema *
+ec_node_type_schema(const struct ec_node_type *type);
+
+/**
+ * Get the name of a node type.
+ */
+const char *
+ec_node_type_name(const struct ec_node_type *type);
+
+enum ec_node_free_state {
+       EC_NODE_FREE_STATE_NONE,
+       EC_NODE_FREE_STATE_TRAVERSED,
+       EC_NODE_FREE_STATE_FREEABLE,
+       EC_NODE_FREE_STATE_NOT_FREEABLE,
+       EC_NODE_FREE_STATE_FREEING,
+};
+
+struct ec_node {
+       const struct ec_node_type *type;
+       struct ec_config *config;    /**< Generic configuration. */
+       char *id;
+       char *desc;
+       struct ec_keyval *attrs;
+       unsigned int refcnt;
+       struct {
+               enum ec_node_free_state state; /**< State of loop detection */
+               unsigned int refcnt;    /**< Number of reachable references
+                                        *   starting from node beeing freed */
+       } free; /**< Freeing state: used for loop detection */
+};
+
+/* create a new node when the type is known, typically called from the node
+ * code */
+struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id);
+
+/* create a new node */
+struct ec_node *ec_node(const char *typename, const char *id);
+
+struct ec_node *ec_node_clone(struct ec_node *node);
+void ec_node_free(struct ec_node *node);
+
+/* set configuration of a node
+ * after a call to this function, the config is
+ * owned by the node and must not be used by the caller
+ * on error, the config is freed. */
+int ec_node_set_config(struct ec_node *node, struct ec_config *config);
+
+/* get the current node configuration. Return NULL if no configuration. */
+const struct ec_config *ec_node_get_config(struct ec_node *node);
+
+size_t ec_node_get_children_count(const struct ec_node *node);
+int
+ec_node_get_child(const struct ec_node *node, size_t i,
+       struct ec_node **child, unsigned int *refs);
+
+/* XXX add more accessors */
+const struct ec_node_type *ec_node_type(const struct ec_node *node);
+struct ec_keyval *ec_node_attrs(const struct ec_node *node);
+const char *ec_node_id(const struct ec_node *node);
+const char *ec_node_desc(const struct ec_node *node);
+
+void ec_node_dump(FILE *out, const struct ec_node *node);
+struct ec_node *ec_node_find(struct ec_node *node, const char *id);
+
+/* check the type of a node */
+int ec_node_check_type(const struct ec_node *node,
+               const struct ec_node_type *type);
+
+#endif
diff --git a/include/ecoli_node_any.h b/include/ecoli_node_any.h
new file mode 100644 (file)
index 0000000..ee638aa
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * This node always matches 1 string in the vector
+ */
+
+#ifndef ECOLI_NODE_ANY_
+#define ECOLI_NODE_ANY_
+
+/* no specific API for this node */
+
+#endif
diff --git a/include/ecoli_node_cmd.h b/include/ecoli_node_cmd.h
new file mode 100644 (file)
index 0000000..99afc01
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_CMD_
+#define ECOLI_NODE_CMD_
+
+#include <ecoli_node.h>
+
+#define EC_NODE_CMD(args...) __ec_node_cmd(args, EC_NODE_ENDLIST)
+
+struct ec_node *__ec_node_cmd(const char *id, const char *cmd_str, ...);
+
+#endif
diff --git a/include/ecoli_node_dynamic.h b/include/ecoli_node_dynamic.h
new file mode 100644 (file)
index 0000000..4f2535e
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2017, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_DYNAMIC_
+#define ECOLI_NODE_DYNAMIC_
+
+struct ec_node;
+struct ec_parse;
+
+/* callback invoked by parse() or complete() to build the dynamic node
+ * the behavior of the node can depend on what is already parsed */
+typedef struct ec_node *(*ec_node_dynamic_build_t)(
+       struct ec_parse *state, void *opaque);
+
+struct ec_node *ec_node_dynamic(const char *id, ec_node_dynamic_build_t build,
+                               void *opaque);
+
+#endif
diff --git a/include/ecoli_node_empty.h b/include/ecoli_node_empty.h
new file mode 100644 (file)
index 0000000..ed5e32e
--- /dev/null
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * This node always matches an empty string vector
+ */
+
+#ifndef ECOLI_NODE_EMPTY_
+#define ECOLI_NODE_EMPTY_
+
+struct ec_node *ec_node_empty(const char *id);
+
+#endif
diff --git a/include/ecoli_node_expr.h b/include/ecoli_node_expr.h
new file mode 100644 (file)
index 0000000..4f21d81
--- /dev/null
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_EXPR_
+#define ECOLI_NODE_EXPR_
+
+#include <ecoli_node.h>
+
+/**
+ * Callback function type for evaluating a variable
+ *
+ * @param result
+ *   On success, this pointer must be set by the user to point
+ *   to a user structure describing the evaluated result.
+ * @param userctx
+ *   A user-defined context passed to all callback functions, which
+ *   can be used to maintain a state or store global information.
+ * @param var
+ *   The parse result referencing the variable.
+ * @return
+ *   0 on success (*result must be set), or -errno on error (*result
+ *   is undefined).
+ */
+typedef int (*ec_node_expr_eval_var_t)(
+       void **result, void *userctx,
+       const struct ec_parse *var);
+
+/**
+ * Callback function type for evaluating a prefix-operator
+ *
+ * @param result
+ *   On success, this pointer must be set by the user to point
+ *   to a user structure describing the evaluated result.
+ * @param userctx
+ *   A user-defined context passed to all callback functions, which
+ *   can be used to maintain a state or store global information.
+ * @param operand
+ *   The evaluated expression on which the operation should be applied.
+ * @param var
+ *   The parse result referencing the operator.
+ * @return
+ *   0 on success (*result must be set, operand is freed),
+ *   or -errno on error (*result is undefined, operand is not freed).
+ */
+typedef int (*ec_node_expr_eval_pre_op_t)(
+       void **result, void *userctx,
+       void *operand,
+       const struct ec_parse *operator);
+
+typedef int (*ec_node_expr_eval_post_op_t)(
+       void **result, void *userctx,
+       void *operand,
+       const struct ec_parse *operator);
+
+typedef int (*ec_node_expr_eval_bin_op_t)(
+       void **result, void *userctx,
+       void *operand1,
+       const struct ec_parse *operator,
+       void *operand2);
+
+typedef int (*ec_node_expr_eval_parenthesis_t)(
+       void **result, void *userctx,
+       const struct ec_parse *open_paren,
+       const struct ec_parse *close_paren,
+       void * value);
+
+typedef void (*ec_node_expr_eval_free_t)(
+       void *result, void *userctx);
+
+
+struct ec_node *ec_node_expr(const char *id);
+int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node);
+int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op);
+int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op);
+int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op);
+int ec_node_expr_add_parenthesis(struct ec_node *gen_node,
+       struct ec_node *open, struct ec_node *close);
+
+struct ec_node_expr_eval_ops {
+       ec_node_expr_eval_var_t eval_var;
+       ec_node_expr_eval_pre_op_t eval_pre_op;
+       ec_node_expr_eval_post_op_t eval_post_op;
+       ec_node_expr_eval_bin_op_t eval_bin_op;
+       ec_node_expr_eval_parenthesis_t eval_parenthesis;
+       ec_node_expr_eval_free_t eval_free;
+};
+
+int ec_node_expr_eval(void **result, const struct ec_node *node,
+       struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops,
+       void *userctx);
+
+#endif
diff --git a/include/ecoli_node_file.h b/include/ecoli_node_file.h
new file mode 100644 (file)
index 0000000..5760902
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_FILE_
+#define ECOLI_NODE_FILE_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_file(const char *id, const char *file);
+
+/* file is duplicated */
+int ec_node_file_set_str(struct ec_node *node, const char *file);
+
+#endif
diff --git a/include/ecoli_node_helper.h b/include/ecoli_node_helper.h
new file mode 100644 (file)
index 0000000..9dbf519
--- /dev/null
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Helpers that are commonly used in nodes.
+ */
+
+#ifndef ECOLI_NODE_HELPERS_
+#define ECOLI_NODE_HELPERS
+
+struct ec_node;
+
+/**
+ * Build a node table from a node list in a ec_config.
+ *
+ * The function takes a node configuration as parameter, which must be a
+ * node list. From it, a node table is built. A reference is taken for
+ * each node.
+ *
+ * On error, no reference is taken.
+ *
+ * @param config
+ *   The configuration (type must be a list of nodes). If it is
+ *   NULL, an error is returned.
+ * @param len
+ *   The length of the allocated table on success, or 0 on error.
+ * @return
+ *   The allocated node table, that must be freed by the caller:
+ *   each entry must be freed with ec_node_free() and the table
+ *   with ec_free(). On error, NULL is returned and errno is set.
+ */
+struct ec_node **
+ec_node_config_node_list_to_table(const struct ec_config *config,
+                               size_t *len);
+
+/**
+ * Build a list of config nodes from variable arguments.
+ *
+ * The va_list argument is a list of pointer to ec_node structures,
+ * terminated with EC_NODE_ENDLIST.
+ *
+ * This helper is used by nodes that contain a list of nodes,
+ * like "seq", "or", ...
+ *
+ * @param ap
+ *   List of pointer to ec_node structures, terminated with
+ *   EC_NODE_ENDLIST.
+ * @return
+ *   A pointer to an ec_config structure. In this case, the
+ *   nodes will be freed when the config structure will be freed.
+ *   On error, NULL is returned (and errno is set), and the
+ *   nodes are freed.
+ */
+struct ec_config *
+ec_node_config_node_list_from_vargs(va_list ap);
+
+#endif
diff --git a/include/ecoli_node_int.h b/include/ecoli_node_int.h
new file mode 100644 (file)
index 0000000..b64c60c
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_INT_
+#define ECOLI_NODE_INT_
+
+#include <stdint.h>
+
+#include <ecoli_node.h>
+
+/* ec_node("int", ...) can be used too
+ * default is no limit, base 10 */
+
+struct ec_node *ec_node_int(const char *id, int64_t min,
+                       int64_t max, unsigned int base);
+
+int ec_node_int_getval(const struct ec_node *node, const char *str,
+                       int64_t *result);
+
+
+
+struct ec_node *ec_node_uint(const char *id, uint64_t min,
+                       uint64_t max, unsigned int base);
+
+int ec_node_uint_getval(const struct ec_node *node, const char *str,
+                       uint64_t *result);
+
+
+#endif
diff --git a/include/ecoli_node_many.h b/include/ecoli_node_many.h
new file mode 100644 (file)
index 0000000..0a50fd7
--- /dev/null
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_MANY_
+#define ECOLI_NODE_MANY_
+
+/*
+ * if min == max == 0, there is no limit
+ */
+struct ec_node *ec_node_many(const char *id, struct ec_node *child,
+       unsigned int min, unsigned int max);
+
+int
+ec_node_many_set_params(struct ec_node *gen_node, struct ec_node *child,
+       unsigned int min, unsigned int max);
+
+#endif
diff --git a/include/ecoli_node_none.h b/include/ecoli_node_none.h
new file mode 100644 (file)
index 0000000..842f211
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * This node does not match anything
+ */
+
+#ifndef ECOLI_NODE_ANY_
+#define ECOLI_NODE_ANY_
+
+#endif
diff --git a/include/ecoli_node_once.h b/include/ecoli_node_once.h
new file mode 100644 (file)
index 0000000..690a10c
--- /dev/null
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_ONCE_
+#define ECOLI_NODE_ONCE_
+
+#include <ecoli_node.h>
+
+/* This node behaves like its child, but prevent from parsing it several
+ * times.
+ *
+ * Example:
+ *   many(
+ *     or(
+ *       once(str("foo")),
+ *       str("bar")))
+ *
+ *   Matches: [], ["foo", "bar"], ["bar", "bar"], ["foo", "bar", "bar"], ...
+ *   But not: ["foo", "foo"], ["foo", "bar", "foo"], ...
+ */
+
+/* on error, child is *not* freed */
+struct ec_node *ec_node_once(const char *id, struct ec_node *child);
+
+/* on error, child is freed */
+int ec_node_once_set_child(struct ec_node *node, struct ec_node *child);
+
+#endif
diff --git a/include/ecoli_node_option.h b/include/ecoli_node_option.h
new file mode 100644 (file)
index 0000000..9f06d5f
--- /dev/null
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_OPTION_
+#define ECOLI_NODE_OPTION_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_option(const char *id, struct ec_node *node);
+int ec_node_option_set_child(struct ec_node *gen_node, struct ec_node *child);
+
+#endif
diff --git a/include/ecoli_node_or.h b/include/ecoli_node_or.h
new file mode 100644 (file)
index 0000000..db115b2
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_OR_
+#define ECOLI_NODE_OR_
+
+#include <ecoli_node.h>
+
+#define EC_NODE_OR(args...) __ec_node_or(args, EC_NODE_ENDLIST)
+
+/* list must be terminated with EC_NODE_ENDLIST */
+/* all nodes given in the list will be freed when freeing this one */
+/* avoid using this function directly, prefer the macro EC_NODE_OR() or
+ * ec_node_or() + ec_node_or_add() */
+struct ec_node *__ec_node_or(const char *id, ...);
+
+struct ec_node *ec_node_or(const char *id);
+
+/* child is consumed */
+int ec_node_or_add(struct ec_node *node, struct ec_node *child);
+
+
+#endif
diff --git a/include/ecoli_node_re.h b/include/ecoli_node_re.h
new file mode 100644 (file)
index 0000000..bc3a317
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_RE_
+#define ECOLI_NODE_RE_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_re(const char *id, const char *str);
+
+/* re is duplicated */
+int ec_node_re_set_regexp(struct ec_node *node, const char *re);
+
+#endif
diff --git a/include/ecoli_node_re_lex.h b/include/ecoli_node_re_lex.h
new file mode 100644 (file)
index 0000000..94d426e
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_RE_LEX_
+#define ECOLI_NODE_RE_LEX_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child);
+
+int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep,
+       const char *attr_name);
+
+#endif
diff --git a/include/ecoli_node_seq.h b/include/ecoli_node_seq.h
new file mode 100644 (file)
index 0000000..21c96b1
--- /dev/null
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_SEQ_
+#define ECOLI_NODE_SEQ_
+
+#include <ecoli_node.h>
+
+#define EC_NODE_SEQ(args...) __ec_node_seq(args, EC_NODE_ENDLIST)
+
+/* list must be terminated with EC_NODE_ENDLIST */
+/* all nodes given in the list will be freed when freeing this one */
+/* avoid using this function directly, prefer the macro EC_NODE_SEQ() or
+ * ec_node_seq() + ec_node_seq_add() */
+struct ec_node *__ec_node_seq(const char *id, ...);
+
+struct ec_node *ec_node_seq(const char *id);
+
+/* child is consumed */
+int ec_node_seq_add(struct ec_node *node, struct ec_node *child);
+
+#endif
diff --git a/include/ecoli_node_sh_lex.h b/include/ecoli_node_sh_lex.h
new file mode 100644 (file)
index 0000000..d45b998
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_SHLEX_
+#define ECOLI_NODE_SHLEX_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child);
+
+#endif
diff --git a/include/ecoli_node_space.h b/include/ecoli_node_space.h
new file mode 100644 (file)
index 0000000..0dd6202
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * This node matches one string in the vector if it is only composed of
+ * spaces, as interpreted by isspace().
+ */
+
+#ifndef ECOLI_NODE_SPACE_
+#define ECOLI_NODE_SPACE_
+
+/* no API for now, since there is no specific configuration for this node */
+
+#endif
diff --git a/include/ecoli_node_str.h b/include/ecoli_node_str.h
new file mode 100644 (file)
index 0000000..8a8634f
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_STR_
+#define ECOLI_NODE_STR_
+
+#include <ecoli_node.h>
+
+struct ec_node *ec_node_str(const char *id, const char *str);
+
+/* str is duplicated */
+int ec_node_str_set_str(struct ec_node *node, const char *str);
+
+#endif
diff --git a/include/ecoli_node_subset.h b/include/ecoli_node_subset.h
new file mode 100644 (file)
index 0000000..734b1ae
--- /dev/null
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_NODE_SUBSET_
+#define ECOLI_NODE_SUBSET_
+
+#include <ecoli_node.h>
+
+#define EC_NODE_SUBSET(args...) __ec_node_subset(args, EC_NODE_ENDLIST)
+
+/* list must be terminated with EC_NODE_ENDLIST */
+/* all nodes given in the list will be freed when freeing this one */
+/* avoid using this function directly, prefer the macro EC_NODE_SUBSET() or
+ * ec_node_subset() + ec_node_subset_add() */
+struct ec_node *__ec_node_subset(const char *id, ...);
+
+struct ec_node *ec_node_subset(const char *id);
+
+/* child is consumed */
+int ec_node_subset_add(struct ec_node *node, struct ec_node *child);
+
+#endif
diff --git a/include/ecoli_parse.h b/include/ecoli_parse.h
new file mode 100644 (file)
index 0000000..a431ba2
--- /dev/null
@@ -0,0 +1,239 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Node parse API.
+ *
+ * The parse operation is to check if an input (a string or vector of
+ * strings) matches the node tree. On success, the result is stored in a
+ * tree that describes which part of the input matches which node.
+ */
+
+#ifndef ECOLI_PARSE_
+#define ECOLI_PARSE_
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+struct ec_node;
+struct ec_parse;
+
+/**
+ * Create an empty parse tree.
+ *
+ * @return
+ *   The empty parse tree.
+ */
+struct ec_parse *ec_parse(const struct ec_node *node);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_free(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_free_children(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_dup(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+// _get_ XXX
+const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse);
+
+/* a NULL return value is an error, with errno set
+  ENOTSUP: no ->parse() operation
+*/
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_node_parse_strvec(const struct ec_node *node,
+                               const struct ec_strvec *strvec);
+
+/**
+ *
+ *
+ *
+ */
+#define EC_PARSE_NOMATCH INT_MAX
+
+/* internal: used by nodes
+ *
+ * state is the current parse tree, which is built piece by piece while
+ *   parsing the node tree: ec_node_parse_child() creates a new child in
+ *   this state parse tree, and calls the parse() method for the child
+ *   node, with state pointing to this new child. If it does not match,
+ *   the child is removed in the state, else it is kept, with its
+ *   possible descendants.
+ *
+ * return:
+ * the number of matched strings in strvec on success
+ * EC_PARSE_NOMATCH (positive) if it does not match
+ * -1 on error, and errno is set
+ */
+int ec_node_parse_child(const struct ec_node *node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_link_child(struct ec_parse *parse,
+                       struct ec_parse *child);
+/**
+ *
+ *
+ *
+ */
+void ec_parse_unlink_child(struct ec_parse *parse,
+                       struct ec_parse *child);
+
+/* keep the const */
+#define ec_parse_get_root(parse) ({                            \
+       const struct ec_parse *p_ = parse; /* check type */     \
+       struct ec_parse *parse_ = (struct ec_parse *)parse;     \
+       typeof(parse) res_;                                     \
+       (void)p_;                                               \
+       res_ = __ec_parse_get_root(parse_);                     \
+       res_;                                                   \
+})
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *__ec_parse_get_root(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse);
+
+/**
+ * Get the first child of a tree.
+ *
+ */
+struct ec_parse *ec_parse_get_first_child(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_get_last_child(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_get_next(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+#define EC_PARSE_FOREACH_CHILD(child, parse)                   \
+       for (child = ec_parse_get_first_child(parse);           \
+               child != NULL;                                  \
+               child = ec_parse_get_next(child))               \
+
+/**
+ *
+ *
+ *
+ */
+bool ec_parse_has_child(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+const struct ec_node *ec_parse_get_node(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_del_last_child(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_keyval *ec_parse_get_attrs(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+void ec_parse_dump(FILE *out, const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+struct ec_parse *ec_parse_find_first(struct ec_parse *parse,
+       const char *id);
+
+/**
+ * Iterate among parse tree
+ *
+ * Use it with:
+ * for (iter = state; iter != NULL; iter = ec_parse_iter_next(iter))
+ */
+struct ec_parse *ec_parse_iter_next(struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+size_t ec_parse_len(const struct ec_parse *parse);
+
+/**
+ *
+ *
+ *
+ */
+size_t ec_parse_matches(const struct ec_parse *parse);
+
+#endif
diff --git a/include/ecoli_string.h b/include/ecoli_string.h
new file mode 100644 (file)
index 0000000..a523b88
--- /dev/null
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_STRING_
+#define ECOLI_STRING_
+
+#include <stddef.h>
+#include <stdbool.h>
+
+/* count the number of identical chars at the beginning of 2 strings */
+size_t ec_strcmp_count(const char *s1, const char *s2);
+
+/* return 1 if 's' starts with 'beginning' */
+int ec_str_startswith(const char *s, const char *beginning);
+
+/* like asprintf, but use libecoli allocator */
+int ec_asprintf(char **buf, const char *fmt, ...);
+
+/* like vasprintf, but use libecoli allocator */
+int ec_vasprintf(char **buf, const char *fmt, va_list ap);
+
+/* return true if string is only composed of spaces (' ', '\n', ...) */
+bool ec_str_is_space(const char *s);
+
+#endif
diff --git a/include/ecoli_strvec.h b/include/ecoli_strvec.h
new file mode 100644 (file)
index 0000000..cabe6b2
--- /dev/null
@@ -0,0 +1,224 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Vectors of strings.
+ *
+ * The ec_strvec API provide helpers to manipulate string vectors.
+ * When duplicating vectors, the strings are not duplicated in memory,
+ * a reference counter is used.
+ */
+
+#ifndef ECOLI_STRVEC_
+#define ECOLI_STRVEC_
+
+#include <stdio.h>
+
+/**
+ * Allocate a new empty string vector.
+ *
+ * @return
+ *   The new strvec object, or NULL on error (errno is set).
+ */
+struct ec_strvec *ec_strvec(void);
+
+#ifndef EC_COUNT_OF
+#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
+               ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+#endif
+
+/**
+ * Allocate a new string vector
+ *
+ * The string vector is initialized with the list of const strings
+ * passed as arguments.
+ *
+ * @return
+ *   The new strvec object, or NULL on error (errno is set).
+ */
+#define EC_STRVEC(args...) ({                                          \
+                       const char *_arr[] = {args};                    \
+                       ec_strvec_from_array(_arr, EC_COUNT_OF(_arr));  \
+               })
+/**
+ * Allocate a new string vector
+ *
+ * The string vector is initialized with the array of const strings
+ * passed as arguments.
+ *
+ * @param strarr
+ *   The array of const strings.
+ * @param n
+ *   The number of strings in the array.
+ * @return
+ *   The new strvec object, or NULL on error (errno is set).
+ */
+struct ec_strvec *ec_strvec_from_array(const char * const *strarr,
+       size_t n);
+
+/**
+ * Set a string in the vector at specified index.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param idx
+ *   The index of the string to set.
+ * @param s
+ *   The string to be set.
+ * @return
+ *   0 on success or -1 on error (errno is set).
+ */
+int ec_strvec_set(struct ec_strvec *strvec, size_t idx, const char *s);
+
+/**
+ * Add a string in a vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param s
+ *   The string to be added at the end of the vector.
+ * @return
+ *   0 on success or -1 on error (errno is set).
+ */
+int ec_strvec_add(struct ec_strvec *strvec, const char *s);
+
+/**
+ * Delete the last entry in the string vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param s
+ *   The string to be added at the end of the vector.
+ * @return
+ *   0 on success or -1 on error (errno is set).
+ */
+int ec_strvec_del_last(struct ec_strvec *strvec);
+
+/**
+ * Duplicate a string vector.
+ *
+ * Attributes are duplicated if any.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @return
+ *   The duplicated strvec object, or NULL on error (errno is set).
+ */
+struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec);
+
+/**
+ * Duplicate a part of a string vector.
+ *
+ * Attributes are duplicated if any.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param off
+ *   The index of the first string to duplicate.
+ * @param
+ *   The number of strings to duplicate.
+ * @return
+ *   The duplicated strvec object, or NULL on error (errno is set).
+ */
+struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec,
+       size_t off, size_t len);
+
+/**
+ * Free a string vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ */
+void ec_strvec_free(struct ec_strvec *strvec);
+
+/**
+ * Get the length of a string vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @return
+ *   The length of the vector.
+ */
+size_t ec_strvec_len(const struct ec_strvec *strvec);
+
+/**
+ * Get a string element from a vector.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param idx
+ *   The index of the string to get.
+ * @return
+ *   The string stored at given index, or NULL on error (strvec is NULL
+ *   or invalid index).
+ */
+const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx);
+
+/**
+ * Get the attributes of a vector element.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param idx
+ *   The index of the string to get.
+ * @return
+ *   The read-only attributes (dictionnary) of the string at specified
+ *   index, or NULL if there is no attribute.
+ */
+const struct ec_keyval *ec_strvec_get_attrs(const struct ec_strvec *strvec,
+       size_t idx);
+
+/**
+ * Set the attributes of a vector element.
+ *
+ * @param strvec
+ *   The pointer to the string vector.
+ * @param idx
+ *   The index of the string to get.
+ * @param attrs
+ *   The attributes to be set.
+ * @return
+ *   0 on success, -1 on error (errno is set). On error, attrs
+ *   are freed and must not be used by the caller.
+ */
+int ec_strvec_set_attrs(struct ec_strvec *strvec, size_t idx,
+                       struct ec_keyval *attrs);
+
+/**
+ * Compare two string vectors
+ *
+ * @param strvec
+ *   The pointer to the first string vector.
+ * @param strvec
+ *   The pointer to the second string vector.
+ * @return
+ *   0 if the string vectors are equal.
+ */
+int ec_strvec_cmp(const struct ec_strvec *strvec1,
+               const struct ec_strvec *strvec2);
+
+/**
+ * Sort the string vector.
+ *
+ * Attributes are not compared.
+ *
+ * @param strvec
+ *   The pointer to the first string vector.
+ * @param str_cmp
+ *   The sort function to use. If NULL, use strcmp.
+ */
+void ec_strvec_sort(struct ec_strvec *strvec,
+               int (*str_cmp)(const char *s1, const char *s2));
+
+/**
+ * Dump a string vector.
+ *
+ * @param out
+ *   The stream where the dump is sent.
+ * @param strvec
+ *   The pointer to the string vector.
+ */
+void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec);
+
+#endif
diff --git a/include/ecoli_test.h b/include/ecoli_test.h
new file mode 100644 (file)
index 0000000..94cd0b9
--- /dev/null
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_TEST_
+#define ECOLI_TEST_
+
+#include <sys/queue.h>
+
+#include <ecoli_log.h>
+
+struct ec_node;
+enum ec_comp_type;
+
+#define EC_TEST_REGISTER(t)                                            \
+       static void ec_test_init_##t(void);                             \
+       static void __attribute__((constructor, used))                  \
+       ec_test_init_##t(void)                                          \
+       {                                                               \
+               if (ec_test_register(&t) < 0)                           \
+                       fprintf(stderr, "cannot register test %s\n",    \
+                               t.name);                                \
+       }
+
+/**
+ * Type of test function. Return 0 on success, -1 on error.
+ */
+typedef int (ec_test_t)(void);
+
+TAILQ_HEAD(ec_test_list, ec_test);
+
+/**
+ * A structure describing a test case.
+ */
+struct ec_test {
+       TAILQ_ENTRY(ec_test) next;  /**< Next in list. */
+       const char *name;           /**< Test name. */
+       ec_test_t *test;            /**< Test function. */
+};
+
+/**
+ * Register a test case.
+ *
+ * @param test
+ *   A pointer to a ec_test structure describing the test
+ *   to be registered.
+ * @return
+ *   0 on success, -1 on error (errno is set).
+ */
+int ec_test_register(struct ec_test *test);
+
+int ec_test_all(void);
+int ec_test_one(const char *name);
+
+/* expected == -1 means no match */
+int ec_test_check_parse(struct ec_node *node, int expected, ...);
+
+#define EC_TEST_ERR(fmt, ...)                                          \
+       EC_LOG(EC_LOG_ERR, "%s:%d: error: " fmt "\n",                   \
+               __FILE__, __LINE__, ##__VA_ARGS__);                     \
+
+#define EC_TEST_CHECK(cond, fmt, ...) ({                               \
+       int ret_ = 0;                                                   \
+       if (!(cond)) {                                                  \
+               EC_TEST_ERR("(" #cond ") is wrong. " fmt                \
+                       ##__VA_ARGS__);                                 \
+               ret_ = -1;                                              \
+       }                                                               \
+       ret_;                                                           \
+})
+
+/* node, input, [expected1, expected2, ...] */
+#define EC_TEST_CHECK_PARSE(node, args...) ({                          \
+       int ret_ = ec_test_check_parse(node, args, EC_NODE_ENDLIST);    \
+       if (ret_)                                                       \
+               EC_TEST_ERR("parse test failed");                       \
+       ret_;                                                           \
+})
+
+int ec_test_check_complete(struct ec_node *node,
+                       enum ec_comp_type type, ...);
+
+#define EC_TEST_CHECK_COMPLETE(node, args...) ({                       \
+       int ret_ = ec_test_check_complete(node, EC_COMP_FULL, args);    \
+       if (ret_)                                                       \
+               EC_TEST_ERR("complete test failed");                    \
+       ret_;                                                           \
+})
+
+#define EC_TEST_CHECK_COMPLETE_PARTIAL(node, args...) ({               \
+       int ret_ = ec_test_check_complete(node, EC_COMP_PARTIAL, args); \
+       if (ret_)                                                       \
+               EC_TEST_ERR("complete test failed");                    \
+       ret_;                                                           \
+})
+
+#endif
diff --git a/include/ecoli_utils.h b/include/ecoli_utils.h
new file mode 100644 (file)
index 0000000..5a14192
--- /dev/null
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#ifndef ECOLI_UTILS_
+#define ECOLI_UTILS_
+
+/**
+ * Cast a variable into a type, ensuring its initial type first
+ */
+#define EC_CAST(x, old_type, new_type) ({      \
+       old_type __x = (x);                     \
+       (new_type)__x;                          \
+       })
+
+#endif
diff --git a/include/ecoli_vec.h b/include/ecoli_vec.h
new file mode 100644 (file)
index 0000000..5fdaa99
--- /dev/null
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * Vectors of objects.
+ *
+ * The ec_vec API provide helpers to manipulate vectors of objects
+ * of any kind.
+ */
+
+#ifndef ECOLI_VEC_
+#define ECOLI_VEC_
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdio.h>
+
+/* if NULL, default does nothing */
+typedef void (*ec_vec_elt_free_t)(void *ptr);
+
+/* if NULL, default is:
+ * memcpy(dst, src, vec->elt_size)
+ */
+typedef void (*ec_vec_elt_copy_t)(void *dst, void *src);
+
+struct ec_vec *ec_vec(size_t elt_size, size_t size,
+               ec_vec_elt_copy_t copy, ec_vec_elt_free_t free);
+int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr);
+
+int ec_vec_add_ptr(struct ec_vec *vec, void *elt);
+int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt);
+int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt);
+int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt);
+int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt);
+
+int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx);
+
+struct ec_vec *ec_vec_dup(const struct ec_vec *vec);
+struct ec_vec *ec_vec_ndup(const struct ec_vec *vec,
+       size_t off, size_t len);
+void ec_vec_free(struct ec_vec *vec);
+
+__attribute__((pure))
+size_t ec_vec_len(const struct ec_vec *vec);
+
+#endif
diff --git a/include/ecoli_yaml.h b/include/ecoli_yaml.h
new file mode 100644 (file)
index 0000000..a63d837
--- /dev/null
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+/**
+ * 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
diff --git a/include/meson.build b/include/meson.build
new file mode 100644 (file)
index 0000000..ea33eb4
--- /dev/null
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+
+libecoli_headers = [
+       'ecoli_assert.h',
+       'ecoli_complete.h',
+       'ecoli_config.h',
+       'ecoli_init.h',
+       'ecoli_keyval.h',
+       'ecoli_log.h',
+       'ecoli_malloc.h',
+       'ecoli_murmurhash.h',
+       'ecoli_node_any.h',
+       'ecoli_node_cmd.h',
+       'ecoli_node_dynamic.h',
+       'ecoli_node_empty.h',
+       'ecoli_node_expr.h',
+       'ecoli_node_file.h',
+       'ecoli_node.h',
+       'ecoli_node_helper.h',
+       'ecoli_node_int.h',
+       'ecoli_node_many.h',
+       'ecoli_node_none.h',
+       'ecoli_node_once.h',
+       'ecoli_node_option.h',
+       'ecoli_node_or.h',
+       'ecoli_node_re.h',
+       'ecoli_node_re_lex.h',
+       'ecoli_node_seq.h',
+       'ecoli_node_sh_lex.h',
+       'ecoli_node_space.h',
+       'ecoli_node_str.h',
+       'ecoli_node_subset.h',
+       'ecoli_parse.h',
+       'ecoli_string.h',
+       'ecoli_strvec.h',
+       'ecoli_test.h',
+       'ecoli_utils.h',
+       'ecoli_vec.h',
+]
+install_headers(libecoli_headers)
diff --git a/libecoli/ecoli_assert.c b/libecoli/ecoli_assert.c
deleted file mode 100644 (file)
index a36d6f6..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-#include <ecoli_assert.h>
-
-void __ec_assert_print(bool expr, const char *expr_str, const char *format, ...)
-{
-       va_list ap;
-
-       if (expr)
-               return;
-
-       /* LCOV_EXCL_START */
-       va_start(ap, format);
-       fprintf(stderr, "assertion failed: '%s' is false\n", expr_str);
-       vfprintf(stderr, format, ap);
-       va_end(ap);
-       abort();
-       /* LCOV_EXCL_END */
-}
diff --git a/libecoli/ecoli_assert.h b/libecoli/ecoli_assert.h
deleted file mode 100644 (file)
index fcd2186..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Assert API
- *
- * Helpers to check at runtime if a condition is true, or otherwise
- * either abort (exit program) or return an error.
- */
-
-#ifndef ECOLI_ASSERT_
-#define ECOLI_ASSERT_
-
-#include <stdbool.h>
-
-/**
- * Abort if the condition is false.
- *
- * If expression is false this macro will prints an error message to
- * standard error and terminates the program by calling abort(3).
- *
- * @param expr
- *   The expression to be checked.
- * @param args
- *   The format string, optionally followed by other arguments.
- */
-#define ec_assert_print(expr, args...) \
-       __ec_assert_print(expr, #expr, args)
-
-/* internal */
-void __ec_assert_print(bool expr, const char *expr_str,
-               const char *format, ...);
-
-/**
- * Check a condition or return.
- *
- * If the condition is true, do nothing. If it is false, set
- * errno and return the specified value.
- *
- * @param cond
- *   The condition to test.
- * @param ret
- *   The value to return.
- * @param err
- *   The errno to set.
- */
-#define EC_CHECK_ARG(cond, ret, err) do {                              \
-               if (!(cond)) {                                          \
-                       errno = err;                                    \
-                       return ret;                                     \
-               }                                                       \
-       } while(0)
-
-#endif
diff --git a/libecoli/ecoli_complete.c b/libecoli/ecoli_complete.c
deleted file mode 100644 (file)
index a9becdf..0000000
+++ /dev/null
@@ -1,765 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_string.h>
-#include <ecoli_strvec.h>
-#include <ecoli_keyval.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_node_sh_lex.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_or.h>
-#include <ecoli_complete.h>
-
-EC_LOG_TYPE_REGISTER(comp);
-
-struct ec_comp_item {
-       TAILQ_ENTRY(ec_comp_item) next;
-       enum ec_comp_type type;
-       struct ec_comp_group *grp;
-       char *start;      /* the initial token */
-       char *full;       /* the full token after completion */
-       char *completion; /* chars that are added, NULL if not applicable */
-       char *display;    /* what should be displayed by help/completers */
-       struct ec_keyval *attrs;
-};
-
-struct ec_comp *ec_comp(struct ec_parse *state)
-{
-       struct ec_comp *comp = NULL;
-
-       comp = ec_calloc(1, sizeof(*comp));
-       if (comp == NULL)
-               goto fail;
-
-       comp->attrs = ec_keyval();
-       if (comp->attrs == NULL)
-               goto fail;
-
-       TAILQ_INIT(&comp->groups);
-
-       comp->cur_state = state;
-
-       return comp;
-
- fail:
-       if (comp != NULL)
-               ec_keyval_free(comp->attrs);
-       ec_free(comp);
-
-       return NULL;
-}
-
-struct ec_parse *ec_comp_get_state(struct ec_comp *comp)
-{
-       return comp->cur_state;
-}
-
-int
-ec_node_complete_child(const struct ec_node *node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_parse *child_state, *cur_state;
-       struct ec_comp_group *cur_group;
-       int ret;
-
-       if (ec_node_type(node)->complete == NULL) {
-               errno = ENOTSUP;
-               return -1;
-       }
-
-       /* save previous parse state, prepare child state */
-       cur_state = comp->cur_state;
-       child_state = ec_parse(node);
-       if (child_state == NULL)
-               return -1;
-
-       if (cur_state != NULL)
-               ec_parse_link_child(cur_state, child_state);
-       comp->cur_state = child_state;
-       cur_group = comp->cur_group;
-       comp->cur_group = NULL;
-
-       /* fill the comp struct with items */
-       ret = ec_node_type(node)->complete(node, comp, strvec);
-
-       /* restore parent parse state */
-       if (cur_state != NULL) {
-               ec_parse_unlink_child(cur_state, child_state);
-               assert(!ec_parse_has_child(child_state));
-       }
-       ec_parse_free(child_state);
-       comp->cur_state = cur_state;
-       comp->cur_group = cur_group;
-
-       if (ret < 0)
-               return -1;
-
-       return 0;
-}
-
-struct ec_comp *ec_node_complete_strvec(const struct ec_node *node,
-       const struct ec_strvec *strvec)
-{
-       struct ec_comp *comp = NULL;
-       int ret;
-
-       comp = ec_comp(NULL);
-       if (comp == NULL)
-               goto fail;
-
-       ret = ec_node_complete_child(node, comp, strvec);
-       if (ret < 0)
-               goto fail;
-
-       return comp;
-
-fail:
-       ec_comp_free(comp);
-       return NULL;
-}
-
-struct ec_comp *ec_node_complete(const struct ec_node *node,
-       const char *str)
-{
-       struct ec_strvec *strvec = NULL;
-       struct ec_comp *comp;
-
-       errno = ENOMEM;
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       if (ec_strvec_add(strvec, str) < 0)
-               goto fail;
-
-       comp = ec_node_complete_strvec(node, strvec);
-       if (comp == NULL)
-               goto fail;
-
-       ec_strvec_free(strvec);
-       return comp;
-
- fail:
-       ec_strvec_free(strvec);
-       return NULL;
-}
-
-static struct ec_comp_group *
-ec_comp_group(const struct ec_node *node, struct ec_parse *parse)
-{
-       struct ec_comp_group *grp = NULL;
-
-       grp = ec_calloc(1, sizeof(*grp));
-       if (grp == NULL)
-               return NULL;
-
-       grp->attrs = ec_keyval();
-       if (grp->attrs == NULL)
-               goto fail;
-
-       grp->state = ec_parse_dup(parse);
-       if (grp->state == NULL)
-               goto fail;
-
-       grp->node = node;
-       TAILQ_INIT(&grp->items);
-
-       return grp;
-
-fail:
-       if (grp != NULL) {
-               ec_parse_free(grp->state);
-               ec_keyval_free(grp->attrs);
-       }
-       ec_free(grp);
-       return NULL;
-}
-
-static struct ec_comp_item *
-ec_comp_item(enum ec_comp_type type,
-       const char *start, const char *full)
-{
-       struct ec_comp_item *item = NULL;
-       struct ec_keyval *attrs = NULL;
-       char *comp_cp = NULL, *start_cp = NULL;
-       char *full_cp = NULL, *display_cp = NULL;
-
-       if (type == EC_COMP_UNKNOWN && full != NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-       if (type != EC_COMP_UNKNOWN && full == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       item = ec_calloc(1, sizeof(*item));
-       if (item == NULL)
-               goto fail;
-
-       attrs = ec_keyval();
-       if (attrs == NULL)
-               goto fail;
-
-       if (start != NULL) {
-               start_cp = ec_strdup(start);
-               if (start_cp == NULL)
-                       goto fail;
-
-               if (ec_str_startswith(full, start)) {
-                       comp_cp = ec_strdup(&full[strlen(start)]);
-                       if (comp_cp == NULL)
-                               goto fail;
-               }
-       }
-       if (full != NULL) {
-               full_cp = ec_strdup(full);
-               if (full_cp == NULL)
-                       goto fail;
-               display_cp = ec_strdup(full);
-               if (display_cp == NULL)
-                       goto fail;
-       }
-
-       item->type = type;
-       item->start = start_cp;
-       item->full = full_cp;
-       item->completion = comp_cp;
-       item->display = display_cp;
-       item->attrs = attrs;
-
-       return item;
-
-fail:
-       ec_keyval_free(attrs);
-       ec_free(comp_cp);
-       ec_free(start_cp);
-       ec_free(full_cp);
-       ec_free(display_cp);
-       ec_free(item);
-
-       return NULL;
-}
-
-int ec_comp_item_set_display(struct ec_comp_item *item,
-                               const char *display)
-{
-       char *display_copy = NULL;
-
-       if (item == NULL || display == NULL ||
-                       item->type == EC_COMP_UNKNOWN) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       display_copy = ec_strdup(display);
-       if (display_copy == NULL)
-               goto fail;
-
-       ec_free(item->display);
-       item->display = display_copy;
-
-       return 0;
-
-fail:
-       ec_free(display_copy);
-       return -1;
-}
-
-int
-ec_comp_item_set_completion(struct ec_comp_item *item,
-                               const char *completion)
-{
-       char *completion_copy = NULL;
-
-       if (item == NULL || completion == NULL ||
-                       item->type == EC_COMP_UNKNOWN) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       completion_copy = ec_strdup(completion);
-       if (completion_copy == NULL)
-               goto fail;
-
-       ec_free(item->completion);
-       item->completion = completion_copy;
-
-       return 0;
-
-fail:
-       ec_free(completion_copy);
-       return -1;
-}
-
-int
-ec_comp_item_set_str(struct ec_comp_item *item,
-                       const char *str)
-{
-       char *str_copy = NULL;
-
-       if (item == NULL || str == NULL ||
-                       item->type == EC_COMP_UNKNOWN) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       str_copy = ec_strdup(str);
-       if (str_copy == NULL)
-               goto fail;
-
-       ec_free(item->full);
-       item->full = str_copy;
-
-       return 0;
-
-fail:
-       ec_free(str_copy);
-       return -1;
-}
-
-static int
-ec_comp_item_add(struct ec_comp *comp, const struct ec_node *node,
-               struct ec_comp_item *item)
-{
-       if (comp == NULL || item == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       switch (item->type) {
-       case EC_COMP_UNKNOWN:
-               comp->count_unknown++;
-               break;
-       case EC_COMP_FULL:
-               comp->count_full++;
-               break;
-       case EC_COMP_PARTIAL:
-               comp->count_partial++;
-               break;
-       default:
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (comp->cur_group == NULL) {
-               struct ec_comp_group *grp;
-
-               grp = ec_comp_group(node, comp->cur_state);
-               if (grp == NULL)
-                       return -1;
-               TAILQ_INSERT_TAIL(&comp->groups, grp, next);
-               comp->cur_group = grp;
-       }
-
-       comp->count++;
-       TAILQ_INSERT_TAIL(&comp->cur_group->items, item, next);
-       item->grp = comp->cur_group;
-
-       return 0;
-}
-
-const char *
-ec_comp_item_get_str(const struct ec_comp_item *item)
-{
-       return item->full;
-}
-
-const char *
-ec_comp_item_get_display(const struct ec_comp_item *item)
-{
-       return item->display;
-}
-
-const char *
-ec_comp_item_get_completion(const struct ec_comp_item *item)
-{
-       return item->completion;
-}
-
-enum ec_comp_type
-ec_comp_item_get_type(const struct ec_comp_item *item)
-{
-       return item->type;
-}
-
-const struct ec_comp_group *
-ec_comp_item_get_grp(const struct ec_comp_item *item)
-{
-       return item->grp;
-}
-
-const struct ec_node *
-ec_comp_item_get_node(const struct ec_comp_item *item)
-{
-       return ec_comp_item_get_grp(item)->node;
-}
-
-static void
-ec_comp_item_free(struct ec_comp_item *item)
-{
-       if (item == NULL)
-               return;
-
-       ec_free(item->full);
-       ec_free(item->start);
-       ec_free(item->completion);
-       ec_free(item->display);
-       ec_keyval_free(item->attrs);
-       ec_free(item);
-}
-
-int ec_comp_add_item(struct ec_comp *comp,
-                       const struct ec_node *node,
-                       struct ec_comp_item **p_item,
-                       enum ec_comp_type type,
-                       const char *start, const char *full)
-{
-       struct ec_comp_item *item = NULL;
-       int ret;
-
-       item = ec_comp_item(type, start, full);
-       if (item == NULL)
-               return -1;
-
-       ret = ec_comp_item_add(comp, node, item);
-       if (ret < 0)
-               goto fail;
-
-       if (p_item != NULL)
-               *p_item = item;
-
-       return 0;
-
-fail:
-       ec_comp_item_free(item);
-
-       return -1;
-}
-
-/* return a completion item of type "unknown" */
-int
-ec_node_complete_unknown(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       int ret;
-
-       if (ec_strvec_len(strvec) != 1)
-               return 0;
-
-       ret = ec_comp_add_item(comp, gen_node, NULL,
-                               EC_COMP_UNKNOWN, NULL, NULL);
-       if (ret < 0)
-               return ret;
-
-       return 0;
-}
-
-static void ec_comp_group_free(struct ec_comp_group *grp)
-{
-       struct ec_comp_item *item;
-
-       if (grp == NULL)
-               return;
-
-       while (!TAILQ_EMPTY(&grp->items)) {
-               item = TAILQ_FIRST(&grp->items);
-               TAILQ_REMOVE(&grp->items, item, next);
-               ec_comp_item_free(item);
-       }
-       ec_parse_free(ec_parse_get_root(grp->state));
-       ec_keyval_free(grp->attrs);
-       ec_free(grp);
-}
-
-void ec_comp_free(struct ec_comp *comp)
-{
-       struct ec_comp_group *grp;
-
-       if (comp == NULL)
-               return;
-
-       while (!TAILQ_EMPTY(&comp->groups)) {
-               grp = TAILQ_FIRST(&comp->groups);
-               TAILQ_REMOVE(&comp->groups, grp, next);
-               ec_comp_group_free(grp);
-       }
-       ec_keyval_free(comp->attrs);
-       ec_free(comp);
-}
-
-void ec_comp_dump(FILE *out, const struct ec_comp *comp)
-{
-       struct ec_comp_group *grp;
-       struct ec_comp_item *item;
-
-       if (comp == NULL || comp->count == 0) {
-               fprintf(out, "no completion\n");
-               return;
-       }
-
-       fprintf(out, "completion: count=%u full=%u partial=%u unknown=%u\n",
-               comp->count, comp->count_full,
-               comp->count_partial,  comp->count_unknown);
-
-       TAILQ_FOREACH(grp, &comp->groups, next) {
-               fprintf(out, "node=%p, node_type=%s\n",
-                       grp->node, ec_node_type(grp->node)->name);
-               TAILQ_FOREACH(item, &grp->items, next) {
-                       const char *typestr;
-
-                       switch (item->type) {
-                       case EC_COMP_UNKNOWN: typestr = "unknown"; break;
-                       case EC_COMP_FULL: typestr = "full"; break;
-                       case EC_COMP_PARTIAL: typestr = "partial"; break;
-                       default: typestr = "unknown"; break;
-                       }
-
-                       fprintf(out, "  type=%s str=<%s> comp=<%s> disp=<%s>\n",
-                               typestr, item->full, item->completion,
-                               item->display);
-               }
-       }
-}
-
-int ec_comp_merge(struct ec_comp *to,
-               struct ec_comp *from)
-{
-       struct ec_comp_group *grp;
-
-       while (!TAILQ_EMPTY(&from->groups)) {
-               grp = TAILQ_FIRST(&from->groups);
-               TAILQ_REMOVE(&from->groups, grp, next);
-               TAILQ_INSERT_TAIL(&to->groups, grp, next);
-       }
-       to->count += from->count;
-       to->count_full += from->count_full;
-       to->count_partial += from->count_partial;
-       to->count_unknown += from->count_unknown;
-
-       ec_comp_free(from);
-       return 0;
-}
-
-unsigned int ec_comp_count(
-       const struct ec_comp *comp,
-       enum ec_comp_type type)
-{
-       unsigned int count = 0;
-
-       if (comp == NULL)
-               return count;
-
-       if (type & EC_COMP_FULL)
-               count += comp->count_full;
-       if (type & EC_COMP_PARTIAL)
-               count += comp->count_partial;
-       if (type & EC_COMP_UNKNOWN)
-               count += comp->count_unknown;
-
-       return count;
-}
-
-struct ec_comp_iter *
-ec_comp_iter(const struct ec_comp *comp,
-       enum ec_comp_type type)
-{
-       struct ec_comp_iter *iter;
-
-       iter = ec_calloc(1, sizeof(*iter));
-       if (iter == NULL)
-               return NULL;
-
-       iter->comp = comp;
-       iter->type = type;
-       iter->cur_node = NULL;
-       iter->cur_match = NULL;
-
-       return iter;
-}
-
-struct ec_comp_item *ec_comp_iter_next(
-       struct ec_comp_iter *iter)
-{
-       const struct ec_comp *comp;
-       struct ec_comp_group *cur_node;
-       struct ec_comp_item *cur_match;
-
-       if (iter == NULL)
-               return NULL;
-       comp = iter->comp;
-       if (comp == NULL)
-               return NULL;
-
-       cur_node = iter->cur_node;
-       cur_match = iter->cur_match;
-
-       /* first call */
-       if (cur_node == NULL) {
-               TAILQ_FOREACH(cur_node, &comp->groups, next) {
-                       TAILQ_FOREACH(cur_match, &cur_node->items, next) {
-                               if (cur_match != NULL &&
-                                               cur_match->type & iter->type)
-                                       goto found;
-                       }
-               }
-               return NULL;
-       } else {
-               cur_match = TAILQ_NEXT(cur_match, next);
-               if (cur_match != NULL &&
-                               cur_match->type & iter->type)
-                       goto found;
-               cur_node = TAILQ_NEXT(cur_node, next);
-               while (cur_node != NULL) {
-                       cur_match = TAILQ_FIRST(&cur_node->items);
-                       if (cur_match != NULL &&
-                                       cur_match->type & iter->type)
-                               goto found;
-                       cur_node = TAILQ_NEXT(cur_node, next);
-               }
-               return NULL;
-       }
-
-found:
-       iter->cur_node = cur_node;
-       iter->cur_match = cur_match;
-
-       return iter->cur_match;
-}
-
-void ec_comp_iter_free(struct ec_comp_iter *iter)
-{
-       ec_free(iter);
-}
-
-/* LCOV_EXCL_START */
-static int ec_comp_testcase(void)
-{
-       struct ec_node *node = NULL;
-       struct ec_comp *c = NULL;
-       struct ec_comp_iter *iter = NULL;
-       struct ec_comp_item *item;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-       int testres = 0;
-
-       node = ec_node_sh_lex(EC_NO_ID,
-                       EC_NODE_OR(EC_NO_ID,
-                               ec_node_str("id_x", "xx"),
-                               ec_node_str("id_y", "yy")));
-       if (node == NULL)
-               goto fail;
-
-       c = ec_node_complete(node, "xcdscds");
-       testres |= EC_TEST_CHECK(
-               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 0,
-               "complete count should is not 0\n");
-       ec_comp_free(c);
-
-       c = ec_node_complete(node, "x");
-       testres |= EC_TEST_CHECK(
-               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 1,
-               "complete count should is not 1\n");
-       ec_comp_free(c);
-
-       c = ec_node_complete(node, "");
-       testres |= EC_TEST_CHECK(
-               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 2,
-               "complete count should is not 2\n");
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_comp_dump(f, NULL);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "no completion"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_comp_dump(f, c);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "comp=<xx>"), "bad dump\n");
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "comp=<yy>"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       iter = ec_comp_iter(c, EC_COMP_ALL);
-       item = ec_comp_iter_next(iter);
-       if (item == NULL)
-               goto fail;
-
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_comp_item_get_display(item), "xx"),
-               "bad item display\n");
-       testres |= EC_TEST_CHECK(
-               ec_comp_item_get_type(item) == EC_COMP_FULL,
-               "bad item type\n");
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_x"),
-               "bad item node\n");
-
-       item = ec_comp_iter_next(iter);
-       if (item == NULL)
-               goto fail;
-
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_comp_item_get_display(item), "yy"),
-               "bad item display\n");
-       testres |= EC_TEST_CHECK(
-               ec_comp_item_get_type(item) == EC_COMP_FULL,
-               "bad item type\n");
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_y"),
-               "bad item node\n");
-
-       item = ec_comp_iter_next(iter);
-       testres |= EC_TEST_CHECK(item == NULL, "should be the last item\n");
-
-       ec_comp_iter_free(iter);
-       ec_comp_free(c);
-       ec_node_free(node);
-
-       return testres;
-
-fail:
-       ec_comp_iter_free(iter);
-       ec_comp_free(c);
-       ec_node_free(node);
-       if (f != NULL)
-               fclose(f);
-       free(buf);
-
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_comp_test = {
-       .name = "comp",
-       .test = ec_comp_testcase,
-};
-
-EC_TEST_REGISTER(ec_comp_test);
diff --git a/libecoli/ecoli_complete.h b/libecoli/ecoli_complete.h
deleted file mode 100644 (file)
index dee4123..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * API for generating completions item on a node.
- *
- * This file provide helpers to list and manipulate the possible
- * completions for a given input.
- *
- * XXX comp vs item
- */
-
-#ifndef ECOLI_COMPLETE_
-#define ECOLI_COMPLETE_
-
-#include <sys/queue.h>
-#include <sys/types.h>
-#include <stdio.h>
-
-struct ec_node;
-
-enum ec_comp_type { /* XXX should be a define */
-       EC_COMP_UNKNOWN = 0x1,
-       EC_COMP_FULL = 0x2,
-       EC_COMP_PARTIAL = 0x4,
-       EC_COMP_ALL = 0x7,
-};
-
-struct ec_comp_item;
-
-TAILQ_HEAD(ec_comp_item_list, ec_comp_item);
-
-struct ec_comp_group {
-       TAILQ_ENTRY(ec_comp_group) next;
-       const struct ec_node *node;
-       struct ec_comp_item_list items;
-       struct ec_parse *state;
-       struct ec_keyval *attrs;
-};
-
-TAILQ_HEAD(ec_comp_group_list, ec_comp_group);
-
-struct ec_comp {
-       unsigned count;
-       unsigned count_full;
-       unsigned count_partial;
-       unsigned count_unknown;
-       struct ec_parse *cur_state;
-       struct ec_comp_group *cur_group;
-       struct ec_comp_group_list groups;
-       struct ec_keyval *attrs;
-};
-
-/*
- * return a comp object filled with items
- * return NULL on error (nomem?)
- */
-struct ec_comp *ec_node_complete(const struct ec_node *node,
-       const char *str);
-struct ec_comp *ec_node_complete_strvec(const struct ec_node *node,
-       const struct ec_strvec *strvec);
-
-/* internal: used by nodes */
-int ec_node_complete_child(const struct ec_node *node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec);
-
-/**
- * Create a completion object (list of completion items).
- *
- *
- */
-struct ec_comp *ec_comp(struct ec_parse *state);
-
-/**
- * Free a completion object and all its items.
- *
- *
- */
-void ec_comp_free(struct ec_comp *comp);
-
-/**
- *
- *
- *
- */
-void ec_comp_dump(FILE *out,
-       const struct ec_comp *comp);
-
-/**
- * Merge items contained in 'from' into 'to'
- *
- * The 'from' comp struct is freed.
- */
-int ec_comp_merge(struct ec_comp *to,
-               struct ec_comp *from);
-
-struct ec_parse *ec_comp_get_state(struct ec_comp *comp);
-
-/* shortcut for ec_comp_item() + ec_comp_item_add() */
-int ec_comp_add_item(struct ec_comp *comp,
-                       const struct ec_node *node,
-                       struct ec_comp_item **p_item,
-                       enum ec_comp_type type,
-                       const char *start, const char *full);
-
-/**
- *
- */
-int ec_comp_item_set_str(struct ec_comp_item *item,
-                       const char *str);
-
-/**
- * Get the string value of a completion item.
- *
- *
- */
-const char *
-ec_comp_item_get_str(const struct ec_comp_item *item);
-
-/**
- * Get the display string value of a completion item.
- *
- *
- */
-const char *
-ec_comp_item_get_display(const struct ec_comp_item *item);
-
-/**
- * Get the completion string value of a completion item.
- *
- *
- */
-const char *
-ec_comp_item_get_completion(const struct ec_comp_item *item);
-
-/**
- * Get the group of a completion item.
- *
- *
- */
-const struct ec_comp_group *
-ec_comp_item_get_grp(const struct ec_comp_item *item);
-
-/**
- * Get the type of a completion item.
- *
- *
- */
-enum ec_comp_type
-ec_comp_item_get_type(const struct ec_comp_item *item);
-
-/**
- * Get the node associated to a completion item.
- *
- *
- */
-const struct ec_node *
-ec_comp_item_get_node(const struct ec_comp_item *item);
-
-/**
- * Set the display value of an item.
- *
- *
- */
-int ec_comp_item_set_display(struct ec_comp_item *item,
-                               const char *display);
-
-/**
- * Set the completion value of an item.
- *
- *
- */
-int ec_comp_item_set_completion(struct ec_comp_item *item,
-                               const char *completion);
-
-/**
- *
- *
- *
- */
-int
-ec_node_complete_unknown(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec);
-
-/**
- *
- *
- *
- */
-unsigned int ec_comp_count(
-       const struct ec_comp *comp,
-       enum ec_comp_type flags);
-
-/**
- *
- *
- *
- */
-struct ec_comp_iter {
-       enum ec_comp_type type;
-       const struct ec_comp *comp;
-       struct ec_comp_group *cur_node;
-       struct ec_comp_item *cur_match;
-};
-
-/**
- *
- *
- *
- */
-struct ec_comp_iter *
-ec_comp_iter(const struct ec_comp *comp,
-       enum ec_comp_type type);
-
-/**
- *
- *
- *
- */
-struct ec_comp_item *ec_comp_iter_next(
-       struct ec_comp_iter *iter);
-
-/**
- *
- *
- *
- */
-void ec_comp_iter_free(struct ec_comp_iter *iter);
-
-
-#endif
diff --git a/libecoli/ecoli_config.c b/libecoli/ecoli_config.c
deleted file mode 100644 (file)
index 66d9232..0000000
+++ /dev/null
@@ -1,1172 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/queue.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <errno.h>
-#include <inttypes.h>
-
-#include <ecoli_string.h>
-#include <ecoli_malloc.h>
-#include <ecoli_keyval.h>
-#include <ecoli_node.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_config.h>
-
-EC_LOG_TYPE_REGISTER(config);
-
-const char *ec_config_reserved_keys[] = {
-       "id",
-       "attrs",
-       "help",
-       "type",
-};
-
-static int
-__ec_config_dump(FILE *out, const char *key, const struct ec_config *config,
-       size_t indent);
-static int
-ec_config_dict_validate(const struct ec_keyval *dict,
-                       const struct ec_config_schema *schema);
-
-bool
-ec_config_key_is_reserved(const char *name)
-{
-       size_t i;
-
-       for (i = 0; i < EC_COUNT_OF(ec_config_reserved_keys); i++) {
-               if (!strcmp(name, ec_config_reserved_keys[i]))
-                       return true;
-       }
-       return false;
-}
-
-/* return ec_value type as a string */
-static const char *
-ec_config_type_str(enum ec_config_type type)
-{
-       switch (type) {
-       case EC_CONFIG_TYPE_BOOL: return "bool";
-       case EC_CONFIG_TYPE_INT64: return "int64";
-       case EC_CONFIG_TYPE_UINT64: return "uint64";
-       case EC_CONFIG_TYPE_STRING: return "string";
-       case EC_CONFIG_TYPE_NODE: return "node";
-       case EC_CONFIG_TYPE_LIST: return "list";
-       case EC_CONFIG_TYPE_DICT: return "dict";
-       default: return "unknown";
-       }
-}
-
-static size_t
-ec_config_schema_len(const struct ec_config_schema *schema)
-{
-       size_t i;
-
-       if (schema == NULL)
-               return 0;
-       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++)
-               ;
-       return i;
-}
-
-static int
-__ec_config_schema_validate(const struct ec_config_schema *schema,
-                       enum ec_config_type type)
-{
-       size_t i, j;
-       int ret;
-
-       if (type == EC_CONFIG_TYPE_LIST) {
-               if (schema[0].key != NULL) {
-                       errno = EINVAL;
-                       EC_LOG(EC_LOG_ERR, "list schema key must be NULL\n");
-                       return -1;
-               }
-       } else if (type == EC_CONFIG_TYPE_DICT) {
-               for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
-                       if (schema[i].key == NULL) {
-                               errno = EINVAL;
-                               EC_LOG(EC_LOG_ERR,
-                                       "dict schema key should not be NULL\n");
-                               return -1;
-                       }
-               }
-       } else {
-               errno = EINVAL;
-               EC_LOG(EC_LOG_ERR, "invalid schema type\n");
-               return -1;
-       }
-
-       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
-               if (schema[i].key != NULL &&
-                               ec_config_key_is_reserved(schema[i].key)) {
-                       errno = EINVAL;
-                       EC_LOG(EC_LOG_ERR,
-                               "key name <%s> is reserved\n", schema[i].key);
-                       return -1;
-               }
-               /* check for duplicate name if more than one element */
-               for (j = i + 1; schema[j].type != EC_CONFIG_TYPE_NONE; j++) {
-                       if (!strcmp(schema[i].key, schema[j].key)) {
-                               errno = EEXIST;
-                               EC_LOG(EC_LOG_ERR,
-                                       "duplicate key <%s> in schema\n",
-                                       schema[i].key);
-                               return -1;
-                       }
-               }
-
-               switch (schema[i].type) {
-               case EC_CONFIG_TYPE_BOOL:
-               case EC_CONFIG_TYPE_INT64:
-               case EC_CONFIG_TYPE_UINT64:
-               case EC_CONFIG_TYPE_STRING:
-               case EC_CONFIG_TYPE_NODE:
-                       if (schema[i].subschema != NULL || ec_config_schema_len(
-                                       schema[i].subschema) != 0) {
-                               errno = EINVAL;
-                               EC_LOG(EC_LOG_ERR,
-                                       "key <%s> should not have subtype/subschema\n",
-                                       schema[i].key);
-                               return -1;
-                       }
-                       break;
-               case EC_CONFIG_TYPE_LIST:
-                       if (schema[i].subschema == NULL || ec_config_schema_len(
-                                       schema[i].subschema) != 1) {
-                               errno = EINVAL;
-                               EC_LOG(EC_LOG_ERR,
-                                       "key <%s> must have subschema of length 1\n",
-                                       schema[i].key);
-                               return -1;
-                       }
-                       break;
-               case EC_CONFIG_TYPE_DICT:
-                       if (schema[i].subschema == NULL || ec_config_schema_len(
-                                       schema[i].subschema) == 0) {
-                               errno = EINVAL;
-                               EC_LOG(EC_LOG_ERR,
-                                       "key <%s> must have subschema\n",
-                                       schema[i].key);
-                               return -1;
-                       }
-                       break;
-               default:
-                       EC_LOG(EC_LOG_ERR, "invalid type for key <%s>\n",
-                               schema[i].key);
-                       errno = EINVAL;
-                       return -1;
-               }
-
-               if (schema[i].subschema == NULL)
-                       continue;
-
-               ret = __ec_config_schema_validate(schema[i].subschema,
-                                               schema[i].type);
-               if (ret < 0) {
-                       EC_LOG(EC_LOG_ERR, "cannot parse subschema %s%s\n",
-                               schema[i].key ? "key=" : "",
-                               schema[i].key ? : "");
-                       return ret;
-               }
-       }
-
-       return 0;
-}
-
-int
-ec_config_schema_validate(const struct ec_config_schema *schema)
-{
-       return __ec_config_schema_validate(schema, EC_CONFIG_TYPE_DICT);
-}
-
-static void
-__ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema,
-                       size_t indent)
-{
-       size_t i;
-
-       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
-               fprintf(out, "%*s" "%s%s%stype=%s desc='%s'\n",
-                       (int)indent * 4, "",
-                       schema[i].key ? "key=": "",
-                       schema[i].key ? : "",
-                       schema[i].key ? " ": "",
-                       ec_config_type_str(schema[i].type),
-                       schema[i].desc);
-               if (schema[i].subschema == NULL)
-                       continue;
-               __ec_config_schema_dump(out, schema[i].subschema,
-                                       indent + 1);
-       }
-}
-
-void
-ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema)
-{
-       fprintf(out, "------------------- schema dump:\n");
-
-       if (schema == NULL) {
-               fprintf(out, "no schema\n");
-               return;
-       }
-
-       __ec_config_schema_dump(out, schema, 0);
-}
-
-enum ec_config_type ec_config_get_type(const struct ec_config *config)
-{
-       return config->type;
-}
-
-struct ec_config *
-ec_config_bool(bool boolean)
-{
-       struct ec_config *value = NULL;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               return NULL;
-
-       value->type = EC_CONFIG_TYPE_BOOL;
-       value->boolean = boolean;
-
-       return value;
-}
-
-struct ec_config *
-ec_config_i64(int64_t i64)
-{
-       struct ec_config *value = NULL;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               return NULL;
-
-       value->type = EC_CONFIG_TYPE_INT64;
-       value->i64 = i64;
-
-       return value;
-}
-
-struct ec_config *
-ec_config_u64(uint64_t u64)
-{
-       struct ec_config *value = NULL;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               return NULL;
-
-       value->type = EC_CONFIG_TYPE_UINT64;
-       value->u64 = u64;
-
-       return value;
-}
-
-/* duplicate string */
-struct ec_config *
-ec_config_string(const char *string)
-{
-       struct ec_config *value = NULL;
-       char *s = NULL;
-
-       if (string == NULL)
-               goto fail;
-
-       s = ec_strdup(string);
-       if (s == NULL)
-               goto fail;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               goto fail;
-
-       value->type = EC_CONFIG_TYPE_STRING;
-       value->string = s;
-
-       return value;
-
-fail:
-       ec_free(value);
-       ec_free(s);
-       return NULL;
-}
-
-/* "consume" the node */
-struct ec_config *
-ec_config_node(struct ec_node *node)
-{
-       struct ec_config *value = NULL;
-
-       if (node == NULL)
-               goto fail;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               goto fail;
-
-       value->type = EC_CONFIG_TYPE_NODE;
-       value->node = node;
-
-       return value;
-
-fail:
-       ec_node_free(node);
-       ec_free(value);
-       return NULL;
-}
-
-struct ec_config *
-ec_config_dict(void)
-{
-       struct ec_config *value = NULL;
-       struct ec_keyval *dict = NULL;
-
-       dict = ec_keyval();
-       if (dict == NULL)
-               goto fail;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               goto fail;
-
-       value->type = EC_CONFIG_TYPE_DICT;
-       value->dict = dict;
-
-       return value;
-
-fail:
-       ec_keyval_free(dict);
-       ec_free(value);
-       return NULL;
-}
-
-struct ec_config *
-ec_config_list(void)
-{
-       struct ec_config *value = NULL;
-
-       value = ec_calloc(1, sizeof(*value));
-       if (value == NULL)
-               return NULL;
-
-       value->type = EC_CONFIG_TYPE_LIST;
-       TAILQ_INIT(&value->list);
-
-       return value;
-}
-
-ssize_t ec_config_count(const struct ec_config *config)
-{
-       const struct ec_config *child;
-       ssize_t n;
-
-       switch (config->type) {
-       case EC_CONFIG_TYPE_LIST:
-               n = 0;
-               TAILQ_FOREACH(child, &config->list, next)
-                       n++;
-               return n;
-       case EC_CONFIG_TYPE_DICT:
-               // XXX todo
-       default:
-               errno = EINVAL;
-               return -1;
-       }
-}
-
-const struct ec_config_schema *
-ec_config_schema_lookup(const struct ec_config_schema *schema,
-                       const char *key)
-{
-       size_t i;
-
-       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
-               if (!strcmp(key, schema[i].key))
-                       return &schema[i];
-       }
-
-       errno = ENOENT;
-       return NULL;
-}
-
-enum ec_config_type
-ec_config_schema_type(const struct ec_config_schema *schema_elt)
-{
-       return schema_elt->type;
-}
-
-const struct ec_config_schema *
-ec_config_schema_sub(const struct ec_config_schema *schema_elt)
-{
-       return schema_elt->subschema;
-}
-
-void
-ec_config_free(struct ec_config *value)
-{
-       if (value == NULL)
-               return;
-
-       switch (value->type) {
-       case EC_CONFIG_TYPE_STRING:
-               ec_free(value->string);
-               break;
-       case EC_CONFIG_TYPE_NODE:
-               ec_node_free(value->node);
-               break;
-       case EC_CONFIG_TYPE_LIST:
-               while (!TAILQ_EMPTY(&value->list)) {
-                       struct ec_config *v;
-                       v = TAILQ_FIRST(&value->list);
-                       TAILQ_REMOVE(&value->list, v, next);
-                       ec_config_free(v);
-               }
-               break;
-       case EC_CONFIG_TYPE_DICT:
-               ec_keyval_free(value->dict);
-               break;
-       default:
-               break;
-       }
-
-       ec_free(value);
-}
-
-static int
-ec_config_list_cmp(const struct ec_config_list *list1,
-               const struct ec_config_list *list2)
-{
-       const struct ec_config *v1, *v2;
-
-       for (v1 = TAILQ_FIRST(list1), v2 = TAILQ_FIRST(list2);
-            v1 != NULL && v2 != NULL;
-            v1 = TAILQ_NEXT(v1, next), v2 = TAILQ_NEXT(v2, next)) {
-               if (ec_config_cmp(v1, v2))
-                       return -1;
-       }
-       if (v1 != NULL || v2 != NULL)
-               return -1;
-
-       return 0;
-}
-
-/* XXX -> ec_keyval_cmp() */
-static int
-ec_config_dict_cmp(const struct ec_keyval *d1,
-               const struct ec_keyval *d2)
-{
-       const struct ec_config *v1, *v2;
-       struct ec_keyval_iter *iter = NULL;
-       const char *key;
-
-       if (ec_keyval_len(d1) != ec_keyval_len(d2))
-               return -1;
-
-       for (iter = ec_keyval_iter(d1);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               key = ec_keyval_iter_get_key(iter);
-               v1 = ec_keyval_iter_get_val(iter);
-               v2 = ec_keyval_get(d2, key);
-
-               if (ec_config_cmp(v1, v2))
-                       goto fail;
-       }
-
-       ec_keyval_iter_free(iter);
-       return 0;
-
-fail:
-       ec_keyval_iter_free(iter);
-       return -1;
-}
-
-int
-ec_config_cmp(const struct ec_config *value1,
-               const struct ec_config *value2)
-{
-       if (value1 == NULL || value2 == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (value1->type != value2->type)
-               return -1;
-
-       switch (value1->type) {
-       case EC_CONFIG_TYPE_BOOL:
-               if (value1->boolean == value2->boolean)
-                       return 0;
-       case EC_CONFIG_TYPE_INT64:
-               if (value1->i64 == value2->i64)
-                       return 0;
-       case EC_CONFIG_TYPE_UINT64:
-               if (value1->u64 == value2->u64)
-                       return 0;
-       case EC_CONFIG_TYPE_STRING:
-               if (!strcmp(value1->string, value2->string))
-                       return 0;
-       case EC_CONFIG_TYPE_NODE:
-               if (value1->node == value2->node)
-                       return 0;
-       case EC_CONFIG_TYPE_LIST:
-               return ec_config_list_cmp(&value1->list, &value2->list);
-       case EC_CONFIG_TYPE_DICT:
-               return ec_config_dict_cmp(value1->dict, value2->dict);
-       default:
-               break;
-       }
-
-       return -1;
-}
-
-static int
-ec_config_list_validate(const struct ec_config_list *list,
-                       const struct ec_config_schema *sch)
-{
-       const struct ec_config *value;
-
-       TAILQ_FOREACH(value, list, next) {
-               if (value->type != sch->type) {
-                       errno = EBADMSG;
-                       return -1;
-               }
-
-               if (value->type == EC_CONFIG_TYPE_LIST) {
-                       if (ec_config_list_validate(&value->list,
-                                                       sch->subschema) < 0)
-                               return -1;
-               } else if (value->type == EC_CONFIG_TYPE_DICT) {
-                       if (ec_config_dict_validate(value->dict,
-                                       sch->subschema) < 0)
-                               return -1;
-               }
-       }
-
-       return 0;
-}
-
-static int
-ec_config_dict_validate(const struct ec_keyval *dict,
-                       const struct ec_config_schema *schema)
-{
-       const struct ec_config *value;
-       struct ec_keyval_iter *iter = NULL;
-       const struct ec_config_schema *sch;
-       const char *key;
-
-       for (iter = ec_keyval_iter(dict);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-
-               key = ec_keyval_iter_get_key(iter);
-               value = ec_keyval_iter_get_val(iter);
-               sch = ec_config_schema_lookup(schema, key);
-               if (sch == NULL || sch->type != value->type) {
-                       errno = EBADMSG;
-                       goto fail;
-               }
-               if (value->type == EC_CONFIG_TYPE_LIST) {
-                       if (ec_config_list_validate(&value->list,
-                                                       sch->subschema) < 0)
-                               goto fail;
-               } else if (value->type == EC_CONFIG_TYPE_DICT) {
-                       if (ec_config_dict_validate(value->dict,
-                                       sch->subschema) < 0)
-                               goto fail;
-               }
-       }
-
-       ec_keyval_iter_free(iter);
-       return 0;
-
-fail:
-       ec_keyval_iter_free(iter);
-       return -1;
-}
-
-int
-ec_config_validate(const struct ec_config *dict,
-               const struct ec_config_schema *schema)
-{
-       if (dict->type != EC_CONFIG_TYPE_DICT || schema == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (ec_config_dict_validate(dict->dict, schema) < 0)
-               goto fail;
-
-       return 0
-;
-fail:
-       return -1;
-}
-
-struct ec_config *
-ec_config_dict_get(const struct ec_config *config, const char *key)
-{
-       if (config == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       if (config->type != EC_CONFIG_TYPE_DICT) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       return ec_keyval_get(config->dict, key);
-}
-
-struct ec_config *
-ec_config_list_first(struct ec_config *list)
-{
-       if (list  == NULL || list->type != EC_CONFIG_TYPE_LIST) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       return TAILQ_FIRST(&list->list);
-}
-
-struct ec_config *
-ec_config_list_next(struct ec_config *list, struct ec_config *config)
-{
-       (void)list;
-       return TAILQ_NEXT(config, next);
-}
-
-/* value is consumed */
-int ec_config_dict_set(struct ec_config *config, const char *key,
-               struct ec_config *value)
-{
-       void (*free_cb)(struct ec_config *) = ec_config_free;
-
-       if (config == NULL || key == NULL || value == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-       if (config->type != EC_CONFIG_TYPE_DICT) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       return ec_keyval_set(config->dict, key, value,
-                       (void (*)(void *))free_cb);
-
-fail:
-       ec_config_free(value);
-       return -1;
-}
-
-int ec_config_dict_del(struct ec_config *config, const char *key)
-{
-       if (config == NULL || key == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-       if (config->type != EC_CONFIG_TYPE_DICT) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       return ec_keyval_del(config->dict, key);
-}
-
-/* value is consumed */
-int
-ec_config_list_add(struct ec_config *list,
-               struct ec_config *value)
-{
-       if (list == NULL || list->type != EC_CONFIG_TYPE_LIST || value == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       TAILQ_INSERT_TAIL(&list->list, value, next);
-
-       return 0;
-
-fail:
-       ec_config_free(value);
-       return -1;
-}
-
-int ec_config_list_del(struct ec_config *list, struct ec_config *config)
-{
-       if (list == NULL || list->type != EC_CONFIG_TYPE_LIST) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       TAILQ_REMOVE(&list->list, config, next);
-       ec_config_free(config);
-       return 0;
-}
-
-static struct ec_config *
-ec_config_list_dup(const struct ec_config_list *list)
-{
-       struct ec_config *dup = NULL, *v, *value;
-
-       dup = ec_config_list();
-       if (dup == NULL)
-               goto fail;
-
-       TAILQ_FOREACH(v, list, next) {
-               value = ec_config_dup(v);
-               if (value == NULL)
-                       goto fail;
-               if (ec_config_list_add(dup, value) < 0)
-                       goto fail;
-       }
-
-       return dup;
-
-fail:
-       ec_config_free(dup);
-       return NULL;
-}
-
-static struct ec_config *
-ec_config_dict_dup(const struct ec_keyval *dict)
-{
-       struct ec_config *dup = NULL, *value;
-       struct ec_keyval_iter *iter = NULL;
-       const char *key;
-
-       dup = ec_config_dict();
-       if (dup == NULL)
-               goto fail;
-
-       for (iter = ec_keyval_iter(dict);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               key = ec_keyval_iter_get_key(iter);
-               value = ec_config_dup(ec_keyval_iter_get_val(iter));
-               if (value == NULL)
-                       goto fail;
-               if (ec_config_dict_set(dup, key, value) < 0)
-                       goto fail;
-       }
-       ec_keyval_iter_free(iter);
-
-       return dup;
-
-fail:
-       ec_config_free(dup);
-       ec_keyval_iter_free(iter);
-       return NULL;
-}
-
-struct ec_config *
-ec_config_dup(const struct ec_config *config)
-{
-       if (config == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       switch (config->type) {
-       case EC_CONFIG_TYPE_BOOL:
-               return ec_config_bool(config->boolean);
-       case EC_CONFIG_TYPE_INT64:
-               return ec_config_i64(config->i64);
-       case EC_CONFIG_TYPE_UINT64:
-               return ec_config_u64(config->u64);
-       case EC_CONFIG_TYPE_STRING:
-               return ec_config_string(config->string);
-       case EC_CONFIG_TYPE_NODE:
-               return ec_config_node(ec_node_clone(config->node));
-       case EC_CONFIG_TYPE_LIST:
-               return ec_config_list_dup(&config->list);
-       case EC_CONFIG_TYPE_DICT:
-               return ec_config_dict_dup(config->dict);
-       default:
-               errno = EINVAL;
-               break;
-       }
-
-       return NULL;
-}
-
-static int
-ec_config_list_dump(FILE *out, const char *key,
-               const struct ec_config_list *list, size_t indent)
-{
-       const struct ec_config *v;
-
-       fprintf(out, "%*s" "%s%s%stype=list\n", (int)indent * 4, "",
-               key ? "key=": "",
-               key ? key: "",
-               key ? " ": "");
-
-       TAILQ_FOREACH(v, list, next) {
-               if (__ec_config_dump(out, NULL, v, indent + 1) < 0)
-                       return -1;
-       }
-
-       return 0;
-}
-
-static int
-ec_config_dict_dump(FILE *out, const char *key, const struct ec_keyval *dict,
-               size_t indent)
-{
-       const struct ec_config *value;
-       struct ec_keyval_iter *iter;
-       const char *k;
-
-       fprintf(out, "%*s" "%s%s%stype=dict\n", (int)indent * 4, "",
-               key ? "key=": "",
-               key ? key: "",
-               key ? " ": "");
-
-       for (iter = ec_keyval_iter(dict);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               k = ec_keyval_iter_get_key(iter);
-               value = ec_keyval_iter_get_val(iter);
-               if (__ec_config_dump(out, k, value, indent + 1) < 0)
-                       goto fail;
-       }
-       ec_keyval_iter_free(iter);
-       return 0;
-
-fail:
-       ec_keyval_iter_free(iter);
-       return -1;
-}
-
-static int
-__ec_config_dump(FILE *out, const char *key, const struct ec_config *value,
-               size_t indent)
-{
-       char *val_str = NULL;
-
-       switch (value->type) {
-       case EC_CONFIG_TYPE_BOOL:
-               if (value->boolean)
-                       ec_asprintf(&val_str, "true");
-               else
-                       ec_asprintf(&val_str, "false");
-               break;
-       case EC_CONFIG_TYPE_INT64:
-               ec_asprintf(&val_str, "%"PRIu64, value->u64);
-               break;
-       case EC_CONFIG_TYPE_UINT64:
-               ec_asprintf(&val_str, "%"PRIi64, value->i64);
-               break;
-       case EC_CONFIG_TYPE_STRING:
-               ec_asprintf(&val_str, "%s", value->string);
-               break;
-       case EC_CONFIG_TYPE_NODE:
-               ec_asprintf(&val_str, "%p", value->node);
-               break;
-       case EC_CONFIG_TYPE_LIST:
-               return ec_config_list_dump(out, key, &value->list, indent);
-       case EC_CONFIG_TYPE_DICT:
-               return ec_config_dict_dump(out, key, value->dict, indent);
-       default:
-               errno = EINVAL;
-               break;
-       }
-
-       /* errno is already set on error */
-       if (val_str == NULL)
-               goto fail;
-
-       fprintf(out, "%*s" "%s%s%stype=%s val=%s\n", (int)indent * 4, "",
-               key ? "key=": "",
-               key ? key: "",
-               key ? " ": "",
-               ec_config_type_str(value->type), val_str);
-
-       ec_free(val_str);
-       return 0;
-
-fail:
-       ec_free(val_str);
-       return -1;
-}
-
-void
-ec_config_dump(FILE *out, const struct ec_config *config)
-{
-       fprintf(out, "------------------- config dump:\n");
-
-       if (config == NULL) {
-               fprintf(out, "no config\n");
-               return;
-       }
-
-       if (__ec_config_dump(out, NULL, config, 0) < 0)
-               fprintf(out, "error while dumping\n");
-}
-
-/* LCOV_EXCL_START */
-static const struct ec_config_schema sch_intlist_elt[] = {
-       {
-               .desc = "This is a description for int",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema sch_dict[] = {
-       {
-               .key = "my_int",
-               .desc = "This is a description for int",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .key = "my_int2",
-               .desc = "This is a description for int2",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema sch_dictlist_elt[] = {
-       {
-               .desc = "This is a description for dict",
-               .type = EC_CONFIG_TYPE_DICT,
-               .subschema = sch_dict,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema sch_baseconfig[] = {
-       {
-               .key = "my_bool",
-               .desc = "This is a description for bool",
-               .type = EC_CONFIG_TYPE_BOOL,
-       },
-       {
-               .key = "my_int",
-               .desc = "This is a description for int",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .key = "my_string",
-               .desc = "This is a description for string",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .key = "my_node",
-               .desc = "This is a description for node",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .key = "my_intlist",
-               .desc = "This is a description for list",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = sch_intlist_elt,
-       },
-       {
-               .key = "my_dictlist",
-               .desc = "This is a description for list",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = sch_dictlist_elt,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_config_testcase(void)
-{
-       struct ec_node *node = NULL;
-       struct ec_keyval *dict = NULL;
-       const struct ec_config *value = NULL;
-       struct ec_config *config = NULL, *config2 = NULL;
-       struct ec_config *list = NULL, *subconfig = NULL;
-       struct ec_config *list_, *config_;
-       int testres = 0;
-       int ret;
-
-       testres |= EC_TEST_CHECK(ec_config_key_is_reserved("id"),
-               "'id' should be reserved");
-       testres |= EC_TEST_CHECK(!ec_config_key_is_reserved("foo"),
-               "'foo' should not be reserved");
-
-       node = ec_node("empty", EC_NO_ID);
-       if (node == NULL)
-               goto fail;
-
-       if (ec_config_schema_validate(sch_baseconfig) < 0) {
-               EC_LOG(EC_LOG_ERR, "invalid config schema\n");
-               goto fail;
-       }
-
-       ec_config_schema_dump(stdout, sch_baseconfig);
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(config, "my_bool", ec_config_bool(true));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set boolean");
-       value = ec_config_dict_get(config, "my_bool");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_BOOL &&
-               value->boolean == true,
-               "unexpected boolean value");
-
-       ret = ec_config_dict_set(config, "my_int", ec_config_i64(1234));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(config, "my_int");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 1234,
-               "unexpected int value");
-
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(config, sch_baseconfig) == 0,
-               "cannot validate config\n");
-
-       ret = ec_config_dict_set(config, "my_string", ec_config_string("toto"));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set string");
-       value = ec_config_dict_get(config, "my_string");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_STRING &&
-               !strcmp(value->string, "toto"),
-               "unexpected string value");
-
-       list = ec_config_list();
-       if (list == NULL)
-               goto fail;
-
-       subconfig = ec_config_dict();
-       if (subconfig == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(1));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(subconfig, "my_int");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 1,
-               "unexpected int value");
-
-       ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(2));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(subconfig, "my_int2");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 2,
-               "unexpected int value");
-
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(subconfig, sch_dict) == 0,
-               "cannot validate subconfig\n");
-
-       ret = ec_config_list_add(list, subconfig);
-       subconfig = NULL; /* freed */
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add in list");
-
-       subconfig = ec_config_dict();
-       if (subconfig == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(3));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(subconfig, "my_int");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 3,
-               "unexpected int value");
-
-       ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(4));
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
-       value = ec_config_dict_get(subconfig, "my_int2");
-       testres |= EC_TEST_CHECK(
-               value != NULL &&
-               value->type == EC_CONFIG_TYPE_INT64 &&
-               value->i64 == 4,
-               "unexpected int value");
-
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(subconfig, sch_dict) == 0,
-               "cannot validate subconfig\n");
-
-       ret = ec_config_list_add(list, subconfig);
-       subconfig = NULL; /* freed */
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add in list");
-
-       ret = ec_config_dict_set(config, "my_dictlist", list);
-       list = NULL;
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set list");
-
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(config, sch_baseconfig) == 0,
-               "cannot validate config\n");
-
-       list_ = ec_config_dict_get(config, "my_dictlist");
-       for (config_ = ec_config_list_first(list_); config_ != NULL;
-            config_ = ec_config_list_next(list_, config_)) {
-               ec_config_dump(stdout, config_);
-       }
-
-       ec_config_dump(stdout, config);
-
-       config2 = ec_config_dup(config);
-       testres |= EC_TEST_CHECK(config2 != NULL, "cannot duplicate config");
-       testres |= EC_TEST_CHECK(
-               ec_config_cmp(config, config2) == 0,
-               "fail to compare config");
-       ec_config_free(config2);
-       config2 = NULL;
-
-       /* remove the first element */
-       ec_config_list_del(list_, ec_config_list_first(list_));
-       testres |= EC_TEST_CHECK(
-               ec_config_validate(config, sch_baseconfig) == 0,
-               "cannot validate config\n");
-
-       ec_config_dump(stdout, config);
-
-       ec_config_free(list);
-       ec_config_free(subconfig);
-       ec_config_free(config);
-       ec_keyval_free(dict);
-       ec_node_free(node);
-
-       return testres;
-
-fail:
-       ec_config_free(list);
-       ec_config_free(subconfig);
-       ec_config_free(config);
-       ec_config_free(config2);
-       ec_keyval_free(dict);
-       ec_node_free(node);
-
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_config_test = {
-       .name = "config",
-       .test = ec_config_testcase,
-};
-
-EC_TEST_REGISTER(ec_config_test);
diff --git a/libecoli/ecoli_config.h b/libecoli/ecoli_config.h
deleted file mode 100644 (file)
index 7aa427f..0000000
+++ /dev/null
@@ -1,404 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_CONFIG_
-#define ECOLI_CONFIG_
-
-#include <sys/queue.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-
-#ifndef EC_COUNT_OF //XXX
-#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
-               ((size_t)(!(sizeof(x) % sizeof(0[x])))))
-#endif
-
-struct ec_config;
-struct ec_keyval;
-
-/**
- * The type identifier for a config value.
- */
-enum ec_config_type {
-       EC_CONFIG_TYPE_NONE = 0,
-       EC_CONFIG_TYPE_BOOL,
-       EC_CONFIG_TYPE_INT64,
-       EC_CONFIG_TYPE_UINT64,
-       EC_CONFIG_TYPE_STRING,
-       EC_CONFIG_TYPE_NODE,
-       EC_CONFIG_TYPE_LIST,
-       EC_CONFIG_TYPE_DICT,
-};
-
-/**
- * Structure describing the format of a configuration value.
- *
- * This structure is used in a const array which is referenced by a
- * struct ec_config. Each entry of the array represents a key/value
- * storage of the configuration dictionary.
- */
-struct ec_config_schema {
-       const char *key;          /**< The key string (NULL for list elts). */
-       const char *desc;         /**< A description of the value. */
-       enum ec_config_type type; /**< Type of the value */
-       /* XXX flags: mandatory */
-       /* XXX default */
-
-       /** If type is dict or list, the schema of the dict or list
-        * elements. Else must be NULL. */
-       const struct ec_config_schema *subschema;
-};
-
-TAILQ_HEAD(ec_config_list, ec_config);
-
-/**
- * Structure storing the configuration data.
- */
-struct ec_config {
-       /** type of value stored in the union */
-       enum ec_config_type type;
-
-       union {
-               bool boolean;   /** Boolean value */
-               int64_t i64;    /** Signed integer value */
-               uint64_t u64;   /** Unsigned integer value */
-               char *string;   /** String value */
-               struct ec_node *node;       /** Node value */
-               struct ec_keyval *dict;     /** Hash table value */
-               struct ec_config_list list; /** List value */
-       };
-
-       /**
-        * Next in list, only valid if type is list.
-        */
-       TAILQ_ENTRY(ec_config) next;
-};
-
-/* schema */
-
-/**
- * Validate a configuration schema array.
- *
- * @param schema
- *   Pointer to the first element of the schema array. The array
- *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
- * @return
- *   0 if the schema is valid, or -1 on error (errno is set).
- */
-int ec_config_schema_validate(const struct ec_config_schema *schema);
-
-/**
- * Dump a configuration schema array.
- *
- * @param out
- *   Output stream on which the dump will be sent.
- * @param schema
- *   Pointer to the first element of the schema array. The array
- *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
- */
-void ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema);
-
-/**
- * Find a schema entry matching the key.
- *
- * Browse the schema array and lookup for the given key.
- *
- * @param schema
- *   Pointer to the first element of the schema array. The array
- *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
- * @return
- *   The schema entry if it matches a key, or NULL if not found.
- */
-const struct ec_config_schema *
-ec_config_schema_lookup(const struct ec_config_schema *schema,
-                       const char *key);
-
-/**
- * Get the type of a schema entry.
- *
- * @param schema_elt
- *   Pointer to an element of the schema array.
- * @return
- *   The type of the schema entry.
- */
-enum ec_config_type
-ec_config_schema_type(const struct ec_config_schema *schema_elt);
-
-/**
- * Get the subschema of a schema entry.
- *
- * @param schema_elt
- *   Pointer to an element of the schema array.
- * @return
- *   The subschema if any, or NULL.
- */
-const struct ec_config_schema *
-ec_config_schema_sub(const struct ec_config_schema *schema_elt);
-
-/**
- * Check if a key name is reserved in a config dict.
- *
- * Some key names are reserved and should not be used in configs.
- *
- * @param name
- *   The name of the key to test.
- * @return
- *   True if the key name is reserved and must not be used, else false.
- */
-bool ec_config_key_is_reserved(const char *name);
-
-/**
- * Array of reserved key names.
- */
-extern const char *ec_config_reserved_keys[];
-
-
-/* config */
-
-/**
- * Get the type of the configuration.
- *
- * @param config
- *   The configuration.
- * @return
- *   The configuration type.
- */
-enum ec_config_type ec_config_get_type(const struct ec_config *config);
-
-/**
- * Create a boolean configuration value.
- *
- * @param boolean
- *   The boolean value to be set.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_bool(bool boolean);
-
-/**
- * Create a signed integer configuration value.
- *
- * @param i64
- *   The signed integer value to be set.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_i64(int64_t i64);
-
-/**
- * Create an unsigned configuration value.
- *
- * @param u64
- *   The unsigned integer value to be set.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_u64(uint64_t u64);
-
-/**
- * Create a string configuration value.
- *
- * @param string
- *   The string value to be set. The string is copied into the
- *   configuration object.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_string(const char *string);
-
-/**
- * Create a node configuration value.
- *
- * @param node
- *   The node pointer to be set. The node is "consumed" by
- *   the function and should not be used by the caller, even
- *   on error. The caller can use ec_node_clone() to keep a
- *   reference on the node.
- * @return
- *   The configuration object, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_node(struct ec_node *node);
-
-/**
- * Create a hash table configuration value.
- *
- * @return
- *   A configuration object containing an empty hash table, or NULL on
- *   error (errno is set).
- */
-struct ec_config *ec_config_dict(void);
-
-/**
- * Create a list configuration value.
- *
- * @return
- *   The configuration object containing an empty list, or NULL on
- *   error (errno is set).
- */
-struct ec_config *ec_config_list(void);
-
-/**
- * Add a config object into a list.
- *
- * @param list
- *   The list configuration in which the value will be added.
- * @param value
- *   The value configuration to add in the list. The value object
- *   will be freed when freeing the list object. On error, the
- *   value object is also freed.
- * @return
- *   0 on success, else -1 (errno is set).
- */
-int ec_config_list_add(struct ec_config *list, struct ec_config *value);
-
-/**
- * Remove an element from a list.
- *
- * The element is freed and should not be accessed.
- *
- * @param list
- *   The list configuration.
- * @param config
- *   The element to remove from the list.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_config_list_del(struct ec_config *list, struct ec_config *config);
-
-/**
- * Count the number of elements in a list or dict.
- *
- * @param config
- *   The configuration that must be a list or a dict.
- * @return
- *   The number of elements, or -1 on error (errno is set).
- */
-ssize_t ec_config_count(const struct ec_config *config);
-
-/**
- * Validate a configuration.
- *
- * @param dict
- *   A hash table configuration to validate.
- * @param schema
- *   Pointer to the first element of the schema array. The array
- *   must be terminated by a sentinel entry (type == EC_CONFIG_TYPE_NONE).
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_config_validate(const struct ec_config *dict,
-               const struct ec_config_schema *schema);
-
-/**
- * Set a value in a hash table configuration
- *
- * @param dict
- *   A hash table configuration to validate.
- * @param key
- *   The key to update.
- * @param value
- *   The value to set. The value object will be freed when freeing the
- *   dict object. On error, the value object is also freed.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_config_dict_set(struct ec_config *dict, const char *key,
-               struct ec_config *value);
-
-/**
- * Remove an element from a hash table configuration.
- *
- * The element is freed and should not be accessed.
- *
- * @param dict
- *   A hash table configuration to validate.
- * @param key
- *   The key of the configuration to delete.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_config_dict_del(struct ec_config *config, const char *key);
-
-/**
- * Compare two configurations.
- */
-int ec_config_cmp(const struct ec_config *config1,
-               const struct ec_config *config2);
-
-/**
- * Get configuration value.
- */
-struct ec_config *ec_config_dict_get(const struct ec_config *config,
-                               const char *key);
-
-/**
- * Get the first element of a list.
- *
- * Example of use:
- * for (config = ec_config_list_iter(list);
- *     config != NULL;
- *     config = ec_config_list_next(list, config)) {
- *             ...
- * }
- *
- * @param list
- *   The list configuration to iterate.
- * @return
- *   The first configuration element, or NULL on error (errno is set).
- */
-struct ec_config *ec_config_list_first(struct ec_config *list);
-
-/**
- * Get next element in list.
- *
- * @param list
- *   The list configuration beeing iterated.
- * @param config
- *   The current configuration element.
- * @return
- *   The next configuration element, or NULL if there is no more element.
- */
-struct ec_config *
-ec_config_list_next(struct ec_config *list, struct ec_config *config);
-
-/**
- * Free a configuration.
- *
- * @param config
- *   The element to free.
- */
-void ec_config_free(struct ec_config *config);
-
-/**
- * Compare two configurations.
- *
- * @return
- *   0 if the configurations are equal, else -1.
- */
-int ec_config_cmp(const struct ec_config *value1,
-               const struct ec_config *value2);
-
-/**
- * Duplicate a configuration.
- *
- * @param config
- *   The configuration to duplicate.
- * @return
- *   The duplicated configuration, or NULL on error (errno is set).
- */
-struct ec_config *
-ec_config_dup(const struct ec_config *config);
-
-/**
- * Dump a configuration.
- *
- * @param out
- *   Output stream on which the dump will be sent.
- * @param config
- *   The configuration to dump.
- */
-void ec_config_dump(FILE *out, const struct ec_config *config);
-
-#endif
diff --git a/libecoli/ecoli_init.c b/libecoli/ecoli_init.c
deleted file mode 100644 (file)
index fd5c0c3..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-#include <ecoli_init.h>
-
-static struct ec_init_list init_list = TAILQ_HEAD_INITIALIZER(init_list);
-
-/* register an init function */
-void ec_init_register(struct ec_init *init)
-{
-       struct ec_init *cur;
-
-       if (TAILQ_EMPTY(&init_list)) {
-               TAILQ_INSERT_HEAD(&init_list, init, next);
-               return;
-       }
-
-
-       TAILQ_FOREACH(cur, &init_list, next) {
-               if (init->priority > cur->priority)
-                       continue;
-
-               TAILQ_INSERT_BEFORE(cur, init, next);
-               return;
-       }
-
-       TAILQ_INSERT_TAIL(&init_list, init, next);
-}
-
-int ec_init(void)
-{
-       struct ec_init *init;
-
-       TAILQ_FOREACH(init, &init_list, next) {
-               if (init->init() < 0)
-                       return -1;
-       }
-
-       return 0;
-}
diff --git a/libecoli/ecoli_init.h b/libecoli/ecoli_init.h
deleted file mode 100644 (file)
index 4e8bc1e..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Register initialization routines.
- */
-
-#ifndef ECOLI_INIT_
-#define ECOLI_INIT_
-
-#include <sys/queue.h>
-
-#include <ecoli_log.h>
-#include <ecoli_node.h>
-
-#define EC_INIT_REGISTER(t)                                            \
-       static void ec_init_init_##t(void);                             \
-       static void __attribute__((constructor, used))                  \
-       ec_init_init_##t(void)                                          \
-       {                                                               \
-                ec_init_register(&t);                                  \
-       }
-
-/**
- * Type of init function. Return 0 on success, -1 on error.
- */
-typedef int (ec_init_t)(void);
-
-TAILQ_HEAD(ec_init_list, ec_init);
-
-/**
- * A structure describing a test case.
- */
-struct ec_init {
-       TAILQ_ENTRY(ec_init) next;  /**< Next in list. */
-       ec_init_t *init;            /**< Init function. */
-       unsigned int priority;      /**< Priority (0=first, 99=last) */
-};
-
-/**
- * Register an initialization function.
- *
- * @param init
- *   A pointer to a ec_init structure to be registered.
- */
-void ec_init_register(struct ec_init *test);
-
-/**
- * Initialize ecoli library
- *
- * Must be called before any other function from libecoli, except
- * ec_malloc_register().
- *
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_init(void);
-
-#endif
diff --git a/libecoli/ecoli_keyval.c b/libecoli/ecoli_keyval.c
deleted file mode 100644 (file)
index 12fe66b..0000000
+++ /dev/null
@@ -1,569 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/queue.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <unistd.h>
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-
-#include <ecoli_init.h>
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_murmurhash.h>
-#include <ecoli_keyval.h>
-
-#define FACTOR 3
-
-EC_LOG_TYPE_REGISTER(keyval);
-
-static uint32_t ec_keyval_seed;
-
-struct ec_keyval_elt {
-       char *key;
-       void *val;
-       uint32_t hash;
-       ec_keyval_elt_free_t free;
-       unsigned int refcount;
-};
-
-struct ec_keyval_elt_ref {
-       LIST_ENTRY(ec_keyval_elt_ref) next;
-       struct ec_keyval_elt *elt;
-};
-
-LIST_HEAD(ec_keyval_elt_ref_list, ec_keyval_elt_ref);
-
-struct ec_keyval {
-       size_t len;
-       size_t table_size;
-       struct ec_keyval_elt_ref_list *table;
-};
-
-struct ec_keyval_iter {
-       const struct ec_keyval *keyval;
-       size_t cur_idx;
-       const struct ec_keyval_elt_ref *cur_ref;
-};
-
-struct ec_keyval *ec_keyval(void)
-{
-       struct ec_keyval *keyval;
-
-       keyval = ec_calloc(1, sizeof(*keyval));
-       if (keyval == NULL)
-               return NULL;
-
-       return keyval;
-}
-
-static struct ec_keyval_elt_ref *
-ec_keyval_lookup(const struct ec_keyval *keyval, const char *key)
-{
-       struct ec_keyval_elt_ref *ref;
-       uint32_t h, mask = keyval->table_size - 1;
-
-       if (keyval == NULL || key == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-       if (keyval->table_size == 0) {
-               errno = ENOENT;
-               return NULL;
-       }
-
-       h = ec_murmurhash3(key, strlen(key), ec_keyval_seed);
-       LIST_FOREACH(ref, &keyval->table[h & mask], next) {
-               if (strcmp(ref->elt->key, key) == 0)
-                       return ref;
-       }
-
-       errno = ENOENT;
-       return NULL;
-}
-
-static void ec_keyval_elt_ref_free(struct ec_keyval_elt_ref *ref)
-{
-       struct ec_keyval_elt *elt;
-
-       if (ref == NULL)
-               return;
-
-       elt = ref->elt;
-       if (elt != NULL && --elt->refcount == 0) {
-               ec_free(elt->key);
-               if (elt->free != NULL)
-                       elt->free(elt->val);
-               ec_free(elt);
-       }
-       ec_free(ref);
-}
-
-bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key)
-{
-       return !!ec_keyval_lookup(keyval, key);
-}
-
-void *ec_keyval_get(const struct ec_keyval *keyval, const char *key)
-{
-       struct ec_keyval_elt_ref *ref;
-
-       ref = ec_keyval_lookup(keyval, key);
-       if (ref == NULL)
-               return NULL;
-
-       return ref->elt->val;
-}
-
-int ec_keyval_del(struct ec_keyval *keyval, const char *key)
-{
-       struct ec_keyval_elt_ref *ref;
-
-       ref = ec_keyval_lookup(keyval, key);
-       if (ref == NULL)
-               return -1;
-
-       /* we could resize table here */
-
-       LIST_REMOVE(ref, next);
-       ec_keyval_elt_ref_free(ref);
-       keyval->len--;
-
-       return 0;
-}
-
-static int ec_keyval_table_resize(struct ec_keyval *keyval, size_t new_size)
-{
-       struct ec_keyval_elt_ref_list *new_table;
-       struct ec_keyval_elt_ref *ref;
-       size_t i;
-
-       if (new_size == 0 || (new_size & (new_size - 1))) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       new_table = ec_calloc(new_size, sizeof(*keyval->table));
-       if (new_table == NULL)
-               return -1;
-
-       for (i = 0; i < keyval->table_size; i++) {
-               while (!LIST_EMPTY(&keyval->table[i])) {
-                       ref = LIST_FIRST(&keyval->table[i]);
-                       LIST_REMOVE(ref, next);
-                       LIST_INSERT_HEAD(
-                               &new_table[ref->elt->hash & (new_size - 1)],
-                               ref, next);
-               }
-       }
-
-       ec_free(keyval->table);
-       keyval->table = new_table;
-       keyval->table_size = new_size;
-
-       return 0;
-}
-
-static int
-__ec_keyval_set(struct ec_keyval *keyval, struct ec_keyval_elt_ref *ref)
-{
-       size_t new_size;
-       uint32_t mask;
-       int ret;
-
-       /* remove previous entry if any */
-       ec_keyval_del(keyval, ref->elt->key);
-
-       if (keyval->len >= keyval->table_size) {
-               if (keyval->table_size != 0)
-                       new_size =  keyval->table_size << FACTOR;
-               else
-                       new_size =  1 << FACTOR;
-               ret = ec_keyval_table_resize(keyval, new_size);
-               if (ret < 0)
-                       return ret;
-       }
-
-       mask = keyval->table_size - 1;
-       LIST_INSERT_HEAD(&keyval->table[ref->elt->hash & mask], ref, next);
-       keyval->len++;
-
-       return 0;
-}
-
-int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val,
-       ec_keyval_elt_free_t free_cb)
-{
-       struct ec_keyval_elt *elt = NULL;
-       struct ec_keyval_elt_ref *ref = NULL;
-       uint32_t h;
-
-       if (keyval == NULL || key == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       ref = ec_calloc(1, sizeof(*ref));
-       if (ref == NULL)
-               goto fail;
-
-       elt = ec_calloc(1, sizeof(*elt));
-       if (elt == NULL)
-               goto fail;
-
-       ref->elt = elt;
-       elt->refcount = 1;
-       elt->val = val;
-       val = NULL;
-       elt->free = free_cb;
-       elt->key = ec_strdup(key);
-       if (elt->key == NULL)
-               goto fail;
-       h = ec_murmurhash3(key, strlen(key), ec_keyval_seed);
-       elt->hash = h;
-
-       if (__ec_keyval_set(keyval, ref) < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       if (free_cb != NULL && val != NULL)
-               free_cb(val);
-       ec_keyval_elt_ref_free(ref);
-       return -1;
-}
-
-void ec_keyval_free(struct ec_keyval *keyval)
-{
-       struct ec_keyval_elt_ref *ref;
-       size_t i;
-
-       if (keyval == NULL)
-               return;
-
-       for (i = 0; i < keyval->table_size; i++) {
-               while (!LIST_EMPTY(&keyval->table[i])) {
-                       ref = LIST_FIRST(&keyval->table[i]);
-                       LIST_REMOVE(ref, next);
-                       ec_keyval_elt_ref_free(ref);
-               }
-       }
-       ec_free(keyval->table);
-       ec_free(keyval);
-}
-
-size_t ec_keyval_len(const struct ec_keyval *keyval)
-{
-       return keyval->len;
-}
-
-void
-ec_keyval_iter_free(struct ec_keyval_iter *iter)
-{
-       ec_free(iter);
-}
-
-struct ec_keyval_iter *
-ec_keyval_iter(const struct ec_keyval *keyval)
-{
-       struct ec_keyval_iter *iter = NULL;
-
-       iter = ec_calloc(1, sizeof(*iter));
-       if (iter == NULL)
-               return NULL;
-
-       iter->keyval = keyval;
-       iter->cur_idx = 0;
-       iter->cur_ref = NULL;
-
-       ec_keyval_iter_next(iter);
-
-       return iter;
-}
-
-void
-ec_keyval_iter_next(struct ec_keyval_iter *iter)
-{
-       const struct ec_keyval_elt_ref *ref;
-       size_t i;
-
-       i = iter->cur_idx;
-       if (i == iter->keyval->table_size)
-               return; /* no more element */
-
-       if (iter->cur_ref != NULL) {
-               ref = LIST_NEXT(iter->cur_ref, next);
-               if (ref != NULL) {
-                       iter->cur_ref = ref;
-                       return;
-               }
-               i++;
-       }
-
-       for (; i < iter->keyval->table_size; i++) {
-               LIST_FOREACH(ref, &iter->keyval->table[i], next) {
-                       iter->cur_idx = i;
-                       iter->cur_ref = LIST_FIRST(&iter->keyval->table[i]);
-                       return;
-               }
-       }
-
-       iter->cur_idx = iter->keyval->table_size;
-       iter->cur_ref = NULL;
-}
-
-bool
-ec_keyval_iter_valid(const struct ec_keyval_iter *iter)
-{
-       if (iter == NULL || iter->cur_ref == NULL)
-               return false;
-
-       return true;
-}
-
-const char *
-ec_keyval_iter_get_key(const struct ec_keyval_iter *iter)
-{
-       if (iter == NULL || iter->cur_ref == NULL)
-               return NULL;
-
-       return iter->cur_ref->elt->key;
-}
-
-void *
-ec_keyval_iter_get_val(const struct ec_keyval_iter *iter)
-{
-       if (iter == NULL || iter->cur_ref == NULL)
-               return NULL;
-
-       return iter->cur_ref->elt->val;
-}
-
-void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval)
-{
-       struct ec_keyval_iter *iter;
-
-       if (keyval == NULL) {
-               fprintf(out, "empty keyval\n");
-               return;
-       }
-
-       fprintf(out, "keyval:\n");
-       for (iter = ec_keyval_iter(keyval);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               fprintf(out, "  %s: %p\n",
-                       ec_keyval_iter_get_key(iter),
-                       ec_keyval_iter_get_val(iter));
-       }
-       ec_keyval_iter_free(iter);
-}
-
-struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval)
-{
-       struct ec_keyval *dup = NULL;
-       struct ec_keyval_elt_ref *ref, *dup_ref = NULL;
-       size_t i;
-
-       dup = ec_keyval();
-       if (dup == NULL)
-               return NULL;
-
-       for (i = 0; i < keyval->table_size; i++) {
-               LIST_FOREACH(ref, &keyval->table[i], next) {
-                       dup_ref = ec_calloc(1, sizeof(*ref));
-                       if (dup_ref == NULL)
-                               goto fail;
-                       dup_ref->elt = ref->elt;
-                       ref->elt->refcount++;
-
-                       if (__ec_keyval_set(dup, dup_ref) < 0)
-                               goto fail;
-               }
-       }
-
-       return dup;
-
-fail:
-       ec_keyval_elt_ref_free(dup_ref);
-       ec_keyval_free(dup);
-       return NULL;
-}
-
-static int ec_keyval_init_func(void)
-{
-       int fd;
-       ssize_t ret;
-
-       return 0;//XXX for test reproduceability
-
-       fd = open("/dev/urandom", 0);
-       if (fd == -1) {
-               fprintf(stderr, "failed to open /dev/urandom\n");
-               return -1;
-       }
-       ret = read(fd, &ec_keyval_seed, sizeof(ec_keyval_seed));
-       if (ret != sizeof(ec_keyval_seed)) {
-               fprintf(stderr, "failed to read /dev/urandom\n");
-               return -1;
-       }
-       close(fd);
-       return 0;
-}
-
-static struct ec_init ec_keyval_init = {
-       .init = ec_keyval_init_func,
-       .priority = 50,
-};
-
-EC_INIT_REGISTER(ec_keyval_init);
-
-/* LCOV_EXCL_START */
-static int ec_keyval_testcase(void)
-{
-       struct ec_keyval *keyval, *dup;
-       struct ec_keyval_iter *iter;
-       char *val;
-       size_t i, count;
-       int ret, testres = 0;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-
-       keyval = ec_keyval();
-       if (keyval == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create keyval\n");
-               return -1;
-       }
-
-       count = 0;
-       for (iter = ec_keyval_iter(keyval);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               count++;
-       }
-       ec_keyval_iter_free(iter);
-       testres |= EC_TEST_CHECK(count == 0, "invalid count in iterator");
-
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0, "bad keyval len");
-       ret = ec_keyval_set(keyval, "key1", "val1", NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       ret = ec_keyval_set(keyval, "key2", ec_strdup("val2"), ec_free_func);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2, "bad keyval len");
-
-       val = ec_keyval_get(keyval, "key1");
-       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val1"),
-                               "invalid keyval value");
-       val = ec_keyval_get(keyval, "key2");
-       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val2"),
-                               "invalid keyval value");
-       val = ec_keyval_get(keyval, "key3");
-       testres |= EC_TEST_CHECK(val == NULL, "key3 should be NULL");
-
-       ret = ec_keyval_set(keyval, "key1", "another_val1", NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       ret = ec_keyval_set(keyval, "key2", ec_strdup("another_val2"),
-                       ec_free_func);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2,
-                               "bad keyval len");
-
-       val = ec_keyval_get(keyval, "key1");
-       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val1"),
-               "invalid keyval value");
-       val = ec_keyval_get(keyval, "key2");
-       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val2"),
-               "invalid keyval value");
-       testres |= EC_TEST_CHECK(ec_keyval_has_key(keyval, "key1"),
-               "key1 should be in keyval");
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_keyval_dump(f, NULL);
-       fclose(f);
-       f = NULL;
-       free(buf);
-       buf = NULL;
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_keyval_dump(f, keyval);
-       fclose(f);
-       f = NULL;
-       free(buf);
-       buf = NULL;
-
-       ret = ec_keyval_del(keyval, "key1");
-       testres |= EC_TEST_CHECK(ret == 0, "cannot del key1");
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 1,
-               "invalid keyval len");
-       ret = ec_keyval_del(keyval, "key2");
-       testres |= EC_TEST_CHECK(ret == 0, "cannot del key2");
-       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0,
-               "invalid keyval len");
-
-       for (i = 0; i < 100; i++) {
-               char key[8];
-               snprintf(key, sizeof(key), "k%zd", i);
-               ret = ec_keyval_set(keyval, key, "val", NULL);
-               testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
-       }
-       dup = ec_keyval_dup(keyval);
-       testres |= EC_TEST_CHECK(dup != NULL, "cannot duplicate keyval");
-       if (dup != NULL) {
-               for (i = 0; i < 100; i++) {
-                       char key[8];
-                       snprintf(key, sizeof(key), "k%zd", i);
-                       val = ec_keyval_get(dup, key);
-                       testres |= EC_TEST_CHECK(
-                               val != NULL && !strcmp(val, "val"),
-                               "invalid keyval value");
-               }
-               ec_keyval_free(dup);
-               dup = NULL;
-       }
-
-       count = 0;
-       for (iter = ec_keyval_iter(keyval);
-            ec_keyval_iter_valid(iter);
-            ec_keyval_iter_next(iter)) {
-               count++;
-       }
-       ec_keyval_iter_free(iter);
-       testres |= EC_TEST_CHECK(count == 100, "invalid count in iterator");
-
-       /* einval */
-       ret = ec_keyval_set(keyval, NULL, "val1", NULL);
-       testres |= EC_TEST_CHECK(ret == -1, "should not be able to set key");
-       val = ec_keyval_get(keyval, NULL);
-       testres |= EC_TEST_CHECK(val == NULL, "get(NULL) should no success");
-
-       ec_keyval_free(keyval);
-
-       return testres;
-
-fail:
-       ec_keyval_free(keyval);
-       if (f)
-               fclose(f);
-       free(buf);
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_keyval_test = {
-       .name = "keyval",
-       .test = ec_keyval_testcase,
-};
-
-EC_TEST_REGISTER(ec_keyval_test);
diff --git a/libecoli/ecoli_keyval.h b/libecoli/ecoli_keyval.h
deleted file mode 100644 (file)
index a3e9400..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Simple hash table API
- *
- * This file provides functions to store objects in hash tables, using strings
- * as keys.
- */
-
-#ifndef ECOLI_KEYVAL_
-#define ECOLI_KEYVAL_
-
-#include <stdio.h>
-#include <stdbool.h>
-
-typedef void (*ec_keyval_elt_free_t)(void *);
-
-struct ec_keyval;
-struct ec_keyval_iter;
-
-/**
- * Create a hash table.
- *
- * @return
- *   The hash table, or NULL on error (errno is set).
- */
-struct ec_keyval *ec_keyval(void);
-
-/**
- * Get a value from the hash table.
- *
- * @param keyval
- *   The hash table.
- * @param key
- *   The key string.
- * @return
- *   The element if it is found, or NULL on error (errno is set).
- *   In case of success but the element is NULL, errno is set to 0.
- */
-void *ec_keyval_get(const struct ec_keyval *keyval, const char *key);
-
-/**
- * Check if the hash table contains this key.
- *
- * @param keyval
- *   The hash table.
- * @param key
- *   The key string.
- * @return
- *   true if it contains the key, else false.
- */
-bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key);
-
-/**
- * Delete an object from the hash table.
- *
- * @param keyval
- *   The hash table.
- * @param key
- *   The key string.
- * @return
- *   0 on success, or -1 on error (errno is set).
- */
-int ec_keyval_del(struct ec_keyval *keyval, const char *key);
-
-/**
- * Add/replace an object in the hash table.
- *
- * @param keyval
- *   The hash table.
- * @param key
- *   The key string.
- * @param val
- *   The pointer to be saved in the hash table.
- * @param free_cb
- *   An optional pointer to a destructor function called when an
- *   object is destroyed (ec_keyval_del() or ec_keyval_free()).
- * @return
- *   0 on success, or -1 on error (errno is set).
- *   On error, the passed value is freed (free_cb(val) is called).
- */
-int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val,
-       ec_keyval_elt_free_t free_cb);
-
-/**
- * Free a hash table an all its objects.
- *
- * @param keyval
- *   The hash table.
- */
-void ec_keyval_free(struct ec_keyval *keyval);
-
-/**
- * Get the length of a hash table.
- *
- * @param keyval
- *   The hash table.
- * @return
- *   The length of the hash table.
- */
-size_t ec_keyval_len(const struct ec_keyval *keyval);
-
-/**
- * Duplicate a hash table
- *
- * A reference counter is shared between the clones of
- * hash tables so that the objects are freed only when
- * the last reference is destroyed.
- *
- * @param keyval
- *   The hash table.
- * @return
- *   The duplicated hash table, or NULL on error (errno is set).
- */
-struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval);
-
-/**
- * Dump a hash table.
- *
- * @param out
- *   The stream where the dump is sent.
- * @param keyval
- *   The hash table.
- */
-void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval);
-
-/**
- * Iterate the elements in the hash table.
- *
- * The typical usage is as below:
- *
- *     // dump elements
- *     for (iter = ec_keyval_iter(keyval);
- *          ec_keyval_iter_valid(iter);
- *          ec_keyval_iter_next(iter)) {
- *             printf("  %s: %p\n",
- *                     ec_keyval_iter_get_key(iter),
- *                     ec_keyval_iter_get_val(iter));
- *     }
- *     ec_keyval_iter_free(iter);
- *
- * @param keyval
- *   The hash table.
- * @return
- *   An iterator, or NULL on error (errno is set).
- */
-struct ec_keyval_iter *
-ec_keyval_iter(const struct ec_keyval *keyval);
-
-/**
- * Make the iterator point to the next element in the hash table.
- *
- * @param iter
- *   The hash table iterator.
- */
-void ec_keyval_iter_next(struct ec_keyval_iter *iter);
-
-/**
- * Free the iterator.
- *
- * @param iter
- *   The hash table iterator.
- */
-void ec_keyval_iter_free(struct ec_keyval_iter *iter);
-
-/**
- * Check if the iterator points to a valid element.
- *
- * @param iter
- *   The hash table iterator.
- * @return
- *   true if the element is valid, else false.
- */
-bool
-ec_keyval_iter_valid(const struct ec_keyval_iter *iter);
-
-/**
- * Get the key of the current element.
- *
- * @param iter
- *   The hash table iterator.
- * @return
- *   The current element key, or NULL if the iterator points to an
- *   invalid element.
- */
-const char *
-ec_keyval_iter_get_key(const struct ec_keyval_iter *iter);
-
-/**
- * Get the value of the current element.
- *
- * @param iter
- *   The hash table iterator.
- * @return
- *   The current element value, or NULL if the iterator points to an
- *   invalid element.
- */
-void *
-ec_keyval_iter_get_val(const struct ec_keyval_iter *iter);
-
-
-#endif
diff --git a/libecoli/ecoli_log.c b/libecoli/ecoli_log.c
deleted file mode 100644 (file)
index aefba83..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#define _GNU_SOURCE /* for vasprintf */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <syslog.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_string.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-
-EC_LOG_TYPE_REGISTER(log);
-
-static ec_log_t ec_log_fct = ec_log_default_cb;
-static void *ec_log_opaque;
-
-struct ec_log_type {
-       char *name;
-       enum ec_log_level level;
-};
-
-static struct ec_log_type *log_types;
-static size_t log_types_len;
-static enum ec_log_level global_level = EC_LOG_WARNING;
-
-int ec_log_level_set(enum ec_log_level level)
-{
-       if (level > EC_LOG_DEBUG)
-               return -1;
-       global_level = level;
-
-       return 0;
-}
-
-enum ec_log_level ec_log_level_get(void)
-{
-       return global_level;
-}
-
-int ec_log_default_cb(int type, enum ec_log_level level, void *opaque,
-               const char *str)
-{
-       (void)opaque;
-
-       if (level > ec_log_level_get())
-               return 0;
-
-       if (fprintf(stderr, "[%d] %-12s %s", level, ec_log_name(type), str) < 0)
-               return -1;
-
-       return 0;
-}
-
-int ec_log_fct_register(ec_log_t usr_log, void *opaque)
-{
-       if (usr_log == NULL) {
-               ec_log_fct = ec_log_default_cb;
-               ec_log_opaque = NULL;
-       } else {
-               ec_log_fct = usr_log;
-               ec_log_opaque = opaque;
-       }
-
-       return 0;
-}
-
-static int
-ec_log_lookup(const char *name)
-{
-       size_t i;
-
-       for (i = 0; i < log_types_len; i++) {
-               if (log_types[i].name == NULL)
-                       continue;
-               if (strcmp(name, log_types[i].name) == 0)
-                       return i;
-       }
-
-       return -1;
-}
-
-int
-ec_log_type_register(const char *name)
-{
-       struct ec_log_type *new_types;
-       char *copy;
-       int id;
-
-       id = ec_log_lookup(name);
-       if (id >= 0)
-               return id;
-
-       // XXX not that good to allocate in constructor
-       new_types = ec_realloc(log_types,
-               sizeof(*new_types) * (log_types_len + 1));
-       if (new_types == NULL)
-               return -1; /* errno is set */
-       log_types = new_types;
-
-       copy = ec_strdup(name);
-       if (copy == NULL)
-               return -1; /* errno is set */
-
-       id = log_types_len++;
-       log_types[id].name = copy;
-       log_types[id].level = EC_LOG_DEBUG;
-
-       return id;
-}
-
-const char *
-ec_log_name(int type)
-{
-       if (type < 0 || (unsigned int)type >= log_types_len)
-               return "unknown";
-       return log_types[type].name;
-}
-
-int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap)
-{
-       char *s;
-       int ret;
-
-       /* don't use ec_vasprintf here, because it will call
-        * ec_malloc(), then ec_log(), ec_vasprintf()...
-        * -> stack overflow */
-       ret = vasprintf(&s, format, ap);
-       if (ret < 0)
-               return ret;
-
-       ret = ec_log_fct(type, level, ec_log_opaque, s);
-       free(s);
-
-       return ret;
-}
-
-int ec_log(int type, enum ec_log_level level, const char *format, ...)
-{
-       va_list ap;
-       int ret;
-
-       va_start(ap, format);
-       ret = ec_vlog(type, level, format, ap);
-       va_end(ap);
-
-       return ret;
-}
-
-/* LCOV_EXCL_START */
-static int
-log_cb(int type, enum ec_log_level level, void *opaque, const char *str)
-{
-       (void)type;
-       (void)level;
-       (void)str;
-       *(int *)opaque = 1;
-
-       return 0;
-}
-
-static int ec_log_testcase(void)
-{
-       ec_log_t prev_log_cb;
-       void *prev_opaque;
-       const char *logname;
-       int testres = 0;
-       int check_cb = 0;
-       int logtype;
-       int level;
-       int ret;
-
-       prev_log_cb = ec_log_fct;
-       prev_opaque = ec_log_opaque;
-
-       ret = ec_log_fct_register(log_cb, &check_cb);
-       testres |= EC_TEST_CHECK(ret == 0,
-                               "cannot register log function\n");
-       EC_LOG(LOG_ERR, "test\n");
-       testres |= EC_TEST_CHECK(check_cb == 1,
-                               "log callback was not invoked\n");
-       logtype = ec_log_lookup("dsdedesdes");
-       testres |= EC_TEST_CHECK(logtype == -1,
-                               "lookup invalid name should return -1");
-       logtype = ec_log_lookup("log");
-       logname = ec_log_name(logtype);
-       testres |= EC_TEST_CHECK(logname != NULL &&
-                               !strcmp(logname, "log"),
-                               "cannot get log name\n");
-       logname = ec_log_name(-1);
-       testres |= EC_TEST_CHECK(logname != NULL &&
-                               !strcmp(logname, "unknown"),
-                               "cannot get invalid log name\n");
-       logname = ec_log_name(34324);
-       testres |= EC_TEST_CHECK(logname != NULL &&
-                               !strcmp(logname, "unknown"),
-                               "cannot get invalid log name\n");
-       level = ec_log_level_get();
-       ret = ec_log_level_set(2);
-       testres |= EC_TEST_CHECK(ret == 0 && ec_log_level_get() == 2,
-                               "cannot set log level\n");
-       ret = ec_log_level_set(10);
-       testres |= EC_TEST_CHECK(ret != 0,
-                               "should not be able to set log level\n");
-
-       ret = ec_log_fct_register(NULL, NULL);
-       ec_log_level_set(LOG_DEBUG);
-       EC_LOG(LOG_DEBUG, "test log\n");
-       ec_log_level_set(LOG_INFO);
-       EC_LOG(LOG_DEBUG, "test log (not displayed)\n");
-       ec_log_level_set(level);
-
-       ec_log_fct = prev_log_cb;
-       ec_log_opaque = prev_opaque;
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_log_test = {
-       .name = "log",
-       .test = ec_log_testcase,
-};
-
-EC_TEST_REGISTER(ec_log_test);
diff --git a/libecoli/ecoli_log.h b/libecoli/ecoli_log.h
deleted file mode 100644 (file)
index 2414dc0..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Logging API
- *
- * This file provide logging helpers:
- * - logging functions, supporting printf-like format
- * - several debug level (similar to syslog)
- * - named log types
- * - redirection of log to a user functions (default logs nothing)
- */
-
-#ifndef ECOLI_LOG_
-#define ECOLI_LOG_
-
-#include <stdarg.h>
-
-#include <ecoli_assert.h>
-
-enum ec_log_level {
-       EC_LOG_EMERG   = 0,  /* system is unusable               */
-       EC_LOG_ALERT   = 1,  /* action must be taken immediately */
-       EC_LOG_CRIT    = 2,  /* critical conditions              */
-       EC_LOG_ERR     = 3,  /* error conditions                 */
-       EC_LOG_WARNING = 4,  /* warning conditions               */
-       EC_LOG_NOTICE  = 5,  /* normal but significant condition */
-       EC_LOG_INFO    = 6,  /* informational                    */
-       EC_LOG_DEBUG   = 7,  /* debug-level messages             */
-};
-
-/**
- * Register a log type.
- *
- * This macro defines a function that will be called at startup (using
- * the "constructor" attribute). This function registers the named type
- * passed as argument, and sets a static global variable
- * "ec_log_local_type". This variable is used as the default log type
- * for this file when using EC_LOG() or EC_VLOG().
- *
- * This macro can be present several times in a file. In this case, the
- * local log type is set to the last registered type.
- *
- * On error, the function aborts.
- *
- * @param name
- *   The name of the log to be registered.
- */
-#define EC_LOG_TYPE_REGISTER(name)                                     \
-       static int name##_log_type;                                     \
-       static int ec_log_local_type;                                   \
-       __attribute__((constructor, used))                              \
-       static void ec_log_register_##name(void)                        \
-       {                                                               \
-               ec_log_local_type = ec_log_type_register(#name);        \
-               ec_assert_print(ec_log_local_type >= 0,                 \
-                               "cannot register log type.\n");         \
-               name##_log_type = ec_log_local_type;                    \
-       }
-
-/**
- * User log function type.
- *
- * It is advised that a user-defined log function drops all messages
- * that are at least as critical as ec_log_level_get(), as done by the
- * default handler.
- *
- * @param type
- *   The log type identifier.
- * @param level
- *   The log level.
- * @param opaque
- *   The opaque pointer that was passed to ec_log_fct_register().
- * @param str
- *   The string to log.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-typedef int (*ec_log_t)(int type, enum ec_log_level level, void *opaque,
-                       const char *str);
-
-/**
- * Register a user log function.
- *
- * @param usr_log
- *   Function pointer that will be invoked for each log call.
- *   If the parameter is NULL, ec_log_default_cb() is used.
- * @param opaque
- *   Opaque pointer passed to the log function.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_log_fct_register(ec_log_t usr_log, void *opaque);
-
-/**
- * Register a named log type.
- *
- * Register a new log type, which is identified by its name. The
- * function returns a log identifier associated to the log name. If the
- * name is already registered, the function just returns its identifier.
- *
- * @param name
- *   The name of the log type.
- * @return
- *   The log type identifier on success (positive or zero), -1 on
- *   error (errno is set).
- */
-int ec_log_type_register(const char *name);
-
-/**
- * Return the log name associated to the log type identifier.
- *
- * @param type
- *   The log type identifier.
- * @return
- *   The name associated to the log type, or "unknown". It always return
- *   a valid string (never NULL).
- */
-const char *ec_log_name(int type);
-
-/**
- * Log a formatted string.
- *
- * @param type
- *   The log type identifier.
- * @param level
- *   The log level.
- * @param format
- *   The format string, followed by optional arguments.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_log(int type, enum ec_log_level level, const char *format, ...)
-       __attribute__((format(__printf__, 3, 4)));
-
-/**
- * Log a formatted string.
- *
- * @param type
- *   The log type identifier.
- * @param level
- *   The log level.
- * @param format
- *   The format string.
- * @param ap
- *   The list of arguments.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap);
-
-/**
- * Log a formatted string using the local log type.
- *
- * This macro requires that a log type is previously register with
- * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type"
- * variable.
- *
- * @param level
- *   The log level.
- * @param format
- *   The format string, followed by optional arguments.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-#define EC_LOG(level, args...) ec_log(ec_log_local_type, level, args)
-
-/**
- * Log a formatted string using the local log type.
- *
- * This macro requires that a log type is previously register with
- * EC_LOG_TYPE_REGISTER() since it uses the "ec_log_local_type"
- * variable.
- *
- * @param level
- *   The log level.
- * @param format
- *   The format string.
- * @param ap
- *   The list of arguments.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-#define EC_VLOG(level, fmt, ap) ec_vlog(ec_log_local_type, level, fmt, ap)
-
-/**
- * Default log handler.
- *
- * This is the default log function that is used by the library. By
- * default, it prints all logs whose level is WARNING or more critical.
- * This level can be changed with ec_log_level_set().
- *
- * @param type
- *   The log type identifier.
- * @param level
- *   The log level.
- * @param opaque
- *   Unused.
- * @param str
- *   The string to be logged.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_log_default_cb(int type, enum ec_log_level level, void *opaque,
-               const char *str);
-
-/**
- * Set the global log level.
- *
- * This level is used by the default log handler, ec_log_default_cb().
- * All messages that are at least as critical as the default level are
- * displayed.
- *
- * It is advised
- *
- * @param level
- *   The log level to be set.
- * @return
- *   0 on success, -1 on error.
- */
-int ec_log_level_set(enum ec_log_level level);
-
-/**
- * Get the global log level.
- *
- * This level is used by the default log handler, ec_log_default_cb().
- * All messages that are at least as critical as the default level are
- * displayed.
- *
- * @param level
- *   The log level to be set.
- * @return
- *   0 on success, -1 on error.
- */
-enum ec_log_level ec_log_level_get(void);
-
-#endif
diff --git a/libecoli/ecoli_malloc.c b/libecoli/ecoli_malloc.c
deleted file mode 100644 (file)
index 5a022ae..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <ecoli_init.h>
-#include <ecoli_test.h>
-#include <ecoli_malloc.h>
-
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-EC_LOG_TYPE_REGISTER(malloc);
-
-static int init_done = 0;
-
-struct ec_malloc_handler ec_malloc_handler;
-
-int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free,
-       ec_realloc_t usr_realloc)
-{
-       if (usr_malloc == NULL || usr_free == NULL || usr_realloc == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (init_done) {
-               errno = EBUSY;
-               return -1;
-       }
-
-       ec_malloc_handler.malloc = usr_malloc;
-       ec_malloc_handler.free = usr_free;
-       ec_malloc_handler.realloc = usr_realloc;
-
-       return 0;
-}
-
-void *__ec_malloc(size_t size, const char *file, unsigned int line)
-{
-       return ec_malloc_handler.malloc(size, file, line);
-}
-
-void *ec_malloc_func(size_t size)
-{
-       return ec_malloc(size);
-}
-
-void __ec_free(void *ptr, const char *file, unsigned int line)
-{
-       ec_malloc_handler.free(ptr, file, line);
-}
-
-void ec_free_func(void *ptr)
-{
-       ec_free(ptr);
-}
-
-void *__ec_calloc(size_t nmemb, size_t size, const char *file,
-       unsigned int line)
-{
-       void *ptr;
-       size_t total;
-
-       /* check overflow */
-       total = size * nmemb;
-       if (nmemb != 0 && size != (total / nmemb)) {
-               errno = ENOMEM;
-               return NULL;
-       }
-
-       ptr = __ec_malloc(total, file, line);
-       if (ptr == NULL)
-               return NULL;
-
-       memset(ptr, 0, total);
-       return ptr;
-}
-
-void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line)
-{
-       return ec_malloc_handler.realloc(ptr, size, file, line);
-}
-
-void ec_realloc_func(void *ptr, size_t size)
-{
-       ec_realloc(ptr, size);
-}
-
-char *__ec_strdup(const char *s, const char *file, unsigned int line)
-{
-       size_t sz = strlen(s) + 1;
-       char *s2;
-
-       s2 = __ec_malloc(sz, file, line);
-       if (s2 == NULL)
-               return NULL;
-
-       memcpy(s2, s, sz);
-
-       return s2;
-}
-
-char *__ec_strndup(const char *s, size_t n, const char *file, unsigned int line)
-{
-       size_t sz = strnlen(s, n);
-       char *s2;
-
-       s2 = __ec_malloc(sz + 1, file, line);
-       if (s2 == NULL)
-               return NULL;
-
-       memcpy(s2, s, sz);
-       s2[sz] = '\0';
-
-       return s2;
-}
-
-static int ec_malloc_init_func(void)
-{
-       init_done = 1;
-       return 0;
-}
-
-static struct ec_init ec_malloc_init = {
-       .init = ec_malloc_init_func,
-       .priority = 40,
-};
-
-EC_INIT_REGISTER(ec_malloc_init);
-
-/* LCOV_EXCL_START */
-static int ec_malloc_testcase(void)
-{
-       int ret, testres = 0;
-       char *ptr, *ptr2;
-
-       ret = ec_malloc_register(NULL, NULL, NULL);
-       testres |= EC_TEST_CHECK(ret == -1,
-               "should not be able to register NULL malloc handlers");
-       ret = ec_malloc_register(__ec_malloc, __ec_free, __ec_realloc);
-       testres |= EC_TEST_CHECK(ret == -1,
-               "should not be able to register after init");
-
-       /* registration is tested in the test main.c */
-
-       ptr = ec_malloc(10);
-       if (ptr == NULL)
-               return -1;
-       memset(ptr, 0, 10);
-       ptr2 = ec_realloc(ptr, 20);
-       EC_TEST_CHECK(ptr2 != NULL, "cannot realloc ptr\n");
-       if (ptr2 == NULL)
-               ec_free(ptr);
-       else
-               ec_free(ptr2);
-       ptr = NULL;
-       ptr2 = NULL;
-
-       ptr = ec_malloc_func(10);
-       if (ptr == NULL)
-               return -1;
-       memset(ptr, 0, 10);
-       ec_free_func(ptr);
-       ptr = NULL;
-
-       ptr = ec_calloc(2, (size_t)-1);
-       EC_TEST_CHECK(ptr == NULL, "bad overflow check in ec_calloc\n");
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_malloc_test = {
-       .name = "malloc",
-       .test = ec_malloc_testcase,
-};
-
-EC_TEST_REGISTER(ec_malloc_test);
diff --git a/libecoli/ecoli_malloc.h b/libecoli/ecoli_malloc.h
deleted file mode 100644 (file)
index 42cbf87..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Interface to configure the allocator used by libecoli.
- * By default, the standard allocation functions from libc are used.
- */
-
-#ifndef ECOLI_MALLOC_
-#define ECOLI_MALLOC_
-
-#include <sys/types.h>
-#include <stdlib.h>
-#include <string.h>
-
-/**
- * Function type of malloc, passed to ec_malloc_register().
- *
- * The API is the same than malloc(), excepted the file and line
- * arguments.
- *
- * @param size
- *   The size of the memory area to allocate.
- * @param file
- *   The path to the file that invoked the malloc.
- * @param line
- *   The line in the file that invoked the malloc.
- * @return
- *   A pointer to the allocated memory area, or NULL on error (errno
- *   is set).
- */
-typedef void *(*ec_malloc_t)(size_t size, const char *file, unsigned int line);
-
-/**
- * Function type of free, passed to ec_malloc_register().
- *
- * The API is the same than free(), excepted the file and line
- * arguments.
- *
- * @param ptr
- *   The pointer to the memory area to be freed.
- * @param file
- *   The path to the file that invoked the malloc.
- * @param line
- *   The line in the file that invoked the malloc.
- */
-typedef void (*ec_free_t)(void *ptr, const char *file, unsigned int line);
-
-/**
- * Function type of realloc, passed to ec_malloc_register().
- *
- * The API is the same than realloc(), excepted the file and line
- * arguments.
- *
- * @param ptr
- *   The pointer to the memory area to be reallocated.
- * @param file
- *   The path to the file that invoked the malloc.
- * @param line
- *   The line in the file that invoked the malloc.
- * @return
- *   A pointer to the allocated memory area, or NULL on error (errno
- *   is set).
- */
-typedef void *(*ec_realloc_t)(void *ptr, size_t size, const char *file,
-       unsigned int line);
-
-/**
- * Register allocation functions.
- *
- * This function can be use to register another allocator
- * to be used by libecoli. By default, ec_malloc(), ec_free() and
- * ec_realloc() use the standard libc allocator. Another handler
- * can be used for debug purposes or when running in a specific
- * environment.
- *
- * This function must be called before ec_init().
- *
- * @param usr_malloc
- *   A user-defined malloc function.
- * @param usr_free
- *   A user-defined free function.
- * @param usr_realloc
- *   A user-defined realloc function.
- * @return
- *   0 on success, or -1 on error (errno is set).
- */
-int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free,
-       ec_realloc_t usr_realloc);
-
-struct ec_malloc_handler {
-       ec_malloc_t malloc;
-       ec_free_t free;
-       ec_realloc_t realloc;
-};
-
-extern struct ec_malloc_handler ec_malloc_handler;
-
-/**
- * Allocate a memory area.
- *
- * Like malloc(), ec_malloc() allocates size bytes and returns a pointer
- * to the allocated memory. The memory is not initialized. The memory is
- * freed with ec_free().
- *
- * @param size
- *   The size of the area to allocate in bytes.
- * @return
- *   The pointer to the allocated memory, or NULL on error (errno is set).
- */
-#define ec_malloc(size) ({                                             \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.malloc == NULL)                           \
-               ret_ = malloc(size);                                    \
-       else                                                            \
-               ret_ = __ec_malloc(size, __FILE__, __LINE__);           \
-       ret_;                                                           \
-       })
-
-/**
- * Ecoli malloc function.
- *
- * Use this function when the macro ec_malloc() cannot be used,
- * for instance when it is passed as a callback pointer.
- */
-void *ec_malloc_func(size_t size);
-
-/**
- * Free a memory area.
- *
- * Like free(), ec_free() frees the area pointed by ptr, which must have
- * been returned by a previous call to ec_malloc() or any other
- * allocation function of this file.
- *
- * @param ptr
- *   The pointer to the memory area.
- */
-#define ec_free(ptr) ({                                                        \
-       if (ec_malloc_handler.free == NULL)                             \
-               free(ptr);                                              \
-       else                                                            \
-               __ec_free(ptr, __FILE__, __LINE__);                     \
-       })
-
-/**
- * Ecoli free function.
- *
- * Use this function when the macro ec_free() cannot be used,
- * for instance when it is passed as a callback pointer.
- */
-void ec_free_func(void *ptr);
-
-/**
- * Resize an allocated memory area.
- *
- * @param ptr
- *   The pointer to the previously allocated memory area, or NULL.
- * @param size
- *   The new size of the memory area.
- * @return
- *   A pointer to the newly allocated memory, or NULL if the request
- *   fails. In that case, the original area is left untouched.
- */
-#define ec_realloc(ptr, size) ({                                       \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.realloc == NULL)                          \
-               ret_ = realloc(ptr, size);                              \
-       else                                                            \
-               ret_ = __ec_realloc(ptr, size, __FILE__, __LINE__);     \
-       ret_;                                                           \
-       })
-
-/**
- * Ecoli realloc function.
- *
- * Use this function when the macro ec_realloc() cannot be used,
- * for instance when it is passed as a callback pointer.
- */
-void ec_realloc_func(void *ptr, size_t size);
-
-/**
- * Allocate and initialize an array of elements.
- *
- * @param n
- *   The number of elements.
- * @param size
- *   The size of each element.
- * @return
- *   The pointer to the allocated memory, or NULL on error (errno is set).
- */
-#define ec_calloc(n, size) ({                                          \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.malloc == NULL)                           \
-               ret_ = calloc(n, size);                                 \
-       else                                                            \
-               ret_ = __ec_calloc(n, size, __FILE__, __LINE__);        \
-       ret_;                                                           \
-       })
-
-/**
- * Duplicate a string.
- *
- * Memory for the new string is obtained with ec_malloc(), and can be
- * freed with ec_free().
- *
- * @param s
- *   The string to be duplicated.
- * @return
- *   The pointer to the duplicated string, or NULL on error (errno is set).
- */
-#define ec_strdup(s) ({                                                        \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.malloc == NULL)                           \
-               ret_ = strdup(s);                                       \
-       else                                                            \
-               ret_ = __ec_strdup(s, __FILE__, __LINE__);              \
-       ret_;                                                           \
-       })
-
-/**
- * Duplicate at most n bytes of a string.
- *
- * This function is similar to ec_strdup(), except that it copies at
- * most n bytes.  If s is longer than n, only n bytes are copied, and a
- * terminating null byte ('\0') is added.
- *
- * @param s
- *   The string to be duplicated.
- * @param n
- *   The maximum length of the new string.
- * @return
- *   The pointer to the duplicated string, or NULL on error (errno is set).
- */
-#define ec_strndup(s, n) ({                                            \
-       void *ret_;                                                     \
-       if (ec_malloc_handler.malloc == NULL)                           \
-               ret_ = strndup(s, n);                                   \
-       else                                                            \
-               ret_ = __ec_strndup(s, n, __FILE__, __LINE__);          \
-       ret_;                                                           \
-       })
-
-/* internal */
-void *__ec_malloc(size_t size, const char *file, unsigned int line);
-void __ec_free(void *ptr, const char *file, unsigned int line);
-void *__ec_calloc(size_t nmemb, size_t size, const char *file,
-       unsigned int line);
-void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line);
-char *__ec_strdup(const char *s, const char *file, unsigned int line);
-char *__ec_strndup(const char *s, size_t n, const char *file,
-       unsigned int line);
-
-
-#endif
diff --git a/libecoli/ecoli_murmurhash.c b/libecoli/ecoli_murmurhash.c
deleted file mode 100644 (file)
index 7aafece..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdint.h>
-
-#include <ecoli_murmurhash.h>
-
-uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed)
-{
-       const uint8_t *data = (const uint8_t *)key;
-       const uint8_t *tail;
-       const int nblocks = len / 4;
-       uint32_t h1 = seed;
-       uint32_t k1;
-       const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4);
-       int i;
-
-       for (i = -nblocks; i; i++) {
-               k1 = blocks[i];
-
-               h1 = ec_murmurhash3_add32(h1, k1);
-               h1 = ec_murmurhash3_mix32(h1);
-       }
-
-       tail = (const uint8_t *)(data + nblocks * 4);
-       k1 = 0;
-
-       switch(len & 3) {
-       case 3: k1 ^= tail[2] << 16;
-       case 2: k1 ^= tail[1] << 8;
-       case 1: k1 ^= tail[0];
-               h1 = ec_murmurhash3_add32(h1, k1);
-       };
-
-       /* finalization */
-       h1 ^= len;
-       h1 = ec_murmurhash3_fmix32(h1);
-       return h1;
-}
diff --git a/libecoli/ecoli_murmurhash.h b/libecoli/ecoli_murmurhash.h
deleted file mode 100644 (file)
index 6b76d34..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * MurmurHash3 is a hash implementation that was written by Austin Appleby, and
- * is placed in the public domain. The author hereby disclaims copyright to this
- * source code.
- */
-
-#ifndef ECOLI_MURMURHASH_H_
-#define ECOLI_MURMURHASH_H_
-
-#include <stdint.h>
-
-/** Hash rotation */
-static inline uint32_t ec_murmurhash_rotl32(uint32_t x, int8_t r)
-{
-       return (x << r) | (x >> (32 - r));
-}
-
-/** Add 32-bit to the hash */
-static inline uint32_t ec_murmurhash3_add32(uint32_t h, uint32_t data)
-{
-       data *= 0xcc9e2d51;
-       data = ec_murmurhash_rotl32(data, 15);
-       data *= 0x1b873593;
-       h ^= data;
-       return h;
-}
-
-/** Intermediate mix */
-static inline uint32_t ec_murmurhash3_mix32(uint32_t h)
-{
-       h = ec_murmurhash_rotl32(h,13);
-       h = h * 5 +0xe6546b64;
-       return h;
-}
-
-/** Final mix: force all bits of a hash block to avalanche */
-static inline uint32_t ec_murmurhash3_fmix32(uint32_t h)
-{
-       h ^= h >> 16;
-       h *= 0x85ebca6b;
-       h ^= h >> 13;
-       h *= 0xc2b2ae35;
-       h ^= h >> 16;
-
-       return h;
-}
-
-/**
- * Calculate a 32-bit murmurhash3
- *
- * @param key
- *   The key (the unaligned variable-length array of bytes).
- * @param len
- *   The length of the key, counting by bytes.
- * @param seed
- *   Can be any 4-byte value initialization value.
- * @return
- *   A 32-bit hash.
- */
-uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed);
-
-#endif /* ECOLI_MURMURHASH_H_ */
diff --git a/libecoli/ecoli_node.c b/libecoli/ecoli_node.c
deleted file mode 100644 (file)
index 9789102..0000000
+++ /dev/null
@@ -1,608 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_string.h>
-#include <ecoli_strvec.h>
-#include <ecoli_keyval.h>
-#include <ecoli_log.h>
-#include <ecoli_config.h>
-#include <ecoli_test.h>
-#include <ecoli_node.h>
-
-#include <ecoli_node_str.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_int.h>
-
-EC_LOG_TYPE_REGISTER(node);
-
-static struct ec_node_type_list node_type_list =
-       TAILQ_HEAD_INITIALIZER(node_type_list);
-
-const struct ec_node_type *
-ec_node_type_lookup(const char *name)
-{
-       struct ec_node_type *type;
-
-       TAILQ_FOREACH(type, &node_type_list, next) {
-               if (!strcmp(name, type->name))
-                       return type;
-       }
-
-       errno = ENOENT;
-       return NULL;
-}
-
-int ec_node_type_register(struct ec_node_type *type)
-{
-       EC_CHECK_ARG(type->size >= sizeof(struct ec_node), -1, EINVAL);
-
-       if (ec_node_type_lookup(type->name) != NULL) {
-               errno = EEXIST;
-               return -1;
-       }
-
-       TAILQ_INSERT_TAIL(&node_type_list, type, next);
-
-       return 0;
-}
-
-void ec_node_type_dump(FILE *out)
-{
-       struct ec_node_type *type;
-
-       TAILQ_FOREACH(type, &node_type_list, next)
-               fprintf(out, "%s\n", type->name);
-}
-
-struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id)
-{
-       struct ec_node *node = NULL;
-
-       EC_LOG(EC_LOG_DEBUG, "create node type=%s id=%s\n",
-               type->name, id);
-       if (id == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       node = ec_calloc(1, type->size);
-       if (node == NULL)
-               goto fail;
-
-       node->type = type;
-       node->refcnt = 1;
-
-       // XXX check that id matches [_a-zA-Z][:-_0-9a-zA-Z]*
-       node->id = ec_strdup(id);
-       if (node->id == NULL)
-               goto fail;
-
-       if (ec_asprintf(&node->desc, "<%s>", type->name) < 0)
-               goto fail;
-
-       node->attrs = ec_keyval();
-       if (node->attrs == NULL)
-               goto fail;
-
-       if (type->init_priv != NULL) {
-               if (type->init_priv(node) < 0)
-                       goto fail;
-       }
-
-       return node;
-
- fail:
-       if (node != NULL) {
-               ec_keyval_free(node->attrs);
-               ec_free(node->desc);
-               ec_free(node->id);
-       }
-       ec_free(node);
-
-       return NULL;
-}
-
-const struct ec_config_schema *
-ec_node_type_schema(const struct ec_node_type *type)
-{
-       return type->schema;
-}
-
-const char *
-ec_node_type_name(const struct ec_node_type *type)
-{
-       return type->name;
-}
-
-struct ec_node *ec_node(const char *typename, const char *id)
-{
-       const struct ec_node_type *type;
-
-       type = ec_node_type_lookup(typename);
-       if (type == NULL) {
-               EC_LOG(EC_LOG_ERR, "type=%s does not exist\n",
-                       typename);
-               return NULL;
-       }
-
-       return ec_node_from_type(type, id);
-}
-
-static void count_references(struct ec_node *node, unsigned int refs)
-{
-       struct ec_node *child;
-       size_t i, n;
-       int ret;
-
-       if (node->free.state == EC_NODE_FREE_STATE_TRAVERSED) {
-               node->free.refcnt += refs;
-               return;
-       }
-       node->free.refcnt = refs;
-       node->free.state = EC_NODE_FREE_STATE_TRAVERSED;
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               count_references(child, refs);
-       }
-}
-
-static void mark_freeable(struct ec_node *node, enum ec_node_free_state mark)
-{
-       struct ec_node *child;
-       unsigned int refs;
-       size_t i, n;
-       int ret;
-
-       if (mark == node->free.state)
-               return;
-
-       if (node->refcnt > node->free.refcnt)
-               mark = EC_NODE_FREE_STATE_NOT_FREEABLE;
-       assert(node->refcnt >= node->free.refcnt);
-       node->free.state = mark;
-
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               mark_freeable(child, mark);
-       }
-}
-
-static void reset_mark(struct ec_node *node)
-{
-       struct ec_node *child;
-       unsigned int refs;
-       size_t i, n;
-       int ret;
-
-       if (node->free.state == EC_NODE_FREE_STATE_NONE)
-               return;
-
-       node->free.state = EC_NODE_FREE_STATE_NONE;
-       node->free.refcnt = 0;
-
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               reset_mark(child);
-       }
-}
-
-/* free a node, taking care of loops in the node graph */
-void ec_node_free(struct ec_node *node)
-{
-       size_t n;
-
-       if (node == NULL)
-               return;
-
-       assert(node->refcnt > 0);
-
-       if (node->free.state == EC_NODE_FREE_STATE_NONE &&
-                       node->refcnt != 1) {
-
-               /* Traverse the node tree starting from this node, and for each
-                * node, count the number of reachable references. Then, all
-                * nodes whose reachable references == total reference are
-                * marked as freeable, and other are marked as unfreeable. Any
-                * node reachable from an unfreeable node is also marked as
-                * unfreeable. */
-               if (node->free.state == EC_NODE_FREE_STATE_NONE) {
-                       count_references(node, 1);
-                       mark_freeable(node, EC_NODE_FREE_STATE_FREEABLE);
-               }
-       }
-
-       if (node->free.state == EC_NODE_FREE_STATE_NOT_FREEABLE) {
-               node->refcnt--;
-               reset_mark(node);
-               return;
-       }
-
-       if (node->free.state != EC_NODE_FREE_STATE_FREEING) {
-               node->free.state = EC_NODE_FREE_STATE_FREEING;
-
-               /* children will be freed by config_free() and free_priv() */
-               ec_config_free(node->config);
-               node->config = NULL;
-               n = ec_node_get_children_count(node);
-               assert(n == 0 || node->type->free_priv != NULL);
-               if (node->type->free_priv != NULL)
-                       node->type->free_priv(node);
-               ec_free(node->id);
-               ec_free(node->desc);
-               ec_keyval_free(node->attrs);
-       }
-
-       node->refcnt--;
-       if (node->refcnt != 0)
-               return;
-
-       node->free.state = EC_NODE_FREE_STATE_NONE;
-       node->free.refcnt = 0;
-
-       ec_free(node);
-}
-
-struct ec_node *ec_node_clone(struct ec_node *node)
-{
-       if (node != NULL)
-               node->refcnt++;
-       return node;
-}
-
-size_t ec_node_get_children_count(const struct ec_node *node)
-{
-       if (node->type->get_children_count == NULL)
-               return 0;
-       return node->type->get_children_count(node);
-}
-
-int
-ec_node_get_child(const struct ec_node *node, size_t i,
-       struct ec_node **child, unsigned int *refs)
-{
-       *child = NULL;
-       *refs = 0;
-       if (node->type->get_child == NULL)
-               return -1;
-       return node->type->get_child(node, i, child, refs);
-}
-
-int
-ec_node_set_config(struct ec_node *node, struct ec_config *config)
-{
-       if (node->type->schema == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-       if (ec_config_validate(config, node->type->schema) < 0)
-               goto fail;
-       if (node->type->set_config != NULL) {
-               if (node->type->set_config(node, config) < 0)
-                       goto fail;
-       }
-
-       ec_config_free(node->config);
-       node->config = config;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       return -1;
-}
-
-const struct ec_config *ec_node_get_config(struct ec_node *node)
-{
-       return node->config;
-}
-
-struct ec_node *ec_node_find(struct ec_node *node, const char *id)
-{
-       struct ec_node *child, *retnode;
-       const char *node_id = ec_node_id(node);
-       unsigned int refs;
-       size_t i, n;
-       int ret;
-
-       if (id != NULL && node_id != NULL && !strcmp(node_id, id))
-               return node;
-
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               retnode = ec_node_find(child, id);
-               if (retnode != NULL)
-                       return retnode;
-       }
-
-       return NULL;
-}
-
-const struct ec_node_type *ec_node_type(const struct ec_node *node)
-{
-       return node->type;
-}
-
-struct ec_keyval *ec_node_attrs(const struct ec_node *node)
-{
-       return node->attrs;
-}
-
-const char *ec_node_id(const struct ec_node *node)
-{
-       return node->id;
-}
-
-static void __ec_node_dump(FILE *out,
-       const struct ec_node *node, size_t indent, struct ec_keyval *dict)
-{
-       const char *id, *typename;
-       struct ec_node *child;
-       unsigned int refs;
-       char buf[32];
-       size_t i, n;
-       int ret;
-
-       id = ec_node_id(node);
-       typename = node->type->name;
-
-       snprintf(buf, sizeof(buf), "%p", node);
-       if (ec_keyval_has_key(dict, buf)) {
-               fprintf(out, "%*s" "type=%s id=%s %p... (loop)\n",
-                       (int)indent * 4, "", typename, id, node);
-               return;
-       }
-
-       ec_keyval_set(dict, buf, NULL, NULL);
-       fprintf(out, "%*s" "type=%s id=%s %p refs=%u free_state=%d free_refs=%d\n",
-               (int)indent * 4, "", typename, id, node, node->refcnt,
-               node->free.state, node->free.refcnt);
-
-       n = ec_node_get_children_count(node);
-       for (i = 0; i < n; i++) {
-               ret = ec_node_get_child(node, i, &child, &refs);
-               assert(ret == 0);
-               __ec_node_dump(out, child, indent + 1, dict);
-       }
-}
-
-/* XXX this is too much debug-oriented, we should have a parameter or 2 funcs */
-void ec_node_dump(FILE *out, const struct ec_node *node)
-{
-       struct ec_keyval *dict = NULL;
-
-       fprintf(out, "------------------- node dump:\n");
-
-       if (node == NULL) {
-               fprintf(out, "node is NULL\n");
-               return;
-       }
-
-       dict = ec_keyval();
-       if (dict == NULL)
-               goto fail;
-
-       __ec_node_dump(out, node, 0, dict);
-
-       ec_keyval_free(dict);
-       return;
-
-fail:
-       ec_keyval_free(dict);
-       EC_LOG(EC_LOG_ERR, "failed to dump node\n");
-}
-
-const char *ec_node_desc(const struct ec_node *node)
-{
-       if (node->type->desc != NULL)
-               return node->type->desc(node);
-
-       return node->desc;
-}
-
-int ec_node_check_type(const struct ec_node *node,
-               const struct ec_node_type *type)
-{
-       if (strcmp(node->type->name, type->name)) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       return 0;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_testcase(void)
-{
-       struct ec_node *node = NULL, *expr = NULL;
-       struct ec_node *expr2 = NULL, *val = NULL, *op = NULL, *seq = NULL;
-       const struct ec_node_type *type;
-       struct ec_node *child;
-       unsigned int refs;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-       int testres = 0;
-       int ret;
-
-       node = EC_NODE_SEQ(EC_NO_ID,
-                       ec_node_str("id_x", "x"),
-                       ec_node_str("id_y", "y"));
-       if (node == NULL)
-               goto fail;
-
-       ec_node_clone(node);
-       ec_node_free(node);
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_node_dump(f, node);
-       ec_node_type_dump(f);
-       ec_node_dump(f, NULL);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "type=seq id=no-id"), "bad dump\n");
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "type=str id=id_x") &&
-               strstr(strstr(buf, "type=str id=id_x") + 1,
-                       "type=str id=id_y"),
-               "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       testres |= EC_TEST_CHECK(
-               !strcmp(ec_node_type(node)->name, "seq") &&
-               !strcmp(ec_node_id(node), EC_NO_ID) &&
-               !strcmp(ec_node_desc(node), "<seq>"),
-               "bad child 0");
-
-       testres |= EC_TEST_CHECK(
-               ec_node_get_children_count(node) == 2,
-               "bad children count\n");
-       ret = ec_node_get_child(node, 0, &child, &refs);
-       testres |= EC_TEST_CHECK(ret == 0 &&
-               child != NULL &&
-               !strcmp(ec_node_type(child)->name, "str") &&
-               !strcmp(ec_node_id(child), "id_x"),
-               "bad child 0");
-       ret = ec_node_get_child(node, 1, &child, &refs);
-       testres |= EC_TEST_CHECK(ret == 0 &&
-               child != NULL &&
-               !strcmp(ec_node_type(child)->name, "str") &&
-               !strcmp(ec_node_id(child), "id_y"),
-               "bad child 1");
-       ret = ec_node_get_child(node, 2, &child, &refs);
-       testres |= EC_TEST_CHECK(ret != 0,
-               "ret should be != 0");
-       testres |= EC_TEST_CHECK(child == NULL,
-               "child 2 should be NULL");
-
-       child = ec_node_find(node, "id_x");
-       testres |= EC_TEST_CHECK(child != NULL &&
-               !strcmp(ec_node_type(child)->name, "str") &&
-               !strcmp(ec_node_id(child), "id_x") &&
-               !strcmp(ec_node_desc(child), "x"),
-               "bad child id_x");
-       child = ec_node_find(node, "id_dezdex");
-       testres |= EC_TEST_CHECK(child == NULL,
-               "child with wrong id should be NULL");
-
-       ret = ec_keyval_set(ec_node_attrs(node), "key", "val", NULL);
-       testres |= EC_TEST_CHECK(ret == 0,
-               "cannot set node attribute\n");
-
-       type = ec_node_type_lookup("seq");
-       testres |= EC_TEST_CHECK(type != NULL &&
-               ec_node_check_type(node, type) == 0,
-               "cannot get seq node type");
-       type = ec_node_type_lookup("str");
-       testres |= EC_TEST_CHECK(type != NULL &&
-               ec_node_check_type(node, type) < 0,
-               "node type should not be str");
-
-       ec_node_free(node);
-       node = NULL;
-
-       node = ec_node("deznuindez", EC_NO_ID);
-       testres |= EC_TEST_CHECK(node == NULL,
-                       "should not be able to create node\n");
-
-       /* test loop */
-       expr = ec_node("or", EC_NO_ID);
-       val = ec_node_int(EC_NO_ID, 0, 10, 0);
-       op = ec_node_str(EC_NO_ID, "!");
-       seq = EC_NODE_SEQ(EC_NO_ID,
-                       op,
-                       ec_node_clone(expr));
-       op = NULL;
-       if (expr == NULL || val == NULL || seq == NULL)
-               goto fail;
-       if (ec_node_or_add(expr, ec_node_clone(seq)) < 0)
-               goto fail;
-       ec_node_free(seq);
-       seq = NULL;
-       if (ec_node_or_add(expr, ec_node_clone(val)) < 0)
-               goto fail;
-       ec_node_free(val);
-       val = NULL;
-
-       testres |= EC_TEST_CHECK_PARSE(expr, 1, "1");
-       testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1");
-       testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!");
-
-       ec_node_free(expr);
-       expr = NULL;
-
-       /* same loop test, but keep some refs (released later) */
-       expr = ec_node("or", EC_NO_ID);
-       ec_node_clone(expr);
-       expr2 = expr;
-       val = ec_node_int(EC_NO_ID, 0, 10, 0);
-       op = ec_node_str(EC_NO_ID, "!");
-       seq = EC_NODE_SEQ(EC_NO_ID,
-                       op,
-                       ec_node_clone(expr));
-       op = NULL;
-       if (expr == NULL || val == NULL || seq == NULL)
-               goto fail;
-       if (ec_node_or_add(expr, ec_node_clone(seq)) < 0)
-               goto fail;
-       ec_node_free(seq);
-       seq = NULL;
-       if (ec_node_or_add(expr, ec_node_clone(val)) < 0)
-               goto fail;
-
-       testres |= EC_TEST_CHECK_PARSE(expr, 1, "1");
-       testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1");
-       testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!");
-
-       ec_node_free(expr2);
-       expr2 = NULL;
-       ec_node_free(val);
-       val = NULL;
-       ec_node_free(expr);
-       expr = NULL;
-
-       return testres;
-
-fail:
-       ec_node_free(expr);
-       ec_node_free(expr2);
-       ec_node_free(val);
-       ec_node_free(seq);
-       ec_node_free(node);
-       if (f != NULL)
-               fclose(f);
-       free(buf);
-
-       assert(errno != 0);
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_test = {
-       .name = "node",
-       .test = ec_node_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_test);
diff --git a/libecoli/ecoli_node.h b/libecoli/ecoli_node.h
deleted file mode 100644 (file)
index 45f3e74..0000000
+++ /dev/null
@@ -1,206 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Interface to manage the ecoli nodes.
- *
- * A node is a main structure of the ecoli library, used to define how
- * to match and complete the input tokens. A node is a generic object
- * that implements:
- * - a parse(node, input) method: check if an input matches
- * - a complete(node, input) method: return possible completions for
- *   a given input
- * - some other methods to initialize, free, ...
- *
- * One basic example is the string node (ec_node_str). A node
- * ec_node_str("foo") will match any token list starting with "foo",
- * for example:
- * - ["foo"]
- * - ["foo", "bar", ...]
- * But will not match:
- * - []
- * - ["bar", ...]
- *
- * A node ec_node_str("foo") will complete with "foo" if the input
- * contains one token, with the same beginning than "foo":
- * - [""]
- * - ["f"]
- * - ["fo"]
- * - ["foo"]
- * But it will not complete:
- * - []
- * - ["bar"]
- * - ["f", ""]
- * - ["", "f"]
- *
- * A node can have child nodes. For instance, a sequence node
- * ec_node_seq(ec_node_str("foo"), ec_node_str("bar")) will match
- * ["foo", "bar"].
- */
-
-#ifndef ECOLI_NODE_
-#define ECOLI_NODE_
-
-#include <sys/queue.h>
-#include <sys/types.h>
-#include <stdio.h>
-
-#define EC_NO_ID "no-id"
-
-#define EC_NODE_ENDLIST ((void *)1)
-
-struct ec_node;
-struct ec_parse;
-struct ec_comp;
-struct ec_strvec;
-struct ec_keyval;
-struct ec_config;
-struct ec_config_schema;
-
-#define EC_NODE_TYPE_REGISTER(t)                                       \
-       static void ec_node_init_##t(void);                             \
-       static void __attribute__((constructor, used))                  \
-       ec_node_init_##t(void)                                          \
-       {                                                               \
-               if (ec_node_type_register(&t) < 0)                      \
-                       fprintf(stderr,                                 \
-                               "cannot register node type %s\n",       \
-                               t.name);                                \
-       }
-
-TAILQ_HEAD(ec_node_type_list, ec_node_type);
-
-typedef int (*ec_node_set_config_t)(struct ec_node *node,
-                               const struct ec_config *config);
-typedef int (*ec_node_parse_t)(const struct ec_node *node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec);
-typedef int (*ec_node_complete_t)(const struct ec_node *node,
-                               struct ec_comp *comp_state,
-                               const struct ec_strvec *strvec);
-typedef const char * (*ec_node_desc_t)(const struct ec_node *);
-typedef int (*ec_node_init_priv_t)(struct ec_node *);
-typedef void (*ec_node_free_priv_t)(struct ec_node *);
-typedef size_t (*ec_node_get_children_count_t)(const struct ec_node *);
-typedef int (*ec_node_get_child_t)(const struct ec_node *,
-       size_t i, struct ec_node **child, unsigned int *refs);
-
-/**
- * A structure describing a node type.
- */
-struct ec_node_type {
-       TAILQ_ENTRY(ec_node_type) next;  /**< Next in list. */
-       const char *name;                /**< Node type name. */
-       /** Configuration schema array, must be terminated by a sentinel
-        *  (.type = EC_CONFIG_TYPE_NONE). */
-       const struct ec_config_schema *schema;
-       ec_node_set_config_t set_config; /* validate/ack a config change */
-       ec_node_parse_t parse;
-       ec_node_complete_t complete;
-       ec_node_desc_t desc;
-       size_t size;
-       ec_node_init_priv_t init_priv;
-       ec_node_free_priv_t free_priv;
-       ec_node_get_children_count_t get_children_count;
-       ec_node_get_child_t get_child;
-};
-
-/**
- * Register a node type.
- *
- * @param type
- *   A pointer to a ec_test structure describing the test
- *   to be registered.
- * @return
- *   0 on success, negative value on error.
- */
-int ec_node_type_register(struct ec_node_type *type);
-
-/**
- * Lookup node type by name
- *
- * @param name
- *   The name of the node type to search.
- * @return
- *   The node type if found, or NULL on error.
- */
-const struct ec_node_type *ec_node_type_lookup(const char *name);
-
-/**
- * Dump registered log types
- */
-void ec_node_type_dump(FILE *out);
-
-/**
- * Get the config schema of a node type.
- */
-const struct ec_config_schema *
-ec_node_type_schema(const struct ec_node_type *type);
-
-/**
- * Get the name of a node type.
- */
-const char *
-ec_node_type_name(const struct ec_node_type *type);
-
-enum ec_node_free_state {
-       EC_NODE_FREE_STATE_NONE,
-       EC_NODE_FREE_STATE_TRAVERSED,
-       EC_NODE_FREE_STATE_FREEABLE,
-       EC_NODE_FREE_STATE_NOT_FREEABLE,
-       EC_NODE_FREE_STATE_FREEING,
-};
-
-struct ec_node {
-       const struct ec_node_type *type;
-       struct ec_config *config;    /**< Generic configuration. */
-       char *id;
-       char *desc;
-       struct ec_keyval *attrs;
-       unsigned int refcnt;
-       struct {
-               enum ec_node_free_state state; /**< State of loop detection */
-               unsigned int refcnt;    /**< Number of reachable references
-                                        *   starting from node beeing freed */
-       } free; /**< Freeing state: used for loop detection */
-};
-
-/* create a new node when the type is known, typically called from the node
- * code */
-struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id);
-
-/* create a new node */
-struct ec_node *ec_node(const char *typename, const char *id);
-
-struct ec_node *ec_node_clone(struct ec_node *node);
-void ec_node_free(struct ec_node *node);
-
-/* set configuration of a node
- * after a call to this function, the config is
- * owned by the node and must not be used by the caller
- * on error, the config is freed. */
-int ec_node_set_config(struct ec_node *node, struct ec_config *config);
-
-/* get the current node configuration. Return NULL if no configuration. */
-const struct ec_config *ec_node_get_config(struct ec_node *node);
-
-size_t ec_node_get_children_count(const struct ec_node *node);
-int
-ec_node_get_child(const struct ec_node *node, size_t i,
-       struct ec_node **child, unsigned int *refs);
-
-/* XXX add more accessors */
-const struct ec_node_type *ec_node_type(const struct ec_node *node);
-struct ec_keyval *ec_node_attrs(const struct ec_node *node);
-const char *ec_node_id(const struct ec_node *node);
-const char *ec_node_desc(const struct ec_node *node);
-
-void ec_node_dump(FILE *out, const struct ec_node *node);
-struct ec_node *ec_node_find(struct ec_node *node, const char *id);
-
-/* check the type of a node */
-int ec_node_check_type(const struct ec_node *node,
-               const struct ec_node_type *type);
-
-#endif
diff --git a/libecoli/ecoli_node_any.c b/libecoli/ecoli_node_any.c
deleted file mode 100644 (file)
index 9166dbb..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_keyval.h>
-#include <ecoli_config.h>
-#include <ecoli_node_any.h>
-
-EC_LOG_TYPE_REGISTER(node_any);
-
-struct ec_node_any {
-       struct ec_node gen;
-       char *attr_name;
-};
-
-static int ec_node_any_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_any *node = (struct ec_node_any *)gen_node;
-       const struct ec_keyval *attrs;
-
-       (void)state;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-       if (node->attr_name != NULL) {
-               attrs = ec_strvec_get_attrs(strvec, 0);
-               if (attrs == NULL || !ec_keyval_has_key(attrs, node->attr_name))
-                       return EC_PARSE_NOMATCH;
-       }
-
-       return 1;
-}
-
-static void ec_node_any_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_any *node = (struct ec_node_any *)gen_node;
-
-       ec_free(node->attr_name);
-}
-
-static const struct ec_config_schema ec_node_any_schema[] = {
-       {
-               .key = "attr",
-               .desc = "The optional attribute name to attach.",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_any_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_any *node = (struct ec_node_any *)gen_node;
-       const struct ec_config *value = NULL;
-       char *s = NULL;
-
-       value = ec_config_dict_get(config, "attr");
-       if (value != NULL) {
-               s = ec_strdup(value->string);
-               if (s == NULL)
-                       goto fail;
-       }
-
-       ec_free(node->attr_name);
-       node->attr_name = s;
-
-       return 0;
-
-fail:
-       ec_free(s);
-       return -1;
-}
-
-static struct ec_node_type ec_node_any_type = {
-       .name = "any",
-       .schema = ec_node_any_schema,
-       .set_config = ec_node_any_set_config,
-       .parse = ec_node_any_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_any),
-       .free_priv = ec_node_any_free_priv,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_any_type);
-
-/* LCOV_EXCL_START */
-static int ec_node_any_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("any", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-       ec_node_free(node);
-
-       /* never completes */
-       node = ec_node("any", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_any_test = {
-       .name = "node_any",
-       .test = ec_node_any_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_any_test);
diff --git a/libecoli/ecoli_node_any.h b/libecoli/ecoli_node_any.h
deleted file mode 100644 (file)
index ee638aa..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * This node always matches 1 string in the vector
- */
-
-#ifndef ECOLI_NODE_ANY_
-#define ECOLI_NODE_ANY_
-
-/* no specific API for this node */
-
-#endif
diff --git a/libecoli/ecoli_node_cmd.c b/libecoli/ecoli_node_cmd.c
deleted file mode 100644 (file)
index 3b56084..0000000
+++ /dev/null
@@ -1,682 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/queue.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <limits.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_helper.h>
-#include <ecoli_node_expr.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_subset.h>
-#include <ecoli_node_int.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_node_option.h>
-#include <ecoli_node_re.h>
-#include <ecoli_node_re_lex.h>
-#include <ecoli_node_cmd.h>
-
-EC_LOG_TYPE_REGISTER(node_cmd);
-
-struct ec_node_cmd {
-       struct ec_node gen;
-       char *cmd_str;           /* the command string. */
-       struct ec_node *cmd;     /* the command node. */
-       struct ec_node *parser;  /* the expression parser. */
-       struct ec_node *expr;    /* the expression parser without lexer. */
-       struct ec_node **table;  /* table of node referenced in command. */
-       unsigned int len;        /* len of the table. */
-};
-
-/* passed as user context to expression parser */
-struct ec_node_cmd_ctx {
-       struct ec_node **table;
-       unsigned int len;
-};
-
-static int
-ec_node_cmd_eval_var(void **result, void *userctx,
-       const struct ec_parse *var)
-{
-       const struct ec_strvec *vec;
-       struct ec_node_cmd_ctx *ctx = userctx;
-       struct ec_node *eval = NULL;
-       const char *str, *id;
-       unsigned int i;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(var);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-       str = ec_strvec_val(vec, 0);
-
-       for (i = 0; i < ctx->len; i++) {
-               id = ec_node_id(ctx->table[i]);
-               if (id == NULL)
-                       continue;
-               if (strcmp(str, id))
-                       continue;
-               /* if id matches, use a node provided by the user... */
-               eval = ec_node_clone(ctx->table[i]);
-               if (eval == NULL)
-                       return -1;
-               break;
-       }
-
-       /* ...or create a string node */
-       if (eval == NULL) {
-               eval = ec_node_str(EC_NO_ID, str);
-               if (eval == NULL)
-                       return -1;
-       }
-
-       *result = eval;
-
-       return 0;
-}
-
-static int
-ec_node_cmd_eval_pre_op(void **result, void *userctx, void *operand,
-       const struct ec_parse *operator)
-{
-       (void)result;
-       (void)userctx;
-       (void)operand;
-       (void)operator;
-
-       errno = EINVAL;
-       return -1;
-}
-
-static int
-ec_node_cmd_eval_post_op(void **result, void *userctx, void *operand,
-       const struct ec_parse *operator)
-{
-       const struct ec_strvec *vec;
-       struct ec_node *in = operand;;
-       struct ec_node *out = NULL;;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "*")) {
-               out = ec_node_many(EC_NO_ID,
-                               ec_node_clone(in), 0, 0);
-               if (out == NULL)
-                       return -1;
-               ec_node_free(in);
-               *result = out;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       return 0;
-}
-
-static int
-ec_node_cmd_eval_bin_op(void **result, void *userctx, void *operand1,
-       const struct ec_parse *operator, void *operand2)
-
-{
-       const struct ec_strvec *vec;
-       struct ec_node *out = NULL;
-       struct ec_node *in1 = operand1;
-       struct ec_node *in2 = operand2;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) > 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (ec_strvec_len(vec) == 0) {
-               if (!strcmp(in1->type->name, "seq")) {
-                       if (ec_node_seq_add(in1, ec_node_clone(in2)) < 0)
-                               return -1;
-                       ec_node_free(in2);
-                       *result = in1;
-               } else {
-                       out = EC_NODE_SEQ(EC_NO_ID, ec_node_clone(in1),
-                                       ec_node_clone(in2));
-                       if (out == NULL)
-                               return -1;
-                       ec_node_free(in1);
-                       ec_node_free(in2);
-                       *result = out;
-               }
-       } else if (!strcmp(ec_strvec_val(vec, 0), "|")) {
-               if (!strcmp(in2->type->name, "or")) {
-                       if (ec_node_or_add(in2, ec_node_clone(in1)) < 0)
-                               return -1;
-                       ec_node_free(in1);
-                       *result = in2;
-               } else if (!strcmp(in1->type->name, "or")) {
-                       if (ec_node_or_add(in1, ec_node_clone(in2)) < 0)
-                               return -1;
-                       ec_node_free(in2);
-                       *result = in1;
-               } else {
-                       out = EC_NODE_OR(EC_NO_ID, ec_node_clone(in1),
-                                       ec_node_clone(in2));
-                       if (out == NULL)
-                               return -1;
-                       ec_node_free(in1);
-                       ec_node_free(in2);
-                       *result = out;
-               }
-       } else if (!strcmp(ec_strvec_val(vec, 0), ",")) {
-               if (!strcmp(in2->type->name, "subset")) {
-                       if (ec_node_subset_add(in2, ec_node_clone(in1)) < 0)
-                               return -1;
-                       ec_node_free(in1);
-                       *result = in2;
-               } else if (!strcmp(in1->type->name, "subset")) {
-                       if (ec_node_subset_add(in1, ec_node_clone(in2)) < 0)
-                               return -1;
-                       ec_node_free(in2);
-                       *result = in1;
-               } else {
-                       out = EC_NODE_SUBSET(EC_NO_ID, ec_node_clone(in1),
-                                       ec_node_clone(in2));
-                       if (out == NULL)
-                               return -1;
-                       ec_node_free(in1);
-                       ec_node_free(in2);
-                       *result = out;
-               }
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       return 0;
-}
-
-static int
-ec_node_cmd_eval_parenthesis(void **result, void *userctx,
-       const struct ec_parse *open_paren,
-       const struct ec_parse *close_paren,
-       void *value)
-{
-       const struct ec_strvec *vec;
-       struct ec_node *in = value;;
-       struct ec_node *out = NULL;;
-
-       (void)userctx;
-       (void)close_paren;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(open_paren);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "[")) {
-               out = ec_node_option(EC_NO_ID, ec_node_clone(in));
-               if (out == NULL)
-                       return -1;
-               ec_node_free(in);
-       } else if (!strcmp(ec_strvec_val(vec, 0), "(")) {
-               out = in;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       *result = out;
-
-       return 0;
-}
-
-static void
-ec_node_cmd_eval_free(void *result, void *userctx)
-{
-       (void)userctx;
-       ec_free(result);
-}
-
-static const struct ec_node_expr_eval_ops expr_ops = {
-       .eval_var = ec_node_cmd_eval_var,
-       .eval_pre_op = ec_node_cmd_eval_pre_op,
-       .eval_post_op = ec_node_cmd_eval_post_op,
-       .eval_bin_op = ec_node_cmd_eval_bin_op,
-       .eval_parenthesis = ec_node_cmd_eval_parenthesis,
-       .eval_free = ec_node_cmd_eval_free,
-};
-
-static struct ec_node *
-ec_node_cmd_build_expr(void)
-{
-       struct ec_node *expr = NULL;
-       int ret;
-
-       /* build the expression parser */
-       expr = ec_node("expr", "expr");
-       if (expr == NULL)
-               goto fail;
-       ret = ec_node_expr_set_val_node(expr, ec_node_re(EC_NO_ID,
-                                       "[a-zA-Z0-9]+"));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, ","));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, "|"));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_bin_op(expr, ec_node("empty", EC_NO_ID));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "+"));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "*"));
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "["),
-               ec_node_str(EC_NO_ID, "]"));
-       if (ret < 0)
-               goto fail;
-       ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "("),
-               ec_node_str(EC_NO_ID, ")"));
-       if (ret < 0)
-               goto fail;
-
-       return expr;
-
-fail:
-       ec_node_free(expr);
-       return NULL;
-}
-
-static struct ec_node *
-ec_node_cmd_build_parser(struct ec_node *expr)
-{
-       struct ec_node *lex = NULL;
-       int ret;
-
-       /* prepend a lexer to the expression node */
-       lex = ec_node_re_lex(EC_NO_ID, ec_node_clone(expr));
-       if (lex == NULL)
-               goto fail;
-
-       ret = ec_node_re_lex_add(lex, "[a-zA-Z0-9]+", 1, NULL);
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_re_lex_add(lex, "[*|,()]", 1, NULL);
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_re_lex_add(lex, "\\[", 1, NULL);
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_re_lex_add(lex, "\\]", 1, NULL);
-       if (ret < 0)
-               goto fail;
-       ret = ec_node_re_lex_add(lex, "[         ]+", 0, NULL);
-       if (ret < 0)
-               goto fail;
-
-       return lex;
-
-fail:
-       ec_node_free(lex);
-
-       return NULL;
-}
-
-static struct ec_node *
-ec_node_cmd_build(struct ec_node_cmd *node, const char *cmd_str,
-       struct ec_node **table, size_t len)
-{
-       struct ec_node_cmd_ctx ctx = { table, len };
-       struct ec_parse *p = NULL;
-       void *result;
-       int ret;
-
-       /* parse the command expression */
-       p = ec_node_parse(node->parser, cmd_str);
-       if (p == NULL)
-               goto fail;
-
-       if (!ec_parse_matches(p)) {
-               errno = EINVAL;
-               goto fail;
-       }
-       if (!ec_parse_has_child(p)) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       ret = ec_node_expr_eval(&result, node->expr,
-                               ec_parse_get_first_child(p),
-                               &expr_ops, &ctx);
-       if (ret < 0)
-               goto fail;
-
-       ec_parse_free(p);
-       return result;
-
-fail:
-       ec_parse_free(p);
-       return NULL;
-}
-
-static int
-ec_node_cmd_parse(const struct ec_node *gen_node, struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-
-       return ec_node_parse_child(node->cmd, state, strvec);
-}
-
-static int
-ec_node_cmd_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-
-       return ec_node_complete_child(node->cmd, comp, strvec);
-}
-
-static void ec_node_cmd_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-       size_t i;
-
-       ec_free(node->cmd_str);
-       node->cmd_str = NULL;
-       ec_node_free(node->expr);
-       node->expr = NULL;
-       ec_node_free(node->parser);
-       node->parser = NULL;
-       ec_node_free(node->cmd);
-       node->cmd = NULL;
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = NULL;
-       node->len = 0;
-}
-
-static const struct ec_config_schema ec_node_cmd_subschema[] = {
-       {
-               .desc = "A child node whose id is referenced in the expression.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema ec_node_cmd_schema[] = {
-       {
-               .key = "expr",
-               .desc = "The expression to match. Supported operators "
-               "are or '|', list ',', many '+', many-or-zero '*', "
-               "option '[]', group '()'. An identifier (alphanumeric) can "
-               "reference a node whose node_id matches. Else it is "
-               "interpreted as ec_node_str() matching this string. "
-               "Example: command [option] (subset1, subset2) x|y",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .key = "children",
-               .desc = "The list of children nodes.",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = ec_node_cmd_subschema,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_cmd_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-       const struct ec_config *expr = NULL;
-       struct ec_node *cmd = NULL;
-       struct ec_node **table = NULL;
-       char *cmd_str = NULL;
-       size_t len = 0, i;
-
-       /* retrieve config locally */
-       expr = ec_config_dict_get(config, "expr");
-       if (expr == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       table = ec_node_config_node_list_to_table(
-               ec_config_dict_get(config, "children"), &len);
-       if (table == NULL)
-               goto fail;
-
-       cmd_str = ec_strdup(expr->string);
-       if (cmd_str == NULL)
-               goto fail;
-
-       /* parse expression to build the cmd child node */
-       cmd = ec_node_cmd_build(node, cmd_str, table, len);
-       if (cmd == NULL)
-               goto fail;
-
-       /* ok, store the config */
-       ec_node_free(node->cmd);
-       node->cmd = cmd;
-       ec_free(node->cmd_str);
-       node->cmd_str = cmd_str;
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = table;
-       node->len = len;
-
-       return 0;
-
-fail:
-       for (i = 0; i < len; i++)
-               ec_node_free(table[i]);
-       ec_free(table);
-       ec_free(cmd_str);
-       ec_node_free(cmd);
-       return -1;
-}
-
-static size_t
-ec_node_cmd_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-
-       if (node->cmd == NULL)
-               return 0;
-       return 1;
-}
-
-static int
-ec_node_cmd_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
-
-       if (i > 0)
-               return -1;
-
-       *child = node->cmd;
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_cmd_type = {
-       .name = "cmd",
-       .schema = ec_node_cmd_schema,
-       .set_config = ec_node_cmd_set_config,
-       .parse = ec_node_cmd_parse,
-       .complete = ec_node_cmd_complete,
-       .size = sizeof(struct ec_node_cmd),
-       .free_priv = ec_node_cmd_free_priv,
-       .get_children_count = ec_node_cmd_get_children_count,
-       .get_child = ec_node_cmd_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_cmd_type);
-
-struct ec_node *__ec_node_cmd(const char *id, const char *cmd, ...)
-{
-       struct ec_config *config = NULL, *children = NULL;
-       struct ec_node *gen_node = NULL;
-       struct ec_node_cmd *node = NULL;
-       va_list ap;
-       int ret;
-
-       /* this block must stay first, it frees the nodes on error */
-       va_start(ap, cmd);
-       children = ec_node_config_node_list_from_vargs(ap);
-       va_end(ap);
-       if (children == NULL)
-               goto fail;
-
-       gen_node = ec_node_from_type(&ec_node_cmd_type, id);
-       if (gen_node == NULL)
-               goto fail;
-       node = (struct ec_node_cmd *)gen_node;
-
-       node->expr = ec_node_cmd_build_expr();
-       if (node->expr == NULL)
-               goto fail;
-
-       node->parser = ec_node_cmd_build_parser(node->expr);
-       if (node->parser == NULL)
-               goto fail;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       if (ec_config_dict_set(config, "expr", ec_config_string(cmd)) < 0)
-               goto fail;
-
-       if (ec_config_dict_set(config, "children", children) < 0) {
-               children = NULL; /* freed */
-               goto fail;
-       }
-       children = NULL;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return gen_node;
-
-fail:
-       ec_node_free(gen_node); /* will also free added children */
-       ec_config_free(children);
-       ec_config_free(config);
-
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_cmd_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = EC_NODE_CMD(EC_NO_ID,
-               "command [option] (subset1, subset2, subset3, subset4) x|y z*",
-               ec_node_int("x", 0, 10, 10),
-               ec_node_int("y", 20, 30, 10)
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "subset1", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 4, "command", "subset3", "subset2",
-                               "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "subset2", "subset3",
-                               "subset1", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 6, "command", "subset3", "subset1",
-                               "subset4", "subset2", "4");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "23");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "option", "23");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "option", "23",
-                               "z", "z");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "command", "15");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       ec_node_free(node);
-
-       node = EC_NODE_CMD(EC_NO_ID, "good morning [count] bob|bobby|michael",
-                       ec_node_int("count", 0, 10, 10));
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 4, "good", "morning", "1", "bob");
-
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "good", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "g", EC_NODE_ENDLIST,
-               "good", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "good", "morning", "", EC_NODE_ENDLIST,
-               "bob", "bobby", "michael", EC_NODE_ENDLIST);
-
-       ec_node_free(node);
-
-       node = EC_NODE_CMD(EC_NO_ID, "[foo [bar]]");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "x");
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_cmd_test = {
-       .name = "node_cmd",
-       .test = ec_node_cmd_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_cmd_test);
diff --git a/libecoli/ecoli_node_cmd.h b/libecoli/ecoli_node_cmd.h
deleted file mode 100644 (file)
index 99afc01..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_CMD_
-#define ECOLI_NODE_CMD_
-
-#include <ecoli_node.h>
-
-#define EC_NODE_CMD(args...) __ec_node_cmd(args, EC_NODE_ENDLIST)
-
-struct ec_node *__ec_node_cmd(const char *id, const char *cmd_str, ...);
-
-#endif
diff --git a/libecoli/ecoli_node_dynamic.c b/libecoli/ecoli_node_dynamic.c
deleted file mode 100644 (file)
index 8a3edf3..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2017, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_keyval.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_string.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_many.h>
-
-#include "ecoli_node_dynamic.h"
-
-EC_LOG_TYPE_REGISTER(node_dynamic);
-
-struct ec_node_dynamic {
-       struct ec_node gen;
-       ec_node_dynamic_build_t build;
-       void *opaque;
-};
-
-static int
-ec_node_dynamic_parse(const struct ec_node *gen_node,
-               struct ec_parse *parse,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node;
-       struct ec_node *child = NULL;
-       void (*node_free)(struct ec_node *) = ec_node_free;
-       char key[64];
-       int ret = -1;
-
-       child = node->build(parse, node->opaque);
-       if (child == NULL)
-               goto fail;
-
-       /* add the node pointer in the attributes, so it will be freed
-        * when parse is freed */
-       snprintf(key, sizeof(key), "_dyn_%p", child);
-       ret = ec_keyval_set(ec_parse_get_attrs(parse), key, child,
-                       (void *)node_free);
-       if (ret < 0) {
-               child = NULL; /* already freed */
-               goto fail;
-       }
-
-       return ec_node_parse_child(child, parse, strvec);
-
-fail:
-       ec_node_free(child);
-       return ret;
-}
-
-static int
-ec_node_dynamic_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node;
-       struct ec_parse *parse;
-       struct ec_node *child = NULL;
-       void (*node_free)(struct ec_node *) = ec_node_free;
-       char key[64];
-       int ret = -1;
-
-       parse = ec_comp_get_state(comp);
-       child = node->build(parse, node->opaque);
-       if (child == NULL)
-               goto fail;
-
-       /* add the node pointer in the attributes, so it will be freed
-        * when parse is freed */
-       snprintf(key, sizeof(key), "_dyn_%p", child);
-       ret = ec_keyval_set(comp->attrs, key, child,
-                       (void *)node_free);
-       if (ret < 0) {
-               child = NULL; /* already freed */
-               goto fail;
-       }
-
-       return ec_node_complete_child(child, comp, strvec);
-
-fail:
-       ec_node_free(child);
-       return ret;
-}
-
-static struct ec_node_type ec_node_dynamic_type = {
-       .name = "dynamic",
-       .parse = ec_node_dynamic_parse,
-       .complete = ec_node_dynamic_complete,
-       .size = sizeof(struct ec_node_dynamic),
-};
-
-struct ec_node *
-ec_node_dynamic(const char *id, ec_node_dynamic_build_t build, void *opaque)
-{
-       struct ec_node *gen_node = NULL;
-       struct ec_node_dynamic *node;
-
-       if (build == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       gen_node = ec_node_from_type(&ec_node_dynamic_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       node = (struct ec_node_dynamic *)gen_node;
-       node->build = build;
-       node->opaque = opaque;
-
-       return gen_node;
-
-fail:
-       ec_node_free(gen_node);
-       return NULL;
-
-}
-
-EC_NODE_TYPE_REGISTER(ec_node_dynamic_type);
-
-static struct ec_node *
-build_counter(struct ec_parse *parse, void *opaque)
-{
-       const struct ec_node *node;
-       struct ec_parse *iter;
-       unsigned int count = 0;
-       char buf[32];
-
-       (void)opaque;
-       for (iter = ec_parse_get_root(parse); iter != NULL;
-            iter = ec_parse_iter_next(iter)) {
-               node = ec_parse_get_node(iter);
-               if (node->id && !strcmp(node->id, "my-id"))
-                       count++;
-       }
-       snprintf(buf, sizeof(buf), "count-%u", count);
-
-       return ec_node_str("my-id", buf);
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_dynamic_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_many(EC_NO_ID,
-                       ec_node_dynamic(EC_NO_ID, build_counter, NULL),
-                       1, 3);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "count-0", "count-1", "count-2");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0", "count-0");
-
-       /* test completion */
-
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "c", EC_NODE_ENDLIST,
-               "count-0", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "count-0", "", EC_NODE_ENDLIST,
-               "count-1", EC_NODE_ENDLIST,
-               "count-1");
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_dynamic_test = {
-       .name = "node_dynamic",
-       .test = ec_node_dynamic_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_dynamic_test);
diff --git a/libecoli/ecoli_node_dynamic.h b/libecoli/ecoli_node_dynamic.h
deleted file mode 100644 (file)
index 4f2535e..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2017, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_DYNAMIC_
-#define ECOLI_NODE_DYNAMIC_
-
-struct ec_node;
-struct ec_parse;
-
-/* callback invoked by parse() or complete() to build the dynamic node
- * the behavior of the node can depend on what is already parsed */
-typedef struct ec_node *(*ec_node_dynamic_build_t)(
-       struct ec_parse *state, void *opaque);
-
-struct ec_node *ec_node_dynamic(const char *id, ec_node_dynamic_build_t build,
-                               void *opaque);
-
-#endif
diff --git a/libecoli/ecoli_node_empty.c b/libecoli/ecoli_node_empty.c
deleted file mode 100644 (file)
index 6ce76e8..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_empty.h>
-
-EC_LOG_TYPE_REGISTER(node_empty);
-
-struct ec_node_empty {
-       struct ec_node gen;
-};
-
-static int ec_node_empty_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       (void)gen_node;
-       (void)state;
-       (void)strvec;
-       return 0;
-}
-
-static struct ec_node_type ec_node_empty_type = {
-       .name = "empty",
-       .parse = ec_node_empty_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_empty),
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_empty_type);
-
-/* LCOV_EXCL_START */
-static int ec_node_empty_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("empty", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "foo", "bar");
-       ec_node_free(node);
-
-       /* never completes */
-       node = ec_node("empty", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_empty_test = {
-       .name = "node_empty",
-       .test = ec_node_empty_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_empty_test);
diff --git a/libecoli/ecoli_node_empty.h b/libecoli/ecoli_node_empty.h
deleted file mode 100644 (file)
index ed5e32e..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * This node always matches an empty string vector
- */
-
-#ifndef ECOLI_NODE_EMPTY_
-#define ECOLI_NODE_EMPTY_
-
-struct ec_node *ec_node_empty(const char *id);
-
-#endif
diff --git a/libecoli/ecoli_node_expr.c b/libecoli/ecoli_node_expr.c
deleted file mode 100644 (file)
index 3b47d8c..0000000
+++ /dev/null
@@ -1,617 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_expr.h>
-
-EC_LOG_TYPE_REGISTER(node_expr);
-
-struct ec_node_expr {
-       struct ec_node gen;
-
-       /* the built node */
-       struct ec_node *child;
-
-       /* the configuration nodes */
-       struct ec_node *val_node;
-       struct ec_node **bin_ops;
-       unsigned int bin_ops_len;
-       struct ec_node **pre_ops;
-       unsigned int pre_ops_len;
-       struct ec_node **post_ops;
-       unsigned int post_ops_len;
-       struct ec_node **open_ops;
-       struct ec_node **close_ops;
-       unsigned int paren_len;
-};
-
-static int ec_node_expr_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (node->child == NULL) {
-               errno = ENOENT;
-               return -1;
-       }
-
-       return ec_node_parse_child(node->child, state, strvec);
-}
-
-static int
-ec_node_expr_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (node->child == NULL) {
-               errno = ENOENT;
-               return -1;
-       }
-
-       return ec_node_complete_child(node->child, comp, strvec);
-}
-
-static void ec_node_expr_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       unsigned int i;
-
-       ec_node_free(node->child);
-       ec_node_free(node->val_node);
-
-       for (i = 0; i < node->bin_ops_len; i++)
-               ec_node_free(node->bin_ops[i]);
-       ec_free(node->bin_ops);
-       for (i = 0; i < node->pre_ops_len; i++)
-               ec_node_free(node->pre_ops[i]);
-       ec_free(node->pre_ops);
-       for (i = 0; i < node->post_ops_len; i++)
-               ec_node_free(node->post_ops[i]);
-       ec_free(node->post_ops);
-       for (i = 0; i < node->paren_len; i++) {
-               ec_node_free(node->open_ops[i]);
-               ec_node_free(node->close_ops[i]);
-       }
-       ec_free(node->open_ops);
-       ec_free(node->close_ops);
-}
-
-static int ec_node_expr_build(struct ec_node_expr *node)
-{
-       struct ec_node *term = NULL, *expr = NULL, *next = NULL,
-               *pre_op = NULL, *post_op = NULL, *ref = NULL,
-               *post = NULL;
-       unsigned int i;
-
-       ec_node_free(node->child);
-       node->child = NULL;
-
-       if (node->val_node == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (node->bin_ops_len == 0 && node->pre_ops_len == 0 &&
-                       node->post_ops_len == 0) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       /*
-        * Example of created grammar:
-        *
-        * pre_op = "!"
-        * post_op = "^"
-        * post = val |
-        *        pre_op expr |
-        *        "(" expr ")"
-        * term = post post_op*
-        * prod = term ( "*" term )*
-        * sum = prod ( "+" prod )*
-        * expr = sum
-        */
-
-       /* we use this as a ref, will be set later */
-       ref = ec_node("seq", "ref");
-       if (ref == NULL)
-               return -1;
-
-       /* prefix unary operators */
-       pre_op = ec_node("or", "pre-op");
-       if (pre_op == NULL)
-               goto fail;
-       for (i = 0; i < node->pre_ops_len; i++) {
-               if (ec_node_or_add(pre_op, ec_node_clone(node->pre_ops[i])) < 0)
-                       goto fail;
-       }
-
-       /* suffix unary operators */
-       post_op = ec_node("or", "post-op");
-       if (post_op == NULL)
-               goto fail;
-       for (i = 0; i < node->post_ops_len; i++) {
-               if (ec_node_or_add(post_op, ec_node_clone(node->post_ops[i])) < 0)
-                       goto fail;
-       }
-
-       post = ec_node("or", "post");
-       if (post == NULL)
-               goto fail;
-       if (ec_node_or_add(post, ec_node_clone(node->val_node)) < 0)
-               goto fail;
-       if (ec_node_or_add(post,
-               EC_NODE_SEQ(EC_NO_ID,
-                       ec_node_clone(pre_op),
-                       ec_node_clone(ref))) < 0)
-               goto fail;
-       for (i = 0; i < node->paren_len; i++) {
-               if (ec_node_or_add(post, EC_NODE_SEQ(EC_NO_ID,
-                                       ec_node_clone(node->open_ops[i]),
-                                       ec_node_clone(ref),
-                                       ec_node_clone(node->close_ops[i]))) < 0)
-                       goto fail;
-       }
-       term = EC_NODE_SEQ("term",
-               ec_node_clone(post),
-               ec_node_many(EC_NO_ID, ec_node_clone(post_op), 0, 0)
-       );
-       if (term == NULL)
-               goto fail;
-
-       for (i = 0; i < node->bin_ops_len; i++) {
-               next = EC_NODE_SEQ("next",
-                       ec_node_clone(term),
-                       ec_node_many(EC_NO_ID,
-                               EC_NODE_SEQ(EC_NO_ID,
-                                       ec_node_clone(node->bin_ops[i]),
-                                       ec_node_clone(term)
-                               ),
-                               0, 0
-                       )
-               );
-               ec_node_free(term);
-               term = next;
-               if (term == NULL)
-                       goto fail;
-       }
-       expr = term;
-       term = NULL;
-
-       /* free the initial references */
-       ec_node_free(pre_op);
-       pre_op = NULL;
-       ec_node_free(post_op);
-       post_op = NULL;
-       ec_node_free(post);
-       post = NULL;
-
-       if (ec_node_seq_add(ref, ec_node_clone(expr)) < 0)
-               goto fail;
-       ec_node_free(ref);
-       ref = NULL;
-
-       node->child = expr;
-
-       return 0;
-
-fail:
-       ec_node_free(term);
-       ec_node_free(expr);
-       ec_node_free(pre_op);
-       ec_node_free(post_op);
-       ec_node_free(post);
-       ec_node_free(ref);
-
-       return -1;
-}
-
-static size_t
-ec_node_expr_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_expr_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_expr_type = {
-       .name = "expr",
-       .parse = ec_node_expr_parse,
-       .complete = ec_node_expr_complete,
-       .size = sizeof(struct ec_node_expr),
-       .free_priv = ec_node_expr_free_priv,
-       .get_children_count = ec_node_expr_get_children_count,
-       .get_child = ec_node_expr_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_expr_type);
-
-int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (val_node == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       ec_node_free(node->val_node);
-       node->val_node = val_node;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(val_node);
-       return -1;
-}
-
-/* add a binary operator */
-int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       struct ec_node **bin_ops;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (node == NULL || op == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       bin_ops = ec_realloc(node->bin_ops,
-               (node->bin_ops_len + 1) * sizeof(*node->bin_ops));
-       if (bin_ops == NULL)
-               goto fail;;
-
-       node->bin_ops = bin_ops;
-       bin_ops[node->bin_ops_len] = op;
-       node->bin_ops_len++;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(op);
-       return -1;
-}
-
-/* add a unary pre-operator */
-int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       struct ec_node **pre_ops;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (node == NULL || op == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       pre_ops = ec_realloc(node->pre_ops,
-               (node->pre_ops_len + 1) * sizeof(*node->pre_ops));
-       if (pre_ops == NULL)
-               goto fail;
-
-       node->pre_ops = pre_ops;
-       pre_ops[node->pre_ops_len] = op;
-       node->pre_ops_len++;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(op);
-       return -1;
-}
-
-/* add a unary post-operator */
-int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       struct ec_node **post_ops;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (node == NULL || op == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       post_ops = ec_realloc(node->post_ops,
-               (node->post_ops_len + 1) * sizeof(*node->post_ops));
-       if (post_ops == NULL)
-               goto fail;
-
-       node->post_ops = post_ops;
-       post_ops[node->post_ops_len] = op;
-       node->post_ops_len++;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(op);
-       return -1;
-}
-
-/* add parenthesis symbols */
-int ec_node_expr_add_parenthesis(struct ec_node *gen_node,
-       struct ec_node *open, struct ec_node *close)
-{
-       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
-       struct ec_node **open_ops, **close_ops;
-
-       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
-               goto fail;
-
-       if (node == NULL || open == NULL || close == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       open_ops = ec_realloc(node->open_ops,
-               (node->paren_len + 1) * sizeof(*node->open_ops));
-       if (open_ops == NULL)
-               goto fail;
-       close_ops = ec_realloc(node->close_ops,
-               (node->paren_len + 1) * sizeof(*node->close_ops));
-       if (close_ops == NULL)
-               goto fail;
-
-       node->open_ops = open_ops;
-       node->close_ops = close_ops;
-       open_ops[node->paren_len] = open;
-       close_ops[node->paren_len] = close;
-       node->paren_len++;
-       ec_node_expr_build(node);
-
-       return 0;
-
-fail:
-       ec_node_free(open);
-       ec_node_free(close);
-       return -1;
-}
-
-enum expr_node_type {
-       NONE,
-       VAL,
-       BIN_OP,
-       PRE_OP,
-       POST_OP,
-       PAREN_OPEN,
-       PAREN_CLOSE,
-};
-
-static enum expr_node_type get_node_type(const struct ec_node *expr_gen_node,
-       const struct ec_node *check)
-{
-       struct ec_node_expr *expr_node = (struct ec_node_expr *)expr_gen_node;
-       size_t i;
-
-       if (check == expr_node->val_node)
-               return VAL;
-
-       for (i = 0; i < expr_node->bin_ops_len; i++) {
-               if (check == expr_node->bin_ops[i])
-                       return BIN_OP;
-       }
-       for (i = 0; i < expr_node->pre_ops_len; i++) {
-               if (check == expr_node->pre_ops[i])
-                       return PRE_OP;
-       }
-       for (i = 0; i < expr_node->post_ops_len; i++) {
-               if (check == expr_node->post_ops[i])
-                       return POST_OP;
-       }
-
-       for (i = 0; i < expr_node->paren_len; i++) {
-               if (check == expr_node->open_ops[i])
-                       return PAREN_OPEN;
-       }
-       for (i = 0; i < expr_node->paren_len; i++) {
-               if (check == expr_node->close_ops[i])
-                       return PAREN_CLOSE;
-       }
-
-       return NONE;
-}
-
-struct result {
-       bool has_val;
-       void *val;
-       const struct ec_parse *op;
-       enum expr_node_type op_type;
-};
-
-/* merge x and y results in x */
-static int merge_results(void *userctx,
-       const struct ec_node_expr_eval_ops *ops,
-       struct result *x, const struct result *y)
-{
-       if (y->has_val == 0 && y->op == NULL)
-               return 0;
-       if (x->has_val == 0 && x->op == NULL) {
-               *x = *y;
-               return 0;
-       }
-
-       if (x->has_val && y->has_val && y->op != NULL) {
-               if (y->op_type == BIN_OP) {
-                       if (ops->eval_bin_op(&x->val, userctx, x->val,
-                                       y->op, y->val) < 0)
-                               return -1;
-
-                       return 0;
-               }
-       }
-
-       if (x->has_val == 0 && x->op != NULL && y->has_val && y->op == NULL) {
-               if (x->op_type == PRE_OP) {
-                       if (ops->eval_pre_op(&x->val, userctx, y->val,
-                                               x->op) < 0)
-                               return -1;
-                       x->has_val = true;
-                       x->op_type = NONE;
-                       x->op = NULL;
-                       return 0;
-               } else if (x->op_type == BIN_OP) {
-                       x->val = y->val;
-                       x->has_val = true;
-                       return 0;
-               }
-       }
-
-       if (x->has_val && x->op == NULL && y->has_val == 0 && y->op != NULL) {
-               if (ops->eval_post_op(&x->val, userctx, x->val, y->op) < 0)
-                       return -1;
-
-               return 0;
-       }
-
-       assert(false); /* we should not get here */
-       return -1;
-}
-
-static int eval_expression(struct result *result,
-       void *userctx,
-       const struct ec_node_expr_eval_ops *ops,
-       const struct ec_node *expr_gen_node,
-       const struct ec_parse *parse)
-
-{
-       struct ec_parse *open = NULL, *close = NULL;
-       struct result child_result;
-       struct ec_parse *child;
-       enum expr_node_type type;
-
-       memset(result, 0, sizeof(*result));
-       memset(&child_result, 0, sizeof(child_result));
-
-       type = get_node_type(expr_gen_node, ec_parse_get_node(parse));
-       if (type == VAL) {
-               if (ops->eval_var(&result->val, userctx, parse) < 0)
-                       goto fail;
-               result->has_val = 1;
-       } else if (type == PRE_OP || type == POST_OP || type == BIN_OP) {
-               result->op = parse;
-               result->op_type = type;
-       }
-
-       EC_PARSE_FOREACH_CHILD(child, parse) {
-
-               type = get_node_type(expr_gen_node, ec_parse_get_node(child));
-               if (type == PAREN_OPEN) {
-                       open = child;
-                       continue;
-               } else if (type == PAREN_CLOSE) {
-                       close = child;
-                       continue;
-               }
-
-               if (eval_expression(&child_result, userctx, ops,
-                       expr_gen_node, child) < 0)
-                       goto fail;
-
-               if (merge_results(userctx, ops, result, &child_result) < 0)
-                       goto fail;
-
-               memset(&child_result, 0, sizeof(child_result));
-       }
-
-       if (open != NULL && close != NULL) {
-               if (ops->eval_parenthesis(&result->val, userctx, open, close,
-                       result->val) < 0)
-                       goto fail;
-       }
-
-       return 0;
-
-fail:
-       if (result->has_val)
-               ops->eval_free(result->val, userctx);
-       if (child_result.has_val)
-               ops->eval_free(child_result.val, userctx);
-       memset(result, 0, sizeof(*result));
-
-       return -1;
-}
-
-int ec_node_expr_eval(void **user_result, const struct ec_node *node,
-       struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops,
-       void *userctx)
-{
-       struct result result;
-
-       if (ops == NULL || ops->eval_var == NULL || ops->eval_pre_op == NULL ||
-                       ops->eval_post_op == NULL || ops->eval_bin_op == NULL ||
-                       ops->eval_parenthesis == NULL ||
-                       ops->eval_free == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (ec_node_check_type(node, &ec_node_expr_type) < 0)
-               return -1;
-
-       if (!ec_parse_matches(parse)) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (eval_expression(&result, userctx, ops, node, parse) < 0)
-               return -1;
-
-       assert(result.has_val);
-       assert(result.op == NULL);
-       *user_result = result.val;
-
-       return 0;
-}
-
-/* the test case is in a separate file ecoli_node_expr_test.c */
diff --git a/libecoli/ecoli_node_expr.h b/libecoli/ecoli_node_expr.h
deleted file mode 100644 (file)
index 4f21d81..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_EXPR_
-#define ECOLI_NODE_EXPR_
-
-#include <ecoli_node.h>
-
-/**
- * Callback function type for evaluating a variable
- *
- * @param result
- *   On success, this pointer must be set by the user to point
- *   to a user structure describing the evaluated result.
- * @param userctx
- *   A user-defined context passed to all callback functions, which
- *   can be used to maintain a state or store global information.
- * @param var
- *   The parse result referencing the variable.
- * @return
- *   0 on success (*result must be set), or -errno on error (*result
- *   is undefined).
- */
-typedef int (*ec_node_expr_eval_var_t)(
-       void **result, void *userctx,
-       const struct ec_parse *var);
-
-/**
- * Callback function type for evaluating a prefix-operator
- *
- * @param result
- *   On success, this pointer must be set by the user to point
- *   to a user structure describing the evaluated result.
- * @param userctx
- *   A user-defined context passed to all callback functions, which
- *   can be used to maintain a state or store global information.
- * @param operand
- *   The evaluated expression on which the operation should be applied.
- * @param var
- *   The parse result referencing the operator.
- * @return
- *   0 on success (*result must be set, operand is freed),
- *   or -errno on error (*result is undefined, operand is not freed).
- */
-typedef int (*ec_node_expr_eval_pre_op_t)(
-       void **result, void *userctx,
-       void *operand,
-       const struct ec_parse *operator);
-
-typedef int (*ec_node_expr_eval_post_op_t)(
-       void **result, void *userctx,
-       void *operand,
-       const struct ec_parse *operator);
-
-typedef int (*ec_node_expr_eval_bin_op_t)(
-       void **result, void *userctx,
-       void *operand1,
-       const struct ec_parse *operator,
-       void *operand2);
-
-typedef int (*ec_node_expr_eval_parenthesis_t)(
-       void **result, void *userctx,
-       const struct ec_parse *open_paren,
-       const struct ec_parse *close_paren,
-       void * value);
-
-typedef void (*ec_node_expr_eval_free_t)(
-       void *result, void *userctx);
-
-
-struct ec_node *ec_node_expr(const char *id);
-int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node);
-int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op);
-int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op);
-int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op);
-int ec_node_expr_add_parenthesis(struct ec_node *gen_node,
-       struct ec_node *open, struct ec_node *close);
-
-struct ec_node_expr_eval_ops {
-       ec_node_expr_eval_var_t eval_var;
-       ec_node_expr_eval_pre_op_t eval_pre_op;
-       ec_node_expr_eval_post_op_t eval_post_op;
-       ec_node_expr_eval_bin_op_t eval_bin_op;
-       ec_node_expr_eval_parenthesis_t eval_parenthesis;
-       ec_node_expr_eval_free_t eval_free;
-};
-
-int ec_node_expr_eval(void **result, const struct ec_node *node,
-       struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops,
-       void *userctx);
-
-#endif
diff --git a/libecoli/ecoli_node_expr_test.c b/libecoli/ecoli_node_expr_test.c
deleted file mode 100644 (file)
index e3a0c79..0000000
+++ /dev/null
@@ -1,308 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <errno.h>
-#include <limits.h>
-#include <stdint.h>
-#include <assert.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_strvec.h>
-#include <ecoli_test.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_node_int.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_re_lex.h>
-#include <ecoli_node_expr.h>
-
-EC_LOG_TYPE_REGISTER(node_expr);
-
-struct my_eval_result {
-       int val;
-};
-
-static int
-ec_node_expr_test_eval_var(void **result, void *userctx,
-       const struct ec_parse *var)
-{
-       const struct ec_strvec *vec;
-       const struct ec_node *node;
-       struct my_eval_result *eval = NULL;
-       int64_t val;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(var);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       node = ec_parse_get_node(var);
-       if (ec_node_int_getval(node, ec_strvec_val(vec, 0), &val) < 0)
-               return -1;
-
-       eval = ec_malloc(sizeof(*eval));
-       if (eval == NULL)
-               return -1;
-
-       eval->val = val;
-       EC_LOG(EC_LOG_DEBUG, "eval var %d\n", eval->val);
-       *result = eval;
-
-       return 0;
-}
-
-static int
-ec_node_expr_test_eval_pre_op(void **result, void *userctx, void *operand,
-       const struct ec_parse *operator)
-{
-       const struct ec_strvec *vec;
-       struct my_eval_result *eval = operand;;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "!")) {
-               eval->val = !eval->val;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-
-       EC_LOG(EC_LOG_DEBUG, "eval pre_op %d\n", eval->val);
-       *result = eval;
-
-       return 0;
-}
-
-static int
-ec_node_expr_test_eval_post_op(void **result, void *userctx, void *operand,
-       const struct ec_parse *operator)
-{
-       const struct ec_strvec *vec;
-       struct my_eval_result *eval = operand;;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "^")) {
-               eval->val = eval->val * eval->val;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       EC_LOG(EC_LOG_DEBUG, "eval post_op %d\n", eval->val);
-       *result = eval;
-
-       return 0;
-}
-
-static int
-ec_node_expr_test_eval_bin_op(void **result, void *userctx, void *operand1,
-       const struct ec_parse *operator, void *operand2)
-
-{
-       const struct ec_strvec *vec;
-       struct my_eval_result *eval1 = operand1;;
-       struct my_eval_result *eval2 = operand2;;
-
-       (void)userctx;
-
-       /* get parsed string vector, it should contain only one str */
-       vec = ec_parse_strvec(operator);
-       if (ec_strvec_len(vec) != 1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (!strcmp(ec_strvec_val(vec, 0), "+")) {
-               eval1->val = eval1->val + eval2->val;
-       } else if (!strcmp(ec_strvec_val(vec, 0), "*")) {
-               eval1->val = eval1->val * eval2->val;
-       } else {
-               errno = EINVAL;
-               return -1;
-       }
-
-       EC_LOG(EC_LOG_DEBUG, "eval bin_op %d\n", eval1->val);
-       ec_free(eval2);
-       *result = eval1;
-
-       return 0;
-}
-
-static int
-ec_node_expr_test_eval_parenthesis(void **result, void *userctx,
-       const struct ec_parse *open_paren,
-       const struct ec_parse *close_paren,
-       void *value)
-{
-       (void)userctx;
-       (void)open_paren;
-       (void)close_paren;
-
-       EC_LOG(EC_LOG_DEBUG, "eval paren\n");
-       *result = value;
-
-       return 0;
-}
-
-static void
-ec_node_expr_test_eval_free(void *result, void *userctx)
-{
-       (void)userctx;
-       ec_free(result);
-}
-
-static const struct ec_node_expr_eval_ops test_ops = {
-       .eval_var = ec_node_expr_test_eval_var,
-       .eval_pre_op = ec_node_expr_test_eval_pre_op,
-       .eval_post_op = ec_node_expr_test_eval_post_op,
-       .eval_bin_op = ec_node_expr_test_eval_bin_op,
-       .eval_parenthesis = ec_node_expr_test_eval_parenthesis,
-       .eval_free = ec_node_expr_test_eval_free,
-};
-
-static int ec_node_expr_test_eval(struct ec_node *lex_node,
-       const struct ec_node *expr_node,
-       const char *str, int val)
-{
-       struct ec_parse *p;
-       void *result;
-       struct my_eval_result *eval;
-       int ret;
-
-       p = ec_node_parse(lex_node, str);
-       if (p == NULL)
-               return -1;
-
-       ret = ec_node_expr_eval(&result, expr_node, p, &test_ops, NULL);
-       ec_parse_free(p);
-       if (ret < 0)
-               return -1;
-
-       /* the parse value is an integer */
-       eval = result;
-       assert(eval != NULL);
-
-       EC_LOG(EC_LOG_DEBUG, "result: %d (expected %d)\n", eval->val, val);
-       if (eval->val == val)
-               ret = 0;
-       else
-               ret = -1;
-
-       ec_free(eval);
-
-       return ret;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_expr_testcase(void)
-{
-       struct ec_node *node = NULL, *lex_node = NULL;
-       int testres = 0;
-
-       node = ec_node("expr", "my_expr");
-       if (node == NULL)
-               return -1;
-
-       ec_node_expr_set_val_node(node, ec_node_int(EC_NO_ID, 0, UCHAR_MAX, 0));
-       ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "+"));
-       ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "*"));
-       ec_node_expr_add_pre_op(node, ec_node_str(EC_NO_ID, "!"));  /* not */
-       ec_node_expr_add_post_op(node, ec_node_str(EC_NO_ID, "^")); /* square */
-       ec_node_expr_add_parenthesis(node, ec_node_str(EC_NO_ID, "("),
-               ec_node_str(EC_NO_ID, ")"));
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "*");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1", "*");
-       testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "+", "!", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "^", "+", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "*", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "+", "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 7, "1", "*", "1", "*", "1", "*",
-                               "1");
-       testres |= EC_TEST_CHECK_PARSE(
-               node, 10, "!", "(", "1", "*", "(", "1", "+", "1", ")", ")");
-       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "+", "!", "1", "^");
-
-       /* prepend a lexer to the expression node */
-       lex_node = ec_node_re_lex(EC_NO_ID, ec_node_clone(node));
-       if (lex_node == NULL)
-               goto fail;
-
-       testres |= ec_node_re_lex_add(lex_node, "[0-9]+", 1, NULL); /* vars */
-       testres |= ec_node_re_lex_add(lex_node, "[+*!^()]", 1, NULL); /* operators */
-       testres |= ec_node_re_lex_add(lex_node, "[      ]+", 0, NULL); /* spaces */
-
-       /* valid expressions */
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^ + 1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1 + 4 * (2 + 3^)^");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1)");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "3*!3+!3*(2+ 2)");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!!(!1)^ + !(4 + (2*3))");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1 + 1)^ * 1^");
-
-       /* invalid expressions */
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "()");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "(");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, ")");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "+1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+*1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+(1*1");
-       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+!1!1)");
-
-       testres |= ec_node_expr_test_eval(lex_node, node, "1^", 1);
-       testres |= ec_node_expr_test_eval(lex_node, node, "2^", 4);
-       testres |= ec_node_expr_test_eval(lex_node, node, "!1", 0);
-       testres |= ec_node_expr_test_eval(lex_node, node, "!0", 1);
-
-       testres |= ec_node_expr_test_eval(lex_node, node, "1+1", 2);
-       testres |= ec_node_expr_test_eval(lex_node, node, "1+2+3", 6);
-       testres |= ec_node_expr_test_eval(lex_node, node, "1+1*2", 4);
-       testres |= ec_node_expr_test_eval(lex_node, node, "2 * 2^", 8);
-       testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !0)^ * !0^", 4);
-       testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !1) * 3", 3);
-
-       ec_node_free(node);
-       ec_node_free(lex_node);
-
-       return testres;
-
-fail:
-       ec_node_free(lex_node);
-       ec_node_free(node);
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_expr_test = {
-       .name = "node_expr",
-       .test = ec_node_expr_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_expr_test);
diff --git a/libecoli/ecoli_node_file.c b/libecoli/ecoli_node_file.c
deleted file mode 100644 (file)
index 001dcb6..0000000
+++ /dev/null
@@ -1,425 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-#include <dirent.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_string.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_file.h>
-
-EC_LOG_TYPE_REGISTER(node_file);
-
-struct ec_node_file {
-       struct ec_node gen;
-
-       /* below functions pointers are only useful for test */
-       int (*lstat)(const char *pathname, struct stat *buf);
-       DIR *(*opendir)(const char *name);
-       struct dirent *(*readdir)(DIR *dirp);
-       int (*closedir)(DIR *dirp);
-       int (*dirfd)(DIR *dirp);
-       int (*fstatat)(int dirfd, const char *pathname, struct stat *buf,
-               int flags);
-};
-
-static int
-ec_node_file_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       (void)gen_node;
-       (void)state;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       return 1;
-}
-
-/*
- * Almost the same than dirname (3) and basename (3) except that:
- * - it always returns a substring of the given path, which can
- *   be empty.
- * - the behavior is different when the path finishes with a '/'
- * - the path argument is not modified
- * - the outputs are allocated and must be freed with ec_free().
- *
- *   path       dirname   basename       split_path
- *   /usr/lib   /usr      lib          /usr/     lib
- *   /usr/      /         usr          /usr/
- *   usr        .         usr                    usr
- *   /          /         /            /
- *   .          .         .                      .
- *   ..         .         ..                     ..
- */
-static int split_path(const char *path, char **dname_p, char **bname_p)
-{
-       char *last_slash;
-       size_t dirlen;
-       char *dname, *bname;
-
-       *dname_p = NULL;
-       *bname_p = NULL;
-
-       last_slash = strrchr(path, '/');
-       if (last_slash == NULL)
-               dirlen = 0;
-       else
-               dirlen = last_slash - path + 1;
-
-       dname = ec_strdup(path);
-       if (dname == NULL)
-               return -1;
-       dname[dirlen] = '\0';
-
-       bname = ec_strdup(path + dirlen);
-       if (bname == NULL) {
-               ec_free(dname);
-               return -1;
-       }
-
-       *dname_p = dname;
-       *bname_p = bname;
-
-       return 0;
-}
-
-static int
-ec_node_file_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_file *node = (struct ec_node_file *)gen_node;
-       char *dname = NULL, *bname = NULL, *effective_dir;
-       struct ec_comp_item *item = NULL;
-       enum ec_comp_type type;
-       struct stat st, st2;
-       const char *input;
-       size_t bname_len;
-       struct dirent *de = NULL;
-       DIR *dir = NULL;
-       char *comp_str = NULL;
-       char *disp_str = NULL;
-       int is_dir = 0;
-
-       /*
-        * Example with this file tree:
-        * /
-        * ├── dir1
-        * │   ├── file1
-        * │   ├── file2
-        * │   └── subdir
-        * │       └── file3
-        * ├── dir2
-        * │   └── file4
-        * └── file5
-        *
-        * Input     Output completions
-        *   /       [dir1/, dir2/, file5]
-        *   /d      [dir1/, dir2/]
-        *   /f      [file5]
-        *   /dir1/  [file1, file2, subdir/]
-        *
-        *
-        *
-        */
-
-       if (ec_strvec_len(strvec) != 1)
-               return 0;
-
-       input = ec_strvec_val(strvec, 0);
-       if (split_path(input, &dname, &bname) < 0)
-               return -1;
-
-       if (strcmp(dname, "") == 0)
-               effective_dir = ".";
-       else
-               effective_dir = dname;
-
-       if (node->lstat(effective_dir, &st) < 0)
-               goto fail;
-       if (!S_ISDIR(st.st_mode))
-               goto out;
-
-       dir = node->opendir(effective_dir);
-       if (dir == NULL)
-               goto fail;
-
-       bname_len = strlen(bname);
-       while (1) {
-               int save_errno = errno;
-
-               errno = 0;
-               de = node->readdir(dir);
-               if (de == NULL) {
-                       if (errno == 0) {
-                               errno = save_errno;
-                               goto out;
-                       } else {
-                               goto fail;
-                       }
-               }
-
-               if (!ec_str_startswith(de->d_name, bname))
-                       continue;
-               if (bname[0] != '.' && de->d_name[0] == '.')
-                       continue;
-
-               /* add '/' if it's a dir */
-               if (de->d_type == DT_DIR) {
-                       is_dir = 1;
-               } else if (de->d_type == DT_UNKNOWN) {
-                       int dir_fd = node->dirfd(dir);
-
-                       if (dir_fd < 0)
-                               goto fail;
-                       if (node->fstatat(dir_fd, de->d_name, &st2, 0) < 0)
-                               goto fail;
-                       if (S_ISDIR(st2.st_mode))
-                               is_dir = 1;
-                       else
-                               is_dir = 0;
-               } else {
-                       is_dir = 0;
-               }
-
-               if (is_dir) {
-                       type = EC_COMP_PARTIAL;
-                       if (ec_asprintf(&comp_str, "%s%s/", input,
-                                       &de->d_name[bname_len]) < 0)
-                               goto fail;
-                       if (ec_asprintf(&disp_str, "%s/", de->d_name) < 0)
-                               goto fail;
-               } else {
-                       type = EC_COMP_FULL;
-                       if (ec_asprintf(&comp_str, "%s%s", input,
-                                       &de->d_name[bname_len]) < 0)
-                               goto fail;
-                       if (ec_asprintf(&disp_str, "%s", de->d_name) < 0)
-                               goto fail;
-               }
-               if (ec_comp_add_item(comp, gen_node, &item,
-                                               type, input, comp_str) < 0)
-                       goto out;
-
-               /* fix the display string: we don't want to display the full
-                * path. */
-               if (ec_comp_item_set_display(item, disp_str) < 0)
-                       goto out;
-
-               item = NULL;
-               ec_free(comp_str);
-               comp_str = NULL;
-               ec_free(disp_str);
-               disp_str = NULL;
-       }
-out:
-       ec_free(comp_str);
-       ec_free(disp_str);
-       ec_free(dname);
-       ec_free(bname);
-       if (dir != NULL)
-               node->closedir(dir);
-
-       return 0;
-
-fail:
-       ec_free(comp_str);
-       ec_free(disp_str);
-       ec_free(dname);
-       ec_free(bname);
-       if (dir != NULL)
-               node->closedir(dir);
-
-       return -1;
-}
-
-static int
-ec_node_file_init_priv(struct ec_node *gen_node)
-{
-       struct ec_node_file *node = (struct ec_node_file *)gen_node;
-
-       node->lstat = lstat;
-       node->opendir = opendir;
-       node->readdir = readdir;
-       node->dirfd = dirfd;
-       node->fstatat = fstatat;
-
-       return 0;
-}
-
-static struct ec_node_type ec_node_file_type = {
-       .name = "file",
-       .parse = ec_node_file_parse,
-       .complete = ec_node_file_complete,
-       .size = sizeof(struct ec_node_file),
-       .init_priv = ec_node_file_init_priv,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_file_type);
-
-/* LCOV_EXCL_START */
-static int
-test_lstat(const char *pathname, struct stat *buf)
-{
-       if (!strcmp(pathname, "/tmp/toto/")) {
-               struct stat st = { .st_mode = S_IFDIR };
-               memcpy(buf, &st, sizeof(*buf));
-               return 0;
-       }
-
-       errno = ENOENT;
-       return -1;
-}
-
-static DIR *
-test_opendir(const char *name)
-{
-       int *p;
-
-       if (strcmp(name, "/tmp/toto/")) {
-               errno = ENOENT;
-               return NULL;
-       }
-
-       p = malloc(sizeof(int));
-       if (p)
-               *p = 0;
-
-       return (DIR *)p;
-}
-
-static struct dirent *
-test_readdir(DIR *dirp)
-{
-       static struct dirent de[] = {
-               { .d_type = DT_DIR, .d_name = ".." },
-               { .d_type = DT_DIR, .d_name = "." },
-               { .d_type = DT_REG, .d_name = "bar" },
-               { .d_type = DT_UNKNOWN, .d_name = "bar2" },
-               { .d_type = DT_REG, .d_name = "foo" },
-               { .d_type = DT_DIR, .d_name = "titi" },
-               { .d_type = DT_UNKNOWN, .d_name = "tutu" },
-               { .d_name = "" },
-       };
-       int *p = (int *)dirp;
-       struct dirent *ret = &de[*p];
-
-       if (!strcmp(ret->d_name, ""))
-               return NULL;
-
-       *p = *p + 1;
-
-       return ret;
-}
-
-static int
-test_closedir(DIR *dirp)
-{
-       free(dirp);
-       return 0;
-}
-
-static int
-test_dirfd(DIR *dirp)
-{
-       int *p = (int *)dirp;
-       return *p;
-}
-
-static int
-test_fstatat(int dirfd, const char *pathname, struct stat *buf,
-       int flags)
-{
-       (void)dirfd;
-       (void)flags;
-
-       if (!strcmp(pathname, "bar2")) {
-               struct stat st = { .st_mode = S_IFREG };
-               memcpy(buf, &st, sizeof(*buf));
-               return 0;
-       } else if (!strcmp(pathname, "tutu")) {
-               struct stat st = { .st_mode = S_IFDIR };
-               memcpy(buf, &st, sizeof(*buf));
-               return 0;
-       }
-
-       errno = ENOENT;
-       return -1;
-}
-
-static int
-ec_node_file_override_functions(struct ec_node *gen_node)
-{
-       struct ec_node_file *node = (struct ec_node_file *)gen_node;
-
-       node->lstat = test_lstat;
-       node->opendir = test_opendir;
-       node->readdir = test_readdir;
-       node->closedir = test_closedir;
-       node->dirfd = test_dirfd;
-       node->fstatat = test_fstatat;
-
-       return 0;
-}
-
-static int ec_node_file_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("file", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       ec_node_file_override_functions(node);
-
-       /* any string matches */
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "/tmp/bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-
-       /* test completion */
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "/tmp/toto/t", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE_PARTIAL(node,
-               "/tmp/toto/t", EC_NODE_ENDLIST,
-               "/tmp/toto/titi/", "/tmp/toto/tutu/", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "/tmp/toto/f", EC_NODE_ENDLIST,
-               "/tmp/toto/foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "/tmp/toto/b", EC_NODE_ENDLIST,
-               "/tmp/toto/bar", "/tmp/toto/bar2", EC_NODE_ENDLIST);
-
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_file_test = {
-       .name = "node_file",
-       .test = ec_node_file_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_file_test);
diff --git a/libecoli/ecoli_node_file.h b/libecoli/ecoli_node_file.h
deleted file mode 100644 (file)
index 5760902..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_FILE_
-#define ECOLI_NODE_FILE_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_file(const char *id, const char *file);
-
-/* file is duplicated */
-int ec_node_file_set_str(struct ec_node *node, const char *file);
-
-#endif
diff --git a/libecoli/ecoli_node_helper.c b/libecoli/ecoli_node_helper.c
deleted file mode 100644 (file)
index 94811f2..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/types.h>
-#include <sys/queue.h>
-#include <stdarg.h>
-#include <stddef.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_config.h>
-#include <ecoli_node.h>
-#include <ecoli_node_helper.h>
-
-struct ec_node **
-ec_node_config_node_list_to_table(const struct ec_config *config,
-                               size_t *len)
-{
-       struct ec_node **table = NULL;
-       struct ec_config *child;
-       ssize_t n, i;
-
-       *len = 0;
-
-       if (config == NULL) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       if (ec_config_get_type(config) != EC_CONFIG_TYPE_LIST) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       n = ec_config_count(config);
-       if (n < 0)
-               return NULL;
-
-       table = ec_calloc(n, sizeof(*table));
-       if (table == NULL)
-               goto fail;
-
-       n = 0;
-       TAILQ_FOREACH(child, &config->list, next) {
-               if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
-                       errno = EINVAL;
-                       goto fail;
-               }
-               table[n] = ec_node_clone(child->node);
-               n++;
-       }
-
-       *len = n;
-
-       return table;
-
-fail:
-       if (table != NULL) {
-               for (i = 0; i < n; i++)
-                       ec_node_free(table[i]);
-       }
-       ec_free(table);
-
-       return NULL;
-}
-
-struct ec_config *
-ec_node_config_node_list_from_vargs(va_list ap)
-{
-       struct ec_config *list = NULL;
-       struct ec_node *node = va_arg(ap, struct ec_node *);
-
-       list = ec_config_list();
-       if (list == NULL)
-               goto fail;
-
-       for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *)) {
-               if (node == NULL)
-                       goto fail;
-
-               if (ec_config_list_add(list, ec_config_node(node)) < 0)
-                       goto fail;
-       }
-
-       return list;
-
-fail:
-       for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *))
-               ec_node_free(node);
-       ec_config_free(list);
-
-       return NULL;
-}
diff --git a/libecoli/ecoli_node_helper.h b/libecoli/ecoli_node_helper.h
deleted file mode 100644 (file)
index 9dbf519..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Helpers that are commonly used in nodes.
- */
-
-#ifndef ECOLI_NODE_HELPERS_
-#define ECOLI_NODE_HELPERS
-
-struct ec_node;
-
-/**
- * Build a node table from a node list in a ec_config.
- *
- * The function takes a node configuration as parameter, which must be a
- * node list. From it, a node table is built. A reference is taken for
- * each node.
- *
- * On error, no reference is taken.
- *
- * @param config
- *   The configuration (type must be a list of nodes). If it is
- *   NULL, an error is returned.
- * @param len
- *   The length of the allocated table on success, or 0 on error.
- * @return
- *   The allocated node table, that must be freed by the caller:
- *   each entry must be freed with ec_node_free() and the table
- *   with ec_free(). On error, NULL is returned and errno is set.
- */
-struct ec_node **
-ec_node_config_node_list_to_table(const struct ec_config *config,
-                               size_t *len);
-
-/**
- * Build a list of config nodes from variable arguments.
- *
- * The va_list argument is a list of pointer to ec_node structures,
- * terminated with EC_NODE_ENDLIST.
- *
- * This helper is used by nodes that contain a list of nodes,
- * like "seq", "or", ...
- *
- * @param ap
- *   List of pointer to ec_node structures, terminated with
- *   EC_NODE_ENDLIST.
- * @return
- *   A pointer to an ec_config structure. In this case, the
- *   nodes will be freed when the config structure will be freed.
- *   On error, NULL is returned (and errno is set), and the
- *   nodes are freed.
- */
-struct ec_config *
-ec_node_config_node_list_from_vargs(va_list ap);
-
-#endif
diff --git a/libecoli/ecoli_node_int.c b/libecoli/ecoli_node_int.c
deleted file mode 100644 (file)
index 9b56e22..0000000
+++ /dev/null
@@ -1,501 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <limits.h>
-#include <ctype.h>
-#include <errno.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_int.h>
-#include <ecoli_test.h>
-
-EC_LOG_TYPE_REGISTER(node_int);
-
-/* common to int and uint */
-struct ec_node_int_uint {
-       struct ec_node gen;
-       bool is_signed;
-       bool check_min;
-       bool check_max;
-       union {
-               int64_t min;
-               uint64_t umin;
-       };
-       union {
-               int64_t max;
-               uint64_t umax;
-       };
-       unsigned int base;
-};
-
-/* XXX to utils.c ? */
-static int parse_llint(struct ec_node_int_uint *node, const char *str,
-       int64_t *val)
-{
-       char *endptr;
-       int save_errno = errno;
-
-       errno = 0;
-       *val = strtoll(str, &endptr, node->base);
-
-       if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) ||
-                       (errno != 0 && *val == 0))
-               return -1;
-
-       if (node->check_min && *val < node->min) {
-               errno = ERANGE;
-               return -1;
-       }
-
-       if (node->check_max && *val > node->max) {
-               errno = ERANGE;
-               return -1;
-       }
-
-       if (*endptr != 0) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       errno = save_errno;
-       return 0;
-}
-
-static int parse_ullint(struct ec_node_int_uint *node, 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, node->base);
-
-       if ((errno == ERANGE && *val == ULLONG_MAX) ||
-                       (errno != 0 && *val == 0))
-               return -1;
-
-       if (node->check_min && *val < node->umin)
-               return -1;
-
-       if (node->check_max && *val > node->umax)
-               return -1;
-
-       if (*endptr != 0)
-               return -1;
-
-       errno = save_errno;
-       return 0;
-}
-
-static int ec_node_int_uint_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       const char *str;
-       uint64_t u64;
-       int64_t i64;
-
-       (void)state;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       str = ec_strvec_val(strvec, 0);
-       if (node->is_signed) {
-               if (parse_llint(node, str, &i64) < 0)
-                       return EC_PARSE_NOMATCH;
-       } else {
-               if (parse_ullint(node, str, &u64) < 0)
-                       return EC_PARSE_NOMATCH;
-       }
-       return 1;
-}
-
-static int
-ec_node_uint_init_priv(struct ec_node *gen_node)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-
-       node->is_signed = true;
-
-       return 0;
-}
-
-static const struct ec_config_schema ec_node_int_schema[] = {
-       {
-               .key = "min",
-               .desc = "The minimum valid value (included).",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .key = "max",
-               .desc = "The maximum valid value (included).",
-               .type = EC_CONFIG_TYPE_INT64,
-       },
-       {
-               .key = "base",
-               .desc = "The base to use. If unset or 0, try to guess.",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_int_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       const struct ec_config *min_value = NULL;
-       const struct ec_config *max_value = NULL;
-       const struct ec_config *base_value = NULL;
-       char *s = NULL;
-
-       min_value = ec_config_dict_get(config, "min");
-       max_value = ec_config_dict_get(config, "max");
-       base_value = ec_config_dict_get(config, "base");
-
-       if (min_value && max_value && min_value->i64 > max_value->i64) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (min_value != NULL) {
-               node->check_min = true;
-               node->min = min_value->i64;
-       } else {
-               node->check_min = false;
-       }
-       if (max_value != NULL) {
-               node->check_max = true;
-               node->max = max_value->i64;
-       } else {
-               node->check_min = false;
-       }
-       if (base_value != NULL)
-               node->base = base_value->u64;
-       else
-               node->base = 0;
-
-       return 0;
-
-fail:
-       ec_free(s);
-       return -1;
-}
-
-static struct ec_node_type ec_node_int_type = {
-       .name = "int",
-       .schema = ec_node_int_schema,
-       .set_config = ec_node_int_set_config,
-       .parse = ec_node_int_uint_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_int_uint),
-       .init_priv = ec_node_uint_init_priv,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_int_type);
-
-struct ec_node *ec_node_int(const char *id, int64_t min,
-       int64_t max, unsigned int base)
-{
-       struct ec_config *config = NULL;
-       struct ec_node *gen_node = NULL;
-       int ret;
-
-       gen_node = ec_node_from_type(&ec_node_int_type, id);
-       if (gen_node == NULL)
-               return NULL;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(config, "min", ec_config_i64(min));
-       if (ret < 0)
-               goto fail;
-       ret = ec_config_dict_set(config, "max", ec_config_i64(max));
-       if (ret < 0)
-               goto fail;
-       ret = ec_config_dict_set(config, "base", ec_config_u64(base));
-       if (ret < 0)
-               goto fail;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL;
-       if (ret < 0)
-               goto fail;
-
-       return gen_node;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(gen_node);
-       return NULL;
-}
-
-static const struct ec_config_schema ec_node_uint_schema[] = {
-       {
-               .key = "min",
-               .desc = "The minimum valid value (included).",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .key = "max",
-               .desc = "The maximum valid value (included).",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .key = "base",
-               .desc = "The base to use. If unset or 0, try to guess.",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_uint_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       const struct ec_config *min_value = NULL;
-       const struct ec_config *max_value = NULL;
-       const struct ec_config *base_value = NULL;
-       char *s = NULL;
-
-       min_value = ec_config_dict_get(config, "min");
-       max_value = ec_config_dict_get(config, "max");
-       base_value = ec_config_dict_get(config, "base");
-
-       if (min_value && max_value && min_value->u64 > max_value->u64) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (min_value != NULL) {
-               node->check_min = true;
-               node->min = min_value->u64;
-       } else {
-               node->check_min = false;
-       }
-       if (max_value != NULL) {
-               node->check_max = true;
-               node->max = max_value->u64;
-       } else {
-               node->check_min = false;
-       }
-       if (base_value != NULL)
-               node->base = base_value->u64;
-       else
-               node->base = 0;
-
-       return 0;
-
-fail:
-       ec_free(s);
-       return -1;
-}
-
-static struct ec_node_type ec_node_uint_type = {
-       .name = "uint",
-       .schema = ec_node_uint_schema,
-       .set_config = ec_node_uint_set_config,
-       .parse = ec_node_int_uint_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_int_uint),
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_uint_type);
-
-struct ec_node *ec_node_uint(const char *id, uint64_t min,
-       uint64_t max, unsigned int base)
-{
-       struct ec_config *config = NULL;
-       struct ec_node *gen_node = NULL;
-       int ret;
-
-       gen_node = ec_node_from_type(&ec_node_uint_type, id);
-       if (gen_node == NULL)
-               return NULL;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(config, "min", ec_config_u64(min));
-       if (ret < 0)
-               goto fail;
-       ret = ec_config_dict_set(config, "max", ec_config_u64(max));
-       if (ret < 0)
-               goto fail;
-       ret = ec_config_dict_set(config, "base", ec_config_u64(base));
-       if (ret < 0)
-               goto fail;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL;
-       if (ret < 0)
-               goto fail;
-
-       return gen_node;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(gen_node);
-       return NULL;
-}
-
-int ec_node_int_getval(const struct ec_node *gen_node, const char *str,
-                       int64_t *result)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       int ret;
-
-       ret = ec_node_check_type(gen_node, &ec_node_int_type);
-       if (ret < 0)
-               return ret;
-
-       if (parse_llint(node, str, result) < 0)
-               return -1;
-
-       return 0;
-}
-
-int ec_node_uint_getval(const struct ec_node *gen_node, const char *str,
-                       uint64_t *result)
-{
-       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
-       int ret;
-
-       ret = ec_node_check_type(gen_node, &ec_node_uint_type);
-       if (ret < 0)
-               return ret;
-
-       if (parse_ullint(node, str, result) < 0)
-               return -1;
-
-       return 0;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_int_testcase(void)
-{
-       struct ec_parse *p;
-       struct ec_node *node;
-       const char *s;
-       int testres = 0;
-       uint64_t u64;
-       int64_t i64;
-
-       node = ec_node_uint(EC_NO_ID, 1, 256, 0);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "256", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "0x100");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, " 1");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "-1");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x101");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x100000000000000000");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "4r");
-
-       p = ec_node_parse(node, "1");
-       s = ec_strvec_val(ec_parse_strvec(p), 0);
-       testres |= EC_TEST_CHECK(s != NULL &&
-               ec_node_uint_getval(node, s, &u64) == 0 &&
-               u64 == 1, "bad integer value");
-       ec_parse_free(p);
-
-       p = ec_node_parse(node, "10");
-       s = ec_strvec_val(ec_parse_strvec(p), 0);
-       testres |= EC_TEST_CHECK(s != NULL &&
-               ec_node_uint_getval(node, s, &u64) == 0 &&
-               u64 == 10, "bad integer value");
-       ec_parse_free(p);
-       ec_node_free(node);
-
-       node = ec_node_int(EC_NO_ID, -1, LLONG_MAX, 16);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "0");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "-1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "7fffffffffffffff");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "0x7fffffffffffffff");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x8000000000000000");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "-2");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "4r");
-
-       p = ec_node_parse(node, "10");
-       s = ec_strvec_val(ec_parse_strvec(p), 0);
-       testres |= EC_TEST_CHECK(s != NULL &&
-               ec_node_int_getval(node, s, &i64) == 0 &&
-               i64 == 16, "bad integer value");
-       ec_parse_free(p);
-       ec_node_free(node);
-
-       node = ec_node_int(EC_NO_ID, LLONG_MIN, 0, 10);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "0");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "-1");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "-9223372036854775808");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x0");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "1");
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_int(EC_NO_ID, 0, 10, 0);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "1", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_int_test = {
-       .name = "node_int",
-       .test = ec_node_int_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_int_test);
diff --git a/libecoli/ecoli_node_int.h b/libecoli/ecoli_node_int.h
deleted file mode 100644 (file)
index b64c60c..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_INT_
-#define ECOLI_NODE_INT_
-
-#include <stdint.h>
-
-#include <ecoli_node.h>
-
-/* ec_node("int", ...) can be used too
- * default is no limit, base 10 */
-
-struct ec_node *ec_node_int(const char *id, int64_t min,
-                       int64_t max, unsigned int base);
-
-int ec_node_int_getval(const struct ec_node *node, const char *str,
-                       int64_t *result);
-
-
-
-struct ec_node *ec_node_uint(const char *id, uint64_t min,
-                       uint64_t max, unsigned int base);
-
-int ec_node_uint_getval(const struct ec_node *node, const char *str,
-                       uint64_t *result);
-
-
-#endif
diff --git a/libecoli/ecoli_node_many.c b/libecoli/ecoli_node_many.c
deleted file mode 100644 (file)
index dfdd866..0000000
+++ /dev/null
@@ -1,416 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_option.h>
-#include <ecoli_config.h>
-#include <ecoli_node_many.h>
-
-EC_LOG_TYPE_REGISTER(node_many);
-
-struct ec_node_many {
-       struct ec_node gen;
-       unsigned int min;
-       unsigned int max;
-       struct ec_node *child;
-};
-
-static int ec_node_many_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-       struct ec_parse *child_parse;
-       struct ec_strvec *childvec = NULL;
-       size_t off = 0, count;
-       int ret;
-
-       for (count = 0; node->max == 0 || count < node->max; count++) {
-               childvec = ec_strvec_ndup(strvec, off,
-                       ec_strvec_len(strvec) - off);
-               if (childvec == NULL)
-                       goto fail;
-
-               ret = ec_node_parse_child(node->child, state, childvec);
-               if (ret < 0)
-                       goto fail;
-
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if (ret == EC_PARSE_NOMATCH)
-                       break;
-
-               /* it matches an empty strvec, no need to continue */
-               if (ret == 0) {
-                       child_parse = ec_parse_get_last_child(state);
-                       ec_parse_unlink_child(state, child_parse);
-                       ec_parse_free(child_parse);
-                       break;
-               }
-
-               off += ret;
-       }
-
-       if (count < node->min) {
-               ec_parse_free_children(state);
-               return EC_PARSE_NOMATCH;
-       }
-
-       return off;
-
-fail:
-       ec_strvec_free(childvec);
-       return -1;
-}
-
-static int
-__ec_node_many_complete(struct ec_node_many *node, unsigned int max,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_parse *parse = ec_comp_get_state(comp);
-       struct ec_strvec *childvec = NULL;
-       unsigned int i;
-       int ret;
-
-       /* first, try to complete with the child node */
-       ret = ec_node_complete_child(node->child, comp, strvec);
-       if (ret < 0)
-               goto fail;
-
-       /* we're done, we reached the max number of nodes */
-       if (max == 1)
-               return 0;
-
-       /* if there is a maximum, decrease it before recursion */
-       if (max != 0)
-               max--;
-
-       /* then, if the node matches the beginning of the strvec, try to
-        * complete the rest */
-       for (i = 0; i < ec_strvec_len(strvec); i++) {
-               childvec = ec_strvec_ndup(strvec, 0, i);
-               if (childvec == NULL)
-                       goto fail;
-
-               ret = ec_node_parse_child(node->child, parse, childvec);
-               if (ret < 0)
-                       goto fail;
-
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if ((unsigned int)ret != i) {
-                       if (ret != EC_PARSE_NOMATCH)
-                               ec_parse_del_last_child(parse);
-                       continue;
-               }
-
-               childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i);
-               if (childvec == NULL) {
-                       ec_parse_del_last_child(parse);
-                       goto fail;
-               }
-
-               ret = __ec_node_many_complete(node, max, comp, childvec);
-               ec_parse_del_last_child(parse);
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if (ret < 0)
-                       goto fail;
-       }
-
-       return 0;
-
-fail:
-       ec_strvec_free(childvec);
-       return -1;
-}
-
-static int
-ec_node_many_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-
-       return __ec_node_many_complete(node, node->max, comp,
-                               strvec);
-}
-
-static void ec_node_many_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-
-       ec_node_free(node->child);
-}
-
-static size_t
-ec_node_many_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_many_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 2;
-       return 0;
-}
-
-static const struct ec_config_schema ec_node_many_schema[] = {
-       {
-               .key = "child",
-               .desc = "The child node.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .key = "min",
-               .desc = "The minimum number of matches (default = 0).",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .key = "max",
-               .desc = "The maximum number of matches. If 0, there is "
-               "no maximum (default = 0).",
-               .type = EC_CONFIG_TYPE_UINT64,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_many_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_many *node = (struct ec_node_many *)gen_node;
-       const struct ec_config *child, *min, *max;
-
-       child = ec_config_dict_get(config, "child");
-       if (child == NULL)
-               goto fail;
-       if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
-               errno = EINVAL;
-               goto fail;
-       }
-       min = ec_config_dict_get(config, "min");
-       if (min != NULL && (ec_config_get_type(min) != EC_CONFIG_TYPE_UINT64 ||
-                               min->u64 >= UINT_MAX)) {
-               errno = EINVAL;
-               goto fail;
-       }
-       max = ec_config_dict_get(config, "max");
-       if (max != NULL && (ec_config_get_type(max) != EC_CONFIG_TYPE_UINT64 ||
-                               max->u64 >= UINT_MAX)) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (node->child != NULL)
-               ec_node_free(node->child);
-       node->child = ec_node_clone(child->node);
-       if (min == NULL)
-               node->min = 0;
-       else
-               node->min = min->u64;
-       if (max == NULL)
-               node->max = 0;
-       else
-               node->max = max->u64;
-
-       return 0;
-
-fail:
-       return -1;
-}
-
-static struct ec_node_type ec_node_many_type = {
-       .name = "many",
-       .schema = ec_node_many_schema,
-       .set_config = ec_node_many_set_config,
-       .parse = ec_node_many_parse,
-       .complete = ec_node_many_complete,
-       .size = sizeof(struct ec_node_many),
-       .free_priv = ec_node_many_free_priv,
-       .get_children_count = ec_node_many_get_children_count,
-       .get_child = ec_node_many_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_many_type);
-
-int
-ec_node_many_set_params(struct ec_node *gen_node, struct ec_node *child,
-       unsigned int min, unsigned int max)
-{
-       const struct ec_config *cur_config = NULL;
-       struct ec_config *config = NULL;
-       int ret;
-
-       if (ec_node_check_type(gen_node, &ec_node_many_type) < 0)
-               goto fail;
-
-       cur_config = ec_node_get_config(gen_node);
-       if (cur_config == NULL)
-               config = ec_config_dict();
-       else
-               config = ec_config_dup(cur_config);
-       if (config == NULL)
-               goto fail;
-
-       if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) {
-               child = NULL; /* freed */
-               goto fail;
-       }
-       child = NULL; /* freed */
-
-       if (ec_config_dict_set(config, "min", ec_config_u64(min)) < 0)
-               goto fail;
-       if (ec_config_dict_set(config, "max", ec_config_u64(max)) < 0)
-               goto fail;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *ec_node_many(const char *id, struct ec_node *child,
-       unsigned int min, unsigned int max)
-{
-       struct ec_node *gen_node = NULL;
-
-       if (child == NULL)
-               return NULL;
-
-       gen_node = ec_node_from_type(&ec_node_many_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       if (ec_node_many_set_params(gen_node, child, min, max) < 0) {
-               child = NULL;
-               goto fail;
-       }
-       child = NULL;
-
-       return gen_node;
-
-fail:
-       ec_node_free(child);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_many_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 0, 0);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       ec_node_free(node);
-
-       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 0);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-       ec_node_free(node);
-
-       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 2);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 2, 4);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "foo", "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "foo", "foo", "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "foo", "foo", "foo", "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_many_test = {
-       .name = "node_many",
-       .test = ec_node_many_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_many_test);
diff --git a/libecoli/ecoli_node_many.h b/libecoli/ecoli_node_many.h
deleted file mode 100644 (file)
index 0a50fd7..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_MANY_
-#define ECOLI_NODE_MANY_
-
-/*
- * if min == max == 0, there is no limit
- */
-struct ec_node *ec_node_many(const char *id, struct ec_node *child,
-       unsigned int min, unsigned int max);
-
-int
-ec_node_many_set_params(struct ec_node *gen_node, struct ec_node *child,
-       unsigned int min, unsigned int max);
-
-#endif
diff --git a/libecoli/ecoli_node_none.c b/libecoli/ecoli_node_none.c
deleted file mode 100644 (file)
index dba9ebf..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_none.h>
-
-EC_LOG_TYPE_REGISTER(node_none);
-
-struct ec_node_none {
-       struct ec_node gen;
-};
-
-static int ec_node_none_parse(const struct ec_node *gen_node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       (void)gen_node;
-       (void)state;
-       (void)strvec;
-
-       return EC_PARSE_NOMATCH;
-}
-
-static int
-ec_node_none_complete(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       (void)gen_node;
-       (void)comp;
-       (void)strvec;
-
-       return 0;
-}
-
-static struct ec_node_type ec_node_none_type = {
-       .name = "none",
-       .parse = ec_node_none_parse,
-       .complete = ec_node_none_complete,
-       .size = sizeof(struct ec_node_none),
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_none_type);
-
-/* LCOV_EXCL_START */
-static int ec_node_none_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("none", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1);
-       ec_node_free(node);
-
-       /* never completes */
-       node = ec_node("none", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_none_test = {
-       .name = "node_none",
-       .test = ec_node_none_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_none_test);
diff --git a/libecoli/ecoli_node_none.h b/libecoli/ecoli_node_none.h
deleted file mode 100644 (file)
index 842f211..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * This node does not match anything
- */
-
-#ifndef ECOLI_NODE_ANY_
-#define ECOLI_NODE_ANY_
-
-#endif
diff --git a/libecoli/ecoli_node_once.c b/libecoli/ecoli_node_once.c
deleted file mode 100644 (file)
index 989c710..0000000
+++ /dev/null
@@ -1,282 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_many.h>
-#include <ecoli_config.h>
-#include <ecoli_node_once.h>
-
-EC_LOG_TYPE_REGISTER(node_once);
-
-struct ec_node_once {
-       struct ec_node gen;
-       struct ec_node *child;
-};
-
-static unsigned int
-count_node(struct ec_parse *parse, const struct ec_node *node)
-{
-       struct ec_parse *child;
-       unsigned int count = 0;
-
-       if (parse == NULL)
-               return 0;
-
-       if (ec_parse_get_node(parse) == node)
-               count++;
-
-       EC_PARSE_FOREACH_CHILD(child, parse)
-               count += count_node(child, node);
-
-       return count;
-}
-
-static int
-ec_node_once_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-       unsigned int count;
-
-       /* count the number of occurences of the node: if already parsed,
-        * do not match
-        */
-       count = count_node(ec_parse_get_root(state), node->child);
-       if (count > 0)
-               return EC_PARSE_NOMATCH;
-
-       return ec_node_parse_child(node->child, state, strvec);
-}
-
-static int
-ec_node_once_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-       struct ec_parse *parse = ec_comp_get_state(comp);
-       unsigned int count;
-       int ret;
-
-       /* count the number of occurences of the node: if already parsed,
-        * do not match
-        */
-       count = count_node(ec_parse_get_root(parse), node->child);
-       if (count > 0)
-               return 0;
-
-       ret = ec_node_complete_child(node->child, comp, strvec);
-       if (ret < 0)
-               return ret;
-
-       return 0;
-}
-
-static void ec_node_once_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-
-       ec_node_free(node->child);
-}
-
-static size_t
-ec_node_once_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_once_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 2;
-       return 0;
-}
-
-static const struct ec_config_schema ec_node_once_schema[] = {
-       {
-               .key = "child",
-               .desc = "The child node.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_once_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_once *node = (struct ec_node_once *)gen_node;
-       const struct ec_config *child;
-
-       child = ec_config_dict_get(config, "child");
-       if (child == NULL)
-               goto fail;
-       if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (node->child != NULL)
-               ec_node_free(node->child);
-       node->child = ec_node_clone(child->node);
-
-       return 0;
-
-fail:
-       return -1;
-}
-
-static struct ec_node_type ec_node_once_type = {
-       .name = "once",
-       .schema = ec_node_once_schema,
-       .set_config = ec_node_once_set_config,
-       .parse = ec_node_once_parse,
-       .complete = ec_node_once_complete,
-       .size = sizeof(struct ec_node_once),
-       .free_priv = ec_node_once_free_priv,
-       .get_children_count = ec_node_once_get_children_count,
-       .get_child = ec_node_once_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_once_type);
-
-int
-ec_node_once_set_child(struct ec_node *gen_node, struct ec_node *child)
-{
-       const struct ec_config *cur_config = NULL;
-       struct ec_config *config = NULL;
-       int ret;
-
-       if (ec_node_check_type(gen_node, &ec_node_once_type) < 0)
-               goto fail;
-
-       cur_config = ec_node_get_config(gen_node);
-       if (cur_config == NULL)
-               config = ec_config_dict();
-       else
-               config = ec_config_dup(cur_config);
-       if (config == NULL)
-               goto fail;
-
-       if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) {
-               child = NULL; /* freed */
-               goto fail;
-       }
-       child = NULL; /* freed */
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *ec_node_once(const char *id, struct ec_node *child)
-{
-       struct ec_node *gen_node = NULL;
-
-       if (child == NULL)
-               return NULL;
-
-       gen_node = ec_node_from_type(&ec_node_once_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       ec_node_once_set_child(gen_node, child);
-       child = NULL;
-
-       return gen_node;
-
-fail:
-       ec_node_free(child);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_once_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_many(EC_NO_ID,
-                       EC_NODE_OR(EC_NO_ID,
-                               ec_node_once(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")),
-                               ec_node_str(EC_NO_ID, "bar")
-                               ), 0, 0
-               );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "foox");
-
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "b", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", "", EC_NODE_ENDLIST,
-               "foo", "bar", EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_once_test = {
-       .name = "node_once",
-       .test = ec_node_once_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_once_test);
diff --git a/libecoli/ecoli_node_once.h b/libecoli/ecoli_node_once.h
deleted file mode 100644 (file)
index 690a10c..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_ONCE_
-#define ECOLI_NODE_ONCE_
-
-#include <ecoli_node.h>
-
-/* This node behaves like its child, but prevent from parsing it several
- * times.
- *
- * Example:
- *   many(
- *     or(
- *       once(str("foo")),
- *       str("bar")))
- *
- *   Matches: [], ["foo", "bar"], ["bar", "bar"], ["foo", "bar", "bar"], ...
- *   But not: ["foo", "foo"], ["foo", "bar", "foo"], ...
- */
-
-/* on error, child is *not* freed */
-struct ec_node *ec_node_once(const char *id, struct ec_node *child);
-
-/* on error, child is freed */
-int ec_node_once_set_child(struct ec_node *node, struct ec_node *child);
-
-#endif
diff --git a/libecoli/ecoli_node_option.c b/libecoli/ecoli_node_option.c
deleted file mode 100644 (file)
index 4b26001..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_str.h>
-#include <ecoli_test.h>
-#include <ecoli_config.h>
-#include <ecoli_node_option.h>
-
-EC_LOG_TYPE_REGISTER(node_option);
-
-struct ec_node_option {
-       struct ec_node gen;
-       struct ec_node *child;
-};
-
-static int
-ec_node_option_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-       int ret;
-
-       ret = ec_node_parse_child(node->child, state, strvec);
-       if (ret < 0)
-               return ret;
-
-       if (ret == EC_PARSE_NOMATCH)
-               return 0;
-
-       return ret;
-}
-
-static int
-ec_node_option_complete(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-
-       return ec_node_complete_child(node->child, comp, strvec);
-}
-
-static void ec_node_option_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-
-       ec_node_free(node->child);
-}
-
-static size_t
-ec_node_option_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_option_get_child(const struct ec_node *gen_node, size_t i,
-       struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 2;
-       return 0;
-}
-
-static const struct ec_config_schema ec_node_option_schema[] = {
-       {
-               .key = "child",
-               .desc = "The child node.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_option_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_option *node = (struct ec_node_option *)gen_node;
-       const struct ec_config *child;
-
-       child = ec_config_dict_get(config, "child");
-       if (child == NULL)
-               goto fail;
-       if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (node->child != NULL)
-               ec_node_free(node->child);
-       node->child = ec_node_clone(child->node);
-
-       return 0;
-
-fail:
-       return -1;
-}
-
-static struct ec_node_type ec_node_option_type = {
-       .name = "option",
-       .schema = ec_node_option_schema,
-       .set_config = ec_node_option_set_config,
-       .parse = ec_node_option_parse,
-       .complete = ec_node_option_complete,
-       .size = sizeof(struct ec_node_option),
-       .free_priv = ec_node_option_free_priv,
-       .get_children_count = ec_node_option_get_children_count,
-       .get_child = ec_node_option_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_option_type);
-
-int
-ec_node_option_set_child(struct ec_node *gen_node, struct ec_node *child)
-{
-       const struct ec_config *cur_config = NULL;
-       struct ec_config *config = NULL;
-       int ret;
-
-       if (ec_node_check_type(gen_node, &ec_node_option_type) < 0)
-               goto fail;
-
-       cur_config = ec_node_get_config(gen_node);
-       if (cur_config == NULL)
-               config = ec_config_dict();
-       else
-               config = ec_config_dup(cur_config);
-       if (config == NULL)
-               goto fail;
-
-       if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) {
-               child = NULL; /* freed */
-               goto fail;
-       }
-       child = NULL; /* freed */
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *ec_node_option(const char *id, struct ec_node *child)
-{
-       struct ec_node *gen_node = NULL;
-
-       if (child == NULL)
-               goto fail;
-
-       gen_node = ec_node_from_type(&ec_node_option_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       ec_node_option_set_child(gen_node, child);
-       child = NULL;
-
-       return gen_node;
-
-fail:
-       ec_node_free(child);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_option_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"));
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"));
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "b", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_option_test = {
-       .name = "node_option",
-       .test = ec_node_option_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_option_test);
diff --git a/libecoli/ecoli_node_option.h b/libecoli/ecoli_node_option.h
deleted file mode 100644 (file)
index 9f06d5f..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_OPTION_
-#define ECOLI_NODE_OPTION_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_option(const char *id, struct ec_node *node);
-int ec_node_option_set_child(struct ec_node *gen_node, struct ec_node *child);
-
-#endif
diff --git a/libecoli/ecoli_node_or.c b/libecoli/ecoli_node_or.c
deleted file mode 100644 (file)
index 2bf8fc7..0000000
+++ /dev/null
@@ -1,347 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_node_helper.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_str.h>
-#include <ecoli_test.h>
-
-EC_LOG_TYPE_REGISTER(node_or);
-
-struct ec_node_or {
-       struct ec_node gen;
-       struct ec_node **table;
-       size_t len;
-};
-
-static int
-ec_node_or_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       unsigned int i;
-       int ret;
-
-       for (i = 0; i < node->len; i++) {
-               ret = ec_node_parse_child(node->table[i], state, strvec);
-               if (ret == EC_PARSE_NOMATCH)
-                       continue;
-               return ret;
-       }
-
-       return EC_PARSE_NOMATCH;
-}
-
-static int
-ec_node_or_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       int ret;
-       size_t n;
-
-       for (n = 0; n < node->len; n++) {
-               ret = ec_node_complete_child(node->table[n],
-                                       comp, strvec);
-               if (ret < 0)
-                       return ret;
-       }
-
-       return 0;
-}
-
-static void ec_node_or_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       size_t i;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = NULL;
-       node->len = 0;
-}
-
-static const struct ec_config_schema ec_node_or_subschema[] = {
-       {
-               .desc = "A child node which is part of the choice.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema ec_node_or_schema[] = {
-       {
-               .key = "children",
-               .desc = "The list of children nodes defining the choice "
-               "elements.",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = ec_node_or_subschema,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_or_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       struct ec_node **table = NULL;
-       size_t i, len = 0;
-
-       table = ec_node_config_node_list_to_table(
-               ec_config_dict_get(config, "children"), &len);
-       if (table == NULL)
-               goto fail;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = table;
-       node->len = len;
-
-       return 0;
-
-fail:
-       for (i = 0; i < len; i++)
-               ec_node_free(table[i]);
-       ec_free(table);
-       return -1;
-}
-
-static size_t
-ec_node_or_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       return node->len;
-}
-
-static int
-ec_node_or_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-
-       if (i >= node->len)
-               return -1;
-
-       *child = node->table[i];
-       /* each child node is referenced twice: once in the config and
-        * once in the node->table[] */
-       *refs = 2;
-       return 0;
-}
-
-static struct ec_node_type ec_node_or_type = {
-       .name = "or",
-       .schema = ec_node_or_schema,
-       .set_config = ec_node_or_set_config,
-       .parse = ec_node_or_parse,
-       .complete = ec_node_or_complete,
-       .size = sizeof(struct ec_node_or),
-       .free_priv = ec_node_or_free_priv,
-       .get_children_count = ec_node_or_get_children_count,
-       .get_child = ec_node_or_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_or_type);
-
-int ec_node_or_add(struct ec_node *gen_node, struct ec_node *child)
-{
-       struct ec_node_or *node = (struct ec_node_or *)gen_node;
-       const struct ec_config *cur_config = NULL;
-       struct ec_config *config = NULL, *children;
-       int ret;
-
-       assert(node != NULL);
-
-       /* XXX factorize this code in a helper */
-
-       if (ec_node_check_type(gen_node, &ec_node_or_type) < 0)
-               goto fail;
-
-       cur_config = ec_node_get_config(gen_node);
-       if (cur_config == NULL)
-               config = ec_config_dict();
-       else
-               config = ec_config_dup(cur_config);
-       if (config == NULL)
-               goto fail;
-
-       children = ec_config_dict_get(config, "children");
-       if (children == NULL) {
-               children = ec_config_list();
-               if (children == NULL)
-                       goto fail;
-
-               if (ec_config_dict_set(config, "children", children) < 0)
-                       goto fail; /* children list is freed on error */
-       }
-
-       if (ec_config_list_add(children, ec_config_node(child)) < 0) {
-               child = NULL;
-               goto fail;
-       }
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *__ec_node_or(const char *id, ...)
-{
-       struct ec_config *config = NULL, *children = NULL;
-       struct ec_node *gen_node = NULL;
-       struct ec_node *child;
-       va_list ap;
-       int ret;
-
-       va_start(ap, id);
-       child = va_arg(ap, struct ec_node *);
-
-       gen_node = ec_node_from_type(&ec_node_or_type, id);
-       if (gen_node == NULL)
-               goto fail_free_children;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail_free_children;
-
-       children = ec_config_list();
-       if (children == NULL)
-               goto fail_free_children;
-
-       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) {
-               if (child == NULL)
-                       goto fail_free_children;
-
-               if (ec_config_list_add(children, ec_config_node(child)) < 0) {
-                       child = NULL;
-                       goto fail_free_children;
-               }
-       }
-
-       if (ec_config_dict_set(config, "children", children) < 0) {
-               children = NULL; /* freed */
-               goto fail;
-       }
-       children = NULL;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       va_end(ap);
-
-       return gen_node;
-
-fail_free_children:
-       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *))
-               ec_node_free(child);
-fail:
-       ec_node_free(gen_node); /* will also free added children */
-       ec_config_free(children);
-       ec_config_free(config);
-       va_end(ap);
-
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_or_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = EC_NODE_OR(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_str(EC_NO_ID, "bar")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, " ");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foox");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "toto");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       ec_node_free(node);
-
-       /* test completion */
-       node = EC_NODE_OR(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_str(EC_NO_ID, "bar"),
-               ec_node_str(EC_NO_ID, "bar2"),
-               ec_node_str(EC_NO_ID, "toto"),
-               ec_node_str(EC_NO_ID, "titi")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "b", EC_NODE_ENDLIST,
-               "bar", "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", EC_NODE_ENDLIST,
-               "bar", "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "t", EC_NODE_ENDLIST,
-               "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "to", EC_NODE_ENDLIST,
-               "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_or_test = {
-       .name = "node_or",
-       .test = ec_node_or_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_or_test);
diff --git a/libecoli/ecoli_node_or.h b/libecoli/ecoli_node_or.h
deleted file mode 100644 (file)
index db115b2..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_OR_
-#define ECOLI_NODE_OR_
-
-#include <ecoli_node.h>
-
-#define EC_NODE_OR(args...) __ec_node_or(args, EC_NODE_ENDLIST)
-
-/* list must be terminated with EC_NODE_ENDLIST */
-/* all nodes given in the list will be freed when freeing this one */
-/* avoid using this function directly, prefer the macro EC_NODE_OR() or
- * ec_node_or() + ec_node_or_add() */
-struct ec_node *__ec_node_or(const char *id, ...);
-
-struct ec_node *ec_node_or(const char *id);
-
-/* child is consumed */
-int ec_node_or_add(struct ec_node *node, struct ec_node *child);
-
-
-#endif
diff --git a/libecoli/ecoli_node_re.c b/libecoli/ecoli_node_re.c
deleted file mode 100644 (file)
index 69f352c..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <regex.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_config.h>
-#include <ecoli_node_re.h>
-
-EC_LOG_TYPE_REGISTER(node_re);
-
-struct ec_node_re {
-       struct ec_node gen;
-       char *re_str;
-       regex_t re;
-};
-
-static int
-ec_node_re_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_re *node = (struct ec_node_re *)gen_node;
-       const char *str;
-       regmatch_t pos;
-
-       (void)state;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       str = ec_strvec_val(strvec, 0);
-       if (regexec(&node->re, str, 1, &pos, 0) != 0)
-               return EC_PARSE_NOMATCH;
-       if (pos.rm_so != 0 || pos.rm_eo != (int)strlen(str))
-               return EC_PARSE_NOMATCH;
-
-       return 1;
-}
-
-static void ec_node_re_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_re *node = (struct ec_node_re *)gen_node;
-
-       if (node->re_str != NULL) {
-               ec_free(node->re_str);
-               regfree(&node->re);
-       }
-}
-
-static const struct ec_config_schema ec_node_re_schema[] = {
-       {
-               .key = "pattern",
-               .desc = "The pattern to match.",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_re_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_re *node = (struct ec_node_re *)gen_node;
-       const struct ec_config *value = NULL;
-       char *s = NULL;
-       regex_t re;
-       int ret;
-
-       value = ec_config_dict_get(config, "pattern");
-       if (value == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       s = ec_strdup(value->string);
-       if (s == NULL)
-               goto fail;
-
-       ret = regcomp(&re, s, REG_EXTENDED);
-       if (ret != 0) {
-               if (ret == REG_ESPACE)
-                       errno = ENOMEM;
-               else
-                       errno = EINVAL;
-               goto fail;
-       }
-
-       if (node->re_str != NULL) {
-               ec_free(node->re_str);
-               regfree(&node->re);
-       }
-       node->re_str = s;
-       node->re = re;
-
-       return 0;
-
-fail:
-       ec_free(s);
-       return -1;
-}
-
-static struct ec_node_type ec_node_re_type = {
-       .name = "re",
-       .schema = ec_node_re_schema,
-       .set_config = ec_node_re_set_config,
-       .parse = ec_node_re_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_re),
-       .free_priv = ec_node_re_free_priv,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_re_type);
-
-int ec_node_re_set_regexp(struct ec_node *gen_node, const char *str)
-{
-       struct ec_config *config = NULL;
-       int ret;
-
-       EC_CHECK_ARG(str != NULL, -1, EINVAL);
-
-       if (ec_node_check_type(gen_node, &ec_node_re_type) < 0)
-               goto fail;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(config, "pattern", ec_config_string(str));
-       if (ret < 0)
-               goto fail;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL;
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       return -1;
-}
-
-struct ec_node *ec_node_re(const char *id, const char *re_str)
-{
-       struct ec_node *gen_node = NULL;
-
-       gen_node = ec_node_from_type(&ec_node_re_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       if (ec_node_re_set_regexp(gen_node, re_str) < 0)
-               goto fail;
-
-       return gen_node;
-
-fail:
-       ec_node_free(gen_node);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_re_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_re(EC_NO_ID, "fo+|bar");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_re_test = {
-       .name = "node_re",
-       .test = ec_node_re_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_re_test);
diff --git a/libecoli/ecoli_node_re.h b/libecoli/ecoli_node_re.h
deleted file mode 100644 (file)
index bc3a317..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_RE_
-#define ECOLI_NODE_RE_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_re(const char *id, const char *str);
-
-/* re is duplicated */
-int ec_node_re_set_regexp(struct ec_node *node, const char *re);
-
-#endif
diff --git a/libecoli/ecoli_node_re_lex.c b/libecoli/ecoli_node_re_lex.c
deleted file mode 100644 (file)
index 4ebb9fd..0000000
+++ /dev/null
@@ -1,564 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <string.h>
-#include <regex.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_keyval.h>
-#include <ecoli_node.h>
-#include <ecoli_complete.h>
-#include <ecoli_parse.h>
-#include <ecoli_config.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_int.h>
-#include <ecoli_node_re_lex.h>
-
-EC_LOG_TYPE_REGISTER(node_re_lex);
-
-struct regexp_pattern {
-       char *pattern;
-       char *attr_name;
-       regex_t r;
-       bool keep;
-};
-
-struct ec_node_re_lex {
-       struct ec_node gen;
-       struct ec_node *child;
-       struct regexp_pattern *table;
-       size_t len;
-};
-
-static struct ec_strvec *
-tokenize(struct regexp_pattern *table, size_t table_len, const char *str)
-{
-       struct ec_strvec *strvec = NULL;
-       struct ec_keyval *attrs = NULL;
-       char *dup = NULL;
-       char c;
-       size_t len, off = 0;
-       size_t i;
-       int ret;
-       regmatch_t pos;
-
-       dup = ec_strdup(str);
-       if (dup == NULL)
-               goto fail;
-
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       len = strlen(dup);
-       while (off < len) {
-               for (i = 0; i < table_len; i++) {
-                       ret = regexec(&table[i].r, &dup[off], 1, &pos, 0);
-                       if (ret != 0)
-                               continue;
-                       if (pos.rm_so != 0 || pos.rm_eo == 0) {
-                               ret = -1;
-                               continue;
-                       }
-
-                       if (table[i].keep == 0)
-                               break;
-
-                       c = dup[pos.rm_eo + off];
-                       dup[pos.rm_eo + off] = '\0';
-                       EC_LOG(EC_LOG_DEBUG, "re_lex match <%s>\n", &dup[off]);
-                       if (ec_strvec_add(strvec, &dup[off]) < 0)
-                               goto fail;
-
-                       if (table[i].attr_name != NULL) {
-                               attrs = ec_keyval();
-                               if (attrs == NULL)
-                                       goto fail;
-                               if (ec_keyval_set(attrs, table[i].attr_name,
-                                               NULL, NULL) < 0)
-                                       goto fail;
-                               if (ec_strvec_set_attrs(strvec,
-                                               ec_strvec_len(strvec) - 1,
-                                               attrs) < 0) {
-                                       attrs = NULL;
-                                       goto fail;
-                               }
-                               attrs = NULL;
-                       }
-
-                       dup[pos.rm_eo + off] = c;
-                       break;
-               }
-
-               if (ret != 0)
-                       goto fail;
-
-               off += pos.rm_eo;
-       }
-
-       ec_free(dup);
-       return strvec;
-
-fail:
-       ec_free(dup);
-       ec_strvec_free(strvec);
-       return NULL;
-}
-
-static int
-ec_node_re_lex_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-       struct ec_strvec *new_vec = NULL;
-       struct ec_parse *child_parse;
-       const char *str;
-       int ret;
-
-       if (node->child == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (ec_strvec_len(strvec) == 0) {
-               new_vec = ec_strvec();
-       } else {
-               str = ec_strvec_val(strvec, 0);
-               new_vec = tokenize(node->table, node->len, str);
-       }
-       if (new_vec == NULL)
-               goto fail;
-
-       ret = ec_node_parse_child(node->child, state, new_vec);
-       if (ret < 0)
-               goto fail;
-
-       if ((unsigned)ret == ec_strvec_len(new_vec)) {
-               ret = 1;
-       } else if (ret != EC_PARSE_NOMATCH) {
-               child_parse = ec_parse_get_last_child(state);
-               ec_parse_unlink_child(state, child_parse);
-               ec_parse_free(child_parse);
-               ret = EC_PARSE_NOMATCH;
-       }
-
-       ec_strvec_free(new_vec);
-       new_vec = NULL;
-
-       return ret;
-
- fail:
-       ec_strvec_free(new_vec);
-       return -1;
-}
-
-static void ec_node_re_lex_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-       unsigned int i;
-
-       ec_node_free(node->child);
-       for (i = 0; i < node->len; i++) {
-               ec_free(node->table[i].pattern);
-               ec_free(node->table[i].attr_name);
-               regfree(&node->table[i].r);
-       }
-
-       ec_free(node->table);
-}
-
-static size_t
-ec_node_re_lex_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_re_lex_get_child(const struct ec_node *gen_node, size_t i,
-                       struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *child = node->child;
-       *refs = 2;
-       return 0;
-}
-
-static const struct ec_config_schema ec_node_re_lex_dict[] = {
-       {
-               .key = "pattern",
-               .desc = "The pattern to match.",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .key = "keep",
-               .desc = "Whether to keep or drop the string matching "
-               "the regular expression.",
-               .type = EC_CONFIG_TYPE_BOOL,
-       },
-       {
-               .key = "attr",
-               .desc = "The optional attribute name to attach.",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema ec_node_re_lex_elt[] = {
-       {
-               .desc = "A pattern element.",
-               .type = EC_CONFIG_TYPE_DICT,
-               .subschema = ec_node_re_lex_dict,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema ec_node_re_lex_schema[] = {
-       {
-               .key = "patterns",
-               .desc = "The list of patterns elements.",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = ec_node_re_lex_elt,
-       },
-       {
-               .key = "child",
-               .desc = "The child node.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_re_lex_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
-       struct regexp_pattern *table = NULL;
-       const struct ec_config *patterns, *child, *elt, *pattern, *keep, *attr;
-       char *pattern_str = NULL, *attr_name = NULL;
-       ssize_t i, n = 0;
-       int ret;
-
-       child = ec_config_dict_get(config, "child");
-       if (child == NULL)
-               goto fail;
-       if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       patterns = ec_config_dict_get(config, "patterns");
-       if (patterns != NULL) {
-               n = ec_config_count(patterns);
-               if (n < 0)
-                       goto fail;
-
-               table = ec_calloc(n, sizeof(*table));
-               if (table == NULL)
-                       goto fail;
-
-               n = 0;
-               TAILQ_FOREACH(elt, &patterns->list, next) {
-                       if (ec_config_get_type(elt) != EC_CONFIG_TYPE_DICT) {
-                               errno = EINVAL;
-                               goto fail;
-                       }
-                       pattern = ec_config_dict_get(elt, "pattern");
-                       if (pattern == NULL) {
-                               errno = EINVAL;
-                               goto fail;
-                       }
-                       if (ec_config_get_type(pattern) != EC_CONFIG_TYPE_STRING) {
-                               errno = EINVAL;
-                               goto fail;
-                       }
-                       keep = ec_config_dict_get(elt, "keep");
-                       if (keep == NULL) {
-                               errno = EINVAL;
-                               goto fail;
-                       }
-                       if (ec_config_get_type(keep) != EC_CONFIG_TYPE_BOOL) {
-                               errno = EINVAL;
-                               goto fail;
-                       }
-                       attr = ec_config_dict_get(elt, "attr");
-                       if (attr != NULL && ec_config_get_type(attr) !=
-                                       EC_CONFIG_TYPE_STRING) {
-                               errno = EINVAL;
-                               goto fail;
-                       }
-                       pattern_str = ec_strdup(pattern->string);
-                       if (pattern_str == NULL)
-                               goto fail;
-                       if (attr != NULL && attr->string != NULL) {
-                               attr_name = ec_strdup(attr->string);
-                               if (attr_name == NULL)
-                                       goto fail;
-                       }
-
-                       ret = regcomp(&table[n].r, pattern_str, REG_EXTENDED);
-                       if (ret != 0) {
-                               EC_LOG(EC_LOG_ERR,
-                                       "Regular expression <%s> compilation failed: %d\n",
-                                       pattern_str, ret);
-                               if (ret == REG_ESPACE)
-                                       errno = ENOMEM;
-                               else
-                                       errno = EINVAL;
-                               goto fail;
-                       }
-                       table[n].pattern = pattern_str;
-                       table[n].keep = keep->boolean;
-                       table[n].attr_name = attr_name;
-                       pattern_str = NULL;
-                       attr_name = NULL;
-
-                       n++;
-               }
-       }
-
-       if (node->child != NULL)
-               ec_node_free(node->child);
-       node->child = ec_node_clone(child->node);
-       for (i = 0; i < (ssize_t)node->len; i++) {
-               ec_free(node->table[i].pattern);
-               regfree(&node->table[i].r);
-       }
-       ec_free(node->table);
-       node->table = table;
-       node->len = n;
-
-       return 0;
-
-fail:
-       if (table != NULL) {
-               for (i = 0; i < n; i++) {
-                       if (table[i].pattern != NULL) {
-                               ec_free(table[i].pattern);
-                               regfree(&table[i].r);
-                       }
-               }
-       }
-       ec_free(table);
-       ec_free(pattern_str);
-       return -1;
-}
-
-static struct ec_node_type ec_node_re_lex_type = {
-       .name = "re_lex",
-       .schema = ec_node_re_lex_schema,
-       .set_config = ec_node_re_lex_set_config,
-       .parse = ec_node_re_lex_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_re_lex),
-       .free_priv = ec_node_re_lex_free_priv,
-       .get_children_count = ec_node_re_lex_get_children_count,
-       .get_child = ec_node_re_lex_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_re_lex_type);
-
-int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep,
-       const char *attr_name)
-{
-       const struct ec_config *cur_config = NULL;
-       struct ec_config *config = NULL, *patterns = NULL, *elt = NULL;
-       int ret;
-
-       if (ec_node_check_type(gen_node, &ec_node_re_lex_type) < 0)
-               goto fail;
-
-       elt = ec_config_dict();
-       if (elt == NULL)
-               goto fail;
-       if (ec_config_dict_set(elt, "pattern", ec_config_string(pattern)) < 0)
-               goto fail;
-       if (ec_config_dict_set(elt, "keep", ec_config_bool(keep)) < 0)
-               goto fail;
-       if (attr_name != NULL) {
-               if (ec_config_dict_set(elt, "attr",
-                                       ec_config_string(attr_name)) < 0)
-                       goto fail;
-       }
-
-       cur_config = ec_node_get_config(gen_node);
-       if (cur_config == NULL)
-               config = ec_config_dict();
-       else
-               config = ec_config_dup(cur_config);
-       if (config == NULL)
-               goto fail;
-
-       patterns = ec_config_dict_get(config, "patterns");
-       if (patterns == NULL) {
-               patterns = ec_config_list();
-               if (patterns == NULL)
-                       goto fail;
-
-               if (ec_config_dict_set(config, "patterns", patterns) < 0)
-                       goto fail; /* patterns list is freed on error */
-       }
-
-       if (ec_config_list_add(patterns, elt) < 0) {
-               elt = NULL;
-               goto fail;
-       }
-       elt = NULL;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       ec_config_free(elt);
-       return -1;
-}
-
-static int
-ec_node_re_lex_set_child(struct ec_node *gen_node, struct ec_node *child)
-{
-       const struct ec_config *cur_config = NULL;
-       struct ec_config *config = NULL;
-       int ret;
-
-       if (ec_node_check_type(gen_node, &ec_node_re_lex_type) < 0)
-               goto fail;
-
-       cur_config = ec_node_get_config(gen_node);
-       if (cur_config == NULL)
-               config = ec_config_dict();
-       else
-               config = ec_config_dup(cur_config);
-       if (config == NULL)
-               goto fail;
-
-       if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) {
-               child = NULL; /* freed */
-               goto fail;
-       }
-       child = NULL; /* freed */
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child)
-{
-       struct ec_node *gen_node = NULL;
-
-       if (child == NULL)
-               return NULL;
-
-       gen_node = ec_node_from_type(&ec_node_re_lex_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       if (ec_node_re_lex_set_child(gen_node, child) < 0) {
-               child = NULL; /* freed */
-               goto fail;
-       }
-
-       return gen_node;
-
-fail:
-       ec_node_free(gen_node);
-       ec_node_free(child);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_re_lex_testcase(void)
-{
-       struct ec_node *node;
-       int ret, testres = 0;
-
-       node = ec_node_re_lex(EC_NO_ID,
-               ec_node_many(EC_NO_ID,
-                       EC_NODE_OR(EC_NO_ID,
-                               ec_node_str(EC_NO_ID, "foo"),
-                               ec_node_str(EC_NO_ID, "bar"),
-                               ec_node_int(EC_NO_ID, 0, 1000, 0)
-                       ), 0, 0
-               )
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-
-       ret = ec_node_re_lex_add(node, "[a-zA-Z]+", 1, NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "[0-9]+", 1, NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "=", 1, NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "-", 1, NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "\\+", 1, NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       ret = ec_node_re_lex_add(node, "[       ]+", 0, NULL);
-       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
-       if (ret != 0) {
-               EC_LOG(EC_LOG_ERR, "cannot add regexp to node\n");
-               ec_node_free(node);
-               return -1;
-       }
-
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo bar  324 bar234");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar324");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
-
-       /* no completion */
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_re_lex_test = {
-       .name = "node_re_lex",
-       .test = ec_node_re_lex_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_re_lex_test);
diff --git a/libecoli/ecoli_node_re_lex.h b/libecoli/ecoli_node_re_lex.h
deleted file mode 100644 (file)
index 94d426e..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_RE_LEX_
-#define ECOLI_NODE_RE_LEX_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child);
-
-int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep,
-       const char *attr_name);
-
-#endif
diff --git a/libecoli/ecoli_node_seq.c b/libecoli/ecoli_node_seq.c
deleted file mode 100644 (file)
index ff0c5de..0000000
+++ /dev/null
@@ -1,442 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_helper.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_option.h>
-#include <ecoli_node_or.h>
-#include <ecoli_node_many.h>
-#include <ecoli_node_seq.h>
-
-EC_LOG_TYPE_REGISTER(node_seq);
-
-struct ec_node_seq {
-       struct ec_node gen;
-       struct ec_node **table;
-       size_t len;
-};
-
-static int
-ec_node_seq_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       struct ec_strvec *childvec = NULL;
-       size_t len = 0;
-       unsigned int i;
-       int ret;
-
-       for (i = 0; i < node->len; i++) {
-               childvec = ec_strvec_ndup(strvec, len,
-                       ec_strvec_len(strvec) - len);
-               if (childvec == NULL)
-                       goto fail;
-
-               ret = ec_node_parse_child(node->table[i], state, childvec);
-               if (ret < 0)
-                       goto fail;
-
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if (ret == EC_PARSE_NOMATCH) {
-                       ec_parse_free_children(state);
-                       return EC_PARSE_NOMATCH;
-               }
-
-               len += ret;
-       }
-
-       return len;
-
-fail:
-       ec_strvec_free(childvec);
-       return -1;
-}
-
-static int
-__ec_node_seq_complete(struct ec_node **table, size_t table_len,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_parse *parse = ec_comp_get_state(comp);
-       struct ec_strvec *childvec = NULL;
-       unsigned int i;
-       int ret;
-
-       if (table_len == 0)
-               return 0;
-
-       /*
-        * Example of completion for a sequence node = [n1,n2] and an
-        * input = [a,b,c,d]:
-        *
-        * result = complete(n1, [a,b,c,d]) +
-        *    complete(n2, [b,c,d]) if n1 matches [a] +
-        *    complete(n2, [c,d]) if n1 matches [a,b] +
-        *    complete(n2, [d]) if n1 matches [a,b,c] +
-        *    complete(n2, []) if n1 matches [a,b,c,d]
-        */
-
-       /* first, try to complete with the first node of the table */
-       ret = ec_node_complete_child(table[0], comp, strvec);
-       if (ret < 0)
-               goto fail;
-
-       /* then, if the first node of the table matches the beginning of the
-        * strvec, try to complete the rest */
-       for (i = 0; i < ec_strvec_len(strvec); i++) {
-               childvec = ec_strvec_ndup(strvec, 0, i);
-               if (childvec == NULL)
-                       goto fail;
-
-               ret = ec_node_parse_child(table[0], parse, childvec);
-               if (ret < 0)
-                       goto fail;
-
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if ((unsigned int)ret != i) {
-                       if (ret != EC_PARSE_NOMATCH)
-                               ec_parse_del_last_child(parse);
-                       continue;
-               }
-
-               childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i);
-               if (childvec == NULL) {
-                       ec_parse_del_last_child(parse);
-                       goto fail;
-               }
-
-               ret = __ec_node_seq_complete(&table[1],
-                                       table_len - 1,
-                                       comp, childvec);
-               ec_parse_del_last_child(parse);
-               ec_strvec_free(childvec);
-               childvec = NULL;
-
-               if (ret < 0)
-                       goto fail;
-       }
-
-       return 0;
-
-fail:
-       ec_strvec_free(childvec);
-       return -1;
-}
-
-static int
-ec_node_seq_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-
-       return __ec_node_seq_complete(node->table, node->len, comp,
-                               strvec);
-}
-
-static void ec_node_seq_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       size_t i;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = NULL;
-       node->len = 0;
-}
-
-static const struct ec_config_schema ec_node_seq_subschema[] = {
-       {
-               .desc = "A child node which is part of the sequence.",
-               .type = EC_CONFIG_TYPE_NODE,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static const struct ec_config_schema ec_node_seq_schema[] = {
-       {
-               .key = "children",
-               .desc = "The list of children nodes, to be parsed in sequence.",
-               .type = EC_CONFIG_TYPE_LIST,
-               .subschema = ec_node_seq_subschema,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_seq_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       struct ec_node **table = NULL;
-       size_t i, len = 0;
-
-       table = ec_node_config_node_list_to_table(
-               ec_config_dict_get(config, "children"), &len);
-       if (table == NULL)
-               goto fail;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-       node->table = table;
-       node->len = len;
-
-       return 0;
-
-fail:
-       for (i = 0; i < len; i++)
-               ec_node_free(table[i]);
-       ec_free(table);
-       return -1;
-}
-
-static size_t
-ec_node_seq_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       return node->len;
-}
-
-static int
-ec_node_seq_get_child(const struct ec_node *gen_node, size_t i,
-               struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-
-       if (i >= node->len)
-               return -1;
-
-       *child = node->table[i];
-       /* each child node is referenced twice: once in the config and
-        * once in the node->table[] */
-       *refs = 2;
-       return 0;
-}
-
-static struct ec_node_type ec_node_seq_type = {
-       .name = "seq",
-       .schema = ec_node_seq_schema,
-       .set_config = ec_node_seq_set_config,
-       .parse = ec_node_seq_parse,
-       .complete = ec_node_seq_complete,
-       .size = sizeof(struct ec_node_seq),
-       .free_priv = ec_node_seq_free_priv,
-       .get_children_count = ec_node_seq_get_children_count,
-       .get_child = ec_node_seq_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_seq_type);
-
-int ec_node_seq_add(struct ec_node *gen_node, struct ec_node *child)
-{
-       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
-       const struct ec_config *cur_config = NULL;
-       struct ec_config *config = NULL, *children;
-       int ret;
-
-       assert(node != NULL);
-
-       /* XXX factorize this code in a helper */
-
-       if (ec_node_check_type(gen_node, &ec_node_seq_type) < 0)
-               goto fail;
-
-       cur_config = ec_node_get_config(gen_node);
-       if (cur_config == NULL)
-               config = ec_config_dict();
-       else
-               config = ec_config_dup(cur_config);
-       if (config == NULL)
-               goto fail;
-
-       children = ec_config_dict_get(config, "children");
-       if (children == NULL) {
-               children = ec_config_list();
-               if (children == NULL)
-                       goto fail;
-
-               if (ec_config_dict_set(config, "children", children) < 0)
-                       goto fail; /* children list is freed on error */
-       }
-
-       if (ec_config_list_add(children, ec_config_node(child)) < 0) {
-               child = NULL;
-               goto fail;
-       }
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *__ec_node_seq(const char *id, ...)
-{
-       struct ec_config *config = NULL, *children = NULL;
-       struct ec_node *gen_node = NULL;
-       struct ec_node *child;
-       va_list ap;
-       int ret;
-
-       va_start(ap, id);
-       child = va_arg(ap, struct ec_node *);
-
-       gen_node = ec_node_from_type(&ec_node_seq_type, id);
-       if (gen_node == NULL)
-               goto fail_free_children;
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail_free_children;
-
-       children = ec_config_list();
-       if (children == NULL)
-               goto fail_free_children;
-
-       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) {
-               if (child == NULL)
-                       goto fail_free_children;
-
-               if (ec_config_list_add(children, ec_config_node(child)) < 0) {
-                       child = NULL;
-                       goto fail_free_children;
-               }
-       }
-
-       if (ec_config_dict_set(config, "children", children) < 0) {
-               children = NULL; /* freed */
-               goto fail;
-       }
-       children = NULL;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL; /* freed */
-       if (ret < 0)
-               goto fail;
-
-       va_end(ap);
-
-       return gen_node;
-
-fail_free_children:
-       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *))
-               ec_node_free(child);
-fail:
-       ec_node_free(gen_node); /* will also free added children */
-       ec_config_free(children);
-       ec_config_free(config);
-       va_end(ap);
-
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_seq_testcase(void)
-{
-       struct ec_node *node = NULL;
-       int testres = 0;
-
-       node = EC_NODE_SEQ(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_str(EC_NO_ID, "bar")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "toto");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foox", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "barx");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "", "foo");
-
-       testres |= (ec_node_seq_add(node, ec_node_str(EC_NO_ID, "grr")) < 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "grr");
-
-       ec_node_free(node);
-
-       /* test completion */
-       node = EC_NODE_SEQ(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "toto")),
-               ec_node_str(EC_NO_ID, "bar")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "", EC_NODE_ENDLIST,
-               "bar", "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "t", EC_NODE_ENDLIST,
-               "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "b", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", "bar", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foobarx", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_seq_test = {
-       .name = "node_seq",
-       .test = ec_node_seq_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_seq_test);
diff --git a/libecoli/ecoli_node_seq.h b/libecoli/ecoli_node_seq.h
deleted file mode 100644 (file)
index 21c96b1..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_SEQ_
-#define ECOLI_NODE_SEQ_
-
-#include <ecoli_node.h>
-
-#define EC_NODE_SEQ(args...) __ec_node_seq(args, EC_NODE_ENDLIST)
-
-/* list must be terminated with EC_NODE_ENDLIST */
-/* all nodes given in the list will be freed when freeing this one */
-/* avoid using this function directly, prefer the macro EC_NODE_SEQ() or
- * ec_node_seq() + ec_node_seq_add() */
-struct ec_node *__ec_node_seq(const char *id, ...);
-
-struct ec_node *ec_node_seq(const char *id);
-
-/* child is consumed */
-int ec_node_seq_add(struct ec_node *node, struct ec_node *child);
-
-#endif
diff --git a/libecoli/ecoli_node_sh_lex.c b/libecoli/ecoli_node_sh_lex.c
deleted file mode 100644 (file)
index e27f21b..0000000
+++ /dev/null
@@ -1,491 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_string.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_option.h>
-#include <ecoli_node_sh_lex.h>
-
-EC_LOG_TYPE_REGISTER(node_sh_lex);
-
-struct ec_node_sh_lex {
-       struct ec_node gen;
-       struct ec_node *child;
-};
-
-static size_t eat_spaces(const char *str)
-{
-       size_t i = 0;
-
-       /* skip spaces */
-       while (isblank(str[i]))
-               i++;
-
-       return i;
-}
-
-/*
- * Allocate a new string which is a copy of the input string with quotes
- * removed. If quotes are not closed properly, set missing_quote to the
- * missing quote char.
- */
-static char *unquote_str(const char *str, size_t n, int allow_missing_quote,
-       char *missing_quote)
-{
-       unsigned s = 1, d = 0;
-       char quote = str[0];
-       char *dst;
-       int closed = 0;
-
-       dst = ec_malloc(n);
-       if (dst == NULL) {
-               errno = ENOMEM;
-               return NULL;
-       }
-
-       /* copy string and remove quotes */
-       while (s < n && d < n && str[s] != '\0') {
-               if (str[s] == '\\' && str[s+1] == quote) {
-                       dst[d++] = quote;
-                       s += 2;
-                       continue;
-               }
-               if (str[s] == '\\' && str[s+1] == '\\') {
-                       dst[d++] = '\\';
-                       s += 2;
-                       continue;
-               }
-               if (str[s] == quote) {
-                       s++;
-                       closed = 1;
-                       break;
-               }
-               dst[d++] = str[s++];
-       }
-
-       /* not enough room in dst buffer (should not happen) */
-       if (d >= n) {
-               ec_free(dst);
-               errno = EMSGSIZE;
-               return NULL;
-       }
-
-       /* quote not closed */
-       if (closed == 0) {
-               if (missing_quote != NULL)
-                       *missing_quote = str[0];
-               if (allow_missing_quote == 0) {
-                       ec_free(dst);
-                       errno = EBADMSG;
-                       return NULL;
-               }
-       }
-       dst[d++] = '\0';
-
-       return dst;
-}
-
-static size_t eat_quoted_str(const char *str)
-{
-       size_t i = 0;
-       char quote = str[0];
-
-       while (str[i] != '\0') {
-               if (str[i] != '\\' && str[i+1] == quote)
-                       return i + 2;
-               i++;
-       }
-
-       /* unclosed quote, will be detected later */
-       return i;
-}
-
-static size_t eat_str(const char *str)
-{
-       size_t i = 0;
-
-       /* eat chars until we find a quote, space, or end of string  */
-       while (!isblank(str[i]) && str[i] != '\0' &&
-                       str[i] != '"' && str[i] != '\'')
-               i++;
-
-       return i;
-}
-
-static struct ec_strvec *tokenize(const char *str, int completion,
-       int allow_missing_quote, char *missing_quote)
-{
-       struct ec_strvec *strvec = NULL;
-       size_t off = 0, len, suboff, sublen;
-       char *word = NULL, *concat = NULL, *tmp;
-       int last_is_space = 1;
-
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       while (str[off] != '\0') {
-               if (missing_quote != NULL)
-                       *missing_quote = '\0';
-               len = eat_spaces(&str[off]);
-               if (len > 0)
-                       last_is_space = 1;
-               off += len;
-
-               len = 0;
-               suboff = off;
-               while (str[suboff] != '\0') {
-                       if (missing_quote != NULL)
-                               *missing_quote = '\0';
-                       last_is_space = 0;
-                       if (str[suboff] == '"' || str[suboff] == '\'') {
-                               sublen = eat_quoted_str(&str[suboff]);
-                               word = unquote_str(&str[suboff], sublen,
-                                       allow_missing_quote, missing_quote);
-                       } else {
-                               sublen = eat_str(&str[suboff]);
-                               if (sublen == 0)
-                                       break;
-                               word = ec_strndup(&str[suboff], sublen);
-                       }
-
-                       if (word == NULL)
-                               goto fail;
-
-                       len += sublen;
-                       suboff += sublen;
-
-                       if (concat == NULL) {
-                               concat = word;
-                               word = NULL;
-                       } else {
-                               tmp = ec_realloc(concat, len + 1);
-                               if (tmp == NULL)
-                                       goto fail;
-                               concat = tmp;
-                               strcat(concat, word);
-                               ec_free(word);
-                               word = NULL;
-                       }
-               }
-
-               if (concat != NULL) {
-                       if (ec_strvec_add(strvec, concat) < 0)
-                               goto fail;
-                       ec_free(concat);
-                       concat = NULL;
-               }
-
-               off += len;
-       }
-
-       /* in completion mode, append an empty string in the vector if
-        * the input string ends with space */
-       if (completion && last_is_space) {
-               if (ec_strvec_add(strvec, "") < 0)
-                       goto fail;
-       }
-
-       return strvec;
-
- fail:
-       ec_free(word);
-       ec_free(concat);
-       ec_strvec_free(strvec);
-       return NULL;
-}
-
-static int
-ec_node_sh_lex_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-       struct ec_strvec *new_vec = NULL;
-       struct ec_parse *child_parse;
-       const char *str;
-       int ret;
-
-       if (ec_strvec_len(strvec) == 0) {
-               new_vec = ec_strvec();
-       } else {
-               str = ec_strvec_val(strvec, 0);
-               new_vec = tokenize(str, 0, 0, NULL);
-       }
-       if (new_vec == NULL && errno == EBADMSG) /* quotes not closed */
-               return EC_PARSE_NOMATCH;
-       if (new_vec == NULL)
-               goto fail;
-
-       ret = ec_node_parse_child(node->child, state, new_vec);
-       if (ret < 0)
-               goto fail;
-
-       if ((unsigned)ret == ec_strvec_len(new_vec)) {
-               ret = 1;
-       } else if (ret != EC_PARSE_NOMATCH) {
-               child_parse = ec_parse_get_last_child(state);
-               ec_parse_unlink_child(state, child_parse);
-               ec_parse_free(child_parse);
-               ret = EC_PARSE_NOMATCH;
-       }
-
-       ec_strvec_free(new_vec);
-       new_vec = NULL;
-
-       return ret;
-
- fail:
-       ec_strvec_free(new_vec);
-       return -1;
-}
-
-static int
-ec_node_sh_lex_complete(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-       struct ec_comp *tmp_comp = NULL;
-       struct ec_strvec *new_vec = NULL;
-       struct ec_comp_iter *iter = NULL;
-       struct ec_comp_item *item = NULL;
-       char *new_str = NULL;
-       const char *str;
-       char missing_quote = '\0';
-       int ret;
-
-       if (ec_strvec_len(strvec) != 1)
-               return 0;
-
-       str = ec_strvec_val(strvec, 0);
-       new_vec = tokenize(str, 1, 1, &missing_quote);
-       if (new_vec == NULL)
-               goto fail;
-
-       /* we will store the completions in a temporary struct, because
-        * we want to update them (ex: add missing quotes) */
-       tmp_comp = ec_comp(ec_comp_get_state(comp));
-       if (tmp_comp == NULL)
-               goto fail;
-
-       ret = ec_node_complete_child(node->child, tmp_comp, new_vec);
-       if (ret < 0)
-               goto fail;
-
-       /* add missing quote for full completions  */
-       if (missing_quote != '\0') {
-               iter = ec_comp_iter(tmp_comp, EC_COMP_FULL);
-               if (iter == NULL)
-                       goto fail;
-               while ((item = ec_comp_iter_next(iter)) != NULL) {
-                       str = ec_comp_item_get_str(item);
-                       if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str,
-                                       missing_quote) < 0) {
-                               new_str = NULL;
-                               goto fail;
-                       }
-                       if (ec_comp_item_set_str(item, new_str) < 0)
-                               goto fail;
-                       ec_free(new_str);
-                       new_str = NULL;
-
-                       str = ec_comp_item_get_completion(item);
-                       if (ec_asprintf(&new_str, "%s%c", str,
-                                       missing_quote) < 0) {
-                               new_str = NULL;
-                               goto fail;
-                       }
-                       if (ec_comp_item_set_completion(item, new_str) < 0)
-                               goto fail;
-                       ec_free(new_str);
-                       new_str = NULL;
-               }
-       }
-
-       ec_comp_iter_free(iter);
-       ec_strvec_free(new_vec);
-
-       ec_comp_merge(comp, tmp_comp);
-
-       return 0;
-
- fail:
-       ec_comp_free(tmp_comp);
-       ec_comp_iter_free(iter);
-       ec_strvec_free(new_vec);
-       ec_free(new_str);
-
-       return -1;
-}
-
-static void ec_node_sh_lex_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-
-       ec_node_free(node->child);
-}
-
-static size_t
-ec_node_sh_lex_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-
-       if (node->child)
-               return 1;
-       return 0;
-}
-
-static int
-ec_node_sh_lex_get_child(const struct ec_node *gen_node, size_t i,
-       struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
-
-       if (i >= 1)
-               return -1;
-
-       *refs = 1;
-       *child = node->child;
-       return 0;
-}
-
-static struct ec_node_type ec_node_sh_lex_type = {
-       .name = "sh_lex",
-       .parse = ec_node_sh_lex_parse,
-       .complete = ec_node_sh_lex_complete,
-       .size = sizeof(struct ec_node_sh_lex),
-       .free_priv = ec_node_sh_lex_free_priv,
-       .get_children_count = ec_node_sh_lex_get_children_count,
-       .get_child = ec_node_sh_lex_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
-
-struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
-{
-       struct ec_node_sh_lex *node = NULL;
-
-       if (child == NULL)
-               return NULL;
-
-       node = (struct ec_node_sh_lex *)ec_node_from_type(&ec_node_sh_lex_type, id);
-       if (node == NULL) {
-               ec_node_free(child);
-               return NULL;
-       }
-
-       node->child = child;
-
-       return &node->gen;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_sh_lex_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_sh_lex(EC_NO_ID,
-               EC_NODE_SEQ(EC_NO_ID,
-                       ec_node_str(EC_NO_ID, "foo"),
-                       ec_node_option(EC_NO_ID,
-                               ec_node_str(EC_NO_ID, "toto")
-                       ),
-                       ec_node_str(EC_NO_ID, "bar")
-               )
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "  foo toto bar'");
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_sh_lex(EC_NO_ID,
-               EC_NODE_SEQ(EC_NO_ID,
-                       ec_node_str(EC_NO_ID, "foo"),
-                       ec_node_option(EC_NO_ID,
-                               ec_node_str(EC_NO_ID, "toto")
-                       ),
-                       ec_node_str(EC_NO_ID, "bar"),
-                       ec_node_str(EC_NO_ID, "titi")
-               )
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               " ", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo ", EC_NODE_ENDLIST,
-               "bar", "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo t", EC_NODE_ENDLIST,
-               "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo b", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo bar", EC_NODE_ENDLIST,
-               "bar", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo bar ", EC_NODE_ENDLIST,
-               "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo toto bar ", EC_NODE_ENDLIST,
-               "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo barx", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo 'b", EC_NODE_ENDLIST,
-               "'bar'", EC_NODE_ENDLIST);
-
-       ec_node_free(node);
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_sh_lex_test = {
-       .name = "node_sh_lex",
-       .test = ec_node_sh_lex_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_sh_lex_test);
diff --git a/libecoli/ecoli_node_sh_lex.h b/libecoli/ecoli_node_sh_lex.h
deleted file mode 100644 (file)
index d45b998..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_SHLEX_
-#define ECOLI_NODE_SHLEX_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child);
-
-#endif
diff --git a/libecoli/ecoli_node_space.c b/libecoli/ecoli_node_space.c
deleted file mode 100644 (file)
index 761ed76..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_malloc.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_space.h>
-
-EC_LOG_TYPE_REGISTER(node_space);
-
-struct ec_node_space {
-       struct ec_node gen;
-};
-
-static int
-ec_node_space_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       const char *str;
-       size_t len = 0;
-
-       (void)state;
-       (void)gen_node;
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       str = ec_strvec_val(strvec, 0);
-       while (isspace(str[len]))
-               len++;
-       if (len == 0 || len != strlen(str))
-               return EC_PARSE_NOMATCH;
-
-       return 1;
-}
-
-static struct ec_node_type ec_node_space_type = {
-       .name = "space",
-       .parse = ec_node_space_parse,
-       .complete = ec_node_complete_unknown,
-       .size = sizeof(struct ec_node_space),
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_space_type);
-
-/* LCOV_EXCL_START */
-static int ec_node_space_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node("space", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, " ");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, " ", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo ");
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node("space", EC_NO_ID);
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       /* never completes whatever the input */
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               " ", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_space_test = {
-       .name = "space",
-       .test = ec_node_space_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_space_test);
diff --git a/libecoli/ecoli_node_space.h b/libecoli/ecoli_node_space.h
deleted file mode 100644 (file)
index 0dd6202..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * This node matches one string in the vector if it is only composed of
- * spaces, as interpreted by isspace().
- */
-
-#ifndef ECOLI_NODE_SPACE_
-#define ECOLI_NODE_SPACE_
-
-/* no API for now, since there is no specific configuration for this node */
-
-#endif
diff --git a/libecoli/ecoli_node_str.c b/libecoli/ecoli_node_str.c
deleted file mode 100644 (file)
index d53ea39..0000000
+++ /dev/null
@@ -1,274 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_str.h>
-
-EC_LOG_TYPE_REGISTER(node_str);
-
-struct ec_node_str {
-       struct ec_node gen;
-       char *string;
-       unsigned len;
-};
-
-static int
-ec_node_str_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-       const char *str;
-
-       (void)state;
-
-       if (node->string == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       if (ec_strvec_len(strvec) == 0)
-               return EC_PARSE_NOMATCH;
-
-       str = ec_strvec_val(strvec, 0);
-       if (strcmp(str, node->string) != 0)
-               return EC_PARSE_NOMATCH;
-
-       return 1;
-}
-
-static int
-ec_node_str_complete(const struct ec_node *gen_node,
-               struct ec_comp *comp,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-       const char *str;
-       size_t n = 0;
-
-       if (ec_strvec_len(strvec) != 1)
-               return 0;
-
-       str = ec_strvec_val(strvec, 0);
-       for (n = 0; n < node->len; n++) {
-               if (str[n] != node->string[n])
-                       break;
-       }
-
-       /* no completion */
-       if (str[n] != '\0')
-               return EC_PARSE_NOMATCH;
-
-       if (ec_comp_add_item(comp, gen_node, NULL, EC_COMP_FULL,
-                                       str, node->string) < 0)
-               return -1;
-
-       return 0;
-}
-
-static const char *ec_node_str_desc(const struct ec_node *gen_node)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-
-       return node->string;
-}
-
-static void ec_node_str_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-
-       ec_free(node->string);
-}
-
-static const struct ec_config_schema ec_node_str_schema[] = {
-       {
-               .key = "string",
-               .desc = "The string to match.",
-               .type = EC_CONFIG_TYPE_STRING,
-       },
-       {
-               .type = EC_CONFIG_TYPE_NONE,
-       },
-};
-
-static int ec_node_str_set_config(struct ec_node *gen_node,
-                               const struct ec_config *config)
-{
-       struct ec_node_str *node = (struct ec_node_str *)gen_node;
-       const struct ec_config *value = NULL;
-       char *s = NULL;
-
-       value = ec_config_dict_get(config, "string");
-       if (value == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       s = ec_strdup(value->string);
-       if (s == NULL)
-               goto fail;
-
-       ec_free(node->string);
-       node->string = s;
-       node->len = strlen(node->string);
-
-       return 0;
-
-fail:
-       ec_free(s);
-       return -1;
-}
-
-static struct ec_node_type ec_node_str_type = {
-       .name = "str",
-       .schema = ec_node_str_schema,
-       .set_config = ec_node_str_set_config,
-       .parse = ec_node_str_parse,
-       .complete = ec_node_str_complete,
-       .desc = ec_node_str_desc,
-       .size = sizeof(struct ec_node_str),
-       .free_priv = ec_node_str_free_priv,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_str_type);
-
-int ec_node_str_set_str(struct ec_node *gen_node, const char *str)
-{
-       struct ec_config *config = NULL;
-       int ret;
-
-       if (ec_node_check_type(gen_node, &ec_node_str_type) < 0)
-               goto fail;
-
-       if (str == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       config = ec_config_dict();
-       if (config == NULL)
-               goto fail;
-
-       ret = ec_config_dict_set(config, "string", ec_config_string(str));
-       if (ret < 0)
-               goto fail;
-
-       ret = ec_node_set_config(gen_node, config);
-       config = NULL;
-       if (ret < 0)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_config_free(config);
-       return -1;
-}
-
-struct ec_node *ec_node_str(const char *id, const char *str)
-{
-       struct ec_node *gen_node = NULL;
-
-       gen_node = ec_node_from_type(&ec_node_str_type, id);
-       if (gen_node == NULL)
-               goto fail;
-
-       if (ec_node_str_set_str(gen_node, str) < 0)
-               goto fail;
-
-       return gen_node;
-
-fail:
-       ec_node_free(gen_node);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_str_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = ec_node_str(EC_NO_ID, "foo");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK(!strcmp(ec_node_desc(node), "foo"),
-               "Invalid node description.");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       ec_node_free(node);
-
-       node = ec_node_str(EC_NO_ID, "Здравствуйте");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте",
-               "John!");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
-       ec_node_free(node);
-
-       /* an empty string node always matches */
-       node = ec_node_str(EC_NO_ID, "");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
-       ec_node_free(node);
-
-       /* test completion */
-       node = ec_node_str(EC_NO_ID, "foo");
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "foo", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_str_test = {
-       .name = "node_str",
-       .test = ec_node_str_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_str_test);
diff --git a/libecoli/ecoli_node_str.h b/libecoli/ecoli_node_str.h
deleted file mode 100644 (file)
index 8a8634f..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_STR_
-#define ECOLI_NODE_STR_
-
-#include <ecoli_node.h>
-
-struct ec_node *ec_node_str(const char *id, const char *str);
-
-/* str is duplicated */
-int ec_node_str_set_str(struct ec_node *node, const char *str);
-
-#endif
diff --git a/libecoli/ecoli_node_subset.c b/libecoli/ecoli_node_subset.c
deleted file mode 100644 (file)
index e3184ef..0000000
+++ /dev/null
@@ -1,430 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <stdbool.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_node_subset.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_or.h>
-#include <ecoli_test.h>
-
-EC_LOG_TYPE_REGISTER(node_subset);
-
-struct ec_node_subset {
-       struct ec_node gen;
-       struct ec_node **table;
-       unsigned int len;
-};
-
-struct parse_result {
-       size_t parse_len;           /* number of parsed nodes */
-       size_t len;                 /* consumed strings */
-};
-
-/* recursively find the longest list of nodes that matches: the state is
- * updated accordingly. */
-static int
-__ec_node_subset_parse(struct parse_result *out, struct ec_node **table,
-               size_t table_len, struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node **child_table;
-       struct ec_strvec *childvec = NULL;
-       size_t i, j, len = 0;
-       struct parse_result best_result, result;
-       struct ec_parse *best_parse = NULL;
-       int ret;
-
-       if (table_len == 0)
-               return 0;
-
-       memset(&best_result, 0, sizeof(best_result));
-
-       child_table = ec_calloc(table_len - 1, sizeof(*child_table));
-       if (child_table == NULL)
-               goto fail;
-
-       for (i = 0; i < table_len; i++) {
-               /* try to parse elt i */
-               ret = ec_node_parse_child(table[i], state, strvec);
-               if (ret < 0)
-                       goto fail;
-
-               if (ret == EC_PARSE_NOMATCH)
-                       continue;
-
-               /* build a new table without elt i */
-               for (j = 0; j < table_len; j++) {
-                       if (j < i)
-                               child_table[j] = table[j];
-                       else if (j > i)
-                               child_table[j - 1] = table[j];
-               }
-
-               /* build a new strvec (ret is the len of matched strvec) */
-               len = ret;
-               childvec = ec_strvec_ndup(strvec, len,
-                                       ec_strvec_len(strvec) - len);
-               if (childvec == NULL)
-                       goto fail;
-
-               memset(&result, 0, sizeof(result));
-               ret = __ec_node_subset_parse(&result, child_table,
-                                       table_len - 1, state, childvec);
-               ec_strvec_free(childvec);
-               childvec = NULL;
-               if (ret < 0)
-                       goto fail;
-
-               /* if result is not the best, ignore */
-               if (result.parse_len < best_result.parse_len) {
-                       memset(&result, 0, sizeof(result));
-                       ec_parse_del_last_child(state);
-                       continue;
-               }
-
-               /* replace the previous best result */
-               ec_parse_free(best_parse);
-               best_parse = ec_parse_get_last_child(state);
-               ec_parse_unlink_child(state, best_parse);
-
-               best_result.parse_len = result.parse_len + 1;
-               best_result.len = len + result.len;
-
-               memset(&result, 0, sizeof(result));
-       }
-
-       *out = best_result;
-       ec_free(child_table);
-       if (best_parse != NULL)
-               ec_parse_link_child(state, best_parse);
-
-       return 0;
-
- fail:
-       ec_parse_free(best_parse);
-       ec_strvec_free(childvec);
-       ec_free(child_table);
-       return -1;
-}
-
-static int
-ec_node_subset_parse(const struct ec_node *gen_node,
-               struct ec_parse *state,
-               const struct ec_strvec *strvec)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-       struct ec_parse *parse = NULL;
-       struct parse_result result;
-       int ret;
-
-       memset(&result, 0, sizeof(result));
-
-       ret = __ec_node_subset_parse(&result, node->table,
-                               node->len, state, strvec);
-       if (ret < 0)
-               goto fail;
-
-       /* if no child node matches, return a matching empty strvec */
-       if (result.parse_len == 0)
-               return 0;
-
-       return result.len;
-
- fail:
-       ec_parse_free(parse);
-       return ret;
-}
-
-static int
-__ec_node_subset_complete(struct ec_node **table, size_t table_len,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_parse *parse = ec_comp_get_state(comp);
-       struct ec_strvec *childvec = NULL;
-       struct ec_node *save;
-       size_t i, len;
-       int ret;
-
-       /*
-        * example with table = [a, b, c]
-        * subset_complete([a,b,c], strvec) returns:
-        *   complete(a, strvec) + complete(b, strvec) + complete(c, strvec) +
-        *   + __subset_complete([b, c], childvec) if a matches
-        *   + __subset_complete([a, c], childvec) if b matches
-        *   + __subset_complete([a, b], childvec) if c matches
-        */
-
-       /* first, try to complete with each node of the table */
-       for (i = 0; i < table_len; i++) {
-               if (table[i] == NULL)
-                       continue;
-
-               ret = ec_node_complete_child(table[i],
-                                       comp, strvec);
-               if (ret < 0)
-                       goto fail;
-       }
-
-       /* then, if a node matches, advance in strvec and try to complete with
-        * all the other nodes */
-       for (i = 0; i < table_len; i++) {
-               if (table[i] == NULL)
-                       continue;
-
-               ret = ec_node_parse_child(table[i], parse, strvec);
-               if (ret < 0)
-                       goto fail;
-
-               if (ret == EC_PARSE_NOMATCH)
-                       continue;
-
-               len = ret;
-               childvec = ec_strvec_ndup(strvec, len,
-                                       ec_strvec_len(strvec) - len);
-               if (childvec == NULL) {
-                       ec_parse_del_last_child(parse);
-                       goto fail;
-               }
-
-               save = table[i];
-               table[i] = NULL;
-               ret = __ec_node_subset_complete(table, table_len,
-                                               comp, childvec);
-               table[i] = save;
-               ec_strvec_free(childvec);
-               childvec = NULL;
-               ec_parse_del_last_child(parse);
-
-               if (ret < 0)
-                       goto fail;
-       }
-
-       return 0;
-
-fail:
-       return -1;
-}
-
-static int
-ec_node_subset_complete(const struct ec_node *gen_node,
-                       struct ec_comp *comp,
-                       const struct ec_strvec *strvec)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-
-       return __ec_node_subset_complete(node->table, node->len, comp,
-                                       strvec);
-}
-
-static void ec_node_subset_free_priv(struct ec_node *gen_node)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-       size_t i;
-
-       for (i = 0; i < node->len; i++)
-               ec_node_free(node->table[i]);
-       ec_free(node->table);
-}
-
-static size_t
-ec_node_subset_get_children_count(const struct ec_node *gen_node)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-       return node->len;
-}
-
-static int
-ec_node_subset_get_child(const struct ec_node *gen_node, size_t i,
-                       struct ec_node **child, unsigned int *refs)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-
-       if (i >= node->len)
-               return -1;
-
-       *child = node->table[i];
-       *refs = 1;
-       return 0;
-}
-
-static struct ec_node_type ec_node_subset_type = {
-       .name = "subset",
-       .parse = ec_node_subset_parse,
-       .complete = ec_node_subset_complete,
-       .size = sizeof(struct ec_node_subset),
-       .free_priv = ec_node_subset_free_priv,
-       .get_children_count = ec_node_subset_get_children_count,
-       .get_child = ec_node_subset_get_child,
-};
-
-EC_NODE_TYPE_REGISTER(ec_node_subset_type);
-
-int ec_node_subset_add(struct ec_node *gen_node, struct ec_node *child)
-{
-       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
-       struct ec_node **table;
-
-       assert(node != NULL); // XXX specific assert for it, like in libyang
-
-       if (child == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       if (ec_node_check_type(gen_node, &ec_node_subset_type) < 0)
-               goto fail;
-
-       table = ec_realloc(node->table, (node->len + 1) * sizeof(*node->table));
-       if (table == NULL) {
-               ec_node_free(child);
-               return -1;
-       }
-
-       node->table = table;
-       table[node->len] = child;
-       node->len++;
-
-       return 0;
-
-fail:
-       ec_node_free(child);
-       return -1;
-}
-
-struct ec_node *__ec_node_subset(const char *id, ...)
-{
-       struct ec_node *gen_node = NULL;
-       struct ec_node_subset *node = NULL;
-       struct ec_node *child;
-       va_list ap;
-       int fail = 0;
-
-       va_start(ap, id);
-
-       gen_node = ec_node_from_type(&ec_node_subset_type, id);
-       node = (struct ec_node_subset *)gen_node;
-       if (node == NULL)
-               fail = 1;;
-
-       for (child = va_arg(ap, struct ec_node *);
-            child != EC_NODE_ENDLIST;
-            child = va_arg(ap, struct ec_node *)) {
-
-               /* on error, don't quit the loop to avoid leaks */
-               if (fail == 1 || child == NULL ||
-                               ec_node_subset_add(gen_node, child) < 0) {
-                       fail = 1;
-                       ec_node_free(child);
-               }
-       }
-
-       if (fail == 1)
-               goto fail;
-
-       va_end(ap);
-       return gen_node;
-
-fail:
-       ec_node_free(gen_node); /* will also free children */
-       va_end(ap);
-       return NULL;
-}
-
-/* LCOV_EXCL_START */
-static int ec_node_subset_testcase(void)
-{
-       struct ec_node *node;
-       int testres = 0;
-
-       node = EC_NODE_SUBSET(EC_NO_ID,
-               EC_NODE_OR(EC_NO_ID,
-                       ec_node_str(EC_NO_ID, "foo"),
-                       ec_node_str(EC_NO_ID, "bar")),
-               ec_node_str(EC_NO_ID, "bar"),
-               ec_node_str(EC_NO_ID, "toto")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_PARSE(node, 0);
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "titi");
-       testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "toto");
-       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "bar");
-       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, " ");
-       testres |= EC_TEST_CHECK_PARSE(node, 0, "foox");
-       ec_node_free(node);
-
-       /* test completion */
-       node = EC_NODE_SUBSET(EC_NO_ID,
-               ec_node_str(EC_NO_ID, "foo"),
-               ec_node_str(EC_NO_ID, "bar"),
-               ec_node_str(EC_NO_ID, "bar2"),
-               ec_node_str(EC_NO_ID, "toto"),
-               ec_node_str(EC_NO_ID, "titi")
-       );
-       if (node == NULL) {
-               EC_LOG(EC_LOG_ERR, "cannot create node\n");
-               return -1;
-       }
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "", EC_NODE_ENDLIST,
-               "bar2", "bar", "foo", "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", "bar2", "", EC_NODE_ENDLIST,
-               "foo", "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "f", EC_NODE_ENDLIST,
-               "foo", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "b", EC_NODE_ENDLIST,
-               "bar", "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", EC_NODE_ENDLIST,
-               "bar", "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "bar", "b", EC_NODE_ENDLIST,
-               "bar2", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "t", EC_NODE_ENDLIST,
-               "toto", "titi", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "to", EC_NODE_ENDLIST,
-               "toto", EC_NODE_ENDLIST);
-       testres |= EC_TEST_CHECK_COMPLETE(node,
-               "x", EC_NODE_ENDLIST,
-               EC_NODE_ENDLIST);
-       ec_node_free(node);
-
-       return testres;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_subset_test = {
-       .name = "node_subset",
-       .test = ec_node_subset_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_subset_test);
diff --git a/libecoli/ecoli_node_subset.h b/libecoli/ecoli_node_subset.h
deleted file mode 100644 (file)
index 734b1ae..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_NODE_SUBSET_
-#define ECOLI_NODE_SUBSET_
-
-#include <ecoli_node.h>
-
-#define EC_NODE_SUBSET(args...) __ec_node_subset(args, EC_NODE_ENDLIST)
-
-/* list must be terminated with EC_NODE_ENDLIST */
-/* all nodes given in the list will be freed when freeing this one */
-/* avoid using this function directly, prefer the macro EC_NODE_SUBSET() or
- * ec_node_subset() + ec_node_subset_add() */
-struct ec_node *__ec_node_subset(const char *id, ...);
-
-struct ec_node *ec_node_subset(const char *id);
-
-/* child is consumed */
-int ec_node_subset_add(struct ec_node *node, struct ec_node *child);
-
-#endif
diff --git a/libecoli/ecoli_parse.c b/libecoli/ecoli_parse.c
deleted file mode 100644 (file)
index 3d6be77..0000000
+++ /dev/null
@@ -1,544 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <ecoli_assert.h>
-#include <ecoli_malloc.h>
-#include <ecoli_strvec.h>
-#include <ecoli_keyval.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_node.h>
-#include <ecoli_node_sh_lex.h>
-#include <ecoli_node_str.h>
-#include <ecoli_node_seq.h>
-#include <ecoli_parse.h>
-
-EC_LOG_TYPE_REGISTER(parse);
-
-TAILQ_HEAD(ec_parse_list, ec_parse);
-
-struct ec_parse {
-       TAILQ_ENTRY(ec_parse) next;
-       struct ec_parse_list children;
-       struct ec_parse *parent;
-       const struct ec_node *node;
-       struct ec_strvec *strvec;
-       struct ec_keyval *attrs;
-};
-
-static int __ec_node_parse_child(const struct ec_node *node,
-                               struct ec_parse *state,
-                               bool is_root, const struct ec_strvec *strvec)
-{
-       struct ec_strvec *match_strvec;
-       struct ec_parse *child = NULL;
-       int ret;
-
-       if (ec_node_type(node)->parse == NULL) {
-               errno = ENOTSUP;
-               return -1;
-       }
-
-       if (!is_root) {
-               child = ec_parse(node);
-               if (child == NULL)
-                       return -1;
-
-               ec_parse_link_child(state, child);
-       } else {
-               child = state;
-       }
-       ret = ec_node_type(node)->parse(node, child, strvec);
-       if (ret < 0)
-               goto fail;
-
-       if (ret == EC_PARSE_NOMATCH) {
-               if (!is_root) {
-                       ec_parse_unlink_child(state, child);
-                       ec_parse_free(child);
-               }
-               return ret;
-       }
-
-       match_strvec = ec_strvec_ndup(strvec, 0, ret);
-       if (match_strvec == NULL)
-               goto fail;
-
-       child->strvec = match_strvec;
-
-       return ret;
-
-fail:
-       if (!is_root) {
-               ec_parse_unlink_child(state, child);
-               ec_parse_free(child);
-       }
-       return -1;
-}
-
-int ec_node_parse_child(const struct ec_node *node, struct ec_parse *state,
-                       const struct ec_strvec *strvec)
-{
-       assert(state != NULL);
-       return __ec_node_parse_child(node, state, false, strvec);
-}
-
-// XXX what is returned if no match ??
-struct ec_parse *ec_node_parse_strvec(const struct ec_node *node,
-                               const struct ec_strvec *strvec)
-{
-       struct ec_parse *parse = ec_parse(node);
-       int ret;
-
-       if (parse == NULL)
-               return NULL;
-
-       ret = __ec_node_parse_child(node, parse, true, strvec);
-       if (ret < 0) {
-               ec_parse_free(parse);
-               return NULL;
-       }
-
-       return parse;
-}
-
-struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str)
-{
-       struct ec_strvec *strvec = NULL;
-       struct ec_parse *parse = NULL;
-
-       errno = ENOMEM;
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       if (ec_strvec_add(strvec, str) < 0)
-               goto fail;
-
-       parse = ec_node_parse_strvec(node, strvec);
-       if (parse == NULL)
-               goto fail;
-
-       ec_strvec_free(strvec);
-       return parse;
-
- fail:
-       ec_strvec_free(strvec);
-       ec_parse_free(parse);
-       return NULL;
-}
-
-struct ec_parse *ec_parse(const struct ec_node *node)
-{
-       struct ec_parse *parse = NULL;
-
-       parse = ec_calloc(1, sizeof(*parse));
-       if (parse == NULL)
-               goto fail;
-
-       TAILQ_INIT(&parse->children);
-
-       parse->node = node;
-       parse->attrs = ec_keyval();
-       if (parse->attrs == NULL)
-               goto fail;
-
-       return parse;
-
- fail:
-       if (parse != NULL)
-               ec_keyval_free(parse->attrs);
-       ec_free(parse);
-
-       return NULL;
-}
-
-static struct ec_parse *
-__ec_parse_dup(const struct ec_parse *root, const struct ec_parse *ref,
-               struct ec_parse **new_ref)
-{
-       struct ec_parse *dup = NULL;
-       struct ec_parse *child, *dup_child;
-       struct ec_keyval *attrs = NULL;
-
-       if (root == NULL)
-               return NULL;
-
-       dup = ec_parse(root->node);
-       if (dup == NULL)
-               return NULL;
-
-       if (root == ref)
-               *new_ref = dup;
-
-       attrs = ec_keyval_dup(root->attrs);
-       if (attrs == NULL)
-               goto fail;
-       ec_keyval_free(dup->attrs);
-       dup->attrs = attrs;
-
-       if (root->strvec != NULL) {
-               dup->strvec = ec_strvec_dup(root->strvec);
-               if (dup->strvec == NULL)
-                       goto fail;
-       }
-
-       TAILQ_FOREACH(child, &root->children, next) {
-               dup_child = __ec_parse_dup(child, ref, new_ref);
-               if (dup_child == NULL)
-                       goto fail;
-               ec_parse_link_child(dup, dup_child);
-       }
-
-       return dup;
-
-fail:
-       ec_parse_free(dup);
-       return NULL;
-}
-
-struct ec_parse *ec_parse_dup(const struct ec_parse *parse)
-{
-       const struct ec_parse *root;
-       struct ec_parse *dup_root, *dup = NULL;
-
-       root = ec_parse_get_root(parse);
-       dup_root = __ec_parse_dup(root, parse, &dup);
-       if (dup_root == NULL)
-               return NULL;
-       assert(dup != NULL);
-
-       return dup;
-}
-
-void ec_parse_free_children(struct ec_parse *parse)
-{
-       struct ec_parse *child;
-
-       if (parse == NULL)
-               return;
-
-       while (!TAILQ_EMPTY(&parse->children)) {
-               child = TAILQ_FIRST(&parse->children);
-               TAILQ_REMOVE(&parse->children, child, next);
-               child->parent = NULL;
-               ec_parse_free(child);
-       }
-}
-
-void ec_parse_free(struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return;
-
-       ec_assert_print(parse->parent == NULL,
-                       "parent not NULL in ec_parse_free()");
-
-       ec_parse_free_children(parse);
-       ec_strvec_free(parse->strvec);
-       ec_keyval_free(parse->attrs);
-       ec_free(parse);
-}
-
-static void __ec_parse_dump(FILE *out,
-       const struct ec_parse *parse, size_t indent)
-{
-       struct ec_parse *child;
-       const struct ec_strvec *vec;
-       const char *id = "none", *typename = "none";
-
-       /* node can be null when parsing is incomplete */
-       if (parse->node != NULL) {
-               id = parse->node->id;
-               typename = ec_node_type(parse->node)->name;
-       }
-
-       fprintf(out, "%*s" "type=%s id=%s vec=",
-               (int)indent * 4, "", typename, id);
-       vec = ec_parse_strvec(parse);
-       ec_strvec_dump(out, vec);
-
-       TAILQ_FOREACH(child, &parse->children, next)
-               __ec_parse_dump(out, child, indent + 1);
-}
-
-void ec_parse_dump(FILE *out, const struct ec_parse *parse)
-{
-       fprintf(out, "------------------- parse dump:\n");
-
-       if (parse == NULL) {
-               fprintf(out, "parse is NULL\n");
-               return;
-       }
-
-       /* only exist if it does not match (strvec == NULL) and if it
-        * does not have children: an incomplete parse, like those
-        * generated by complete() don't match but have children that
-        * may match. */
-       if (!ec_parse_matches(parse) && TAILQ_EMPTY(&parse->children)) {
-               fprintf(out, "no match\n");
-               return;
-       }
-
-       __ec_parse_dump(out, parse, 0);
-}
-
-void ec_parse_link_child(struct ec_parse *parse,
-       struct ec_parse *child)
-{
-       TAILQ_INSERT_TAIL(&parse->children, child, next);
-       child->parent = parse;
-}
-
-void ec_parse_unlink_child(struct ec_parse *parse,
-       struct ec_parse *child)
-{
-       TAILQ_REMOVE(&parse->children, child, next);
-       child->parent = NULL;
-}
-
-struct ec_parse *
-ec_parse_get_first_child(const struct ec_parse *parse)
-{
-       return TAILQ_FIRST(&parse->children);
-}
-
-struct ec_parse *
-ec_parse_get_last_child(const struct ec_parse *parse)
-{
-       return TAILQ_LAST(&parse->children, ec_parse_list);
-}
-
-struct ec_parse *ec_parse_get_next(const struct ec_parse *parse)
-{
-       return TAILQ_NEXT(parse, next);
-}
-
-bool ec_parse_has_child(const struct ec_parse *parse)
-{
-       return !TAILQ_EMPTY(&parse->children);
-}
-
-const struct ec_node *ec_parse_get_node(const struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return NULL;
-
-       return parse->node;
-}
-
-void ec_parse_del_last_child(struct ec_parse *parse)
-{
-       struct ec_parse *child;
-
-       child = ec_parse_get_last_child(parse);
-       ec_parse_unlink_child(parse, child);
-       ec_parse_free(child);
-}
-
-struct ec_parse *__ec_parse_get_root(struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return NULL;
-
-       while (parse->parent != NULL)
-               parse = parse->parent;
-
-       return parse;
-}
-
-struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return NULL;
-
-       return parse->parent;
-}
-
-struct ec_parse *ec_parse_iter_next(struct ec_parse *parse)
-{
-       struct ec_parse *child, *parent, *next;
-
-       child = TAILQ_FIRST(&parse->children);
-       if (child != NULL)
-               return child;
-       parent = parse->parent;
-       while (parent != NULL) {
-               next = TAILQ_NEXT(parse, next);
-               if (next != NULL)
-                       return next;
-               parse = parent;
-               parent = parse->parent;
-       }
-       return NULL;
-}
-
-struct ec_parse *ec_parse_find_first(struct ec_parse *parse,
-       const char *id)
-{
-       struct ec_parse *iter;
-
-       if (parse == NULL)
-               return NULL;
-
-       for (iter = parse; iter != NULL; iter = ec_parse_iter_next(iter)) {
-               if (iter->node != NULL &&
-                               iter->node->id != NULL &&
-                               !strcmp(iter->node->id, id))
-                       return iter;
-       }
-
-       return NULL;
-}
-
-struct ec_keyval *
-ec_parse_get_attrs(struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return NULL;
-
-       return parse->attrs;
-}
-
-const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse)
-{
-       if (parse == NULL || parse->strvec == NULL)
-               return NULL;
-
-       return parse->strvec;
-}
-
-/* number of strings in the parse vector */
-size_t ec_parse_len(const struct ec_parse *parse)
-{
-       if (parse == NULL || parse->strvec == NULL)
-               return 0;
-
-       return ec_strvec_len(parse->strvec);
-}
-
-size_t ec_parse_matches(const struct ec_parse *parse)
-{
-       if (parse == NULL)
-               return 0;
-
-       if (parse->strvec == NULL)
-               return 0;
-
-       return 1;
-}
-
-/* LCOV_EXCL_START */
-static int ec_parse_testcase(void)
-{
-       struct ec_node *node = NULL;
-       struct ec_parse *p = NULL, *p2 = NULL;
-       const struct ec_parse *pc;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-       int testres = 0;
-       int ret;
-
-       node = ec_node_sh_lex(EC_NO_ID,
-                       EC_NODE_SEQ(EC_NO_ID,
-                               ec_node_str("id_x", "x"),
-                               ec_node_str("id_y", "y")));
-       if (node == NULL)
-               goto fail;
-
-       p = ec_node_parse(node, "xcdscds");
-       testres |= EC_TEST_CHECK(
-               p != NULL && !ec_parse_matches(p),
-               "parse should not match\n");
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_parse_dump(f, p);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "no match"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-       ec_parse_free(p);
-
-       p = ec_node_parse(node, "x y");
-       testres |= EC_TEST_CHECK(
-               p != NULL && ec_parse_matches(p),
-               "parse should match\n");
-       testres |= EC_TEST_CHECK(
-               ec_parse_len(p) == 1, "bad parse len\n");
-
-       ret = ec_keyval_set(ec_parse_get_attrs(p), "key", "val", NULL);
-       testres |= EC_TEST_CHECK(ret == 0,
-               "cannot set parse attribute\n");
-
-       p2 = ec_parse_dup(p);
-       testres |= EC_TEST_CHECK(
-               p2 != NULL && ec_parse_matches(p2),
-               "parse should match\n");
-       ec_parse_free(p2);
-       p2 = NULL;
-
-       pc = ec_parse_find_first(p, "id_x");
-       testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_x");
-       testres |= EC_TEST_CHECK(pc != NULL &&
-               ec_parse_get_parent(pc) != NULL &&
-               ec_parse_get_parent(ec_parse_get_parent(pc)) == p,
-               "invalid parent\n");
-
-       pc = ec_parse_find_first(p, "id_y");
-       testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_y");
-       pc = ec_parse_find_first(p, "id_dezdezdez");
-       testres |= EC_TEST_CHECK(pc == NULL, "should not find bad id");
-
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_parse_dump(f, p);
-       fclose(f);
-       f = NULL;
-
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "type=sh_lex id=no-id") &&
-               strstr(buf, "type=seq id=no-id") &&
-               strstr(buf, "type=str id=id_x") &&
-               strstr(buf, "type=str id=id_x"),
-               "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       ec_parse_free(p);
-       ec_node_free(node);
-       return testres;
-
-fail:
-       ec_parse_free(p2);
-       ec_parse_free(p);
-       ec_node_free(node);
-       if (f != NULL)
-               fclose(f);
-       free(buf);
-
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_parse_test = {
-       .name = "parse",
-       .test = ec_parse_testcase,
-};
-
-EC_TEST_REGISTER(ec_parse_test);
diff --git a/libecoli/ecoli_parse.h b/libecoli/ecoli_parse.h
deleted file mode 100644 (file)
index a431ba2..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Node parse API.
- *
- * The parse operation is to check if an input (a string or vector of
- * strings) matches the node tree. On success, the result is stored in a
- * tree that describes which part of the input matches which node.
- */
-
-#ifndef ECOLI_PARSE_
-#define ECOLI_PARSE_
-
-#include <sys/queue.h>
-#include <sys/types.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdbool.h>
-
-struct ec_node;
-struct ec_parse;
-
-/**
- * Create an empty parse tree.
- *
- * @return
- *   The empty parse tree.
- */
-struct ec_parse *ec_parse(const struct ec_node *node);
-
-/**
- *
- *
- *
- */
-void ec_parse_free(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-void ec_parse_free_children(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_dup(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-// _get_ XXX
-const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse);
-
-/* a NULL return value is an error, with errno set
-  ENOTSUP: no ->parse() operation
-*/
-/**
- *
- *
- *
- */
-struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_node_parse_strvec(const struct ec_node *node,
-                               const struct ec_strvec *strvec);
-
-/**
- *
- *
- *
- */
-#define EC_PARSE_NOMATCH INT_MAX
-
-/* internal: used by nodes
- *
- * state is the current parse tree, which is built piece by piece while
- *   parsing the node tree: ec_node_parse_child() creates a new child in
- *   this state parse tree, and calls the parse() method for the child
- *   node, with state pointing to this new child. If it does not match,
- *   the child is removed in the state, else it is kept, with its
- *   possible descendants.
- *
- * return:
- * the number of matched strings in strvec on success
- * EC_PARSE_NOMATCH (positive) if it does not match
- * -1 on error, and errno is set
- */
-int ec_node_parse_child(const struct ec_node *node,
-                       struct ec_parse *state,
-                       const struct ec_strvec *strvec);
-
-/**
- *
- *
- *
- */
-void ec_parse_link_child(struct ec_parse *parse,
-                       struct ec_parse *child);
-/**
- *
- *
- *
- */
-void ec_parse_unlink_child(struct ec_parse *parse,
-                       struct ec_parse *child);
-
-/* keep the const */
-#define ec_parse_get_root(parse) ({                            \
-       const struct ec_parse *p_ = parse; /* check type */     \
-       struct ec_parse *parse_ = (struct ec_parse *)parse;     \
-       typeof(parse) res_;                                     \
-       (void)p_;                                               \
-       res_ = __ec_parse_get_root(parse_);                     \
-       res_;                                                   \
-})
-
-/**
- *
- *
- *
- */
-struct ec_parse *__ec_parse_get_root(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse);
-
-/**
- * Get the first child of a tree.
- *
- */
-struct ec_parse *ec_parse_get_first_child(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_get_last_child(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_get_next(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-#define EC_PARSE_FOREACH_CHILD(child, parse)                   \
-       for (child = ec_parse_get_first_child(parse);           \
-               child != NULL;                                  \
-               child = ec_parse_get_next(child))               \
-
-/**
- *
- *
- *
- */
-bool ec_parse_has_child(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-const struct ec_node *ec_parse_get_node(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-void ec_parse_del_last_child(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_keyval *ec_parse_get_attrs(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-void ec_parse_dump(FILE *out, const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-struct ec_parse *ec_parse_find_first(struct ec_parse *parse,
-       const char *id);
-
-/**
- * Iterate among parse tree
- *
- * Use it with:
- * for (iter = state; iter != NULL; iter = ec_parse_iter_next(iter))
- */
-struct ec_parse *ec_parse_iter_next(struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-size_t ec_parse_len(const struct ec_parse *parse);
-
-/**
- *
- *
- *
- */
-size_t ec_parse_matches(const struct ec_parse *parse);
-
-#endif
diff --git a/libecoli/ecoli_string.c b/libecoli/ecoli_string.c
deleted file mode 100644 (file)
index fd427b4..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdarg.h>
-#include <stddef.h>
-#include <string.h>
-#include <stdio.h>
-#include <ctype.h>
-
-#include <ecoli_assert.h>
-#include <ecoli_malloc.h>
-#include <ecoli_string.h>
-
-/* count the number of identical chars at the beginning of 2 strings */
-size_t ec_strcmp_count(const char *s1, const char *s2)
-{
-       size_t i = 0;
-
-       while (s1[i] && s2[i] && s1[i] == s2[i])
-               i++;
-
-       return i;
-}
-
-int ec_str_startswith(const char *s, const char *beginning)
-{
-       size_t len;
-
-       len = ec_strcmp_count(s, beginning);
-       if (beginning[len] == '\0')
-               return 1;
-
-       return 0;
-}
-
-int ec_vasprintf(char **buf, const char *fmt, va_list ap)
-{
-       char dummy;
-       int buflen, ret;
-       va_list aq;
-
-       va_copy(aq, ap);
-       *buf = NULL;
-       ret = vsnprintf(&dummy, 1, fmt, aq);
-       va_end(aq);
-       if (ret < 0)
-               return ret;
-
-       buflen = ret + 1;
-       *buf = ec_malloc(buflen);
-       if (*buf == NULL)
-               return -1;
-
-       va_copy(aq, ap);
-       ret = vsnprintf(*buf, buflen, fmt, aq);
-       va_end(aq);
-
-       ec_assert_print(ret < buflen, "invalid return value for vsnprintf");
-       if (ret < 0) {
-               free(*buf);
-               *buf = NULL;
-               return -1;
-       }
-
-       return ret;
-}
-
-int ec_asprintf(char **buf, const char *fmt, ...)
-{
-       va_list ap;
-       int ret;
-
-       va_start(ap, fmt);
-       ret = ec_vasprintf(buf, fmt, ap);
-       va_end(ap);
-
-       return ret;
-}
-
-bool ec_str_is_space(const char *s)
-{
-       while (*s) {
-               if (!isspace(*s))
-                       return false;
-               s++;
-       }
-       return true;
-}
diff --git a/libecoli/ecoli_string.h b/libecoli/ecoli_string.h
deleted file mode 100644 (file)
index a523b88..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_STRING_
-#define ECOLI_STRING_
-
-#include <stddef.h>
-#include <stdbool.h>
-
-/* count the number of identical chars at the beginning of 2 strings */
-size_t ec_strcmp_count(const char *s1, const char *s2);
-
-/* return 1 if 's' starts with 'beginning' */
-int ec_str_startswith(const char *s, const char *beginning);
-
-/* like asprintf, but use libecoli allocator */
-int ec_asprintf(char **buf, const char *fmt, ...);
-
-/* like vasprintf, but use libecoli allocator */
-int ec_vasprintf(char **buf, const char *fmt, va_list ap);
-
-/* return true if string is only composed of spaces (' ', '\n', ...) */
-bool ec_str_is_space(const char *s);
-
-#endif
diff --git a/libecoli/ecoli_strvec.c b/libecoli/ecoli_strvec.c
deleted file mode 100644 (file)
index 98a952f..0000000
+++ /dev/null
@@ -1,536 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#define _GNU_SOURCE /* qsort_r */
-#include <sys/types.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_log.h>
-#include <ecoli_keyval.h>
-#include <ecoli_strvec.h>
-
-EC_LOG_TYPE_REGISTER(strvec);
-
-struct ec_strvec_elt {
-       unsigned int refcnt;
-       char *str;
-       struct ec_keyval *attrs;
-};
-
-struct ec_strvec {
-       size_t len;
-       struct ec_strvec_elt **vec;
-};
-
-struct ec_strvec *ec_strvec(void)
-{
-       struct ec_strvec *strvec;
-
-       strvec = ec_calloc(1, sizeof(*strvec));
-       if (strvec == NULL)
-               return NULL;
-
-       return strvec;
-}
-
-static struct ec_strvec_elt *
-__ec_strvec_elt(const char *s)
-{
-       struct ec_strvec_elt *elt;
-
-       elt = ec_calloc(1, sizeof(*elt));
-       if (elt == NULL)
-               return NULL;
-
-       elt->str = ec_strdup(s);
-       if (elt->str == NULL) {
-               ec_free(elt);
-               return NULL;
-       }
-       elt->refcnt = 1;
-
-       return elt;
-}
-
-static void
-__ec_strvec_elt_free(struct ec_strvec_elt *elt)
-{
-       elt->refcnt--;
-       if (elt->refcnt == 0) {
-               ec_free(elt->str);
-               ec_keyval_free(elt->attrs);
-               ec_free(elt);
-       }
-}
-
-int ec_strvec_set(struct ec_strvec *strvec, size_t idx, const char *s)
-{
-       struct ec_strvec_elt *elt;
-
-       if (strvec == NULL || s == NULL || idx >= strvec->len) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       elt = __ec_strvec_elt(s);
-       if (elt == NULL)
-               return -1;
-
-       __ec_strvec_elt_free(strvec->vec[idx]);
-       strvec->vec[idx] = elt;
-
-       return 0;
-}
-
-int ec_strvec_add(struct ec_strvec *strvec, const char *s)
-{
-       struct ec_strvec_elt *elt, **new_vec;
-
-       if (strvec == NULL || s == NULL) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       new_vec = ec_realloc(strvec->vec,
-               sizeof(*strvec->vec) * (strvec->len + 1));
-       if (new_vec == NULL)
-               return -1;
-
-       strvec->vec = new_vec;
-
-       elt = __ec_strvec_elt(s);
-       if (elt == NULL)
-               return -1;
-
-       new_vec[strvec->len] = elt;
-       strvec->len++;
-
-       return 0;
-}
-
-struct ec_strvec *ec_strvec_from_array(const char * const *strarr,
-       size_t n)
-{
-       struct ec_strvec *strvec = NULL;
-       size_t i;
-
-       strvec = ec_strvec();
-       if (strvec == NULL)
-               goto fail;
-
-       for (i = 0; i < n; i++) {
-               if (ec_strvec_add(strvec, strarr[i]) < 0)
-                       goto fail;
-       }
-
-       return strvec;
-
-fail:
-       ec_strvec_free(strvec);
-       return NULL;
-}
-
-int ec_strvec_del_last(struct ec_strvec *strvec)
-{
-       if (strvec->len == 0) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       __ec_strvec_elt_free(strvec->vec[strvec->len - 1]);
-       strvec->len--;
-
-       return 0;
-}
-
-struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, size_t off,
-       size_t len)
-{
-       struct ec_strvec *copy = NULL;
-       size_t i, veclen;
-
-       veclen = ec_strvec_len(strvec);
-       if (off + len > veclen)
-               return NULL;
-
-       copy = ec_strvec();
-       if (copy == NULL)
-               goto fail;
-
-       if (len == 0)
-               return copy;
-
-       copy->vec = ec_calloc(len, sizeof(*copy->vec));
-       if (copy->vec == NULL)
-               goto fail;
-
-       for (i = 0; i < len; i++) {
-               copy->vec[i] = strvec->vec[i + off];
-               copy->vec[i]->refcnt++;
-       }
-       copy->len = len;
-
-       return copy;
-
-fail:
-       ec_strvec_free(copy);
-       return NULL;
-}
-
-struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec)
-{
-       return ec_strvec_ndup(strvec, 0, ec_strvec_len(strvec));
-}
-
-void ec_strvec_free(struct ec_strvec *strvec)
-{
-       struct ec_strvec_elt *elt;
-       size_t i;
-
-       if (strvec == NULL)
-               return;
-
-       for (i = 0; i < ec_strvec_len(strvec); i++) {
-               elt = strvec->vec[i];
-               __ec_strvec_elt_free(elt);
-       }
-
-       ec_free(strvec->vec);
-       ec_free(strvec);
-}
-
-size_t ec_strvec_len(const struct ec_strvec *strvec)
-{
-       return strvec->len;
-}
-
-const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx)
-{
-       if (strvec == NULL || idx >= strvec->len)
-               return NULL;
-
-       return strvec->vec[idx]->str;
-}
-
-const struct ec_keyval *ec_strvec_get_attrs(const struct ec_strvec *strvec,
-       size_t idx)
-{
-       if (strvec == NULL || idx >= strvec->len) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       return strvec->vec[idx]->attrs;
-}
-
-int ec_strvec_set_attrs(struct ec_strvec *strvec, size_t idx,
-                       struct ec_keyval *attrs)
-{
-       struct ec_strvec_elt *elt;
-
-       if (strvec == NULL || idx >= strvec->len) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       elt = strvec->vec[idx];
-       if (elt->refcnt > 1) {
-               if (ec_strvec_set(strvec, idx, elt->str) < 0)
-                       goto fail;
-               elt = strvec->vec[idx];
-       }
-
-       if (elt->attrs != NULL)
-               ec_keyval_free(elt->attrs);
-
-       elt->attrs = attrs;
-
-       return 0;
-
-fail:
-       ec_keyval_free(attrs);
-       return -1;
-}
-
-int ec_strvec_cmp(const struct ec_strvec *strvec1,
-               const struct ec_strvec *strvec2)
-{
-       size_t i;
-
-       if (ec_strvec_len(strvec1) != ec_strvec_len(strvec2))
-               return -1;
-
-       for (i = 0; i < ec_strvec_len(strvec1); i++) {
-               if (strcmp(ec_strvec_val(strvec1, i),
-                               ec_strvec_val(strvec2, i)))
-                       return -1;
-       }
-
-       return 0;
-}
-
-static int
-cmp_vec_elt(const void *p1, const void *p2, void *arg)
-{
-       int (*str_cmp)(const char *s1, const char *s2) = arg;
-       const struct ec_strvec_elt * const *e1 = p1, * const *e2 = p2;
-
-       return str_cmp((*e1)->str, (*e2)->str);
-}
-
-void ec_strvec_sort(struct ec_strvec *strvec,
-       int (*str_cmp)(const char *s1, const char *s2))
-{
-       if (str_cmp == NULL)
-               str_cmp = strcmp;
-       qsort_r(strvec->vec, ec_strvec_len(strvec),
-               sizeof(*strvec->vec), cmp_vec_elt, str_cmp);
-}
-
-void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec)
-{
-       size_t i;
-
-       if (strvec == NULL) {
-               fprintf(out, "none\n");
-               return;
-       }
-
-       fprintf(out, "strvec (len=%zu) [", strvec->len);
-       for (i = 0; i < ec_strvec_len(strvec); i++) {
-               if (i == 0)
-                       fprintf(out, "%s", strvec->vec[i]->str);
-               else
-                       fprintf(out, ", %s", strvec->vec[i]->str);
-       }
-       fprintf(out, "]\n");
-
-}
-
-/* LCOV_EXCL_START */
-static int ec_strvec_testcase(void)
-{
-       struct ec_strvec *strvec = NULL;
-       struct ec_strvec *strvec2 = NULL;
-       const struct ec_keyval *const_attrs = NULL;
-       struct ec_keyval *attrs = NULL;
-       FILE *f = NULL;
-       char *buf = NULL;
-       size_t buflen = 0;
-       int testres = 0;
-
-       strvec = ec_strvec();
-       if (strvec == NULL) {
-               EC_TEST_ERR("cannot create strvec\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec) != 0) {
-               EC_TEST_ERR("bad strvec len (0)\n");
-               goto fail;
-       }
-       if (ec_strvec_add(strvec, "0") < 0) {
-               EC_TEST_ERR("cannot add (0) in strvec\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec) != 1) {
-               EC_TEST_ERR("bad strvec len (1)\n");
-               goto fail;
-       }
-       if (ec_strvec_add(strvec, "1") < 0) {
-               EC_TEST_ERR("cannot add (1) in strvec\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec) != 2) {
-               EC_TEST_ERR("bad strvec len (2)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec, 0), "0")) {
-               EC_TEST_ERR("invalid element in strvec (0)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec, 1), "1")) {
-               EC_TEST_ERR("invalid element in strvec (1)\n");
-               goto fail;
-       }
-       if (ec_strvec_val(strvec, 2) != NULL) {
-               EC_TEST_ERR("strvec val should be NULL\n");
-               goto fail;
-       }
-
-       strvec2 = ec_strvec_dup(strvec);
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec2\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec2) != 2) {
-               EC_TEST_ERR("bad strvec2 len (2)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec2, 0), "0")) {
-               EC_TEST_ERR("invalid element in strvec2 (0)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec2, 1), "1")) {
-               EC_TEST_ERR("invalid element in strvec2 (1)\n");
-               goto fail;
-       }
-       if (ec_strvec_val(strvec2, 2) != NULL) {
-               EC_TEST_ERR("strvec2 val should be NULL\n");
-               goto fail;
-       }
-       ec_strvec_free(strvec2);
-
-       strvec2 = ec_strvec_ndup(strvec, 0, 0);
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec2\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec2) != 0) {
-               EC_TEST_ERR("bad strvec2 len (0)\n");
-               goto fail;
-       }
-       if (ec_strvec_val(strvec2, 0) != NULL) {
-               EC_TEST_ERR("strvec2 val should be NULL\n");
-               goto fail;
-       }
-       ec_strvec_free(strvec2);
-
-       strvec2 = ec_strvec_ndup(strvec, 1, 1);
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec2\n");
-               goto fail;
-       }
-       if (ec_strvec_len(strvec2) != 1) {
-               EC_TEST_ERR("bad strvec2 len (1)\n");
-               goto fail;
-       }
-       if (strcmp(ec_strvec_val(strvec2, 0), "1")) {
-               EC_TEST_ERR("invalid element in strvec2 (1)\n");
-               goto fail;
-       }
-       if (ec_strvec_val(strvec2, 1) != NULL) {
-               EC_TEST_ERR("strvec2 val should be NULL\n");
-               goto fail;
-       }
-       ec_strvec_free(strvec2);
-
-       strvec2 = ec_strvec_ndup(strvec, 3, 1);
-       if (strvec2 != NULL) {
-               EC_TEST_ERR("strvec2 should be NULL\n");
-               goto fail;
-       }
-       ec_strvec_free(strvec2);
-
-       strvec2 = EC_STRVEC("0", "1");
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec from array\n");
-               goto fail;
-       }
-       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
-               "strvec and strvec2 should be equal\n");
-       ec_strvec_free(strvec2);
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_strvec_dump(f, strvec);
-       fclose(f);
-       f = NULL;
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "strvec (len=2) [0, 1]"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       ec_strvec_del_last(strvec);
-       strvec2 = EC_STRVEC("0");
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec from array\n");
-               goto fail;
-       }
-       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
-               "strvec and strvec2 should be equal\n");
-       ec_strvec_free(strvec2);
-       strvec2 = NULL;
-
-       f = open_memstream(&buf, &buflen);
-       if (f == NULL)
-               goto fail;
-       ec_strvec_dump(f, NULL);
-       fclose(f);
-       f = NULL;
-       testres |= EC_TEST_CHECK(
-               strstr(buf, "none"), "bad dump\n");
-       free(buf);
-       buf = NULL;
-
-       ec_strvec_free(strvec);
-
-       strvec = EC_STRVEC("e", "a", "f", "d", "b", "c");
-       if (strvec == NULL) {
-               EC_TEST_ERR("cannot create strvec from array\n");
-               goto fail;
-       }
-       attrs = ec_keyval();
-       if (attrs == NULL) {
-               EC_TEST_ERR("cannot create attrs\n");
-               goto fail;
-       }
-       if (ec_keyval_set(attrs, "key", "value", NULL) < 0) {
-               EC_TEST_ERR("cannot set attr\n");
-               goto fail;
-       }
-       if (ec_strvec_set_attrs(strvec, 1, attrs) < 0) {
-               attrs = NULL;
-               EC_TEST_ERR("cannot set attrs in strvec\n");
-               goto fail;
-       }
-       attrs = NULL;
-
-       ec_strvec_sort(strvec, NULL);
-
-       /* attrs are now at index 0 after sorting */
-       const_attrs = ec_strvec_get_attrs(strvec, 0);
-       if (const_attrs == NULL) {
-               EC_TEST_ERR("cannot get attrs\n");
-               goto fail;
-       }
-       testres |= EC_TEST_CHECK(
-               ec_keyval_has_key(const_attrs, "key"), "cannot get attrs key\n");
-
-       strvec2 = EC_STRVEC("a", "b", "c", "d", "e", "f");
-       if (strvec2 == NULL) {
-               EC_TEST_ERR("cannot create strvec from array\n");
-               goto fail;
-       }
-       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
-               "strvec and strvec2 should be equal\n");
-       ec_strvec_free(strvec);
-       strvec = NULL;
-       ec_strvec_free(strvec2);
-       strvec2 = NULL;
-
-       return testres;
-
-fail:
-       if (f != NULL)
-               fclose(f);
-       ec_keyval_free(attrs);
-       ec_strvec_free(strvec);
-       ec_strvec_free(strvec2);
-       free(buf);
-
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_node_str_test = {
-       .name = "strvec",
-       .test = ec_strvec_testcase,
-};
-
-EC_TEST_REGISTER(ec_node_str_test);
diff --git a/libecoli/ecoli_strvec.h b/libecoli/ecoli_strvec.h
deleted file mode 100644 (file)
index cabe6b2..0000000
+++ /dev/null
@@ -1,224 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Vectors of strings.
- *
- * The ec_strvec API provide helpers to manipulate string vectors.
- * When duplicating vectors, the strings are not duplicated in memory,
- * a reference counter is used.
- */
-
-#ifndef ECOLI_STRVEC_
-#define ECOLI_STRVEC_
-
-#include <stdio.h>
-
-/**
- * Allocate a new empty string vector.
- *
- * @return
- *   The new strvec object, or NULL on error (errno is set).
- */
-struct ec_strvec *ec_strvec(void);
-
-#ifndef EC_COUNT_OF
-#define EC_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
-               ((size_t)(!(sizeof(x) % sizeof(0[x])))))
-#endif
-
-/**
- * Allocate a new string vector
- *
- * The string vector is initialized with the list of const strings
- * passed as arguments.
- *
- * @return
- *   The new strvec object, or NULL on error (errno is set).
- */
-#define EC_STRVEC(args...) ({                                          \
-                       const char *_arr[] = {args};                    \
-                       ec_strvec_from_array(_arr, EC_COUNT_OF(_arr));  \
-               })
-/**
- * Allocate a new string vector
- *
- * The string vector is initialized with the array of const strings
- * passed as arguments.
- *
- * @param strarr
- *   The array of const strings.
- * @param n
- *   The number of strings in the array.
- * @return
- *   The new strvec object, or NULL on error (errno is set).
- */
-struct ec_strvec *ec_strvec_from_array(const char * const *strarr,
-       size_t n);
-
-/**
- * Set a string in the vector at specified index.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param idx
- *   The index of the string to set.
- * @param s
- *   The string to be set.
- * @return
- *   0 on success or -1 on error (errno is set).
- */
-int ec_strvec_set(struct ec_strvec *strvec, size_t idx, const char *s);
-
-/**
- * Add a string in a vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param s
- *   The string to be added at the end of the vector.
- * @return
- *   0 on success or -1 on error (errno is set).
- */
-int ec_strvec_add(struct ec_strvec *strvec, const char *s);
-
-/**
- * Delete the last entry in the string vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param s
- *   The string to be added at the end of the vector.
- * @return
- *   0 on success or -1 on error (errno is set).
- */
-int ec_strvec_del_last(struct ec_strvec *strvec);
-
-/**
- * Duplicate a string vector.
- *
- * Attributes are duplicated if any.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @return
- *   The duplicated strvec object, or NULL on error (errno is set).
- */
-struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec);
-
-/**
- * Duplicate a part of a string vector.
- *
- * Attributes are duplicated if any.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param off
- *   The index of the first string to duplicate.
- * @param
- *   The number of strings to duplicate.
- * @return
- *   The duplicated strvec object, or NULL on error (errno is set).
- */
-struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec,
-       size_t off, size_t len);
-
-/**
- * Free a string vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- */
-void ec_strvec_free(struct ec_strvec *strvec);
-
-/**
- * Get the length of a string vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @return
- *   The length of the vector.
- */
-size_t ec_strvec_len(const struct ec_strvec *strvec);
-
-/**
- * Get a string element from a vector.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param idx
- *   The index of the string to get.
- * @return
- *   The string stored at given index, or NULL on error (strvec is NULL
- *   or invalid index).
- */
-const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx);
-
-/**
- * Get the attributes of a vector element.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param idx
- *   The index of the string to get.
- * @return
- *   The read-only attributes (dictionnary) of the string at specified
- *   index, or NULL if there is no attribute.
- */
-const struct ec_keyval *ec_strvec_get_attrs(const struct ec_strvec *strvec,
-       size_t idx);
-
-/**
- * Set the attributes of a vector element.
- *
- * @param strvec
- *   The pointer to the string vector.
- * @param idx
- *   The index of the string to get.
- * @param attrs
- *   The attributes to be set.
- * @return
- *   0 on success, -1 on error (errno is set). On error, attrs
- *   are freed and must not be used by the caller.
- */
-int ec_strvec_set_attrs(struct ec_strvec *strvec, size_t idx,
-                       struct ec_keyval *attrs);
-
-/**
- * Compare two string vectors
- *
- * @param strvec
- *   The pointer to the first string vector.
- * @param strvec
- *   The pointer to the second string vector.
- * @return
- *   0 if the string vectors are equal.
- */
-int ec_strvec_cmp(const struct ec_strvec *strvec1,
-               const struct ec_strvec *strvec2);
-
-/**
- * Sort the string vector.
- *
- * Attributes are not compared.
- *
- * @param strvec
- *   The pointer to the first string vector.
- * @param str_cmp
- *   The sort function to use. If NULL, use strcmp.
- */
-void ec_strvec_sort(struct ec_strvec *strvec,
-               int (*str_cmp)(const char *s1, const char *s2));
-
-/**
- * Dump a string vector.
- *
- * @param out
- *   The stream where the dump is sent.
- * @param strvec
- *   The pointer to the string vector.
- */
-void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec);
-
-#endif
diff --git a/libecoli/ecoli_test.c b/libecoli/ecoli_test.c
deleted file mode 100644 (file)
index b090bd3..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <ecoli_log.h>
-#include <ecoli_malloc.h>
-#include <ecoli_test.h>
-#include <ecoli_strvec.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-#include <ecoli_parse.h>
-
-static struct ec_test_list test_list = TAILQ_HEAD_INITIALIZER(test_list);
-
-EC_LOG_TYPE_REGISTER(test);
-
-static struct ec_test *ec_test_lookup(const char *name)
-{
-       struct ec_test *test;
-
-       TAILQ_FOREACH(test, &test_list, next) {
-               if (!strcmp(name, test->name))
-                       return test;
-       }
-
-       errno = EEXIST;
-       return NULL;
-}
-
-int ec_test_register(struct ec_test *test)
-{
-       if (ec_test_lookup(test->name) != NULL)
-               return -1;
-
-       TAILQ_INSERT_TAIL(&test_list, test, next);
-
-       return 0;
-}
-
-int ec_test_check_parse(struct ec_node *tk, int expected, ...)
-{
-       struct ec_parse *p;
-       struct ec_strvec *vec = NULL;
-       const char *s;
-       int ret = -1, match;
-       va_list ap;
-
-       va_start(ap, expected);
-
-       /* build a string vector */
-       vec = ec_strvec();
-       if (vec == NULL)
-               goto out;
-
-       for (s = va_arg(ap, const char *);
-            s != EC_NODE_ENDLIST;
-            s = va_arg(ap, const char *)) {
-               if (s == NULL)
-                       goto out;
-
-               if (ec_strvec_add(vec, s) < 0)
-                       goto out;
-       }
-
-       p = ec_node_parse_strvec(tk, vec);
-       if (p == NULL) {
-               EC_LOG(EC_LOG_ERR, "parse is NULL\n");
-       }
-       if (ec_parse_matches(p))
-               match = ec_parse_len(p);
-       else
-               match = -1;
-       if (expected == match) {
-               ret = 0;
-       } else {
-               EC_LOG(EC_LOG_ERR,
-                       "parse len (%d) does not match expected (%d)\n",
-                       match, expected);
-       }
-
-       ec_parse_free(p);
-
-out:
-       ec_strvec_free(vec);
-       va_end(ap);
-       return ret;
-}
-
-int ec_test_check_complete(struct ec_node *tk, enum ec_comp_type type, ...)
-{
-       struct ec_comp *c = NULL;
-       struct ec_strvec *vec = NULL;
-       const char *s;
-       int ret = 0;
-       unsigned int count = 0;
-       va_list ap;
-
-       va_start(ap, type);
-
-       /* build a string vector */
-       vec = ec_strvec();
-       if (vec == NULL)
-               goto out;
-
-       for (s = va_arg(ap, const char *);
-            s != EC_NODE_ENDLIST;
-            s = va_arg(ap, const char *)) {
-               if (s == NULL)
-                       goto out;
-
-               if (ec_strvec_add(vec, s) < 0)
-                       goto out;
-       }
-
-       c = ec_node_complete_strvec(tk, vec);
-       if (c == NULL) {
-               ret = -1;
-               goto out;
-       }
-
-       /* for each expected completion, check it is there */
-       for (s = va_arg(ap, const char *);
-            s != EC_NODE_ENDLIST;
-            s = va_arg(ap, const char *)) {
-               struct ec_comp_iter *iter;
-               const struct ec_comp_item *item;
-
-               if (s == NULL) {
-                       ret = -1;
-                       goto out;
-               }
-
-               count++;
-
-               /* only check matching completions */
-               iter = ec_comp_iter(c, type);
-               while ((item = ec_comp_iter_next(iter)) != NULL) {
-                       const char *str = ec_comp_item_get_str(item);
-                       if (str != NULL && strcmp(str, s) == 0)
-                               break;
-               }
-
-               if (item == NULL) {
-                       EC_LOG(EC_LOG_ERR,
-                               "completion <%s> not in list\n", s);
-                       ret = -1;
-               }
-               ec_comp_iter_free(iter);
-       }
-
-       /* check if we have more completions (or less) than expected */
-       if (count != ec_comp_count(c, type)) {
-               EC_LOG(EC_LOG_ERR,
-                       "nb_completion (%d) does not match (%d)\n",
-                       count, ec_comp_count(c, type));
-               ec_comp_dump(stdout, c);
-               ret = -1;
-       }
-
-out:
-       ec_strvec_free(vec);
-       ec_comp_free(c);
-       va_end(ap);
-       return ret;
-}
-
-static int launch_test(const char *name)
-{
-       struct ec_test *test;
-       int ret = 0;
-       unsigned int count = 0;
-
-       TAILQ_FOREACH(test, &test_list, next) {
-               if (name != NULL && strcmp(name, test->name))
-                       continue;
-
-               EC_LOG(EC_LOG_INFO, "== starting test %-20s\n",
-                       test->name);
-
-               count++;
-               if (test->test() == 0) {
-                       EC_LOG(EC_LOG_INFO,
-                               "== test %-20s success\n",
-                               test->name);
-               } else {
-                       EC_LOG(EC_LOG_INFO,
-                               "== test %-20s failed\n",
-                               test->name);
-                       ret = -1;
-               }
-       }
-
-       if (name != NULL && count == 0) {
-               EC_LOG(EC_LOG_WARNING,
-                       "== test %s not found\n", name);
-               ret = -1;
-       }
-
-       return ret;
-}
-
-int ec_test_all(void)
-{
-       return launch_test(NULL);
-}
-
-int ec_test_one(const char *name)
-{
-       return launch_test(name);
-}
diff --git a/libecoli/ecoli_test.h b/libecoli/ecoli_test.h
deleted file mode 100644 (file)
index 94cd0b9..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_TEST_
-#define ECOLI_TEST_
-
-#include <sys/queue.h>
-
-#include <ecoli_log.h>
-
-struct ec_node;
-enum ec_comp_type;
-
-#define EC_TEST_REGISTER(t)                                            \
-       static void ec_test_init_##t(void);                             \
-       static void __attribute__((constructor, used))                  \
-       ec_test_init_##t(void)                                          \
-       {                                                               \
-               if (ec_test_register(&t) < 0)                           \
-                       fprintf(stderr, "cannot register test %s\n",    \
-                               t.name);                                \
-       }
-
-/**
- * Type of test function. Return 0 on success, -1 on error.
- */
-typedef int (ec_test_t)(void);
-
-TAILQ_HEAD(ec_test_list, ec_test);
-
-/**
- * A structure describing a test case.
- */
-struct ec_test {
-       TAILQ_ENTRY(ec_test) next;  /**< Next in list. */
-       const char *name;           /**< Test name. */
-       ec_test_t *test;            /**< Test function. */
-};
-
-/**
- * Register a test case.
- *
- * @param test
- *   A pointer to a ec_test structure describing the test
- *   to be registered.
- * @return
- *   0 on success, -1 on error (errno is set).
- */
-int ec_test_register(struct ec_test *test);
-
-int ec_test_all(void);
-int ec_test_one(const char *name);
-
-/* expected == -1 means no match */
-int ec_test_check_parse(struct ec_node *node, int expected, ...);
-
-#define EC_TEST_ERR(fmt, ...)                                          \
-       EC_LOG(EC_LOG_ERR, "%s:%d: error: " fmt "\n",                   \
-               __FILE__, __LINE__, ##__VA_ARGS__);                     \
-
-#define EC_TEST_CHECK(cond, fmt, ...) ({                               \
-       int ret_ = 0;                                                   \
-       if (!(cond)) {                                                  \
-               EC_TEST_ERR("(" #cond ") is wrong. " fmt                \
-                       ##__VA_ARGS__);                                 \
-               ret_ = -1;                                              \
-       }                                                               \
-       ret_;                                                           \
-})
-
-/* node, input, [expected1, expected2, ...] */
-#define EC_TEST_CHECK_PARSE(node, args...) ({                          \
-       int ret_ = ec_test_check_parse(node, args, EC_NODE_ENDLIST);    \
-       if (ret_)                                                       \
-               EC_TEST_ERR("parse test failed");                       \
-       ret_;                                                           \
-})
-
-int ec_test_check_complete(struct ec_node *node,
-                       enum ec_comp_type type, ...);
-
-#define EC_TEST_CHECK_COMPLETE(node, args...) ({                       \
-       int ret_ = ec_test_check_complete(node, EC_COMP_FULL, args);    \
-       if (ret_)                                                       \
-               EC_TEST_ERR("complete test failed");                    \
-       ret_;                                                           \
-})
-
-#define EC_TEST_CHECK_COMPLETE_PARTIAL(node, args...) ({               \
-       int ret_ = ec_test_check_complete(node, EC_COMP_PARTIAL, args); \
-       if (ret_)                                                       \
-               EC_TEST_ERR("complete test failed");                    \
-       ret_;                                                           \
-})
-
-#endif
diff --git a/libecoli/ecoli_utils.h b/libecoli/ecoli_utils.h
deleted file mode 100644 (file)
index 5a14192..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#ifndef ECOLI_UTILS_
-#define ECOLI_UTILS_
-
-/**
- * Cast a variable into a type, ensuring its initial type first
- */
-#define EC_CAST(x, old_type, new_type) ({      \
-       old_type __x = (x);                     \
-       (new_type)__x;                          \
-       })
-
-#endif
diff --git a/libecoli/ecoli_vec.c b/libecoli/ecoli_vec.c
deleted file mode 100644 (file)
index fe8c572..0000000
+++ /dev/null
@@ -1,468 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <sys/types.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <errno.h>
-#include <assert.h>
-
-#include <ecoli_assert.h>
-#include <ecoli_malloc.h>
-#include <ecoli_log.h>
-#include <ecoli_test.h>
-#include <ecoli_vec.h>
-
-EC_LOG_TYPE_REGISTER(vec);
-
-struct ec_vec {
-       size_t len;
-       size_t size;
-       size_t elt_size;
-       ec_vec_elt_copy_t copy;
-       ec_vec_elt_free_t free;
-       void *vec;
-};
-
-static void *get_obj(const struct ec_vec *vec, size_t idx)
-{
-       assert(vec->elt_size != 0);
-       return (char *)vec->vec + (idx * vec->elt_size);
-}
-
-struct ec_vec *
-ec_vec(size_t elt_size, size_t size, ec_vec_elt_copy_t copy,
-       ec_vec_elt_free_t free)
-{
-       struct ec_vec *vec;
-
-       if (elt_size == 0) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       vec = ec_calloc(1, sizeof(*vec));
-       if (vec == NULL)
-               return NULL;
-
-       vec->elt_size = elt_size;
-       vec->copy = copy;
-       vec->free = free;
-
-       if (size == 0)
-               return vec;
-
-       vec->vec = ec_calloc(size, vec->elt_size);
-       if (vec->vec == NULL) {
-               ec_free(vec);
-               return NULL;
-       }
-
-       return vec;
-}
-
-int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr)
-{
-       void *new_vec;
-
-       if (vec->len + 1 > vec->size) {
-               new_vec = ec_realloc(vec->vec, vec->elt_size * (vec->len + 1));
-               if (new_vec == NULL)
-                       return -1;
-               vec->size = vec->len + 1;
-               vec->vec = new_vec;
-       }
-
-       memcpy(get_obj(vec, vec->len), ptr, vec->elt_size);
-       vec->len++;
-
-       return 0;
-}
-
-int ec_vec_add_ptr(struct ec_vec *vec, void *elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt)
-{
-       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
-
-       return ec_vec_add_by_ref(vec, &elt);
-}
-
-struct ec_vec *ec_vec_ndup(const struct ec_vec *vec, size_t off,
-       size_t len)
-{
-       struct ec_vec *copy = NULL;
-       size_t i, veclen;
-
-       veclen = ec_vec_len(vec);
-       if (off + len > veclen)
-               return NULL;
-
-       copy = ec_vec(vec->elt_size, len, vec->copy, vec->free);
-       if (copy == NULL)
-               goto fail;
-
-       if (len == 0)
-               return copy;
-
-       for (i = 0; i < len; i++) {
-               if (vec->copy)
-                       vec->copy(get_obj(copy, i), get_obj(vec, i + off));
-               else
-                       memcpy(get_obj(copy, i), get_obj(vec, i + off),
-                               vec->elt_size);
-       }
-       copy->len = len;
-
-       return copy;
-
-fail:
-       ec_vec_free(copy);
-       return NULL;
-}
-
-size_t ec_vec_len(const struct ec_vec *vec)
-{
-       if (vec == NULL)
-               return 0;
-
-       return vec->len;
-}
-
-struct ec_vec *ec_vec_dup(const struct ec_vec *vec)
-{
-       return ec_vec_ndup(vec, 0, ec_vec_len(vec));
-}
-
-void ec_vec_free(struct ec_vec *vec)
-{
-       size_t i;
-
-       if (vec == NULL)
-               return;
-
-       for (i = 0; i < ec_vec_len(vec); i++) {
-               if (vec->free)
-                       vec->free(get_obj(vec, i));
-       }
-
-       ec_free(vec->vec);
-       ec_free(vec);
-}
-
-int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx)
-{
-       if (vec == NULL || idx >= vec->len) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       memcpy(ptr, get_obj(vec, idx), vec->elt_size);
-
-       return 0;
-}
-
-static void str_free(void *elt)
-{
-       char **s = elt;
-
-       ec_free(*s);
-}
-
-#define GOTO_FAIL do {                                      \
-               EC_LOG(EC_LOG_ERR, "%s:%d: test failed\n",   \
-                       __FILE__, __LINE__);                 \
-               goto fail;                                   \
-       } while(0)
-
-/* LCOV_EXCL_START */
-static int ec_vec_testcase(void)
-{
-       struct ec_vec *vec = NULL;
-       struct ec_vec *vec2 = NULL;
-       uint8_t val8;
-       uint16_t val16;
-       uint32_t val32;
-       uint64_t val64;
-       void *valp;
-       char *vals;
-
-       /* uint8_t vector */
-       vec = ec_vec(sizeof(val8), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_u8(vec, 0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u8(vec, 1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u8(vec, 2) < 0)
-               GOTO_FAIL;
-       /* should fail */
-       if (ec_vec_add_u16(vec, 3) == 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u32(vec, 3) == 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u64(vec, 3) == 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, (void *)3) == 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&val8, vec, 0) < 0)
-               GOTO_FAIL;
-       if (val8 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec, 1) < 0)
-               GOTO_FAIL;
-       if (val8 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec, 2) < 0)
-               GOTO_FAIL;
-       if (val8 != 2)
-               GOTO_FAIL;
-
-       /* duplicate the vector */
-       vec2 = ec_vec_dup(vec);
-       if (vec2 == NULL)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 0) < 0)
-               GOTO_FAIL;
-       if (val8 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 1) < 0)
-               GOTO_FAIL;
-       if (val8 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 2) < 0)
-               GOTO_FAIL;
-       if (val8 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec2);
-       vec2 = NULL;
-
-       /* dup at offset 1 */
-       vec2 = ec_vec_ndup(vec, 1, 2);
-       if (vec2 == NULL)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 0) < 0)
-               GOTO_FAIL;
-       if (val8 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 1) < 0)
-               GOTO_FAIL;
-       if (val8 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec2);
-       vec2 = NULL;
-
-       /* len = 0, duplicate is empty */
-       vec2 = ec_vec_ndup(vec, 2, 0);
-       if (vec2 == NULL)
-               GOTO_FAIL;
-       if (ec_vec_get(&val8, vec2, 0) == 0)
-               GOTO_FAIL;
-
-       ec_vec_free(vec2);
-       vec2 = NULL;
-
-       /* bad dup args */
-       vec2 = ec_vec_ndup(vec, 10, 1);
-       if (vec2 != NULL)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* uint16_t vector */
-       vec = ec_vec(sizeof(val16), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_u16(vec, 0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u16(vec, 1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u16(vec, 2) < 0)
-               GOTO_FAIL;
-       /* should fail */
-       if (ec_vec_add_u8(vec, 3) == 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&val16, vec, 0) < 0)
-               GOTO_FAIL;
-       if (val16 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val16, vec, 1) < 0)
-               GOTO_FAIL;
-       if (val16 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val16, vec, 2) < 0)
-               GOTO_FAIL;
-       if (val16 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* uint32_t vector */
-       vec = ec_vec(sizeof(val32), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_u32(vec, 0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u32(vec, 1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u32(vec, 2) < 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&val32, vec, 0) < 0)
-               GOTO_FAIL;
-       if (val32 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val32, vec, 1) < 0)
-               GOTO_FAIL;
-       if (val32 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val32, vec, 2) < 0)
-               GOTO_FAIL;
-       if (val32 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* uint64_t vector */
-       vec = ec_vec(sizeof(val64), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_u64(vec, 0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u64(vec, 1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_u64(vec, 2) < 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&val64, vec, 0) < 0)
-               GOTO_FAIL;
-       if (val64 != 0)
-               GOTO_FAIL;
-       if (ec_vec_get(&val64, vec, 1) < 0)
-               GOTO_FAIL;
-       if (val64 != 1)
-               GOTO_FAIL;
-       if (ec_vec_get(&val64, vec, 2) < 0)
-               GOTO_FAIL;
-       if (val64 != 2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* ptr vector */
-       vec = ec_vec(sizeof(valp), 0, NULL, NULL);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_ptr(vec, (void *)0) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, (void *)1) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, (void *)2) < 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&valp, vec, 0) < 0)
-               GOTO_FAIL;
-       if (valp != (void *)0)
-               GOTO_FAIL;
-       if (ec_vec_get(&valp, vec, 1) < 0)
-               GOTO_FAIL;
-       if (valp != (void *)1)
-               GOTO_FAIL;
-       if (ec_vec_get(&valp, vec, 2) < 0)
-               GOTO_FAIL;
-       if (valp != (void *)2)
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* string vector */
-       vec = ec_vec(sizeof(valp), 0, NULL, str_free);
-       if (vec == NULL)
-               GOTO_FAIL;
-
-       if (ec_vec_add_ptr(vec, ec_strdup("0")) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, ec_strdup("1")) < 0)
-               GOTO_FAIL;
-       if (ec_vec_add_ptr(vec, ec_strdup("2")) < 0)
-               GOTO_FAIL;
-
-       if (ec_vec_get(&vals, vec, 0) < 0)
-               GOTO_FAIL;
-       if (vals == NULL || strcmp(vals, "0"))
-               GOTO_FAIL;
-       if (ec_vec_get(&vals, vec, 1) < 0)
-               GOTO_FAIL;
-       if (vals == NULL || strcmp(vals, "1"))
-               GOTO_FAIL;
-       if (ec_vec_get(&vals, vec, 2) < 0)
-               GOTO_FAIL;
-       if (vals == NULL || strcmp(vals, "2"))
-               GOTO_FAIL;
-
-       ec_vec_free(vec);
-       vec = NULL;
-
-       /* invalid args */
-       vec = ec_vec(0, 0, NULL, NULL);
-       if (vec != NULL)
-               GOTO_FAIL;
-
-       return 0;
-
-fail:
-       ec_vec_free(vec);
-       ec_vec_free(vec2);
-       return -1;
-}
-/* LCOV_EXCL_STOP */
-
-static struct ec_test ec_vec_test = {
-       .name = "vec",
-       .test = ec_vec_testcase,
-};
-
-EC_TEST_REGISTER(ec_vec_test);
diff --git a/libecoli/ecoli_vec.h b/libecoli/ecoli_vec.h
deleted file mode 100644 (file)
index 5fdaa99..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Vectors of objects.
- *
- * The ec_vec API provide helpers to manipulate vectors of objects
- * of any kind.
- */
-
-#ifndef ECOLI_VEC_
-#define ECOLI_VEC_
-
-#include <sys/types.h>
-#include <stdint.h>
-#include <stdio.h>
-
-/* if NULL, default does nothing */
-typedef void (*ec_vec_elt_free_t)(void *ptr);
-
-/* if NULL, default is:
- * memcpy(dst, src, vec->elt_size)
- */
-typedef void (*ec_vec_elt_copy_t)(void *dst, void *src);
-
-struct ec_vec *ec_vec(size_t elt_size, size_t size,
-               ec_vec_elt_copy_t copy, ec_vec_elt_free_t free);
-int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr);
-
-int ec_vec_add_ptr(struct ec_vec *vec, void *elt);
-int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt);
-int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt);
-int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt);
-int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt);
-
-int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx);
-
-struct ec_vec *ec_vec_dup(const struct ec_vec *vec);
-struct ec_vec *ec_vec_ndup(const struct ec_vec *vec,
-       size_t off, size_t len);
-void ec_vec_free(struct ec_vec *vec);
-
-__attribute__((pure))
-size_t ec_vec_len(const struct ec_vec *vec);
-
-#endif
diff --git a/libecoli_editline/ecoli_editline.c b/libecoli_editline/ecoli_editline.c
deleted file mode 100644 (file)
index 4cc7ec4..0000000
+++ /dev/null
@@ -1,684 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include <histedit.h>
-
-#include <ecoli_utils.h>
-#include <ecoli_malloc.h>
-#include <ecoli_string.h>
-#include <ecoli_editline.h>
-#include <ecoli_keyval.h>
-#include <ecoli_node.h>
-#include <ecoli_parse.h>
-#include <ecoli_complete.h>
-
-struct ec_editline {
-       EditLine *el;
-       History *history;
-       HistEvent histev;
-       const struct ec_node *node;
-       char *prompt;
-};
-
-/* used by qsort below */
-static int
-strcasecmp_cb(const void *p1, const void *p2)
-{
-       return strcasecmp(*(char * const *)p1, *(char * const *)p2);
-}
-
-/* Show the matches as a multi-columns list */
-int
-ec_editline_print_cols(struct ec_editline *editline,
-               char const * const *matches, size_t n)
-{
-       size_t max_strlen = 0, len, i, j, ncols;
-       int width, height;
-       const char *space;
-       char **matches_copy = NULL;
-       FILE *f;
-
-       if (el_get(editline->el, EL_GETFP, 1, &f))
-               return -1;
-
-       fprintf(f, "\n");
-       if (n == 0)
-               return 0;
-
-       if (el_get(editline->el, EL_GETTC, "li", &height, (void *)0))
-               return -1;
-       if (el_get(editline->el, EL_GETTC, "co", &width, (void *)0))
-               return -1;
-
-       /* duplicate the matches table, and sort it */
-       matches_copy = calloc(n, sizeof(const char *));
-       if (matches_copy == NULL)
-               return -1;
-       memcpy(matches_copy, matches, sizeof(const char *) * n);
-       qsort(matches_copy, n, sizeof(char *), strcasecmp_cb);
-
-       /* get max string length */
-       for (i = 0; i < n; i++) {
-               len = strlen(matches_copy[i]);
-               if (len > max_strlen)
-                       max_strlen = len;
-       }
-
-       /* write the columns */
-       ncols = width / (max_strlen + 4);
-       if (ncols == 0)
-               ncols = 1;
-       for (i = 0; i < n; i+= ncols) {
-               for (j = 0; j < ncols; j++) {
-                       if (i + j >= n)
-                               break;
-                       if (j == 0)
-                               space = "";
-                       else
-                               space = "    ";
-                       fprintf(f, "%s%-*s", space,
-                               (int)max_strlen, matches[i+j]);
-               }
-               fprintf(f, "\n");
-       }
-
-       free(matches_copy);
-       return 0;
-}
-
-/* Show the helps on editline output */
-int
-ec_editline_print_helps(struct ec_editline *editline,
-                       const struct ec_editline_help *helps, size_t len)
-{
-       size_t i;
-       FILE *out;
-
-       if (el_get(editline->el, EL_GETFP, 1, &out))
-               return -1;
-
-       for (i = 0; i < len; i++) {
-               if (fprintf(out, "%-20s %s\n",
-                               helps[i].desc, helps[i].help) < 0)
-                       return -1;
-       }
-
-       return 0;
-}
-
-void
-ec_editline_free_helps(struct ec_editline_help *helps, size_t len)
-{
-       size_t i;
-
-       if (helps == NULL)
-               return;
-       for (i = 0; i < len; i++) {
-               ec_free(helps[i].desc);
-               ec_free(helps[i].help);
-       }
-       ec_free(helps);
-}
-
-int
-ec_editline_set_prompt(struct ec_editline *editline, const char *prompt)
-{
-       char *copy = NULL;
-
-       if (prompt != NULL) {
-               ec_strdup(prompt);
-               if (copy == NULL)
-                       return -1;
-       }
-
-       ec_free(editline->prompt);
-       editline->prompt = copy;
-
-       return 0;
-}
-
-static char *
-prompt_cb(EditLine *el)
-{
-       struct ec_editline *editline;
-       void *clientdata;
-
-       if (el_get(el, EL_CLIENTDATA, &clientdata))
-               return "> ";
-       editline = clientdata;
-
-       if (editline == NULL)
-               return "> ";
-
-       return editline->prompt;
-}
-
-struct ec_editline *
-ec_editline(const char *name, FILE *f_in, FILE *f_out, FILE *f_err,
-       unsigned int flags)
-{
-       struct ec_editline *editline = NULL;
-       EditLine *el;
-
-       if (f_in == NULL || f_out == NULL || f_err == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-
-       editline = ec_calloc(1, sizeof(*editline));
-       if (editline == NULL)
-               goto fail;
-
-       el = el_init(name, f_in, f_out, f_err);
-       if (el == NULL)
-               goto fail;
-       editline->el = el;
-
-       /* save editline pointer as user data */
-       if (el_set(el, EL_CLIENTDATA, editline))
-               goto fail;
-
-       /* install default editline signals */
-       if (el_set(el, EL_SIGNAL, 1))
-               goto fail;
-
-       if (el_set(el, EL_PREP_TERM, 0))
-               goto fail;
-
-       /* use emacs bindings */
-       if (el_set(el, EL_EDITOR, "emacs"))
-               goto fail;
-       if (el_set(el, EL_BIND, "^W", "ed-delete-prev-word", NULL))
-               goto fail;
-
-       /* ask terminal to not send signals */
-       if (flags & EC_EDITLINE_DISABLE_SIGNALS) {
-               if (el_set(el, EL_SETTY, "-d", "-isig", NULL))
-                       goto fail;
-       }
-
-       /* set prompt */
-       editline->prompt = ec_strdup("> ");
-       if (editline->prompt == NULL)
-               goto fail;
-       if (el_set(el, EL_PROMPT, prompt_cb))
-               goto fail;
-
-       /* set up history */
-       if ((flags & EC_EDITLINE_DISABLE_HISTORY) == 0) {
-               if (ec_editline_set_history(
-                               editline, EC_EDITLINE_HISTORY_SIZE) < 0)
-                       goto fail;
-       }
-
-       /* register completion callback */
-       if ((flags & EC_EDITLINE_DISABLE_COMPLETION) == 0) {
-               if (el_set(el, EL_ADDFN, "ed-complete", "Complete buffer",
-                               ec_editline_complete))
-                       goto fail;
-               if (el_set(el, EL_BIND, "^I", "ed-complete", NULL))
-                       goto fail;
-               if (el_set(el, EL_BIND, "?", "ed-complete", NULL))
-                       goto fail;
-       }
-
-       return editline;
-
-fail:
-       ec_editline_free(editline);
-       return NULL;
-}
-
-void ec_editline_free(struct ec_editline *editline)
-{
-       if (editline == NULL)
-               return;
-       if (editline->el != NULL)
-               el_end(editline->el);
-       if (editline->history != NULL)
-               history_end(editline->history);
-       ec_free(editline->prompt);
-       ec_free(editline);
-}
-
-EditLine *ec_editline_get_el(struct ec_editline *editline)
-{
-       return editline->el;
-}
-
-const struct ec_node *
-ec_editline_get_node(struct ec_editline *editline)
-{
-       return editline->node;
-}
-
-void
-ec_editline_set_node(struct ec_editline *editline, const struct ec_node *node)
-{
-       editline->node = node;
-}
-
-int ec_editline_set_history(struct ec_editline *editline,
-       size_t hist_size)
-{
-       EditLine *el = editline->el;
-
-       if (editline->history != NULL)
-               history_end(editline->history);
-
-       if (hist_size == 0)
-               return 0;
-
-       editline->history = history_init();
-       if (editline->history == NULL)
-               goto fail;
-       if (history(editline->history, &editline->histev, H_SETSIZE,
-                       hist_size) < 0)
-               goto fail;
-       if (history(editline->history, &editline->histev, H_SETUNIQUE, 1))
-               goto fail;
-       if (el_set(el, EL_HIST, history, editline->history))
-               goto fail;
-
-       return 0;
-
-fail:
-       //XXX errno
-       if (editline->history != NULL) {
-               history_end(editline->history);
-               editline->history = NULL;
-       }
-       return -1;
-}
-
-void ec_editline_free_completions(char **matches, size_t len)
-{
-       size_t i;
-
-       // XXX use ec_malloc/ec_free() instead for consistency
-       if (matches == NULL)
-               return;
-       for (i = 0; i < len; i++)
-               free(matches[i]);
-       free(matches);
-}
-
-ssize_t
-ec_editline_get_completions(const struct ec_comp *cmpl, char ***matches_out)
-{
-       const struct ec_comp_item *item;
-       struct ec_comp_iter *iter = NULL;
-       char **matches = NULL;
-       size_t count = 0;
-
-       iter = ec_comp_iter(cmpl, EC_COMP_FULL | EC_COMP_PARTIAL);
-       if (iter == NULL)
-               goto fail;
-
-       while ((item = ec_comp_iter_next(iter)) != NULL) {
-               char **tmp;
-
-               tmp = realloc(matches, (count + 1) * sizeof(char *));
-               if (tmp == NULL)
-                       goto fail;
-               matches = tmp;
-               matches[count] = strdup(ec_comp_item_get_display(item));
-               if (matches[count] == NULL)
-                       goto fail;
-               count++;
-       }
-
-       *matches_out = matches;
-       return count;
-
-fail:
-       ec_editline_free_completions(matches, count);
-       *matches_out = NULL;
-       ec_comp_iter_free(iter);
-       return -1;
-}
-
-char *
-ec_editline_append_chars(const struct ec_comp *cmpl)
-{
-       const struct ec_comp_item *item;
-       struct ec_comp_iter *iter = NULL;
-       const char *append;
-       char *ret = NULL;
-       size_t n;
-
-       iter = ec_comp_iter(cmpl, EC_COMP_FULL | EC_COMP_PARTIAL);
-       if (iter == NULL)
-               goto fail;
-
-       while ((item = ec_comp_iter_next(iter)) != NULL) {
-               append = ec_comp_item_get_completion(item);
-               if (ret == NULL) {
-                       ret = ec_strdup(append);
-                       if (ret == NULL)
-                               goto fail;
-               } else {
-                       n = ec_strcmp_count(ret, append);
-                       ret[n] = '\0';
-               }
-       }
-       ec_comp_iter_free(iter);
-
-       return ret;
-
-fail:
-       ec_comp_iter_free(iter);
-       ec_free(ret);
-
-       return NULL;
-}
-
-/* this function builds the help string */
-static int get_node_help(const struct ec_comp_item *item,
-                       struct ec_editline_help *help)
-{
-       const struct ec_comp_group *grp;
-       const struct ec_parse *state;
-       const struct ec_node *node;
-       const char *node_help = NULL;
-       const char *node_desc = NULL;
-
-       help->desc = NULL;
-       help->help = NULL;
-
-       grp = ec_comp_item_get_grp(item);
-
-       for (state = grp->state; state != NULL;
-            state = ec_parse_get_parent(state)) {
-               node = ec_parse_get_node(state);
-               if (node_help == NULL)
-                       node_help = ec_keyval_get(ec_node_attrs(node), "help");
-               if (node_desc == NULL)
-                       node_desc = ec_node_desc(node);
-       }
-
-       if (node_help == NULL)
-               node_help = "";
-       if (node_desc == NULL)
-               goto fail;
-
-       help->desc = ec_strdup(node_desc);
-       if (help->desc == NULL)
-               goto fail;
-
-       help->help = ec_strdup(node_help);
-       if (help->help == NULL)
-               goto fail;
-
-       return 0;
-
-fail:
-       ec_free(help->desc);
-       ec_free(help->help);
-       return -1;
-}
-
-ssize_t
-ec_editline_get_helps(const struct ec_editline *editline, const char *line,
-       const char *full_line, struct ec_editline_help **helps_out)
-{
-       struct ec_comp_iter *iter = NULL;
-       const struct ec_comp_group *grp, *prev_grp = NULL;
-       const struct ec_comp_item *item;
-       struct ec_comp *cmpl = NULL;
-       struct ec_parse *parse = NULL;
-       unsigned int count = 0;
-       struct ec_editline_help *helps = NULL;
-
-       *helps_out = NULL;
-
-       /* check if the current line matches */
-       parse = ec_node_parse(editline->node, full_line);
-       if (ec_parse_matches(parse))
-               count = 1;
-       ec_parse_free(parse);
-       parse = NULL;
-
-       /* complete at current cursor position */
-       cmpl = ec_node_complete(editline->node, line);
-       if (cmpl == NULL) //XXX log error
-               goto fail;
-
-       /* let's display one contextual help per node */
-       iter = ec_comp_iter(cmpl,
-               EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL);
-       if (iter == NULL)
-               goto fail;
-
-       helps = ec_calloc(1, sizeof(*helps));
-       if (helps == NULL)
-               goto fail;
-       if (count == 1) {
-               helps[0].desc = ec_strdup("<return>");
-               if (helps[0].desc == NULL)
-                       goto fail;
-               helps[0].help = ec_strdup("Validate command.");
-               if (helps[0].help == NULL)
-                       goto fail;
-       }
-
-       while ((item = ec_comp_iter_next(iter)) != NULL) {
-               struct ec_editline_help *tmp = NULL;
-
-               /* keep one help per group, skip other items  */
-               grp = ec_comp_item_get_grp(item);
-               if (grp == prev_grp)
-                       continue;
-
-               prev_grp = grp;
-
-               tmp = ec_realloc(helps, (count + 1) * sizeof(*helps));
-               if (tmp == NULL)
-                       goto fail;
-               helps = tmp;
-               if (get_node_help(item, &helps[count]) < 0)
-                       goto fail;
-               count++;
-       }
-
-       ec_comp_iter_free(iter);
-       ec_comp_free(cmpl);
-       *helps_out = helps;
-
-       return count;
-
-fail:
-       ec_comp_iter_free(iter);
-       ec_parse_free(parse);
-       ec_comp_free(cmpl);
-       if (helps != NULL) {
-               while (count--) {
-                       ec_free(helps[count].desc);
-                       ec_free(helps[count].help);
-               }
-               ec_free(helps);
-       }
-
-       return -1;
-}
-
-int
-ec_editline_complete(EditLine *el, int c)
-{
-       struct ec_editline *editline;
-       const LineInfo *line_info;
-       int ret = CC_REFRESH;
-       struct ec_comp *cmpl = NULL;
-       char *append = NULL;
-       char *line = NULL;
-       void *clientdata;
-       FILE *out, *err;
-       int len;
-
-       if (el_get(el, EL_GETFP, 1, &out))
-               return -1;
-       if (el_get(el, EL_GETFP, 1, &err))
-               return -1;
-
-       (void)c;
-
-       if (el_get(el, EL_CLIENTDATA, &clientdata)) {
-               fprintf(err, "completion failure: no client data\n");
-               goto fail;
-       }
-       editline = clientdata;
-       (void)editline;
-
-       line_info = el_line(el);
-       if (line_info == NULL) {
-               fprintf(err, "completion failure: no line info\n");
-               goto fail;
-       }
-
-       len = line_info->cursor - line_info->buffer;
-       if (ec_asprintf(&line, "%.*s", len, line_info->buffer) < 0) {
-               fprintf(err, "completion failure: no memory\n");
-               goto fail;
-       }
-
-       if (editline->node == NULL) {
-               fprintf(err, "completion failure: no ec_node\n");
-               goto fail;
-       }
-
-       cmpl = ec_node_complete(editline->node, line);
-       if (cmpl == NULL)
-               goto fail;
-
-       append = ec_editline_append_chars(cmpl);
-
-       if (c == '?') {
-               struct ec_editline_help *helps = NULL;
-               ssize_t count = 0;
-
-               count = ec_editline_get_helps(editline, line, line_info->buffer,
-                               &helps);
-
-               fprintf(out, "\n");
-               if (ec_editline_print_helps(editline, helps, count) < 0) {
-                       fprintf(err, "completion failure: cannot show help\n");
-                       ec_editline_free_helps(helps, count);
-                       goto fail;
-               }
-
-               ec_editline_free_helps(helps, count);
-               ret = CC_REDISPLAY;
-       } else if (append == NULL || strcmp(append, "") == 0) {
-               char **matches = NULL;
-               ssize_t count = 0;
-
-               count = ec_editline_get_completions(cmpl, &matches);
-               if (count < 0) {
-                       fprintf(err, "completion failure: cannot get completions\n");
-                       goto fail;
-               }
-
-               if (ec_editline_print_cols(
-                               editline,
-                               EC_CAST(matches, char **,
-                                       char const * const *),
-                               count) < 0) {
-                       fprintf(err, "completion failure: cannot print\n");
-                       ec_editline_free_completions(matches, count);
-                       goto fail;
-               }
-
-               ec_editline_free_completions(matches, count);
-               ret = CC_REDISPLAY;
-       } else {
-               if (el_insertstr(el, append) < 0) {
-                       fprintf(err, "completion failure: cannot insert\n");
-                       goto fail;
-               }
-               if (ec_comp_count(cmpl, EC_COMP_FULL) +
-                       ec_comp_count(cmpl, EC_COMP_PARTIAL) == 1) {
-                       if (el_insertstr(el, " ") < 0) {
-                               fprintf(err, "completion failure: cannot insert space\n");
-                               goto fail;
-                       }
-               }
-       }
-
-       ec_comp_free(cmpl);
-       ec_free(line);
-       ec_free(append);
-
-       return ret;
-
-fail:
-       ec_comp_free(cmpl);
-       ec_free(line);
-       ec_free(append);
-
-       return CC_ERROR;
-}
-
-char *
-ec_editline_gets(struct ec_editline *editline)
-{
-       EditLine *el = editline->el;
-       char *line_copy = NULL;
-       const char *line;
-       int count;
-
-       line = el_gets(el, &count);
-       if (line == NULL)
-               return NULL;
-
-       line_copy = ec_strdup(line);
-       if (line_copy == NULL)
-               goto fail;
-
-       line_copy[strlen(line_copy) - 1] = '\0'; //XXX needed because of sh_lex bug?
-
-       if (editline->history != NULL && !ec_str_is_space(line_copy)) {
-               history(editline->history, &editline->histev,
-                       H_ENTER, line_copy);
-       }
-
-       return line_copy;
-
-fail:
-       ec_free(line_copy);
-       return NULL;
-}
-
-struct ec_parse *
-ec_editline_parse(struct ec_editline *editline, const struct ec_node *node)
-{
-       char *line = NULL;
-       struct ec_parse *parse = NULL;
-
-       /* XXX add sh_lex automatically? This node is required, parse and
-        * complete are based on it. */
-
-       ec_editline_set_node(editline, node);
-
-       line = ec_editline_gets(editline);
-       if (line == NULL)
-               goto fail;
-
-       parse = ec_node_parse(node, line);
-       if (parse == NULL)
-               goto fail;
-
-       ec_free(line);
-       return parse;
-
-fail:
-       ec_free(line);
-       ec_parse_free(parse);
-
-       return NULL;
-}
-
diff --git a/libecoli_editline/ecoli_editline.h b/libecoli_editline/ecoli_editline.h
deleted file mode 100644 (file)
index 58824d2..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * Helpers that can be used to associate an editline instance with
- * an ecoli node tree.
- *
- * XXX support saved history
- * XXX support multiline edition
- * XXX set prompt
- */
-
-#ifndef ECOLI_EDITLINE_
-#define ECOLI_EDITLINE_
-
-#include <histedit.h>
-
-struct ec_editline;
-struct ec_node;
-struct ec_parse;
-struct ec_comp;
-
-struct ec_editline_help {
-       char *desc;
-       char *help;
-};
-
-/**
- * Default history size.
- */
-#define EC_EDITLINE_HISTORY_SIZE 128
-
-/**
- * Ask the terminal to not send signals (STOP, SUSPEND, XXX). The
- * ctrl-c, ctrl-z will be interpreted as standard characters. An
- * action can be associated to these characters with:
- *
- *     static int cb(EditLine *editline, int c) {
- *     {
- *             see editline documentation for details
- *     }
- *
- *     if (el_set(el, EL_ADDFN, "ed-foobar", "Help string about foobar", cb))
- *             handle_error;
- *     if (el_set(el, EL_BIND, "^C", "ed-break", NULL))
- *             handle_error;
- *
- * The default behavior (without this flag) is to let the signal pass: ctrl-c
- * will stop program and ctrl-z will suspend it.
- */
-#define EC_EDITLINE_DISABLE_SIGNALS 0x01
-
-/**
- * Disable history. The default behavior creates an history with
- * EC_EDITLINE_HISTORY_SIZE entries. To change this value, use
- * ec_editline_set_history().
- */
-#define EC_EDITLINE_DISABLE_HISTORY 0x02
-
-/**
- * Disable completion. The default behavior is to complete when
- * '?' or '<tab>' is hit. You can register your own callback with:
- *
- *     if (el_set(el, EL_ADDFN, "ed-complete", "Complete buffer", callback))
- *             handle_error;
- *     if (el_set(el, EL_BIND, "^I", "ed-complete", NULL))
- *             handle_error;
- *
- * The default used callback is ec_editline_complete().
- */
-#define EC_EDITLINE_DISABLE_COMPLETION 0x04
-
-typedef int (*ec_editline_cmpl_t)(struct ec_editline *editline, int c);
-
-/**
- * Create an editline instance with default behavior.
- *
- * XXX Wrapper to editline's el_init() 
- *
- * It 
- */
-struct ec_editline *
-ec_editline(const char *name, FILE *f_in, FILE *f_out, FILE *f_err,
-       unsigned int flags);
-
-/**
- * Free an editline instance allocated with ec_editline().
- */
-void ec_editline_free(struct ec_editline *editline);
-
-/**
- * Return the editline instance attached to the ec_editline object.
- */
-EditLine *ec_editline_get_el(struct ec_editline *editline);
-
-// XXX public?
-const struct ec_node *ec_editline_get_node(struct ec_editline *editline);
-void ec_editline_set_node(struct ec_editline *editline,
-                       const struct ec_node *node);
-
-//XXX get history, get_...
-
-/**
- * Change the history size.
- *
- * The default behavior is to have an history whose size
- * is EC_EDITLINE_HISTORY_SIZE. This can be changed with this
- * function.
- *
- * @param editline
- *   The pointer to the ec_editline structure.
- * @param hist_size
- *   The desired size of the history.
- * @return
- *   0 on success, or -1 on error (errno is set).
- */
-int ec_editline_set_history(struct ec_editline *editline,
-       size_t hist_size);
-
-int
-ec_editline_print_cols(struct ec_editline *editline,
-               char const * const *matches, size_t n);
-
-void ec_editline_free_completions(char **matches, size_t len);
-ssize_t
-ec_editline_get_completions(const struct ec_comp *cmpl, char ***matches_out);
-char *
-ec_editline_append_chars(const struct ec_comp *cmpl);
-
-ssize_t
-ec_editline_get_helps(const struct ec_editline *editline, const char *line,
-       const char *full_line, struct ec_editline_help **helps_out);
-int
-ec_editline_print_helps(struct ec_editline *editline,
-                       const struct ec_editline_help *helps, size_t n);
-void
-ec_editline_free_helps(struct ec_editline_help *helps, size_t len);
-
-int
-ec_editline_set_prompt(struct ec_editline *editline, const char *prompt);
-
-
-
-
-/**
- * Get a line.
- *
- * The returned line must be freed by the caller using ec_free().
- */
-char *ec_editline_gets(struct ec_editline *editline);
-
-/**
- * Get a line (managing completion) and parse it with passed node
- * XXX find a better name?
- */
-struct ec_parse *
-ec_editline_parse(struct ec_editline *editline, const struct ec_node *node);
-
-int
-ec_editline_complete(EditLine *el, int c);
-
-#endif
diff --git a/libecoli_editline/editline.c b/libecoli_editline/editline.c
deleted file mode 100644 (file)
index 449aebb..0000000
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * Copyright 2018 6WIND S.A.
- */
-
-#define _GNU_SOURCE
-#include <stdio.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <string.h>
-#include <assert.h>
-#include <ctype.h>
-#include <stdarg.h>
-#include <signal.h>
-#include <unistd.h>
-
-#include <histedit.h>
-
-#include "string_utils.h"
-#include "editline.h"
-
-#define NC_CLI_HISTORY_SIZE 128
-
-struct nc_cli_editline {
-       EditLine *editline;
-       History *history;
-       HistEvent histev;
-       bool break_received;
-       nc_cli_editline_complete_t complete;
-       FILE *null_out;
-       bool interactive;
-       bool incomplete_line;
-       char *full_line;
-       char *(*prompt_cb)(EditLine *);
-};
-
-struct nc_cli_editline *nc_cli_el;
-
-static int check_quotes(const char *str)
-{
-       char quote = 0;
-       size_t i = 0;
-
-       while (str[i] != '\0') {
-               if (quote == 0) {
-                       if (str[i] == '"' || str[i] == '\'') {
-                               quote = str[i];
-                       }
-                       i++;
-                       continue;
-               } else {
-                       if (str[i] == quote) {
-                               i++;
-                               quote = 0;
-                       } else if (str[i] == '\\' && str[i+1] == quote) {
-                               i += 2;
-                       } else {
-                               i++;
-                       }
-                       continue;
-               }
-       }
-
-       return quote;
-}
-
-static int
-editline_break(EditLine *editline, int c)
-{
-       struct nc_cli_editline *el;
-       void *ptr;
-
-       (void)c;
-
-       if (el_get(editline, EL_CLIENTDATA, &ptr))
-               return CC_ERROR;
-
-       el = ptr;
-       el->break_received = true;
-       nc_cli_printf(el, "\n");
-
-       return CC_EOF;
-}
-
-static int
-editline_suspend(EditLine *editline, int c)
-{
-       (void)editline;
-       (void)c;
-
-       kill(getpid(), SIGSTOP);
-
-       return CC_NORM;
-}
-
-int
-editline_get_screen_size(struct nc_cli_editline *el, size_t *rows, size_t *cols)
-{
-       int w, h;
-
-       if (rows != NULL) {
-               if (el_get(el->editline, EL_GETTC, "li", &h, (void *)0))
-                       return -1;
-               *rows = h;
-       }
-       if (cols != NULL) {
-               if (el_get(el->editline, EL_GETTC, "co", &w, (void *)0))
-                       return -1;
-               *cols = w;
-       }
-       return 0;
-}
-
-FILE *
-nc_cli_editline_get_file(struct nc_cli_editline *el, int num)
-{
-       FILE *f;
-
-       if (el == NULL)
-               return NULL;
-       if (num > 2)
-               return NULL;
-       if (el_get(el->editline, EL_GETFP, num, &f))
-               return NULL;
-
-       return f;
-}
-
-/* match the prototype expected by qsort() */
-static int
-strcasecmp_cb(const void *p1, const void *p2)
-{
-       return strcasecmp(*(char * const *)p1, *(char * const *)p2);
-}
-
-/* Show the matches as a multi-columns list */
-int
-nc_cli_editline_print_cols(struct nc_cli_editline *el,
-                       char const * const *matches, size_t n)
-{
-       size_t max_strlen = 0, len, i, j, ncols;
-       size_t width, height;
-       const char *space;
-       char **matches_copy = NULL;
-
-       nc_cli_printf(nc_cli_el, "\n");
-       if (n == 0)
-               return 0;
-
-       if (editline_get_screen_size(el, &height, &width) < 0)
-               width = 80;
-
-       /* duplicate the matches table, and sort it */
-       matches_copy = calloc(n, sizeof(const char *));
-       if (matches_copy == NULL)
-               return -1;
-       memcpy(matches_copy, matches, sizeof(const char *) * n);
-       qsort(matches_copy, n, sizeof(char *), strcasecmp_cb);
-
-       /* get max string length */
-       for (i = 0; i < n; i++) {
-               len = strlen(matches_copy[i]);
-               if (len > max_strlen)
-                       max_strlen = len;
-       }
-
-       /* write the columns */
-       ncols = width / (max_strlen + 4);
-       if (ncols == 0)
-               ncols = 1;
-       for (i = 0; i < n; i+= ncols) {
-               for (j = 0; j < ncols; j++) {
-                       if (i + j >= n)
-                               break;
-                       if (j == 0)
-                               space = "";
-                       else
-                               space = "    ";
-                       nc_cli_printf(nc_cli_el, "%s%-*s", space,
-                               (int)max_strlen, matches[i+j]);
-               }
-               nc_cli_printf(nc_cli_el, "\n");
-       }
-
-       free(matches_copy);
-       return 0;
-}
-
-static int
-editline_complete(EditLine *editline, int c)
-{
-       enum nc_cli_editline_complete_status ret;
-       const LineInfo *line_info;
-       struct nc_cli_editline *el;
-       int len;
-       char *line;
-       void *ptr;
-
-       if (el_get(editline, EL_CLIENTDATA, &ptr))
-               return CC_ERROR;
-
-       el = ptr;
-
-       if (el->complete == NULL)
-               return CC_NORM;
-
-       line_info = el_line(editline);
-       if (line_info == NULL)
-               return CC_ERROR;
-
-       len = line_info->cursor - line_info->buffer;
-       if (asprintf(&line, "%s%.*s", el->full_line ? : "", len,
-                       line_info->buffer) < 0)
-               return CC_ERROR;
-
-       if (c == '?' && check_quotes(line) != 0) {
-               free(line);
-               el_insertstr(editline, "?");
-               return CC_REFRESH;
-       }
-
-       ret = el->complete(c, line);
-       free(line);
-
-       if (ret == ERROR)
-               return CC_ERROR;
-       else if (ret == REDISPLAY)
-               return CC_REDISPLAY;
-       else
-               return CC_REFRESH;
-}
-
-static bool is_blank_string(const char *s)
-{
-       while (*s) {
-               if (!isspace(*s))
-                       return false;
-               s++;
-       }
-       return true;
-}
-
-static char *
-multiline_prompt_cb(EditLine *e)
-{
-       (void)e;
-       return strdup("... ");
-}
-
-const char *
-nc_cli_editline_edit(struct nc_cli_editline *el)
-{
-       const char *line;
-       int count;
-
-       assert(el->editline != NULL);
-
-       if (el->incomplete_line == false) {
-               free(el->full_line);
-               el->full_line = NULL;
-       }
-
-       el->break_received = false;
-
-       if (el->incomplete_line)
-               el_set(el->editline, EL_PROMPT, multiline_prompt_cb);
-       else
-               el_set(el->editline, EL_PROMPT, el->prompt_cb);
-
-       line = el_gets(el->editline, &count);
-
-       if (line == NULL && el->break_received) {
-               free(el->full_line);
-               el->full_line = NULL;
-               el->incomplete_line = false;
-               return ""; /* abort current line */
-       }
-
-       if (line == NULL || astrcat(&el->full_line, line) < 0) {
-               free(el->full_line);
-               el->full_line = NULL;
-               el->incomplete_line = false;
-               return NULL; /* error / eof */
-       }
-
-       if (check_quotes(el->full_line) != 0) {
-               el->incomplete_line = true;
-               return "";
-       }
-
-       el->incomplete_line = false;
-       if (el->history != NULL && !is_blank_string(el->full_line))
-               history(el->history, &el->histev,
-                       H_ENTER, el->full_line);
-
-       return el->full_line;
-}
-
-int
-nc_cli_editline_register_complete(struct nc_cli_editline *el,
-                               nc_cli_editline_complete_t complete)
-{
-       const char *name;
-
-       if (el_set(el->editline, EL_ADDFN, "ed-complete",
-                       "Complete buffer",
-                       editline_complete))
-               return -1;
-
-       if (complete != NULL)
-               name = "ed-complete";
-       else
-               name = "ed-unassigned";
-       if (el_set(el->editline, EL_BIND, "^I", name, NULL))
-               return -1;
-
-       if (complete != NULL)
-               name = "ed-complete";
-       else
-               name = "ed-insert";
-       if (el_set(el->editline, EL_BIND, "?", name, NULL))
-               return -1;
-
-       el->complete = complete;
-
-       return 0;
-}
-
-int
-nc_cli_editline_insert_str(struct nc_cli_editline *el, const char *str)
-{
-       return el_insertstr(el->editline, str);
-}
-
-int nc_cli_printf(struct nc_cli_editline *el, const char *format, ...)
-{
-       FILE *out = nc_cli_editline_get_file(el, 1);
-       va_list ap;
-       int ret;
-
-       if (out == NULL)
-               out = stdout;
-
-       va_start(ap, format);
-       ret = vfprintf(out, format, ap);
-       va_end(ap);
-
-       return ret;
-}
-
-int nc_cli_eprintf(struct nc_cli_editline *el, const char *format, ...)
-{
-       FILE *out = nc_cli_editline_get_file(el, 2);
-       va_list ap;
-       int ret;
-
-       if (out == NULL)
-               out = stderr;
-
-       va_start(ap, format);
-       ret = vfprintf(out, format, ap);
-       va_end(ap);
-
-       return ret;
-}
-
-bool
-nc_cli_editline_is_interactive(struct nc_cli_editline *el)
-{
-       return el->interactive;
-}
-
-bool
-nc_cli_editline_is_running(struct nc_cli_editline *el)
-{
-       return el == nc_cli_el;
-}
-
-void
-nc_cli_editline_start(struct nc_cli_editline *el)
-{
-       nc_cli_el = el;
-}
-
-void
-nc_cli_editline_stop(struct nc_cli_editline *el)
-{
-       if (el == nc_cli_el)
-               nc_cli_el = NULL;
-}
-
-int
-nc_cli_editline_getc(struct nc_cli_editline *el)
-{
-       char c;
-
-       if (el->interactive == false)
-               return -1;
-
-       if (el_getc(el->editline, &c) != 1)
-               return -1;
-
-       return c;
-}
-
-int
-nc_cli_editline_resize(struct nc_cli_editline *el)
-{
-       el_resize(el->editline);
-       return 0;
-}
-
-int nc_cli_editline_set_prompt_cb(struct nc_cli_editline *el,
-                               char *(*prompt_cb)(EditLine *el))
-{
-       el->prompt_cb = prompt_cb;
-       return 0;
-}
-
-int
-nc_cli_editline_mask_interrupts(bool do_mask)
-{
-       const char *setty = do_mask ? "-isig" : "+isig";
-
-       if (nc_cli_el == NULL)
-               return -1;
-
-       if (nc_cli_el->interactive == false)
-               return 0;
-
-       if (el_set(nc_cli_el->editline, EL_SETTY, "-d", setty, NULL))
-               return -1;
-
-       return 0;
-}
-
-struct nc_cli_editline *
-nc_cli_editline_init(FILE *f_in, FILE *f_out, FILE *f_err, bool interactive,
-               char *(*prompt_cb)(EditLine *el))
-{
-       struct nc_cli_editline *el = NULL;
-
-       el = calloc(1, sizeof(*el));
-       if (el == NULL)
-               goto fail;
-       if (f_in == NULL) {
-               errno = EINVAL;
-               goto fail;
-       }
-       if (f_out == NULL || f_err == NULL) {
-               el->null_out = fopen("/dev/null", "w");
-               if (el->null_out == NULL)
-                       goto fail;
-       }
-       if (f_out == NULL)
-               f_out = el->null_out;
-       if (f_err == NULL)
-               f_err = el->null_out;
-
-       el->interactive = interactive;
-       el->prompt_cb = prompt_cb;
-
-       el->editline = el_init("nc-cli", f_in, f_out, f_err);
-       if (el->editline == NULL)
-               goto fail;
-
-       if (el_set(el->editline, EL_SIGNAL, 1))
-               goto fail;
-
-       if (el_set(el->editline, EL_CLIENTDATA, el))
-               goto fail;
-
-       if (el_set(el->editline, EL_PROMPT, prompt_cb))
-               goto fail;
-
-       if (interactive == false)
-               goto end;
-
-       if (el_set(el->editline, EL_PREP_TERM, 0))
-               goto fail;
-
-       if (el_set(el->editline, EL_EDITOR, "emacs"))
-               goto fail;
-
-       if (el_set(el->editline, EL_SETTY, "-d", "-isig", NULL))
-               goto fail;
-       if (el_set(el->editline, EL_ADDFN, "ed-break",
-                       "Break and flush the buffer",
-                       editline_break))
-               goto fail;
-       if (el_set(el->editline, EL_BIND, "^C", "ed-break", NULL))
-               goto fail;
-       if (el_set(el->editline, EL_ADDFN, "ed-suspend",
-                       "Suspend the terminal",
-                       editline_suspend))
-               goto fail;
-       if (el_set(el->editline, EL_BIND, "^Z", "ed-suspend", NULL))
-               goto fail;
-       if (el_set(el->editline, EL_BIND, "^W", "ed-delete-prev-word", NULL))
-               goto fail;
-
-       el->history = history_init();
-       if (!el->history)
-               goto fail;
-       if (history(el->history, &el->histev, H_SETSIZE,
-                       NC_CLI_HISTORY_SIZE) < 0)
-               goto fail;
-       if (history(el->history, &el->histev,
-                       H_SETUNIQUE, 1))
-               goto fail;
-       if (el_set(el->editline, EL_HIST, history,
-                       el->history))
-               goto fail;
-
-end:
-       return el;
-
-fail:
-       if (el != NULL) {
-               if (el->null_out != NULL)
-                       fclose(el->null_out);
-               if (el->history != NULL)
-                       history_end(el->history);
-               if (el->editline != NULL)
-                       el_end(el->editline);
-               free(el);
-       }
-       return NULL;
-}
-
-void
-nc_cli_editline_free(struct nc_cli_editline *el)
-{
-       if (el == NULL)
-               return;
-       nc_cli_editline_stop(el);
-       if (el->null_out != NULL)
-               fclose(el->null_out);
-       if (el->history != NULL)
-               history_end(el->history);
-       el_end(el->editline);
-       free(el->full_line);
-       free(el);
-}
diff --git a/libecoli_yaml/ecoli_yaml.c b/libecoli_yaml/ecoli_yaml.c
deleted file mode 100644 (file)
index 84007d5..0000000
+++ /dev/null
@@ -1,550 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-#include <errno.h>
-#include <limits.h>
-#include <assert.h>
-
-#include <yaml.h>
-
-#include <ecoli_malloc.h>
-#include <ecoli_keyval.h>
-#include <ecoli_node.h>
-#include <ecoli_config.h>
-#include <ecoli_yaml.h>
-
-/* 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 enode_table *table, const yaml_document_t *document,
-       const yaml_node_t *ynode);
-
-static struct ec_config *
-parse_ec_config_list(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 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 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(table, document, ynode);
-               if (enode == NULL)
-                       goto fail;
-               config = ec_config_node(enode);
-               enode = NULL;
-               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(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(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 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(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 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(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 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;
-       char *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 = ec_strdup(value_str);
-                       if (help == NULL) {
-                               fprintf(stderr, "Failed to allocate help\n");
-                               goto fail;
-                       }
-               }
-       }
-
-       /* 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;
-       }
-
-       /* 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(table, schema, document, ynode);
-       if (config == NULL)
-               goto fail;
-
-       if (ec_node_set_config(enode, config) < 0) {
-               config = NULL; /* freed */
-               fprintf(stderr, "Failed to set config\n");
-               goto fail;
-       }
-       config = NULL; /* freed */
-
-       if (help != NULL) {
-               if (ec_keyval_set(ec_node_attrs(enode), "help", help,
-                                       ec_free_func) < 0) {
-                       fprintf(stderr, "Failed to set help\n");
-                       help = NULL;
-                       goto fail;
-               }
-               help = NULL;
-       }
-
-       /* add attributes (all as string) */
-       //XXX
-
-       return enode;
-
-fail:
-       ec_node_free(enode);
-       ec_config_free(config);
-       ec_free(help);
-
-       return NULL;
-}
-
-static struct ec_node *
-parse_document(struct enode_table *table,
-       const yaml_document_t *document)
-{
-       yaml_node_t *node;
-
-       node = document->nodes.start;
-       return parse_ec_node(table, document, node);
-}
-
-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;
-       }
-
-       root = parse_document(&table, &document);
-       if (root == NULL) {
-               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);
-       free_table(&table);
-       ec_node_free(root);
-
-       return NULL;
-}
diff --git a/libecoli_yaml/ecoli_yaml.h b/libecoli_yaml/ecoli_yaml.h
deleted file mode 100644 (file)
index a63d837..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
- */
-
-/**
- * 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
diff --git a/meson.build b/meson.build
new file mode 100644 (file)
index 0000000..dee9cb6
--- /dev/null
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+
+project('libecoli',
+       'c',
+       version : '0.1',
+       license : 'BSD-3-clause')
+
+# if debug
+add_global_arguments('-Wall', language : 'c')
+add_global_arguments('-Werror', language : 'c')
+add_global_arguments('-W', language : 'c')
+add_global_arguments('-Wextra', language : 'c')
+
+edit_dep = dependency('libedit', method: 'pkg-config')
+yaml_dep = dependency('yaml-0.1', method: 'pkg-config')
+
+subdir('src')
+subdir('test')
+subdir('examples')
+
+pkg_mod = import('pkgconfig')
+pkg_mod.generate(libraries : libecoli,
+       version: '0.1',
+       name : 'libecoli',
+       filebase : 'libecoli',
+       description : 'Extensible command line library.')
diff --git a/mk/ecoli-ar-rules.mk b/mk/ecoli-ar-rules.mk
deleted file mode 100644 (file)
index 9121d4f..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# dump some infos if debug is enabled
-ifeq ($(D),1)
-$(call disp_list,------ all-ar,$(all-ar))
-$(foreach ar,$(all-ar),\
-       $(info,out-$(ar): $(out-$(ar))) \
-       $(call disp_list,pre-$(ar),$(pre-$(ar))) \
-)
-endif
-
-# include dependencies and commands files if they exist
-$(foreach ar,$(all-ar),\
-       $(eval -include $(call depfile,$(ar))) \
-       $(eval -include $(call cmdfile,$(ar))) \
-)
-
-# remove duplicates
-filtered-all-ar := $(sort $(all-ar))
-
-# link several objects files into one shared object
-$(filtered-all-ar): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
-       @$(call display_deps,$(pre-$(@)),$@,\
-               $(call ar_cmd,$(pre-$(@)),$@),$?)
-       @$(if $(call check_deps,$@,$(call ar_cmd,$(pre-$(@)),$@),$?),\
-               $(call ar_print_cmd,$(pre-$(@)),$@) && \
-               $(call ar_cmd,$(pre-$(@)),$@) && \
-               $(call save_cmd,$(call ar_cmd,$(pre-$(@)),$@),$@) && \
-               $(call create_empty_depfile,$@))
diff --git a/mk/ecoli-ar-vars.mk b/mk/ecoli-ar-vars.mk
deleted file mode 100644 (file)
index 0a06b26..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# ar-y-$(ar) is provided by the user
-#   $(ar) is the path of the static library, and the variable contains
-#   the list of sources. Several ar-y-$(ar) can be present.
-
-# list all ar builds requested by user
-all-ar := $(patsubst ar-y-%,%,$(filter ar-y-%,$(.VARIABLES)))
-
-# add them to the list of targets
-all-targets += $(all-ar)
-
-# for each ar, create the following variables:
-#   out-$(ar) = output path of the arcutable
-#   pre-$(ar) = list of prerequisites for this arcutable
-# Some source files need intermediate objects, we define these variables
-# for them too, and add them in a list: $(all-iobj).
-# Last, we add the generated files in $(all-clean-file).
-$(foreach ar,$(all-ar),\
-       $(eval out-$(ar) := $(dir $(ar))) \
-       $(eval pre-$(ar) := ) \
-       $(foreach src,$(ar-y-$(ar)), \
-               $(if $(call is_cc_source,$(src)), \
-                       $(eval iobj := $(call src2iobj,$(src),$(out-$(ar)))) \
-                       $(eval pre-$(iobj) := $(src)) \
-                       $(eval all-iobj += $(iobj)) \
-                       $(eval all-clean-file += $(iobj)) \
-                       $(eval pre-$(ar) += $(iobj)) \
-               , \
-               $(if $(call is_obj_source,$(src)),\
-                       $(eval pre-$(ar) += $(src)) \
-               , \
-               $(error "unsupported source format: $(src)"))) \
-       )\
-       $(eval all-clean-file += $(ar)) \
-)
-
-# link several *.o files into a static libary
-#   $1: sources (*.o)
-#   $2: dst (xyz.a)
-ar_cmd = ar crsD $(2) $(1)
-
-# print line used to ar object files
-ifeq ($(V),1)
-ar_print_cmd = echo $(call protect_quote,$(call ar_cmd,$1,$2))
-else
-ar_print_cmd = echo "  AR $(2)"
-endif
-
-all-clean-file += $(all-ar)
diff --git a/mk/ecoli-clean-rules.mk b/mk/ecoli-clean-rules.mk
deleted file mode 100644 (file)
index 6cdf02b..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-.PHONY: _ecoli_clean
-_ecoli_clean: $(all-clean-target) FORCE
-       @$(call clean_print_cmd,$(all-clean-file) $(call depfile,$(all-clean-file)) \
-               $(call cmdfile,$(all-clean-file))) && \
-       $(call clean_cmd,$(all-clean-file) $(call depfile,$(all-clean-file)) \
-               $(call cmdfile,$(all-clean-file)))
diff --git a/mk/ecoli-clean-vars.mk b/mk/ecoli-clean-vars.mk
deleted file mode 100644 (file)
index 6b47154..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# remove files
-#  $1: files
-clean_cmd = rm -rf $(1)
-
-# print line used to clean files
-ifeq ($(V),1)
-clean_print_cmd = echo $(call protect_quote,$(call clean_cmd,$1))
-else
-clean_print_cmd = echo "  CLEAN $(CURDIR)"
-endif
diff --git a/mk/ecoli-copy-rules.mk b/mk/ecoli-copy-rules.mk
deleted file mode 100644 (file)
index 3c3c43b..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# dump some infos if debug is enabled
-ifeq ($(D),1)
-$(call disp_list,------ all-copy,$(all-copy))
-$(foreach copy,$(all-copy),\
-       $(info,out-$(copy): $(out-$(copy))) \
-       $(call disp_list,pre-$(copy),$(pre-$(copy))) \
-)
-endif
-
-# include dependencies and commands files if they exist
-$(foreach copy,$(all-copy),\
-       $(eval -include $(call depfile,$(copy))) \
-       $(eval -include $(call cmdfile,$(copy))) \
-)
-
-# remove duplicates
-filtered-all-copy := $(sort $(all-copy))
-
-# convert format of executable
-$(filtered-all-copy): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
-       @$(call display_deps,$(pre-$(@)),$@,\
-               $(call copy_cmd,$(pre-$(@)),$@),$?)
-       @$(if $(call check_deps,$@,$(call copy_cmd,$(pre-$(@)),$@),$?),\
-               $(call copy_print_cmd,$(pre-$(@)),$@) && \
-               $(call copy_cmd,$(pre-$(@)),$@) && \
-               $(call save_cmd,$(call copy_cmd,$(pre-$(@)),$@),$@) && \
-               $(call create_empty_depfile,$@))
diff --git a/mk/ecoli-copy-vars.mk b/mk/ecoli-copy-vars.mk
deleted file mode 100644 (file)
index d6230d7..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# copy a file
-# copy-y-$(copy) is provided by the user
-#   $(copy) is the path of the directory containing the destination
-#   files, and the variable contains the path of the files to copy. Several
-#   copy-y-$(copy) can be present.
-
-# list all path requested by user
-_all-copy := $(patsubst copy-y-%,%,$(filter copy-y-%,$(.VARIABLES)))
-all-copy :=
-
-# for each copy, create the following variables:
-#   out-$(copy) = output path of the executable
-#   pre-$(copy) = list of prerequisites for this executable
-# We also add the files in $(all-copy).
-$(foreach copy,$(_all-copy),\
-       $(if $(notdir $(copy)), \
-               $(if $(call compare,$(words $(copy-y-$(copy))),1), \
-                       $(error "only one source file is allowed in copy-y-$(copy)")) \
-               $(eval dst := $(dir $(copy))$(notdir $(copy-y-$(copy)))) \
-               $(eval out-$(copy) := $(dir $(copy))) \
-               $(eval pre-$(copy) := $(copy-y-$(copy))) \
-               $(eval all-copy += $(dst)) \
-       , \
-               $(foreach src,$(copy-y-$(copy)),\
-                       $(eval dst := $(copy)$(notdir $(src))) \
-                       $(eval out-$(copy) := $(copy)) \
-                       $(eval pre-$(dst) := $(src)) \
-                       $(eval all-copy += $(dst)) \
-               ) \
-       ) \
-)
-
-# add them to the list of targets and clean
-all-targets += $(all-copy)
-all-clean-file += $(all-copy)
-
-# convert format of executable from elf to ihex
-#   $1: source executable (elf)
-#   $2: destination file
-copy_cmd = $(CP) $(1) $(2)
-
-# print line used to convert executable format
-ifeq ($(V),1)
-copy_print_cmd = echo $(call protect_quote,$(call copy_cmd,$1,$2))
-else
-copy_print_cmd = echo "  COPY $(2)"
-endif
diff --git a/mk/ecoli-exe-rules.mk b/mk/ecoli-exe-rules.mk
deleted file mode 100644 (file)
index 71b7ed1..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# dump some infos if debug is enabled
-ifeq ($(D),1)
-$(call disp_list,------ all-exe,$(all-exe))
-$(foreach exe,$(all-exe),\
-       $(info,out-$(exe): $(out-$(exe))) \
-       $(call disp_list,pre-$(exe),$(pre-$(exe))) \
-)
-endif
-
-# include dependencies and commands files if they exist
-$(foreach exe,$(all-exe),\
-       $(eval -include $(call depfile,$(exe))) \
-       $(eval -include $(call cmdfile,$(exe))) \
-)
-
-# remove duplicates
-filtered-all-exe := $(sort $(all-exe))
-
-# link several objects files into one executable
-$(filtered-all-exe): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
-       @$(call display_deps,$(pre-$(@)),$@,\
-               $(call link_cmd,$(pre-$(@)),$@),$?)
-       @$(if $(call check_deps,$@,$(call link_cmd,$(pre-$(@)),$@),$?),\
-               $(call link_print_cmd,$(pre-$(@)),$@) && \
-               $(call link_cmd,$(pre-$(@)),$@) && \
-               $(call save_cmd,$(call link_cmd,$(pre-$(@)),$@),$@) && \
-               $(call create_empty_depfile,$@))
diff --git a/mk/ecoli-exe-vars.mk b/mk/ecoli-exe-vars.mk
deleted file mode 100644 (file)
index 1e5ae16..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# exe-y-$(exe) is provided by the user
-#   $(exe) is the path of the binary, and the variable contains
-#   the list of sources. Several exe-y-$(exe) can be present.
-
-# list all exe builds requested by user
-all-exe := $(patsubst exe-y-%,%,$(filter exe-y-%,$(.VARIABLES)))
-
-# add them to the list of targets
-all-targets += $(all-exe)
-
-# for each exe, create the following variables:
-#   out-$(exe) = output path of the executable
-#   pre-$(exe) = list of prerequisites for this executable
-# Some source files need intermediate objects, we define these variables
-# for them too, and add them in a list: $(all-iobj).
-# Last, we add the generated files in $(all-clean-file).
-$(foreach exe,$(all-exe),\
-       $(eval out-$(exe) := $(dir $(exe))) \
-       $(eval pre-$(exe) := ) \
-       $(foreach src,$(exe-y-$(exe)), \
-               $(if $(call is_cc_source,$(src)), \
-                       $(eval iobj := $(call src2iobj,$(src),$(out-$(exe)))) \
-                       $(eval pre-$(iobj) := $(src)) \
-                       $(eval all-iobj += $(iobj)) \
-                       $(eval all-clean-file += $(iobj)) \
-                       $(eval pre-$(exe) += $(iobj)) \
-               , \
-               $(if $(call is_obj_source,$(src)),\
-                       $(eval pre-$(exe) += $(src)) \
-               , \
-               $(if $(call is_alib_source,$(src)),\
-                       $(eval pre-$(exe) += $(src)) \
-               , \
-               $(error "unsupported source format: $(src)")))) \
-       )\
-       $(eval all-clean-file += $(exe)) \
-)
-
-# link several *.o files into a exeary
-#   $1: sources (*.o) (*.a)
-#   $2: dst (xyz.o too)
-link_cmd = $(CC) $(LDFLAGS) $(ldflags-$(2)) -o $(2) $(filter %.o,$(1)) \
-        $(filter %.a,$(1)) $(LDLIBS) $(ldlibs-$(2))
-
-# print line used to link object files
-ifeq ($(V),1)
-link_print_cmd = echo $(call protect_quote,$(call link_cmd,$1,$2))
-else
-link_print_cmd = echo "  EXE $(2)"
-endif
-
-all-clean-file += $(all-exe)
diff --git a/mk/ecoli-obj-rules.mk b/mk/ecoli-obj-rules.mk
deleted file mode 100644 (file)
index 24c606f..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# dump some infos if debug is enabled
-ifeq ($(D),1)
-$(call disp_list,------ all-obj,$(all-obj))
-$(foreach obj,$(all-obj),\
-       $(info,out-$(obj): $(out-$(obj))) \
-       $(call disp_list,pre-$(obj),$(pre-$(obj))) \
-)
-$(call disp_list,------ all-iobj,$(all-iobj))
-$(foreach iobj,$(all-iobj),\
-       $(call disp_list,pre-$(iobj),$(pre-$(iobj))) \
-)
-endif
-
-# if a generated file has the same name than a user target,
-# generate an error
-conflicts := $(filter $(all-iobj),$(all-targets))
-$(if $(conflicts), \
-       $(error Intermediate file has the same names than user targets:\
-               $(conflicts)))
-
-# include dependencies and commands files if they exist
-$(foreach obj,$(all-obj),\
-       $(eval -include $(call depfile,$(obj))) \
-       $(eval -include $(call cmdfile,$(obj))) \
-)
-$(foreach iobj,$(all-iobj),\
-       $(eval -include $(call depfile,$(iobj))) \
-       $(eval -include $(call cmdfile,$(iobj))) \
-)
-
-# remove duplicates
-filtered-all-iobj := $(sort $(all-iobj))
-
-# convert source files to intermediate object file
-$(filtered-all-iobj): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
-       @$(call display_deps,$(pre-$(@)),$@,$(call compile_cmd,$(pre-$(@)),$@),$?)
-       @$(if $(call check_deps,$@,$(call compile_cmd,$(pre-$(@)),$@),$?),\
-               $(call compile_print_cmd,$(pre-$(@)),$@) && \
-               $(call compile_cmd,$(pre-$(@)),$@) && \
-               $(call save_cmd,$(call compile_cmd,$(pre-$(@)),$@),$@) && \
-               $(call obj-fixdep,$@))
-
-# remove duplicates
-filtered-all-obj := $(sort $(all-obj))
-
-# combine several objects files to one
-$(filtered-all-obj): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
-       @$(call display_deps,$(pre-$(@)),$@,\
-               $(call combine_cmd,$(pre-$(@)),$@),$?)
-       @$(if $(call check_deps,$@,$(call combine_cmd,$(pre-$(@)),$@),$?),\
-               $(call combine_print_cmd,$(pre-$(@)),$@) && \
-               $(call combine_cmd,$(pre-$(@)),$@) && \
-               $(call save_cmd,$(call combine_cmd,$(pre-$(@)),$@),$@) && \
-               $(call create_empty_depfile,$@))
diff --git a/mk/ecoli-obj-vars.mk b/mk/ecoli-obj-vars.mk
deleted file mode 100644 (file)
index b6fe847..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# obj-y-$(obj) is provided by the user
-#  $(obj) is the path of the object, and the variable contains
-#  the list of sources. Several obj-y-$(obj) can be present.
-
-# list all object builds requested by user
-all-obj := $(patsubst obj-y-%,%,$(filter obj-y-%,$(.VARIABLES)))
-
-# add them to the list of targets
-all-targets += $(all-obj)
-
-# convert source path to intermediate object path, and filter
-# objects from sources
-#  $1: list of source paths
-#  $2: output directory (including trailing slash)
-#  return: list of intermediate object paths
-src2iobj = $(addprefix $(filter-out ./,$(2)),$(notdir $(strip \
-       $(patsubst %.c,%.o,\
-       $(patsubst %.s,%.o,\
-       $(filter-out %.o,$(1)))))))
-
-# return the file if it matches a extension that is built with cc
-#   $1: source file
-is_cc_source = $(filter %.c %.s %S,$(1))
-
-# return the file if it's already an object file: in this case no
-# intermediate object is needed
-#   $1: source file
-is_obj_source = $(filter %.o,$(1))
-
-# return the file if it's a static library
-#   $1: source file
-is_alib_source = $(filter %.a,$(1))
-
-# for each obj, create the following variables:
-#   out-$(obj) = output path of the object
-#   pre-$(obj) = list of prerequisites for this object
-# Some source files need intermediate objects, we define these variables
-# for them too, and add them in a list: $(all-iobj).
-# Last, we add the generated files in $(all-clean-file).
-$(foreach obj,$(all-obj),\
-       $(eval out-$(obj) := $(dir $(obj))) \
-       $(eval pre-$(obj) := ) \
-       $(foreach src,$(obj-y-$(obj)), \
-               $(if $(call is_cc_source,$(src)), \
-                       $(eval iobj := $(call src2iobj,$(src),$(out-$(obj)))) \
-                       $(eval pre-$(iobj) := $(src)) \
-                       $(eval all-iobj += $(iobj)) \
-                       $(eval all-clean-file += $(iobj)) \
-                       $(eval pre-$(obj) += $(iobj)) \
-               , \
-               $(if $(call is_obj_source,$(src)),\
-                       $(eval pre-$(obj) += $(src)) \
-               , \
-               $(error "unsupported source format: $(src)"))) \
-       )\
-       $(eval all-clean-file += $(obj)) \
-)
-
-# fix the format of .o.d.tmp (generated by gcc) to a .o.d that defines
-# dependencies as makefile variables
-#  $1: object file (.o)
-obj-fixdep = if [ -f $(call file2tmpdep,$(1)) ]; then\
-               echo -n "dep-$(1) = " > $(call depfile,$(1)) && \
-               sed 's,^[^ ][^:]*: ,,' $(call file2tmpdep,$(1)) >> $(call depfile,$(1)) && \
-               rm -f $(call file2tmpdep,$(1)); \
-       else \
-               $(call create_empty_depfile,$(1)); \
-       fi
-
-# compile a file
-#  $1: sources
-#  $2: dst
-compile_cmd = $(CC) -Wp,-MD,$(call file2tmpdep,$(2)) \
-       $(CPPFLAGS) $(cppflags-$(2)) \
-       $(CFLAGS) $(cflags-$(2)) \
-       -c -o $2 $1
-
-# print line used to compile a file
-ifeq ($(V),1)
-compile_print_cmd = echo $(call protect_quote,$(call compile_cmd,$1,$2))
-else
-compile_print_cmd = echo "  CC $(2)"
-endif
-
-# combine several *.o files into one
-#   $1: sources (*.o)
-#   $2: dst (xyz.o too)
-combine_cmd = $(LD) -r $(1) -o $(2)
-
-# print line used to combine object files
-ifeq ($(V),1)
-combine_print_cmd = echo $(call protect_quote,$(call combine_cmd,$1,$2))
-else
-combine_print_cmd = echo "  LD $(2)"
-endif
-
-all-clean-file += $(all-obj)
diff --git a/mk/ecoli-objcopy-rules.mk b/mk/ecoli-objcopy-rules.mk
deleted file mode 100644 (file)
index ebe9306..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# dump some infos if debug is enabled
-ifeq ($(D),1)
-$(call disp_list,------ all-objcopy-hex,$(all-objcopy-hex))
-$(foreach objcopy,$(all-objcopy-hex),\
-       $(info,out-$(objcopy): $(out-$(objcopy))) \
-       $(call disp_list,pre-$(objcopy),$(pre-$(objcopy))) \
-)
-$(call disp_list,------ all-objcopy-bin,$(all-objcopy-bin))
-$(foreach objcopy,$(all-objcopy-bin),\
-       $(info,out-$(objcopy): $(out-$(objcopy))) \
-       $(call disp_list,pre-$(objcopy),$(pre-$(objcopy))) \
-)
-endif
-
-# include dependencies and commands files if they exist
-$(foreach objcopy,$(all-objcopy-hex) $(all-objcopy-bin),\
-       $(eval -include $(call depfile,$(objcopy))) \
-       $(eval -include $(call cmdfile,$(objcopy))) \
-)
-
-# remove duplicates
-filtered-all-objcopy-hex := $(sort $(all-objcopy-hex))
-
-# convert format of executable
-$(filtered-all-objcopy-hex): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
-       @$(call display_deps,$(pre-$(@)),$@,\
-               $(call objcopy_hex_cmd,$(pre-$(@)),$@),$?)
-       @$(if $(call check_deps,$@,$(call objcopy_hex_cmd,$(pre-$(@)),$@),$?),\
-               $(call objcopy_print_cmd,$(pre-$(@)),$@) && \
-               $(call objcopy_hex_cmd,$(pre-$(@)),$@) && \
-               $(call save_cmd,$(call objcopy_hex_cmd,$(pre-$(@)),$@),$@) && \
-               $(call create_empty_depfile,$@))
-
-# remove duplicates
-filtered-all-objcopy-bin := $(sort $(all-objcopy-bin))
-
-# convert format of executable
-$(filtered-all-objcopy-bin): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
-       @$(call display_deps,$(pre-$(@)),$@,\
-               $(call objcopy_bin_cmd,$(pre-$(@)),$@),$?)
-       @$(if $(call check_deps,$@,$(call objcopy_bin_cmd,$(pre-$(@)),$@),$?),\
-               $(call objcopy_print_cmd,$(pre-$(@)),$@) && \
-               $(call objcopy_bin_cmd,$(pre-$(@)),$@) && \
-               $(call save_cmd,$(call objcopy_bin_cmd,$(pre-$(@)),$@),$@) && \
-               $(call create_empty_depfile,$@))
diff --git a/mk/ecoli-objcopy-vars.mk b/mk/ecoli-objcopy-vars.mk
deleted file mode 100644 (file)
index 0a4b486..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# objcopy changes the format of a binary
-# objcopy-hex-y-$(objcopy), objcopy-bin-y-$(objcopy) are provided by the user
-#   $(objcopy) is the path of the binary, and the variable contains
-#   the path to the elf. Several objcopy-y-$(objcopy) can be present.
-
-# list all executable builds requested by user
-all-objcopy-hex := $(patsubst objcopy-hex-y-%,%,$(filter objcopy-hex-y-%,$(.VARIABLES)))
-all-objcopy-bin := $(patsubst objcopy-bin-y-%,%,$(filter objcopy-bin-y-%,$(.VARIABLES)))
-
-# add them to the list of targets
-all-targets += $(all-objcopy-hex) $(all-objcopy-bin)
-
-# for each objcopy, create the following variables:
-#   out-$(objcopy) = output path of the executable
-#   pre-$(objcopy) = list of prerequisites for this executable
-# We also add the generated files in $(all-clean-file).
-$(foreach objcopy,$(all-objcopy-hex),\
-       $(if $(call compare,$(words $(objcopy-hex-y-$(objcopy))),1),\
-               $(error "only one source file is allowed in objcopy-hex-y-$(objcopy)")) \
-       $(eval out-$(objcopy) := $(dir $(objcopy))) \
-       $(eval pre-$(objcopy) := $(objcopy-hex-y-$(objcopy))) \
-       $(eval all-clean-file += $(objcopy)) \
-)
-
-# for each objcopy, create the following variables:
-#   out-$(objcopy) = output path of the executable
-#   pre-$(objcopy) = list of prerequisites for this executable
-# We also add the generated files in $(all-clean-file).
-$(foreach objcopy,$(all-objcopy-bin),\
-       $(if $(call compare,$(words $(objcopy-bin-y-$(objcopy))),1),\
-               $(error "only one source file is allowed in objcopy-bin-y-$(objcopy)")) \
-       $(eval out-$(objcopy) := $(dir $(objcopy))) \
-       $(eval pre-$(objcopy) := $(objcopy-bin-y-$(objcopy))) \
-       $(eval all-clean-file += $(objcopy)) \
-)
-
-# convert format of executable from elf to ihex
-#   $1: source executable (elf)
-#   $2: destination file
-objcopy_hex_cmd = $(OBJCOPY) -O ihex $(1) $(2)
-
-# print line used to convert executable format
-ifeq ($(V),1)
-objcopy_print_cmd = echo $(call protect_quote,$(call objcopy_hex_cmd,$1,$2))
-else
-objcopy_print_cmd = echo "  OBJCOPY $(2)"
-endif
-
-# convert format of executable from elf to binary
-#   $1: source executable (elf)
-#   $2: destination file
-objcopy_bin_cmd = $(OBJCOPY) -O binary $(1) $(2)
-
-# print line used to convert executable format
-ifeq ($(V),1)
-objcopy_print_cmd = echo $(call protect_quote,$(call objcopy_bin_cmd,$1,$2))
-else
-objcopy_print_cmd = echo "  OBJCOPY $(2)"
-endif
-
-# XXX dup ?
-all-clean-file += $(all-objcopy-hex) $(all-objcopy-bin)
diff --git a/mk/ecoli-post.mk b/mk/ecoli-post.mk
deleted file mode 100644 (file)
index 7430673..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# ---- variables that must be defined:
-#
-#  ECOLI: path to ecoli root
-#
-# ---- variable that can be defined anywhere
-#
-#  CROSS: prefix of the toolchain
-#  CP, LN, GAWK, GREP: coreutils tools
-#  CC, CPP, AR, LD, OBJCOPY, OBJDUMP, STRIP: compilers/binutils
-#
-# ---- variable that can be defined by Makefile:
-#
-#  obj-y-$(path)
-#  exe-y-$(path)
-#  ar-y-$(path)
-#  shlib-y-$(path)
-#  copy-y-$(path)
-#  slink-y-$(path)
-#  objcopy-y-$(path)
-#  subdir-y
-#
-#  CPPFLAGS, CFLAGS, LDFLAGS, LDLIBS: global flags
-#  cflags-$(path), cppflags-$(path), ldflags-$(path), ldlibs-$(path): per
-#    file flags
-#  mkflags-$(path): flags for subdirectories
-#
-# ---- variables that can be defined on the command line:
-#
-#  EXTRA_CPPFLAGS, EXTRA_CFLAGS, EXTRA_LDFLAGS, EXTRA_LDLIBS: global
-#    extra flags
-#  extra-cflags-$(path), extra-cppflags-$(path): per object extra flags
-
-ifeq ($(ECOLI),)
-$(error ECOLI environment variable is not defined)
-endif
-
-# list of targets asked by user
-all-targets :=
-# list of files generated
-all-clean-file :=
-
-# usual internal variables:
-#   out-$(file) = output path of a generated file
-#   pre-$(file) = list of files needed to generate $(file)
-#   all-type = list of targets for this type
-
-include $(ECOLI)/mk/ecoli-obj-vars.mk
-include $(ECOLI)/mk/ecoli-exe-vars.mk
-include $(ECOLI)/mk/ecoli-ar-vars.mk
-include $(ECOLI)/mk/ecoli-shlib-vars.mk
-include $(ECOLI)/mk/ecoli-copy-vars.mk
-include $(ECOLI)/mk/ecoli-slink-vars.mk
-include $(ECOLI)/mk/ecoli-objcopy-vars.mk
-include $(ECOLI)/mk/ecoli-subdir-vars.mk
-# must stay at the end
-include $(ECOLI)/mk/ecoli-clean-vars.mk
-
-# dump the list of targets
-ifeq ($(D),1)
-$(call disp_list,------ all-targets,$(all-targets))
-endif
-
-# first rule (default)
-.PHONY: _ecoli_all
-_ecoli_all: $(all-targets)
-
-# the includes below require second expansion
-.SECONDEXPANSION:
-
-include $(ECOLI)/mk/ecoli-obj-rules.mk
-include $(ECOLI)/mk/ecoli-exe-rules.mk
-include $(ECOLI)/mk/ecoli-ar-rules.mk
-include $(ECOLI)/mk/ecoli-shlib-rules.mk
-include $(ECOLI)/mk/ecoli-copy-rules.mk
-include $(ECOLI)/mk/ecoli-slink-rules.mk
-include $(ECOLI)/mk/ecoli-objcopy-rules.mk
-include $(ECOLI)/mk/ecoli-subdir-rules.mk
-include $(ECOLI)/mk/ecoli-clean-rules.mk
-
-.PHONY: FORCE
-FORCE:
diff --git a/mk/ecoli-pre.mk b/mk/ecoli-pre.mk
deleted file mode 100644 (file)
index 1d56ec2..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# ---- variables that must be defined:
-#
-#  ECOLI: path to ecoli root
-#
-
-ifeq ($(ECOLI),)
-$(error ECOLI environment variable is not defined)
-endif
-
-MAKEFLAGS += --no-print-directory
-
-include $(ECOLI)/mk/ecoli-tools.mk
-
-include $(ECOLI)/mk/ecoli-vars.mk
-
diff --git a/mk/ecoli-shlib-rules.mk b/mk/ecoli-shlib-rules.mk
deleted file mode 100644 (file)
index b03d2a7..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# dump some infos if debug is enabled
-ifeq ($(D),1)
-$(call disp_list,------ all-shlib,$(all-shlib))
-$(foreach shlib,$(all-shlib),\
-       $(info,out-$(shlib): $(out-$(shlib))) \
-       $(call disp_list,pre-$(shlib),$(pre-$(shlib))) \
-)
-endif
-
-# include dependencies and commands files if they exist
-$(foreach shlib,$(all-shlib),\
-       $(eval -include $(call depfile,$(shlib))) \
-       $(eval -include $(call cmdfile,$(shlib))) \
-)
-
-# remove duplicates
-filtered-all-shlib := $(sort $(all-shlib))
-
-# link several objects files into one shared object
-$(filtered-all-shlib): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
-       @$(call display_deps,$(pre-$(@)),$@,\
-               $(call shlib_cmd,$(pre-$(@)),$@),$?)
-       @$(if $(call check_deps,$@,$(call shlib_cmd,$(pre-$(@)),$@),$?),\
-               $(call shlib_print_cmd,$(pre-$(@)),$@) && \
-               $(call shlib_cmd,$(pre-$(@)),$@) && \
-               $(call save_cmd,$(call shlib_cmd,$(pre-$(@)),$@),$@) && \
-               $(call create_empty_depfile,$@))
diff --git a/mk/ecoli-shlib-vars.mk b/mk/ecoli-shlib-vars.mk
deleted file mode 100644 (file)
index 55b982c..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# shlib-y-$(shlib) is provided by the user
-#   $(shlib) is the path of the shared library, and the variable
-#   contains the list of sources. Several shlib-y-$(shlib) can be
-#   present.
-
-# list all shlib builds requested by user
-all-shlib := $(patsubst shlib-y-%,%,$(filter shlib-y-%,$(.VARIABLES)))
-
-# add them to the list of targets
-all-targets += $(all-shlib)
-
-# for each shlib, create the following variables:
-#   out-$(shlib) = output path of the shlibcutable
-#   pre-$(shlib) = list of prerequisites for this shlibcutable
-# Some source files need intermediate objects, we define these variables
-# for them too, and add them in a list: $(all-iobj).
-# Last, we add the generated files in $(all-clean-file).
-$(foreach shlib,$(all-shlib),\
-       $(eval out-$(shlib) := $(dir $(shlib))) \
-       $(eval pre-$(shlib) := ) \
-       $(foreach src,$(shlib-y-$(shlib)), \
-               $(if $(call is_cc_source,$(src)), \
-                       $(eval iobj := $(call src2iobj,$(src),$(out-$(shlib)))) \
-                       $(eval pre-$(iobj) := $(src)) \
-                       $(eval all-iobj += $(iobj)) \
-                       $(eval all-clean-file += $(iobj)) \
-                       $(eval pre-$(shlib) += $(iobj)) \
-               , \
-               $(if $(call is_obj_source,$(src)),\
-                       $(eval pre-$(shlib) += $(src)) \
-               , \
-               $(error "unsupported source format: $(src)"))) \
-       )\
-       $(eval all-clean-file += $(shlib)) \
-)
-
-# link several *.o files into a shared libary
-#   $1: sources (*.o)
-#   $2: dst (xyz.so)
-shlib_cmd = $(CC) $(LDFLAGS) $(ldflags-$(2)) -shared -o $(2) $(1)
-
-# print line used to shlib object files
-ifeq ($(V),1)
-shlib_print_cmd = echo $(call protect_quote,$(call shlib_cmd,$1,$2))
-else
-shlib_print_cmd = echo "  SHLIB $(2)"
-endif
-
-all-clean-file += $(all-shlib)
diff --git a/mk/ecoli-slink-rules.mk b/mk/ecoli-slink-rules.mk
deleted file mode 100644 (file)
index f91a09a..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# dump some infos if debug is enabled
-ifeq ($(D),1)
-$(call disp_list,------ all-slink,$(all-slink))
-$(foreach slink,$(all-slink),\
-       $(info,out-$(slink): $(out-$(slink))) \
-       $(call disp_list,pre-$(slink),$(pre-$(slink))) \
-)
-endif
-
-# include dependencies and commands files if they exist
-$(foreach slink,$(all-slink),\
-       $(eval -include $(call depfile,$(slink))) \
-       $(eval -include $(call cmdfile,$(slink))) \
-)
-
-# remove duplicates
-filtered-all-slink := $(sort $(all-slink))
-
-# convert format of executable
-$(filtered-all-slink): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@)
-       @$(call display_deps,$(pre-$(@)),$@,\
-               $(call slink_cmd,$(pre-$(@)),$@),$?)
-       @$(if $(call check_deps,$@,$(call slink_cmd,$(pre-$(@)),$@),$?),\
-               $(call slink_print_cmd,$(pre-$(@)),$@) && \
-               $(call slink_cmd,$(pre-$(@)),$@) && \
-               $(call save_cmd,$(call slink_cmd,$(pre-$(@)),$@),$@) && \
-               $(call create_empty_depfile,$@))
diff --git a/mk/ecoli-slink-vars.mk b/mk/ecoli-slink-vars.mk
deleted file mode 100644 (file)
index 1559a98..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# create a symbolic link of a file
-# slink-y-$(slink) is provided by the user
-#   $(slink) is the path of the directory containing the destination
-#   files, and the variable contains the path of the files to linked. Several
-#   slink-y-$(slink) can be present.
-
-# list all path requested by user
-_all-slink := $(patsubst slink-y-%,%,$(filter slink-y-%,$(.VARIABLES)))
-all-slink :=
-
-# for each slink, create the following variables:
-#   out-$(slink) = output path of the executable
-#   pre-$(slink) = list of prerequisites for this executable
-# We also add the files in $(all-slink).
-$(foreach slink,$(_all-slink),\
-       $(if $(notdir $(slink)), \
-               $(if $(call compare,$(words $(slink-y-$(slink))),1), \
-                       $(error "only one source file is allowed in slink-y-$(slink)")) \
-               $(eval dst := $(dir $(slink))$(notdir $(slink-y-$(slink)))) \
-               $(eval out-$(slink) := $(dir $(slink))) \
-               $(eval pre-$(slink) := $(slink-y-$(slink))) \
-               $(eval all-slink += $(dst)) \
-       , \
-               $(foreach src,$(slink-y-$(slink)),\
-                       $(eval dst := $(slink)$(notdir $(src))) \
-                       $(eval out-$(slink) := $(slink)) \
-                       $(eval pre-$(dst) := $(src)) \
-                       $(eval all-slink += $(dst)) \
-               ) \
-       ) \
-)
-
-# add them to the list of targets and clean
-all-targets += $(all-slink)
-all-clean-file += $(all-slink)
-
-# convert format of executable from elf to ihex
-#   $1: source executable (elf)
-#   $2: destination file
-slink_cmd = $(LN) -nsf $(abspath $(1)) $(2)
-
-# print line used to convert executable format
-ifeq ($(V),1)
-slink_print_cmd = echo $(call protect_quote,$(call slink_cmd,$1,$2))
-else
-slink_print_cmd = echo "  SLINK $(2)"
-endif
diff --git a/mk/ecoli-subdir-rules.mk b/mk/ecoli-subdir-rules.mk
deleted file mode 100644 (file)
index c80166b..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-.PHONY: $(subdir-y)
-$(subdir-y): FORCE
-       $(Q)$(MAKE) -C $(@) $(mkflags-$(@)) $(MAKECMDGOALS)
diff --git a/mk/ecoli-subdir-vars.mk b/mk/ecoli-subdir-vars.mk
deleted file mode 100644 (file)
index 4190595..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# subdir-y is provided by the user
-#  it contains the list of directory to build
-
-# add them to the list of targets
-all-targets += $(subdir-y)
-all-clean-target += $(subdir-y)
diff --git a/mk/ecoli-tools.mk b/mk/ecoli-tools.mk
deleted file mode 100644 (file)
index 93be008..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-empty:=
-space:= $(empty) $(empty)
-indent:= $(space)$(space)
-
-# define a newline char, useful for debugging with $(info)
-define newline
-
-
-endef
-
-# $(prefix shell commands with $(Q) to silent them, except if V=1
-Q=@
-ifeq ("$(V)-$(origin V)", "1-command line")
-Q=
-endif
-
-# set variable $1 to $2 if the variable has an implicit value or
-# is not defined
-#  $1 variable name
-#  $2 new variable content
-set_default = $(if \
-       $(call not,$(or \
-               $(compare $(origin $(1)),default), \
-               $(compare $(origin $(1)),undefined) \
-       )),\
-       $(eval $(1) = $(2)) \
-)
-
-# display a list
-#  $1 title
-#  $2 list
-disp_list = $(info $(1)$(newline)\
-       $(addsuffix $(newline),$(addprefix $(space),$(2))))
-
-# add a dot in front of the file name
-#  $1 list of paths
-#  return: full paths with files prefixed by a dot
-dotfile = $(strip $(foreach f,$(1),\
-       $(join $(dir $f),.$(notdir $f))))
-
-# convert source/obj files into dot-dep filename
-#  $1 list of paths
-#  return: full paths with files prefixed by a dot and suffixed with .d
-depfile = $(strip $(call dotfile,$(addsuffix .d,$(1))))
-
-# convert source/obj files into dot-dep filename
-#  $1 list of paths
-#  return: full paths with files prefixed by a dot and suffixed with .d.tmp
-file2tmpdep = $(strip $(call dotfile,$(addsuffix .d.tmp,$(1))))
-
-# convert source/obj files into dot-cmd filename
-#  $1 list of paths
-#  return: full paths with files prefixed by a dot and suffixed with .cmd
-cmdfile = $(strip $(call dotfile,$(addsuffix .cmd,$(1))))
-
-# add a \ before each quote
-protect_quote = $(subst ','\'',$(1))
-#'# editor syntax highlight fix
-
-# return an non-empty string if $1 is empty, and vice versa
-#  $1 a string
-not = $(if $1,,true)
-
-# return 1 if parameter is a non-empty string, else 0
-boolean = $(if $1,1,0)
-
-# return an empty string if string are equal
-compare = $(strip $(subst $(1),,$(2)) $(subst $(2),,$(1)))
-
-# return a non-empty string if a file does not exist
-#  $1: file
-file_missing = $(call compare,$(wildcard $1),$1)
-
-# return a non-empty string if cmdline changed
-#  $1: file to be built
-#  $2: the command to build it
-cmdline_changed = $(call compare,$(strip $(cmd-$(1))),$(strip $(2)))
-
-# return an non-empty string if the .d file does not exist
-#  $1: the dep file (.d)
-depfile_missing = $(call compare,$(wildcard $(1)),$(1))
-
-# return a non-empty string if, according to dep-xyz variable, a file
-# needed to build $1 does not exist. In this case we need to rebuild
-# the file and the .d file.
-#  $1: file to be built
-dep-missing = $(call compare,$(wildcard $(dep-$(1))),$(dep-$(1)))
-
-# return an empty string if no prereq is newer than target
-#  $1: list of prerequisites newer than target ($?)
-dep-newer = $(strip $(filter-out FORCE,$(1)))
-
-# display why a file should be re-built
-#  $1: source files
-#  $2: dst file
-#  $3: build command
-#  $4: all prerequisites newer than target ($?)
-ifeq ($(D),1)
-display_deps = \
-       echo -n "$1 -> $2 " ; \
-       echo -n "file_missing=$(call boolean,$(call file_missing,$(2))) " ; \
-       echo -n "cmdline_changed=$(call boolean,$(call cmdline_changed,$(2),$(3))) " ; \
-       echo -n "depfile_missing=$(call boolean,$(call depfile_missing,$(call depfile,$(2)))) " ; \
-       echo -n "dep-missing=$(call boolean,$(call dep-missing,$(2))) " ; \
-       echo "dep-newer=$(call boolean,$(call dep-newer,$(4)))"
-else
-display_deps=
-endif
-
-# return an empty string if a file should be rebuilt
-#  $1: dst file
-#  $2: build command
-#  $3: all prerequisites newer than target ($?)
-check_deps = \
-       $(or $(call file_missing,$(1)),\
-       $(call cmdline_changed,$(1),$(2)),\
-       $(call depfile_missing,$(call depfile,$(1))),\
-       $(call dep-missing,$(1)),\
-       $(call dep-newer,$(3)))
-
-# create a depfile (.d) with no additional deps
-#  $1: object file (.o)
-create_empty_depfile = echo "dep-$(1) =" > $(call depfile,$(1))
-
-# save a command in a file
-#  $1: command to build the file
-#  $2: name of the file
-save_cmd = echo "cmd-$(2) = $(call protect_quote,$(1))" > $(call cmdfile,$(2))
-
-# remove the FORCE target from the list of all prerequisites $+
-#  no arguments, use $+
-prereq = $(filter-out FORCE,$(+))
diff --git a/mk/ecoli-vars.mk b/mk/ecoli-vars.mk
deleted file mode 100644 (file)
index 7c69466..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2015, Olivier MATZ <zer0@droids-corp.org>
-
-# core tools
-CP ?= cp
-LN ?= ln
-GAWK ?= gawk
-GREP ?= grep
-# compiler and binutils, set_default overrides mk implicit value
-# but not command line or standard variables
-$(call set_default,CC,$(CROSS)gcc)
-$(call set_default,CPP,$(CROSS)cpp)
-$(call set_default,AR,$(CROSS)ar)
-$(call set_default,LD,$(CROSS)ld)
-$(call set_default,OBJCOPY,$(CROSS)objcopy)
-$(call set_default,OBJDUMP,$(CROSS)objdump)
-$(call set_default,STRIP,$(CROSS)strip)
-HOSTCC ?= cc
-
-CFLAGS += $(EXTRA_CFLAGS)
-CPPFLAGS += $(EXTRA_CPPFLAGS)
-LDFLAGS += $(EXTRA_LDFLAGS)
-LDLIBS += $(EXTRA_LDLIBS)
diff --git a/src/ecoli_assert.c b/src/ecoli_assert.c
new file mode 100644 (file)
index 0000000..a36d6f6
--- /dev/null
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <ecoli_assert.h>
+
+void __ec_assert_print(bool expr, const char *expr_str, const char *format, ...)
+{
+       va_list ap;
+
+       if (expr)
+               return;
+
+       /* LCOV_EXCL_START */
+       va_start(ap, format);
+       fprintf(stderr, "assertion failed: '%s' is false\n", expr_str);
+       vfprintf(stderr, format, ap);
+       va_end(ap);
+       abort();
+       /* LCOV_EXCL_END */
+}
diff --git a/src/ecoli_complete.c b/src/ecoli_complete.c
new file mode 100644 (file)
index 0000000..a9becdf
--- /dev/null
@@ -0,0 +1,765 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_string.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_node_sh_lex.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_or.h>
+#include <ecoli_complete.h>
+
+EC_LOG_TYPE_REGISTER(comp);
+
+struct ec_comp_item {
+       TAILQ_ENTRY(ec_comp_item) next;
+       enum ec_comp_type type;
+       struct ec_comp_group *grp;
+       char *start;      /* the initial token */
+       char *full;       /* the full token after completion */
+       char *completion; /* chars that are added, NULL if not applicable */
+       char *display;    /* what should be displayed by help/completers */
+       struct ec_keyval *attrs;
+};
+
+struct ec_comp *ec_comp(struct ec_parse *state)
+{
+       struct ec_comp *comp = NULL;
+
+       comp = ec_calloc(1, sizeof(*comp));
+       if (comp == NULL)
+               goto fail;
+
+       comp->attrs = ec_keyval();
+       if (comp->attrs == NULL)
+               goto fail;
+
+       TAILQ_INIT(&comp->groups);
+
+       comp->cur_state = state;
+
+       return comp;
+
+ fail:
+       if (comp != NULL)
+               ec_keyval_free(comp->attrs);
+       ec_free(comp);
+
+       return NULL;
+}
+
+struct ec_parse *ec_comp_get_state(struct ec_comp *comp)
+{
+       return comp->cur_state;
+}
+
+int
+ec_node_complete_child(const struct ec_node *node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_parse *child_state, *cur_state;
+       struct ec_comp_group *cur_group;
+       int ret;
+
+       if (ec_node_type(node)->complete == NULL) {
+               errno = ENOTSUP;
+               return -1;
+       }
+
+       /* save previous parse state, prepare child state */
+       cur_state = comp->cur_state;
+       child_state = ec_parse(node);
+       if (child_state == NULL)
+               return -1;
+
+       if (cur_state != NULL)
+               ec_parse_link_child(cur_state, child_state);
+       comp->cur_state = child_state;
+       cur_group = comp->cur_group;
+       comp->cur_group = NULL;
+
+       /* fill the comp struct with items */
+       ret = ec_node_type(node)->complete(node, comp, strvec);
+
+       /* restore parent parse state */
+       if (cur_state != NULL) {
+               ec_parse_unlink_child(cur_state, child_state);
+               assert(!ec_parse_has_child(child_state));
+       }
+       ec_parse_free(child_state);
+       comp->cur_state = cur_state;
+       comp->cur_group = cur_group;
+
+       if (ret < 0)
+               return -1;
+
+       return 0;
+}
+
+struct ec_comp *ec_node_complete_strvec(const struct ec_node *node,
+       const struct ec_strvec *strvec)
+{
+       struct ec_comp *comp = NULL;
+       int ret;
+
+       comp = ec_comp(NULL);
+       if (comp == NULL)
+               goto fail;
+
+       ret = ec_node_complete_child(node, comp, strvec);
+       if (ret < 0)
+               goto fail;
+
+       return comp;
+
+fail:
+       ec_comp_free(comp);
+       return NULL;
+}
+
+struct ec_comp *ec_node_complete(const struct ec_node *node,
+       const char *str)
+{
+       struct ec_strvec *strvec = NULL;
+       struct ec_comp *comp;
+
+       errno = ENOMEM;
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       if (ec_strvec_add(strvec, str) < 0)
+               goto fail;
+
+       comp = ec_node_complete_strvec(node, strvec);
+       if (comp == NULL)
+               goto fail;
+
+       ec_strvec_free(strvec);
+       return comp;
+
+ fail:
+       ec_strvec_free(strvec);
+       return NULL;
+}
+
+static struct ec_comp_group *
+ec_comp_group(const struct ec_node *node, struct ec_parse *parse)
+{
+       struct ec_comp_group *grp = NULL;
+
+       grp = ec_calloc(1, sizeof(*grp));
+       if (grp == NULL)
+               return NULL;
+
+       grp->attrs = ec_keyval();
+       if (grp->attrs == NULL)
+               goto fail;
+
+       grp->state = ec_parse_dup(parse);
+       if (grp->state == NULL)
+               goto fail;
+
+       grp->node = node;
+       TAILQ_INIT(&grp->items);
+
+       return grp;
+
+fail:
+       if (grp != NULL) {
+               ec_parse_free(grp->state);
+               ec_keyval_free(grp->attrs);
+       }
+       ec_free(grp);
+       return NULL;
+}
+
+static struct ec_comp_item *
+ec_comp_item(enum ec_comp_type type,
+       const char *start, const char *full)
+{
+       struct ec_comp_item *item = NULL;
+       struct ec_keyval *attrs = NULL;
+       char *comp_cp = NULL, *start_cp = NULL;
+       char *full_cp = NULL, *display_cp = NULL;
+
+       if (type == EC_COMP_UNKNOWN && full != NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+       if (type != EC_COMP_UNKNOWN && full == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       item = ec_calloc(1, sizeof(*item));
+       if (item == NULL)
+               goto fail;
+
+       attrs = ec_keyval();
+       if (attrs == NULL)
+               goto fail;
+
+       if (start != NULL) {
+               start_cp = ec_strdup(start);
+               if (start_cp == NULL)
+                       goto fail;
+
+               if (ec_str_startswith(full, start)) {
+                       comp_cp = ec_strdup(&full[strlen(start)]);
+                       if (comp_cp == NULL)
+                               goto fail;
+               }
+       }
+       if (full != NULL) {
+               full_cp = ec_strdup(full);
+               if (full_cp == NULL)
+                       goto fail;
+               display_cp = ec_strdup(full);
+               if (display_cp == NULL)
+                       goto fail;
+       }
+
+       item->type = type;
+       item->start = start_cp;
+       item->full = full_cp;
+       item->completion = comp_cp;
+       item->display = display_cp;
+       item->attrs = attrs;
+
+       return item;
+
+fail:
+       ec_keyval_free(attrs);
+       ec_free(comp_cp);
+       ec_free(start_cp);
+       ec_free(full_cp);
+       ec_free(display_cp);
+       ec_free(item);
+
+       return NULL;
+}
+
+int ec_comp_item_set_display(struct ec_comp_item *item,
+                               const char *display)
+{
+       char *display_copy = NULL;
+
+       if (item == NULL || display == NULL ||
+                       item->type == EC_COMP_UNKNOWN) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       display_copy = ec_strdup(display);
+       if (display_copy == NULL)
+               goto fail;
+
+       ec_free(item->display);
+       item->display = display_copy;
+
+       return 0;
+
+fail:
+       ec_free(display_copy);
+       return -1;
+}
+
+int
+ec_comp_item_set_completion(struct ec_comp_item *item,
+                               const char *completion)
+{
+       char *completion_copy = NULL;
+
+       if (item == NULL || completion == NULL ||
+                       item->type == EC_COMP_UNKNOWN) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       completion_copy = ec_strdup(completion);
+       if (completion_copy == NULL)
+               goto fail;
+
+       ec_free(item->completion);
+       item->completion = completion_copy;
+
+       return 0;
+
+fail:
+       ec_free(completion_copy);
+       return -1;
+}
+
+int
+ec_comp_item_set_str(struct ec_comp_item *item,
+                       const char *str)
+{
+       char *str_copy = NULL;
+
+       if (item == NULL || str == NULL ||
+                       item->type == EC_COMP_UNKNOWN) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       str_copy = ec_strdup(str);
+       if (str_copy == NULL)
+               goto fail;
+
+       ec_free(item->full);
+       item->full = str_copy;
+
+       return 0;
+
+fail:
+       ec_free(str_copy);
+       return -1;
+}
+
+static int
+ec_comp_item_add(struct ec_comp *comp, const struct ec_node *node,
+               struct ec_comp_item *item)
+{
+       if (comp == NULL || item == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       switch (item->type) {
+       case EC_COMP_UNKNOWN:
+               comp->count_unknown++;
+               break;
+       case EC_COMP_FULL:
+               comp->count_full++;
+               break;
+       case EC_COMP_PARTIAL:
+               comp->count_partial++;
+               break;
+       default:
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (comp->cur_group == NULL) {
+               struct ec_comp_group *grp;
+
+               grp = ec_comp_group(node, comp->cur_state);
+               if (grp == NULL)
+                       return -1;
+               TAILQ_INSERT_TAIL(&comp->groups, grp, next);
+               comp->cur_group = grp;
+       }
+
+       comp->count++;
+       TAILQ_INSERT_TAIL(&comp->cur_group->items, item, next);
+       item->grp = comp->cur_group;
+
+       return 0;
+}
+
+const char *
+ec_comp_item_get_str(const struct ec_comp_item *item)
+{
+       return item->full;
+}
+
+const char *
+ec_comp_item_get_display(const struct ec_comp_item *item)
+{
+       return item->display;
+}
+
+const char *
+ec_comp_item_get_completion(const struct ec_comp_item *item)
+{
+       return item->completion;
+}
+
+enum ec_comp_type
+ec_comp_item_get_type(const struct ec_comp_item *item)
+{
+       return item->type;
+}
+
+const struct ec_comp_group *
+ec_comp_item_get_grp(const struct ec_comp_item *item)
+{
+       return item->grp;
+}
+
+const struct ec_node *
+ec_comp_item_get_node(const struct ec_comp_item *item)
+{
+       return ec_comp_item_get_grp(item)->node;
+}
+
+static void
+ec_comp_item_free(struct ec_comp_item *item)
+{
+       if (item == NULL)
+               return;
+
+       ec_free(item->full);
+       ec_free(item->start);
+       ec_free(item->completion);
+       ec_free(item->display);
+       ec_keyval_free(item->attrs);
+       ec_free(item);
+}
+
+int ec_comp_add_item(struct ec_comp *comp,
+                       const struct ec_node *node,
+                       struct ec_comp_item **p_item,
+                       enum ec_comp_type type,
+                       const char *start, const char *full)
+{
+       struct ec_comp_item *item = NULL;
+       int ret;
+
+       item = ec_comp_item(type, start, full);
+       if (item == NULL)
+               return -1;
+
+       ret = ec_comp_item_add(comp, node, item);
+       if (ret < 0)
+               goto fail;
+
+       if (p_item != NULL)
+               *p_item = item;
+
+       return 0;
+
+fail:
+       ec_comp_item_free(item);
+
+       return -1;
+}
+
+/* return a completion item of type "unknown" */
+int
+ec_node_complete_unknown(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       int ret;
+
+       if (ec_strvec_len(strvec) != 1)
+               return 0;
+
+       ret = ec_comp_add_item(comp, gen_node, NULL,
+                               EC_COMP_UNKNOWN, NULL, NULL);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static void ec_comp_group_free(struct ec_comp_group *grp)
+{
+       struct ec_comp_item *item;
+
+       if (grp == NULL)
+               return;
+
+       while (!TAILQ_EMPTY(&grp->items)) {
+               item = TAILQ_FIRST(&grp->items);
+               TAILQ_REMOVE(&grp->items, item, next);
+               ec_comp_item_free(item);
+       }
+       ec_parse_free(ec_parse_get_root(grp->state));
+       ec_keyval_free(grp->attrs);
+       ec_free(grp);
+}
+
+void ec_comp_free(struct ec_comp *comp)
+{
+       struct ec_comp_group *grp;
+
+       if (comp == NULL)
+               return;
+
+       while (!TAILQ_EMPTY(&comp->groups)) {
+               grp = TAILQ_FIRST(&comp->groups);
+               TAILQ_REMOVE(&comp->groups, grp, next);
+               ec_comp_group_free(grp);
+       }
+       ec_keyval_free(comp->attrs);
+       ec_free(comp);
+}
+
+void ec_comp_dump(FILE *out, const struct ec_comp *comp)
+{
+       struct ec_comp_group *grp;
+       struct ec_comp_item *item;
+
+       if (comp == NULL || comp->count == 0) {
+               fprintf(out, "no completion\n");
+               return;
+       }
+
+       fprintf(out, "completion: count=%u full=%u partial=%u unknown=%u\n",
+               comp->count, comp->count_full,
+               comp->count_partial,  comp->count_unknown);
+
+       TAILQ_FOREACH(grp, &comp->groups, next) {
+               fprintf(out, "node=%p, node_type=%s\n",
+                       grp->node, ec_node_type(grp->node)->name);
+               TAILQ_FOREACH(item, &grp->items, next) {
+                       const char *typestr;
+
+                       switch (item->type) {
+                       case EC_COMP_UNKNOWN: typestr = "unknown"; break;
+                       case EC_COMP_FULL: typestr = "full"; break;
+                       case EC_COMP_PARTIAL: typestr = "partial"; break;
+                       default: typestr = "unknown"; break;
+                       }
+
+                       fprintf(out, "  type=%s str=<%s> comp=<%s> disp=<%s>\n",
+                               typestr, item->full, item->completion,
+                               item->display);
+               }
+       }
+}
+
+int ec_comp_merge(struct ec_comp *to,
+               struct ec_comp *from)
+{
+       struct ec_comp_group *grp;
+
+       while (!TAILQ_EMPTY(&from->groups)) {
+               grp = TAILQ_FIRST(&from->groups);
+               TAILQ_REMOVE(&from->groups, grp, next);
+               TAILQ_INSERT_TAIL(&to->groups, grp, next);
+       }
+       to->count += from->count;
+       to->count_full += from->count_full;
+       to->count_partial += from->count_partial;
+       to->count_unknown += from->count_unknown;
+
+       ec_comp_free(from);
+       return 0;
+}
+
+unsigned int ec_comp_count(
+       const struct ec_comp *comp,
+       enum ec_comp_type type)
+{
+       unsigned int count = 0;
+
+       if (comp == NULL)
+               return count;
+
+       if (type & EC_COMP_FULL)
+               count += comp->count_full;
+       if (type & EC_COMP_PARTIAL)
+               count += comp->count_partial;
+       if (type & EC_COMP_UNKNOWN)
+               count += comp->count_unknown;
+
+       return count;
+}
+
+struct ec_comp_iter *
+ec_comp_iter(const struct ec_comp *comp,
+       enum ec_comp_type type)
+{
+       struct ec_comp_iter *iter;
+
+       iter = ec_calloc(1, sizeof(*iter));
+       if (iter == NULL)
+               return NULL;
+
+       iter->comp = comp;
+       iter->type = type;
+       iter->cur_node = NULL;
+       iter->cur_match = NULL;
+
+       return iter;
+}
+
+struct ec_comp_item *ec_comp_iter_next(
+       struct ec_comp_iter *iter)
+{
+       const struct ec_comp *comp;
+       struct ec_comp_group *cur_node;
+       struct ec_comp_item *cur_match;
+
+       if (iter == NULL)
+               return NULL;
+       comp = iter->comp;
+       if (comp == NULL)
+               return NULL;
+
+       cur_node = iter->cur_node;
+       cur_match = iter->cur_match;
+
+       /* first call */
+       if (cur_node == NULL) {
+               TAILQ_FOREACH(cur_node, &comp->groups, next) {
+                       TAILQ_FOREACH(cur_match, &cur_node->items, next) {
+                               if (cur_match != NULL &&
+                                               cur_match->type & iter->type)
+                                       goto found;
+                       }
+               }
+               return NULL;
+       } else {
+               cur_match = TAILQ_NEXT(cur_match, next);
+               if (cur_match != NULL &&
+                               cur_match->type & iter->type)
+                       goto found;
+               cur_node = TAILQ_NEXT(cur_node, next);
+               while (cur_node != NULL) {
+                       cur_match = TAILQ_FIRST(&cur_node->items);
+                       if (cur_match != NULL &&
+                                       cur_match->type & iter->type)
+                               goto found;
+                       cur_node = TAILQ_NEXT(cur_node, next);
+               }
+               return NULL;
+       }
+
+found:
+       iter->cur_node = cur_node;
+       iter->cur_match = cur_match;
+
+       return iter->cur_match;
+}
+
+void ec_comp_iter_free(struct ec_comp_iter *iter)
+{
+       ec_free(iter);
+}
+
+/* LCOV_EXCL_START */
+static int ec_comp_testcase(void)
+{
+       struct ec_node *node = NULL;
+       struct ec_comp *c = NULL;
+       struct ec_comp_iter *iter = NULL;
+       struct ec_comp_item *item;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+       int testres = 0;
+
+       node = ec_node_sh_lex(EC_NO_ID,
+                       EC_NODE_OR(EC_NO_ID,
+                               ec_node_str("id_x", "xx"),
+                               ec_node_str("id_y", "yy")));
+       if (node == NULL)
+               goto fail;
+
+       c = ec_node_complete(node, "xcdscds");
+       testres |= EC_TEST_CHECK(
+               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 0,
+               "complete count should is not 0\n");
+       ec_comp_free(c);
+
+       c = ec_node_complete(node, "x");
+       testres |= EC_TEST_CHECK(
+               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 1,
+               "complete count should is not 1\n");
+       ec_comp_free(c);
+
+       c = ec_node_complete(node, "");
+       testres |= EC_TEST_CHECK(
+               c != NULL && ec_comp_count(c, EC_COMP_ALL) == 2,
+               "complete count should is not 2\n");
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_comp_dump(f, NULL);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "no completion"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_comp_dump(f, c);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "comp=<xx>"), "bad dump\n");
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "comp=<yy>"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       iter = ec_comp_iter(c, EC_COMP_ALL);
+       item = ec_comp_iter_next(iter);
+       if (item == NULL)
+               goto fail;
+
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_comp_item_get_display(item), "xx"),
+               "bad item display\n");
+       testres |= EC_TEST_CHECK(
+               ec_comp_item_get_type(item) == EC_COMP_FULL,
+               "bad item type\n");
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_x"),
+               "bad item node\n");
+
+       item = ec_comp_iter_next(iter);
+       if (item == NULL)
+               goto fail;
+
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_comp_item_get_display(item), "yy"),
+               "bad item display\n");
+       testres |= EC_TEST_CHECK(
+               ec_comp_item_get_type(item) == EC_COMP_FULL,
+               "bad item type\n");
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_y"),
+               "bad item node\n");
+
+       item = ec_comp_iter_next(iter);
+       testres |= EC_TEST_CHECK(item == NULL, "should be the last item\n");
+
+       ec_comp_iter_free(iter);
+       ec_comp_free(c);
+       ec_node_free(node);
+
+       return testres;
+
+fail:
+       ec_comp_iter_free(iter);
+       ec_comp_free(c);
+       ec_node_free(node);
+       if (f != NULL)
+               fclose(f);
+       free(buf);
+
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_comp_test = {
+       .name = "comp",
+       .test = ec_comp_testcase,
+};
+
+EC_TEST_REGISTER(ec_comp_test);
diff --git a/src/ecoli_config.c b/src/ecoli_config.c
new file mode 100644 (file)
index 0000000..66d9232
--- /dev/null
@@ -0,0 +1,1172 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/queue.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <ecoli_string.h>
+#include <ecoli_malloc.h>
+#include <ecoli_keyval.h>
+#include <ecoli_node.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_config.h>
+
+EC_LOG_TYPE_REGISTER(config);
+
+const char *ec_config_reserved_keys[] = {
+       "id",
+       "attrs",
+       "help",
+       "type",
+};
+
+static int
+__ec_config_dump(FILE *out, const char *key, const struct ec_config *config,
+       size_t indent);
+static int
+ec_config_dict_validate(const struct ec_keyval *dict,
+                       const struct ec_config_schema *schema);
+
+bool
+ec_config_key_is_reserved(const char *name)
+{
+       size_t i;
+
+       for (i = 0; i < EC_COUNT_OF(ec_config_reserved_keys); i++) {
+               if (!strcmp(name, ec_config_reserved_keys[i]))
+                       return true;
+       }
+       return false;
+}
+
+/* return ec_value type as a string */
+static const char *
+ec_config_type_str(enum ec_config_type type)
+{
+       switch (type) {
+       case EC_CONFIG_TYPE_BOOL: return "bool";
+       case EC_CONFIG_TYPE_INT64: return "int64";
+       case EC_CONFIG_TYPE_UINT64: return "uint64";
+       case EC_CONFIG_TYPE_STRING: return "string";
+       case EC_CONFIG_TYPE_NODE: return "node";
+       case EC_CONFIG_TYPE_LIST: return "list";
+       case EC_CONFIG_TYPE_DICT: return "dict";
+       default: return "unknown";
+       }
+}
+
+static size_t
+ec_config_schema_len(const struct ec_config_schema *schema)
+{
+       size_t i;
+
+       if (schema == NULL)
+               return 0;
+       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++)
+               ;
+       return i;
+}
+
+static int
+__ec_config_schema_validate(const struct ec_config_schema *schema,
+                       enum ec_config_type type)
+{
+       size_t i, j;
+       int ret;
+
+       if (type == EC_CONFIG_TYPE_LIST) {
+               if (schema[0].key != NULL) {
+                       errno = EINVAL;
+                       EC_LOG(EC_LOG_ERR, "list schema key must be NULL\n");
+                       return -1;
+               }
+       } else if (type == EC_CONFIG_TYPE_DICT) {
+               for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
+                       if (schema[i].key == NULL) {
+                               errno = EINVAL;
+                               EC_LOG(EC_LOG_ERR,
+                                       "dict schema key should not be NULL\n");
+                               return -1;
+                       }
+               }
+       } else {
+               errno = EINVAL;
+               EC_LOG(EC_LOG_ERR, "invalid schema type\n");
+               return -1;
+       }
+
+       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
+               if (schema[i].key != NULL &&
+                               ec_config_key_is_reserved(schema[i].key)) {
+                       errno = EINVAL;
+                       EC_LOG(EC_LOG_ERR,
+                               "key name <%s> is reserved\n", schema[i].key);
+                       return -1;
+               }
+               /* check for duplicate name if more than one element */
+               for (j = i + 1; schema[j].type != EC_CONFIG_TYPE_NONE; j++) {
+                       if (!strcmp(schema[i].key, schema[j].key)) {
+                               errno = EEXIST;
+                               EC_LOG(EC_LOG_ERR,
+                                       "duplicate key <%s> in schema\n",
+                                       schema[i].key);
+                               return -1;
+                       }
+               }
+
+               switch (schema[i].type) {
+               case EC_CONFIG_TYPE_BOOL:
+               case EC_CONFIG_TYPE_INT64:
+               case EC_CONFIG_TYPE_UINT64:
+               case EC_CONFIG_TYPE_STRING:
+               case EC_CONFIG_TYPE_NODE:
+                       if (schema[i].subschema != NULL || ec_config_schema_len(
+                                       schema[i].subschema) != 0) {
+                               errno = EINVAL;
+                               EC_LOG(EC_LOG_ERR,
+                                       "key <%s> should not have subtype/subschema\n",
+                                       schema[i].key);
+                               return -1;
+                       }
+                       break;
+               case EC_CONFIG_TYPE_LIST:
+                       if (schema[i].subschema == NULL || ec_config_schema_len(
+                                       schema[i].subschema) != 1) {
+                               errno = EINVAL;
+                               EC_LOG(EC_LOG_ERR,
+                                       "key <%s> must have subschema of length 1\n",
+                                       schema[i].key);
+                               return -1;
+                       }
+                       break;
+               case EC_CONFIG_TYPE_DICT:
+                       if (schema[i].subschema == NULL || ec_config_schema_len(
+                                       schema[i].subschema) == 0) {
+                               errno = EINVAL;
+                               EC_LOG(EC_LOG_ERR,
+                                       "key <%s> must have subschema\n",
+                                       schema[i].key);
+                               return -1;
+                       }
+                       break;
+               default:
+                       EC_LOG(EC_LOG_ERR, "invalid type for key <%s>\n",
+                               schema[i].key);
+                       errno = EINVAL;
+                       return -1;
+               }
+
+               if (schema[i].subschema == NULL)
+                       continue;
+
+               ret = __ec_config_schema_validate(schema[i].subschema,
+                                               schema[i].type);
+               if (ret < 0) {
+                       EC_LOG(EC_LOG_ERR, "cannot parse subschema %s%s\n",
+                               schema[i].key ? "key=" : "",
+                               schema[i].key ? : "");
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+int
+ec_config_schema_validate(const struct ec_config_schema *schema)
+{
+       return __ec_config_schema_validate(schema, EC_CONFIG_TYPE_DICT);
+}
+
+static void
+__ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema,
+                       size_t indent)
+{
+       size_t i;
+
+       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
+               fprintf(out, "%*s" "%s%s%stype=%s desc='%s'\n",
+                       (int)indent * 4, "",
+                       schema[i].key ? "key=": "",
+                       schema[i].key ? : "",
+                       schema[i].key ? " ": "",
+                       ec_config_type_str(schema[i].type),
+                       schema[i].desc);
+               if (schema[i].subschema == NULL)
+                       continue;
+               __ec_config_schema_dump(out, schema[i].subschema,
+                                       indent + 1);
+       }
+}
+
+void
+ec_config_schema_dump(FILE *out, const struct ec_config_schema *schema)
+{
+       fprintf(out, "------------------- schema dump:\n");
+
+       if (schema == NULL) {
+               fprintf(out, "no schema\n");
+               return;
+       }
+
+       __ec_config_schema_dump(out, schema, 0);
+}
+
+enum ec_config_type ec_config_get_type(const struct ec_config *config)
+{
+       return config->type;
+}
+
+struct ec_config *
+ec_config_bool(bool boolean)
+{
+       struct ec_config *value = NULL;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               return NULL;
+
+       value->type = EC_CONFIG_TYPE_BOOL;
+       value->boolean = boolean;
+
+       return value;
+}
+
+struct ec_config *
+ec_config_i64(int64_t i64)
+{
+       struct ec_config *value = NULL;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               return NULL;
+
+       value->type = EC_CONFIG_TYPE_INT64;
+       value->i64 = i64;
+
+       return value;
+}
+
+struct ec_config *
+ec_config_u64(uint64_t u64)
+{
+       struct ec_config *value = NULL;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               return NULL;
+
+       value->type = EC_CONFIG_TYPE_UINT64;
+       value->u64 = u64;
+
+       return value;
+}
+
+/* duplicate string */
+struct ec_config *
+ec_config_string(const char *string)
+{
+       struct ec_config *value = NULL;
+       char *s = NULL;
+
+       if (string == NULL)
+               goto fail;
+
+       s = ec_strdup(string);
+       if (s == NULL)
+               goto fail;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               goto fail;
+
+       value->type = EC_CONFIG_TYPE_STRING;
+       value->string = s;
+
+       return value;
+
+fail:
+       ec_free(value);
+       ec_free(s);
+       return NULL;
+}
+
+/* "consume" the node */
+struct ec_config *
+ec_config_node(struct ec_node *node)
+{
+       struct ec_config *value = NULL;
+
+       if (node == NULL)
+               goto fail;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               goto fail;
+
+       value->type = EC_CONFIG_TYPE_NODE;
+       value->node = node;
+
+       return value;
+
+fail:
+       ec_node_free(node);
+       ec_free(value);
+       return NULL;
+}
+
+struct ec_config *
+ec_config_dict(void)
+{
+       struct ec_config *value = NULL;
+       struct ec_keyval *dict = NULL;
+
+       dict = ec_keyval();
+       if (dict == NULL)
+               goto fail;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               goto fail;
+
+       value->type = EC_CONFIG_TYPE_DICT;
+       value->dict = dict;
+
+       return value;
+
+fail:
+       ec_keyval_free(dict);
+       ec_free(value);
+       return NULL;
+}
+
+struct ec_config *
+ec_config_list(void)
+{
+       struct ec_config *value = NULL;
+
+       value = ec_calloc(1, sizeof(*value));
+       if (value == NULL)
+               return NULL;
+
+       value->type = EC_CONFIG_TYPE_LIST;
+       TAILQ_INIT(&value->list);
+
+       return value;
+}
+
+ssize_t ec_config_count(const struct ec_config *config)
+{
+       const struct ec_config *child;
+       ssize_t n;
+
+       switch (config->type) {
+       case EC_CONFIG_TYPE_LIST:
+               n = 0;
+               TAILQ_FOREACH(child, &config->list, next)
+                       n++;
+               return n;
+       case EC_CONFIG_TYPE_DICT:
+               // XXX todo
+       default:
+               errno = EINVAL;
+               return -1;
+       }
+}
+
+const struct ec_config_schema *
+ec_config_schema_lookup(const struct ec_config_schema *schema,
+                       const char *key)
+{
+       size_t i;
+
+       for (i = 0; schema[i].type != EC_CONFIG_TYPE_NONE; i++) {
+               if (!strcmp(key, schema[i].key))
+                       return &schema[i];
+       }
+
+       errno = ENOENT;
+       return NULL;
+}
+
+enum ec_config_type
+ec_config_schema_type(const struct ec_config_schema *schema_elt)
+{
+       return schema_elt->type;
+}
+
+const struct ec_config_schema *
+ec_config_schema_sub(const struct ec_config_schema *schema_elt)
+{
+       return schema_elt->subschema;
+}
+
+void
+ec_config_free(struct ec_config *value)
+{
+       if (value == NULL)
+               return;
+
+       switch (value->type) {
+       case EC_CONFIG_TYPE_STRING:
+               ec_free(value->string);
+               break;
+       case EC_CONFIG_TYPE_NODE:
+               ec_node_free(value->node);
+               break;
+       case EC_CONFIG_TYPE_LIST:
+               while (!TAILQ_EMPTY(&value->list)) {
+                       struct ec_config *v;
+                       v = TAILQ_FIRST(&value->list);
+                       TAILQ_REMOVE(&value->list, v, next);
+                       ec_config_free(v);
+               }
+               break;
+       case EC_CONFIG_TYPE_DICT:
+               ec_keyval_free(value->dict);
+               break;
+       default:
+               break;
+       }
+
+       ec_free(value);
+}
+
+static int
+ec_config_list_cmp(const struct ec_config_list *list1,
+               const struct ec_config_list *list2)
+{
+       const struct ec_config *v1, *v2;
+
+       for (v1 = TAILQ_FIRST(list1), v2 = TAILQ_FIRST(list2);
+            v1 != NULL && v2 != NULL;
+            v1 = TAILQ_NEXT(v1, next), v2 = TAILQ_NEXT(v2, next)) {
+               if (ec_config_cmp(v1, v2))
+                       return -1;
+       }
+       if (v1 != NULL || v2 != NULL)
+               return -1;
+
+       return 0;
+}
+
+/* XXX -> ec_keyval_cmp() */
+static int
+ec_config_dict_cmp(const struct ec_keyval *d1,
+               const struct ec_keyval *d2)
+{
+       const struct ec_config *v1, *v2;
+       struct ec_keyval_iter *iter = NULL;
+       const char *key;
+
+       if (ec_keyval_len(d1) != ec_keyval_len(d2))
+               return -1;
+
+       for (iter = ec_keyval_iter(d1);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               key = ec_keyval_iter_get_key(iter);
+               v1 = ec_keyval_iter_get_val(iter);
+               v2 = ec_keyval_get(d2, key);
+
+               if (ec_config_cmp(v1, v2))
+                       goto fail;
+       }
+
+       ec_keyval_iter_free(iter);
+       return 0;
+
+fail:
+       ec_keyval_iter_free(iter);
+       return -1;
+}
+
+int
+ec_config_cmp(const struct ec_config *value1,
+               const struct ec_config *value2)
+{
+       if (value1 == NULL || value2 == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (value1->type != value2->type)
+               return -1;
+
+       switch (value1->type) {
+       case EC_CONFIG_TYPE_BOOL:
+               if (value1->boolean == value2->boolean)
+                       return 0;
+       case EC_CONFIG_TYPE_INT64:
+               if (value1->i64 == value2->i64)
+                       return 0;
+       case EC_CONFIG_TYPE_UINT64:
+               if (value1->u64 == value2->u64)
+                       return 0;
+       case EC_CONFIG_TYPE_STRING:
+               if (!strcmp(value1->string, value2->string))
+                       return 0;
+       case EC_CONFIG_TYPE_NODE:
+               if (value1->node == value2->node)
+                       return 0;
+       case EC_CONFIG_TYPE_LIST:
+               return ec_config_list_cmp(&value1->list, &value2->list);
+       case EC_CONFIG_TYPE_DICT:
+               return ec_config_dict_cmp(value1->dict, value2->dict);
+       default:
+               break;
+       }
+
+       return -1;
+}
+
+static int
+ec_config_list_validate(const struct ec_config_list *list,
+                       const struct ec_config_schema *sch)
+{
+       const struct ec_config *value;
+
+       TAILQ_FOREACH(value, list, next) {
+               if (value->type != sch->type) {
+                       errno = EBADMSG;
+                       return -1;
+               }
+
+               if (value->type == EC_CONFIG_TYPE_LIST) {
+                       if (ec_config_list_validate(&value->list,
+                                                       sch->subschema) < 0)
+                               return -1;
+               } else if (value->type == EC_CONFIG_TYPE_DICT) {
+                       if (ec_config_dict_validate(value->dict,
+                                       sch->subschema) < 0)
+                               return -1;
+               }
+       }
+
+       return 0;
+}
+
+static int
+ec_config_dict_validate(const struct ec_keyval *dict,
+                       const struct ec_config_schema *schema)
+{
+       const struct ec_config *value;
+       struct ec_keyval_iter *iter = NULL;
+       const struct ec_config_schema *sch;
+       const char *key;
+
+       for (iter = ec_keyval_iter(dict);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+
+               key = ec_keyval_iter_get_key(iter);
+               value = ec_keyval_iter_get_val(iter);
+               sch = ec_config_schema_lookup(schema, key);
+               if (sch == NULL || sch->type != value->type) {
+                       errno = EBADMSG;
+                       goto fail;
+               }
+               if (value->type == EC_CONFIG_TYPE_LIST) {
+                       if (ec_config_list_validate(&value->list,
+                                                       sch->subschema) < 0)
+                               goto fail;
+               } else if (value->type == EC_CONFIG_TYPE_DICT) {
+                       if (ec_config_dict_validate(value->dict,
+                                       sch->subschema) < 0)
+                               goto fail;
+               }
+       }
+
+       ec_keyval_iter_free(iter);
+       return 0;
+
+fail:
+       ec_keyval_iter_free(iter);
+       return -1;
+}
+
+int
+ec_config_validate(const struct ec_config *dict,
+               const struct ec_config_schema *schema)
+{
+       if (dict->type != EC_CONFIG_TYPE_DICT || schema == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (ec_config_dict_validate(dict->dict, schema) < 0)
+               goto fail;
+
+       return 0
+;
+fail:
+       return -1;
+}
+
+struct ec_config *
+ec_config_dict_get(const struct ec_config *config, const char *key)
+{
+       if (config == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if (config->type != EC_CONFIG_TYPE_DICT) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       return ec_keyval_get(config->dict, key);
+}
+
+struct ec_config *
+ec_config_list_first(struct ec_config *list)
+{
+       if (list  == NULL || list->type != EC_CONFIG_TYPE_LIST) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       return TAILQ_FIRST(&list->list);
+}
+
+struct ec_config *
+ec_config_list_next(struct ec_config *list, struct ec_config *config)
+{
+       (void)list;
+       return TAILQ_NEXT(config, next);
+}
+
+/* value is consumed */
+int ec_config_dict_set(struct ec_config *config, const char *key,
+               struct ec_config *value)
+{
+       void (*free_cb)(struct ec_config *) = ec_config_free;
+
+       if (config == NULL || key == NULL || value == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+       if (config->type != EC_CONFIG_TYPE_DICT) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       return ec_keyval_set(config->dict, key, value,
+                       (void (*)(void *))free_cb);
+
+fail:
+       ec_config_free(value);
+       return -1;
+}
+
+int ec_config_dict_del(struct ec_config *config, const char *key)
+{
+       if (config == NULL || key == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+       if (config->type != EC_CONFIG_TYPE_DICT) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       return ec_keyval_del(config->dict, key);
+}
+
+/* value is consumed */
+int
+ec_config_list_add(struct ec_config *list,
+               struct ec_config *value)
+{
+       if (list == NULL || list->type != EC_CONFIG_TYPE_LIST || value == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       TAILQ_INSERT_TAIL(&list->list, value, next);
+
+       return 0;
+
+fail:
+       ec_config_free(value);
+       return -1;
+}
+
+int ec_config_list_del(struct ec_config *list, struct ec_config *config)
+{
+       if (list == NULL || list->type != EC_CONFIG_TYPE_LIST) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       TAILQ_REMOVE(&list->list, config, next);
+       ec_config_free(config);
+       return 0;
+}
+
+static struct ec_config *
+ec_config_list_dup(const struct ec_config_list *list)
+{
+       struct ec_config *dup = NULL, *v, *value;
+
+       dup = ec_config_list();
+       if (dup == NULL)
+               goto fail;
+
+       TAILQ_FOREACH(v, list, next) {
+               value = ec_config_dup(v);
+               if (value == NULL)
+                       goto fail;
+               if (ec_config_list_add(dup, value) < 0)
+                       goto fail;
+       }
+
+       return dup;
+
+fail:
+       ec_config_free(dup);
+       return NULL;
+}
+
+static struct ec_config *
+ec_config_dict_dup(const struct ec_keyval *dict)
+{
+       struct ec_config *dup = NULL, *value;
+       struct ec_keyval_iter *iter = NULL;
+       const char *key;
+
+       dup = ec_config_dict();
+       if (dup == NULL)
+               goto fail;
+
+       for (iter = ec_keyval_iter(dict);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               key = ec_keyval_iter_get_key(iter);
+               value = ec_config_dup(ec_keyval_iter_get_val(iter));
+               if (value == NULL)
+                       goto fail;
+               if (ec_config_dict_set(dup, key, value) < 0)
+                       goto fail;
+       }
+       ec_keyval_iter_free(iter);
+
+       return dup;
+
+fail:
+       ec_config_free(dup);
+       ec_keyval_iter_free(iter);
+       return NULL;
+}
+
+struct ec_config *
+ec_config_dup(const struct ec_config *config)
+{
+       if (config == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       switch (config->type) {
+       case EC_CONFIG_TYPE_BOOL:
+               return ec_config_bool(config->boolean);
+       case EC_CONFIG_TYPE_INT64:
+               return ec_config_i64(config->i64);
+       case EC_CONFIG_TYPE_UINT64:
+               return ec_config_u64(config->u64);
+       case EC_CONFIG_TYPE_STRING:
+               return ec_config_string(config->string);
+       case EC_CONFIG_TYPE_NODE:
+               return ec_config_node(ec_node_clone(config->node));
+       case EC_CONFIG_TYPE_LIST:
+               return ec_config_list_dup(&config->list);
+       case EC_CONFIG_TYPE_DICT:
+               return ec_config_dict_dup(config->dict);
+       default:
+               errno = EINVAL;
+               break;
+       }
+
+       return NULL;
+}
+
+static int
+ec_config_list_dump(FILE *out, const char *key,
+               const struct ec_config_list *list, size_t indent)
+{
+       const struct ec_config *v;
+
+       fprintf(out, "%*s" "%s%s%stype=list\n", (int)indent * 4, "",
+               key ? "key=": "",
+               key ? key: "",
+               key ? " ": "");
+
+       TAILQ_FOREACH(v, list, next) {
+               if (__ec_config_dump(out, NULL, v, indent + 1) < 0)
+                       return -1;
+       }
+
+       return 0;
+}
+
+static int
+ec_config_dict_dump(FILE *out, const char *key, const struct ec_keyval *dict,
+               size_t indent)
+{
+       const struct ec_config *value;
+       struct ec_keyval_iter *iter;
+       const char *k;
+
+       fprintf(out, "%*s" "%s%s%stype=dict\n", (int)indent * 4, "",
+               key ? "key=": "",
+               key ? key: "",
+               key ? " ": "");
+
+       for (iter = ec_keyval_iter(dict);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               k = ec_keyval_iter_get_key(iter);
+               value = ec_keyval_iter_get_val(iter);
+               if (__ec_config_dump(out, k, value, indent + 1) < 0)
+                       goto fail;
+       }
+       ec_keyval_iter_free(iter);
+       return 0;
+
+fail:
+       ec_keyval_iter_free(iter);
+       return -1;
+}
+
+static int
+__ec_config_dump(FILE *out, const char *key, const struct ec_config *value,
+               size_t indent)
+{
+       char *val_str = NULL;
+
+       switch (value->type) {
+       case EC_CONFIG_TYPE_BOOL:
+               if (value->boolean)
+                       ec_asprintf(&val_str, "true");
+               else
+                       ec_asprintf(&val_str, "false");
+               break;
+       case EC_CONFIG_TYPE_INT64:
+               ec_asprintf(&val_str, "%"PRIu64, value->u64);
+               break;
+       case EC_CONFIG_TYPE_UINT64:
+               ec_asprintf(&val_str, "%"PRIi64, value->i64);
+               break;
+       case EC_CONFIG_TYPE_STRING:
+               ec_asprintf(&val_str, "%s", value->string);
+               break;
+       case EC_CONFIG_TYPE_NODE:
+               ec_asprintf(&val_str, "%p", value->node);
+               break;
+       case EC_CONFIG_TYPE_LIST:
+               return ec_config_list_dump(out, key, &value->list, indent);
+       case EC_CONFIG_TYPE_DICT:
+               return ec_config_dict_dump(out, key, value->dict, indent);
+       default:
+               errno = EINVAL;
+               break;
+       }
+
+       /* errno is already set on error */
+       if (val_str == NULL)
+               goto fail;
+
+       fprintf(out, "%*s" "%s%s%stype=%s val=%s\n", (int)indent * 4, "",
+               key ? "key=": "",
+               key ? key: "",
+               key ? " ": "",
+               ec_config_type_str(value->type), val_str);
+
+       ec_free(val_str);
+       return 0;
+
+fail:
+       ec_free(val_str);
+       return -1;
+}
+
+void
+ec_config_dump(FILE *out, const struct ec_config *config)
+{
+       fprintf(out, "------------------- config dump:\n");
+
+       if (config == NULL) {
+               fprintf(out, "no config\n");
+               return;
+       }
+
+       if (__ec_config_dump(out, NULL, config, 0) < 0)
+               fprintf(out, "error while dumping\n");
+}
+
+/* LCOV_EXCL_START */
+static const struct ec_config_schema sch_intlist_elt[] = {
+       {
+               .desc = "This is a description for int",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema sch_dict[] = {
+       {
+               .key = "my_int",
+               .desc = "This is a description for int",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .key = "my_int2",
+               .desc = "This is a description for int2",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema sch_dictlist_elt[] = {
+       {
+               .desc = "This is a description for dict",
+               .type = EC_CONFIG_TYPE_DICT,
+               .subschema = sch_dict,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema sch_baseconfig[] = {
+       {
+               .key = "my_bool",
+               .desc = "This is a description for bool",
+               .type = EC_CONFIG_TYPE_BOOL,
+       },
+       {
+               .key = "my_int",
+               .desc = "This is a description for int",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .key = "my_string",
+               .desc = "This is a description for string",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .key = "my_node",
+               .desc = "This is a description for node",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .key = "my_intlist",
+               .desc = "This is a description for list",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = sch_intlist_elt,
+       },
+       {
+               .key = "my_dictlist",
+               .desc = "This is a description for list",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = sch_dictlist_elt,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_config_testcase(void)
+{
+       struct ec_node *node = NULL;
+       struct ec_keyval *dict = NULL;
+       const struct ec_config *value = NULL;
+       struct ec_config *config = NULL, *config2 = NULL;
+       struct ec_config *list = NULL, *subconfig = NULL;
+       struct ec_config *list_, *config_;
+       int testres = 0;
+       int ret;
+
+       testres |= EC_TEST_CHECK(ec_config_key_is_reserved("id"),
+               "'id' should be reserved");
+       testres |= EC_TEST_CHECK(!ec_config_key_is_reserved("foo"),
+               "'foo' should not be reserved");
+
+       node = ec_node("empty", EC_NO_ID);
+       if (node == NULL)
+               goto fail;
+
+       if (ec_config_schema_validate(sch_baseconfig) < 0) {
+               EC_LOG(EC_LOG_ERR, "invalid config schema\n");
+               goto fail;
+       }
+
+       ec_config_schema_dump(stdout, sch_baseconfig);
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(config, "my_bool", ec_config_bool(true));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set boolean");
+       value = ec_config_dict_get(config, "my_bool");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_BOOL &&
+               value->boolean == true,
+               "unexpected boolean value");
+
+       ret = ec_config_dict_set(config, "my_int", ec_config_i64(1234));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(config, "my_int");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 1234,
+               "unexpected int value");
+
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(config, sch_baseconfig) == 0,
+               "cannot validate config\n");
+
+       ret = ec_config_dict_set(config, "my_string", ec_config_string("toto"));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set string");
+       value = ec_config_dict_get(config, "my_string");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_STRING &&
+               !strcmp(value->string, "toto"),
+               "unexpected string value");
+
+       list = ec_config_list();
+       if (list == NULL)
+               goto fail;
+
+       subconfig = ec_config_dict();
+       if (subconfig == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(1));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(subconfig, "my_int");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 1,
+               "unexpected int value");
+
+       ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(2));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(subconfig, "my_int2");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 2,
+               "unexpected int value");
+
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(subconfig, sch_dict) == 0,
+               "cannot validate subconfig\n");
+
+       ret = ec_config_list_add(list, subconfig);
+       subconfig = NULL; /* freed */
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add in list");
+
+       subconfig = ec_config_dict();
+       if (subconfig == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(subconfig, "my_int", ec_config_i64(3));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(subconfig, "my_int");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 3,
+               "unexpected int value");
+
+       ret = ec_config_dict_set(subconfig, "my_int2", ec_config_i64(4));
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set int");
+       value = ec_config_dict_get(subconfig, "my_int2");
+       testres |= EC_TEST_CHECK(
+               value != NULL &&
+               value->type == EC_CONFIG_TYPE_INT64 &&
+               value->i64 == 4,
+               "unexpected int value");
+
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(subconfig, sch_dict) == 0,
+               "cannot validate subconfig\n");
+
+       ret = ec_config_list_add(list, subconfig);
+       subconfig = NULL; /* freed */
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add in list");
+
+       ret = ec_config_dict_set(config, "my_dictlist", list);
+       list = NULL;
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set list");
+
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(config, sch_baseconfig) == 0,
+               "cannot validate config\n");
+
+       list_ = ec_config_dict_get(config, "my_dictlist");
+       for (config_ = ec_config_list_first(list_); config_ != NULL;
+            config_ = ec_config_list_next(list_, config_)) {
+               ec_config_dump(stdout, config_);
+       }
+
+       ec_config_dump(stdout, config);
+
+       config2 = ec_config_dup(config);
+       testres |= EC_TEST_CHECK(config2 != NULL, "cannot duplicate config");
+       testres |= EC_TEST_CHECK(
+               ec_config_cmp(config, config2) == 0,
+               "fail to compare config");
+       ec_config_free(config2);
+       config2 = NULL;
+
+       /* remove the first element */
+       ec_config_list_del(list_, ec_config_list_first(list_));
+       testres |= EC_TEST_CHECK(
+               ec_config_validate(config, sch_baseconfig) == 0,
+               "cannot validate config\n");
+
+       ec_config_dump(stdout, config);
+
+       ec_config_free(list);
+       ec_config_free(subconfig);
+       ec_config_free(config);
+       ec_keyval_free(dict);
+       ec_node_free(node);
+
+       return testres;
+
+fail:
+       ec_config_free(list);
+       ec_config_free(subconfig);
+       ec_config_free(config);
+       ec_config_free(config2);
+       ec_keyval_free(dict);
+       ec_node_free(node);
+
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_config_test = {
+       .name = "config",
+       .test = ec_config_testcase,
+};
+
+EC_TEST_REGISTER(ec_config_test);
diff --git a/src/ecoli_editline.c b/src/ecoli_editline.c
new file mode 100644 (file)
index 0000000..4cc7ec4
--- /dev/null
@@ -0,0 +1,684 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <histedit.h>
+
+#include <ecoli_utils.h>
+#include <ecoli_malloc.h>
+#include <ecoli_string.h>
+#include <ecoli_editline.h>
+#include <ecoli_keyval.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+
+struct ec_editline {
+       EditLine *el;
+       History *history;
+       HistEvent histev;
+       const struct ec_node *node;
+       char *prompt;
+};
+
+/* used by qsort below */
+static int
+strcasecmp_cb(const void *p1, const void *p2)
+{
+       return strcasecmp(*(char * const *)p1, *(char * const *)p2);
+}
+
+/* Show the matches as a multi-columns list */
+int
+ec_editline_print_cols(struct ec_editline *editline,
+               char const * const *matches, size_t n)
+{
+       size_t max_strlen = 0, len, i, j, ncols;
+       int width, height;
+       const char *space;
+       char **matches_copy = NULL;
+       FILE *f;
+
+       if (el_get(editline->el, EL_GETFP, 1, &f))
+               return -1;
+
+       fprintf(f, "\n");
+       if (n == 0)
+               return 0;
+
+       if (el_get(editline->el, EL_GETTC, "li", &height, (void *)0))
+               return -1;
+       if (el_get(editline->el, EL_GETTC, "co", &width, (void *)0))
+               return -1;
+
+       /* duplicate the matches table, and sort it */
+       matches_copy = calloc(n, sizeof(const char *));
+       if (matches_copy == NULL)
+               return -1;
+       memcpy(matches_copy, matches, sizeof(const char *) * n);
+       qsort(matches_copy, n, sizeof(char *), strcasecmp_cb);
+
+       /* get max string length */
+       for (i = 0; i < n; i++) {
+               len = strlen(matches_copy[i]);
+               if (len > max_strlen)
+                       max_strlen = len;
+       }
+
+       /* write the columns */
+       ncols = width / (max_strlen + 4);
+       if (ncols == 0)
+               ncols = 1;
+       for (i = 0; i < n; i+= ncols) {
+               for (j = 0; j < ncols; j++) {
+                       if (i + j >= n)
+                               break;
+                       if (j == 0)
+                               space = "";
+                       else
+                               space = "    ";
+                       fprintf(f, "%s%-*s", space,
+                               (int)max_strlen, matches[i+j]);
+               }
+               fprintf(f, "\n");
+       }
+
+       free(matches_copy);
+       return 0;
+}
+
+/* Show the helps on editline output */
+int
+ec_editline_print_helps(struct ec_editline *editline,
+                       const struct ec_editline_help *helps, size_t len)
+{
+       size_t i;
+       FILE *out;
+
+       if (el_get(editline->el, EL_GETFP, 1, &out))
+               return -1;
+
+       for (i = 0; i < len; i++) {
+               if (fprintf(out, "%-20s %s\n",
+                               helps[i].desc, helps[i].help) < 0)
+                       return -1;
+       }
+
+       return 0;
+}
+
+void
+ec_editline_free_helps(struct ec_editline_help *helps, size_t len)
+{
+       size_t i;
+
+       if (helps == NULL)
+               return;
+       for (i = 0; i < len; i++) {
+               ec_free(helps[i].desc);
+               ec_free(helps[i].help);
+       }
+       ec_free(helps);
+}
+
+int
+ec_editline_set_prompt(struct ec_editline *editline, const char *prompt)
+{
+       char *copy = NULL;
+
+       if (prompt != NULL) {
+               ec_strdup(prompt);
+               if (copy == NULL)
+                       return -1;
+       }
+
+       ec_free(editline->prompt);
+       editline->prompt = copy;
+
+       return 0;
+}
+
+static char *
+prompt_cb(EditLine *el)
+{
+       struct ec_editline *editline;
+       void *clientdata;
+
+       if (el_get(el, EL_CLIENTDATA, &clientdata))
+               return "> ";
+       editline = clientdata;
+
+       if (editline == NULL)
+               return "> ";
+
+       return editline->prompt;
+}
+
+struct ec_editline *
+ec_editline(const char *name, FILE *f_in, FILE *f_out, FILE *f_err,
+       unsigned int flags)
+{
+       struct ec_editline *editline = NULL;
+       EditLine *el;
+
+       if (f_in == NULL || f_out == NULL || f_err == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       editline = ec_calloc(1, sizeof(*editline));
+       if (editline == NULL)
+               goto fail;
+
+       el = el_init(name, f_in, f_out, f_err);
+       if (el == NULL)
+               goto fail;
+       editline->el = el;
+
+       /* save editline pointer as user data */
+       if (el_set(el, EL_CLIENTDATA, editline))
+               goto fail;
+
+       /* install default editline signals */
+       if (el_set(el, EL_SIGNAL, 1))
+               goto fail;
+
+       if (el_set(el, EL_PREP_TERM, 0))
+               goto fail;
+
+       /* use emacs bindings */
+       if (el_set(el, EL_EDITOR, "emacs"))
+               goto fail;
+       if (el_set(el, EL_BIND, "^W", "ed-delete-prev-word", NULL))
+               goto fail;
+
+       /* ask terminal to not send signals */
+       if (flags & EC_EDITLINE_DISABLE_SIGNALS) {
+               if (el_set(el, EL_SETTY, "-d", "-isig", NULL))
+                       goto fail;
+       }
+
+       /* set prompt */
+       editline->prompt = ec_strdup("> ");
+       if (editline->prompt == NULL)
+               goto fail;
+       if (el_set(el, EL_PROMPT, prompt_cb))
+               goto fail;
+
+       /* set up history */
+       if ((flags & EC_EDITLINE_DISABLE_HISTORY) == 0) {
+               if (ec_editline_set_history(
+                               editline, EC_EDITLINE_HISTORY_SIZE) < 0)
+                       goto fail;
+       }
+
+       /* register completion callback */
+       if ((flags & EC_EDITLINE_DISABLE_COMPLETION) == 0) {
+               if (el_set(el, EL_ADDFN, "ed-complete", "Complete buffer",
+                               ec_editline_complete))
+                       goto fail;
+               if (el_set(el, EL_BIND, "^I", "ed-complete", NULL))
+                       goto fail;
+               if (el_set(el, EL_BIND, "?", "ed-complete", NULL))
+                       goto fail;
+       }
+
+       return editline;
+
+fail:
+       ec_editline_free(editline);
+       return NULL;
+}
+
+void ec_editline_free(struct ec_editline *editline)
+{
+       if (editline == NULL)
+               return;
+       if (editline->el != NULL)
+               el_end(editline->el);
+       if (editline->history != NULL)
+               history_end(editline->history);
+       ec_free(editline->prompt);
+       ec_free(editline);
+}
+
+EditLine *ec_editline_get_el(struct ec_editline *editline)
+{
+       return editline->el;
+}
+
+const struct ec_node *
+ec_editline_get_node(struct ec_editline *editline)
+{
+       return editline->node;
+}
+
+void
+ec_editline_set_node(struct ec_editline *editline, const struct ec_node *node)
+{
+       editline->node = node;
+}
+
+int ec_editline_set_history(struct ec_editline *editline,
+       size_t hist_size)
+{
+       EditLine *el = editline->el;
+
+       if (editline->history != NULL)
+               history_end(editline->history);
+
+       if (hist_size == 0)
+               return 0;
+
+       editline->history = history_init();
+       if (editline->history == NULL)
+               goto fail;
+       if (history(editline->history, &editline->histev, H_SETSIZE,
+                       hist_size) < 0)
+               goto fail;
+       if (history(editline->history, &editline->histev, H_SETUNIQUE, 1))
+               goto fail;
+       if (el_set(el, EL_HIST, history, editline->history))
+               goto fail;
+
+       return 0;
+
+fail:
+       //XXX errno
+       if (editline->history != NULL) {
+               history_end(editline->history);
+               editline->history = NULL;
+       }
+       return -1;
+}
+
+void ec_editline_free_completions(char **matches, size_t len)
+{
+       size_t i;
+
+       // XXX use ec_malloc/ec_free() instead for consistency
+       if (matches == NULL)
+               return;
+       for (i = 0; i < len; i++)
+               free(matches[i]);
+       free(matches);
+}
+
+ssize_t
+ec_editline_get_completions(const struct ec_comp *cmpl, char ***matches_out)
+{
+       const struct ec_comp_item *item;
+       struct ec_comp_iter *iter = NULL;
+       char **matches = NULL;
+       size_t count = 0;
+
+       iter = ec_comp_iter(cmpl, EC_COMP_FULL | EC_COMP_PARTIAL);
+       if (iter == NULL)
+               goto fail;
+
+       while ((item = ec_comp_iter_next(iter)) != NULL) {
+               char **tmp;
+
+               tmp = realloc(matches, (count + 1) * sizeof(char *));
+               if (tmp == NULL)
+                       goto fail;
+               matches = tmp;
+               matches[count] = strdup(ec_comp_item_get_display(item));
+               if (matches[count] == NULL)
+                       goto fail;
+               count++;
+       }
+
+       *matches_out = matches;
+       return count;
+
+fail:
+       ec_editline_free_completions(matches, count);
+       *matches_out = NULL;
+       ec_comp_iter_free(iter);
+       return -1;
+}
+
+char *
+ec_editline_append_chars(const struct ec_comp *cmpl)
+{
+       const struct ec_comp_item *item;
+       struct ec_comp_iter *iter = NULL;
+       const char *append;
+       char *ret = NULL;
+       size_t n;
+
+       iter = ec_comp_iter(cmpl, EC_COMP_FULL | EC_COMP_PARTIAL);
+       if (iter == NULL)
+               goto fail;
+
+       while ((item = ec_comp_iter_next(iter)) != NULL) {
+               append = ec_comp_item_get_completion(item);
+               if (ret == NULL) {
+                       ret = ec_strdup(append);
+                       if (ret == NULL)
+                               goto fail;
+               } else {
+                       n = ec_strcmp_count(ret, append);
+                       ret[n] = '\0';
+               }
+       }
+       ec_comp_iter_free(iter);
+
+       return ret;
+
+fail:
+       ec_comp_iter_free(iter);
+       ec_free(ret);
+
+       return NULL;
+}
+
+/* this function builds the help string */
+static int get_node_help(const struct ec_comp_item *item,
+                       struct ec_editline_help *help)
+{
+       const struct ec_comp_group *grp;
+       const struct ec_parse *state;
+       const struct ec_node *node;
+       const char *node_help = NULL;
+       const char *node_desc = NULL;
+
+       help->desc = NULL;
+       help->help = NULL;
+
+       grp = ec_comp_item_get_grp(item);
+
+       for (state = grp->state; state != NULL;
+            state = ec_parse_get_parent(state)) {
+               node = ec_parse_get_node(state);
+               if (node_help == NULL)
+                       node_help = ec_keyval_get(ec_node_attrs(node), "help");
+               if (node_desc == NULL)
+                       node_desc = ec_node_desc(node);
+       }
+
+       if (node_help == NULL)
+               node_help = "";
+       if (node_desc == NULL)
+               goto fail;
+
+       help->desc = ec_strdup(node_desc);
+       if (help->desc == NULL)
+               goto fail;
+
+       help->help = ec_strdup(node_help);
+       if (help->help == NULL)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_free(help->desc);
+       ec_free(help->help);
+       return -1;
+}
+
+ssize_t
+ec_editline_get_helps(const struct ec_editline *editline, const char *line,
+       const char *full_line, struct ec_editline_help **helps_out)
+{
+       struct ec_comp_iter *iter = NULL;
+       const struct ec_comp_group *grp, *prev_grp = NULL;
+       const struct ec_comp_item *item;
+       struct ec_comp *cmpl = NULL;
+       struct ec_parse *parse = NULL;
+       unsigned int count = 0;
+       struct ec_editline_help *helps = NULL;
+
+       *helps_out = NULL;
+
+       /* check if the current line matches */
+       parse = ec_node_parse(editline->node, full_line);
+       if (ec_parse_matches(parse))
+               count = 1;
+       ec_parse_free(parse);
+       parse = NULL;
+
+       /* complete at current cursor position */
+       cmpl = ec_node_complete(editline->node, line);
+       if (cmpl == NULL) //XXX log error
+               goto fail;
+
+       /* let's display one contextual help per node */
+       iter = ec_comp_iter(cmpl,
+               EC_COMP_UNKNOWN | EC_COMP_FULL | EC_COMP_PARTIAL);
+       if (iter == NULL)
+               goto fail;
+
+       helps = ec_calloc(1, sizeof(*helps));
+       if (helps == NULL)
+               goto fail;
+       if (count == 1) {
+               helps[0].desc = ec_strdup("<return>");
+               if (helps[0].desc == NULL)
+                       goto fail;
+               helps[0].help = ec_strdup("Validate command.");
+               if (helps[0].help == NULL)
+                       goto fail;
+       }
+
+       while ((item = ec_comp_iter_next(iter)) != NULL) {
+               struct ec_editline_help *tmp = NULL;
+
+               /* keep one help per group, skip other items  */
+               grp = ec_comp_item_get_grp(item);
+               if (grp == prev_grp)
+                       continue;
+
+               prev_grp = grp;
+
+               tmp = ec_realloc(helps, (count + 1) * sizeof(*helps));
+               if (tmp == NULL)
+                       goto fail;
+               helps = tmp;
+               if (get_node_help(item, &helps[count]) < 0)
+                       goto fail;
+               count++;
+       }
+
+       ec_comp_iter_free(iter);
+       ec_comp_free(cmpl);
+       *helps_out = helps;
+
+       return count;
+
+fail:
+       ec_comp_iter_free(iter);
+       ec_parse_free(parse);
+       ec_comp_free(cmpl);
+       if (helps != NULL) {
+               while (count--) {
+                       ec_free(helps[count].desc);
+                       ec_free(helps[count].help);
+               }
+               ec_free(helps);
+       }
+
+       return -1;
+}
+
+int
+ec_editline_complete(EditLine *el, int c)
+{
+       struct ec_editline *editline;
+       const LineInfo *line_info;
+       int ret = CC_REFRESH;
+       struct ec_comp *cmpl = NULL;
+       char *append = NULL;
+       char *line = NULL;
+       void *clientdata;
+       FILE *out, *err;
+       int len;
+
+       if (el_get(el, EL_GETFP, 1, &out))
+               return -1;
+       if (el_get(el, EL_GETFP, 1, &err))
+               return -1;
+
+       (void)c;
+
+       if (el_get(el, EL_CLIENTDATA, &clientdata)) {
+               fprintf(err, "completion failure: no client data\n");
+               goto fail;
+       }
+       editline = clientdata;
+       (void)editline;
+
+       line_info = el_line(el);
+       if (line_info == NULL) {
+               fprintf(err, "completion failure: no line info\n");
+               goto fail;
+       }
+
+       len = line_info->cursor - line_info->buffer;
+       if (ec_asprintf(&line, "%.*s", len, line_info->buffer) < 0) {
+               fprintf(err, "completion failure: no memory\n");
+               goto fail;
+       }
+
+       if (editline->node == NULL) {
+               fprintf(err, "completion failure: no ec_node\n");
+               goto fail;
+       }
+
+       cmpl = ec_node_complete(editline->node, line);
+       if (cmpl == NULL)
+               goto fail;
+
+       append = ec_editline_append_chars(cmpl);
+
+       if (c == '?') {
+               struct ec_editline_help *helps = NULL;
+               ssize_t count = 0;
+
+               count = ec_editline_get_helps(editline, line, line_info->buffer,
+                               &helps);
+
+               fprintf(out, "\n");
+               if (ec_editline_print_helps(editline, helps, count) < 0) {
+                       fprintf(err, "completion failure: cannot show help\n");
+                       ec_editline_free_helps(helps, count);
+                       goto fail;
+               }
+
+               ec_editline_free_helps(helps, count);
+               ret = CC_REDISPLAY;
+       } else if (append == NULL || strcmp(append, "") == 0) {
+               char **matches = NULL;
+               ssize_t count = 0;
+
+               count = ec_editline_get_completions(cmpl, &matches);
+               if (count < 0) {
+                       fprintf(err, "completion failure: cannot get completions\n");
+                       goto fail;
+               }
+
+               if (ec_editline_print_cols(
+                               editline,
+                               EC_CAST(matches, char **,
+                                       char const * const *),
+                               count) < 0) {
+                       fprintf(err, "completion failure: cannot print\n");
+                       ec_editline_free_completions(matches, count);
+                       goto fail;
+               }
+
+               ec_editline_free_completions(matches, count);
+               ret = CC_REDISPLAY;
+       } else {
+               if (el_insertstr(el, append) < 0) {
+                       fprintf(err, "completion failure: cannot insert\n");
+                       goto fail;
+               }
+               if (ec_comp_count(cmpl, EC_COMP_FULL) +
+                       ec_comp_count(cmpl, EC_COMP_PARTIAL) == 1) {
+                       if (el_insertstr(el, " ") < 0) {
+                               fprintf(err, "completion failure: cannot insert space\n");
+                               goto fail;
+                       }
+               }
+       }
+
+       ec_comp_free(cmpl);
+       ec_free(line);
+       ec_free(append);
+
+       return ret;
+
+fail:
+       ec_comp_free(cmpl);
+       ec_free(line);
+       ec_free(append);
+
+       return CC_ERROR;
+}
+
+char *
+ec_editline_gets(struct ec_editline *editline)
+{
+       EditLine *el = editline->el;
+       char *line_copy = NULL;
+       const char *line;
+       int count;
+
+       line = el_gets(el, &count);
+       if (line == NULL)
+               return NULL;
+
+       line_copy = ec_strdup(line);
+       if (line_copy == NULL)
+               goto fail;
+
+       line_copy[strlen(line_copy) - 1] = '\0'; //XXX needed because of sh_lex bug?
+
+       if (editline->history != NULL && !ec_str_is_space(line_copy)) {
+               history(editline->history, &editline->histev,
+                       H_ENTER, line_copy);
+       }
+
+       return line_copy;
+
+fail:
+       ec_free(line_copy);
+       return NULL;
+}
+
+struct ec_parse *
+ec_editline_parse(struct ec_editline *editline, const struct ec_node *node)
+{
+       char *line = NULL;
+       struct ec_parse *parse = NULL;
+
+       /* XXX add sh_lex automatically? This node is required, parse and
+        * complete are based on it. */
+
+       ec_editline_set_node(editline, node);
+
+       line = ec_editline_gets(editline);
+       if (line == NULL)
+               goto fail;
+
+       parse = ec_node_parse(node, line);
+       if (parse == NULL)
+               goto fail;
+
+       ec_free(line);
+       return parse;
+
+fail:
+       ec_free(line);
+       ec_parse_free(parse);
+
+       return NULL;
+}
+
diff --git a/src/ecoli_init.c b/src/ecoli_init.c
new file mode 100644 (file)
index 0000000..fd5c0c3
--- /dev/null
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <ecoli_init.h>
+
+static struct ec_init_list init_list = TAILQ_HEAD_INITIALIZER(init_list);
+
+/* register an init function */
+void ec_init_register(struct ec_init *init)
+{
+       struct ec_init *cur;
+
+       if (TAILQ_EMPTY(&init_list)) {
+               TAILQ_INSERT_HEAD(&init_list, init, next);
+               return;
+       }
+
+
+       TAILQ_FOREACH(cur, &init_list, next) {
+               if (init->priority > cur->priority)
+                       continue;
+
+               TAILQ_INSERT_BEFORE(cur, init, next);
+               return;
+       }
+
+       TAILQ_INSERT_TAIL(&init_list, init, next);
+}
+
+int ec_init(void)
+{
+       struct ec_init *init;
+
+       TAILQ_FOREACH(init, &init_list, next) {
+               if (init->init() < 0)
+                       return -1;
+       }
+
+       return 0;
+}
diff --git a/src/ecoli_keyval.c b/src/ecoli_keyval.c
new file mode 100644 (file)
index 0000000..12fe66b
--- /dev/null
@@ -0,0 +1,569 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <ecoli_init.h>
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_murmurhash.h>
+#include <ecoli_keyval.h>
+
+#define FACTOR 3
+
+EC_LOG_TYPE_REGISTER(keyval);
+
+static uint32_t ec_keyval_seed;
+
+struct ec_keyval_elt {
+       char *key;
+       void *val;
+       uint32_t hash;
+       ec_keyval_elt_free_t free;
+       unsigned int refcount;
+};
+
+struct ec_keyval_elt_ref {
+       LIST_ENTRY(ec_keyval_elt_ref) next;
+       struct ec_keyval_elt *elt;
+};
+
+LIST_HEAD(ec_keyval_elt_ref_list, ec_keyval_elt_ref);
+
+struct ec_keyval {
+       size_t len;
+       size_t table_size;
+       struct ec_keyval_elt_ref_list *table;
+};
+
+struct ec_keyval_iter {
+       const struct ec_keyval *keyval;
+       size_t cur_idx;
+       const struct ec_keyval_elt_ref *cur_ref;
+};
+
+struct ec_keyval *ec_keyval(void)
+{
+       struct ec_keyval *keyval;
+
+       keyval = ec_calloc(1, sizeof(*keyval));
+       if (keyval == NULL)
+               return NULL;
+
+       return keyval;
+}
+
+static struct ec_keyval_elt_ref *
+ec_keyval_lookup(const struct ec_keyval *keyval, const char *key)
+{
+       struct ec_keyval_elt_ref *ref;
+       uint32_t h, mask = keyval->table_size - 1;
+
+       if (keyval == NULL || key == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+       if (keyval->table_size == 0) {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       h = ec_murmurhash3(key, strlen(key), ec_keyval_seed);
+       LIST_FOREACH(ref, &keyval->table[h & mask], next) {
+               if (strcmp(ref->elt->key, key) == 0)
+                       return ref;
+       }
+
+       errno = ENOENT;
+       return NULL;
+}
+
+static void ec_keyval_elt_ref_free(struct ec_keyval_elt_ref *ref)
+{
+       struct ec_keyval_elt *elt;
+
+       if (ref == NULL)
+               return;
+
+       elt = ref->elt;
+       if (elt != NULL && --elt->refcount == 0) {
+               ec_free(elt->key);
+               if (elt->free != NULL)
+                       elt->free(elt->val);
+               ec_free(elt);
+       }
+       ec_free(ref);
+}
+
+bool ec_keyval_has_key(const struct ec_keyval *keyval, const char *key)
+{
+       return !!ec_keyval_lookup(keyval, key);
+}
+
+void *ec_keyval_get(const struct ec_keyval *keyval, const char *key)
+{
+       struct ec_keyval_elt_ref *ref;
+
+       ref = ec_keyval_lookup(keyval, key);
+       if (ref == NULL)
+               return NULL;
+
+       return ref->elt->val;
+}
+
+int ec_keyval_del(struct ec_keyval *keyval, const char *key)
+{
+       struct ec_keyval_elt_ref *ref;
+
+       ref = ec_keyval_lookup(keyval, key);
+       if (ref == NULL)
+               return -1;
+
+       /* we could resize table here */
+
+       LIST_REMOVE(ref, next);
+       ec_keyval_elt_ref_free(ref);
+       keyval->len--;
+
+       return 0;
+}
+
+static int ec_keyval_table_resize(struct ec_keyval *keyval, size_t new_size)
+{
+       struct ec_keyval_elt_ref_list *new_table;
+       struct ec_keyval_elt_ref *ref;
+       size_t i;
+
+       if (new_size == 0 || (new_size & (new_size - 1))) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       new_table = ec_calloc(new_size, sizeof(*keyval->table));
+       if (new_table == NULL)
+               return -1;
+
+       for (i = 0; i < keyval->table_size; i++) {
+               while (!LIST_EMPTY(&keyval->table[i])) {
+                       ref = LIST_FIRST(&keyval->table[i]);
+                       LIST_REMOVE(ref, next);
+                       LIST_INSERT_HEAD(
+                               &new_table[ref->elt->hash & (new_size - 1)],
+                               ref, next);
+               }
+       }
+
+       ec_free(keyval->table);
+       keyval->table = new_table;
+       keyval->table_size = new_size;
+
+       return 0;
+}
+
+static int
+__ec_keyval_set(struct ec_keyval *keyval, struct ec_keyval_elt_ref *ref)
+{
+       size_t new_size;
+       uint32_t mask;
+       int ret;
+
+       /* remove previous entry if any */
+       ec_keyval_del(keyval, ref->elt->key);
+
+       if (keyval->len >= keyval->table_size) {
+               if (keyval->table_size != 0)
+                       new_size =  keyval->table_size << FACTOR;
+               else
+                       new_size =  1 << FACTOR;
+               ret = ec_keyval_table_resize(keyval, new_size);
+               if (ret < 0)
+                       return ret;
+       }
+
+       mask = keyval->table_size - 1;
+       LIST_INSERT_HEAD(&keyval->table[ref->elt->hash & mask], ref, next);
+       keyval->len++;
+
+       return 0;
+}
+
+int ec_keyval_set(struct ec_keyval *keyval, const char *key, void *val,
+       ec_keyval_elt_free_t free_cb)
+{
+       struct ec_keyval_elt *elt = NULL;
+       struct ec_keyval_elt_ref *ref = NULL;
+       uint32_t h;
+
+       if (keyval == NULL || key == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       ref = ec_calloc(1, sizeof(*ref));
+       if (ref == NULL)
+               goto fail;
+
+       elt = ec_calloc(1, sizeof(*elt));
+       if (elt == NULL)
+               goto fail;
+
+       ref->elt = elt;
+       elt->refcount = 1;
+       elt->val = val;
+       val = NULL;
+       elt->free = free_cb;
+       elt->key = ec_strdup(key);
+       if (elt->key == NULL)
+               goto fail;
+       h = ec_murmurhash3(key, strlen(key), ec_keyval_seed);
+       elt->hash = h;
+
+       if (__ec_keyval_set(keyval, ref) < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       if (free_cb != NULL && val != NULL)
+               free_cb(val);
+       ec_keyval_elt_ref_free(ref);
+       return -1;
+}
+
+void ec_keyval_free(struct ec_keyval *keyval)
+{
+       struct ec_keyval_elt_ref *ref;
+       size_t i;
+
+       if (keyval == NULL)
+               return;
+
+       for (i = 0; i < keyval->table_size; i++) {
+               while (!LIST_EMPTY(&keyval->table[i])) {
+                       ref = LIST_FIRST(&keyval->table[i]);
+                       LIST_REMOVE(ref, next);
+                       ec_keyval_elt_ref_free(ref);
+               }
+       }
+       ec_free(keyval->table);
+       ec_free(keyval);
+}
+
+size_t ec_keyval_len(const struct ec_keyval *keyval)
+{
+       return keyval->len;
+}
+
+void
+ec_keyval_iter_free(struct ec_keyval_iter *iter)
+{
+       ec_free(iter);
+}
+
+struct ec_keyval_iter *
+ec_keyval_iter(const struct ec_keyval *keyval)
+{
+       struct ec_keyval_iter *iter = NULL;
+
+       iter = ec_calloc(1, sizeof(*iter));
+       if (iter == NULL)
+               return NULL;
+
+       iter->keyval = keyval;
+       iter->cur_idx = 0;
+       iter->cur_ref = NULL;
+
+       ec_keyval_iter_next(iter);
+
+       return iter;
+}
+
+void
+ec_keyval_iter_next(struct ec_keyval_iter *iter)
+{
+       const struct ec_keyval_elt_ref *ref;
+       size_t i;
+
+       i = iter->cur_idx;
+       if (i == iter->keyval->table_size)
+               return; /* no more element */
+
+       if (iter->cur_ref != NULL) {
+               ref = LIST_NEXT(iter->cur_ref, next);
+               if (ref != NULL) {
+                       iter->cur_ref = ref;
+                       return;
+               }
+               i++;
+       }
+
+       for (; i < iter->keyval->table_size; i++) {
+               LIST_FOREACH(ref, &iter->keyval->table[i], next) {
+                       iter->cur_idx = i;
+                       iter->cur_ref = LIST_FIRST(&iter->keyval->table[i]);
+                       return;
+               }
+       }
+
+       iter->cur_idx = iter->keyval->table_size;
+       iter->cur_ref = NULL;
+}
+
+bool
+ec_keyval_iter_valid(const struct ec_keyval_iter *iter)
+{
+       if (iter == NULL || iter->cur_ref == NULL)
+               return false;
+
+       return true;
+}
+
+const char *
+ec_keyval_iter_get_key(const struct ec_keyval_iter *iter)
+{
+       if (iter == NULL || iter->cur_ref == NULL)
+               return NULL;
+
+       return iter->cur_ref->elt->key;
+}
+
+void *
+ec_keyval_iter_get_val(const struct ec_keyval_iter *iter)
+{
+       if (iter == NULL || iter->cur_ref == NULL)
+               return NULL;
+
+       return iter->cur_ref->elt->val;
+}
+
+void ec_keyval_dump(FILE *out, const struct ec_keyval *keyval)
+{
+       struct ec_keyval_iter *iter;
+
+       if (keyval == NULL) {
+               fprintf(out, "empty keyval\n");
+               return;
+       }
+
+       fprintf(out, "keyval:\n");
+       for (iter = ec_keyval_iter(keyval);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               fprintf(out, "  %s: %p\n",
+                       ec_keyval_iter_get_key(iter),
+                       ec_keyval_iter_get_val(iter));
+       }
+       ec_keyval_iter_free(iter);
+}
+
+struct ec_keyval *ec_keyval_dup(const struct ec_keyval *keyval)
+{
+       struct ec_keyval *dup = NULL;
+       struct ec_keyval_elt_ref *ref, *dup_ref = NULL;
+       size_t i;
+
+       dup = ec_keyval();
+       if (dup == NULL)
+               return NULL;
+
+       for (i = 0; i < keyval->table_size; i++) {
+               LIST_FOREACH(ref, &keyval->table[i], next) {
+                       dup_ref = ec_calloc(1, sizeof(*ref));
+                       if (dup_ref == NULL)
+                               goto fail;
+                       dup_ref->elt = ref->elt;
+                       ref->elt->refcount++;
+
+                       if (__ec_keyval_set(dup, dup_ref) < 0)
+                               goto fail;
+               }
+       }
+
+       return dup;
+
+fail:
+       ec_keyval_elt_ref_free(dup_ref);
+       ec_keyval_free(dup);
+       return NULL;
+}
+
+static int ec_keyval_init_func(void)
+{
+       int fd;
+       ssize_t ret;
+
+       return 0;//XXX for test reproduceability
+
+       fd = open("/dev/urandom", 0);
+       if (fd == -1) {
+               fprintf(stderr, "failed to open /dev/urandom\n");
+               return -1;
+       }
+       ret = read(fd, &ec_keyval_seed, sizeof(ec_keyval_seed));
+       if (ret != sizeof(ec_keyval_seed)) {
+               fprintf(stderr, "failed to read /dev/urandom\n");
+               return -1;
+       }
+       close(fd);
+       return 0;
+}
+
+static struct ec_init ec_keyval_init = {
+       .init = ec_keyval_init_func,
+       .priority = 50,
+};
+
+EC_INIT_REGISTER(ec_keyval_init);
+
+/* LCOV_EXCL_START */
+static int ec_keyval_testcase(void)
+{
+       struct ec_keyval *keyval, *dup;
+       struct ec_keyval_iter *iter;
+       char *val;
+       size_t i, count;
+       int ret, testres = 0;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+
+       keyval = ec_keyval();
+       if (keyval == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create keyval\n");
+               return -1;
+       }
+
+       count = 0;
+       for (iter = ec_keyval_iter(keyval);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               count++;
+       }
+       ec_keyval_iter_free(iter);
+       testres |= EC_TEST_CHECK(count == 0, "invalid count in iterator");
+
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0, "bad keyval len");
+       ret = ec_keyval_set(keyval, "key1", "val1", NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       ret = ec_keyval_set(keyval, "key2", ec_strdup("val2"), ec_free_func);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2, "bad keyval len");
+
+       val = ec_keyval_get(keyval, "key1");
+       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val1"),
+                               "invalid keyval value");
+       val = ec_keyval_get(keyval, "key2");
+       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "val2"),
+                               "invalid keyval value");
+       val = ec_keyval_get(keyval, "key3");
+       testres |= EC_TEST_CHECK(val == NULL, "key3 should be NULL");
+
+       ret = ec_keyval_set(keyval, "key1", "another_val1", NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       ret = ec_keyval_set(keyval, "key2", ec_strdup("another_val2"),
+                       ec_free_func);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 2,
+                               "bad keyval len");
+
+       val = ec_keyval_get(keyval, "key1");
+       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val1"),
+               "invalid keyval value");
+       val = ec_keyval_get(keyval, "key2");
+       testres |= EC_TEST_CHECK(val != NULL && !strcmp(val, "another_val2"),
+               "invalid keyval value");
+       testres |= EC_TEST_CHECK(ec_keyval_has_key(keyval, "key1"),
+               "key1 should be in keyval");
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_keyval_dump(f, NULL);
+       fclose(f);
+       f = NULL;
+       free(buf);
+       buf = NULL;
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_keyval_dump(f, keyval);
+       fclose(f);
+       f = NULL;
+       free(buf);
+       buf = NULL;
+
+       ret = ec_keyval_del(keyval, "key1");
+       testres |= EC_TEST_CHECK(ret == 0, "cannot del key1");
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 1,
+               "invalid keyval len");
+       ret = ec_keyval_del(keyval, "key2");
+       testres |= EC_TEST_CHECK(ret == 0, "cannot del key2");
+       testres |= EC_TEST_CHECK(ec_keyval_len(keyval) == 0,
+               "invalid keyval len");
+
+       for (i = 0; i < 100; i++) {
+               char key[8];
+               snprintf(key, sizeof(key), "k%zd", i);
+               ret = ec_keyval_set(keyval, key, "val", NULL);
+               testres |= EC_TEST_CHECK(ret == 0, "cannot set key");
+       }
+       dup = ec_keyval_dup(keyval);
+       testres |= EC_TEST_CHECK(dup != NULL, "cannot duplicate keyval");
+       if (dup != NULL) {
+               for (i = 0; i < 100; i++) {
+                       char key[8];
+                       snprintf(key, sizeof(key), "k%zd", i);
+                       val = ec_keyval_get(dup, key);
+                       testres |= EC_TEST_CHECK(
+                               val != NULL && !strcmp(val, "val"),
+                               "invalid keyval value");
+               }
+               ec_keyval_free(dup);
+               dup = NULL;
+       }
+
+       count = 0;
+       for (iter = ec_keyval_iter(keyval);
+            ec_keyval_iter_valid(iter);
+            ec_keyval_iter_next(iter)) {
+               count++;
+       }
+       ec_keyval_iter_free(iter);
+       testres |= EC_TEST_CHECK(count == 100, "invalid count in iterator");
+
+       /* einval */
+       ret = ec_keyval_set(keyval, NULL, "val1", NULL);
+       testres |= EC_TEST_CHECK(ret == -1, "should not be able to set key");
+       val = ec_keyval_get(keyval, NULL);
+       testres |= EC_TEST_CHECK(val == NULL, "get(NULL) should no success");
+
+       ec_keyval_free(keyval);
+
+       return testres;
+
+fail:
+       ec_keyval_free(keyval);
+       if (f)
+               fclose(f);
+       free(buf);
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_keyval_test = {
+       .name = "keyval",
+       .test = ec_keyval_testcase,
+};
+
+EC_TEST_REGISTER(ec_keyval_test);
diff --git a/src/ecoli_log.c b/src/ecoli_log.c
new file mode 100644 (file)
index 0000000..aefba83
--- /dev/null
@@ -0,0 +1,229 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#define _GNU_SOURCE /* for vasprintf */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <syslog.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_string.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+
+EC_LOG_TYPE_REGISTER(log);
+
+static ec_log_t ec_log_fct = ec_log_default_cb;
+static void *ec_log_opaque;
+
+struct ec_log_type {
+       char *name;
+       enum ec_log_level level;
+};
+
+static struct ec_log_type *log_types;
+static size_t log_types_len;
+static enum ec_log_level global_level = EC_LOG_WARNING;
+
+int ec_log_level_set(enum ec_log_level level)
+{
+       if (level > EC_LOG_DEBUG)
+               return -1;
+       global_level = level;
+
+       return 0;
+}
+
+enum ec_log_level ec_log_level_get(void)
+{
+       return global_level;
+}
+
+int ec_log_default_cb(int type, enum ec_log_level level, void *opaque,
+               const char *str)
+{
+       (void)opaque;
+
+       if (level > ec_log_level_get())
+               return 0;
+
+       if (fprintf(stderr, "[%d] %-12s %s", level, ec_log_name(type), str) < 0)
+               return -1;
+
+       return 0;
+}
+
+int ec_log_fct_register(ec_log_t usr_log, void *opaque)
+{
+       if (usr_log == NULL) {
+               ec_log_fct = ec_log_default_cb;
+               ec_log_opaque = NULL;
+       } else {
+               ec_log_fct = usr_log;
+               ec_log_opaque = opaque;
+       }
+
+       return 0;
+}
+
+static int
+ec_log_lookup(const char *name)
+{
+       size_t i;
+
+       for (i = 0; i < log_types_len; i++) {
+               if (log_types[i].name == NULL)
+                       continue;
+               if (strcmp(name, log_types[i].name) == 0)
+                       return i;
+       }
+
+       return -1;
+}
+
+int
+ec_log_type_register(const char *name)
+{
+       struct ec_log_type *new_types;
+       char *copy;
+       int id;
+
+       id = ec_log_lookup(name);
+       if (id >= 0)
+               return id;
+
+       // XXX not that good to allocate in constructor
+       new_types = ec_realloc(log_types,
+               sizeof(*new_types) * (log_types_len + 1));
+       if (new_types == NULL)
+               return -1; /* errno is set */
+       log_types = new_types;
+
+       copy = ec_strdup(name);
+       if (copy == NULL)
+               return -1; /* errno is set */
+
+       id = log_types_len++;
+       log_types[id].name = copy;
+       log_types[id].level = EC_LOG_DEBUG;
+
+       return id;
+}
+
+const char *
+ec_log_name(int type)
+{
+       if (type < 0 || (unsigned int)type >= log_types_len)
+               return "unknown";
+       return log_types[type].name;
+}
+
+int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap)
+{
+       char *s;
+       int ret;
+
+       /* don't use ec_vasprintf here, because it will call
+        * ec_malloc(), then ec_log(), ec_vasprintf()...
+        * -> stack overflow */
+       ret = vasprintf(&s, format, ap);
+       if (ret < 0)
+               return ret;
+
+       ret = ec_log_fct(type, level, ec_log_opaque, s);
+       free(s);
+
+       return ret;
+}
+
+int ec_log(int type, enum ec_log_level level, const char *format, ...)
+{
+       va_list ap;
+       int ret;
+
+       va_start(ap, format);
+       ret = ec_vlog(type, level, format, ap);
+       va_end(ap);
+
+       return ret;
+}
+
+/* LCOV_EXCL_START */
+static int
+log_cb(int type, enum ec_log_level level, void *opaque, const char *str)
+{
+       (void)type;
+       (void)level;
+       (void)str;
+       *(int *)opaque = 1;
+
+       return 0;
+}
+
+static int ec_log_testcase(void)
+{
+       ec_log_t prev_log_cb;
+       void *prev_opaque;
+       const char *logname;
+       int testres = 0;
+       int check_cb = 0;
+       int logtype;
+       int level;
+       int ret;
+
+       prev_log_cb = ec_log_fct;
+       prev_opaque = ec_log_opaque;
+
+       ret = ec_log_fct_register(log_cb, &check_cb);
+       testres |= EC_TEST_CHECK(ret == 0,
+                               "cannot register log function\n");
+       EC_LOG(LOG_ERR, "test\n");
+       testres |= EC_TEST_CHECK(check_cb == 1,
+                               "log callback was not invoked\n");
+       logtype = ec_log_lookup("dsdedesdes");
+       testres |= EC_TEST_CHECK(logtype == -1,
+                               "lookup invalid name should return -1");
+       logtype = ec_log_lookup("log");
+       logname = ec_log_name(logtype);
+       testres |= EC_TEST_CHECK(logname != NULL &&
+                               !strcmp(logname, "log"),
+                               "cannot get log name\n");
+       logname = ec_log_name(-1);
+       testres |= EC_TEST_CHECK(logname != NULL &&
+                               !strcmp(logname, "unknown"),
+                               "cannot get invalid log name\n");
+       logname = ec_log_name(34324);
+       testres |= EC_TEST_CHECK(logname != NULL &&
+                               !strcmp(logname, "unknown"),
+                               "cannot get invalid log name\n");
+       level = ec_log_level_get();
+       ret = ec_log_level_set(2);
+       testres |= EC_TEST_CHECK(ret == 0 && ec_log_level_get() == 2,
+                               "cannot set log level\n");
+       ret = ec_log_level_set(10);
+       testres |= EC_TEST_CHECK(ret != 0,
+                               "should not be able to set log level\n");
+
+       ret = ec_log_fct_register(NULL, NULL);
+       ec_log_level_set(LOG_DEBUG);
+       EC_LOG(LOG_DEBUG, "test log\n");
+       ec_log_level_set(LOG_INFO);
+       EC_LOG(LOG_DEBUG, "test log (not displayed)\n");
+       ec_log_level_set(level);
+
+       ec_log_fct = prev_log_cb;
+       ec_log_opaque = prev_opaque;
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_log_test = {
+       .name = "log",
+       .test = ec_log_testcase,
+};
+
+EC_TEST_REGISTER(ec_log_test);
diff --git a/src/ecoli_malloc.c b/src/ecoli_malloc.c
new file mode 100644 (file)
index 0000000..5a022ae
--- /dev/null
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <ecoli_init.h>
+#include <ecoli_test.h>
+#include <ecoli_malloc.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+EC_LOG_TYPE_REGISTER(malloc);
+
+static int init_done = 0;
+
+struct ec_malloc_handler ec_malloc_handler;
+
+int ec_malloc_register(ec_malloc_t usr_malloc, ec_free_t usr_free,
+       ec_realloc_t usr_realloc)
+{
+       if (usr_malloc == NULL || usr_free == NULL || usr_realloc == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (init_done) {
+               errno = EBUSY;
+               return -1;
+       }
+
+       ec_malloc_handler.malloc = usr_malloc;
+       ec_malloc_handler.free = usr_free;
+       ec_malloc_handler.realloc = usr_realloc;
+
+       return 0;
+}
+
+void *__ec_malloc(size_t size, const char *file, unsigned int line)
+{
+       return ec_malloc_handler.malloc(size, file, line);
+}
+
+void *ec_malloc_func(size_t size)
+{
+       return ec_malloc(size);
+}
+
+void __ec_free(void *ptr, const char *file, unsigned int line)
+{
+       ec_malloc_handler.free(ptr, file, line);
+}
+
+void ec_free_func(void *ptr)
+{
+       ec_free(ptr);
+}
+
+void *__ec_calloc(size_t nmemb, size_t size, const char *file,
+       unsigned int line)
+{
+       void *ptr;
+       size_t total;
+
+       /* check overflow */
+       total = size * nmemb;
+       if (nmemb != 0 && size != (total / nmemb)) {
+               errno = ENOMEM;
+               return NULL;
+       }
+
+       ptr = __ec_malloc(total, file, line);
+       if (ptr == NULL)
+               return NULL;
+
+       memset(ptr, 0, total);
+       return ptr;
+}
+
+void *__ec_realloc(void *ptr, size_t size, const char *file, unsigned int line)
+{
+       return ec_malloc_handler.realloc(ptr, size, file, line);
+}
+
+void ec_realloc_func(void *ptr, size_t size)
+{
+       ec_realloc(ptr, size);
+}
+
+char *__ec_strdup(const char *s, const char *file, unsigned int line)
+{
+       size_t sz = strlen(s) + 1;
+       char *s2;
+
+       s2 = __ec_malloc(sz, file, line);
+       if (s2 == NULL)
+               return NULL;
+
+       memcpy(s2, s, sz);
+
+       return s2;
+}
+
+char *__ec_strndup(const char *s, size_t n, const char *file, unsigned int line)
+{
+       size_t sz = strnlen(s, n);
+       char *s2;
+
+       s2 = __ec_malloc(sz + 1, file, line);
+       if (s2 == NULL)
+               return NULL;
+
+       memcpy(s2, s, sz);
+       s2[sz] = '\0';
+
+       return s2;
+}
+
+static int ec_malloc_init_func(void)
+{
+       init_done = 1;
+       return 0;
+}
+
+static struct ec_init ec_malloc_init = {
+       .init = ec_malloc_init_func,
+       .priority = 40,
+};
+
+EC_INIT_REGISTER(ec_malloc_init);
+
+/* LCOV_EXCL_START */
+static int ec_malloc_testcase(void)
+{
+       int ret, testres = 0;
+       char *ptr, *ptr2;
+
+       ret = ec_malloc_register(NULL, NULL, NULL);
+       testres |= EC_TEST_CHECK(ret == -1,
+               "should not be able to register NULL malloc handlers");
+       ret = ec_malloc_register(__ec_malloc, __ec_free, __ec_realloc);
+       testres |= EC_TEST_CHECK(ret == -1,
+               "should not be able to register after init");
+
+       /* registration is tested in the test main.c */
+
+       ptr = ec_malloc(10);
+       if (ptr == NULL)
+               return -1;
+       memset(ptr, 0, 10);
+       ptr2 = ec_realloc(ptr, 20);
+       EC_TEST_CHECK(ptr2 != NULL, "cannot realloc ptr\n");
+       if (ptr2 == NULL)
+               ec_free(ptr);
+       else
+               ec_free(ptr2);
+       ptr = NULL;
+       ptr2 = NULL;
+
+       ptr = ec_malloc_func(10);
+       if (ptr == NULL)
+               return -1;
+       memset(ptr, 0, 10);
+       ec_free_func(ptr);
+       ptr = NULL;
+
+       ptr = ec_calloc(2, (size_t)-1);
+       EC_TEST_CHECK(ptr == NULL, "bad overflow check in ec_calloc\n");
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_malloc_test = {
+       .name = "malloc",
+       .test = ec_malloc_testcase,
+};
+
+EC_TEST_REGISTER(ec_malloc_test);
diff --git a/src/ecoli_murmurhash.c b/src/ecoli_murmurhash.c
new file mode 100644 (file)
index 0000000..7aafece
--- /dev/null
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdint.h>
+
+#include <ecoli_murmurhash.h>
+
+uint32_t ec_murmurhash3(const void *key, int len, uint32_t seed)
+{
+       const uint8_t *data = (const uint8_t *)key;
+       const uint8_t *tail;
+       const int nblocks = len / 4;
+       uint32_t h1 = seed;
+       uint32_t k1;
+       const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4);
+       int i;
+
+       for (i = -nblocks; i; i++) {
+               k1 = blocks[i];
+
+               h1 = ec_murmurhash3_add32(h1, k1);
+               h1 = ec_murmurhash3_mix32(h1);
+       }
+
+       tail = (const uint8_t *)(data + nblocks * 4);
+       k1 = 0;
+
+       switch(len & 3) {
+       case 3: k1 ^= tail[2] << 16;
+       case 2: k1 ^= tail[1] << 8;
+       case 1: k1 ^= tail[0];
+               h1 = ec_murmurhash3_add32(h1, k1);
+       };
+
+       /* finalization */
+       h1 ^= len;
+       h1 = ec_murmurhash3_fmix32(h1);
+       return h1;
+}
diff --git a/src/ecoli_node.c b/src/ecoli_node.c
new file mode 100644 (file)
index 0000000..9789102
--- /dev/null
@@ -0,0 +1,608 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_string.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_log.h>
+#include <ecoli_config.h>
+#include <ecoli_test.h>
+#include <ecoli_node.h>
+
+#include <ecoli_node_str.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_int.h>
+
+EC_LOG_TYPE_REGISTER(node);
+
+static struct ec_node_type_list node_type_list =
+       TAILQ_HEAD_INITIALIZER(node_type_list);
+
+const struct ec_node_type *
+ec_node_type_lookup(const char *name)
+{
+       struct ec_node_type *type;
+
+       TAILQ_FOREACH(type, &node_type_list, next) {
+               if (!strcmp(name, type->name))
+                       return type;
+       }
+
+       errno = ENOENT;
+       return NULL;
+}
+
+int ec_node_type_register(struct ec_node_type *type)
+{
+       EC_CHECK_ARG(type->size >= sizeof(struct ec_node), -1, EINVAL);
+
+       if (ec_node_type_lookup(type->name) != NULL) {
+               errno = EEXIST;
+               return -1;
+       }
+
+       TAILQ_INSERT_TAIL(&node_type_list, type, next);
+
+       return 0;
+}
+
+void ec_node_type_dump(FILE *out)
+{
+       struct ec_node_type *type;
+
+       TAILQ_FOREACH(type, &node_type_list, next)
+               fprintf(out, "%s\n", type->name);
+}
+
+struct ec_node *ec_node_from_type(const struct ec_node_type *type, const char *id)
+{
+       struct ec_node *node = NULL;
+
+       EC_LOG(EC_LOG_DEBUG, "create node type=%s id=%s\n",
+               type->name, id);
+       if (id == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       node = ec_calloc(1, type->size);
+       if (node == NULL)
+               goto fail;
+
+       node->type = type;
+       node->refcnt = 1;
+
+       // XXX check that id matches [_a-zA-Z][:-_0-9a-zA-Z]*
+       node->id = ec_strdup(id);
+       if (node->id == NULL)
+               goto fail;
+
+       if (ec_asprintf(&node->desc, "<%s>", type->name) < 0)
+               goto fail;
+
+       node->attrs = ec_keyval();
+       if (node->attrs == NULL)
+               goto fail;
+
+       if (type->init_priv != NULL) {
+               if (type->init_priv(node) < 0)
+                       goto fail;
+       }
+
+       return node;
+
+ fail:
+       if (node != NULL) {
+               ec_keyval_free(node->attrs);
+               ec_free(node->desc);
+               ec_free(node->id);
+       }
+       ec_free(node);
+
+       return NULL;
+}
+
+const struct ec_config_schema *
+ec_node_type_schema(const struct ec_node_type *type)
+{
+       return type->schema;
+}
+
+const char *
+ec_node_type_name(const struct ec_node_type *type)
+{
+       return type->name;
+}
+
+struct ec_node *ec_node(const char *typename, const char *id)
+{
+       const struct ec_node_type *type;
+
+       type = ec_node_type_lookup(typename);
+       if (type == NULL) {
+               EC_LOG(EC_LOG_ERR, "type=%s does not exist\n",
+                       typename);
+               return NULL;
+       }
+
+       return ec_node_from_type(type, id);
+}
+
+static void count_references(struct ec_node *node, unsigned int refs)
+{
+       struct ec_node *child;
+       size_t i, n;
+       int ret;
+
+       if (node->free.state == EC_NODE_FREE_STATE_TRAVERSED) {
+               node->free.refcnt += refs;
+               return;
+       }
+       node->free.refcnt = refs;
+       node->free.state = EC_NODE_FREE_STATE_TRAVERSED;
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               count_references(child, refs);
+       }
+}
+
+static void mark_freeable(struct ec_node *node, enum ec_node_free_state mark)
+{
+       struct ec_node *child;
+       unsigned int refs;
+       size_t i, n;
+       int ret;
+
+       if (mark == node->free.state)
+               return;
+
+       if (node->refcnt > node->free.refcnt)
+               mark = EC_NODE_FREE_STATE_NOT_FREEABLE;
+       assert(node->refcnt >= node->free.refcnt);
+       node->free.state = mark;
+
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               mark_freeable(child, mark);
+       }
+}
+
+static void reset_mark(struct ec_node *node)
+{
+       struct ec_node *child;
+       unsigned int refs;
+       size_t i, n;
+       int ret;
+
+       if (node->free.state == EC_NODE_FREE_STATE_NONE)
+               return;
+
+       node->free.state = EC_NODE_FREE_STATE_NONE;
+       node->free.refcnt = 0;
+
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               reset_mark(child);
+       }
+}
+
+/* free a node, taking care of loops in the node graph */
+void ec_node_free(struct ec_node *node)
+{
+       size_t n;
+
+       if (node == NULL)
+               return;
+
+       assert(node->refcnt > 0);
+
+       if (node->free.state == EC_NODE_FREE_STATE_NONE &&
+                       node->refcnt != 1) {
+
+               /* Traverse the node tree starting from this node, and for each
+                * node, count the number of reachable references. Then, all
+                * nodes whose reachable references == total reference are
+                * marked as freeable, and other are marked as unfreeable. Any
+                * node reachable from an unfreeable node is also marked as
+                * unfreeable. */
+               if (node->free.state == EC_NODE_FREE_STATE_NONE) {
+                       count_references(node, 1);
+                       mark_freeable(node, EC_NODE_FREE_STATE_FREEABLE);
+               }
+       }
+
+       if (node->free.state == EC_NODE_FREE_STATE_NOT_FREEABLE) {
+               node->refcnt--;
+               reset_mark(node);
+               return;
+       }
+
+       if (node->free.state != EC_NODE_FREE_STATE_FREEING) {
+               node->free.state = EC_NODE_FREE_STATE_FREEING;
+
+               /* children will be freed by config_free() and free_priv() */
+               ec_config_free(node->config);
+               node->config = NULL;
+               n = ec_node_get_children_count(node);
+               assert(n == 0 || node->type->free_priv != NULL);
+               if (node->type->free_priv != NULL)
+                       node->type->free_priv(node);
+               ec_free(node->id);
+               ec_free(node->desc);
+               ec_keyval_free(node->attrs);
+       }
+
+       node->refcnt--;
+       if (node->refcnt != 0)
+               return;
+
+       node->free.state = EC_NODE_FREE_STATE_NONE;
+       node->free.refcnt = 0;
+
+       ec_free(node);
+}
+
+struct ec_node *ec_node_clone(struct ec_node *node)
+{
+       if (node != NULL)
+               node->refcnt++;
+       return node;
+}
+
+size_t ec_node_get_children_count(const struct ec_node *node)
+{
+       if (node->type->get_children_count == NULL)
+               return 0;
+       return node->type->get_children_count(node);
+}
+
+int
+ec_node_get_child(const struct ec_node *node, size_t i,
+       struct ec_node **child, unsigned int *refs)
+{
+       *child = NULL;
+       *refs = 0;
+       if (node->type->get_child == NULL)
+               return -1;
+       return node->type->get_child(node, i, child, refs);
+}
+
+int
+ec_node_set_config(struct ec_node *node, struct ec_config *config)
+{
+       if (node->type->schema == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+       if (ec_config_validate(config, node->type->schema) < 0)
+               goto fail;
+       if (node->type->set_config != NULL) {
+               if (node->type->set_config(node, config) < 0)
+                       goto fail;
+       }
+
+       ec_config_free(node->config);
+       node->config = config;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       return -1;
+}
+
+const struct ec_config *ec_node_get_config(struct ec_node *node)
+{
+       return node->config;
+}
+
+struct ec_node *ec_node_find(struct ec_node *node, const char *id)
+{
+       struct ec_node *child, *retnode;
+       const char *node_id = ec_node_id(node);
+       unsigned int refs;
+       size_t i, n;
+       int ret;
+
+       if (id != NULL && node_id != NULL && !strcmp(node_id, id))
+               return node;
+
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               retnode = ec_node_find(child, id);
+               if (retnode != NULL)
+                       return retnode;
+       }
+
+       return NULL;
+}
+
+const struct ec_node_type *ec_node_type(const struct ec_node *node)
+{
+       return node->type;
+}
+
+struct ec_keyval *ec_node_attrs(const struct ec_node *node)
+{
+       return node->attrs;
+}
+
+const char *ec_node_id(const struct ec_node *node)
+{
+       return node->id;
+}
+
+static void __ec_node_dump(FILE *out,
+       const struct ec_node *node, size_t indent, struct ec_keyval *dict)
+{
+       const char *id, *typename;
+       struct ec_node *child;
+       unsigned int refs;
+       char buf[32];
+       size_t i, n;
+       int ret;
+
+       id = ec_node_id(node);
+       typename = node->type->name;
+
+       snprintf(buf, sizeof(buf), "%p", node);
+       if (ec_keyval_has_key(dict, buf)) {
+               fprintf(out, "%*s" "type=%s id=%s %p... (loop)\n",
+                       (int)indent * 4, "", typename, id, node);
+               return;
+       }
+
+       ec_keyval_set(dict, buf, NULL, NULL);
+       fprintf(out, "%*s" "type=%s id=%s %p refs=%u free_state=%d free_refs=%d\n",
+               (int)indent * 4, "", typename, id, node, node->refcnt,
+               node->free.state, node->free.refcnt);
+
+       n = ec_node_get_children_count(node);
+       for (i = 0; i < n; i++) {
+               ret = ec_node_get_child(node, i, &child, &refs);
+               assert(ret == 0);
+               __ec_node_dump(out, child, indent + 1, dict);
+       }
+}
+
+/* XXX this is too much debug-oriented, we should have a parameter or 2 funcs */
+void ec_node_dump(FILE *out, const struct ec_node *node)
+{
+       struct ec_keyval *dict = NULL;
+
+       fprintf(out, "------------------- node dump:\n");
+
+       if (node == NULL) {
+               fprintf(out, "node is NULL\n");
+               return;
+       }
+
+       dict = ec_keyval();
+       if (dict == NULL)
+               goto fail;
+
+       __ec_node_dump(out, node, 0, dict);
+
+       ec_keyval_free(dict);
+       return;
+
+fail:
+       ec_keyval_free(dict);
+       EC_LOG(EC_LOG_ERR, "failed to dump node\n");
+}
+
+const char *ec_node_desc(const struct ec_node *node)
+{
+       if (node->type->desc != NULL)
+               return node->type->desc(node);
+
+       return node->desc;
+}
+
+int ec_node_check_type(const struct ec_node *node,
+               const struct ec_node_type *type)
+{
+       if (strcmp(node->type->name, type->name)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       return 0;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_testcase(void)
+{
+       struct ec_node *node = NULL, *expr = NULL;
+       struct ec_node *expr2 = NULL, *val = NULL, *op = NULL, *seq = NULL;
+       const struct ec_node_type *type;
+       struct ec_node *child;
+       unsigned int refs;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+       int testres = 0;
+       int ret;
+
+       node = EC_NODE_SEQ(EC_NO_ID,
+                       ec_node_str("id_x", "x"),
+                       ec_node_str("id_y", "y"));
+       if (node == NULL)
+               goto fail;
+
+       ec_node_clone(node);
+       ec_node_free(node);
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_node_dump(f, node);
+       ec_node_type_dump(f);
+       ec_node_dump(f, NULL);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "type=seq id=no-id"), "bad dump\n");
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "type=str id=id_x") &&
+               strstr(strstr(buf, "type=str id=id_x") + 1,
+                       "type=str id=id_y"),
+               "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       testres |= EC_TEST_CHECK(
+               !strcmp(ec_node_type(node)->name, "seq") &&
+               !strcmp(ec_node_id(node), EC_NO_ID) &&
+               !strcmp(ec_node_desc(node), "<seq>"),
+               "bad child 0");
+
+       testres |= EC_TEST_CHECK(
+               ec_node_get_children_count(node) == 2,
+               "bad children count\n");
+       ret = ec_node_get_child(node, 0, &child, &refs);
+       testres |= EC_TEST_CHECK(ret == 0 &&
+               child != NULL &&
+               !strcmp(ec_node_type(child)->name, "str") &&
+               !strcmp(ec_node_id(child), "id_x"),
+               "bad child 0");
+       ret = ec_node_get_child(node, 1, &child, &refs);
+       testres |= EC_TEST_CHECK(ret == 0 &&
+               child != NULL &&
+               !strcmp(ec_node_type(child)->name, "str") &&
+               !strcmp(ec_node_id(child), "id_y"),
+               "bad child 1");
+       ret = ec_node_get_child(node, 2, &child, &refs);
+       testres |= EC_TEST_CHECK(ret != 0,
+               "ret should be != 0");
+       testres |= EC_TEST_CHECK(child == NULL,
+               "child 2 should be NULL");
+
+       child = ec_node_find(node, "id_x");
+       testres |= EC_TEST_CHECK(child != NULL &&
+               !strcmp(ec_node_type(child)->name, "str") &&
+               !strcmp(ec_node_id(child), "id_x") &&
+               !strcmp(ec_node_desc(child), "x"),
+               "bad child id_x");
+       child = ec_node_find(node, "id_dezdex");
+       testres |= EC_TEST_CHECK(child == NULL,
+               "child with wrong id should be NULL");
+
+       ret = ec_keyval_set(ec_node_attrs(node), "key", "val", NULL);
+       testres |= EC_TEST_CHECK(ret == 0,
+               "cannot set node attribute\n");
+
+       type = ec_node_type_lookup("seq");
+       testres |= EC_TEST_CHECK(type != NULL &&
+               ec_node_check_type(node, type) == 0,
+               "cannot get seq node type");
+       type = ec_node_type_lookup("str");
+       testres |= EC_TEST_CHECK(type != NULL &&
+               ec_node_check_type(node, type) < 0,
+               "node type should not be str");
+
+       ec_node_free(node);
+       node = NULL;
+
+       node = ec_node("deznuindez", EC_NO_ID);
+       testres |= EC_TEST_CHECK(node == NULL,
+                       "should not be able to create node\n");
+
+       /* test loop */
+       expr = ec_node("or", EC_NO_ID);
+       val = ec_node_int(EC_NO_ID, 0, 10, 0);
+       op = ec_node_str(EC_NO_ID, "!");
+       seq = EC_NODE_SEQ(EC_NO_ID,
+                       op,
+                       ec_node_clone(expr));
+       op = NULL;
+       if (expr == NULL || val == NULL || seq == NULL)
+               goto fail;
+       if (ec_node_or_add(expr, ec_node_clone(seq)) < 0)
+               goto fail;
+       ec_node_free(seq);
+       seq = NULL;
+       if (ec_node_or_add(expr, ec_node_clone(val)) < 0)
+               goto fail;
+       ec_node_free(val);
+       val = NULL;
+
+       testres |= EC_TEST_CHECK_PARSE(expr, 1, "1");
+       testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1");
+       testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!");
+
+       ec_node_free(expr);
+       expr = NULL;
+
+       /* same loop test, but keep some refs (released later) */
+       expr = ec_node("or", EC_NO_ID);
+       ec_node_clone(expr);
+       expr2 = expr;
+       val = ec_node_int(EC_NO_ID, 0, 10, 0);
+       op = ec_node_str(EC_NO_ID, "!");
+       seq = EC_NODE_SEQ(EC_NO_ID,
+                       op,
+                       ec_node_clone(expr));
+       op = NULL;
+       if (expr == NULL || val == NULL || seq == NULL)
+               goto fail;
+       if (ec_node_or_add(expr, ec_node_clone(seq)) < 0)
+               goto fail;
+       ec_node_free(seq);
+       seq = NULL;
+       if (ec_node_or_add(expr, ec_node_clone(val)) < 0)
+               goto fail;
+
+       testres |= EC_TEST_CHECK_PARSE(expr, 1, "1");
+       testres |= EC_TEST_CHECK_PARSE(expr, 3, "!", "!", "1");
+       testres |= EC_TEST_CHECK_PARSE(expr, -1, "!", "!", "!");
+
+       ec_node_free(expr2);
+       expr2 = NULL;
+       ec_node_free(val);
+       val = NULL;
+       ec_node_free(expr);
+       expr = NULL;
+
+       return testres;
+
+fail:
+       ec_node_free(expr);
+       ec_node_free(expr2);
+       ec_node_free(val);
+       ec_node_free(seq);
+       ec_node_free(node);
+       if (f != NULL)
+               fclose(f);
+       free(buf);
+
+       assert(errno != 0);
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_test = {
+       .name = "node",
+       .test = ec_node_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_test);
diff --git a/src/ecoli_node_any.c b/src/ecoli_node_any.c
new file mode 100644 (file)
index 0000000..9166dbb
--- /dev/null
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_keyval.h>
+#include <ecoli_config.h>
+#include <ecoli_node_any.h>
+
+EC_LOG_TYPE_REGISTER(node_any);
+
+struct ec_node_any {
+       struct ec_node gen;
+       char *attr_name;
+};
+
+static int ec_node_any_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_any *node = (struct ec_node_any *)gen_node;
+       const struct ec_keyval *attrs;
+
+       (void)state;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+       if (node->attr_name != NULL) {
+               attrs = ec_strvec_get_attrs(strvec, 0);
+               if (attrs == NULL || !ec_keyval_has_key(attrs, node->attr_name))
+                       return EC_PARSE_NOMATCH;
+       }
+
+       return 1;
+}
+
+static void ec_node_any_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_any *node = (struct ec_node_any *)gen_node;
+
+       ec_free(node->attr_name);
+}
+
+static const struct ec_config_schema ec_node_any_schema[] = {
+       {
+               .key = "attr",
+               .desc = "The optional attribute name to attach.",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_any_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_any *node = (struct ec_node_any *)gen_node;
+       const struct ec_config *value = NULL;
+       char *s = NULL;
+
+       value = ec_config_dict_get(config, "attr");
+       if (value != NULL) {
+               s = ec_strdup(value->string);
+               if (s == NULL)
+                       goto fail;
+       }
+
+       ec_free(node->attr_name);
+       node->attr_name = s;
+
+       return 0;
+
+fail:
+       ec_free(s);
+       return -1;
+}
+
+static struct ec_node_type ec_node_any_type = {
+       .name = "any",
+       .schema = ec_node_any_schema,
+       .set_config = ec_node_any_set_config,
+       .parse = ec_node_any_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_any),
+       .free_priv = ec_node_any_free_priv,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_any_type);
+
+/* LCOV_EXCL_START */
+static int ec_node_any_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("any", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+       ec_node_free(node);
+
+       /* never completes */
+       node = ec_node("any", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_any_test = {
+       .name = "node_any",
+       .test = ec_node_any_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_any_test);
diff --git a/src/ecoli_node_cmd.c b/src/ecoli_node_cmd.c
new file mode 100644 (file)
index 0000000..3b56084
--- /dev/null
@@ -0,0 +1,682 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/queue.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_helper.h>
+#include <ecoli_node_expr.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_subset.h>
+#include <ecoli_node_int.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_node_option.h>
+#include <ecoli_node_re.h>
+#include <ecoli_node_re_lex.h>
+#include <ecoli_node_cmd.h>
+
+EC_LOG_TYPE_REGISTER(node_cmd);
+
+struct ec_node_cmd {
+       struct ec_node gen;
+       char *cmd_str;           /* the command string. */
+       struct ec_node *cmd;     /* the command node. */
+       struct ec_node *parser;  /* the expression parser. */
+       struct ec_node *expr;    /* the expression parser without lexer. */
+       struct ec_node **table;  /* table of node referenced in command. */
+       unsigned int len;        /* len of the table. */
+};
+
+/* passed as user context to expression parser */
+struct ec_node_cmd_ctx {
+       struct ec_node **table;
+       unsigned int len;
+};
+
+static int
+ec_node_cmd_eval_var(void **result, void *userctx,
+       const struct ec_parse *var)
+{
+       const struct ec_strvec *vec;
+       struct ec_node_cmd_ctx *ctx = userctx;
+       struct ec_node *eval = NULL;
+       const char *str, *id;
+       unsigned int i;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(var);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+       str = ec_strvec_val(vec, 0);
+
+       for (i = 0; i < ctx->len; i++) {
+               id = ec_node_id(ctx->table[i]);
+               if (id == NULL)
+                       continue;
+               if (strcmp(str, id))
+                       continue;
+               /* if id matches, use a node provided by the user... */
+               eval = ec_node_clone(ctx->table[i]);
+               if (eval == NULL)
+                       return -1;
+               break;
+       }
+
+       /* ...or create a string node */
+       if (eval == NULL) {
+               eval = ec_node_str(EC_NO_ID, str);
+               if (eval == NULL)
+                       return -1;
+       }
+
+       *result = eval;
+
+       return 0;
+}
+
+static int
+ec_node_cmd_eval_pre_op(void **result, void *userctx, void *operand,
+       const struct ec_parse *operator)
+{
+       (void)result;
+       (void)userctx;
+       (void)operand;
+       (void)operator;
+
+       errno = EINVAL;
+       return -1;
+}
+
+static int
+ec_node_cmd_eval_post_op(void **result, void *userctx, void *operand,
+       const struct ec_parse *operator)
+{
+       const struct ec_strvec *vec;
+       struct ec_node *in = operand;;
+       struct ec_node *out = NULL;;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "*")) {
+               out = ec_node_many(EC_NO_ID,
+                               ec_node_clone(in), 0, 0);
+               if (out == NULL)
+                       return -1;
+               ec_node_free(in);
+               *result = out;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       return 0;
+}
+
+static int
+ec_node_cmd_eval_bin_op(void **result, void *userctx, void *operand1,
+       const struct ec_parse *operator, void *operand2)
+
+{
+       const struct ec_strvec *vec;
+       struct ec_node *out = NULL;
+       struct ec_node *in1 = operand1;
+       struct ec_node *in2 = operand2;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) > 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (ec_strvec_len(vec) == 0) {
+               if (!strcmp(in1->type->name, "seq")) {
+                       if (ec_node_seq_add(in1, ec_node_clone(in2)) < 0)
+                               return -1;
+                       ec_node_free(in2);
+                       *result = in1;
+               } else {
+                       out = EC_NODE_SEQ(EC_NO_ID, ec_node_clone(in1),
+                                       ec_node_clone(in2));
+                       if (out == NULL)
+                               return -1;
+                       ec_node_free(in1);
+                       ec_node_free(in2);
+                       *result = out;
+               }
+       } else if (!strcmp(ec_strvec_val(vec, 0), "|")) {
+               if (!strcmp(in2->type->name, "or")) {
+                       if (ec_node_or_add(in2, ec_node_clone(in1)) < 0)
+                               return -1;
+                       ec_node_free(in1);
+                       *result = in2;
+               } else if (!strcmp(in1->type->name, "or")) {
+                       if (ec_node_or_add(in1, ec_node_clone(in2)) < 0)
+                               return -1;
+                       ec_node_free(in2);
+                       *result = in1;
+               } else {
+                       out = EC_NODE_OR(EC_NO_ID, ec_node_clone(in1),
+                                       ec_node_clone(in2));
+                       if (out == NULL)
+                               return -1;
+                       ec_node_free(in1);
+                       ec_node_free(in2);
+                       *result = out;
+               }
+       } else if (!strcmp(ec_strvec_val(vec, 0), ",")) {
+               if (!strcmp(in2->type->name, "subset")) {
+                       if (ec_node_subset_add(in2, ec_node_clone(in1)) < 0)
+                               return -1;
+                       ec_node_free(in1);
+                       *result = in2;
+               } else if (!strcmp(in1->type->name, "subset")) {
+                       if (ec_node_subset_add(in1, ec_node_clone(in2)) < 0)
+                               return -1;
+                       ec_node_free(in2);
+                       *result = in1;
+               } else {
+                       out = EC_NODE_SUBSET(EC_NO_ID, ec_node_clone(in1),
+                                       ec_node_clone(in2));
+                       if (out == NULL)
+                               return -1;
+                       ec_node_free(in1);
+                       ec_node_free(in2);
+                       *result = out;
+               }
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       return 0;
+}
+
+static int
+ec_node_cmd_eval_parenthesis(void **result, void *userctx,
+       const struct ec_parse *open_paren,
+       const struct ec_parse *close_paren,
+       void *value)
+{
+       const struct ec_strvec *vec;
+       struct ec_node *in = value;;
+       struct ec_node *out = NULL;;
+
+       (void)userctx;
+       (void)close_paren;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(open_paren);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "[")) {
+               out = ec_node_option(EC_NO_ID, ec_node_clone(in));
+               if (out == NULL)
+                       return -1;
+               ec_node_free(in);
+       } else if (!strcmp(ec_strvec_val(vec, 0), "(")) {
+               out = in;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       *result = out;
+
+       return 0;
+}
+
+static void
+ec_node_cmd_eval_free(void *result, void *userctx)
+{
+       (void)userctx;
+       ec_free(result);
+}
+
+static const struct ec_node_expr_eval_ops expr_ops = {
+       .eval_var = ec_node_cmd_eval_var,
+       .eval_pre_op = ec_node_cmd_eval_pre_op,
+       .eval_post_op = ec_node_cmd_eval_post_op,
+       .eval_bin_op = ec_node_cmd_eval_bin_op,
+       .eval_parenthesis = ec_node_cmd_eval_parenthesis,
+       .eval_free = ec_node_cmd_eval_free,
+};
+
+static struct ec_node *
+ec_node_cmd_build_expr(void)
+{
+       struct ec_node *expr = NULL;
+       int ret;
+
+       /* build the expression parser */
+       expr = ec_node("expr", "expr");
+       if (expr == NULL)
+               goto fail;
+       ret = ec_node_expr_set_val_node(expr, ec_node_re(EC_NO_ID,
+                                       "[a-zA-Z0-9]+"));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, ","));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_bin_op(expr, ec_node_str(EC_NO_ID, "|"));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_bin_op(expr, ec_node("empty", EC_NO_ID));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "+"));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_post_op(expr, ec_node_str(EC_NO_ID, "*"));
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "["),
+               ec_node_str(EC_NO_ID, "]"));
+       if (ret < 0)
+               goto fail;
+       ec_node_expr_add_parenthesis(expr, ec_node_str(EC_NO_ID, "("),
+               ec_node_str(EC_NO_ID, ")"));
+       if (ret < 0)
+               goto fail;
+
+       return expr;
+
+fail:
+       ec_node_free(expr);
+       return NULL;
+}
+
+static struct ec_node *
+ec_node_cmd_build_parser(struct ec_node *expr)
+{
+       struct ec_node *lex = NULL;
+       int ret;
+
+       /* prepend a lexer to the expression node */
+       lex = ec_node_re_lex(EC_NO_ID, ec_node_clone(expr));
+       if (lex == NULL)
+               goto fail;
+
+       ret = ec_node_re_lex_add(lex, "[a-zA-Z0-9]+", 1, NULL);
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_re_lex_add(lex, "[*|,()]", 1, NULL);
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_re_lex_add(lex, "\\[", 1, NULL);
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_re_lex_add(lex, "\\]", 1, NULL);
+       if (ret < 0)
+               goto fail;
+       ret = ec_node_re_lex_add(lex, "[         ]+", 0, NULL);
+       if (ret < 0)
+               goto fail;
+
+       return lex;
+
+fail:
+       ec_node_free(lex);
+
+       return NULL;
+}
+
+static struct ec_node *
+ec_node_cmd_build(struct ec_node_cmd *node, const char *cmd_str,
+       struct ec_node **table, size_t len)
+{
+       struct ec_node_cmd_ctx ctx = { table, len };
+       struct ec_parse *p = NULL;
+       void *result;
+       int ret;
+
+       /* parse the command expression */
+       p = ec_node_parse(node->parser, cmd_str);
+       if (p == NULL)
+               goto fail;
+
+       if (!ec_parse_matches(p)) {
+               errno = EINVAL;
+               goto fail;
+       }
+       if (!ec_parse_has_child(p)) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       ret = ec_node_expr_eval(&result, node->expr,
+                               ec_parse_get_first_child(p),
+                               &expr_ops, &ctx);
+       if (ret < 0)
+               goto fail;
+
+       ec_parse_free(p);
+       return result;
+
+fail:
+       ec_parse_free(p);
+       return NULL;
+}
+
+static int
+ec_node_cmd_parse(const struct ec_node *gen_node, struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+
+       return ec_node_parse_child(node->cmd, state, strvec);
+}
+
+static int
+ec_node_cmd_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+
+       return ec_node_complete_child(node->cmd, comp, strvec);
+}
+
+static void ec_node_cmd_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+       size_t i;
+
+       ec_free(node->cmd_str);
+       node->cmd_str = NULL;
+       ec_node_free(node->expr);
+       node->expr = NULL;
+       ec_node_free(node->parser);
+       node->parser = NULL;
+       ec_node_free(node->cmd);
+       node->cmd = NULL;
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = NULL;
+       node->len = 0;
+}
+
+static const struct ec_config_schema ec_node_cmd_subschema[] = {
+       {
+               .desc = "A child node whose id is referenced in the expression.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema ec_node_cmd_schema[] = {
+       {
+               .key = "expr",
+               .desc = "The expression to match. Supported operators "
+               "are or '|', list ',', many '+', many-or-zero '*', "
+               "option '[]', group '()'. An identifier (alphanumeric) can "
+               "reference a node whose node_id matches. Else it is "
+               "interpreted as ec_node_str() matching this string. "
+               "Example: command [option] (subset1, subset2) x|y",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .key = "children",
+               .desc = "The list of children nodes.",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = ec_node_cmd_subschema,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_cmd_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+       const struct ec_config *expr = NULL;
+       struct ec_node *cmd = NULL;
+       struct ec_node **table = NULL;
+       char *cmd_str = NULL;
+       size_t len = 0, i;
+
+       /* retrieve config locally */
+       expr = ec_config_dict_get(config, "expr");
+       if (expr == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       table = ec_node_config_node_list_to_table(
+               ec_config_dict_get(config, "children"), &len);
+       if (table == NULL)
+               goto fail;
+
+       cmd_str = ec_strdup(expr->string);
+       if (cmd_str == NULL)
+               goto fail;
+
+       /* parse expression to build the cmd child node */
+       cmd = ec_node_cmd_build(node, cmd_str, table, len);
+       if (cmd == NULL)
+               goto fail;
+
+       /* ok, store the config */
+       ec_node_free(node->cmd);
+       node->cmd = cmd;
+       ec_free(node->cmd_str);
+       node->cmd_str = cmd_str;
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = table;
+       node->len = len;
+
+       return 0;
+
+fail:
+       for (i = 0; i < len; i++)
+               ec_node_free(table[i]);
+       ec_free(table);
+       ec_free(cmd_str);
+       ec_node_free(cmd);
+       return -1;
+}
+
+static size_t
+ec_node_cmd_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+
+       if (node->cmd == NULL)
+               return 0;
+       return 1;
+}
+
+static int
+ec_node_cmd_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_cmd *node = (struct ec_node_cmd *)gen_node;
+
+       if (i > 0)
+               return -1;
+
+       *child = node->cmd;
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_cmd_type = {
+       .name = "cmd",
+       .schema = ec_node_cmd_schema,
+       .set_config = ec_node_cmd_set_config,
+       .parse = ec_node_cmd_parse,
+       .complete = ec_node_cmd_complete,
+       .size = sizeof(struct ec_node_cmd),
+       .free_priv = ec_node_cmd_free_priv,
+       .get_children_count = ec_node_cmd_get_children_count,
+       .get_child = ec_node_cmd_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_cmd_type);
+
+struct ec_node *__ec_node_cmd(const char *id, const char *cmd, ...)
+{
+       struct ec_config *config = NULL, *children = NULL;
+       struct ec_node *gen_node = NULL;
+       struct ec_node_cmd *node = NULL;
+       va_list ap;
+       int ret;
+
+       /* this block must stay first, it frees the nodes on error */
+       va_start(ap, cmd);
+       children = ec_node_config_node_list_from_vargs(ap);
+       va_end(ap);
+       if (children == NULL)
+               goto fail;
+
+       gen_node = ec_node_from_type(&ec_node_cmd_type, id);
+       if (gen_node == NULL)
+               goto fail;
+       node = (struct ec_node_cmd *)gen_node;
+
+       node->expr = ec_node_cmd_build_expr();
+       if (node->expr == NULL)
+               goto fail;
+
+       node->parser = ec_node_cmd_build_parser(node->expr);
+       if (node->parser == NULL)
+               goto fail;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       if (ec_config_dict_set(config, "expr", ec_config_string(cmd)) < 0)
+               goto fail;
+
+       if (ec_config_dict_set(config, "children", children) < 0) {
+               children = NULL; /* freed */
+               goto fail;
+       }
+       children = NULL;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return gen_node;
+
+fail:
+       ec_node_free(gen_node); /* will also free added children */
+       ec_config_free(children);
+       ec_config_free(config);
+
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_cmd_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = EC_NODE_CMD(EC_NO_ID,
+               "command [option] (subset1, subset2, subset3, subset4) x|y z*",
+               ec_node_int("x", 0, 10, 10),
+               ec_node_int("y", 20, 30, 10)
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "subset1", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 4, "command", "subset3", "subset2",
+                               "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "subset2", "subset3",
+                               "subset1", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 6, "command", "subset3", "subset1",
+                               "subset4", "subset2", "4");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "command", "23");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "command", "option", "23");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "command", "option", "23",
+                               "z", "z");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "command", "15");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       ec_node_free(node);
+
+       node = EC_NODE_CMD(EC_NO_ID, "good morning [count] bob|bobby|michael",
+                       ec_node_int("count", 0, 10, 10));
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 4, "good", "morning", "1", "bob");
+
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "good", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "g", EC_NODE_ENDLIST,
+               "good", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "good", "morning", "", EC_NODE_ENDLIST,
+               "bob", "bobby", "michael", EC_NODE_ENDLIST);
+
+       ec_node_free(node);
+
+       node = EC_NODE_CMD(EC_NO_ID, "[foo [bar]]");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "x");
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_cmd_test = {
+       .name = "node_cmd",
+       .test = ec_node_cmd_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_cmd_test);
diff --git a/src/ecoli_node_dynamic.c b/src/ecoli_node_dynamic.c
new file mode 100644 (file)
index 0000000..8a3edf3
--- /dev/null
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2017, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_string.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_many.h>
+
+#include "ecoli_node_dynamic.h"
+
+EC_LOG_TYPE_REGISTER(node_dynamic);
+
+struct ec_node_dynamic {
+       struct ec_node gen;
+       ec_node_dynamic_build_t build;
+       void *opaque;
+};
+
+static int
+ec_node_dynamic_parse(const struct ec_node *gen_node,
+               struct ec_parse *parse,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node;
+       struct ec_node *child = NULL;
+       void (*node_free)(struct ec_node *) = ec_node_free;
+       char key[64];
+       int ret = -1;
+
+       child = node->build(parse, node->opaque);
+       if (child == NULL)
+               goto fail;
+
+       /* add the node pointer in the attributes, so it will be freed
+        * when parse is freed */
+       snprintf(key, sizeof(key), "_dyn_%p", child);
+       ret = ec_keyval_set(ec_parse_get_attrs(parse), key, child,
+                       (void *)node_free);
+       if (ret < 0) {
+               child = NULL; /* already freed */
+               goto fail;
+       }
+
+       return ec_node_parse_child(child, parse, strvec);
+
+fail:
+       ec_node_free(child);
+       return ret;
+}
+
+static int
+ec_node_dynamic_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_dynamic *node = (struct ec_node_dynamic *)gen_node;
+       struct ec_parse *parse;
+       struct ec_node *child = NULL;
+       void (*node_free)(struct ec_node *) = ec_node_free;
+       char key[64];
+       int ret = -1;
+
+       parse = ec_comp_get_state(comp);
+       child = node->build(parse, node->opaque);
+       if (child == NULL)
+               goto fail;
+
+       /* add the node pointer in the attributes, so it will be freed
+        * when parse is freed */
+       snprintf(key, sizeof(key), "_dyn_%p", child);
+       ret = ec_keyval_set(comp->attrs, key, child,
+                       (void *)node_free);
+       if (ret < 0) {
+               child = NULL; /* already freed */
+               goto fail;
+       }
+
+       return ec_node_complete_child(child, comp, strvec);
+
+fail:
+       ec_node_free(child);
+       return ret;
+}
+
+static struct ec_node_type ec_node_dynamic_type = {
+       .name = "dynamic",
+       .parse = ec_node_dynamic_parse,
+       .complete = ec_node_dynamic_complete,
+       .size = sizeof(struct ec_node_dynamic),
+};
+
+struct ec_node *
+ec_node_dynamic(const char *id, ec_node_dynamic_build_t build, void *opaque)
+{
+       struct ec_node *gen_node = NULL;
+       struct ec_node_dynamic *node;
+
+       if (build == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       gen_node = ec_node_from_type(&ec_node_dynamic_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       node = (struct ec_node_dynamic *)gen_node;
+       node->build = build;
+       node->opaque = opaque;
+
+       return gen_node;
+
+fail:
+       ec_node_free(gen_node);
+       return NULL;
+
+}
+
+EC_NODE_TYPE_REGISTER(ec_node_dynamic_type);
+
+static struct ec_node *
+build_counter(struct ec_parse *parse, void *opaque)
+{
+       const struct ec_node *node;
+       struct ec_parse *iter;
+       unsigned int count = 0;
+       char buf[32];
+
+       (void)opaque;
+       for (iter = ec_parse_get_root(parse); iter != NULL;
+            iter = ec_parse_iter_next(iter)) {
+               node = ec_parse_get_node(iter);
+               if (node->id && !strcmp(node->id, "my-id"))
+                       count++;
+       }
+       snprintf(buf, sizeof(buf), "count-%u", count);
+
+       return ec_node_str("my-id", buf);
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_dynamic_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_many(EC_NO_ID,
+                       ec_node_dynamic(EC_NO_ID, build_counter, NULL),
+                       1, 3);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "count-0", "count-1", "count-2");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "count-0", "count-0");
+
+       /* test completion */
+
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "c", EC_NODE_ENDLIST,
+               "count-0", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "count-0", "", EC_NODE_ENDLIST,
+               "count-1", EC_NODE_ENDLIST,
+               "count-1");
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_dynamic_test = {
+       .name = "node_dynamic",
+       .test = ec_node_dynamic_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_dynamic_test);
diff --git a/src/ecoli_node_empty.c b/src/ecoli_node_empty.c
new file mode 100644 (file)
index 0000000..6ce76e8
--- /dev/null
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_empty.h>
+
+EC_LOG_TYPE_REGISTER(node_empty);
+
+struct ec_node_empty {
+       struct ec_node gen;
+};
+
+static int ec_node_empty_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)state;
+       (void)strvec;
+       return 0;
+}
+
+static struct ec_node_type ec_node_empty_type = {
+       .name = "empty",
+       .parse = ec_node_empty_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_empty),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_empty_type);
+
+/* LCOV_EXCL_START */
+static int ec_node_empty_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("empty", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "foo", "bar");
+       ec_node_free(node);
+
+       /* never completes */
+       node = ec_node("empty", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_empty_test = {
+       .name = "node_empty",
+       .test = ec_node_empty_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_empty_test);
diff --git a/src/ecoli_node_expr.c b/src/ecoli_node_expr.c
new file mode 100644 (file)
index 0000000..3b47d8c
--- /dev/null
@@ -0,0 +1,617 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_expr.h>
+
+EC_LOG_TYPE_REGISTER(node_expr);
+
+struct ec_node_expr {
+       struct ec_node gen;
+
+       /* the built node */
+       struct ec_node *child;
+
+       /* the configuration nodes */
+       struct ec_node *val_node;
+       struct ec_node **bin_ops;
+       unsigned int bin_ops_len;
+       struct ec_node **pre_ops;
+       unsigned int pre_ops_len;
+       struct ec_node **post_ops;
+       unsigned int post_ops_len;
+       struct ec_node **open_ops;
+       struct ec_node **close_ops;
+       unsigned int paren_len;
+};
+
+static int ec_node_expr_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (node->child == NULL) {
+               errno = ENOENT;
+               return -1;
+       }
+
+       return ec_node_parse_child(node->child, state, strvec);
+}
+
+static int
+ec_node_expr_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (node->child == NULL) {
+               errno = ENOENT;
+               return -1;
+       }
+
+       return ec_node_complete_child(node->child, comp, strvec);
+}
+
+static void ec_node_expr_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       unsigned int i;
+
+       ec_node_free(node->child);
+       ec_node_free(node->val_node);
+
+       for (i = 0; i < node->bin_ops_len; i++)
+               ec_node_free(node->bin_ops[i]);
+       ec_free(node->bin_ops);
+       for (i = 0; i < node->pre_ops_len; i++)
+               ec_node_free(node->pre_ops[i]);
+       ec_free(node->pre_ops);
+       for (i = 0; i < node->post_ops_len; i++)
+               ec_node_free(node->post_ops[i]);
+       ec_free(node->post_ops);
+       for (i = 0; i < node->paren_len; i++) {
+               ec_node_free(node->open_ops[i]);
+               ec_node_free(node->close_ops[i]);
+       }
+       ec_free(node->open_ops);
+       ec_free(node->close_ops);
+}
+
+static int ec_node_expr_build(struct ec_node_expr *node)
+{
+       struct ec_node *term = NULL, *expr = NULL, *next = NULL,
+               *pre_op = NULL, *post_op = NULL, *ref = NULL,
+               *post = NULL;
+       unsigned int i;
+
+       ec_node_free(node->child);
+       node->child = NULL;
+
+       if (node->val_node == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (node->bin_ops_len == 0 && node->pre_ops_len == 0 &&
+                       node->post_ops_len == 0) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       /*
+        * Example of created grammar:
+        *
+        * pre_op = "!"
+        * post_op = "^"
+        * post = val |
+        *        pre_op expr |
+        *        "(" expr ")"
+        * term = post post_op*
+        * prod = term ( "*" term )*
+        * sum = prod ( "+" prod )*
+        * expr = sum
+        */
+
+       /* we use this as a ref, will be set later */
+       ref = ec_node("seq", "ref");
+       if (ref == NULL)
+               return -1;
+
+       /* prefix unary operators */
+       pre_op = ec_node("or", "pre-op");
+       if (pre_op == NULL)
+               goto fail;
+       for (i = 0; i < node->pre_ops_len; i++) {
+               if (ec_node_or_add(pre_op, ec_node_clone(node->pre_ops[i])) < 0)
+                       goto fail;
+       }
+
+       /* suffix unary operators */
+       post_op = ec_node("or", "post-op");
+       if (post_op == NULL)
+               goto fail;
+       for (i = 0; i < node->post_ops_len; i++) {
+               if (ec_node_or_add(post_op, ec_node_clone(node->post_ops[i])) < 0)
+                       goto fail;
+       }
+
+       post = ec_node("or", "post");
+       if (post == NULL)
+               goto fail;
+       if (ec_node_or_add(post, ec_node_clone(node->val_node)) < 0)
+               goto fail;
+       if (ec_node_or_add(post,
+               EC_NODE_SEQ(EC_NO_ID,
+                       ec_node_clone(pre_op),
+                       ec_node_clone(ref))) < 0)
+               goto fail;
+       for (i = 0; i < node->paren_len; i++) {
+               if (ec_node_or_add(post, EC_NODE_SEQ(EC_NO_ID,
+                                       ec_node_clone(node->open_ops[i]),
+                                       ec_node_clone(ref),
+                                       ec_node_clone(node->close_ops[i]))) < 0)
+                       goto fail;
+       }
+       term = EC_NODE_SEQ("term",
+               ec_node_clone(post),
+               ec_node_many(EC_NO_ID, ec_node_clone(post_op), 0, 0)
+       );
+       if (term == NULL)
+               goto fail;
+
+       for (i = 0; i < node->bin_ops_len; i++) {
+               next = EC_NODE_SEQ("next",
+                       ec_node_clone(term),
+                       ec_node_many(EC_NO_ID,
+                               EC_NODE_SEQ(EC_NO_ID,
+                                       ec_node_clone(node->bin_ops[i]),
+                                       ec_node_clone(term)
+                               ),
+                               0, 0
+                       )
+               );
+               ec_node_free(term);
+               term = next;
+               if (term == NULL)
+                       goto fail;
+       }
+       expr = term;
+       term = NULL;
+
+       /* free the initial references */
+       ec_node_free(pre_op);
+       pre_op = NULL;
+       ec_node_free(post_op);
+       post_op = NULL;
+       ec_node_free(post);
+       post = NULL;
+
+       if (ec_node_seq_add(ref, ec_node_clone(expr)) < 0)
+               goto fail;
+       ec_node_free(ref);
+       ref = NULL;
+
+       node->child = expr;
+
+       return 0;
+
+fail:
+       ec_node_free(term);
+       ec_node_free(expr);
+       ec_node_free(pre_op);
+       ec_node_free(post_op);
+       ec_node_free(post);
+       ec_node_free(ref);
+
+       return -1;
+}
+
+static size_t
+ec_node_expr_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_expr_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_expr_type = {
+       .name = "expr",
+       .parse = ec_node_expr_parse,
+       .complete = ec_node_expr_complete,
+       .size = sizeof(struct ec_node_expr),
+       .free_priv = ec_node_expr_free_priv,
+       .get_children_count = ec_node_expr_get_children_count,
+       .get_child = ec_node_expr_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_expr_type);
+
+int ec_node_expr_set_val_node(struct ec_node *gen_node, struct ec_node *val_node)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (val_node == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       ec_node_free(node->val_node);
+       node->val_node = val_node;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(val_node);
+       return -1;
+}
+
+/* add a binary operator */
+int ec_node_expr_add_bin_op(struct ec_node *gen_node, struct ec_node *op)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       struct ec_node **bin_ops;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (node == NULL || op == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       bin_ops = ec_realloc(node->bin_ops,
+               (node->bin_ops_len + 1) * sizeof(*node->bin_ops));
+       if (bin_ops == NULL)
+               goto fail;;
+
+       node->bin_ops = bin_ops;
+       bin_ops[node->bin_ops_len] = op;
+       node->bin_ops_len++;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(op);
+       return -1;
+}
+
+/* add a unary pre-operator */
+int ec_node_expr_add_pre_op(struct ec_node *gen_node, struct ec_node *op)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       struct ec_node **pre_ops;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (node == NULL || op == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       pre_ops = ec_realloc(node->pre_ops,
+               (node->pre_ops_len + 1) * sizeof(*node->pre_ops));
+       if (pre_ops == NULL)
+               goto fail;
+
+       node->pre_ops = pre_ops;
+       pre_ops[node->pre_ops_len] = op;
+       node->pre_ops_len++;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(op);
+       return -1;
+}
+
+/* add a unary post-operator */
+int ec_node_expr_add_post_op(struct ec_node *gen_node, struct ec_node *op)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       struct ec_node **post_ops;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (node == NULL || op == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       post_ops = ec_realloc(node->post_ops,
+               (node->post_ops_len + 1) * sizeof(*node->post_ops));
+       if (post_ops == NULL)
+               goto fail;
+
+       node->post_ops = post_ops;
+       post_ops[node->post_ops_len] = op;
+       node->post_ops_len++;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(op);
+       return -1;
+}
+
+/* add parenthesis symbols */
+int ec_node_expr_add_parenthesis(struct ec_node *gen_node,
+       struct ec_node *open, struct ec_node *close)
+{
+       struct ec_node_expr *node = (struct ec_node_expr *)gen_node;
+       struct ec_node **open_ops, **close_ops;
+
+       if (ec_node_check_type(gen_node, &ec_node_expr_type) < 0)
+               goto fail;
+
+       if (node == NULL || open == NULL || close == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       open_ops = ec_realloc(node->open_ops,
+               (node->paren_len + 1) * sizeof(*node->open_ops));
+       if (open_ops == NULL)
+               goto fail;
+       close_ops = ec_realloc(node->close_ops,
+               (node->paren_len + 1) * sizeof(*node->close_ops));
+       if (close_ops == NULL)
+               goto fail;
+
+       node->open_ops = open_ops;
+       node->close_ops = close_ops;
+       open_ops[node->paren_len] = open;
+       close_ops[node->paren_len] = close;
+       node->paren_len++;
+       ec_node_expr_build(node);
+
+       return 0;
+
+fail:
+       ec_node_free(open);
+       ec_node_free(close);
+       return -1;
+}
+
+enum expr_node_type {
+       NONE,
+       VAL,
+       BIN_OP,
+       PRE_OP,
+       POST_OP,
+       PAREN_OPEN,
+       PAREN_CLOSE,
+};
+
+static enum expr_node_type get_node_type(const struct ec_node *expr_gen_node,
+       const struct ec_node *check)
+{
+       struct ec_node_expr *expr_node = (struct ec_node_expr *)expr_gen_node;
+       size_t i;
+
+       if (check == expr_node->val_node)
+               return VAL;
+
+       for (i = 0; i < expr_node->bin_ops_len; i++) {
+               if (check == expr_node->bin_ops[i])
+                       return BIN_OP;
+       }
+       for (i = 0; i < expr_node->pre_ops_len; i++) {
+               if (check == expr_node->pre_ops[i])
+                       return PRE_OP;
+       }
+       for (i = 0; i < expr_node->post_ops_len; i++) {
+               if (check == expr_node->post_ops[i])
+                       return POST_OP;
+       }
+
+       for (i = 0; i < expr_node->paren_len; i++) {
+               if (check == expr_node->open_ops[i])
+                       return PAREN_OPEN;
+       }
+       for (i = 0; i < expr_node->paren_len; i++) {
+               if (check == expr_node->close_ops[i])
+                       return PAREN_CLOSE;
+       }
+
+       return NONE;
+}
+
+struct result {
+       bool has_val;
+       void *val;
+       const struct ec_parse *op;
+       enum expr_node_type op_type;
+};
+
+/* merge x and y results in x */
+static int merge_results(void *userctx,
+       const struct ec_node_expr_eval_ops *ops,
+       struct result *x, const struct result *y)
+{
+       if (y->has_val == 0 && y->op == NULL)
+               return 0;
+       if (x->has_val == 0 && x->op == NULL) {
+               *x = *y;
+               return 0;
+       }
+
+       if (x->has_val && y->has_val && y->op != NULL) {
+               if (y->op_type == BIN_OP) {
+                       if (ops->eval_bin_op(&x->val, userctx, x->val,
+                                       y->op, y->val) < 0)
+                               return -1;
+
+                       return 0;
+               }
+       }
+
+       if (x->has_val == 0 && x->op != NULL && y->has_val && y->op == NULL) {
+               if (x->op_type == PRE_OP) {
+                       if (ops->eval_pre_op(&x->val, userctx, y->val,
+                                               x->op) < 0)
+                               return -1;
+                       x->has_val = true;
+                       x->op_type = NONE;
+                       x->op = NULL;
+                       return 0;
+               } else if (x->op_type == BIN_OP) {
+                       x->val = y->val;
+                       x->has_val = true;
+                       return 0;
+               }
+       }
+
+       if (x->has_val && x->op == NULL && y->has_val == 0 && y->op != NULL) {
+               if (ops->eval_post_op(&x->val, userctx, x->val, y->op) < 0)
+                       return -1;
+
+               return 0;
+       }
+
+       assert(false); /* we should not get here */
+       return -1;
+}
+
+static int eval_expression(struct result *result,
+       void *userctx,
+       const struct ec_node_expr_eval_ops *ops,
+       const struct ec_node *expr_gen_node,
+       const struct ec_parse *parse)
+
+{
+       struct ec_parse *open = NULL, *close = NULL;
+       struct result child_result;
+       struct ec_parse *child;
+       enum expr_node_type type;
+
+       memset(result, 0, sizeof(*result));
+       memset(&child_result, 0, sizeof(child_result));
+
+       type = get_node_type(expr_gen_node, ec_parse_get_node(parse));
+       if (type == VAL) {
+               if (ops->eval_var(&result->val, userctx, parse) < 0)
+                       goto fail;
+               result->has_val = 1;
+       } else if (type == PRE_OP || type == POST_OP || type == BIN_OP) {
+               result->op = parse;
+               result->op_type = type;
+       }
+
+       EC_PARSE_FOREACH_CHILD(child, parse) {
+
+               type = get_node_type(expr_gen_node, ec_parse_get_node(child));
+               if (type == PAREN_OPEN) {
+                       open = child;
+                       continue;
+               } else if (type == PAREN_CLOSE) {
+                       close = child;
+                       continue;
+               }
+
+               if (eval_expression(&child_result, userctx, ops,
+                       expr_gen_node, child) < 0)
+                       goto fail;
+
+               if (merge_results(userctx, ops, result, &child_result) < 0)
+                       goto fail;
+
+               memset(&child_result, 0, sizeof(child_result));
+       }
+
+       if (open != NULL && close != NULL) {
+               if (ops->eval_parenthesis(&result->val, userctx, open, close,
+                       result->val) < 0)
+                       goto fail;
+       }
+
+       return 0;
+
+fail:
+       if (result->has_val)
+               ops->eval_free(result->val, userctx);
+       if (child_result.has_val)
+               ops->eval_free(child_result.val, userctx);
+       memset(result, 0, sizeof(*result));
+
+       return -1;
+}
+
+int ec_node_expr_eval(void **user_result, const struct ec_node *node,
+       struct ec_parse *parse, const struct ec_node_expr_eval_ops *ops,
+       void *userctx)
+{
+       struct result result;
+
+       if (ops == NULL || ops->eval_var == NULL || ops->eval_pre_op == NULL ||
+                       ops->eval_post_op == NULL || ops->eval_bin_op == NULL ||
+                       ops->eval_parenthesis == NULL ||
+                       ops->eval_free == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (ec_node_check_type(node, &ec_node_expr_type) < 0)
+               return -1;
+
+       if (!ec_parse_matches(parse)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (eval_expression(&result, userctx, ops, node, parse) < 0)
+               return -1;
+
+       assert(result.has_val);
+       assert(result.op == NULL);
+       *user_result = result.val;
+
+       return 0;
+}
+
+/* the test case is in a separate file ecoli_node_expr_test.c */
diff --git a/src/ecoli_node_expr_test.c b/src/ecoli_node_expr_test.c
new file mode 100644 (file)
index 0000000..e3a0c79
--- /dev/null
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <assert.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_strvec.h>
+#include <ecoli_test.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_node_int.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_re_lex.h>
+#include <ecoli_node_expr.h>
+
+EC_LOG_TYPE_REGISTER(node_expr);
+
+struct my_eval_result {
+       int val;
+};
+
+static int
+ec_node_expr_test_eval_var(void **result, void *userctx,
+       const struct ec_parse *var)
+{
+       const struct ec_strvec *vec;
+       const struct ec_node *node;
+       struct my_eval_result *eval = NULL;
+       int64_t val;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(var);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       node = ec_parse_get_node(var);
+       if (ec_node_int_getval(node, ec_strvec_val(vec, 0), &val) < 0)
+               return -1;
+
+       eval = ec_malloc(sizeof(*eval));
+       if (eval == NULL)
+               return -1;
+
+       eval->val = val;
+       EC_LOG(EC_LOG_DEBUG, "eval var %d\n", eval->val);
+       *result = eval;
+
+       return 0;
+}
+
+static int
+ec_node_expr_test_eval_pre_op(void **result, void *userctx, void *operand,
+       const struct ec_parse *operator)
+{
+       const struct ec_strvec *vec;
+       struct my_eval_result *eval = operand;;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "!")) {
+               eval->val = !eval->val;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+
+       EC_LOG(EC_LOG_DEBUG, "eval pre_op %d\n", eval->val);
+       *result = eval;
+
+       return 0;
+}
+
+static int
+ec_node_expr_test_eval_post_op(void **result, void *userctx, void *operand,
+       const struct ec_parse *operator)
+{
+       const struct ec_strvec *vec;
+       struct my_eval_result *eval = operand;;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "^")) {
+               eval->val = eval->val * eval->val;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       EC_LOG(EC_LOG_DEBUG, "eval post_op %d\n", eval->val);
+       *result = eval;
+
+       return 0;
+}
+
+static int
+ec_node_expr_test_eval_bin_op(void **result, void *userctx, void *operand1,
+       const struct ec_parse *operator, void *operand2)
+
+{
+       const struct ec_strvec *vec;
+       struct my_eval_result *eval1 = operand1;;
+       struct my_eval_result *eval2 = operand2;;
+
+       (void)userctx;
+
+       /* get parsed string vector, it should contain only one str */
+       vec = ec_parse_strvec(operator);
+       if (ec_strvec_len(vec) != 1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!strcmp(ec_strvec_val(vec, 0), "+")) {
+               eval1->val = eval1->val + eval2->val;
+       } else if (!strcmp(ec_strvec_val(vec, 0), "*")) {
+               eval1->val = eval1->val * eval2->val;
+       } else {
+               errno = EINVAL;
+               return -1;
+       }
+
+       EC_LOG(EC_LOG_DEBUG, "eval bin_op %d\n", eval1->val);
+       ec_free(eval2);
+       *result = eval1;
+
+       return 0;
+}
+
+static int
+ec_node_expr_test_eval_parenthesis(void **result, void *userctx,
+       const struct ec_parse *open_paren,
+       const struct ec_parse *close_paren,
+       void *value)
+{
+       (void)userctx;
+       (void)open_paren;
+       (void)close_paren;
+
+       EC_LOG(EC_LOG_DEBUG, "eval paren\n");
+       *result = value;
+
+       return 0;
+}
+
+static void
+ec_node_expr_test_eval_free(void *result, void *userctx)
+{
+       (void)userctx;
+       ec_free(result);
+}
+
+static const struct ec_node_expr_eval_ops test_ops = {
+       .eval_var = ec_node_expr_test_eval_var,
+       .eval_pre_op = ec_node_expr_test_eval_pre_op,
+       .eval_post_op = ec_node_expr_test_eval_post_op,
+       .eval_bin_op = ec_node_expr_test_eval_bin_op,
+       .eval_parenthesis = ec_node_expr_test_eval_parenthesis,
+       .eval_free = ec_node_expr_test_eval_free,
+};
+
+static int ec_node_expr_test_eval(struct ec_node *lex_node,
+       const struct ec_node *expr_node,
+       const char *str, int val)
+{
+       struct ec_parse *p;
+       void *result;
+       struct my_eval_result *eval;
+       int ret;
+
+       p = ec_node_parse(lex_node, str);
+       if (p == NULL)
+               return -1;
+
+       ret = ec_node_expr_eval(&result, expr_node, p, &test_ops, NULL);
+       ec_parse_free(p);
+       if (ret < 0)
+               return -1;
+
+       /* the parse value is an integer */
+       eval = result;
+       assert(eval != NULL);
+
+       EC_LOG(EC_LOG_DEBUG, "result: %d (expected %d)\n", eval->val, val);
+       if (eval->val == val)
+               ret = 0;
+       else
+               ret = -1;
+
+       ec_free(eval);
+
+       return ret;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_expr_testcase(void)
+{
+       struct ec_node *node = NULL, *lex_node = NULL;
+       int testres = 0;
+
+       node = ec_node("expr", "my_expr");
+       if (node == NULL)
+               return -1;
+
+       ec_node_expr_set_val_node(node, ec_node_int(EC_NO_ID, 0, UCHAR_MAX, 0));
+       ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "+"));
+       ec_node_expr_add_bin_op(node, ec_node_str(EC_NO_ID, "*"));
+       ec_node_expr_add_pre_op(node, ec_node_str(EC_NO_ID, "!"));  /* not */
+       ec_node_expr_add_post_op(node, ec_node_str(EC_NO_ID, "^")); /* square */
+       ec_node_expr_add_parenthesis(node, ec_node_str(EC_NO_ID, "("),
+               ec_node_str(EC_NO_ID, ")"));
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "1", "*");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "1", "*", "1", "*");
+       testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "+", "!", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 4, "1", "^", "+", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "*", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "*", "1", "+", "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 7, "1", "*", "1", "*", "1", "*",
+                               "1");
+       testres |= EC_TEST_CHECK_PARSE(
+               node, 10, "!", "(", "1", "*", "(", "1", "+", "1", ")", ")");
+       testres |= EC_TEST_CHECK_PARSE(node, 5, "1", "+", "!", "1", "^");
+
+       /* prepend a lexer to the expression node */
+       lex_node = ec_node_re_lex(EC_NO_ID, ec_node_clone(node));
+       if (lex_node == NULL)
+               goto fail;
+
+       testres |= ec_node_re_lex_add(lex_node, "[0-9]+", 1, NULL); /* vars */
+       testres |= ec_node_re_lex_add(lex_node, "[+*!^()]", 1, NULL); /* operators */
+       testres |= ec_node_re_lex_add(lex_node, "[      ]+", 0, NULL); /* spaces */
+
+       /* valid expressions */
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1^ + 1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "1 + 4 * (2 + 3^)^");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1)");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "3*!3+!3*(2+ 2)");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "!!(!1)^ + !(4 + (2*3))");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, 1, "(1 + 1)^ * 1^");
+
+       /* invalid expressions */
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "()");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "(");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, ")");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "+1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+*1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+(1*1");
+       testres |= EC_TEST_CHECK_PARSE(lex_node, -1, "1+!1!1)");
+
+       testres |= ec_node_expr_test_eval(lex_node, node, "1^", 1);
+       testres |= ec_node_expr_test_eval(lex_node, node, "2^", 4);
+       testres |= ec_node_expr_test_eval(lex_node, node, "!1", 0);
+       testres |= ec_node_expr_test_eval(lex_node, node, "!0", 1);
+
+       testres |= ec_node_expr_test_eval(lex_node, node, "1+1", 2);
+       testres |= ec_node_expr_test_eval(lex_node, node, "1+2+3", 6);
+       testres |= ec_node_expr_test_eval(lex_node, node, "1+1*2", 4);
+       testres |= ec_node_expr_test_eval(lex_node, node, "2 * 2^", 8);
+       testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !0)^ * !0^", 4);
+       testres |= ec_node_expr_test_eval(lex_node, node, "(1 + !1) * 3", 3);
+
+       ec_node_free(node);
+       ec_node_free(lex_node);
+
+       return testres;
+
+fail:
+       ec_node_free(lex_node);
+       ec_node_free(node);
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_expr_test = {
+       .name = "node_expr",
+       .test = ec_node_expr_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_expr_test);
diff --git a/src/ecoli_node_file.c b/src/ecoli_node_file.c
new file mode 100644 (file)
index 0000000..001dcb6
--- /dev/null
@@ -0,0 +1,425 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_string.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_file.h>
+
+EC_LOG_TYPE_REGISTER(node_file);
+
+struct ec_node_file {
+       struct ec_node gen;
+
+       /* below functions pointers are only useful for test */
+       int (*lstat)(const char *pathname, struct stat *buf);
+       DIR *(*opendir)(const char *name);
+       struct dirent *(*readdir)(DIR *dirp);
+       int (*closedir)(DIR *dirp);
+       int (*dirfd)(DIR *dirp);
+       int (*fstatat)(int dirfd, const char *pathname, struct stat *buf,
+               int flags);
+};
+
+static int
+ec_node_file_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)state;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       return 1;
+}
+
+/*
+ * Almost the same than dirname (3) and basename (3) except that:
+ * - it always returns a substring of the given path, which can
+ *   be empty.
+ * - the behavior is different when the path finishes with a '/'
+ * - the path argument is not modified
+ * - the outputs are allocated and must be freed with ec_free().
+ *
+ *   path       dirname   basename       split_path
+ *   /usr/lib   /usr      lib          /usr/     lib
+ *   /usr/      /         usr          /usr/
+ *   usr        .         usr                    usr
+ *   /          /         /            /
+ *   .          .         .                      .
+ *   ..         .         ..                     ..
+ */
+static int split_path(const char *path, char **dname_p, char **bname_p)
+{
+       char *last_slash;
+       size_t dirlen;
+       char *dname, *bname;
+
+       *dname_p = NULL;
+       *bname_p = NULL;
+
+       last_slash = strrchr(path, '/');
+       if (last_slash == NULL)
+               dirlen = 0;
+       else
+               dirlen = last_slash - path + 1;
+
+       dname = ec_strdup(path);
+       if (dname == NULL)
+               return -1;
+       dname[dirlen] = '\0';
+
+       bname = ec_strdup(path + dirlen);
+       if (bname == NULL) {
+               ec_free(dname);
+               return -1;
+       }
+
+       *dname_p = dname;
+       *bname_p = bname;
+
+       return 0;
+}
+
+static int
+ec_node_file_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_file *node = (struct ec_node_file *)gen_node;
+       char *dname = NULL, *bname = NULL, *effective_dir;
+       struct ec_comp_item *item = NULL;
+       enum ec_comp_type type;
+       struct stat st, st2;
+       const char *input;
+       size_t bname_len;
+       struct dirent *de = NULL;
+       DIR *dir = NULL;
+       char *comp_str = NULL;
+       char *disp_str = NULL;
+       int is_dir = 0;
+
+       /*
+        * Example with this file tree:
+        * /
+        * ├── dir1
+        * │   ├── file1
+        * │   ├── file2
+        * │   └── subdir
+        * │       └── file3
+        * ├── dir2
+        * │   └── file4
+        * └── file5
+        *
+        * Input     Output completions
+        *   /       [dir1/, dir2/, file5]
+        *   /d      [dir1/, dir2/]
+        *   /f      [file5]
+        *   /dir1/  [file1, file2, subdir/]
+        *
+        *
+        *
+        */
+
+       if (ec_strvec_len(strvec) != 1)
+               return 0;
+
+       input = ec_strvec_val(strvec, 0);
+       if (split_path(input, &dname, &bname) < 0)
+               return -1;
+
+       if (strcmp(dname, "") == 0)
+               effective_dir = ".";
+       else
+               effective_dir = dname;
+
+       if (node->lstat(effective_dir, &st) < 0)
+               goto fail;
+       if (!S_ISDIR(st.st_mode))
+               goto out;
+
+       dir = node->opendir(effective_dir);
+       if (dir == NULL)
+               goto fail;
+
+       bname_len = strlen(bname);
+       while (1) {
+               int save_errno = errno;
+
+               errno = 0;
+               de = node->readdir(dir);
+               if (de == NULL) {
+                       if (errno == 0) {
+                               errno = save_errno;
+                               goto out;
+                       } else {
+                               goto fail;
+                       }
+               }
+
+               if (!ec_str_startswith(de->d_name, bname))
+                       continue;
+               if (bname[0] != '.' && de->d_name[0] == '.')
+                       continue;
+
+               /* add '/' if it's a dir */
+               if (de->d_type == DT_DIR) {
+                       is_dir = 1;
+               } else if (de->d_type == DT_UNKNOWN) {
+                       int dir_fd = node->dirfd(dir);
+
+                       if (dir_fd < 0)
+                               goto fail;
+                       if (node->fstatat(dir_fd, de->d_name, &st2, 0) < 0)
+                               goto fail;
+                       if (S_ISDIR(st2.st_mode))
+                               is_dir = 1;
+                       else
+                               is_dir = 0;
+               } else {
+                       is_dir = 0;
+               }
+
+               if (is_dir) {
+                       type = EC_COMP_PARTIAL;
+                       if (ec_asprintf(&comp_str, "%s%s/", input,
+                                       &de->d_name[bname_len]) < 0)
+                               goto fail;
+                       if (ec_asprintf(&disp_str, "%s/", de->d_name) < 0)
+                               goto fail;
+               } else {
+                       type = EC_COMP_FULL;
+                       if (ec_asprintf(&comp_str, "%s%s", input,
+                                       &de->d_name[bname_len]) < 0)
+                               goto fail;
+                       if (ec_asprintf(&disp_str, "%s", de->d_name) < 0)
+                               goto fail;
+               }
+               if (ec_comp_add_item(comp, gen_node, &item,
+                                               type, input, comp_str) < 0)
+                       goto out;
+
+               /* fix the display string: we don't want to display the full
+                * path. */
+               if (ec_comp_item_set_display(item, disp_str) < 0)
+                       goto out;
+
+               item = NULL;
+               ec_free(comp_str);
+               comp_str = NULL;
+               ec_free(disp_str);
+               disp_str = NULL;
+       }
+out:
+       ec_free(comp_str);
+       ec_free(disp_str);
+       ec_free(dname);
+       ec_free(bname);
+       if (dir != NULL)
+               node->closedir(dir);
+
+       return 0;
+
+fail:
+       ec_free(comp_str);
+       ec_free(disp_str);
+       ec_free(dname);
+       ec_free(bname);
+       if (dir != NULL)
+               node->closedir(dir);
+
+       return -1;
+}
+
+static int
+ec_node_file_init_priv(struct ec_node *gen_node)
+{
+       struct ec_node_file *node = (struct ec_node_file *)gen_node;
+
+       node->lstat = lstat;
+       node->opendir = opendir;
+       node->readdir = readdir;
+       node->dirfd = dirfd;
+       node->fstatat = fstatat;
+
+       return 0;
+}
+
+static struct ec_node_type ec_node_file_type = {
+       .name = "file",
+       .parse = ec_node_file_parse,
+       .complete = ec_node_file_complete,
+       .size = sizeof(struct ec_node_file),
+       .init_priv = ec_node_file_init_priv,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_file_type);
+
+/* LCOV_EXCL_START */
+static int
+test_lstat(const char *pathname, struct stat *buf)
+{
+       if (!strcmp(pathname, "/tmp/toto/")) {
+               struct stat st = { .st_mode = S_IFDIR };
+               memcpy(buf, &st, sizeof(*buf));
+               return 0;
+       }
+
+       errno = ENOENT;
+       return -1;
+}
+
+static DIR *
+test_opendir(const char *name)
+{
+       int *p;
+
+       if (strcmp(name, "/tmp/toto/")) {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       p = malloc(sizeof(int));
+       if (p)
+               *p = 0;
+
+       return (DIR *)p;
+}
+
+static struct dirent *
+test_readdir(DIR *dirp)
+{
+       static struct dirent de[] = {
+               { .d_type = DT_DIR, .d_name = ".." },
+               { .d_type = DT_DIR, .d_name = "." },
+               { .d_type = DT_REG, .d_name = "bar" },
+               { .d_type = DT_UNKNOWN, .d_name = "bar2" },
+               { .d_type = DT_REG, .d_name = "foo" },
+               { .d_type = DT_DIR, .d_name = "titi" },
+               { .d_type = DT_UNKNOWN, .d_name = "tutu" },
+               { .d_name = "" },
+       };
+       int *p = (int *)dirp;
+       struct dirent *ret = &de[*p];
+
+       if (!strcmp(ret->d_name, ""))
+               return NULL;
+
+       *p = *p + 1;
+
+       return ret;
+}
+
+static int
+test_closedir(DIR *dirp)
+{
+       free(dirp);
+       return 0;
+}
+
+static int
+test_dirfd(DIR *dirp)
+{
+       int *p = (int *)dirp;
+       return *p;
+}
+
+static int
+test_fstatat(int dirfd, const char *pathname, struct stat *buf,
+       int flags)
+{
+       (void)dirfd;
+       (void)flags;
+
+       if (!strcmp(pathname, "bar2")) {
+               struct stat st = { .st_mode = S_IFREG };
+               memcpy(buf, &st, sizeof(*buf));
+               return 0;
+       } else if (!strcmp(pathname, "tutu")) {
+               struct stat st = { .st_mode = S_IFDIR };
+               memcpy(buf, &st, sizeof(*buf));
+               return 0;
+       }
+
+       errno = ENOENT;
+       return -1;
+}
+
+static int
+ec_node_file_override_functions(struct ec_node *gen_node)
+{
+       struct ec_node_file *node = (struct ec_node_file *)gen_node;
+
+       node->lstat = test_lstat;
+       node->opendir = test_opendir;
+       node->readdir = test_readdir;
+       node->closedir = test_closedir;
+       node->dirfd = test_dirfd;
+       node->fstatat = test_fstatat;
+
+       return 0;
+}
+
+static int ec_node_file_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("file", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       ec_node_file_override_functions(node);
+
+       /* any string matches */
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "/tmp/bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+
+       /* test completion */
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "/tmp/toto/t", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE_PARTIAL(node,
+               "/tmp/toto/t", EC_NODE_ENDLIST,
+               "/tmp/toto/titi/", "/tmp/toto/tutu/", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "/tmp/toto/f", EC_NODE_ENDLIST,
+               "/tmp/toto/foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "/tmp/toto/b", EC_NODE_ENDLIST,
+               "/tmp/toto/bar", "/tmp/toto/bar2", EC_NODE_ENDLIST);
+
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_file_test = {
+       .name = "node_file",
+       .test = ec_node_file_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_file_test);
diff --git a/src/ecoli_node_helper.c b/src/ecoli_node_helper.c
new file mode 100644 (file)
index 0000000..94811f2
--- /dev/null
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_config.h>
+#include <ecoli_node.h>
+#include <ecoli_node_helper.h>
+
+struct ec_node **
+ec_node_config_node_list_to_table(const struct ec_config *config,
+                               size_t *len)
+{
+       struct ec_node **table = NULL;
+       struct ec_config *child;
+       ssize_t n, i;
+
+       *len = 0;
+
+       if (config == NULL) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if (ec_config_get_type(config) != EC_CONFIG_TYPE_LIST) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       n = ec_config_count(config);
+       if (n < 0)
+               return NULL;
+
+       table = ec_calloc(n, sizeof(*table));
+       if (table == NULL)
+               goto fail;
+
+       n = 0;
+       TAILQ_FOREACH(child, &config->list, next) {
+               if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
+                       errno = EINVAL;
+                       goto fail;
+               }
+               table[n] = ec_node_clone(child->node);
+               n++;
+       }
+
+       *len = n;
+
+       return table;
+
+fail:
+       if (table != NULL) {
+               for (i = 0; i < n; i++)
+                       ec_node_free(table[i]);
+       }
+       ec_free(table);
+
+       return NULL;
+}
+
+struct ec_config *
+ec_node_config_node_list_from_vargs(va_list ap)
+{
+       struct ec_config *list = NULL;
+       struct ec_node *node = va_arg(ap, struct ec_node *);
+
+       list = ec_config_list();
+       if (list == NULL)
+               goto fail;
+
+       for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *)) {
+               if (node == NULL)
+                       goto fail;
+
+               if (ec_config_list_add(list, ec_config_node(node)) < 0)
+                       goto fail;
+       }
+
+       return list;
+
+fail:
+       for (; node != EC_NODE_ENDLIST; node = va_arg(ap, struct ec_node *))
+               ec_node_free(node);
+       ec_config_free(list);
+
+       return NULL;
+}
diff --git a/src/ecoli_node_int.c b/src/ecoli_node_int.c
new file mode 100644 (file)
index 0000000..9b56e22
--- /dev/null
@@ -0,0 +1,501 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_int.h>
+#include <ecoli_test.h>
+
+EC_LOG_TYPE_REGISTER(node_int);
+
+/* common to int and uint */
+struct ec_node_int_uint {
+       struct ec_node gen;
+       bool is_signed;
+       bool check_min;
+       bool check_max;
+       union {
+               int64_t min;
+               uint64_t umin;
+       };
+       union {
+               int64_t max;
+               uint64_t umax;
+       };
+       unsigned int base;
+};
+
+/* XXX to utils.c ? */
+static int parse_llint(struct ec_node_int_uint *node, const char *str,
+       int64_t *val)
+{
+       char *endptr;
+       int save_errno = errno;
+
+       errno = 0;
+       *val = strtoll(str, &endptr, node->base);
+
+       if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) ||
+                       (errno != 0 && *val == 0))
+               return -1;
+
+       if (node->check_min && *val < node->min) {
+               errno = ERANGE;
+               return -1;
+       }
+
+       if (node->check_max && *val > node->max) {
+               errno = ERANGE;
+               return -1;
+       }
+
+       if (*endptr != 0) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       errno = save_errno;
+       return 0;
+}
+
+static int parse_ullint(struct ec_node_int_uint *node, 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, node->base);
+
+       if ((errno == ERANGE && *val == ULLONG_MAX) ||
+                       (errno != 0 && *val == 0))
+               return -1;
+
+       if (node->check_min && *val < node->umin)
+               return -1;
+
+       if (node->check_max && *val > node->umax)
+               return -1;
+
+       if (*endptr != 0)
+               return -1;
+
+       errno = save_errno;
+       return 0;
+}
+
+static int ec_node_int_uint_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       const char *str;
+       uint64_t u64;
+       int64_t i64;
+
+       (void)state;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       str = ec_strvec_val(strvec, 0);
+       if (node->is_signed) {
+               if (parse_llint(node, str, &i64) < 0)
+                       return EC_PARSE_NOMATCH;
+       } else {
+               if (parse_ullint(node, str, &u64) < 0)
+                       return EC_PARSE_NOMATCH;
+       }
+       return 1;
+}
+
+static int
+ec_node_uint_init_priv(struct ec_node *gen_node)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+
+       node->is_signed = true;
+
+       return 0;
+}
+
+static const struct ec_config_schema ec_node_int_schema[] = {
+       {
+               .key = "min",
+               .desc = "The minimum valid value (included).",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .key = "max",
+               .desc = "The maximum valid value (included).",
+               .type = EC_CONFIG_TYPE_INT64,
+       },
+       {
+               .key = "base",
+               .desc = "The base to use. If unset or 0, try to guess.",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_int_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       const struct ec_config *min_value = NULL;
+       const struct ec_config *max_value = NULL;
+       const struct ec_config *base_value = NULL;
+       char *s = NULL;
+
+       min_value = ec_config_dict_get(config, "min");
+       max_value = ec_config_dict_get(config, "max");
+       base_value = ec_config_dict_get(config, "base");
+
+       if (min_value && max_value && min_value->i64 > max_value->i64) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (min_value != NULL) {
+               node->check_min = true;
+               node->min = min_value->i64;
+       } else {
+               node->check_min = false;
+       }
+       if (max_value != NULL) {
+               node->check_max = true;
+               node->max = max_value->i64;
+       } else {
+               node->check_min = false;
+       }
+       if (base_value != NULL)
+               node->base = base_value->u64;
+       else
+               node->base = 0;
+
+       return 0;
+
+fail:
+       ec_free(s);
+       return -1;
+}
+
+static struct ec_node_type ec_node_int_type = {
+       .name = "int",
+       .schema = ec_node_int_schema,
+       .set_config = ec_node_int_set_config,
+       .parse = ec_node_int_uint_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_int_uint),
+       .init_priv = ec_node_uint_init_priv,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_int_type);
+
+struct ec_node *ec_node_int(const char *id, int64_t min,
+       int64_t max, unsigned int base)
+{
+       struct ec_config *config = NULL;
+       struct ec_node *gen_node = NULL;
+       int ret;
+
+       gen_node = ec_node_from_type(&ec_node_int_type, id);
+       if (gen_node == NULL)
+               return NULL;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(config, "min", ec_config_i64(min));
+       if (ret < 0)
+               goto fail;
+       ret = ec_config_dict_set(config, "max", ec_config_i64(max));
+       if (ret < 0)
+               goto fail;
+       ret = ec_config_dict_set(config, "base", ec_config_u64(base));
+       if (ret < 0)
+               goto fail;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL;
+       if (ret < 0)
+               goto fail;
+
+       return gen_node;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(gen_node);
+       return NULL;
+}
+
+static const struct ec_config_schema ec_node_uint_schema[] = {
+       {
+               .key = "min",
+               .desc = "The minimum valid value (included).",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .key = "max",
+               .desc = "The maximum valid value (included).",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .key = "base",
+               .desc = "The base to use. If unset or 0, try to guess.",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_uint_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       const struct ec_config *min_value = NULL;
+       const struct ec_config *max_value = NULL;
+       const struct ec_config *base_value = NULL;
+       char *s = NULL;
+
+       min_value = ec_config_dict_get(config, "min");
+       max_value = ec_config_dict_get(config, "max");
+       base_value = ec_config_dict_get(config, "base");
+
+       if (min_value && max_value && min_value->u64 > max_value->u64) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (min_value != NULL) {
+               node->check_min = true;
+               node->min = min_value->u64;
+       } else {
+               node->check_min = false;
+       }
+       if (max_value != NULL) {
+               node->check_max = true;
+               node->max = max_value->u64;
+       } else {
+               node->check_min = false;
+       }
+       if (base_value != NULL)
+               node->base = base_value->u64;
+       else
+               node->base = 0;
+
+       return 0;
+
+fail:
+       ec_free(s);
+       return -1;
+}
+
+static struct ec_node_type ec_node_uint_type = {
+       .name = "uint",
+       .schema = ec_node_uint_schema,
+       .set_config = ec_node_uint_set_config,
+       .parse = ec_node_int_uint_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_int_uint),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_uint_type);
+
+struct ec_node *ec_node_uint(const char *id, uint64_t min,
+       uint64_t max, unsigned int base)
+{
+       struct ec_config *config = NULL;
+       struct ec_node *gen_node = NULL;
+       int ret;
+
+       gen_node = ec_node_from_type(&ec_node_uint_type, id);
+       if (gen_node == NULL)
+               return NULL;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(config, "min", ec_config_u64(min));
+       if (ret < 0)
+               goto fail;
+       ret = ec_config_dict_set(config, "max", ec_config_u64(max));
+       if (ret < 0)
+               goto fail;
+       ret = ec_config_dict_set(config, "base", ec_config_u64(base));
+       if (ret < 0)
+               goto fail;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL;
+       if (ret < 0)
+               goto fail;
+
+       return gen_node;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(gen_node);
+       return NULL;
+}
+
+int ec_node_int_getval(const struct ec_node *gen_node, const char *str,
+                       int64_t *result)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       int ret;
+
+       ret = ec_node_check_type(gen_node, &ec_node_int_type);
+       if (ret < 0)
+               return ret;
+
+       if (parse_llint(node, str, result) < 0)
+               return -1;
+
+       return 0;
+}
+
+int ec_node_uint_getval(const struct ec_node *gen_node, const char *str,
+                       uint64_t *result)
+{
+       struct ec_node_int_uint *node = (struct ec_node_int_uint *)gen_node;
+       int ret;
+
+       ret = ec_node_check_type(gen_node, &ec_node_uint_type);
+       if (ret < 0)
+               return ret;
+
+       if (parse_ullint(node, str, result) < 0)
+               return -1;
+
+       return 0;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_int_testcase(void)
+{
+       struct ec_parse *p;
+       struct ec_node *node;
+       const char *s;
+       int testres = 0;
+       uint64_t u64;
+       int64_t i64;
+
+       node = ec_node_uint(EC_NO_ID, 1, 256, 0);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "256", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "0x100");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, " 1");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "-1");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x101");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x100000000000000000");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "4r");
+
+       p = ec_node_parse(node, "1");
+       s = ec_strvec_val(ec_parse_strvec(p), 0);
+       testres |= EC_TEST_CHECK(s != NULL &&
+               ec_node_uint_getval(node, s, &u64) == 0 &&
+               u64 == 1, "bad integer value");
+       ec_parse_free(p);
+
+       p = ec_node_parse(node, "10");
+       s = ec_strvec_val(ec_parse_strvec(p), 0);
+       testres |= EC_TEST_CHECK(s != NULL &&
+               ec_node_uint_getval(node, s, &u64) == 0 &&
+               u64 == 10, "bad integer value");
+       ec_parse_free(p);
+       ec_node_free(node);
+
+       node = ec_node_int(EC_NO_ID, -1, LLONG_MAX, 16);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "0");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "-1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "7fffffffffffffff");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "0x7fffffffffffffff");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x8000000000000000");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "-2");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "zzz");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "4r");
+
+       p = ec_node_parse(node, "10");
+       s = ec_strvec_val(ec_parse_strvec(p), 0);
+       testres |= EC_TEST_CHECK(s != NULL &&
+               ec_node_int_getval(node, s, &i64) == 0 &&
+               i64 == 16, "bad integer value");
+       ec_parse_free(p);
+       ec_node_free(node);
+
+       node = ec_node_int(EC_NO_ID, LLONG_MIN, 0, 10);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "0");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "-1");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "-9223372036854775808");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "0x0");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "1");
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_int(EC_NO_ID, 0, 10, 0);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "1", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_int_test = {
+       .name = "node_int",
+       .test = ec_node_int_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_int_test);
diff --git a/src/ecoli_node_many.c b/src/ecoli_node_many.c
new file mode 100644 (file)
index 0000000..dfdd866
--- /dev/null
@@ -0,0 +1,416 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_option.h>
+#include <ecoli_config.h>
+#include <ecoli_node_many.h>
+
+EC_LOG_TYPE_REGISTER(node_many);
+
+struct ec_node_many {
+       struct ec_node gen;
+       unsigned int min;
+       unsigned int max;
+       struct ec_node *child;
+};
+
+static int ec_node_many_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+       struct ec_parse *child_parse;
+       struct ec_strvec *childvec = NULL;
+       size_t off = 0, count;
+       int ret;
+
+       for (count = 0; node->max == 0 || count < node->max; count++) {
+               childvec = ec_strvec_ndup(strvec, off,
+                       ec_strvec_len(strvec) - off);
+               if (childvec == NULL)
+                       goto fail;
+
+               ret = ec_node_parse_child(node->child, state, childvec);
+               if (ret < 0)
+                       goto fail;
+
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if (ret == EC_PARSE_NOMATCH)
+                       break;
+
+               /* it matches an empty strvec, no need to continue */
+               if (ret == 0) {
+                       child_parse = ec_parse_get_last_child(state);
+                       ec_parse_unlink_child(state, child_parse);
+                       ec_parse_free(child_parse);
+                       break;
+               }
+
+               off += ret;
+       }
+
+       if (count < node->min) {
+               ec_parse_free_children(state);
+               return EC_PARSE_NOMATCH;
+       }
+
+       return off;
+
+fail:
+       ec_strvec_free(childvec);
+       return -1;
+}
+
+static int
+__ec_node_many_complete(struct ec_node_many *node, unsigned int max,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_parse *parse = ec_comp_get_state(comp);
+       struct ec_strvec *childvec = NULL;
+       unsigned int i;
+       int ret;
+
+       /* first, try to complete with the child node */
+       ret = ec_node_complete_child(node->child, comp, strvec);
+       if (ret < 0)
+               goto fail;
+
+       /* we're done, we reached the max number of nodes */
+       if (max == 1)
+               return 0;
+
+       /* if there is a maximum, decrease it before recursion */
+       if (max != 0)
+               max--;
+
+       /* then, if the node matches the beginning of the strvec, try to
+        * complete the rest */
+       for (i = 0; i < ec_strvec_len(strvec); i++) {
+               childvec = ec_strvec_ndup(strvec, 0, i);
+               if (childvec == NULL)
+                       goto fail;
+
+               ret = ec_node_parse_child(node->child, parse, childvec);
+               if (ret < 0)
+                       goto fail;
+
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if ((unsigned int)ret != i) {
+                       if (ret != EC_PARSE_NOMATCH)
+                               ec_parse_del_last_child(parse);
+                       continue;
+               }
+
+               childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i);
+               if (childvec == NULL) {
+                       ec_parse_del_last_child(parse);
+                       goto fail;
+               }
+
+               ret = __ec_node_many_complete(node, max, comp, childvec);
+               ec_parse_del_last_child(parse);
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if (ret < 0)
+                       goto fail;
+       }
+
+       return 0;
+
+fail:
+       ec_strvec_free(childvec);
+       return -1;
+}
+
+static int
+ec_node_many_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+
+       return __ec_node_many_complete(node, node->max, comp,
+                               strvec);
+}
+
+static void ec_node_many_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+
+       ec_node_free(node->child);
+}
+
+static size_t
+ec_node_many_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_many_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 2;
+       return 0;
+}
+
+static const struct ec_config_schema ec_node_many_schema[] = {
+       {
+               .key = "child",
+               .desc = "The child node.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .key = "min",
+               .desc = "The minimum number of matches (default = 0).",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .key = "max",
+               .desc = "The maximum number of matches. If 0, there is "
+               "no maximum (default = 0).",
+               .type = EC_CONFIG_TYPE_UINT64,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_many_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_many *node = (struct ec_node_many *)gen_node;
+       const struct ec_config *child, *min, *max;
+
+       child = ec_config_dict_get(config, "child");
+       if (child == NULL)
+               goto fail;
+       if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
+               errno = EINVAL;
+               goto fail;
+       }
+       min = ec_config_dict_get(config, "min");
+       if (min != NULL && (ec_config_get_type(min) != EC_CONFIG_TYPE_UINT64 ||
+                               min->u64 >= UINT_MAX)) {
+               errno = EINVAL;
+               goto fail;
+       }
+       max = ec_config_dict_get(config, "max");
+       if (max != NULL && (ec_config_get_type(max) != EC_CONFIG_TYPE_UINT64 ||
+                               max->u64 >= UINT_MAX)) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (node->child != NULL)
+               ec_node_free(node->child);
+       node->child = ec_node_clone(child->node);
+       if (min == NULL)
+               node->min = 0;
+       else
+               node->min = min->u64;
+       if (max == NULL)
+               node->max = 0;
+       else
+               node->max = max->u64;
+
+       return 0;
+
+fail:
+       return -1;
+}
+
+static struct ec_node_type ec_node_many_type = {
+       .name = "many",
+       .schema = ec_node_many_schema,
+       .set_config = ec_node_many_set_config,
+       .parse = ec_node_many_parse,
+       .complete = ec_node_many_complete,
+       .size = sizeof(struct ec_node_many),
+       .free_priv = ec_node_many_free_priv,
+       .get_children_count = ec_node_many_get_children_count,
+       .get_child = ec_node_many_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_many_type);
+
+int
+ec_node_many_set_params(struct ec_node *gen_node, struct ec_node *child,
+       unsigned int min, unsigned int max)
+{
+       const struct ec_config *cur_config = NULL;
+       struct ec_config *config = NULL;
+       int ret;
+
+       if (ec_node_check_type(gen_node, &ec_node_many_type) < 0)
+               goto fail;
+
+       cur_config = ec_node_get_config(gen_node);
+       if (cur_config == NULL)
+               config = ec_config_dict();
+       else
+               config = ec_config_dup(cur_config);
+       if (config == NULL)
+               goto fail;
+
+       if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) {
+               child = NULL; /* freed */
+               goto fail;
+       }
+       child = NULL; /* freed */
+
+       if (ec_config_dict_set(config, "min", ec_config_u64(min)) < 0)
+               goto fail;
+       if (ec_config_dict_set(config, "max", ec_config_u64(max)) < 0)
+               goto fail;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *ec_node_many(const char *id, struct ec_node *child,
+       unsigned int min, unsigned int max)
+{
+       struct ec_node *gen_node = NULL;
+
+       if (child == NULL)
+               return NULL;
+
+       gen_node = ec_node_from_type(&ec_node_many_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       if (ec_node_many_set_params(gen_node, child, min, max) < 0) {
+               child = NULL;
+               goto fail;
+       }
+       child = NULL;
+
+       return gen_node;
+
+fail:
+       ec_node_free(child);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_many_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 0, 0);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       ec_node_free(node);
+
+       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 0);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+       ec_node_free(node);
+
+       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 1, 2);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "foo", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_many(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"), 2, 4);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "foo", "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "foo", "foo", "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "foo", "foo", "foo", "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_many_test = {
+       .name = "node_many",
+       .test = ec_node_many_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_many_test);
diff --git a/src/ecoli_node_none.c b/src/ecoli_node_none.c
new file mode 100644 (file)
index 0000000..dba9ebf
--- /dev/null
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_none.h>
+
+EC_LOG_TYPE_REGISTER(node_none);
+
+struct ec_node_none {
+       struct ec_node gen;
+};
+
+static int ec_node_none_parse(const struct ec_node *gen_node,
+                       struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)state;
+       (void)strvec;
+
+       return EC_PARSE_NOMATCH;
+}
+
+static int
+ec_node_none_complete(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)comp;
+       (void)strvec;
+
+       return 0;
+}
+
+static struct ec_node_type ec_node_none_type = {
+       .name = "none",
+       .parse = ec_node_none_parse,
+       .complete = ec_node_none_complete,
+       .size = sizeof(struct ec_node_none),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_none_type);
+
+/* LCOV_EXCL_START */
+static int ec_node_none_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("none", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1);
+       ec_node_free(node);
+
+       /* never completes */
+       node = ec_node("none", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_none_test = {
+       .name = "node_none",
+       .test = ec_node_none_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_none_test);
diff --git a/src/ecoli_node_once.c b/src/ecoli_node_once.c
new file mode 100644 (file)
index 0000000..989c710
--- /dev/null
@@ -0,0 +1,282 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_many.h>
+#include <ecoli_config.h>
+#include <ecoli_node_once.h>
+
+EC_LOG_TYPE_REGISTER(node_once);
+
+struct ec_node_once {
+       struct ec_node gen;
+       struct ec_node *child;
+};
+
+static unsigned int
+count_node(struct ec_parse *parse, const struct ec_node *node)
+{
+       struct ec_parse *child;
+       unsigned int count = 0;
+
+       if (parse == NULL)
+               return 0;
+
+       if (ec_parse_get_node(parse) == node)
+               count++;
+
+       EC_PARSE_FOREACH_CHILD(child, parse)
+               count += count_node(child, node);
+
+       return count;
+}
+
+static int
+ec_node_once_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+       unsigned int count;
+
+       /* count the number of occurences of the node: if already parsed,
+        * do not match
+        */
+       count = count_node(ec_parse_get_root(state), node->child);
+       if (count > 0)
+               return EC_PARSE_NOMATCH;
+
+       return ec_node_parse_child(node->child, state, strvec);
+}
+
+static int
+ec_node_once_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+       struct ec_parse *parse = ec_comp_get_state(comp);
+       unsigned int count;
+       int ret;
+
+       /* count the number of occurences of the node: if already parsed,
+        * do not match
+        */
+       count = count_node(ec_parse_get_root(parse), node->child);
+       if (count > 0)
+               return 0;
+
+       ret = ec_node_complete_child(node->child, comp, strvec);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static void ec_node_once_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+
+       ec_node_free(node->child);
+}
+
+static size_t
+ec_node_once_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_once_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 2;
+       return 0;
+}
+
+static const struct ec_config_schema ec_node_once_schema[] = {
+       {
+               .key = "child",
+               .desc = "The child node.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_once_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_once *node = (struct ec_node_once *)gen_node;
+       const struct ec_config *child;
+
+       child = ec_config_dict_get(config, "child");
+       if (child == NULL)
+               goto fail;
+       if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (node->child != NULL)
+               ec_node_free(node->child);
+       node->child = ec_node_clone(child->node);
+
+       return 0;
+
+fail:
+       return -1;
+}
+
+static struct ec_node_type ec_node_once_type = {
+       .name = "once",
+       .schema = ec_node_once_schema,
+       .set_config = ec_node_once_set_config,
+       .parse = ec_node_once_parse,
+       .complete = ec_node_once_complete,
+       .size = sizeof(struct ec_node_once),
+       .free_priv = ec_node_once_free_priv,
+       .get_children_count = ec_node_once_get_children_count,
+       .get_child = ec_node_once_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_once_type);
+
+int
+ec_node_once_set_child(struct ec_node *gen_node, struct ec_node *child)
+{
+       const struct ec_config *cur_config = NULL;
+       struct ec_config *config = NULL;
+       int ret;
+
+       if (ec_node_check_type(gen_node, &ec_node_once_type) < 0)
+               goto fail;
+
+       cur_config = ec_node_get_config(gen_node);
+       if (cur_config == NULL)
+               config = ec_config_dict();
+       else
+               config = ec_config_dup(cur_config);
+       if (config == NULL)
+               goto fail;
+
+       if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) {
+               child = NULL; /* freed */
+               goto fail;
+       }
+       child = NULL; /* freed */
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *ec_node_once(const char *id, struct ec_node *child)
+{
+       struct ec_node *gen_node = NULL;
+
+       if (child == NULL)
+               return NULL;
+
+       gen_node = ec_node_from_type(&ec_node_once_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       ec_node_once_set_child(gen_node, child);
+       child = NULL;
+
+       return gen_node;
+
+fail:
+       ec_node_free(child);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_once_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_many(EC_NO_ID,
+                       EC_NODE_OR(EC_NO_ID,
+                               ec_node_once(EC_NO_ID, ec_node_str(EC_NO_ID, "foo")),
+                               ec_node_str(EC_NO_ID, "bar")
+                               ), 0, 0
+               );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "foox");
+
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "b", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", "", EC_NODE_ENDLIST,
+               "foo", "bar", EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_once_test = {
+       .name = "node_once",
+       .test = ec_node_once_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_once_test);
diff --git a/src/ecoli_node_option.c b/src/ecoli_node_option.c
new file mode 100644 (file)
index 0000000..4b26001
--- /dev/null
@@ -0,0 +1,239 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_str.h>
+#include <ecoli_test.h>
+#include <ecoli_config.h>
+#include <ecoli_node_option.h>
+
+EC_LOG_TYPE_REGISTER(node_option);
+
+struct ec_node_option {
+       struct ec_node gen;
+       struct ec_node *child;
+};
+
+static int
+ec_node_option_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+       int ret;
+
+       ret = ec_node_parse_child(node->child, state, strvec);
+       if (ret < 0)
+               return ret;
+
+       if (ret == EC_PARSE_NOMATCH)
+               return 0;
+
+       return ret;
+}
+
+static int
+ec_node_option_complete(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+
+       return ec_node_complete_child(node->child, comp, strvec);
+}
+
+static void ec_node_option_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+
+       ec_node_free(node->child);
+}
+
+static size_t
+ec_node_option_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_option_get_child(const struct ec_node *gen_node, size_t i,
+       struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 2;
+       return 0;
+}
+
+static const struct ec_config_schema ec_node_option_schema[] = {
+       {
+               .key = "child",
+               .desc = "The child node.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_option_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_option *node = (struct ec_node_option *)gen_node;
+       const struct ec_config *child;
+
+       child = ec_config_dict_get(config, "child");
+       if (child == NULL)
+               goto fail;
+       if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (node->child != NULL)
+               ec_node_free(node->child);
+       node->child = ec_node_clone(child->node);
+
+       return 0;
+
+fail:
+       return -1;
+}
+
+static struct ec_node_type ec_node_option_type = {
+       .name = "option",
+       .schema = ec_node_option_schema,
+       .set_config = ec_node_option_set_config,
+       .parse = ec_node_option_parse,
+       .complete = ec_node_option_complete,
+       .size = sizeof(struct ec_node_option),
+       .free_priv = ec_node_option_free_priv,
+       .get_children_count = ec_node_option_get_children_count,
+       .get_child = ec_node_option_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_option_type);
+
+int
+ec_node_option_set_child(struct ec_node *gen_node, struct ec_node *child)
+{
+       const struct ec_config *cur_config = NULL;
+       struct ec_config *config = NULL;
+       int ret;
+
+       if (ec_node_check_type(gen_node, &ec_node_option_type) < 0)
+               goto fail;
+
+       cur_config = ec_node_get_config(gen_node);
+       if (cur_config == NULL)
+               config = ec_config_dict();
+       else
+               config = ec_config_dup(cur_config);
+       if (config == NULL)
+               goto fail;
+
+       if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) {
+               child = NULL; /* freed */
+               goto fail;
+       }
+       child = NULL; /* freed */
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *ec_node_option(const char *id, struct ec_node *child)
+{
+       struct ec_node *gen_node = NULL;
+
+       if (child == NULL)
+               goto fail;
+
+       gen_node = ec_node_from_type(&ec_node_option_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       ec_node_option_set_child(gen_node, child);
+       child = NULL;
+
+       return gen_node;
+
+fail:
+       ec_node_free(child);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_option_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"));
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "foo"));
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "b", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_option_test = {
+       .name = "node_option",
+       .test = ec_node_option_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_option_test);
diff --git a/src/ecoli_node_or.c b/src/ecoli_node_or.c
new file mode 100644 (file)
index 0000000..2bf8fc7
--- /dev/null
@@ -0,0 +1,347 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_node_helper.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_str.h>
+#include <ecoli_test.h>
+
+EC_LOG_TYPE_REGISTER(node_or);
+
+struct ec_node_or {
+       struct ec_node gen;
+       struct ec_node **table;
+       size_t len;
+};
+
+static int
+ec_node_or_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < node->len; i++) {
+               ret = ec_node_parse_child(node->table[i], state, strvec);
+               if (ret == EC_PARSE_NOMATCH)
+                       continue;
+               return ret;
+       }
+
+       return EC_PARSE_NOMATCH;
+}
+
+static int
+ec_node_or_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       int ret;
+       size_t n;
+
+       for (n = 0; n < node->len; n++) {
+               ret = ec_node_complete_child(node->table[n],
+                                       comp, strvec);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void ec_node_or_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       size_t i;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = NULL;
+       node->len = 0;
+}
+
+static const struct ec_config_schema ec_node_or_subschema[] = {
+       {
+               .desc = "A child node which is part of the choice.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema ec_node_or_schema[] = {
+       {
+               .key = "children",
+               .desc = "The list of children nodes defining the choice "
+               "elements.",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = ec_node_or_subschema,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_or_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       struct ec_node **table = NULL;
+       size_t i, len = 0;
+
+       table = ec_node_config_node_list_to_table(
+               ec_config_dict_get(config, "children"), &len);
+       if (table == NULL)
+               goto fail;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = table;
+       node->len = len;
+
+       return 0;
+
+fail:
+       for (i = 0; i < len; i++)
+               ec_node_free(table[i]);
+       ec_free(table);
+       return -1;
+}
+
+static size_t
+ec_node_or_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       return node->len;
+}
+
+static int
+ec_node_or_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+
+       if (i >= node->len)
+               return -1;
+
+       *child = node->table[i];
+       /* each child node is referenced twice: once in the config and
+        * once in the node->table[] */
+       *refs = 2;
+       return 0;
+}
+
+static struct ec_node_type ec_node_or_type = {
+       .name = "or",
+       .schema = ec_node_or_schema,
+       .set_config = ec_node_or_set_config,
+       .parse = ec_node_or_parse,
+       .complete = ec_node_or_complete,
+       .size = sizeof(struct ec_node_or),
+       .free_priv = ec_node_or_free_priv,
+       .get_children_count = ec_node_or_get_children_count,
+       .get_child = ec_node_or_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_or_type);
+
+int ec_node_or_add(struct ec_node *gen_node, struct ec_node *child)
+{
+       struct ec_node_or *node = (struct ec_node_or *)gen_node;
+       const struct ec_config *cur_config = NULL;
+       struct ec_config *config = NULL, *children;
+       int ret;
+
+       assert(node != NULL);
+
+       /* XXX factorize this code in a helper */
+
+       if (ec_node_check_type(gen_node, &ec_node_or_type) < 0)
+               goto fail;
+
+       cur_config = ec_node_get_config(gen_node);
+       if (cur_config == NULL)
+               config = ec_config_dict();
+       else
+               config = ec_config_dup(cur_config);
+       if (config == NULL)
+               goto fail;
+
+       children = ec_config_dict_get(config, "children");
+       if (children == NULL) {
+               children = ec_config_list();
+               if (children == NULL)
+                       goto fail;
+
+               if (ec_config_dict_set(config, "children", children) < 0)
+                       goto fail; /* children list is freed on error */
+       }
+
+       if (ec_config_list_add(children, ec_config_node(child)) < 0) {
+               child = NULL;
+               goto fail;
+       }
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *__ec_node_or(const char *id, ...)
+{
+       struct ec_config *config = NULL, *children = NULL;
+       struct ec_node *gen_node = NULL;
+       struct ec_node *child;
+       va_list ap;
+       int ret;
+
+       va_start(ap, id);
+       child = va_arg(ap, struct ec_node *);
+
+       gen_node = ec_node_from_type(&ec_node_or_type, id);
+       if (gen_node == NULL)
+               goto fail_free_children;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail_free_children;
+
+       children = ec_config_list();
+       if (children == NULL)
+               goto fail_free_children;
+
+       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) {
+               if (child == NULL)
+                       goto fail_free_children;
+
+               if (ec_config_list_add(children, ec_config_node(child)) < 0) {
+                       child = NULL;
+                       goto fail_free_children;
+               }
+       }
+
+       if (ec_config_dict_set(config, "children", children) < 0) {
+               children = NULL; /* freed */
+               goto fail;
+       }
+       children = NULL;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       va_end(ap);
+
+       return gen_node;
+
+fail_free_children:
+       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *))
+               ec_node_free(child);
+fail:
+       ec_node_free(gen_node); /* will also free added children */
+       ec_config_free(children);
+       ec_config_free(config);
+       va_end(ap);
+
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_or_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = EC_NODE_OR(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_str(EC_NO_ID, "bar")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, " ");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foox");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "toto");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       ec_node_free(node);
+
+       /* test completion */
+       node = EC_NODE_OR(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_str(EC_NO_ID, "bar"),
+               ec_node_str(EC_NO_ID, "bar2"),
+               ec_node_str(EC_NO_ID, "toto"),
+               ec_node_str(EC_NO_ID, "titi")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "b", EC_NODE_ENDLIST,
+               "bar", "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", EC_NODE_ENDLIST,
+               "bar", "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "t", EC_NODE_ENDLIST,
+               "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "to", EC_NODE_ENDLIST,
+               "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_or_test = {
+       .name = "node_or",
+       .test = ec_node_or_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_or_test);
diff --git a/src/ecoli_node_re.c b/src/ecoli_node_re.c
new file mode 100644 (file)
index 0000000..69f352c
--- /dev/null
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <regex.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_config.h>
+#include <ecoli_node_re.h>
+
+EC_LOG_TYPE_REGISTER(node_re);
+
+struct ec_node_re {
+       struct ec_node gen;
+       char *re_str;
+       regex_t re;
+};
+
+static int
+ec_node_re_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_re *node = (struct ec_node_re *)gen_node;
+       const char *str;
+       regmatch_t pos;
+
+       (void)state;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       str = ec_strvec_val(strvec, 0);
+       if (regexec(&node->re, str, 1, &pos, 0) != 0)
+               return EC_PARSE_NOMATCH;
+       if (pos.rm_so != 0 || pos.rm_eo != (int)strlen(str))
+               return EC_PARSE_NOMATCH;
+
+       return 1;
+}
+
+static void ec_node_re_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_re *node = (struct ec_node_re *)gen_node;
+
+       if (node->re_str != NULL) {
+               ec_free(node->re_str);
+               regfree(&node->re);
+       }
+}
+
+static const struct ec_config_schema ec_node_re_schema[] = {
+       {
+               .key = "pattern",
+               .desc = "The pattern to match.",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_re_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_re *node = (struct ec_node_re *)gen_node;
+       const struct ec_config *value = NULL;
+       char *s = NULL;
+       regex_t re;
+       int ret;
+
+       value = ec_config_dict_get(config, "pattern");
+       if (value == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       s = ec_strdup(value->string);
+       if (s == NULL)
+               goto fail;
+
+       ret = regcomp(&re, s, REG_EXTENDED);
+       if (ret != 0) {
+               if (ret == REG_ESPACE)
+                       errno = ENOMEM;
+               else
+                       errno = EINVAL;
+               goto fail;
+       }
+
+       if (node->re_str != NULL) {
+               ec_free(node->re_str);
+               regfree(&node->re);
+       }
+       node->re_str = s;
+       node->re = re;
+
+       return 0;
+
+fail:
+       ec_free(s);
+       return -1;
+}
+
+static struct ec_node_type ec_node_re_type = {
+       .name = "re",
+       .schema = ec_node_re_schema,
+       .set_config = ec_node_re_set_config,
+       .parse = ec_node_re_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_re),
+       .free_priv = ec_node_re_free_priv,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_re_type);
+
+int ec_node_re_set_regexp(struct ec_node *gen_node, const char *str)
+{
+       struct ec_config *config = NULL;
+       int ret;
+
+       EC_CHECK_ARG(str != NULL, -1, EINVAL);
+
+       if (ec_node_check_type(gen_node, &ec_node_re_type) < 0)
+               goto fail;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(config, "pattern", ec_config_string(str));
+       if (ret < 0)
+               goto fail;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL;
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       return -1;
+}
+
+struct ec_node *ec_node_re(const char *id, const char *re_str)
+{
+       struct ec_node *gen_node = NULL;
+
+       gen_node = ec_node_from_type(&ec_node_re_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       if (ec_node_re_set_regexp(gen_node, re_str) < 0)
+               goto fail;
+
+       return gen_node;
+
+fail:
+       ec_node_free(gen_node);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_re_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_re(EC_NO_ID, "fo+|bar");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_re_test = {
+       .name = "node_re",
+       .test = ec_node_re_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_re_test);
diff --git a/src/ecoli_node_re_lex.c b/src/ecoli_node_re_lex.c
new file mode 100644 (file)
index 0000000..4ebb9fd
--- /dev/null
@@ -0,0 +1,564 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <regex.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_node.h>
+#include <ecoli_complete.h>
+#include <ecoli_parse.h>
+#include <ecoli_config.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_int.h>
+#include <ecoli_node_re_lex.h>
+
+EC_LOG_TYPE_REGISTER(node_re_lex);
+
+struct regexp_pattern {
+       char *pattern;
+       char *attr_name;
+       regex_t r;
+       bool keep;
+};
+
+struct ec_node_re_lex {
+       struct ec_node gen;
+       struct ec_node *child;
+       struct regexp_pattern *table;
+       size_t len;
+};
+
+static struct ec_strvec *
+tokenize(struct regexp_pattern *table, size_t table_len, const char *str)
+{
+       struct ec_strvec *strvec = NULL;
+       struct ec_keyval *attrs = NULL;
+       char *dup = NULL;
+       char c;
+       size_t len, off = 0;
+       size_t i;
+       int ret;
+       regmatch_t pos;
+
+       dup = ec_strdup(str);
+       if (dup == NULL)
+               goto fail;
+
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       len = strlen(dup);
+       while (off < len) {
+               for (i = 0; i < table_len; i++) {
+                       ret = regexec(&table[i].r, &dup[off], 1, &pos, 0);
+                       if (ret != 0)
+                               continue;
+                       if (pos.rm_so != 0 || pos.rm_eo == 0) {
+                               ret = -1;
+                               continue;
+                       }
+
+                       if (table[i].keep == 0)
+                               break;
+
+                       c = dup[pos.rm_eo + off];
+                       dup[pos.rm_eo + off] = '\0';
+                       EC_LOG(EC_LOG_DEBUG, "re_lex match <%s>\n", &dup[off]);
+                       if (ec_strvec_add(strvec, &dup[off]) < 0)
+                               goto fail;
+
+                       if (table[i].attr_name != NULL) {
+                               attrs = ec_keyval();
+                               if (attrs == NULL)
+                                       goto fail;
+                               if (ec_keyval_set(attrs, table[i].attr_name,
+                                               NULL, NULL) < 0)
+                                       goto fail;
+                               if (ec_strvec_set_attrs(strvec,
+                                               ec_strvec_len(strvec) - 1,
+                                               attrs) < 0) {
+                                       attrs = NULL;
+                                       goto fail;
+                               }
+                               attrs = NULL;
+                       }
+
+                       dup[pos.rm_eo + off] = c;
+                       break;
+               }
+
+               if (ret != 0)
+                       goto fail;
+
+               off += pos.rm_eo;
+       }
+
+       ec_free(dup);
+       return strvec;
+
+fail:
+       ec_free(dup);
+       ec_strvec_free(strvec);
+       return NULL;
+}
+
+static int
+ec_node_re_lex_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+       struct ec_strvec *new_vec = NULL;
+       struct ec_parse *child_parse;
+       const char *str;
+       int ret;
+
+       if (node->child == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (ec_strvec_len(strvec) == 0) {
+               new_vec = ec_strvec();
+       } else {
+               str = ec_strvec_val(strvec, 0);
+               new_vec = tokenize(node->table, node->len, str);
+       }
+       if (new_vec == NULL)
+               goto fail;
+
+       ret = ec_node_parse_child(node->child, state, new_vec);
+       if (ret < 0)
+               goto fail;
+
+       if ((unsigned)ret == ec_strvec_len(new_vec)) {
+               ret = 1;
+       } else if (ret != EC_PARSE_NOMATCH) {
+               child_parse = ec_parse_get_last_child(state);
+               ec_parse_unlink_child(state, child_parse);
+               ec_parse_free(child_parse);
+               ret = EC_PARSE_NOMATCH;
+       }
+
+       ec_strvec_free(new_vec);
+       new_vec = NULL;
+
+       return ret;
+
+ fail:
+       ec_strvec_free(new_vec);
+       return -1;
+}
+
+static void ec_node_re_lex_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+       unsigned int i;
+
+       ec_node_free(node->child);
+       for (i = 0; i < node->len; i++) {
+               ec_free(node->table[i].pattern);
+               ec_free(node->table[i].attr_name);
+               regfree(&node->table[i].r);
+       }
+
+       ec_free(node->table);
+}
+
+static size_t
+ec_node_re_lex_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_re_lex_get_child(const struct ec_node *gen_node, size_t i,
+                       struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *child = node->child;
+       *refs = 2;
+       return 0;
+}
+
+static const struct ec_config_schema ec_node_re_lex_dict[] = {
+       {
+               .key = "pattern",
+               .desc = "The pattern to match.",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .key = "keep",
+               .desc = "Whether to keep or drop the string matching "
+               "the regular expression.",
+               .type = EC_CONFIG_TYPE_BOOL,
+       },
+       {
+               .key = "attr",
+               .desc = "The optional attribute name to attach.",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema ec_node_re_lex_elt[] = {
+       {
+               .desc = "A pattern element.",
+               .type = EC_CONFIG_TYPE_DICT,
+               .subschema = ec_node_re_lex_dict,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema ec_node_re_lex_schema[] = {
+       {
+               .key = "patterns",
+               .desc = "The list of patterns elements.",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = ec_node_re_lex_elt,
+       },
+       {
+               .key = "child",
+               .desc = "The child node.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_re_lex_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_re_lex *node = (struct ec_node_re_lex *)gen_node;
+       struct regexp_pattern *table = NULL;
+       const struct ec_config *patterns, *child, *elt, *pattern, *keep, *attr;
+       char *pattern_str = NULL, *attr_name = NULL;
+       ssize_t i, n = 0;
+       int ret;
+
+       child = ec_config_dict_get(config, "child");
+       if (child == NULL)
+               goto fail;
+       if (ec_config_get_type(child) != EC_CONFIG_TYPE_NODE) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       patterns = ec_config_dict_get(config, "patterns");
+       if (patterns != NULL) {
+               n = ec_config_count(patterns);
+               if (n < 0)
+                       goto fail;
+
+               table = ec_calloc(n, sizeof(*table));
+               if (table == NULL)
+                       goto fail;
+
+               n = 0;
+               TAILQ_FOREACH(elt, &patterns->list, next) {
+                       if (ec_config_get_type(elt) != EC_CONFIG_TYPE_DICT) {
+                               errno = EINVAL;
+                               goto fail;
+                       }
+                       pattern = ec_config_dict_get(elt, "pattern");
+                       if (pattern == NULL) {
+                               errno = EINVAL;
+                               goto fail;
+                       }
+                       if (ec_config_get_type(pattern) != EC_CONFIG_TYPE_STRING) {
+                               errno = EINVAL;
+                               goto fail;
+                       }
+                       keep = ec_config_dict_get(elt, "keep");
+                       if (keep == NULL) {
+                               errno = EINVAL;
+                               goto fail;
+                       }
+                       if (ec_config_get_type(keep) != EC_CONFIG_TYPE_BOOL) {
+                               errno = EINVAL;
+                               goto fail;
+                       }
+                       attr = ec_config_dict_get(elt, "attr");
+                       if (attr != NULL && ec_config_get_type(attr) !=
+                                       EC_CONFIG_TYPE_STRING) {
+                               errno = EINVAL;
+                               goto fail;
+                       }
+                       pattern_str = ec_strdup(pattern->string);
+                       if (pattern_str == NULL)
+                               goto fail;
+                       if (attr != NULL && attr->string != NULL) {
+                               attr_name = ec_strdup(attr->string);
+                               if (attr_name == NULL)
+                                       goto fail;
+                       }
+
+                       ret = regcomp(&table[n].r, pattern_str, REG_EXTENDED);
+                       if (ret != 0) {
+                               EC_LOG(EC_LOG_ERR,
+                                       "Regular expression <%s> compilation failed: %d\n",
+                                       pattern_str, ret);
+                               if (ret == REG_ESPACE)
+                                       errno = ENOMEM;
+                               else
+                                       errno = EINVAL;
+                               goto fail;
+                       }
+                       table[n].pattern = pattern_str;
+                       table[n].keep = keep->boolean;
+                       table[n].attr_name = attr_name;
+                       pattern_str = NULL;
+                       attr_name = NULL;
+
+                       n++;
+               }
+       }
+
+       if (node->child != NULL)
+               ec_node_free(node->child);
+       node->child = ec_node_clone(child->node);
+       for (i = 0; i < (ssize_t)node->len; i++) {
+               ec_free(node->table[i].pattern);
+               regfree(&node->table[i].r);
+       }
+       ec_free(node->table);
+       node->table = table;
+       node->len = n;
+
+       return 0;
+
+fail:
+       if (table != NULL) {
+               for (i = 0; i < n; i++) {
+                       if (table[i].pattern != NULL) {
+                               ec_free(table[i].pattern);
+                               regfree(&table[i].r);
+                       }
+               }
+       }
+       ec_free(table);
+       ec_free(pattern_str);
+       return -1;
+}
+
+static struct ec_node_type ec_node_re_lex_type = {
+       .name = "re_lex",
+       .schema = ec_node_re_lex_schema,
+       .set_config = ec_node_re_lex_set_config,
+       .parse = ec_node_re_lex_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_re_lex),
+       .free_priv = ec_node_re_lex_free_priv,
+       .get_children_count = ec_node_re_lex_get_children_count,
+       .get_child = ec_node_re_lex_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_re_lex_type);
+
+int ec_node_re_lex_add(struct ec_node *gen_node, const char *pattern, int keep,
+       const char *attr_name)
+{
+       const struct ec_config *cur_config = NULL;
+       struct ec_config *config = NULL, *patterns = NULL, *elt = NULL;
+       int ret;
+
+       if (ec_node_check_type(gen_node, &ec_node_re_lex_type) < 0)
+               goto fail;
+
+       elt = ec_config_dict();
+       if (elt == NULL)
+               goto fail;
+       if (ec_config_dict_set(elt, "pattern", ec_config_string(pattern)) < 0)
+               goto fail;
+       if (ec_config_dict_set(elt, "keep", ec_config_bool(keep)) < 0)
+               goto fail;
+       if (attr_name != NULL) {
+               if (ec_config_dict_set(elt, "attr",
+                                       ec_config_string(attr_name)) < 0)
+                       goto fail;
+       }
+
+       cur_config = ec_node_get_config(gen_node);
+       if (cur_config == NULL)
+               config = ec_config_dict();
+       else
+               config = ec_config_dup(cur_config);
+       if (config == NULL)
+               goto fail;
+
+       patterns = ec_config_dict_get(config, "patterns");
+       if (patterns == NULL) {
+               patterns = ec_config_list();
+               if (patterns == NULL)
+                       goto fail;
+
+               if (ec_config_dict_set(config, "patterns", patterns) < 0)
+                       goto fail; /* patterns list is freed on error */
+       }
+
+       if (ec_config_list_add(patterns, elt) < 0) {
+               elt = NULL;
+               goto fail;
+       }
+       elt = NULL;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       ec_config_free(elt);
+       return -1;
+}
+
+static int
+ec_node_re_lex_set_child(struct ec_node *gen_node, struct ec_node *child)
+{
+       const struct ec_config *cur_config = NULL;
+       struct ec_config *config = NULL;
+       int ret;
+
+       if (ec_node_check_type(gen_node, &ec_node_re_lex_type) < 0)
+               goto fail;
+
+       cur_config = ec_node_get_config(gen_node);
+       if (cur_config == NULL)
+               config = ec_config_dict();
+       else
+               config = ec_config_dup(cur_config);
+       if (config == NULL)
+               goto fail;
+
+       if (ec_config_dict_set(config, "child", ec_config_node(child)) < 0) {
+               child = NULL; /* freed */
+               goto fail;
+       }
+       child = NULL; /* freed */
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *ec_node_re_lex(const char *id, struct ec_node *child)
+{
+       struct ec_node *gen_node = NULL;
+
+       if (child == NULL)
+               return NULL;
+
+       gen_node = ec_node_from_type(&ec_node_re_lex_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       if (ec_node_re_lex_set_child(gen_node, child) < 0) {
+               child = NULL; /* freed */
+               goto fail;
+       }
+
+       return gen_node;
+
+fail:
+       ec_node_free(gen_node);
+       ec_node_free(child);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_re_lex_testcase(void)
+{
+       struct ec_node *node;
+       int ret, testres = 0;
+
+       node = ec_node_re_lex(EC_NO_ID,
+               ec_node_many(EC_NO_ID,
+                       EC_NODE_OR(EC_NO_ID,
+                               ec_node_str(EC_NO_ID, "foo"),
+                               ec_node_str(EC_NO_ID, "bar"),
+                               ec_node_int(EC_NO_ID, 0, 1000, 0)
+                       ), 0, 0
+               )
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+
+       ret = ec_node_re_lex_add(node, "[a-zA-Z]+", 1, NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "[0-9]+", 1, NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "=", 1, NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "-", 1, NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "\\+", 1, NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       ret = ec_node_re_lex_add(node, "[       ]+", 0, NULL);
+       testres |= EC_TEST_CHECK(ret == 0, "cannot add regexp");
+       if (ret != 0) {
+               EC_LOG(EC_LOG_ERR, "cannot add regexp to node\n");
+               ec_node_free(node);
+               return -1;
+       }
+
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo bar  324 bar234");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar324");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
+
+       /* no completion */
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_re_lex_test = {
+       .name = "node_re_lex",
+       .test = ec_node_re_lex_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_re_lex_test);
diff --git a/src/ecoli_node_seq.c b/src/ecoli_node_seq.c
new file mode 100644 (file)
index 0000000..ff0c5de
--- /dev/null
@@ -0,0 +1,442 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_helper.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_option.h>
+#include <ecoli_node_or.h>
+#include <ecoli_node_many.h>
+#include <ecoli_node_seq.h>
+
+EC_LOG_TYPE_REGISTER(node_seq);
+
+struct ec_node_seq {
+       struct ec_node gen;
+       struct ec_node **table;
+       size_t len;
+};
+
+static int
+ec_node_seq_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       struct ec_strvec *childvec = NULL;
+       size_t len = 0;
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < node->len; i++) {
+               childvec = ec_strvec_ndup(strvec, len,
+                       ec_strvec_len(strvec) - len);
+               if (childvec == NULL)
+                       goto fail;
+
+               ret = ec_node_parse_child(node->table[i], state, childvec);
+               if (ret < 0)
+                       goto fail;
+
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if (ret == EC_PARSE_NOMATCH) {
+                       ec_parse_free_children(state);
+                       return EC_PARSE_NOMATCH;
+               }
+
+               len += ret;
+       }
+
+       return len;
+
+fail:
+       ec_strvec_free(childvec);
+       return -1;
+}
+
+static int
+__ec_node_seq_complete(struct ec_node **table, size_t table_len,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_parse *parse = ec_comp_get_state(comp);
+       struct ec_strvec *childvec = NULL;
+       unsigned int i;
+       int ret;
+
+       if (table_len == 0)
+               return 0;
+
+       /*
+        * Example of completion for a sequence node = [n1,n2] and an
+        * input = [a,b,c,d]:
+        *
+        * result = complete(n1, [a,b,c,d]) +
+        *    complete(n2, [b,c,d]) if n1 matches [a] +
+        *    complete(n2, [c,d]) if n1 matches [a,b] +
+        *    complete(n2, [d]) if n1 matches [a,b,c] +
+        *    complete(n2, []) if n1 matches [a,b,c,d]
+        */
+
+       /* first, try to complete with the first node of the table */
+       ret = ec_node_complete_child(table[0], comp, strvec);
+       if (ret < 0)
+               goto fail;
+
+       /* then, if the first node of the table matches the beginning of the
+        * strvec, try to complete the rest */
+       for (i = 0; i < ec_strvec_len(strvec); i++) {
+               childvec = ec_strvec_ndup(strvec, 0, i);
+               if (childvec == NULL)
+                       goto fail;
+
+               ret = ec_node_parse_child(table[0], parse, childvec);
+               if (ret < 0)
+                       goto fail;
+
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if ((unsigned int)ret != i) {
+                       if (ret != EC_PARSE_NOMATCH)
+                               ec_parse_del_last_child(parse);
+                       continue;
+               }
+
+               childvec = ec_strvec_ndup(strvec, i, ec_strvec_len(strvec) - i);
+               if (childvec == NULL) {
+                       ec_parse_del_last_child(parse);
+                       goto fail;
+               }
+
+               ret = __ec_node_seq_complete(&table[1],
+                                       table_len - 1,
+                                       comp, childvec);
+               ec_parse_del_last_child(parse);
+               ec_strvec_free(childvec);
+               childvec = NULL;
+
+               if (ret < 0)
+                       goto fail;
+       }
+
+       return 0;
+
+fail:
+       ec_strvec_free(childvec);
+       return -1;
+}
+
+static int
+ec_node_seq_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+
+       return __ec_node_seq_complete(node->table, node->len, comp,
+                               strvec);
+}
+
+static void ec_node_seq_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       size_t i;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = NULL;
+       node->len = 0;
+}
+
+static const struct ec_config_schema ec_node_seq_subschema[] = {
+       {
+               .desc = "A child node which is part of the sequence.",
+               .type = EC_CONFIG_TYPE_NODE,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static const struct ec_config_schema ec_node_seq_schema[] = {
+       {
+               .key = "children",
+               .desc = "The list of children nodes, to be parsed in sequence.",
+               .type = EC_CONFIG_TYPE_LIST,
+               .subschema = ec_node_seq_subschema,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_seq_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       struct ec_node **table = NULL;
+       size_t i, len = 0;
+
+       table = ec_node_config_node_list_to_table(
+               ec_config_dict_get(config, "children"), &len);
+       if (table == NULL)
+               goto fail;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+       node->table = table;
+       node->len = len;
+
+       return 0;
+
+fail:
+       for (i = 0; i < len; i++)
+               ec_node_free(table[i]);
+       ec_free(table);
+       return -1;
+}
+
+static size_t
+ec_node_seq_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       return node->len;
+}
+
+static int
+ec_node_seq_get_child(const struct ec_node *gen_node, size_t i,
+               struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+
+       if (i >= node->len)
+               return -1;
+
+       *child = node->table[i];
+       /* each child node is referenced twice: once in the config and
+        * once in the node->table[] */
+       *refs = 2;
+       return 0;
+}
+
+static struct ec_node_type ec_node_seq_type = {
+       .name = "seq",
+       .schema = ec_node_seq_schema,
+       .set_config = ec_node_seq_set_config,
+       .parse = ec_node_seq_parse,
+       .complete = ec_node_seq_complete,
+       .size = sizeof(struct ec_node_seq),
+       .free_priv = ec_node_seq_free_priv,
+       .get_children_count = ec_node_seq_get_children_count,
+       .get_child = ec_node_seq_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_seq_type);
+
+int ec_node_seq_add(struct ec_node *gen_node, struct ec_node *child)
+{
+       struct ec_node_seq *node = (struct ec_node_seq *)gen_node;
+       const struct ec_config *cur_config = NULL;
+       struct ec_config *config = NULL, *children;
+       int ret;
+
+       assert(node != NULL);
+
+       /* XXX factorize this code in a helper */
+
+       if (ec_node_check_type(gen_node, &ec_node_seq_type) < 0)
+               goto fail;
+
+       cur_config = ec_node_get_config(gen_node);
+       if (cur_config == NULL)
+               config = ec_config_dict();
+       else
+               config = ec_config_dup(cur_config);
+       if (config == NULL)
+               goto fail;
+
+       children = ec_config_dict_get(config, "children");
+       if (children == NULL) {
+               children = ec_config_list();
+               if (children == NULL)
+                       goto fail;
+
+               if (ec_config_dict_set(config, "children", children) < 0)
+                       goto fail; /* children list is freed on error */
+       }
+
+       if (ec_config_list_add(children, ec_config_node(child)) < 0) {
+               child = NULL;
+               goto fail;
+       }
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *__ec_node_seq(const char *id, ...)
+{
+       struct ec_config *config = NULL, *children = NULL;
+       struct ec_node *gen_node = NULL;
+       struct ec_node *child;
+       va_list ap;
+       int ret;
+
+       va_start(ap, id);
+       child = va_arg(ap, struct ec_node *);
+
+       gen_node = ec_node_from_type(&ec_node_seq_type, id);
+       if (gen_node == NULL)
+               goto fail_free_children;
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail_free_children;
+
+       children = ec_config_list();
+       if (children == NULL)
+               goto fail_free_children;
+
+       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *)) {
+               if (child == NULL)
+                       goto fail_free_children;
+
+               if (ec_config_list_add(children, ec_config_node(child)) < 0) {
+                       child = NULL;
+                       goto fail_free_children;
+               }
+       }
+
+       if (ec_config_dict_set(config, "children", children) < 0) {
+               children = NULL; /* freed */
+               goto fail;
+       }
+       children = NULL;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL; /* freed */
+       if (ret < 0)
+               goto fail;
+
+       va_end(ap);
+
+       return gen_node;
+
+fail_free_children:
+       for (; child != EC_NODE_ENDLIST; child = va_arg(ap, struct ec_node *))
+               ec_node_free(child);
+fail:
+       ec_node_free(gen_node); /* will also free added children */
+       ec_config_free(children);
+       ec_config_free(config);
+       va_end(ap);
+
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_seq_testcase(void)
+{
+       struct ec_node *node = NULL;
+       int testres = 0;
+
+       node = EC_NODE_SEQ(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_str(EC_NO_ID, "bar")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "toto");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foox", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo", "barx");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "bar", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "", "foo");
+
+       testres |= (ec_node_seq_add(node, ec_node_str(EC_NO_ID, "grr")) < 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "foo", "bar", "grr");
+
+       ec_node_free(node);
+
+       /* test completion */
+       node = EC_NODE_SEQ(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_option(EC_NO_ID, ec_node_str(EC_NO_ID, "toto")),
+               ec_node_str(EC_NO_ID, "bar")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "", EC_NODE_ENDLIST,
+               "bar", "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "t", EC_NODE_ENDLIST,
+               "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "b", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", "bar", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foobarx", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_seq_test = {
+       .name = "node_seq",
+       .test = ec_node_seq_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_seq_test);
diff --git a/src/ecoli_node_sh_lex.c b/src/ecoli_node_sh_lex.c
new file mode 100644 (file)
index 0000000..e27f21b
--- /dev/null
@@ -0,0 +1,491 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_string.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_option.h>
+#include <ecoli_node_sh_lex.h>
+
+EC_LOG_TYPE_REGISTER(node_sh_lex);
+
+struct ec_node_sh_lex {
+       struct ec_node gen;
+       struct ec_node *child;
+};
+
+static size_t eat_spaces(const char *str)
+{
+       size_t i = 0;
+
+       /* skip spaces */
+       while (isblank(str[i]))
+               i++;
+
+       return i;
+}
+
+/*
+ * Allocate a new string which is a copy of the input string with quotes
+ * removed. If quotes are not closed properly, set missing_quote to the
+ * missing quote char.
+ */
+static char *unquote_str(const char *str, size_t n, int allow_missing_quote,
+       char *missing_quote)
+{
+       unsigned s = 1, d = 0;
+       char quote = str[0];
+       char *dst;
+       int closed = 0;
+
+       dst = ec_malloc(n);
+       if (dst == NULL) {
+               errno = ENOMEM;
+               return NULL;
+       }
+
+       /* copy string and remove quotes */
+       while (s < n && d < n && str[s] != '\0') {
+               if (str[s] == '\\' && str[s+1] == quote) {
+                       dst[d++] = quote;
+                       s += 2;
+                       continue;
+               }
+               if (str[s] == '\\' && str[s+1] == '\\') {
+                       dst[d++] = '\\';
+                       s += 2;
+                       continue;
+               }
+               if (str[s] == quote) {
+                       s++;
+                       closed = 1;
+                       break;
+               }
+               dst[d++] = str[s++];
+       }
+
+       /* not enough room in dst buffer (should not happen) */
+       if (d >= n) {
+               ec_free(dst);
+               errno = EMSGSIZE;
+               return NULL;
+       }
+
+       /* quote not closed */
+       if (closed == 0) {
+               if (missing_quote != NULL)
+                       *missing_quote = str[0];
+               if (allow_missing_quote == 0) {
+                       ec_free(dst);
+                       errno = EBADMSG;
+                       return NULL;
+               }
+       }
+       dst[d++] = '\0';
+
+       return dst;
+}
+
+static size_t eat_quoted_str(const char *str)
+{
+       size_t i = 0;
+       char quote = str[0];
+
+       while (str[i] != '\0') {
+               if (str[i] != '\\' && str[i+1] == quote)
+                       return i + 2;
+               i++;
+       }
+
+       /* unclosed quote, will be detected later */
+       return i;
+}
+
+static size_t eat_str(const char *str)
+{
+       size_t i = 0;
+
+       /* eat chars until we find a quote, space, or end of string  */
+       while (!isblank(str[i]) && str[i] != '\0' &&
+                       str[i] != '"' && str[i] != '\'')
+               i++;
+
+       return i;
+}
+
+static struct ec_strvec *tokenize(const char *str, int completion,
+       int allow_missing_quote, char *missing_quote)
+{
+       struct ec_strvec *strvec = NULL;
+       size_t off = 0, len, suboff, sublen;
+       char *word = NULL, *concat = NULL, *tmp;
+       int last_is_space = 1;
+
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       while (str[off] != '\0') {
+               if (missing_quote != NULL)
+                       *missing_quote = '\0';
+               len = eat_spaces(&str[off]);
+               if (len > 0)
+                       last_is_space = 1;
+               off += len;
+
+               len = 0;
+               suboff = off;
+               while (str[suboff] != '\0') {
+                       if (missing_quote != NULL)
+                               *missing_quote = '\0';
+                       last_is_space = 0;
+                       if (str[suboff] == '"' || str[suboff] == '\'') {
+                               sublen = eat_quoted_str(&str[suboff]);
+                               word = unquote_str(&str[suboff], sublen,
+                                       allow_missing_quote, missing_quote);
+                       } else {
+                               sublen = eat_str(&str[suboff]);
+                               if (sublen == 0)
+                                       break;
+                               word = ec_strndup(&str[suboff], sublen);
+                       }
+
+                       if (word == NULL)
+                               goto fail;
+
+                       len += sublen;
+                       suboff += sublen;
+
+                       if (concat == NULL) {
+                               concat = word;
+                               word = NULL;
+                       } else {
+                               tmp = ec_realloc(concat, len + 1);
+                               if (tmp == NULL)
+                                       goto fail;
+                               concat = tmp;
+                               strcat(concat, word);
+                               ec_free(word);
+                               word = NULL;
+                       }
+               }
+
+               if (concat != NULL) {
+                       if (ec_strvec_add(strvec, concat) < 0)
+                               goto fail;
+                       ec_free(concat);
+                       concat = NULL;
+               }
+
+               off += len;
+       }
+
+       /* in completion mode, append an empty string in the vector if
+        * the input string ends with space */
+       if (completion && last_is_space) {
+               if (ec_strvec_add(strvec, "") < 0)
+                       goto fail;
+       }
+
+       return strvec;
+
+ fail:
+       ec_free(word);
+       ec_free(concat);
+       ec_strvec_free(strvec);
+       return NULL;
+}
+
+static int
+ec_node_sh_lex_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+       struct ec_strvec *new_vec = NULL;
+       struct ec_parse *child_parse;
+       const char *str;
+       int ret;
+
+       if (ec_strvec_len(strvec) == 0) {
+               new_vec = ec_strvec();
+       } else {
+               str = ec_strvec_val(strvec, 0);
+               new_vec = tokenize(str, 0, 0, NULL);
+       }
+       if (new_vec == NULL && errno == EBADMSG) /* quotes not closed */
+               return EC_PARSE_NOMATCH;
+       if (new_vec == NULL)
+               goto fail;
+
+       ret = ec_node_parse_child(node->child, state, new_vec);
+       if (ret < 0)
+               goto fail;
+
+       if ((unsigned)ret == ec_strvec_len(new_vec)) {
+               ret = 1;
+       } else if (ret != EC_PARSE_NOMATCH) {
+               child_parse = ec_parse_get_last_child(state);
+               ec_parse_unlink_child(state, child_parse);
+               ec_parse_free(child_parse);
+               ret = EC_PARSE_NOMATCH;
+       }
+
+       ec_strvec_free(new_vec);
+       new_vec = NULL;
+
+       return ret;
+
+ fail:
+       ec_strvec_free(new_vec);
+       return -1;
+}
+
+static int
+ec_node_sh_lex_complete(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+       struct ec_comp *tmp_comp = NULL;
+       struct ec_strvec *new_vec = NULL;
+       struct ec_comp_iter *iter = NULL;
+       struct ec_comp_item *item = NULL;
+       char *new_str = NULL;
+       const char *str;
+       char missing_quote = '\0';
+       int ret;
+
+       if (ec_strvec_len(strvec) != 1)
+               return 0;
+
+       str = ec_strvec_val(strvec, 0);
+       new_vec = tokenize(str, 1, 1, &missing_quote);
+       if (new_vec == NULL)
+               goto fail;
+
+       /* we will store the completions in a temporary struct, because
+        * we want to update them (ex: add missing quotes) */
+       tmp_comp = ec_comp(ec_comp_get_state(comp));
+       if (tmp_comp == NULL)
+               goto fail;
+
+       ret = ec_node_complete_child(node->child, tmp_comp, new_vec);
+       if (ret < 0)
+               goto fail;
+
+       /* add missing quote for full completions  */
+       if (missing_quote != '\0') {
+               iter = ec_comp_iter(tmp_comp, EC_COMP_FULL);
+               if (iter == NULL)
+                       goto fail;
+               while ((item = ec_comp_iter_next(iter)) != NULL) {
+                       str = ec_comp_item_get_str(item);
+                       if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str,
+                                       missing_quote) < 0) {
+                               new_str = NULL;
+                               goto fail;
+                       }
+                       if (ec_comp_item_set_str(item, new_str) < 0)
+                               goto fail;
+                       ec_free(new_str);
+                       new_str = NULL;
+
+                       str = ec_comp_item_get_completion(item);
+                       if (ec_asprintf(&new_str, "%s%c", str,
+                                       missing_quote) < 0) {
+                               new_str = NULL;
+                               goto fail;
+                       }
+                       if (ec_comp_item_set_completion(item, new_str) < 0)
+                               goto fail;
+                       ec_free(new_str);
+                       new_str = NULL;
+               }
+       }
+
+       ec_comp_iter_free(iter);
+       ec_strvec_free(new_vec);
+
+       ec_comp_merge(comp, tmp_comp);
+
+       return 0;
+
+ fail:
+       ec_comp_free(tmp_comp);
+       ec_comp_iter_free(iter);
+       ec_strvec_free(new_vec);
+       ec_free(new_str);
+
+       return -1;
+}
+
+static void ec_node_sh_lex_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+
+       ec_node_free(node->child);
+}
+
+static size_t
+ec_node_sh_lex_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+
+       if (node->child)
+               return 1;
+       return 0;
+}
+
+static int
+ec_node_sh_lex_get_child(const struct ec_node *gen_node, size_t i,
+       struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
+
+       if (i >= 1)
+               return -1;
+
+       *refs = 1;
+       *child = node->child;
+       return 0;
+}
+
+static struct ec_node_type ec_node_sh_lex_type = {
+       .name = "sh_lex",
+       .parse = ec_node_sh_lex_parse,
+       .complete = ec_node_sh_lex_complete,
+       .size = sizeof(struct ec_node_sh_lex),
+       .free_priv = ec_node_sh_lex_free_priv,
+       .get_children_count = ec_node_sh_lex_get_children_count,
+       .get_child = ec_node_sh_lex_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
+
+struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
+{
+       struct ec_node_sh_lex *node = NULL;
+
+       if (child == NULL)
+               return NULL;
+
+       node = (struct ec_node_sh_lex *)ec_node_from_type(&ec_node_sh_lex_type, id);
+       if (node == NULL) {
+               ec_node_free(child);
+               return NULL;
+       }
+
+       node->child = child;
+
+       return &node->gen;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_sh_lex_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_sh_lex(EC_NO_ID,
+               EC_NODE_SEQ(EC_NO_ID,
+                       ec_node_str(EC_NO_ID, "foo"),
+                       ec_node_option(EC_NO_ID,
+                               ec_node_str(EC_NO_ID, "toto")
+                       ),
+                       ec_node_str(EC_NO_ID, "bar")
+               )
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "  foo toto bar'");
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_sh_lex(EC_NO_ID,
+               EC_NODE_SEQ(EC_NO_ID,
+                       ec_node_str(EC_NO_ID, "foo"),
+                       ec_node_option(EC_NO_ID,
+                               ec_node_str(EC_NO_ID, "toto")
+                       ),
+                       ec_node_str(EC_NO_ID, "bar"),
+                       ec_node_str(EC_NO_ID, "titi")
+               )
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               " ", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo ", EC_NODE_ENDLIST,
+               "bar", "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo t", EC_NODE_ENDLIST,
+               "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo b", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo bar", EC_NODE_ENDLIST,
+               "bar", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo bar ", EC_NODE_ENDLIST,
+               "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo toto bar ", EC_NODE_ENDLIST,
+               "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo barx", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo 'b", EC_NODE_ENDLIST,
+               "'bar'", EC_NODE_ENDLIST);
+
+       ec_node_free(node);
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_sh_lex_test = {
+       .name = "node_sh_lex",
+       .test = ec_node_sh_lex_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_sh_lex_test);
diff --git a/src/ecoli_node_space.c b/src/ecoli_node_space.c
new file mode 100644 (file)
index 0000000..761ed76
--- /dev/null
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_malloc.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_space.h>
+
+EC_LOG_TYPE_REGISTER(node_space);
+
+struct ec_node_space {
+       struct ec_node gen;
+};
+
+static int
+ec_node_space_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       const char *str;
+       size_t len = 0;
+
+       (void)state;
+       (void)gen_node;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       str = ec_strvec_val(strvec, 0);
+       while (isspace(str[len]))
+               len++;
+       if (len == 0 || len != strlen(str))
+               return EC_PARSE_NOMATCH;
+
+       return 1;
+}
+
+static struct ec_node_type ec_node_space_type = {
+       .name = "space",
+       .parse = ec_node_space_parse,
+       .complete = ec_node_complete_unknown,
+       .size = sizeof(struct ec_node_space),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_space_type);
+
+/* LCOV_EXCL_START */
+static int ec_node_space_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node("space", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, " ");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, " ", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo ");
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node("space", EC_NO_ID);
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       /* never completes whatever the input */
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               " ", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_space_test = {
+       .name = "space",
+       .test = ec_node_space_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_space_test);
diff --git a/src/ecoli_node_str.c b/src/ecoli_node_str.c
new file mode 100644 (file)
index 0000000..d53ea39
--- /dev/null
@@ -0,0 +1,274 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_str.h>
+
+EC_LOG_TYPE_REGISTER(node_str);
+
+struct ec_node_str {
+       struct ec_node gen;
+       char *string;
+       unsigned len;
+};
+
+static int
+ec_node_str_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+       const char *str;
+
+       (void)state;
+
+       if (node->string == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSE_NOMATCH;
+
+       str = ec_strvec_val(strvec, 0);
+       if (strcmp(str, node->string) != 0)
+               return EC_PARSE_NOMATCH;
+
+       return 1;
+}
+
+static int
+ec_node_str_complete(const struct ec_node *gen_node,
+               struct ec_comp *comp,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+       const char *str;
+       size_t n = 0;
+
+       if (ec_strvec_len(strvec) != 1)
+               return 0;
+
+       str = ec_strvec_val(strvec, 0);
+       for (n = 0; n < node->len; n++) {
+               if (str[n] != node->string[n])
+                       break;
+       }
+
+       /* no completion */
+       if (str[n] != '\0')
+               return EC_PARSE_NOMATCH;
+
+       if (ec_comp_add_item(comp, gen_node, NULL, EC_COMP_FULL,
+                                       str, node->string) < 0)
+               return -1;
+
+       return 0;
+}
+
+static const char *ec_node_str_desc(const struct ec_node *gen_node)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+
+       return node->string;
+}
+
+static void ec_node_str_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+
+       ec_free(node->string);
+}
+
+static const struct ec_config_schema ec_node_str_schema[] = {
+       {
+               .key = "string",
+               .desc = "The string to match.",
+               .type = EC_CONFIG_TYPE_STRING,
+       },
+       {
+               .type = EC_CONFIG_TYPE_NONE,
+       },
+};
+
+static int ec_node_str_set_config(struct ec_node *gen_node,
+                               const struct ec_config *config)
+{
+       struct ec_node_str *node = (struct ec_node_str *)gen_node;
+       const struct ec_config *value = NULL;
+       char *s = NULL;
+
+       value = ec_config_dict_get(config, "string");
+       if (value == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       s = ec_strdup(value->string);
+       if (s == NULL)
+               goto fail;
+
+       ec_free(node->string);
+       node->string = s;
+       node->len = strlen(node->string);
+
+       return 0;
+
+fail:
+       ec_free(s);
+       return -1;
+}
+
+static struct ec_node_type ec_node_str_type = {
+       .name = "str",
+       .schema = ec_node_str_schema,
+       .set_config = ec_node_str_set_config,
+       .parse = ec_node_str_parse,
+       .complete = ec_node_str_complete,
+       .desc = ec_node_str_desc,
+       .size = sizeof(struct ec_node_str),
+       .free_priv = ec_node_str_free_priv,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_str_type);
+
+int ec_node_str_set_str(struct ec_node *gen_node, const char *str)
+{
+       struct ec_config *config = NULL;
+       int ret;
+
+       if (ec_node_check_type(gen_node, &ec_node_str_type) < 0)
+               goto fail;
+
+       if (str == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       config = ec_config_dict();
+       if (config == NULL)
+               goto fail;
+
+       ret = ec_config_dict_set(config, "string", ec_config_string(str));
+       if (ret < 0)
+               goto fail;
+
+       ret = ec_node_set_config(gen_node, config);
+       config = NULL;
+       if (ret < 0)
+               goto fail;
+
+       return 0;
+
+fail:
+       ec_config_free(config);
+       return -1;
+}
+
+struct ec_node *ec_node_str(const char *id, const char *str)
+{
+       struct ec_node *gen_node = NULL;
+
+       gen_node = ec_node_from_type(&ec_node_str_type, id);
+       if (gen_node == NULL)
+               goto fail;
+
+       if (ec_node_str_set_str(gen_node, str) < 0)
+               goto fail;
+
+       return gen_node;
+
+fail:
+       ec_node_free(gen_node);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_str_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = ec_node_str(EC_NO_ID, "foo");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK(!strcmp(ec_node_desc(node), "foo"),
+               "Invalid node description.");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foobar");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, " foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       ec_node_free(node);
+
+       node = ec_node_str(EC_NO_ID, "Здравствуйте");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "Здравствуйте",
+               "John!");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "");
+       ec_node_free(node);
+
+       /* an empty string node always matches */
+       node = ec_node_str(EC_NO_ID, "");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, -1, "foo");
+       ec_node_free(node);
+
+       /* test completion */
+       node = ec_node_str(EC_NO_ID, "foo");
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "foo", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_str_test = {
+       .name = "node_str",
+       .test = ec_node_str_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_str_test);
diff --git a/src/ecoli_node_subset.c b/src/ecoli_node_subset.c
new file mode 100644 (file)
index 0000000..e3184ef
--- /dev/null
@@ -0,0 +1,430 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_node_subset.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_or.h>
+#include <ecoli_test.h>
+
+EC_LOG_TYPE_REGISTER(node_subset);
+
+struct ec_node_subset {
+       struct ec_node gen;
+       struct ec_node **table;
+       unsigned int len;
+};
+
+struct parse_result {
+       size_t parse_len;           /* number of parsed nodes */
+       size_t len;                 /* consumed strings */
+};
+
+/* recursively find the longest list of nodes that matches: the state is
+ * updated accordingly. */
+static int
+__ec_node_subset_parse(struct parse_result *out, struct ec_node **table,
+               size_t table_len, struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node **child_table;
+       struct ec_strvec *childvec = NULL;
+       size_t i, j, len = 0;
+       struct parse_result best_result, result;
+       struct ec_parse *best_parse = NULL;
+       int ret;
+
+       if (table_len == 0)
+               return 0;
+
+       memset(&best_result, 0, sizeof(best_result));
+
+       child_table = ec_calloc(table_len - 1, sizeof(*child_table));
+       if (child_table == NULL)
+               goto fail;
+
+       for (i = 0; i < table_len; i++) {
+               /* try to parse elt i */
+               ret = ec_node_parse_child(table[i], state, strvec);
+               if (ret < 0)
+                       goto fail;
+
+               if (ret == EC_PARSE_NOMATCH)
+                       continue;
+
+               /* build a new table without elt i */
+               for (j = 0; j < table_len; j++) {
+                       if (j < i)
+                               child_table[j] = table[j];
+                       else if (j > i)
+                               child_table[j - 1] = table[j];
+               }
+
+               /* build a new strvec (ret is the len of matched strvec) */
+               len = ret;
+               childvec = ec_strvec_ndup(strvec, len,
+                                       ec_strvec_len(strvec) - len);
+               if (childvec == NULL)
+                       goto fail;
+
+               memset(&result, 0, sizeof(result));
+               ret = __ec_node_subset_parse(&result, child_table,
+                                       table_len - 1, state, childvec);
+               ec_strvec_free(childvec);
+               childvec = NULL;
+               if (ret < 0)
+                       goto fail;
+
+               /* if result is not the best, ignore */
+               if (result.parse_len < best_result.parse_len) {
+                       memset(&result, 0, sizeof(result));
+                       ec_parse_del_last_child(state);
+                       continue;
+               }
+
+               /* replace the previous best result */
+               ec_parse_free(best_parse);
+               best_parse = ec_parse_get_last_child(state);
+               ec_parse_unlink_child(state, best_parse);
+
+               best_result.parse_len = result.parse_len + 1;
+               best_result.len = len + result.len;
+
+               memset(&result, 0, sizeof(result));
+       }
+
+       *out = best_result;
+       ec_free(child_table);
+       if (best_parse != NULL)
+               ec_parse_link_child(state, best_parse);
+
+       return 0;
+
+ fail:
+       ec_parse_free(best_parse);
+       ec_strvec_free(childvec);
+       ec_free(child_table);
+       return -1;
+}
+
+static int
+ec_node_subset_parse(const struct ec_node *gen_node,
+               struct ec_parse *state,
+               const struct ec_strvec *strvec)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+       struct ec_parse *parse = NULL;
+       struct parse_result result;
+       int ret;
+
+       memset(&result, 0, sizeof(result));
+
+       ret = __ec_node_subset_parse(&result, node->table,
+                               node->len, state, strvec);
+       if (ret < 0)
+               goto fail;
+
+       /* if no child node matches, return a matching empty strvec */
+       if (result.parse_len == 0)
+               return 0;
+
+       return result.len;
+
+ fail:
+       ec_parse_free(parse);
+       return ret;
+}
+
+static int
+__ec_node_subset_complete(struct ec_node **table, size_t table_len,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_parse *parse = ec_comp_get_state(comp);
+       struct ec_strvec *childvec = NULL;
+       struct ec_node *save;
+       size_t i, len;
+       int ret;
+
+       /*
+        * example with table = [a, b, c]
+        * subset_complete([a,b,c], strvec) returns:
+        *   complete(a, strvec) + complete(b, strvec) + complete(c, strvec) +
+        *   + __subset_complete([b, c], childvec) if a matches
+        *   + __subset_complete([a, c], childvec) if b matches
+        *   + __subset_complete([a, b], childvec) if c matches
+        */
+
+       /* first, try to complete with each node of the table */
+       for (i = 0; i < table_len; i++) {
+               if (table[i] == NULL)
+                       continue;
+
+               ret = ec_node_complete_child(table[i],
+                                       comp, strvec);
+               if (ret < 0)
+                       goto fail;
+       }
+
+       /* then, if a node matches, advance in strvec and try to complete with
+        * all the other nodes */
+       for (i = 0; i < table_len; i++) {
+               if (table[i] == NULL)
+                       continue;
+
+               ret = ec_node_parse_child(table[i], parse, strvec);
+               if (ret < 0)
+                       goto fail;
+
+               if (ret == EC_PARSE_NOMATCH)
+                       continue;
+
+               len = ret;
+               childvec = ec_strvec_ndup(strvec, len,
+                                       ec_strvec_len(strvec) - len);
+               if (childvec == NULL) {
+                       ec_parse_del_last_child(parse);
+                       goto fail;
+               }
+
+               save = table[i];
+               table[i] = NULL;
+               ret = __ec_node_subset_complete(table, table_len,
+                                               comp, childvec);
+               table[i] = save;
+               ec_strvec_free(childvec);
+               childvec = NULL;
+               ec_parse_del_last_child(parse);
+
+               if (ret < 0)
+                       goto fail;
+       }
+
+       return 0;
+
+fail:
+       return -1;
+}
+
+static int
+ec_node_subset_complete(const struct ec_node *gen_node,
+                       struct ec_comp *comp,
+                       const struct ec_strvec *strvec)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+
+       return __ec_node_subset_complete(node->table, node->len, comp,
+                                       strvec);
+}
+
+static void ec_node_subset_free_priv(struct ec_node *gen_node)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+       size_t i;
+
+       for (i = 0; i < node->len; i++)
+               ec_node_free(node->table[i]);
+       ec_free(node->table);
+}
+
+static size_t
+ec_node_subset_get_children_count(const struct ec_node *gen_node)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+       return node->len;
+}
+
+static int
+ec_node_subset_get_child(const struct ec_node *gen_node, size_t i,
+                       struct ec_node **child, unsigned int *refs)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+
+       if (i >= node->len)
+               return -1;
+
+       *child = node->table[i];
+       *refs = 1;
+       return 0;
+}
+
+static struct ec_node_type ec_node_subset_type = {
+       .name = "subset",
+       .parse = ec_node_subset_parse,
+       .complete = ec_node_subset_complete,
+       .size = sizeof(struct ec_node_subset),
+       .free_priv = ec_node_subset_free_priv,
+       .get_children_count = ec_node_subset_get_children_count,
+       .get_child = ec_node_subset_get_child,
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_subset_type);
+
+int ec_node_subset_add(struct ec_node *gen_node, struct ec_node *child)
+{
+       struct ec_node_subset *node = (struct ec_node_subset *)gen_node;
+       struct ec_node **table;
+
+       assert(node != NULL); // XXX specific assert for it, like in libyang
+
+       if (child == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       if (ec_node_check_type(gen_node, &ec_node_subset_type) < 0)
+               goto fail;
+
+       table = ec_realloc(node->table, (node->len + 1) * sizeof(*node->table));
+       if (table == NULL) {
+               ec_node_free(child);
+               return -1;
+       }
+
+       node->table = table;
+       table[node->len] = child;
+       node->len++;
+
+       return 0;
+
+fail:
+       ec_node_free(child);
+       return -1;
+}
+
+struct ec_node *__ec_node_subset(const char *id, ...)
+{
+       struct ec_node *gen_node = NULL;
+       struct ec_node_subset *node = NULL;
+       struct ec_node *child;
+       va_list ap;
+       int fail = 0;
+
+       va_start(ap, id);
+
+       gen_node = ec_node_from_type(&ec_node_subset_type, id);
+       node = (struct ec_node_subset *)gen_node;
+       if (node == NULL)
+               fail = 1;;
+
+       for (child = va_arg(ap, struct ec_node *);
+            child != EC_NODE_ENDLIST;
+            child = va_arg(ap, struct ec_node *)) {
+
+               /* on error, don't quit the loop to avoid leaks */
+               if (fail == 1 || child == NULL ||
+                               ec_node_subset_add(gen_node, child) < 0) {
+                       fail = 1;
+                       ec_node_free(child);
+               }
+       }
+
+       if (fail == 1)
+               goto fail;
+
+       va_end(ap);
+       return gen_node;
+
+fail:
+       ec_node_free(gen_node); /* will also free children */
+       va_end(ap);
+       return NULL;
+}
+
+/* LCOV_EXCL_START */
+static int ec_node_subset_testcase(void)
+{
+       struct ec_node *node;
+       int testres = 0;
+
+       node = EC_NODE_SUBSET(EC_NO_ID,
+               EC_NODE_OR(EC_NO_ID,
+                       ec_node_str(EC_NO_ID, "foo"),
+                       ec_node_str(EC_NO_ID, "bar")),
+               ec_node_str(EC_NO_ID, "bar"),
+               ec_node_str(EC_NO_ID, "toto")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_PARSE(node, 0);
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "foo", "bar", "titi");
+       testres |= EC_TEST_CHECK_PARSE(node, 3, "bar", "foo", "toto");
+       testres |= EC_TEST_CHECK_PARSE(node, 1, "foo", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "bar");
+       testres |= EC_TEST_CHECK_PARSE(node, 2, "bar", "foo");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, " ");
+       testres |= EC_TEST_CHECK_PARSE(node, 0, "foox");
+       ec_node_free(node);
+
+       /* test completion */
+       node = EC_NODE_SUBSET(EC_NO_ID,
+               ec_node_str(EC_NO_ID, "foo"),
+               ec_node_str(EC_NO_ID, "bar"),
+               ec_node_str(EC_NO_ID, "bar2"),
+               ec_node_str(EC_NO_ID, "toto"),
+               ec_node_str(EC_NO_ID, "titi")
+       );
+       if (node == NULL) {
+               EC_LOG(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "foo", "bar", "bar2", "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "bar2", "bar", "foo", "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", "bar2", "", EC_NODE_ENDLIST,
+               "foo", "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "f", EC_NODE_ENDLIST,
+               "foo", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "b", EC_NODE_ENDLIST,
+               "bar", "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", EC_NODE_ENDLIST,
+               "bar", "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "bar", "b", EC_NODE_ENDLIST,
+               "bar2", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "t", EC_NODE_ENDLIST,
+               "toto", "titi", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "to", EC_NODE_ENDLIST,
+               "toto", EC_NODE_ENDLIST);
+       testres |= EC_TEST_CHECK_COMPLETE(node,
+               "x", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST);
+       ec_node_free(node);
+
+       return testres;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_subset_test = {
+       .name = "node_subset",
+       .test = ec_node_subset_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_subset_test);
diff --git a/src/ecoli_parse.c b/src/ecoli_parse.c
new file mode 100644 (file)
index 0000000..3d6be77
--- /dev/null
@@ -0,0 +1,544 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <ecoli_assert.h>
+#include <ecoli_malloc.h>
+#include <ecoli_strvec.h>
+#include <ecoli_keyval.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_node.h>
+#include <ecoli_node_sh_lex.h>
+#include <ecoli_node_str.h>
+#include <ecoli_node_seq.h>
+#include <ecoli_parse.h>
+
+EC_LOG_TYPE_REGISTER(parse);
+
+TAILQ_HEAD(ec_parse_list, ec_parse);
+
+struct ec_parse {
+       TAILQ_ENTRY(ec_parse) next;
+       struct ec_parse_list children;
+       struct ec_parse *parent;
+       const struct ec_node *node;
+       struct ec_strvec *strvec;
+       struct ec_keyval *attrs;
+};
+
+static int __ec_node_parse_child(const struct ec_node *node,
+                               struct ec_parse *state,
+                               bool is_root, const struct ec_strvec *strvec)
+{
+       struct ec_strvec *match_strvec;
+       struct ec_parse *child = NULL;
+       int ret;
+
+       if (ec_node_type(node)->parse == NULL) {
+               errno = ENOTSUP;
+               return -1;
+       }
+
+       if (!is_root) {
+               child = ec_parse(node);
+               if (child == NULL)
+                       return -1;
+
+               ec_parse_link_child(state, child);
+       } else {
+               child = state;
+       }
+       ret = ec_node_type(node)->parse(node, child, strvec);
+       if (ret < 0)
+               goto fail;
+
+       if (ret == EC_PARSE_NOMATCH) {
+               if (!is_root) {
+                       ec_parse_unlink_child(state, child);
+                       ec_parse_free(child);
+               }
+               return ret;
+       }
+
+       match_strvec = ec_strvec_ndup(strvec, 0, ret);
+       if (match_strvec == NULL)
+               goto fail;
+
+       child->strvec = match_strvec;
+
+       return ret;
+
+fail:
+       if (!is_root) {
+               ec_parse_unlink_child(state, child);
+               ec_parse_free(child);
+       }
+       return -1;
+}
+
+int ec_node_parse_child(const struct ec_node *node, struct ec_parse *state,
+                       const struct ec_strvec *strvec)
+{
+       assert(state != NULL);
+       return __ec_node_parse_child(node, state, false, strvec);
+}
+
+// XXX what is returned if no match ??
+struct ec_parse *ec_node_parse_strvec(const struct ec_node *node,
+                               const struct ec_strvec *strvec)
+{
+       struct ec_parse *parse = ec_parse(node);
+       int ret;
+
+       if (parse == NULL)
+               return NULL;
+
+       ret = __ec_node_parse_child(node, parse, true, strvec);
+       if (ret < 0) {
+               ec_parse_free(parse);
+               return NULL;
+       }
+
+       return parse;
+}
+
+struct ec_parse *ec_node_parse(const struct ec_node *node, const char *str)
+{
+       struct ec_strvec *strvec = NULL;
+       struct ec_parse *parse = NULL;
+
+       errno = ENOMEM;
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       if (ec_strvec_add(strvec, str) < 0)
+               goto fail;
+
+       parse = ec_node_parse_strvec(node, strvec);
+       if (parse == NULL)
+               goto fail;
+
+       ec_strvec_free(strvec);
+       return parse;
+
+ fail:
+       ec_strvec_free(strvec);
+       ec_parse_free(parse);
+       return NULL;
+}
+
+struct ec_parse *ec_parse(const struct ec_node *node)
+{
+       struct ec_parse *parse = NULL;
+
+       parse = ec_calloc(1, sizeof(*parse));
+       if (parse == NULL)
+               goto fail;
+
+       TAILQ_INIT(&parse->children);
+
+       parse->node = node;
+       parse->attrs = ec_keyval();
+       if (parse->attrs == NULL)
+               goto fail;
+
+       return parse;
+
+ fail:
+       if (parse != NULL)
+               ec_keyval_free(parse->attrs);
+       ec_free(parse);
+
+       return NULL;
+}
+
+static struct ec_parse *
+__ec_parse_dup(const struct ec_parse *root, const struct ec_parse *ref,
+               struct ec_parse **new_ref)
+{
+       struct ec_parse *dup = NULL;
+       struct ec_parse *child, *dup_child;
+       struct ec_keyval *attrs = NULL;
+
+       if (root == NULL)
+               return NULL;
+
+       dup = ec_parse(root->node);
+       if (dup == NULL)
+               return NULL;
+
+       if (root == ref)
+               *new_ref = dup;
+
+       attrs = ec_keyval_dup(root->attrs);
+       if (attrs == NULL)
+               goto fail;
+       ec_keyval_free(dup->attrs);
+       dup->attrs = attrs;
+
+       if (root->strvec != NULL) {
+               dup->strvec = ec_strvec_dup(root->strvec);
+               if (dup->strvec == NULL)
+                       goto fail;
+       }
+
+       TAILQ_FOREACH(child, &root->children, next) {
+               dup_child = __ec_parse_dup(child, ref, new_ref);
+               if (dup_child == NULL)
+                       goto fail;
+               ec_parse_link_child(dup, dup_child);
+       }
+
+       return dup;
+
+fail:
+       ec_parse_free(dup);
+       return NULL;
+}
+
+struct ec_parse *ec_parse_dup(const struct ec_parse *parse)
+{
+       const struct ec_parse *root;
+       struct ec_parse *dup_root, *dup = NULL;
+
+       root = ec_parse_get_root(parse);
+       dup_root = __ec_parse_dup(root, parse, &dup);
+       if (dup_root == NULL)
+               return NULL;
+       assert(dup != NULL);
+
+       return dup;
+}
+
+void ec_parse_free_children(struct ec_parse *parse)
+{
+       struct ec_parse *child;
+
+       if (parse == NULL)
+               return;
+
+       while (!TAILQ_EMPTY(&parse->children)) {
+               child = TAILQ_FIRST(&parse->children);
+               TAILQ_REMOVE(&parse->children, child, next);
+               child->parent = NULL;
+               ec_parse_free(child);
+       }
+}
+
+void ec_parse_free(struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return;
+
+       ec_assert_print(parse->parent == NULL,
+                       "parent not NULL in ec_parse_free()");
+
+       ec_parse_free_children(parse);
+       ec_strvec_free(parse->strvec);
+       ec_keyval_free(parse->attrs);
+       ec_free(parse);
+}
+
+static void __ec_parse_dump(FILE *out,
+       const struct ec_parse *parse, size_t indent)
+{
+       struct ec_parse *child;
+       const struct ec_strvec *vec;
+       const char *id = "none", *typename = "none";
+
+       /* node can be null when parsing is incomplete */
+       if (parse->node != NULL) {
+               id = parse->node->id;
+               typename = ec_node_type(parse->node)->name;
+       }
+
+       fprintf(out, "%*s" "type=%s id=%s vec=",
+               (int)indent * 4, "", typename, id);
+       vec = ec_parse_strvec(parse);
+       ec_strvec_dump(out, vec);
+
+       TAILQ_FOREACH(child, &parse->children, next)
+               __ec_parse_dump(out, child, indent + 1);
+}
+
+void ec_parse_dump(FILE *out, const struct ec_parse *parse)
+{
+       fprintf(out, "------------------- parse dump:\n");
+
+       if (parse == NULL) {
+               fprintf(out, "parse is NULL\n");
+               return;
+       }
+
+       /* only exist if it does not match (strvec == NULL) and if it
+        * does not have children: an incomplete parse, like those
+        * generated by complete() don't match but have children that
+        * may match. */
+       if (!ec_parse_matches(parse) && TAILQ_EMPTY(&parse->children)) {
+               fprintf(out, "no match\n");
+               return;
+       }
+
+       __ec_parse_dump(out, parse, 0);
+}
+
+void ec_parse_link_child(struct ec_parse *parse,
+       struct ec_parse *child)
+{
+       TAILQ_INSERT_TAIL(&parse->children, child, next);
+       child->parent = parse;
+}
+
+void ec_parse_unlink_child(struct ec_parse *parse,
+       struct ec_parse *child)
+{
+       TAILQ_REMOVE(&parse->children, child, next);
+       child->parent = NULL;
+}
+
+struct ec_parse *
+ec_parse_get_first_child(const struct ec_parse *parse)
+{
+       return TAILQ_FIRST(&parse->children);
+}
+
+struct ec_parse *
+ec_parse_get_last_child(const struct ec_parse *parse)
+{
+       return TAILQ_LAST(&parse->children, ec_parse_list);
+}
+
+struct ec_parse *ec_parse_get_next(const struct ec_parse *parse)
+{
+       return TAILQ_NEXT(parse, next);
+}
+
+bool ec_parse_has_child(const struct ec_parse *parse)
+{
+       return !TAILQ_EMPTY(&parse->children);
+}
+
+const struct ec_node *ec_parse_get_node(const struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return NULL;
+
+       return parse->node;
+}
+
+void ec_parse_del_last_child(struct ec_parse *parse)
+{
+       struct ec_parse *child;
+
+       child = ec_parse_get_last_child(parse);
+       ec_parse_unlink_child(parse, child);
+       ec_parse_free(child);
+}
+
+struct ec_parse *__ec_parse_get_root(struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return NULL;
+
+       while (parse->parent != NULL)
+               parse = parse->parent;
+
+       return parse;
+}
+
+struct ec_parse *ec_parse_get_parent(const struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return NULL;
+
+       return parse->parent;
+}
+
+struct ec_parse *ec_parse_iter_next(struct ec_parse *parse)
+{
+       struct ec_parse *child, *parent, *next;
+
+       child = TAILQ_FIRST(&parse->children);
+       if (child != NULL)
+               return child;
+       parent = parse->parent;
+       while (parent != NULL) {
+               next = TAILQ_NEXT(parse, next);
+               if (next != NULL)
+                       return next;
+               parse = parent;
+               parent = parse->parent;
+       }
+       return NULL;
+}
+
+struct ec_parse *ec_parse_find_first(struct ec_parse *parse,
+       const char *id)
+{
+       struct ec_parse *iter;
+
+       if (parse == NULL)
+               return NULL;
+
+       for (iter = parse; iter != NULL; iter = ec_parse_iter_next(iter)) {
+               if (iter->node != NULL &&
+                               iter->node->id != NULL &&
+                               !strcmp(iter->node->id, id))
+                       return iter;
+       }
+
+       return NULL;
+}
+
+struct ec_keyval *
+ec_parse_get_attrs(struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return NULL;
+
+       return parse->attrs;
+}
+
+const struct ec_strvec *ec_parse_strvec(const struct ec_parse *parse)
+{
+       if (parse == NULL || parse->strvec == NULL)
+               return NULL;
+
+       return parse->strvec;
+}
+
+/* number of strings in the parse vector */
+size_t ec_parse_len(const struct ec_parse *parse)
+{
+       if (parse == NULL || parse->strvec == NULL)
+               return 0;
+
+       return ec_strvec_len(parse->strvec);
+}
+
+size_t ec_parse_matches(const struct ec_parse *parse)
+{
+       if (parse == NULL)
+               return 0;
+
+       if (parse->strvec == NULL)
+               return 0;
+
+       return 1;
+}
+
+/* LCOV_EXCL_START */
+static int ec_parse_testcase(void)
+{
+       struct ec_node *node = NULL;
+       struct ec_parse *p = NULL, *p2 = NULL;
+       const struct ec_parse *pc;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+       int testres = 0;
+       int ret;
+
+       node = ec_node_sh_lex(EC_NO_ID,
+                       EC_NODE_SEQ(EC_NO_ID,
+                               ec_node_str("id_x", "x"),
+                               ec_node_str("id_y", "y")));
+       if (node == NULL)
+               goto fail;
+
+       p = ec_node_parse(node, "xcdscds");
+       testres |= EC_TEST_CHECK(
+               p != NULL && !ec_parse_matches(p),
+               "parse should not match\n");
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_parse_dump(f, p);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "no match"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+       ec_parse_free(p);
+
+       p = ec_node_parse(node, "x y");
+       testres |= EC_TEST_CHECK(
+               p != NULL && ec_parse_matches(p),
+               "parse should match\n");
+       testres |= EC_TEST_CHECK(
+               ec_parse_len(p) == 1, "bad parse len\n");
+
+       ret = ec_keyval_set(ec_parse_get_attrs(p), "key", "val", NULL);
+       testres |= EC_TEST_CHECK(ret == 0,
+               "cannot set parse attribute\n");
+
+       p2 = ec_parse_dup(p);
+       testres |= EC_TEST_CHECK(
+               p2 != NULL && ec_parse_matches(p2),
+               "parse should match\n");
+       ec_parse_free(p2);
+       p2 = NULL;
+
+       pc = ec_parse_find_first(p, "id_x");
+       testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_x");
+       testres |= EC_TEST_CHECK(pc != NULL &&
+               ec_parse_get_parent(pc) != NULL &&
+               ec_parse_get_parent(ec_parse_get_parent(pc)) == p,
+               "invalid parent\n");
+
+       pc = ec_parse_find_first(p, "id_y");
+       testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_y");
+       pc = ec_parse_find_first(p, "id_dezdezdez");
+       testres |= EC_TEST_CHECK(pc == NULL, "should not find bad id");
+
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_parse_dump(f, p);
+       fclose(f);
+       f = NULL;
+
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "type=sh_lex id=no-id") &&
+               strstr(buf, "type=seq id=no-id") &&
+               strstr(buf, "type=str id=id_x") &&
+               strstr(buf, "type=str id=id_x"),
+               "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       ec_parse_free(p);
+       ec_node_free(node);
+       return testres;
+
+fail:
+       ec_parse_free(p2);
+       ec_parse_free(p);
+       ec_node_free(node);
+       if (f != NULL)
+               fclose(f);
+       free(buf);
+
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_parse_test = {
+       .name = "parse",
+       .test = ec_parse_testcase,
+};
+
+EC_TEST_REGISTER(ec_parse_test);
diff --git a/src/ecoli_string.c b/src/ecoli_string.c
new file mode 100644 (file)
index 0000000..fd427b4
--- /dev/null
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include <ecoli_assert.h>
+#include <ecoli_malloc.h>
+#include <ecoli_string.h>
+
+/* count the number of identical chars at the beginning of 2 strings */
+size_t ec_strcmp_count(const char *s1, const char *s2)
+{
+       size_t i = 0;
+
+       while (s1[i] && s2[i] && s1[i] == s2[i])
+               i++;
+
+       return i;
+}
+
+int ec_str_startswith(const char *s, const char *beginning)
+{
+       size_t len;
+
+       len = ec_strcmp_count(s, beginning);
+       if (beginning[len] == '\0')
+               return 1;
+
+       return 0;
+}
+
+int ec_vasprintf(char **buf, const char *fmt, va_list ap)
+{
+       char dummy;
+       int buflen, ret;
+       va_list aq;
+
+       va_copy(aq, ap);
+       *buf = NULL;
+       ret = vsnprintf(&dummy, 1, fmt, aq);
+       va_end(aq);
+       if (ret < 0)
+               return ret;
+
+       buflen = ret + 1;
+       *buf = ec_malloc(buflen);
+       if (*buf == NULL)
+               return -1;
+
+       va_copy(aq, ap);
+       ret = vsnprintf(*buf, buflen, fmt, aq);
+       va_end(aq);
+
+       ec_assert_print(ret < buflen, "invalid return value for vsnprintf");
+       if (ret < 0) {
+               free(*buf);
+               *buf = NULL;
+               return -1;
+       }
+
+       return ret;
+}
+
+int ec_asprintf(char **buf, const char *fmt, ...)
+{
+       va_list ap;
+       int ret;
+
+       va_start(ap, fmt);
+       ret = ec_vasprintf(buf, fmt, ap);
+       va_end(ap);
+
+       return ret;
+}
+
+bool ec_str_is_space(const char *s)
+{
+       while (*s) {
+               if (!isspace(*s))
+                       return false;
+               s++;
+       }
+       return true;
+}
diff --git a/src/ecoli_strvec.c b/src/ecoli_strvec.c
new file mode 100644 (file)
index 0000000..98a952f
--- /dev/null
@@ -0,0 +1,536 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#define _GNU_SOURCE /* qsort_r */
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_log.h>
+#include <ecoli_keyval.h>
+#include <ecoli_strvec.h>
+
+EC_LOG_TYPE_REGISTER(strvec);
+
+struct ec_strvec_elt {
+       unsigned int refcnt;
+       char *str;
+       struct ec_keyval *attrs;
+};
+
+struct ec_strvec {
+       size_t len;
+       struct ec_strvec_elt **vec;
+};
+
+struct ec_strvec *ec_strvec(void)
+{
+       struct ec_strvec *strvec;
+
+       strvec = ec_calloc(1, sizeof(*strvec));
+       if (strvec == NULL)
+               return NULL;
+
+       return strvec;
+}
+
+static struct ec_strvec_elt *
+__ec_strvec_elt(const char *s)
+{
+       struct ec_strvec_elt *elt;
+
+       elt = ec_calloc(1, sizeof(*elt));
+       if (elt == NULL)
+               return NULL;
+
+       elt->str = ec_strdup(s);
+       if (elt->str == NULL) {
+               ec_free(elt);
+               return NULL;
+       }
+       elt->refcnt = 1;
+
+       return elt;
+}
+
+static void
+__ec_strvec_elt_free(struct ec_strvec_elt *elt)
+{
+       elt->refcnt--;
+       if (elt->refcnt == 0) {
+               ec_free(elt->str);
+               ec_keyval_free(elt->attrs);
+               ec_free(elt);
+       }
+}
+
+int ec_strvec_set(struct ec_strvec *strvec, size_t idx, const char *s)
+{
+       struct ec_strvec_elt *elt;
+
+       if (strvec == NULL || s == NULL || idx >= strvec->len) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       elt = __ec_strvec_elt(s);
+       if (elt == NULL)
+               return -1;
+
+       __ec_strvec_elt_free(strvec->vec[idx]);
+       strvec->vec[idx] = elt;
+
+       return 0;
+}
+
+int ec_strvec_add(struct ec_strvec *strvec, const char *s)
+{
+       struct ec_strvec_elt *elt, **new_vec;
+
+       if (strvec == NULL || s == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       new_vec = ec_realloc(strvec->vec,
+               sizeof(*strvec->vec) * (strvec->len + 1));
+       if (new_vec == NULL)
+               return -1;
+
+       strvec->vec = new_vec;
+
+       elt = __ec_strvec_elt(s);
+       if (elt == NULL)
+               return -1;
+
+       new_vec[strvec->len] = elt;
+       strvec->len++;
+
+       return 0;
+}
+
+struct ec_strvec *ec_strvec_from_array(const char * const *strarr,
+       size_t n)
+{
+       struct ec_strvec *strvec = NULL;
+       size_t i;
+
+       strvec = ec_strvec();
+       if (strvec == NULL)
+               goto fail;
+
+       for (i = 0; i < n; i++) {
+               if (ec_strvec_add(strvec, strarr[i]) < 0)
+                       goto fail;
+       }
+
+       return strvec;
+
+fail:
+       ec_strvec_free(strvec);
+       return NULL;
+}
+
+int ec_strvec_del_last(struct ec_strvec *strvec)
+{
+       if (strvec->len == 0) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       __ec_strvec_elt_free(strvec->vec[strvec->len - 1]);
+       strvec->len--;
+
+       return 0;
+}
+
+struct ec_strvec *ec_strvec_ndup(const struct ec_strvec *strvec, size_t off,
+       size_t len)
+{
+       struct ec_strvec *copy = NULL;
+       size_t i, veclen;
+
+       veclen = ec_strvec_len(strvec);
+       if (off + len > veclen)
+               return NULL;
+
+       copy = ec_strvec();
+       if (copy == NULL)
+               goto fail;
+
+       if (len == 0)
+               return copy;
+
+       copy->vec = ec_calloc(len, sizeof(*copy->vec));
+       if (copy->vec == NULL)
+               goto fail;
+
+       for (i = 0; i < len; i++) {
+               copy->vec[i] = strvec->vec[i + off];
+               copy->vec[i]->refcnt++;
+       }
+       copy->len = len;
+
+       return copy;
+
+fail:
+       ec_strvec_free(copy);
+       return NULL;
+}
+
+struct ec_strvec *ec_strvec_dup(const struct ec_strvec *strvec)
+{
+       return ec_strvec_ndup(strvec, 0, ec_strvec_len(strvec));
+}
+
+void ec_strvec_free(struct ec_strvec *strvec)
+{
+       struct ec_strvec_elt *elt;
+       size_t i;
+
+       if (strvec == NULL)
+               return;
+
+       for (i = 0; i < ec_strvec_len(strvec); i++) {
+               elt = strvec->vec[i];
+               __ec_strvec_elt_free(elt);
+       }
+
+       ec_free(strvec->vec);
+       ec_free(strvec);
+}
+
+size_t ec_strvec_len(const struct ec_strvec *strvec)
+{
+       return strvec->len;
+}
+
+const char *ec_strvec_val(const struct ec_strvec *strvec, size_t idx)
+{
+       if (strvec == NULL || idx >= strvec->len)
+               return NULL;
+
+       return strvec->vec[idx]->str;
+}
+
+const struct ec_keyval *ec_strvec_get_attrs(const struct ec_strvec *strvec,
+       size_t idx)
+{
+       if (strvec == NULL || idx >= strvec->len) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       return strvec->vec[idx]->attrs;
+}
+
+int ec_strvec_set_attrs(struct ec_strvec *strvec, size_t idx,
+                       struct ec_keyval *attrs)
+{
+       struct ec_strvec_elt *elt;
+
+       if (strvec == NULL || idx >= strvec->len) {
+               errno = EINVAL;
+               goto fail;
+       }
+
+       elt = strvec->vec[idx];
+       if (elt->refcnt > 1) {
+               if (ec_strvec_set(strvec, idx, elt->str) < 0)
+                       goto fail;
+               elt = strvec->vec[idx];
+       }
+
+       if (elt->attrs != NULL)
+               ec_keyval_free(elt->attrs);
+
+       elt->attrs = attrs;
+
+       return 0;
+
+fail:
+       ec_keyval_free(attrs);
+       return -1;
+}
+
+int ec_strvec_cmp(const struct ec_strvec *strvec1,
+               const struct ec_strvec *strvec2)
+{
+       size_t i;
+
+       if (ec_strvec_len(strvec1) != ec_strvec_len(strvec2))
+               return -1;
+
+       for (i = 0; i < ec_strvec_len(strvec1); i++) {
+               if (strcmp(ec_strvec_val(strvec1, i),
+                               ec_strvec_val(strvec2, i)))
+                       return -1;
+       }
+
+       return 0;
+}
+
+static int
+cmp_vec_elt(const void *p1, const void *p2, void *arg)
+{
+       int (*str_cmp)(const char *s1, const char *s2) = arg;
+       const struct ec_strvec_elt * const *e1 = p1, * const *e2 = p2;
+
+       return str_cmp((*e1)->str, (*e2)->str);
+}
+
+void ec_strvec_sort(struct ec_strvec *strvec,
+       int (*str_cmp)(const char *s1, const char *s2))
+{
+       if (str_cmp == NULL)
+               str_cmp = strcmp;
+       qsort_r(strvec->vec, ec_strvec_len(strvec),
+               sizeof(*strvec->vec), cmp_vec_elt, str_cmp);
+}
+
+void ec_strvec_dump(FILE *out, const struct ec_strvec *strvec)
+{
+       size_t i;
+
+       if (strvec == NULL) {
+               fprintf(out, "none\n");
+               return;
+       }
+
+       fprintf(out, "strvec (len=%zu) [", strvec->len);
+       for (i = 0; i < ec_strvec_len(strvec); i++) {
+               if (i == 0)
+                       fprintf(out, "%s", strvec->vec[i]->str);
+               else
+                       fprintf(out, ", %s", strvec->vec[i]->str);
+       }
+       fprintf(out, "]\n");
+
+}
+
+/* LCOV_EXCL_START */
+static int ec_strvec_testcase(void)
+{
+       struct ec_strvec *strvec = NULL;
+       struct ec_strvec *strvec2 = NULL;
+       const struct ec_keyval *const_attrs = NULL;
+       struct ec_keyval *attrs = NULL;
+       FILE *f = NULL;
+       char *buf = NULL;
+       size_t buflen = 0;
+       int testres = 0;
+
+       strvec = ec_strvec();
+       if (strvec == NULL) {
+               EC_TEST_ERR("cannot create strvec\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec) != 0) {
+               EC_TEST_ERR("bad strvec len (0)\n");
+               goto fail;
+       }
+       if (ec_strvec_add(strvec, "0") < 0) {
+               EC_TEST_ERR("cannot add (0) in strvec\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec) != 1) {
+               EC_TEST_ERR("bad strvec len (1)\n");
+               goto fail;
+       }
+       if (ec_strvec_add(strvec, "1") < 0) {
+               EC_TEST_ERR("cannot add (1) in strvec\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec) != 2) {
+               EC_TEST_ERR("bad strvec len (2)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec, 0), "0")) {
+               EC_TEST_ERR("invalid element in strvec (0)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec, 1), "1")) {
+               EC_TEST_ERR("invalid element in strvec (1)\n");
+               goto fail;
+       }
+       if (ec_strvec_val(strvec, 2) != NULL) {
+               EC_TEST_ERR("strvec val should be NULL\n");
+               goto fail;
+       }
+
+       strvec2 = ec_strvec_dup(strvec);
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec2\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec2) != 2) {
+               EC_TEST_ERR("bad strvec2 len (2)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec2, 0), "0")) {
+               EC_TEST_ERR("invalid element in strvec2 (0)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec2, 1), "1")) {
+               EC_TEST_ERR("invalid element in strvec2 (1)\n");
+               goto fail;
+       }
+       if (ec_strvec_val(strvec2, 2) != NULL) {
+               EC_TEST_ERR("strvec2 val should be NULL\n");
+               goto fail;
+       }
+       ec_strvec_free(strvec2);
+
+       strvec2 = ec_strvec_ndup(strvec, 0, 0);
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec2\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec2) != 0) {
+               EC_TEST_ERR("bad strvec2 len (0)\n");
+               goto fail;
+       }
+       if (ec_strvec_val(strvec2, 0) != NULL) {
+               EC_TEST_ERR("strvec2 val should be NULL\n");
+               goto fail;
+       }
+       ec_strvec_free(strvec2);
+
+       strvec2 = ec_strvec_ndup(strvec, 1, 1);
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec2\n");
+               goto fail;
+       }
+       if (ec_strvec_len(strvec2) != 1) {
+               EC_TEST_ERR("bad strvec2 len (1)\n");
+               goto fail;
+       }
+       if (strcmp(ec_strvec_val(strvec2, 0), "1")) {
+               EC_TEST_ERR("invalid element in strvec2 (1)\n");
+               goto fail;
+       }
+       if (ec_strvec_val(strvec2, 1) != NULL) {
+               EC_TEST_ERR("strvec2 val should be NULL\n");
+               goto fail;
+       }
+       ec_strvec_free(strvec2);
+
+       strvec2 = ec_strvec_ndup(strvec, 3, 1);
+       if (strvec2 != NULL) {
+               EC_TEST_ERR("strvec2 should be NULL\n");
+               goto fail;
+       }
+       ec_strvec_free(strvec2);
+
+       strvec2 = EC_STRVEC("0", "1");
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec from array\n");
+               goto fail;
+       }
+       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
+               "strvec and strvec2 should be equal\n");
+       ec_strvec_free(strvec2);
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_strvec_dump(f, strvec);
+       fclose(f);
+       f = NULL;
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "strvec (len=2) [0, 1]"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       ec_strvec_del_last(strvec);
+       strvec2 = EC_STRVEC("0");
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec from array\n");
+               goto fail;
+       }
+       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
+               "strvec and strvec2 should be equal\n");
+       ec_strvec_free(strvec2);
+       strvec2 = NULL;
+
+       f = open_memstream(&buf, &buflen);
+       if (f == NULL)
+               goto fail;
+       ec_strvec_dump(f, NULL);
+       fclose(f);
+       f = NULL;
+       testres |= EC_TEST_CHECK(
+               strstr(buf, "none"), "bad dump\n");
+       free(buf);
+       buf = NULL;
+
+       ec_strvec_free(strvec);
+
+       strvec = EC_STRVEC("e", "a", "f", "d", "b", "c");
+       if (strvec == NULL) {
+               EC_TEST_ERR("cannot create strvec from array\n");
+               goto fail;
+       }
+       attrs = ec_keyval();
+       if (attrs == NULL) {
+               EC_TEST_ERR("cannot create attrs\n");
+               goto fail;
+       }
+       if (ec_keyval_set(attrs, "key", "value", NULL) < 0) {
+               EC_TEST_ERR("cannot set attr\n");
+               goto fail;
+       }
+       if (ec_strvec_set_attrs(strvec, 1, attrs) < 0) {
+               attrs = NULL;
+               EC_TEST_ERR("cannot set attrs in strvec\n");
+               goto fail;
+       }
+       attrs = NULL;
+
+       ec_strvec_sort(strvec, NULL);
+
+       /* attrs are now at index 0 after sorting */
+       const_attrs = ec_strvec_get_attrs(strvec, 0);
+       if (const_attrs == NULL) {
+               EC_TEST_ERR("cannot get attrs\n");
+               goto fail;
+       }
+       testres |= EC_TEST_CHECK(
+               ec_keyval_has_key(const_attrs, "key"), "cannot get attrs key\n");
+
+       strvec2 = EC_STRVEC("a", "b", "c", "d", "e", "f");
+       if (strvec2 == NULL) {
+               EC_TEST_ERR("cannot create strvec from array\n");
+               goto fail;
+       }
+       testres |= EC_TEST_CHECK(ec_strvec_cmp(strvec, strvec2) == 0,
+               "strvec and strvec2 should be equal\n");
+       ec_strvec_free(strvec);
+       strvec = NULL;
+       ec_strvec_free(strvec2);
+       strvec2 = NULL;
+
+       return testres;
+
+fail:
+       if (f != NULL)
+               fclose(f);
+       ec_keyval_free(attrs);
+       ec_strvec_free(strvec);
+       ec_strvec_free(strvec2);
+       free(buf);
+
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_str_test = {
+       .name = "strvec",
+       .test = ec_strvec_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_str_test);
diff --git a/src/ecoli_test.c b/src/ecoli_test.c
new file mode 100644 (file)
index 0000000..b090bd3
--- /dev/null
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <ecoli_log.h>
+#include <ecoli_malloc.h>
+#include <ecoli_test.h>
+#include <ecoli_strvec.h>
+#include <ecoli_node.h>
+#include <ecoli_parse.h>
+#include <ecoli_complete.h>
+#include <ecoli_parse.h>
+
+static struct ec_test_list test_list = TAILQ_HEAD_INITIALIZER(test_list);
+
+EC_LOG_TYPE_REGISTER(test);
+
+static struct ec_test *ec_test_lookup(const char *name)
+{
+       struct ec_test *test;
+
+       TAILQ_FOREACH(test, &test_list, next) {
+               if (!strcmp(name, test->name))
+                       return test;
+       }
+
+       errno = EEXIST;
+       return NULL;
+}
+
+int ec_test_register(struct ec_test *test)
+{
+       if (ec_test_lookup(test->name) != NULL)
+               return -1;
+
+       TAILQ_INSERT_TAIL(&test_list, test, next);
+
+       return 0;
+}
+
+int ec_test_check_parse(struct ec_node *tk, int expected, ...)
+{
+       struct ec_parse *p;
+       struct ec_strvec *vec = NULL;
+       const char *s;
+       int ret = -1, match;
+       va_list ap;
+
+       va_start(ap, expected);
+
+       /* build a string vector */
+       vec = ec_strvec();
+       if (vec == NULL)
+               goto out;
+
+       for (s = va_arg(ap, const char *);
+            s != EC_NODE_ENDLIST;
+            s = va_arg(ap, const char *)) {
+               if (s == NULL)
+                       goto out;
+
+               if (ec_strvec_add(vec, s) < 0)
+                       goto out;
+       }
+
+       p = ec_node_parse_strvec(tk, vec);
+       if (p == NULL) {
+               EC_LOG(EC_LOG_ERR, "parse is NULL\n");
+       }
+       if (ec_parse_matches(p))
+               match = ec_parse_len(p);
+       else
+               match = -1;
+       if (expected == match) {
+               ret = 0;
+       } else {
+               EC_LOG(EC_LOG_ERR,
+                       "parse len (%d) does not match expected (%d)\n",
+                       match, expected);
+       }
+
+       ec_parse_free(p);
+
+out:
+       ec_strvec_free(vec);
+       va_end(ap);
+       return ret;
+}
+
+int ec_test_check_complete(struct ec_node *tk, enum ec_comp_type type, ...)
+{
+       struct ec_comp *c = NULL;
+       struct ec_strvec *vec = NULL;
+       const char *s;
+       int ret = 0;
+       unsigned int count = 0;
+       va_list ap;
+
+       va_start(ap, type);
+
+       /* build a string vector */
+       vec = ec_strvec();
+       if (vec == NULL)
+               goto out;
+
+       for (s = va_arg(ap, const char *);
+            s != EC_NODE_ENDLIST;
+            s = va_arg(ap, const char *)) {
+               if (s == NULL)
+                       goto out;
+
+               if (ec_strvec_add(vec, s) < 0)
+                       goto out;
+       }
+
+       c = ec_node_complete_strvec(tk, vec);
+       if (c == NULL) {
+               ret = -1;
+               goto out;
+       }
+
+       /* for each expected completion, check it is there */
+       for (s = va_arg(ap, const char *);
+            s != EC_NODE_ENDLIST;
+            s = va_arg(ap, const char *)) {
+               struct ec_comp_iter *iter;
+               const struct ec_comp_item *item;
+
+               if (s == NULL) {
+                       ret = -1;
+                       goto out;
+               }
+
+               count++;
+
+               /* only check matching completions */
+               iter = ec_comp_iter(c, type);
+               while ((item = ec_comp_iter_next(iter)) != NULL) {
+                       const char *str = ec_comp_item_get_str(item);
+                       if (str != NULL && strcmp(str, s) == 0)
+                               break;
+               }
+
+               if (item == NULL) {
+                       EC_LOG(EC_LOG_ERR,
+                               "completion <%s> not in list\n", s);
+                       ret = -1;
+               }
+               ec_comp_iter_free(iter);
+       }
+
+       /* check if we have more completions (or less) than expected */
+       if (count != ec_comp_count(c, type)) {
+               EC_LOG(EC_LOG_ERR,
+                       "nb_completion (%d) does not match (%d)\n",
+                       count, ec_comp_count(c, type));
+               ec_comp_dump(stdout, c);
+               ret = -1;
+       }
+
+out:
+       ec_strvec_free(vec);
+       ec_comp_free(c);
+       va_end(ap);
+       return ret;
+}
+
+static int launch_test(const char *name)
+{
+       struct ec_test *test;
+       int ret = 0;
+       unsigned int count = 0;
+
+       TAILQ_FOREACH(test, &test_list, next) {
+               if (name != NULL && strcmp(name, test->name))
+                       continue;
+
+               EC_LOG(EC_LOG_INFO, "== starting test %-20s\n",
+                       test->name);
+
+               count++;
+               if (test->test() == 0) {
+                       EC_LOG(EC_LOG_INFO,
+                               "== test %-20s success\n",
+                               test->name);
+               } else {
+                       EC_LOG(EC_LOG_INFO,
+                               "== test %-20s failed\n",
+                               test->name);
+                       ret = -1;
+               }
+       }
+
+       if (name != NULL && count == 0) {
+               EC_LOG(EC_LOG_WARNING,
+                       "== test %s not found\n", name);
+               ret = -1;
+       }
+
+       return ret;
+}
+
+int ec_test_all(void)
+{
+       return launch_test(NULL);
+}
+
+int ec_test_one(const char *name)
+{
+       return launch_test(name);
+}
diff --git a/src/ecoli_vec.c b/src/ecoli_vec.c
new file mode 100644 (file)
index 0000000..fe8c572
--- /dev/null
@@ -0,0 +1,468 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <ecoli_assert.h>
+#include <ecoli_malloc.h>
+#include <ecoli_log.h>
+#include <ecoli_test.h>
+#include <ecoli_vec.h>
+
+EC_LOG_TYPE_REGISTER(vec);
+
+struct ec_vec {
+       size_t len;
+       size_t size;
+       size_t elt_size;
+       ec_vec_elt_copy_t copy;
+       ec_vec_elt_free_t free;
+       void *vec;
+};
+
+static void *get_obj(const struct ec_vec *vec, size_t idx)
+{
+       assert(vec->elt_size != 0);
+       return (char *)vec->vec + (idx * vec->elt_size);
+}
+
+struct ec_vec *
+ec_vec(size_t elt_size, size_t size, ec_vec_elt_copy_t copy,
+       ec_vec_elt_free_t free)
+{
+       struct ec_vec *vec;
+
+       if (elt_size == 0) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       vec = ec_calloc(1, sizeof(*vec));
+       if (vec == NULL)
+               return NULL;
+
+       vec->elt_size = elt_size;
+       vec->copy = copy;
+       vec->free = free;
+
+       if (size == 0)
+               return vec;
+
+       vec->vec = ec_calloc(size, vec->elt_size);
+       if (vec->vec == NULL) {
+               ec_free(vec);
+               return NULL;
+       }
+
+       return vec;
+}
+
+int ec_vec_add_by_ref(struct ec_vec *vec, void *ptr)
+{
+       void *new_vec;
+
+       if (vec->len + 1 > vec->size) {
+               new_vec = ec_realloc(vec->vec, vec->elt_size * (vec->len + 1));
+               if (new_vec == NULL)
+                       return -1;
+               vec->size = vec->len + 1;
+               vec->vec = new_vec;
+       }
+
+       memcpy(get_obj(vec, vec->len), ptr, vec->elt_size);
+       vec->len++;
+
+       return 0;
+}
+
+int ec_vec_add_ptr(struct ec_vec *vec, void *elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+int ec_vec_add_u8(struct ec_vec *vec, uint8_t elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+int ec_vec_add_u16(struct ec_vec *vec, uint16_t elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+int ec_vec_add_u32(struct ec_vec *vec, uint32_t elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+int ec_vec_add_u64(struct ec_vec *vec, uint64_t elt)
+{
+       EC_CHECK_ARG(vec->elt_size == sizeof(elt), -1, EINVAL);
+
+       return ec_vec_add_by_ref(vec, &elt);
+}
+
+struct ec_vec *ec_vec_ndup(const struct ec_vec *vec, size_t off,
+       size_t len)
+{
+       struct ec_vec *copy = NULL;
+       size_t i, veclen;
+
+       veclen = ec_vec_len(vec);
+       if (off + len > veclen)
+               return NULL;
+
+       copy = ec_vec(vec->elt_size, len, vec->copy, vec->free);
+       if (copy == NULL)
+               goto fail;
+
+       if (len == 0)
+               return copy;
+
+       for (i = 0; i < len; i++) {
+               if (vec->copy)
+                       vec->copy(get_obj(copy, i), get_obj(vec, i + off));
+               else
+                       memcpy(get_obj(copy, i), get_obj(vec, i + off),
+                               vec->elt_size);
+       }
+       copy->len = len;
+
+       return copy;
+
+fail:
+       ec_vec_free(copy);
+       return NULL;
+}
+
+size_t ec_vec_len(const struct ec_vec *vec)
+{
+       if (vec == NULL)
+               return 0;
+
+       return vec->len;
+}
+
+struct ec_vec *ec_vec_dup(const struct ec_vec *vec)
+{
+       return ec_vec_ndup(vec, 0, ec_vec_len(vec));
+}
+
+void ec_vec_free(struct ec_vec *vec)
+{
+       size_t i;
+
+       if (vec == NULL)
+               return;
+
+       for (i = 0; i < ec_vec_len(vec); i++) {
+               if (vec->free)
+                       vec->free(get_obj(vec, i));
+       }
+
+       ec_free(vec->vec);
+       ec_free(vec);
+}
+
+int ec_vec_get(void *ptr, const struct ec_vec *vec, size_t idx)
+{
+       if (vec == NULL || idx >= vec->len) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       memcpy(ptr, get_obj(vec, idx), vec->elt_size);
+
+       return 0;
+}
+
+static void str_free(void *elt)
+{
+       char **s = elt;
+
+       ec_free(*s);
+}
+
+#define GOTO_FAIL do {                                      \
+               EC_LOG(EC_LOG_ERR, "%s:%d: test failed\n",   \
+                       __FILE__, __LINE__);                 \
+               goto fail;                                   \
+       } while(0)
+
+/* LCOV_EXCL_START */
+static int ec_vec_testcase(void)
+{
+       struct ec_vec *vec = NULL;
+       struct ec_vec *vec2 = NULL;
+       uint8_t val8;
+       uint16_t val16;
+       uint32_t val32;
+       uint64_t val64;
+       void *valp;
+       char *vals;
+
+       /* uint8_t vector */
+       vec = ec_vec(sizeof(val8), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_u8(vec, 0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u8(vec, 1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u8(vec, 2) < 0)
+               GOTO_FAIL;
+       /* should fail */
+       if (ec_vec_add_u16(vec, 3) == 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u32(vec, 3) == 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u64(vec, 3) == 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, (void *)3) == 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&val8, vec, 0) < 0)
+               GOTO_FAIL;
+       if (val8 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec, 1) < 0)
+               GOTO_FAIL;
+       if (val8 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec, 2) < 0)
+               GOTO_FAIL;
+       if (val8 != 2)
+               GOTO_FAIL;
+
+       /* duplicate the vector */
+       vec2 = ec_vec_dup(vec);
+       if (vec2 == NULL)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 0) < 0)
+               GOTO_FAIL;
+       if (val8 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 1) < 0)
+               GOTO_FAIL;
+       if (val8 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 2) < 0)
+               GOTO_FAIL;
+       if (val8 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec2);
+       vec2 = NULL;
+
+       /* dup at offset 1 */
+       vec2 = ec_vec_ndup(vec, 1, 2);
+       if (vec2 == NULL)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 0) < 0)
+               GOTO_FAIL;
+       if (val8 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 1) < 0)
+               GOTO_FAIL;
+       if (val8 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec2);
+       vec2 = NULL;
+
+       /* len = 0, duplicate is empty */
+       vec2 = ec_vec_ndup(vec, 2, 0);
+       if (vec2 == NULL)
+               GOTO_FAIL;
+       if (ec_vec_get(&val8, vec2, 0) == 0)
+               GOTO_FAIL;
+
+       ec_vec_free(vec2);
+       vec2 = NULL;
+
+       /* bad dup args */
+       vec2 = ec_vec_ndup(vec, 10, 1);
+       if (vec2 != NULL)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* uint16_t vector */
+       vec = ec_vec(sizeof(val16), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_u16(vec, 0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u16(vec, 1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u16(vec, 2) < 0)
+               GOTO_FAIL;
+       /* should fail */
+       if (ec_vec_add_u8(vec, 3) == 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&val16, vec, 0) < 0)
+               GOTO_FAIL;
+       if (val16 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val16, vec, 1) < 0)
+               GOTO_FAIL;
+       if (val16 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val16, vec, 2) < 0)
+               GOTO_FAIL;
+       if (val16 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* uint32_t vector */
+       vec = ec_vec(sizeof(val32), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_u32(vec, 0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u32(vec, 1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u32(vec, 2) < 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&val32, vec, 0) < 0)
+               GOTO_FAIL;
+       if (val32 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val32, vec, 1) < 0)
+               GOTO_FAIL;
+       if (val32 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val32, vec, 2) < 0)
+               GOTO_FAIL;
+       if (val32 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* uint64_t vector */
+       vec = ec_vec(sizeof(val64), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_u64(vec, 0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u64(vec, 1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_u64(vec, 2) < 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&val64, vec, 0) < 0)
+               GOTO_FAIL;
+       if (val64 != 0)
+               GOTO_FAIL;
+       if (ec_vec_get(&val64, vec, 1) < 0)
+               GOTO_FAIL;
+       if (val64 != 1)
+               GOTO_FAIL;
+       if (ec_vec_get(&val64, vec, 2) < 0)
+               GOTO_FAIL;
+       if (val64 != 2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* ptr vector */
+       vec = ec_vec(sizeof(valp), 0, NULL, NULL);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_ptr(vec, (void *)0) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, (void *)1) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, (void *)2) < 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&valp, vec, 0) < 0)
+               GOTO_FAIL;
+       if (valp != (void *)0)
+               GOTO_FAIL;
+       if (ec_vec_get(&valp, vec, 1) < 0)
+               GOTO_FAIL;
+       if (valp != (void *)1)
+               GOTO_FAIL;
+       if (ec_vec_get(&valp, vec, 2) < 0)
+               GOTO_FAIL;
+       if (valp != (void *)2)
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* string vector */
+       vec = ec_vec(sizeof(valp), 0, NULL, str_free);
+       if (vec == NULL)
+               GOTO_FAIL;
+
+       if (ec_vec_add_ptr(vec, ec_strdup("0")) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, ec_strdup("1")) < 0)
+               GOTO_FAIL;
+       if (ec_vec_add_ptr(vec, ec_strdup("2")) < 0)
+               GOTO_FAIL;
+
+       if (ec_vec_get(&vals, vec, 0) < 0)
+               GOTO_FAIL;
+       if (vals == NULL || strcmp(vals, "0"))
+               GOTO_FAIL;
+       if (ec_vec_get(&vals, vec, 1) < 0)
+               GOTO_FAIL;
+       if (vals == NULL || strcmp(vals, "1"))
+               GOTO_FAIL;
+       if (ec_vec_get(&vals, vec, 2) < 0)
+               GOTO_FAIL;
+       if (vals == NULL || strcmp(vals, "2"))
+               GOTO_FAIL;
+
+       ec_vec_free(vec);
+       vec = NULL;
+
+       /* invalid args */
+       vec = ec_vec(0, 0, NULL, NULL);
+       if (vec != NULL)
+               GOTO_FAIL;
+
+       return 0;
+
+fail:
+       ec_vec_free(vec);
+       ec_vec_free(vec2);
+       return -1;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_vec_test = {
+       .name = "vec",
+       .test = ec_vec_testcase,
+};
+
+EC_TEST_REGISTER(ec_vec_test);
diff --git a/src/ecoli_yaml.c b/src/ecoli_yaml.c
new file mode 100644 (file)
index 0000000..84007d5
--- /dev/null
@@ -0,0 +1,550 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+
+#include <yaml.h>
+
+#include <ecoli_malloc.h>
+#include <ecoli_keyval.h>
+#include <ecoli_node.h>
+#include <ecoli_config.h>
+#include <ecoli_yaml.h>
+
+/* 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 enode_table *table, const yaml_document_t *document,
+       const yaml_node_t *ynode);
+
+static struct ec_config *
+parse_ec_config_list(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 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 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(table, document, ynode);
+               if (enode == NULL)
+                       goto fail;
+               config = ec_config_node(enode);
+               enode = NULL;
+               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(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(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 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(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 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(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 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;
+       char *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 = ec_strdup(value_str);
+                       if (help == NULL) {
+                               fprintf(stderr, "Failed to allocate help\n");
+                               goto fail;
+                       }
+               }
+       }
+
+       /* 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;
+       }
+
+       /* 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(table, schema, document, ynode);
+       if (config == NULL)
+               goto fail;
+
+       if (ec_node_set_config(enode, config) < 0) {
+               config = NULL; /* freed */
+               fprintf(stderr, "Failed to set config\n");
+               goto fail;
+       }
+       config = NULL; /* freed */
+
+       if (help != NULL) {
+               if (ec_keyval_set(ec_node_attrs(enode), "help", help,
+                                       ec_free_func) < 0) {
+                       fprintf(stderr, "Failed to set help\n");
+                       help = NULL;
+                       goto fail;
+               }
+               help = NULL;
+       }
+
+       /* add attributes (all as string) */
+       //XXX
+
+       return enode;
+
+fail:
+       ec_node_free(enode);
+       ec_config_free(config);
+       ec_free(help);
+
+       return NULL;
+}
+
+static struct ec_node *
+parse_document(struct enode_table *table,
+       const yaml_document_t *document)
+{
+       yaml_node_t *node;
+
+       node = document->nodes.start;
+       return parse_ec_node(table, document, node);
+}
+
+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;
+       }
+
+       root = parse_document(&table, &document);
+       if (root == NULL) {
+               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);
+       free_table(&table);
+       ec_node_free(root);
+
+       return NULL;
+}
diff --git a/src/editline.c b/src/editline.c
new file mode 100644 (file)
index 0000000..449aebb
--- /dev/null
@@ -0,0 +1,544 @@
+/*
+ * Copyright 2018 6WIND S.A.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <histedit.h>
+
+#include "string_utils.h"
+#include "editline.h"
+
+#define NC_CLI_HISTORY_SIZE 128
+
+struct nc_cli_editline {
+       EditLine *editline;
+       History *history;
+       HistEvent histev;
+       bool break_received;
+       nc_cli_editline_complete_t complete;
+       FILE *null_out;
+       bool interactive;
+       bool incomplete_line;
+       char *full_line;
+       char *(*prompt_cb)(EditLine *);
+};
+
+struct nc_cli_editline *nc_cli_el;
+
+static int check_quotes(const char *str)
+{
+       char quote = 0;
+       size_t i = 0;
+
+       while (str[i] != '\0') {
+               if (quote == 0) {
+                       if (str[i] == '"' || str[i] == '\'') {
+                               quote = str[i];
+                       }
+                       i++;
+                       continue;
+               } else {
+                       if (str[i] == quote) {
+                               i++;
+                               quote = 0;
+                       } else if (str[i] == '\\' && str[i+1] == quote) {
+                               i += 2;
+                       } else {
+                               i++;
+                       }
+                       continue;
+               }
+       }
+
+       return quote;
+}
+
+static int
+editline_break(EditLine *editline, int c)
+{
+       struct nc_cli_editline *el;
+       void *ptr;
+
+       (void)c;
+
+       if (el_get(editline, EL_CLIENTDATA, &ptr))
+               return CC_ERROR;
+
+       el = ptr;
+       el->break_received = true;
+       nc_cli_printf(el, "\n");
+
+       return CC_EOF;
+}
+
+static int
+editline_suspend(EditLine *editline, int c)
+{
+       (void)editline;
+       (void)c;
+
+       kill(getpid(), SIGSTOP);
+
+       return CC_NORM;
+}
+
+int
+editline_get_screen_size(struct nc_cli_editline *el, size_t *rows, size_t *cols)
+{
+       int w, h;
+
+       if (rows != NULL) {
+               if (el_get(el->editline, EL_GETTC, "li", &h, (void *)0))
+                       return -1;
+               *rows = h;
+       }
+       if (cols != NULL) {
+               if (el_get(el->editline, EL_GETTC, "co", &w, (void *)0))
+                       return -1;
+               *cols = w;
+       }
+       return 0;
+}
+
+FILE *
+nc_cli_editline_get_file(struct nc_cli_editline *el, int num)
+{
+       FILE *f;
+
+       if (el == NULL)
+               return NULL;
+       if (num > 2)
+               return NULL;
+       if (el_get(el->editline, EL_GETFP, num, &f))
+               return NULL;
+
+       return f;
+}
+
+/* match the prototype expected by qsort() */
+static int
+strcasecmp_cb(const void *p1, const void *p2)
+{
+       return strcasecmp(*(char * const *)p1, *(char * const *)p2);
+}
+
+/* Show the matches as a multi-columns list */
+int
+nc_cli_editline_print_cols(struct nc_cli_editline *el,
+                       char const * const *matches, size_t n)
+{
+       size_t max_strlen = 0, len, i, j, ncols;
+       size_t width, height;
+       const char *space;
+       char **matches_copy = NULL;
+
+       nc_cli_printf(nc_cli_el, "\n");
+       if (n == 0)
+               return 0;
+
+       if (editline_get_screen_size(el, &height, &width) < 0)
+               width = 80;
+
+       /* duplicate the matches table, and sort it */
+       matches_copy = calloc(n, sizeof(const char *));
+       if (matches_copy == NULL)
+               return -1;
+       memcpy(matches_copy, matches, sizeof(const char *) * n);
+       qsort(matches_copy, n, sizeof(char *), strcasecmp_cb);
+
+       /* get max string length */
+       for (i = 0; i < n; i++) {
+               len = strlen(matches_copy[i]);
+               if (len > max_strlen)
+                       max_strlen = len;
+       }
+
+       /* write the columns */
+       ncols = width / (max_strlen + 4);
+       if (ncols == 0)
+               ncols = 1;
+       for (i = 0; i < n; i+= ncols) {
+               for (j = 0; j < ncols; j++) {
+                       if (i + j >= n)
+                               break;
+                       if (j == 0)
+                               space = "";
+                       else
+                               space = "    ";
+                       nc_cli_printf(nc_cli_el, "%s%-*s", space,
+                               (int)max_strlen, matches[i+j]);
+               }
+               nc_cli_printf(nc_cli_el, "\n");
+       }
+
+       free(matches_copy);
+       return 0;
+}
+
+static int
+editline_complete(EditLine *editline, int c)
+{
+       enum nc_cli_editline_complete_status ret;
+       const LineInfo *line_info;
+       struct nc_cli_editline *el;
+       int len;
+       char *line;
+       void *ptr;
+
+       if (el_get(editline, EL_CLIENTDATA, &ptr))
+               return CC_ERROR;
+
+       el = ptr;
+
+       if (el->complete == NULL)
+               return CC_NORM;
+
+       line_info = el_line(editline);
+       if (line_info == NULL)
+               return CC_ERROR;
+
+       len = line_info->cursor - line_info->buffer;
+       if (asprintf(&line, "%s%.*s", el->full_line ? : "", len,
+                       line_info->buffer) < 0)
+               return CC_ERROR;
+
+       if (c == '?' && check_quotes(line) != 0) {
+               free(line);
+               el_insertstr(editline, "?");
+               return CC_REFRESH;
+       }
+
+       ret = el->complete(c, line);
+       free(line);
+
+       if (ret == ERROR)
+               return CC_ERROR;
+       else if (ret == REDISPLAY)
+               return CC_REDISPLAY;
+       else
+               return CC_REFRESH;
+}
+
+static bool is_blank_string(const char *s)
+{
+       while (*s) {
+               if (!isspace(*s))
+                       return false;
+               s++;
+       }
+       return true;
+}
+
+static char *
+multiline_prompt_cb(EditLine *e)
+{
+       (void)e;
+       return strdup("... ");
+}
+
+const char *
+nc_cli_editline_edit(struct nc_cli_editline *el)
+{
+       const char *line;
+       int count;
+
+       assert(el->editline != NULL);
+
+       if (el->incomplete_line == false) {
+               free(el->full_line);
+               el->full_line = NULL;
+       }
+
+       el->break_received = false;
+
+       if (el->incomplete_line)
+               el_set(el->editline, EL_PROMPT, multiline_prompt_cb);
+       else
+               el_set(el->editline, EL_PROMPT, el->prompt_cb);
+
+       line = el_gets(el->editline, &count);
+
+       if (line == NULL && el->break_received) {
+               free(el->full_line);
+               el->full_line = NULL;
+               el->incomplete_line = false;
+               return ""; /* abort current line */
+       }
+
+       if (line == NULL || astrcat(&el->full_line, line) < 0) {
+               free(el->full_line);
+               el->full_line = NULL;
+               el->incomplete_line = false;
+               return NULL; /* error / eof */
+       }
+
+       if (check_quotes(el->full_line) != 0) {
+               el->incomplete_line = true;
+               return "";
+       }
+
+       el->incomplete_line = false;
+       if (el->history != NULL && !is_blank_string(el->full_line))
+               history(el->history, &el->histev,
+                       H_ENTER, el->full_line);
+
+       return el->full_line;
+}
+
+int
+nc_cli_editline_register_complete(struct nc_cli_editline *el,
+                               nc_cli_editline_complete_t complete)
+{
+       const char *name;
+
+       if (el_set(el->editline, EL_ADDFN, "ed-complete",
+                       "Complete buffer",
+                       editline_complete))
+               return -1;
+
+       if (complete != NULL)
+               name = "ed-complete";
+       else
+               name = "ed-unassigned";
+       if (el_set(el->editline, EL_BIND, "^I", name, NULL))
+               return -1;
+
+       if (complete != NULL)
+               name = "ed-complete";
+       else
+               name = "ed-insert";
+       if (el_set(el->editline, EL_BIND, "?", name, NULL))
+               return -1;
+
+       el->complete = complete;
+
+       return 0;
+}
+
+int
+nc_cli_editline_insert_str(struct nc_cli_editline *el, const char *str)
+{
+       return el_insertstr(el->editline, str);
+}
+
+int nc_cli_printf(struct nc_cli_editline *el, const char *format, ...)
+{
+       FILE *out = nc_cli_editline_get_file(el, 1);
+       va_list ap;
+       int ret;
+
+       if (out == NULL)
+               out = stdout;
+
+       va_start(ap, format);
+       ret = vfprintf(out, format, ap);
+       va_end(ap);
+
+       return ret;
+}
+
+int nc_cli_eprintf(struct nc_cli_editline *el, const char *format, ...)
+{
+       FILE *out = nc_cli_editline_get_file(el, 2);
+       va_list ap;
+       int ret;
+
+       if (out == NULL)
+               out = stderr;
+
+       va_start(ap, format);
+       ret = vfprintf(out, format, ap);
+       va_end(ap);
+
+       return ret;
+}
+
+bool
+nc_cli_editline_is_interactive(struct nc_cli_editline *el)
+{
+       return el->interactive;
+}
+
+bool
+nc_cli_editline_is_running(struct nc_cli_editline *el)
+{
+       return el == nc_cli_el;
+}
+
+void
+nc_cli_editline_start(struct nc_cli_editline *el)
+{
+       nc_cli_el = el;
+}
+
+void
+nc_cli_editline_stop(struct nc_cli_editline *el)
+{
+       if (el == nc_cli_el)
+               nc_cli_el = NULL;
+}
+
+int
+nc_cli_editline_getc(struct nc_cli_editline *el)
+{
+       char c;
+
+       if (el->interactive == false)
+               return -1;
+
+       if (el_getc(el->editline, &c) != 1)
+               return -1;
+
+       return c;
+}
+
+int
+nc_cli_editline_resize(struct nc_cli_editline *el)
+{
+       el_resize(el->editline);
+       return 0;
+}
+
+int nc_cli_editline_set_prompt_cb(struct nc_cli_editline *el,
+                               char *(*prompt_cb)(EditLine *el))
+{
+       el->prompt_cb = prompt_cb;
+       return 0;
+}
+
+int
+nc_cli_editline_mask_interrupts(bool do_mask)
+{
+       const char *setty = do_mask ? "-isig" : "+isig";
+
+       if (nc_cli_el == NULL)
+               return -1;
+
+       if (nc_cli_el->interactive == false)
+               return 0;
+
+       if (el_set(nc_cli_el->editline, EL_SETTY, "-d", setty, NULL))
+               return -1;
+
+       return 0;
+}
+
+struct nc_cli_editline *
+nc_cli_editline_init(FILE *f_in, FILE *f_out, FILE *f_err, bool interactive,
+               char *(*prompt_cb)(EditLine *el))
+{
+       struct nc_cli_editline *el = NULL;
+
+       el = calloc(1, sizeof(*el));
+       if (el == NULL)
+               goto fail;
+       if (f_in == NULL) {
+               errno = EINVAL;
+               goto fail;
+       }
+       if (f_out == NULL || f_err == NULL) {
+               el->null_out = fopen("/dev/null", "w");
+               if (el->null_out == NULL)
+                       goto fail;
+       }
+       if (f_out == NULL)
+               f_out = el->null_out;
+       if (f_err == NULL)
+               f_err = el->null_out;
+
+       el->interactive = interactive;
+       el->prompt_cb = prompt_cb;
+
+       el->editline = el_init("nc-cli", f_in, f_out, f_err);
+       if (el->editline == NULL)
+               goto fail;
+
+       if (el_set(el->editline, EL_SIGNAL, 1))
+               goto fail;
+
+       if (el_set(el->editline, EL_CLIENTDATA, el))
+               goto fail;
+
+       if (el_set(el->editline, EL_PROMPT, prompt_cb))
+               goto fail;
+
+       if (interactive == false)
+               goto end;
+
+       if (el_set(el->editline, EL_PREP_TERM, 0))
+               goto fail;
+
+       if (el_set(el->editline, EL_EDITOR, "emacs"))
+               goto fail;
+
+       if (el_set(el->editline, EL_SETTY, "-d", "-isig", NULL))
+               goto fail;
+       if (el_set(el->editline, EL_ADDFN, "ed-break",
+                       "Break and flush the buffer",
+                       editline_break))
+               goto fail;
+       if (el_set(el->editline, EL_BIND, "^C", "ed-break", NULL))
+               goto fail;
+       if (el_set(el->editline, EL_ADDFN, "ed-suspend",
+                       "Suspend the terminal",
+                       editline_suspend))
+               goto fail;
+       if (el_set(el->editline, EL_BIND, "^Z", "ed-suspend", NULL))
+               goto fail;
+       if (el_set(el->editline, EL_BIND, "^W", "ed-delete-prev-word", NULL))
+               goto fail;
+
+       el->history = history_init();
+       if (!el->history)
+               goto fail;
+       if (history(el->history, &el->histev, H_SETSIZE,
+                       NC_CLI_HISTORY_SIZE) < 0)
+               goto fail;
+       if (history(el->history, &el->histev,
+                       H_SETUNIQUE, 1))
+               goto fail;
+       if (el_set(el->editline, EL_HIST, history,
+                       el->history))
+               goto fail;
+
+end:
+       return el;
+
+fail:
+       if (el != NULL) {
+               if (el->null_out != NULL)
+                       fclose(el->null_out);
+               if (el->history != NULL)
+                       history_end(el->history);
+               if (el->editline != NULL)
+                       el_end(el->editline);
+               free(el);
+       }
+       return NULL;
+}
+
+void
+nc_cli_editline_free(struct nc_cli_editline *el)
+{
+       if (el == NULL)
+               return;
+       nc_cli_editline_stop(el);
+       if (el->null_out != NULL)
+               fclose(el->null_out);
+       if (el->history != NULL)
+               history_end(el->history);
+       el_end(el->editline);
+       free(el->full_line);
+       free(el);
+}
diff --git a/src/meson.build b/src/meson.build
new file mode 100644 (file)
index 0000000..4c9860f
--- /dev/null
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+
+inc = include_directories('../include')
+
+libecoli_sources = [
+       'ecoli_assert.c',
+       'ecoli_complete.c',
+       'ecoli_config.c',
+       'ecoli_editline.c',
+       'ecoli_init.c',
+       'ecoli_keyval.c',
+       'ecoli_log.c',
+       'ecoli_malloc.c',
+       'ecoli_murmurhash.c',
+       'ecoli_node_any.c',
+       'ecoli_node.c',
+       'ecoli_node_cmd.c',
+#      'ecoli_node_cond.c',
+       'ecoli_node_dynamic.c',
+       'ecoli_node_empty.c',
+       'ecoli_node_expr.c',
+       'ecoli_node_expr_test.c',
+       'ecoli_node_file.c',
+       'ecoli_node_helper.c',
+       'ecoli_node_int.c',
+       'ecoli_node_many.c',
+       'ecoli_node_none.c',
+       'ecoli_node_once.c',
+       'ecoli_node_option.c',
+       'ecoli_node_or.c',
+       'ecoli_node_re.c',
+       'ecoli_node_re_lex.c',
+       'ecoli_node_seq.c',
+       'ecoli_node_sh_lex.c',
+       'ecoli_node_space.c',
+       'ecoli_node_str.c',
+       'ecoli_node_subset.c',
+       'ecoli_parse.c',
+       'ecoli_string.c',
+       'ecoli_strvec.c',
+       'ecoli_test.c',
+       'ecoli_vec.c',
+       'ecoli_yaml.c',
+]
+libecoli = shared_library('ecoli',
+       libecoli_sources,
+       include_directories : inc,
+       dependencies : [edit_dep, yaml_dep],
+       install : true)
diff --git a/test/meson.build b/test/meson.build
new file mode 100644 (file)
index 0000000..f61dbc6
--- /dev/null
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+
+inc = include_directories('../include')
+
+test_sources = [
+       'test.c',
+]
+
+ecoli_test = executable('ecoli-test',
+       test_sources,
+       include_directories : inc,
+       link_with : libecoli)
+
+test('libecoli test', ecoli_test)
index 2b84064..acb7069 100644 (file)
--- a/todo.txt
+++ b/todo.txt
@@ -123,6 +123,8 @@ X file + partial completion
 - fusion node: need to match several children, same for completion?
 - float
 - not
+- reparse: parse a tree with received strvec, then another tree
+  with strvec generated from first tree
 
 encoding
 ========
@@ -421,3 +423,17 @@ c
 ---------------
 
 
+about split in several libraries
+
+There are several options:
+
+1/ one library, config options to select libyaml, libedit
+   - need to manage dependencies in build system
+
+2/ one library for ecoli-core, one for ecoli-yaml, one for
+   ecoli-edit
+   - extra complexity
+
+3/ one library with core + yaml + edit
+   dependency is managed at runtime
+