test coverage
[protos/libecoli.git] / lib / ecoli_complete.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <assert.h>
9 #include <errno.h>
10
11 #include <ecoli_malloc.h>
12 #include <ecoli_string.h>
13 #include <ecoli_strvec.h>
14 #include <ecoli_keyval.h>
15 #include <ecoli_log.h>
16 #include <ecoli_test.h>
17 #include <ecoli_node.h>
18 #include <ecoli_parse.h>
19 #include <ecoli_node_sh_lex.h>
20 #include <ecoli_node_str.h>
21 #include <ecoli_node_or.h>
22 #include <ecoli_complete.h>
23
24 EC_LOG_TYPE_REGISTER(comp);
25
26 struct ec_comp_item {
27         TAILQ_ENTRY(ec_comp_item) next;
28         enum ec_comp_type type;
29         struct ec_comp_group *grp;
30         char *start;      /* the initial token */
31         char *full;       /* the full token after completion */
32         char *completion; /* chars that are added, NULL if not applicable */
33         char *display;    /* what should be displayed by help/completers */
34         struct ec_keyval *attrs;
35 };
36
37 struct ec_comp *ec_comp(struct ec_parse *state)
38 {
39         struct ec_comp *comp = NULL;
40
41         comp = ec_calloc(1, sizeof(*comp));
42         if (comp == NULL)
43                 goto fail;
44
45         comp->attrs = ec_keyval();
46         if (comp->attrs == NULL)
47                 goto fail;
48
49         TAILQ_INIT(&comp->groups);
50
51         comp->cur_state = state;
52
53         return comp;
54
55  fail:
56         if (comp != NULL)
57                 ec_keyval_free(comp->attrs);
58         ec_free(comp);
59
60         return NULL;
61 }
62
63 struct ec_parse *ec_comp_get_state(struct ec_comp *comp)
64 {
65         return comp->cur_state;
66 }
67
68 int
69 ec_node_complete_child(const struct ec_node *node,
70                 struct ec_comp *comp,
71                 const struct ec_strvec *strvec)
72 {
73         struct ec_parse *child_state, *cur_state;
74         struct ec_comp_group *cur_group;
75         int ret;
76
77         if (ec_node_type(node)->complete == NULL)
78                 return -ENOTSUP;
79
80         /* save previous parse state, prepare child state */
81         cur_state = comp->cur_state;
82         child_state = ec_parse(node);
83         if (child_state == NULL)
84                 return -ENOMEM;
85
86         if (cur_state != NULL)
87                 ec_parse_link_child(cur_state, child_state);
88         comp->cur_state = child_state;
89         cur_group = comp->cur_group;
90         comp->cur_group = NULL;
91
92         /* fill the comp struct with items */
93         ret = ec_node_type(node)->complete(node, comp, strvec);
94
95         /* restore parent parse state */
96         if (cur_state != NULL) {
97                 ec_parse_unlink_child(cur_state, child_state);
98                 assert(!ec_parse_has_child(child_state));
99         }
100         ec_parse_free(child_state);
101         comp->cur_state = cur_state;
102         comp->cur_group = cur_group;
103
104         if (ret < 0)
105                 return ret;
106
107         return 0;
108 }
109
110 struct ec_comp *ec_node_complete_strvec(const struct ec_node *node,
111         const struct ec_strvec *strvec)
112 {
113         struct ec_comp *comp = NULL;
114         int ret;
115
116         comp = ec_comp(NULL);
117         if (comp == NULL)
118                 goto fail;
119
120         ret = ec_node_complete_child(node, comp, strvec);
121         if (ret < 0)
122                 goto fail;
123
124         return comp;
125
126 fail:
127         ec_comp_free(comp);
128         return NULL;
129 }
130
131 struct ec_comp *ec_node_complete(const struct ec_node *node,
132         const char *str)
133 {
134         struct ec_strvec *strvec = NULL;
135         struct ec_comp *comp;
136
137         errno = ENOMEM;
138         strvec = ec_strvec();
139         if (strvec == NULL)
140                 goto fail;
141
142         if (ec_strvec_add(strvec, str) < 0)
143                 goto fail;
144
145         comp = ec_node_complete_strvec(node, strvec);
146         if (comp == NULL)
147                 goto fail;
148
149         ec_strvec_free(strvec);
150         return comp;
151
152  fail:
153         ec_strvec_free(strvec);
154         return NULL;
155 }
156
157 static struct ec_comp_group *
158 ec_comp_group(const struct ec_node *node, struct ec_parse *parse)
159 {
160         struct ec_comp_group *grp = NULL;
161
162         grp = ec_calloc(1, sizeof(*grp));
163         if (grp == NULL)
164                 return NULL;
165
166         grp->attrs = ec_keyval();
167         if (grp->attrs == NULL)
168                 goto fail;
169
170         grp->state = ec_parse_dup(parse);
171         if (grp->state == NULL)
172                 goto fail;
173
174         grp->node = node;
175         TAILQ_INIT(&grp->items);
176
177         return grp;
178
179 fail:
180         if (grp != NULL) {
181                 ec_parse_free(grp->state);
182                 ec_keyval_free(grp->attrs);
183         }
184         ec_free(grp);
185         return NULL;
186 }
187
188 static struct ec_comp_item *
189 ec_comp_item(enum ec_comp_type type,
190         const char *start, const char *full)
191 {
192         struct ec_comp_item *item = NULL;
193         struct ec_keyval *attrs = NULL;
194         char *comp_cp = NULL, *start_cp = NULL;
195         char *full_cp = NULL, *display_cp = NULL;
196
197         if (type == EC_COMP_UNKNOWN && full != NULL) {
198                 errno = EINVAL;
199                 return NULL;
200         }
201         if (type != EC_COMP_UNKNOWN && full == NULL) {
202                 errno = EINVAL;
203                 return NULL;
204         }
205
206         item = ec_calloc(1, sizeof(*item));
207         if (item == NULL)
208                 goto fail;
209
210         attrs = ec_keyval();
211         if (attrs == NULL)
212                 goto fail;
213
214         if (start != NULL) {
215                 start_cp = ec_strdup(start);
216                 if (start_cp == NULL)
217                         goto fail;
218
219                 if (ec_str_startswith(full, start)) {
220                         comp_cp = ec_strdup(&full[strlen(start)]);
221                         if (comp_cp == NULL)
222                                 goto fail;
223                 }
224         }
225         if (full != NULL) {
226                 full_cp = ec_strdup(full);
227                 if (full_cp == NULL)
228                         goto fail;
229                 display_cp = ec_strdup(full);
230                 if (display_cp == NULL)
231                         goto fail;
232         }
233
234         item->type = type;
235         item->start = start_cp;
236         item->full = full_cp;
237         item->completion = comp_cp;
238         item->display = display_cp;
239         item->attrs = attrs;
240
241         return item;
242
243 fail:
244         ec_keyval_free(attrs);
245         ec_free(comp_cp);
246         ec_free(start_cp);
247         ec_free(full_cp);
248         ec_free(display_cp);
249         ec_free(item);
250
251         return NULL;
252 }
253
254 int ec_comp_item_set_display(struct ec_comp_item *item,
255                                 const char *display)
256 {
257         char *display_copy = NULL;
258         int ret = 0;
259
260         if (item == NULL || display == NULL ||
261                         item->type == EC_COMP_UNKNOWN)
262                 return -EINVAL;
263
264         display_copy = ec_strdup(display);
265         if (display_copy == NULL)
266                 goto fail;
267
268         ec_free(item->display);
269         item->display = display_copy;
270
271         return 0;
272
273 fail:
274         ec_free(display_copy);
275         return ret;
276 }
277
278 int
279 ec_comp_item_set_completion(struct ec_comp_item *item,
280                                 const char *completion)
281 {
282         char *completion_copy = NULL;
283         int ret = 0;
284
285         if (item == NULL || completion == NULL ||
286                         item->type == EC_COMP_UNKNOWN)
287                 return -EINVAL;
288
289         ret = -ENOMEM;
290         completion_copy = ec_strdup(completion);
291         if (completion_copy == NULL)
292                 goto fail;
293
294         ec_free(item->completion);
295         item->completion = completion_copy;
296
297         return 0;
298
299 fail:
300         ec_free(completion_copy);
301         return ret;
302 }
303
304 int
305 ec_comp_item_set_str(struct ec_comp_item *item,
306                         const char *str)
307 {
308         char *str_copy = NULL;
309         int ret = 0;
310
311         if (item == NULL || str == NULL ||
312                         item->type == EC_COMP_UNKNOWN)
313                 return -EINVAL;
314
315         ret = -ENOMEM;
316         str_copy = ec_strdup(str);
317         if (str_copy == NULL)
318                 goto fail;
319
320         ec_free(item->full);
321         item->full = str_copy;
322
323         return 0;
324
325 fail:
326         ec_free(str_copy);
327         return ret;
328 }
329
330 static int
331 ec_comp_item_add(struct ec_comp *comp, const struct ec_node *node,
332                 struct ec_comp_item *item)
333 {
334         if (comp == NULL || item == NULL)
335                 return -EINVAL;
336
337         switch (item->type) {
338         case EC_COMP_UNKNOWN:
339                 comp->count_unknown++;
340                 break;
341         case EC_COMP_FULL:
342                 comp->count_full++;
343                 break;
344         case EC_COMP_PARTIAL:
345                 comp->count_partial++;
346                 break;
347         default:
348                 return -EINVAL;
349         }
350
351         if (comp->cur_group == NULL) {
352                 struct ec_comp_group *grp;
353
354                 grp = ec_comp_group(node, comp->cur_state);
355                 if (grp == NULL)
356                         return -ENOMEM;
357                 TAILQ_INSERT_TAIL(&comp->groups, grp, next);
358                 comp->cur_group = grp;
359         }
360
361         comp->count++;
362         TAILQ_INSERT_TAIL(&comp->cur_group->items, item, next);
363         item->grp = comp->cur_group;
364
365         return 0;
366 }
367
368 const char *
369 ec_comp_item_get_str(const struct ec_comp_item *item)
370 {
371         return item->full;
372 }
373
374 const char *
375 ec_comp_item_get_display(const struct ec_comp_item *item)
376 {
377         return item->display;
378 }
379
380 const char *
381 ec_comp_item_get_completion(const struct ec_comp_item *item)
382 {
383         return item->completion;
384 }
385
386 enum ec_comp_type
387 ec_comp_item_get_type(const struct ec_comp_item *item)
388 {
389         return item->type;
390 }
391
392 const struct ec_comp_group *
393 ec_comp_item_get_grp(const struct ec_comp_item *item)
394 {
395         return item->grp;
396 }
397
398 const struct ec_node *
399 ec_comp_item_get_node(const struct ec_comp_item *item)
400 {
401         return ec_comp_item_get_grp(item)->node;
402 }
403
404 static void
405 ec_comp_item_free(struct ec_comp_item *item)
406 {
407         if (item == NULL)
408                 return;
409
410         ec_free(item->full);
411         ec_free(item->start);
412         ec_free(item->completion);
413         ec_free(item->display);
414         ec_keyval_free(item->attrs);
415         ec_free(item);
416 }
417
418 int ec_comp_add_item(struct ec_comp *comp,
419                         const struct ec_node *node,
420                         struct ec_comp_item **p_item,
421                         enum ec_comp_type type,
422                         const char *start, const char *full)
423 {
424         struct ec_comp_item *item = NULL;
425         int ret;
426
427         item = ec_comp_item(type, start, full);
428         if (item == NULL)
429                 return -1;
430
431         ret = ec_comp_item_add(comp, node, item);
432         if (ret < 0)
433                 goto fail;
434
435         if (p_item != NULL)
436                 *p_item = item;
437
438         return 0;
439
440 fail:
441         ec_comp_item_free(item);
442
443         return -1;
444 }
445
446 /* return a completion item of type "unknown" */
447 int
448 ec_node_complete_unknown(const struct ec_node *gen_node,
449                         struct ec_comp *comp,
450                         const struct ec_strvec *strvec)
451 {
452         int ret;
453
454         if (ec_strvec_len(strvec) != 1)
455                 return 0;
456
457         ret = ec_comp_add_item(comp, gen_node, NULL,
458                                 EC_COMP_UNKNOWN, NULL, NULL);
459         if (ret < 0)
460                 return ret;
461
462         return 0;
463 }
464
465 static void ec_comp_group_free(struct ec_comp_group *grp)
466 {
467         struct ec_comp_item *item;
468
469         if (grp == NULL)
470                 return;
471
472         while (!TAILQ_EMPTY(&grp->items)) {
473                 item = TAILQ_FIRST(&grp->items);
474                 TAILQ_REMOVE(&grp->items, item, next);
475                 ec_comp_item_free(item);
476         }
477         ec_parse_free(ec_parse_get_root(grp->state));
478         ec_keyval_free(grp->attrs);
479         ec_free(grp);
480 }
481
482 void ec_comp_free(struct ec_comp *comp)
483 {
484         struct ec_comp_group *grp;
485
486         if (comp == NULL)
487                 return;
488
489         while (!TAILQ_EMPTY(&comp->groups)) {
490                 grp = TAILQ_FIRST(&comp->groups);
491                 TAILQ_REMOVE(&comp->groups, grp, next);
492                 ec_comp_group_free(grp);
493         }
494         ec_keyval_free(comp->attrs);
495         ec_free(comp);
496 }
497
498 void ec_comp_dump(FILE *out, const struct ec_comp *comp)
499 {
500         struct ec_comp_group *grp;
501         struct ec_comp_item *item;
502
503         if (comp == NULL || comp->count == 0) {
504                 fprintf(out, "no completion\n");
505                 return;
506         }
507
508         fprintf(out, "completion: count=%u full=%u partial=%u unknown=%u\n",
509                 comp->count, comp->count_full,
510                 comp->count_partial,  comp->count_unknown);
511
512         TAILQ_FOREACH(grp, &comp->groups, next) {
513                 fprintf(out, "node=%p, node_type=%s\n",
514                         grp->node, ec_node_type(grp->node)->name);
515                 TAILQ_FOREACH(item, &grp->items, next) {
516                         const char *typestr;
517
518                         switch (item->type) {
519                         case EC_COMP_UNKNOWN: typestr = "unknown"; break;
520                         case EC_COMP_FULL: typestr = "full"; break;
521                         case EC_COMP_PARTIAL: typestr = "partial"; break;
522                         default: typestr = "unknown"; break;
523                         }
524
525                         fprintf(out, "  type=%s str=<%s> comp=<%s> disp=<%s>\n",
526                                 typestr, item->full, item->completion,
527                                 item->display);
528                 }
529         }
530 }
531
532 int ec_comp_merge(struct ec_comp *to,
533                 struct ec_comp *from)
534 {
535         struct ec_comp_group *grp;
536
537         while (!TAILQ_EMPTY(&from->groups)) {
538                 grp = TAILQ_FIRST(&from->groups);
539                 TAILQ_REMOVE(&from->groups, grp, next);
540                 TAILQ_INSERT_TAIL(&to->groups, grp, next);
541         }
542         to->count += from->count;
543         to->count_full += from->count_full;
544         to->count_partial += from->count_partial;
545         to->count_unknown += from->count_unknown;
546
547         ec_comp_free(from);
548         return 0;
549 }
550
551 unsigned int ec_comp_count(
552         const struct ec_comp *comp,
553         enum ec_comp_type type)
554 {
555         unsigned int count = 0;
556
557         if (comp == NULL)
558                 return count;
559
560         if (type & EC_COMP_FULL)
561                 count += comp->count_full;
562         if (type & EC_COMP_PARTIAL)
563                 count += comp->count_partial;
564         if (type & EC_COMP_UNKNOWN)
565                 count += comp->count_unknown;
566
567         return count;
568 }
569
570 struct ec_comp_iter *
571 ec_comp_iter(struct ec_comp *comp,
572         enum ec_comp_type type)
573 {
574         struct ec_comp_iter *iter;
575
576         iter = ec_calloc(1, sizeof(*iter));
577         if (iter == NULL)
578                 return NULL;
579
580         iter->comp = comp;
581         iter->type = type;
582         iter->cur_node = NULL;
583         iter->cur_match = NULL;
584
585         return iter;
586 }
587
588 struct ec_comp_item *ec_comp_iter_next(
589         struct ec_comp_iter *iter)
590 {
591         struct ec_comp *comp;
592         struct ec_comp_group *cur_node;
593         struct ec_comp_item *cur_match;
594
595         if (iter == NULL)
596                 return NULL;
597         comp = iter->comp;
598         if (comp == NULL)
599                 return NULL;
600
601         cur_node = iter->cur_node;
602         cur_match = iter->cur_match;
603
604         /* first call */
605         if (cur_node == NULL) {
606                 TAILQ_FOREACH(cur_node, &comp->groups, next) {
607                         TAILQ_FOREACH(cur_match, &cur_node->items, next) {
608                                 if (cur_match != NULL &&
609                                                 cur_match->type & iter->type)
610                                         goto found;
611                         }
612                 }
613                 return NULL;
614         } else {
615                 cur_match = TAILQ_NEXT(cur_match, next);
616                 if (cur_match != NULL &&
617                                 cur_match->type & iter->type)
618                         goto found;
619                 cur_node = TAILQ_NEXT(cur_node, next);
620                 while (cur_node != NULL) {
621                         cur_match = TAILQ_FIRST(&cur_node->items);
622                         if (cur_match != NULL &&
623                                         cur_match->type & iter->type)
624                                 goto found;
625                         cur_node = TAILQ_NEXT(cur_node, next);
626                 }
627                 return NULL;
628         }
629
630 found:
631         iter->cur_node = cur_node;
632         iter->cur_match = cur_match;
633
634         return iter->cur_match;
635 }
636
637 void ec_comp_iter_free(struct ec_comp_iter *iter)
638 {
639         ec_free(iter);
640 }
641
642 /* LCOV_EXCL_START */
643 static int ec_comp_testcase(void)
644 {
645         struct ec_node *node = NULL;
646         struct ec_comp *c = NULL;
647         struct ec_comp_iter *iter = NULL;
648         struct ec_comp_item *item;
649         FILE *f = NULL;
650         char *buf = NULL;
651         size_t buflen = 0;
652         int testres = 0;
653
654         node = ec_node_sh_lex(EC_NO_ID,
655                         EC_NODE_OR(EC_NO_ID,
656                                 ec_node_str("id_x", "xx"),
657                                 ec_node_str("id_y", "yy")));
658         if (node == NULL)
659                 goto fail;
660
661         c = ec_node_complete(node, "xcdscds");
662         testres |= EC_TEST_CHECK(
663                 c != NULL && ec_comp_count(c, EC_COMP_ALL) == 0,
664                 "complete count should is not 0\n");
665         ec_comp_free(c);
666
667         c = ec_node_complete(node, "x");
668         testres |= EC_TEST_CHECK(
669                 c != NULL && ec_comp_count(c, EC_COMP_ALL) == 1,
670                 "complete count should is not 1\n");
671         ec_comp_free(c);
672
673         c = ec_node_complete(node, "");
674         testres |= EC_TEST_CHECK(
675                 c != NULL && ec_comp_count(c, EC_COMP_ALL) == 2,
676                 "complete count should is not 2\n");
677
678         f = open_memstream(&buf, &buflen);
679         if (f == NULL)
680                 goto fail;
681         ec_comp_dump(f, c);
682         fclose(f);
683         f = NULL;
684
685         /* testres |= EC_TEST_CHECK( */
686         /*      strstr(buf, "no match"), "bad dump\n"); */
687         free(buf);
688         buf = NULL;
689
690         iter = ec_comp_iter(c, EC_COMP_ALL);
691         item = ec_comp_iter_next(iter);
692         if (item == NULL)
693                 goto fail;
694
695         testres |= EC_TEST_CHECK(
696                 !strcmp(ec_comp_item_get_display(item), "xx"),
697                 "bad item display\n");
698         testres |= EC_TEST_CHECK(
699                 ec_comp_item_get_type(item) == EC_COMP_FULL,
700                 "bad item type\n");
701         testres |= EC_TEST_CHECK(
702                 !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_x"),
703                 "bad item node\n");
704
705         item = ec_comp_iter_next(iter);
706         if (item == NULL)
707                 goto fail;
708
709         testres |= EC_TEST_CHECK(
710                 !strcmp(ec_comp_item_get_display(item), "yy"),
711                 "bad item display\n");
712         testres |= EC_TEST_CHECK(
713                 ec_comp_item_get_type(item) == EC_COMP_FULL,
714                 "bad item type\n");
715         testres |= EC_TEST_CHECK(
716                 !strcmp(ec_node_id(ec_comp_item_get_node(item)), "id_y"),
717                 "bad item node\n");
718
719         item = ec_comp_iter_next(iter);
720         testres |= EC_TEST_CHECK(item == NULL, "should be the last item\n");
721
722         ec_comp_iter_free(iter);
723         ec_comp_free(c);
724         ec_node_free(node);
725
726         return testres;
727
728 fail:
729         ec_comp_iter_free(iter);
730         ec_comp_free(c);
731         ec_node_free(node);
732         if (f != NULL)
733                 fclose(f);
734         free(buf);
735
736         return -1;
737 }
738 /* LCOV_EXCL_STOP */
739
740 static struct ec_test ec_comp_test = {
741         .name = "comp",
742         .test = ec_comp_testcase,
743 };
744
745 EC_TEST_REGISTER(ec_comp_test);