genconf: basic support of completion for nodes starting with "//"
[libcmdline.git] / src / genconf / parse_confnode.c
index 5c8202b..562e688 100644 (file)
@@ -29,6 +29,7 @@
 #include <inttypes.h>
 #include <ctype.h>
 #include <string.h>
+#include <stdlib.h>
 #include <netinet/in.h>
 #include <sys/queue.h>
 
 #include "conf_htable.h"
 #include "parse_confnode.h"
 
-struct cmdline_token_ops token_conf_node_ops = {
-       .parse = parse_conf_node,
-       .complete_get_nb = complete_get_nb_conf_node,
-       .complete_get_elt = complete_get_elt_conf_node,
-       .get_help = get_help_conf_node,
-};
-
 /* return 0 if string matches pattern (string must not contain '*')*/
-static int strcmp_joker(const char *s, const char *pattern)
+static int
+strcmp_joker(const char *s, const char *pattern)
 {
        if (s == pattern)
                return 0;
@@ -95,47 +90,8 @@ find_in_children(struct confnode_list *l, struct confnode *n,
 }
 
 static int
-count_children(struct confnode *n, int flags, int mask)
-{
-       struct confnode *c;
-       int i = 0;
-
-       TAILQ_FOREACH(c, &n->children, next) {
-               /* if it's a "if" node, parse children */
-               if (c->flags & CONFNODE_F_INVISIBLE)
-                       i += count_children(c, flags, mask);
-               else if ((c->flags & mask) == flags)
-                       i ++;
-       }
-       return i;
-}
-
-static struct confnode *
-get_from_idx(struct confnode *n, unsigned int *cur, unsigned int idx,
-            int flags, int mask)
-{
-       struct confnode *c, *tmp;
-
-       TAILQ_FOREACH(c, &n->children, next) {
-               /* if it's a "if" node, parse children */
-               if (c->flags & CONFNODE_F_INVISIBLE) {
-                       tmp = get_from_idx(c, cur, idx, flags, mask);
-                       if (tmp)
-                               return tmp;
-               }
-               else {
-                       if ((c->flags & mask) != flags)
-                               continue;
-                       if (*cur == idx)
-                               return c;
-                       *cur = *cur + 1;
-               }
-       }
-       return NULL;
-}
-
-int
-parse_conf_node(cmdline_parse_token_hdr_t *tk, const char *buf, void *res)
+parse_conf_node(cmdline_parse_token_hdr_t *tk, const char *buf, void *res,
+               unsigned ressize)
 {
        struct token_conf_node *tk2 = (struct token_conf_node *)tk;
        struct token_conf_node_data *tkd = &tk2->conf_node_data;
@@ -144,6 +100,9 @@ parse_conf_node(cmdline_parse_token_hdr_t *tk, const char *buf, void *res)
        unsigned int token_len = 0;
        char token[CMDLINE_MAX_TOKEN_SIZE];
 
+       if (res && ressize < sizeof(struct confnode *))
+               return -1;
+
        /* if token is too big... */
        token_len = snprintf(token, sizeof(token), "%s", buf);
        if (token_len >= sizeof(token))
@@ -159,6 +118,8 @@ parse_conf_node(cmdline_parse_token_hdr_t *tk, const char *buf, void *res)
                                         tkd->mask, 1);
                else {
                        n = conf_htable_lookup(token + 2);
+                       if (n == NULL)
+                               return -1;
                        if ((n->flags & tkd->mask) != tkd->flags)
                                return -1;
                        TAILQ_INSERT_TAIL(&l, n, user_next);
@@ -174,40 +135,157 @@ parse_conf_node(cmdline_parse_token_hdr_t *tk, const char *buf, void *res)
        if (res)
                *(struct confnode **)res = TAILQ_FIRST(&l);
 
-       return token_len;
+       return 0;
+}
+
+struct confnode_complete_callback {
+       struct confnode *start;
+       struct confnode *cur;
+       int xpath;
+};
+
+
+static struct confnode *get_next_node(struct confnode *start,
+                                     struct confnode *cur, unsigned flags,
+                                     unsigned mask, int xpath)
+{
+       struct confnode *prev;
+       struct confnode *parent = start;
+       int descend = 1;
+
+       while (1) {
+               prev = cur;
+
+               /* no more node to browse */
+               if (cur == start)
+                       return NULL;
+
+               /* first iteration */
+               if (cur == NULL)
+                       cur = TAILQ_FIRST(&parent->children);
+               /* directory in xpath mode */
+               else if (xpath && descend &&
+                        (cur->flags & CONFNODE_F_IS_DIR) &&
+                        (cur->flags & CONFNODE_F_INVISIBLE) == 0)
+                       cur = TAILQ_FIRST(&cur->children);
+               /* other cases */
+               else
+                       cur = TAILQ_NEXT(cur, next);
+               descend = 1;
+
+               /* no next, return to upper level */
+               if (cur == NULL) {
+                       if (prev == NULL)
+                               return NULL; /* no node */
+                       cur = prev->parent;
+                       descend = 0;
+                       continue;
+               }
+
+               /* skip invisible nodes, and browse their contents */
+               if (cur->flags & CONFNODE_F_INVISIBLE) {
+                       parent = cur;
+                       cur = NULL;
+                       continue;
+               }
+
+               /* skip other nodes with no name */
+               if (cur->flags & CONFNODE_F_NO_NAME)
+                       continue;
+
+               /* check that flags match what we want */
+               if ((cur->flags & mask) != flags)
+                       continue;
+
+               /* standard node, return it */
+               return cur;
+       }
 }
 
-int complete_get_nb_conf_node(cmdline_parse_token_hdr_t *tk)
+static int
+complete_conf_node_start(cmdline_parse_token_hdr_t *tk,
+                        __attribute__((unused)) const char *tokstr,
+                        void **opaque)
 {
        struct token_conf_node *tk2 = (struct token_conf_node *)tk;
        struct token_conf_node_data *tkd = &tk2->conf_node_data;
+       struct confnode_complete_callback *cb;
+       struct confnode *n, *start;
+       unsigned flags = tkd->flags;
+       unsigned mask = tkd->mask;
+
+       *opaque = NULL;
+       cb = malloc(sizeof(*cb));
+       if (cb == NULL)
+               return -1;
+       *opaque = cb;
 
-       return count_children(*tkd->cur, tkd->flags, tkd->mask);
+       if (strlen(tokstr) >= 2 && tokstr[0] == '/' && tokstr[1] == '/') {
+               start = *tkd->root;
+               cb->xpath = 1;
+       }
+       else {
+               start = *tkd->cur;
+               cb->xpath = 0;
+       }
+
+       n = get_next_node(start, NULL, flags, mask, cb->xpath);
+       if (n == NULL) {
+               /* no completion.
+                * cb is freed in complete_conf_node_end()*/
+               return -1;
+       }
+
+       cb->start = start;
+       cb->cur = n;
+       return 0;
 }
 
-int complete_get_elt_conf_node(cmdline_parse_token_hdr_t *tk, int idx,
-                              char *dstbuf, unsigned int size)
+static int
+complete_conf_node_iterate(cmdline_parse_token_hdr_t *tk, void **opaque,
+                         char *dstbuf, unsigned int size)
 {
        struct token_conf_node *tk2 = (struct token_conf_node *)tk;
        struct token_conf_node_data *tkd = &tk2->conf_node_data;
+       struct confnode_complete_callback *cb;
        struct confnode *n;
-       unsigned int i = 0, len;
+       unsigned flags = tkd->flags;
+       unsigned mask = tkd->mask;
+       int len;
 
-       n = get_from_idx(*tkd->cur, &i, idx, tkd->flags, tkd->mask);
+       cb = *opaque;
+       n = cb->cur;
        if (n == NULL)
                return -1;
+       cb->cur = get_next_node(cb->start, n, flags, mask, cb->xpath);
 
-       len = strlen(n->name) + 1;
-       if (len > size)
+       len = snprintf(dstbuf, size, "%s%s",
+                      cb->xpath ? "//" : "",
+                      n->name);
+       if (len < 0 || len >= size)
                return -1;
 
-       strcpy(dstbuf, n->name);
        return 0;
 }
 
+static void
+complete_conf_node_end(cmdline_parse_token_hdr_t *tk, void **opaque)
+{
+       if (*opaque)
+               free(*opaque);
+}
 
-int get_help_conf_node(cmdline_parse_token_hdr_t *tk, char *dstbuf, unsigned int size)
+int help_conf_node(cmdline_parse_token_hdr_t *tk, char *dstbuf, unsigned int size)
 {
        snprintf(dstbuf, size, "conf-node");
        return 0;
 }
+
+struct cmdline_token_ops token_conf_node_ops = {
+       .parse = parse_conf_node,
+       .complete_start = complete_conf_node_start,
+       .complete_iterate = complete_conf_node_iterate,
+       .complete_end = complete_conf_node_end,
+       .help = help_conf_node,
+};
+