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