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