save
authorOlivier Matz <zer0@droids-corp.org>
Thu, 21 Sep 2017 14:07:03 +0000 (16:07 +0200)
committerOlivier Matz <zer0@droids-corp.org>
Thu, 21 Sep 2017 14:07:03 +0000 (16:07 +0200)
lib/Makefile
lib/ecoli_completed.c
lib/ecoli_completed.h
lib/ecoli_node_cmd.c
lib/ecoli_node_file.c [new file with mode: 0644]
lib/ecoli_node_file.h [new file with mode: 0644]
lib/ecoli_node_sh_lex.c
lib/ecoli_test.c
lib/main-readline.c
lib/todo.txt

index 29df719..8d44796 100644 (file)
@@ -53,7 +53,7 @@ srcs += ecoli_node_cmd.c
 srcs += ecoli_node_empty.c
 srcs += ecoli_node_expr.c
 srcs += ecoli_node_expr_test.c
-#srcs += ecoli_node_file.c
+srcs += ecoli_node_file.c
 srcs += ecoli_node_int.c
 srcs += ecoli_node_many.c
 srcs += ecoli_node_once.c
@@ -76,7 +76,7 @@ ldflags-$(O)test = -rdynamic
 exe-y-$(O)test = $(srcs) main.c
 
 ldflags-$(O)readline = -lreadline -ltermcap
-#exe-y-$(O)readline = $(srcs) main-readline.c
+exe-y-$(O)readline = $(srcs) main-readline.c
 
 include $(ECOLI)/mk/ecoli-post.mk
 
index a600902..c9aff48 100644 (file)
@@ -193,32 +193,26 @@ ec_completed_match(enum ec_completed_type type, struct ec_parsed *state,
                const struct ec_node *node, const char *add)
 {
        struct ec_completed_match *item = NULL;
+       struct ec_parsed *p;
+       size_t len;
 
        item = ec_calloc(1, sizeof(*item));
        if (item == NULL)
                return NULL;
 
-       /* XXX can state be NULL? */
-       if (state != NULL) {
-               struct ec_parsed *p;
-               size_t len;
-
-               /* get path len */
-               for (p = state, len = 0; p != NULL;
-                    p = ec_parsed_get_parent(p), len++)
-                       ;
-
-               item->path = ec_calloc(len, sizeof(*item->path));
-               if (item->path == NULL)
-                       goto fail;
-
-               item->pathlen = len;
-
-               /* write path in array */
-               for (p = state, len = 0; p != NULL;
-                    p = ec_parsed_get_parent(p), len++)
-                       item->path[len] = p->node;
-       }
+       /* get path len */
+       for (p = state, len = 0; p != NULL;
+            p = ec_parsed_get_parent(p), len++)
+               ;
+       /* allocate room for path */
+       item->path = ec_calloc(len, sizeof(*item->path));
+       if (item->path == NULL)
+               goto fail;
+       item->pathlen = len;
+       /* write path in array */
+       for (p = state, len = 0; p != NULL;
+            p = ec_parsed_get_parent(p), len++)
+               item->path[len] = p->node;
 
        item->type = type;
        item->node = node;
@@ -240,16 +234,18 @@ fail:
        return NULL;
 }
 
-int
-ec_completed_add_match(struct ec_completed *completed,
-               struct ec_parsed *parsed_state,
-               const struct ec_node *node, const char *add)
+static int
+__ec_completed_add_match(enum ec_completed_type type,
+                       struct ec_completed *completed,
+                       struct ec_parsed *parsed_state,
+                       const struct ec_node *node, const char *add)
 {
        struct ec_completed_node *compnode = NULL;
-       struct ec_completed_match *item = NULL;
+       struct ec_completed_match *match = NULL;
        int ret = -ENOMEM;
        size_t n;
 
+       /* find the compnode entry corresponding to this node */
        TAILQ_FOREACH(compnode, &completed->nodes, next) {
                if (compnode->node == node)
                        break;
@@ -257,68 +253,97 @@ ec_completed_add_match(struct ec_completed *completed,
        if (compnode == NULL)
                return -ENOENT;
 
-       item = ec_completed_match(EC_MATCH, parsed_state, node, add);
-       if (item == NULL)
+       match = ec_completed_match(type, parsed_state, node, add);
+       if (match == NULL)
                goto fail;
 
-       if (item->add != NULL) {
+       if (match->add != NULL) {
                if (completed->smallest_start == NULL) {
-                       completed->smallest_start = ec_strdup(item->add);
+                       completed->smallest_start = ec_strdup(match->add);
                        if (completed->smallest_start == NULL)
                                goto fail;
                } else {
-                       n = strcmp_count(item->add,
+                       n = strcmp_count(match->add,
                                completed->smallest_start);
                        completed->smallest_start[n] = '\0';
                }
                completed->count_match++;
        }
 
-       TAILQ_INSERT_TAIL(&compnode->matches, item, next);
+       TAILQ_INSERT_TAIL(&compnode->matches, match, next);
        completed->count++;
 
        return 0;
 
 fail:
-       ec_completed_match_free(item);
+       ec_completed_match_free(match);
        return ret;
 }
 
+int
+ec_completed_add_match(struct ec_completed *completed,
+               struct ec_parsed *parsed_state,
+               const struct ec_node *node, const char *add)
+{
+       return __ec_completed_add_match(EC_MATCH, completed, parsed_state,
+                                       node, add);
+}
+
+int
+ec_completed_add_no_match(struct ec_completed *completed,
+               struct ec_parsed *parsed_state,
+               const struct ec_node *node)
+{
+       return __ec_completed_add_match(EC_NO_MATCH, completed, parsed_state,
+                                       node, NULL);
+}
+
+int
+ec_completed_add_partial_match(struct ec_completed *completed,
+               struct ec_parsed *parsed_state,
+               const struct ec_node *node, const char *add)
+{
+       return __ec_completed_add_match(EC_PARTIAL_MATCH, completed, parsed_state,
+                                       node, add);
+}
+
 int
 ec_completed_add_node(struct ec_completed *completed,
                const struct ec_node *node)
 {
-       struct ec_completed_node *item = NULL;
+       struct ec_completed_node *compnode = NULL;
 
-       item = ec_completed_node(node);
-       if (item == NULL)
+       compnode = ec_completed_node(node);
+       if (compnode == NULL)
                return -ENOMEM;
 
-       TAILQ_INSERT_TAIL(&completed->nodes, item, next);
+       TAILQ_INSERT_TAIL(&completed->nodes, compnode, next);
        return 0;
 }
 
-void ec_completed_match_free(struct ec_completed_match *item)
+void ec_completed_match_free(struct ec_completed_match *match)
 {
-       ec_free(item->add);
-       ec_free(item->path);
-       ec_free(item);
+       ec_free(match->add);
+       ec_free(match->path);
+       ec_free(match);
 }
 
-/* default completion function: return a no-item element */
+/* default completion function: return a no-match element */
 int
 ec_node_default_complete(const struct ec_node *gen_node,
                        struct ec_completed *completed,
-                       struct ec_parsed *parsed,
+                       struct ec_parsed *parsed_state,
                        const struct ec_strvec *strvec)
 {
-       (void)gen_node;
-       (void)completed;
-       (void)parsed;
+       int ret;
 
-       if (ec_strvec_len(strvec) != 1) //XXX needed?
+       if (ec_strvec_len(strvec) != 1)
                return 0;
 
+       ret = ec_completed_add_no_match(completed, parsed_state, gen_node);
+       if (ret < 0)
+               return ret;
+
        return 0;
 }
 
@@ -363,7 +388,16 @@ void ec_completed_dump(FILE *out, const struct ec_completed *completed)
                fprintf(out, "node=%p, node_type=%s\n",
                        compnode->node, compnode->node->type->name);
                TAILQ_FOREACH(item, &compnode->matches, next) {
-                       fprintf(out, "add=<%s>\n", item->add); /* XXX comp type */
+                       const char *typestr;
+
+                       switch (item->type) {
+                       case EC_NO_MATCH: typestr = "no-match"; break;
+                       case EC_MATCH: typestr = "match"; break;
+                       case EC_PARTIAL_MATCH: typestr = "partial-match"; break;
+                       default: typestr = "unknown"; break;
+                       }
+
+                       fprintf(out, "  type=%s add=<%s>\n", typestr, item->add);
                }
        }
 }
@@ -429,19 +463,22 @@ const struct ec_completed_match *ec_completed_iter_next(
        if (cur_node == NULL) {
                TAILQ_FOREACH(cur_node, &completed->nodes, next) {
                        TAILQ_FOREACH(cur_match, &cur_node->matches, next) {
-                               if (cur_match != NULL)
+                               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)
+               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->matches);
-                       if (cur_match != NULL)
+                       if (cur_match != NULL &&
+                                       cur_match->type & iter->type)
                                goto found;
                        cur_node = TAILQ_NEXT(cur_node, next);
                }
index 6da00af..c81544a 100644 (file)
@@ -37,7 +37,7 @@ struct ec_node;
 enum ec_completed_type {
        EC_NO_MATCH,
        EC_MATCH,
-       EC_PARTIAL,
+       EC_PARTIAL_MATCH,
 };
 
 struct ec_completed_match {
@@ -46,7 +46,7 @@ struct ec_completed_match {
        const struct ec_node *node;
        char *add;
 
-       /* reverse order: [0] = last parsed, [len-1] = root */
+       /* reverse order: [0] = last, [len-1] = root */
        const struct ec_node **path;
        size_t pathlen;
 };
@@ -88,12 +88,16 @@ struct ec_completed *ec_completed(void);
 int ec_completed_add_match(struct ec_completed *completed,
                        struct ec_parsed *state,
                        const struct ec_node *node, const char *add);
-int ec_completed_add_node(struct ec_completed *completed,
+int ec_completed_add_no_match(struct ec_completed *completed,
+                       struct ec_parsed *parsed_state,
                        const struct ec_node *node);
-int ec_completed_add_partial(struct ec_completed *completed,
+int ec_completed_add_partial_match(struct ec_completed *completed,
                        struct ec_parsed *state, const struct ec_node *node,
                        const char *add);
 
+int ec_completed_add_node(struct ec_completed *completed,
+                       const struct ec_node *node);
+
 void ec_completed_match_free(struct ec_completed_match *item);
 void ec_completed_free(struct ec_completed *completed);
 void ec_completed_dump(FILE *out,
index 1f40b0f..01c2cd2 100644 (file)
@@ -515,16 +515,28 @@ static int ec_node_cmd_testcase(void)
        ret |= EC_TEST_CHECK_PARSE(node, -1, "foo");
        ec_node_free(node);
 
-       node = EC_NODE_CMD(NULL, "good morning bob|bobby|michael [count]",
+       node = EC_NODE_CMD(NULL, "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;
        }
-       ret |= EC_TEST_CHECK_PARSE(node, 4, "good", "morning", "bob", "1");
-       ec_node_free(node);
+       ret |= EC_TEST_CHECK_PARSE(node, 4, "good", "morning", "1", "bob");
+
+       ret |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               "good", EC_NODE_ENDLIST,
+               "good");
+       ret |= EC_TEST_CHECK_COMPLETE(node,
+               "g", EC_NODE_ENDLIST,
+               "ood", EC_NODE_ENDLIST,
+               "ood");
+       ret |= EC_TEST_CHECK_COMPLETE(node,
+               "good", "morning", "", EC_NODE_ENDLIST,
+               "bob", "bobby", "michael", EC_NODE_ENDLIST,
+               "");
 
-       // XXX completion
+       ec_node_free(node);
 
        return ret;
 }
diff --git a/lib/ecoli_node_file.c b/lib/ecoli_node_file.c
new file mode 100644 (file)
index 0000000..73a0174
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the University of California, Berkeley nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _GNU_SOURCE /* for asprintf */
+#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_node.h>
+#include <ecoli_parsed.h>
+#include <ecoli_completed.h>
+#include <ecoli_node_file.h>
+
+struct ec_node_file {
+       struct ec_node gen;
+};
+
+static int
+ec_node_file_parse(const struct ec_node *gen_node,
+               struct ec_parsed *state,
+               const struct ec_strvec *strvec)
+{
+       (void)gen_node;
+       (void)state;
+
+       if (ec_strvec_len(strvec) == 0)
+               return EC_PARSED_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 argument is not modified
+ * - the output is 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 -ENOMEM;
+       dname[dirlen] = '\0';
+
+       bname = ec_strdup(path + dirlen);
+       if (bname == NULL) {
+               ec_free(dname);
+               return -ENOMEM;
+       }
+
+       *dname_p = dname;
+       *bname_p = bname;
+
+       return 0;
+}
+
+static int
+ec_node_file_complete(const struct ec_node *gen_node,
+               struct ec_completed *completed,
+               struct ec_parsed *state,
+               const struct ec_strvec *strvec)
+{
+       struct stat st;
+       const char *path;
+       size_t bname_len;
+       struct dirent *de = NULL;
+       DIR *dir = NULL;
+       char *dname = NULL, *bname = NULL, *effective_dir;
+       char *add = NULL;
+       int ret;
+       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)
+               goto out;
+
+       path = ec_strvec_val(strvec, 0);
+       ret = split_path(path, &dname, &bname);
+       if (ret < 0) {
+               ec_completed_free(completed);
+               completed = NULL;
+               goto out;
+       }
+
+       if (strcmp(dname, "") == 0)
+               effective_dir = ".";
+       else
+               effective_dir = dname;
+
+       ret = lstat(effective_dir, &st);
+       if (ret != 0) {
+               ret = -errno;
+               goto out;
+       }
+       if (!S_ISDIR(st.st_mode))
+               goto out;
+
+       dir = opendir(effective_dir);
+       if (dir == NULL)
+               goto out;
+
+       bname_len = strlen(bname);
+       while (1) {
+               de = readdir(dir);
+               if (de == NULL)
+                       goto out;
+
+               if (strncmp(bname, de->d_name, bname_len))
+                       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) { // XXX todo
+               } else {
+                       is_dir = 0;
+               }
+
+               if (is_dir) {
+                       if (asprintf(&add, "%s/", &de->d_name[bname_len]) < 0) {
+                               ret = -errno;
+                               goto out;
+                       }
+                       if (ec_completed_add_partial_match(
+                                       completed, state, gen_node, add) < 0) {
+                               ec_completed_free(completed);
+                               completed = NULL;
+                               goto out;
+                       }
+               } else {
+                       if (asprintf(&add, "%s", &de->d_name[bname_len]) < 0) {
+                               ret = -errno;
+                               goto out;
+                       }
+                       if (ec_completed_add_match(completed, state, gen_node,
+                                                       add) < 0) {
+                               ec_completed_free(completed);
+                               completed = NULL;
+                               goto out;
+                       }
+               }
+       }
+       ret = 0;
+
+out:
+       free(add);
+       ec_free(dname);
+       ec_free(bname);
+       if (dir != NULL)
+               closedir(dir);
+
+       return ret;
+}
+
+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),
+};
+
+EC_NODE_TYPE_REGISTER(ec_node_file_type);
+
+/* LCOV_EXCL_START */
+static int ec_node_file_testcase(void)
+{
+       struct ec_node *node;
+       int ret = 0;
+
+       node = ec_node("file", NULL);
+       if (node == NULL) {
+               ec_log(EC_LOG_ERR, "cannot create node\n");
+               return -1;
+       }
+       /* any string matches */
+       ret |= EC_TEST_CHECK_PARSE(node, 1, "foo");
+       ret |= EC_TEST_CHECK_PARSE(node, 1, "/tmp/bar");
+       ret |= EC_TEST_CHECK_PARSE(node, -1);
+
+       /* test completion */
+       ret |= EC_TEST_CHECK_COMPLETE(node,
+               EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST,
+               "");
+       ret |= EC_TEST_CHECK_COMPLETE(node,
+               "", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST,
+               "");
+       ret |= EC_TEST_CHECK_COMPLETE(node,
+               "/", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST,
+               "");
+       ret |= EC_TEST_CHECK_COMPLETE(node,
+               "/tmp", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST,
+               "");
+       ret |= EC_TEST_CHECK_COMPLETE(node,
+               "/tmp/", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST,
+               "");
+       ret |= EC_TEST_CHECK_COMPLETE(node,
+               "/tmp/.", EC_NODE_ENDLIST,
+               EC_NODE_ENDLIST,
+               "");
+       ec_node_free(node);
+
+       return ret;
+}
+/* LCOV_EXCL_STOP */
+
+static struct ec_test ec_node_file_test = {
+       .name = "node_file",
+       .test = ec_node_file_testcase,
+};
+
+EC_TEST_REGISTER(ec_node_file_test);
diff --git a/lib/ecoli_node_file.h b/lib/ecoli_node_file.h
new file mode 100644 (file)
index 0000000..6a6d52a
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the University of California, Berkeley nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#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
index 59b917f..897f922 100644 (file)
@@ -141,8 +141,9 @@ static size_t eat_str(const char *str)
 {
        size_t i = 0;
 
-       /* skip spaces */
-       while (!isblank(str[i]) && str[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;
@@ -295,10 +296,14 @@ ec_node_sh_lex_complete(const struct ec_node *gen_node,
                return 0;
 
        str = ec_strvec_val(strvec, 0);
+//     printf("\nold:%s\n", str);
        new_vec = tokenize(str, 1, 1, &missing_quote);
        if (new_vec == NULL)
                goto fail;
+//     printf("new:%s\n", ec_strvec_val(new_vec, 0));
 
+       // XXX: complete should add the quotes for !EC_PARTIAL: use another
+       // completed object
        ret = ec_node_complete_child(node->child, completed, parsed, new_vec);
        if (ret < 0)
                goto fail;
@@ -370,6 +375,7 @@ static int ec_node_sh_lex_testcase(void)
        ret |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
        ret |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
        ret |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
+       ret |= EC_TEST_CHECK_PARSE(node, -1, "  foo toto bar'");
        ec_node_free(node);
 
        /* test completion */
index efe4c04..1e98c65 100644 (file)
@@ -166,7 +166,8 @@ int ec_test_check_complete(struct ec_node *tk, ...)
                        count, ec_completed_count(c, EC_MATCH));
                ec_completed_dump(stdout, c);
                ret = -1;
-       }
+       } else
+               ec_completed_dump(stdout, c); //XXX
 
        /* check the expected smallest start */
        expected = va_arg(ap, const char *);
index b8c6459..b8c2f0b 100644 (file)
@@ -47,6 +47,7 @@
 #include <ecoli_node_cmd.h>
 #include <ecoli_node_many.h>
 #include <ecoli_node_once.h>
+#include <ecoli_node_file.h>
 
 static struct ec_node *commands;
 
@@ -54,9 +55,12 @@ static char *my_completion_entry(const char *s, int state)
 {
        static struct ec_completed *c;
        static struct ec_completed_iter *iter;
-       static const struct ec_completed_item *item;
+       static const struct ec_completed_match *item;
        char *out_string;
 
+       /* don't append a quote */
+       rl_completion_suppress_quote = 1;
+       rl_basic_quote_characters = "";
 
        if (state == 0) {
                char *line;
@@ -74,16 +78,27 @@ static char *my_completion_entry(const char *s, int state)
                        return NULL;
 
                ec_completed_iter_free(iter);
-               iter = ec_completed_iter(c, EC_MATCH);
+               iter = ec_completed_iter(c, EC_MATCH | EC_PARTIAL_MATCH);
                if (iter == NULL)
                        return NULL;
        }
 
-       elt = ec_completed_iter_next(iter);
-       if (elt == NULL)
+       item = ec_completed_iter_next(iter);
+       if (item == NULL)
                return NULL;
 
-       if (asprintf(&out_string, "%s%s", s, elt->add) < 0)
+       /* don't add the trailing space for partial completions */
+       if (state == 0) {
+               if (item->type == EC_MATCH)
+                       rl_completion_suppress_append = 0;
+               else
+                       rl_completion_suppress_append = 1;
+       }
+
+       //XXX see printable_part() and rl_display_match_list()
+       //XXX or: if match count > 1, strip beginning (in node_file)
+
+       if (asprintf(&out_string, "%s%s", s, item->add) < 0) // XXX ec_asprintf (check all in code)
                return NULL;
 
        return out_string;
@@ -101,7 +116,7 @@ static char **my_attempted_completion(const char *text, int start, int end)
 }
 
 /* this function builds the help string */
-static char *get_node_help(const struct ec_completed_elt *elt)
+static char *get_node_help(const struct ec_completed_match *elt)
 {
        const struct ec_node *node;
        char *help = NULL;
@@ -130,8 +145,9 @@ static char *get_node_help(const struct ec_completed_elt *elt)
 
 static int show_help(int ignore, int invoking_key)
 {
-       const struct ec_completed_item *item;
-       struct ec_completed_iter *iter;
+       const struct ec_completed_node *compnode;
+//     const struct ec_completed_match *item;
+//     struct ec_completed_iter *iter;
        struct ec_completed *c;
        struct ec_parsed *p;
        char *line;
@@ -160,7 +176,9 @@ static int show_help(int ignore, int invoking_key)
        if (c == NULL)
                return 1;
 
-       count = ec_completed_count(c, EC_MATCH | EC_NO_MATCH);
+#if 0 // old method
+       count = ec_completed_count(c, EC_MATCH | EC_NO_MATCH |
+                               EC_PARTIAL_MATCH);
        helps = calloc(count + match + 1, sizeof(char *));
        if (helps == NULL)
                return 1;
@@ -168,7 +186,8 @@ static int show_help(int ignore, int invoking_key)
        if (match)
                helps[1] = "<return>";
 
-       iter = ec_completed_iter(c, EC_MATCH | EC_NO_MATCH);
+       iter = ec_completed_iter(c, EC_MATCH | EC_NO_MATCH |
+                               EC_PARTIAL_MATCH);
        if (iter == NULL)
                goto fail;
 
@@ -178,7 +197,31 @@ static int show_help(int ignore, int invoking_key)
             i++, item = ec_completed_iter_next(iter)) {
                helps[i] = get_node_help(item);
        }
+#else
+       count = 0;
+       TAILQ_FOREACH(compnode, &c->nodes, next) {
+               if (TAILQ_EMPTY(&compnode->matches))
+                       continue;
+               count++;
+       }
 
+       helps = calloc(count + match + 1, sizeof(char *));
+       if (helps == NULL)
+               return 1;
+
+       if (match)
+               helps[1] = "<return>";
+
+       i = match + 1;
+       TAILQ_FOREACH(compnode, &c->nodes, next) {
+               if (TAILQ_EMPTY(&compnode->matches))
+                       continue;
+               // we should pass compnode instead
+               helps[i++] = get_node_help(TAILQ_FIRST(&compnode->matches)); //XXX
+       }
+#endif
+
+       ec_completed_dump(stdout, c);
        ec_completed_free(c);
 
        rl_display_match_list(helps, count + match, 1000); /* XXX 1000 */
@@ -187,7 +230,7 @@ static int show_help(int ignore, int invoking_key)
 
        return 0;
 
-fail:
+//fail:
        free(helps);
        // free helps[n] XXX
        return 1;
@@ -275,6 +318,15 @@ static int create_commands(void)
                goto fail;
 
 
+       cmd = EC_NODE_SEQ(NULL,
+               ec_node_str(NULL, "load"),
+               ec_node("file", NULL)
+       );
+       ec_keyval_set(ec_node_attrs(cmd), "help", "load a file", NULL);
+       if (ec_node_or_add(cmdlist, cmd) < 0)
+               goto fail;
+
+
        commands = ec_node_sh_lex(NULL, cmdlist);
        if (commands == NULL)
                goto fail;
@@ -290,10 +342,8 @@ static int create_commands(void)
 int main(void)
 {
        struct ec_parsed *p;
-//     const char *name;
        char *line;
 
-
        if (create_commands() < 0)
                return 1;
 
index 7426228..37a61d7 100644 (file)
@@ -9,7 +9,10 @@ X tk_re
 cleanup / rework
 ================
 
+- add_no_match
+- add_partial_match
 - check XXX in code
+- properly manage quotes in shlex
 X remove the _new() functions
 - iterate children nodes without chaining them
 - add a tk vector type: will be used in several nodes (ex: or, seq, ...)
@@ -126,3 +129,21 @@ netconf example
 ===============
 
 - demonstration example that parses yang file and generate cli
+
+
+
+-----------------------
+
+readline:
+
+[tab]  list possible completions (matches only)
+[?]    list what is expected, example:
+
+"command [foo] toto|titi|<int>"
+
+help("command f") ->
+  foo     (help of foo)
+  toto    (help of toto)
+  titi    (help of titi)
+  <int>   (help of int)
+