+++ /dev/null
-# 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
--- /dev/null
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
+
+subdir('readline')
+subdir('parse-yaml')
--- /dev/null
+# 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])
--- /dev/null
+/* 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;
+}
--- /dev/null
+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
--- /dev/null
+# 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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_ */
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+/* 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
--- /dev/null
+# 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)
+++ /dev/null
-/* 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 */
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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_ */
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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 */
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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);
+++ /dev/null
-/* 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
+++ /dev/null
-/* 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;
-}
-
+++ /dev/null
-/* 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
+++ /dev/null
-/*
- * 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);
-}
+++ /dev/null
-/* 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;
-}
+++ /dev/null
-/* 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
--- /dev/null
+# 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.')
+++ /dev/null
-# 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,$@))
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)))
+++ /dev/null
-# 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
+++ /dev/null
-# 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,$@))
+++ /dev/null
-# 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
+++ /dev/null
-# 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,$@))
+++ /dev/null
-# 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)
+++ /dev/null
-# 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,$@))
+++ /dev/null
-# 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)
+++ /dev/null
-# 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,$@))
+++ /dev/null
-# 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)
+++ /dev/null
-# 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:
+++ /dev/null
-# 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
-
+++ /dev/null
-# 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,$@))
+++ /dev/null
-# 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)
+++ /dev/null
-# 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,$@))
+++ /dev/null
-# 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
+++ /dev/null
-# 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)
+++ /dev/null
-# 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)
+++ /dev/null
-# 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,$(+))
+++ /dev/null
-# 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)
--- /dev/null
+/* 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 */
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
+
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
--- /dev/null
+/* 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+# 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)
--- /dev/null
+# 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)
- 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
========
---------------
+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
+