# XXX -O0
CFLAGS = -g -O0 -Wall -Werror -W -Wextra -fPIC -Wmissing-prototypes
-CFLAGS += -Ilibecoli -Ilibecoli_yaml
+CFLAGS += -Ilibecoli -Ilibecoli_yaml -Ilibecoli_editline
# XXX coverage
CFLAGS += --coverage
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
exe-y-$(O)readline = $(addprefix libecoli/,$(srcs)) \
examples/readline/main.c
-ldflags-$(O)parse-yaml = -lyaml
+ldflags-$(O)parse-yaml = -lyaml -ledit
exe-y-$(O)parse-yaml = $(addprefix libecoli/,$(srcs)) \
- libecoli_yaml/ecoli_yaml.c examples/yaml/parse-yaml.c
+ libecoli_yaml/ecoli_yaml.c libecoli_editline/ecoli_editline.c\
+ examples/yaml/parse-yaml.c
include $(ECOLI)/mk/ecoli-post.mk
#include <stdlib.h>
#include <stdio.h>
+#include <string.h>
+#include <getopt.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;
- if (argc != 2) {
- fprintf(stderr, "Invalid args\n");
+ 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(argv[1]);
+
+ node = ec_yaml_import(input_file);
if (node == NULL) {
fprintf(stderr, "Failed to parse file\n");
goto fail;
}
- ec_node_dump(stdout, node);
+ //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;
-type: seq
-attrs:
- toto: 1
- titi: 2
-help: Say hello to someone
+type: or
children:
-- type: str
- string: hello
-- type: or
- id: name
- help: Name of the person to greet
+- type: seq
+ id: hello
+ help: Say hello to someone
children:
- type: str
- string: john
+ 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: mike
+ string: bye
+ - type: or
+ id: name
+ help: Name of the person to greet
+ children:
+ - type: str
+ string: mary
+ - type: str
+ string: jessica
}
struct ec_comp_iter *
-ec_comp_iter(struct ec_comp *comp,
+ec_comp_iter(const struct ec_comp *comp,
enum ec_comp_type type)
{
struct ec_comp_iter *iter;
struct ec_comp_item *ec_comp_iter_next(
struct ec_comp_iter *iter)
{
- struct ec_comp *comp;
+ const struct ec_comp *comp;
struct ec_comp_group *cur_node;
struct ec_comp_item *cur_match;
*/
struct ec_comp_iter {
enum ec_comp_type type;
- struct ec_comp *comp;
+ const struct ec_comp *comp;
struct ec_comp_group *cur_node;
struct ec_comp_item *cur_match;
};
*
*/
struct ec_comp_iter *
-ec_comp_iter(struct ec_comp *comp,
+ec_comp_iter(const struct ec_comp *comp,
enum ec_comp_type type);
/**
* Register a log type.
*
* This macro defines a function that will be called at startup (using
- * the "constructor" attribute). This function register the named type
+ * 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().
const struct ec_node *ec_parse_get_node(const struct ec_parse *parse)
{
+ if (parse == NULL)
+ return NULL;
+
return parse->node;
}
#include <sys/types.h>
#include <limits.h>
#include <stdio.h>
+#include <stdbool.h>
struct ec_node;
struct ec_parse;
#include <stddef.h>
#include <string.h>
#include <stdio.h>
+#include <ctype.h>
#include <ecoli_assert.h>
#include <ecoli_malloc.h>
return ret;
}
+
+bool ec_str_is_space(const char *s)
+{
+ while (*s) {
+ if (!isspace(*s))
+ return false;
+ s++;
+ }
+ return true;
+}
#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);
/* 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 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 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);
+}
#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>
{
const struct ec_config_schema *schema;
const struct ec_node_type *type = NULL;
- const char *id = NULL, *help = 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;
fprintf(stderr, "Help must be a scalar\n");
goto fail;
}
- help = value_str;
+ help = ec_strdup(value_str);
+ if (help == NULL) {
+ fprintf(stderr, "Failed to allocate help\n");
+ goto fail;
+ }
}
}
goto fail;
}
+ 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
fail:
ec_node_free(enode);
ec_config_free(config);
+ ec_free(help);
+
return NULL;
}
--- /dev/null
+#!/bin/sh
+
+set -e
+
+# use a safer version of echo (no option)
+echo()
+{
+ printf "%s\n" "$*"
+}
+
+debug()
+{
+ echo "$@" >&2
+}
+
+# $1: node sequence number (ex: ec_node4)
+ec_parse_get_first_child()
+{
+ local first_child=${1}_first_child
+ echo $(eval 'echo ${'$first_child'}')
+}
+
+# $1: node sequence number (ex: ec_node4)
+ec_parse_get_next()
+{
+ local next=${1}_next
+ echo $(eval 'echo ${'$next'}')
+}
+
+# $1: node sequence number (ex: ec_node4)
+ec_parse_iter_next()
+{
+ local seq=${1#ec_node}
+ seq=$((seq+1))
+ local next=ec_node${seq}
+ if [ "$(ec_parse_get_id $next)" != "" ]; then
+ echo $next
+ fi
+}
+
+# $1: node sequence number (ex: ec_node4)
+ec_parse_get_id()
+{
+ local id=${1}_id
+ echo $(eval 'echo ${'$id'}')
+}
+
+# $1: node sequence number (ex: ec_node4)
+ec_parse_get_strvec_len()
+{
+ local strvec_len=${1}_strvec_len
+ echo $(eval 'echo ${'$strvec_len'}')
+}
+
+# $1: node sequence number (ex: ec_node4)
+# $2: index in strvec
+ec_parse_get_str()
+{
+ if [ $# -ne 2 ]; then
+ return
+ fi
+ local str=${1}_str${2}
+ echo $(eval 'echo ${'$str'}')
+}
+
+# $1: node sequence number (ex: ec_node4)
+# $2: node id (string)
+ec_parse_find_first()
+{
+ if [ $# -ne 2 ]; then
+ return
+ fi
+ local node_seq=$1
+ while [ "$node_seq" != "" ]; do
+ local id=$(ec_parse_get_id $node_seq)
+ if [ "$id" = "$2" ]; then
+ echo $node_seq
+ return 0
+ fi
+ node_seq=$(ec_parse_iter_next $node_seq)
+ done
+}
+
+path=$(dirname $0)
+
+yaml=$(mktemp)
+cat << EOF > $yaml
+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
+EOF
+
+output=$(mktemp)
+match=1
+$path/build/parse-yaml -i $yaml -o $output || match=0
+if [ "$match" = "1" ]; then
+ cat $output
+ . $output
+ name=$(ec_parse_get_str $(ec_parse_find_first ec_node1 name) 0)
+ hello=$(ec_parse_get_str $(ec_parse_find_first ec_node1 hello) 0)
+
+ if [ "$hello" != "" ]; then
+ echo "$name says hello to you!"
+ else
+ echo "$name says good bye to you!"
+ fi
+else
+ echo "no match"
+fi
+rm $output
+rm $yaml
--- /dev/null
+
+# complete -F _parser_options dummy_command
+
+_parser_options()
+{
+ local curr_arg
+ local i
+
+ curr_arg=${COMP_WORDS[COMP_CWORD]}
+
+ # remove the command name, only keep args
+ words=( "${COMP_WORDS[@]}" )
+ unset words[0]
+
+ IFS=$'\n' read -d '' -r -a COMPREPLY <<- EOF
+ $(./build/parse-yaml --complete -i examples/yaml/test.yaml -o pipo -- "${words[@]}")
+ EOF
+}
#define EC_OPT_SEED "seed"
static const struct option ec_long_options[] = {
- {EC_OPT_HELP, 1, NULL, 'h'},
+ {EC_OPT_HELP, 0, NULL, 'h'},
{EC_OPT_LOG_LEVEL, 1, NULL, 'l'},
{EC_OPT_RANDOM_ALLOC_FAIL, 1, NULL, 'r'},
{EC_OPT_SEED, 1, NULL, 's'},
static void usage(const char *prgname)
{
- printf("%s [options] [test1 test2 test3...]\n"
+ fprintf(stderr, "%s [options] [test1 test2 test3...]\n"
" -h\n"
" --"EC_OPT_HELP"\n"
" Show this help.\n"
X evaluate expression tree in ec_tk_expr
X cmd token
-- example
+X example
X tk_re
cleanup / rework
- better logs
- check return values (-1 or NULL) + use errno
- check missing static / const
-- license: SPDX
+X license: SPDX
- check all completion nodes
X split ecoli_tk.h
- size_t or unsigned int?
X save node path in completion to fix help string
- code coverage
- try to hide structures
-- anything better than weakref?
+X anything better than weakref?
- add ec_node_defaults.[ch] providing usual implementations of node methods
X use vec for strvec
-- ELOOP in case of loop
-- remove weakref?
+/ ELOOP in case of loop
+X remove weakref?
- sh_lex to provide offsets in attributes
- accessors for all structs
====
X register nodes by name
-- interface to add attributes: all nodes must be configurable through a
- generic api
- - attr string
- - attr string list
- - attr node
- - attr node list
- - attr int
-
-- yaml interface to create nodes
+X interface to add attributes: all nodes must be configurable through a
+ generic api:
+ X attr string
+ X attr string list
+ X attr node
+ X attr node list
+ X attr int
+
+X yaml interface to create nodes
- example
examples
- configuration file
- mini shell: cd, ls, cat, stat
- mini network console based on ip
+- dialog-like for use in shell
doc
===
- add make help
- add make config
- -fvisibility=
+- use meson
tests
=====
=========
- regexp
-- node which always matches
-- file + partial completion
+X node which always matches
+X file + partial completion
- ether, ip, network
-- fusion node: need to match several children, same for completion
+- fusion node: need to match several children, same for completion?
- float
- not
netconf example
===============
-- demonstration example that parses yang file and generate cli
+/ demonstration example that parses yang file and generate cli