save
[protos/libecoli.git] / lib / main-readline.c
1 /*
2  * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #define _GNU_SOURCE /* for asprintf */
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <assert.h>
32
33 #include <readline/readline.h>
34 #include <readline/history.h>
35
36 #include <ecoli_node.h>
37 #include <ecoli_parsed.h>
38 #include <ecoli_completed.h>
39 #include <ecoli_keyval.h>
40 #include <ecoli_node_str.h>
41 #include <ecoli_node_seq.h>
42 #include <ecoli_node_space.h>
43 #include <ecoli_node_or.h>
44 #include <ecoli_node_sh_lex.h>
45 #include <ecoli_node_int.h>
46 #include <ecoli_node_option.h>
47 #include <ecoli_node_cmd.h>
48 #include <ecoli_node_many.h>
49 #include <ecoli_node_once.h>
50 #include <ecoli_node_file.h>
51
52 static struct ec_node *commands;
53
54 static char *my_completion_entry(const char *s, int state)
55 {
56         static struct ec_completed *c;
57         static struct ec_completed_iter *iter;
58         static const struct ec_completed_match *item;
59         char *out_string;
60
61         /* don't append a quote */
62         rl_completion_suppress_quote = 1;
63         rl_basic_quote_characters = "";
64
65         if (state == 0) {
66                 char *line;
67
68                 ec_completed_free(c);
69
70                 line = strdup(rl_line_buffer);
71                 if (line == NULL)
72                         return NULL;
73                 line[rl_point] = '\0';
74
75                 c = ec_node_complete(commands, line);
76                 free(line);
77                 if (c == NULL)
78                         return NULL;
79
80                 ec_completed_iter_free(iter);
81                 iter = ec_completed_iter(c, EC_MATCH | EC_PARTIAL_MATCH);
82                 if (iter == NULL)
83                         return NULL;
84         }
85
86         item = ec_completed_iter_next(iter);
87         if (item == NULL)
88                 return NULL;
89
90         /* don't add the trailing space for partial completions */
91         if (state == 0) {
92                 if (item->type == EC_MATCH)
93                         rl_completion_suppress_append = 0;
94                 else
95                         rl_completion_suppress_append = 1;
96         }
97
98         //XXX see printable_part() and rl_display_match_list()
99         //XXX or: if match count > 1, strip beginning (in node_file)
100
101         if (asprintf(&out_string, "%s%s", s, item->add) < 0) // XXX ec_asprintf (check all in code)
102                 return NULL;
103
104         return out_string;
105 }
106
107 static char **my_attempted_completion(const char *text, int start, int end)
108 {
109         (void)start;
110         (void)end;
111
112         /* remove default file completion */
113         rl_attempted_completion_over = 1;
114
115         return rl_completion_matches(text, my_completion_entry);
116 }
117
118 /* this function builds the help string */
119 static char *get_node_help(const struct ec_completed_match *elt)
120 {
121         const struct ec_node *node;
122         char *help = NULL;
123         const char *node_help = NULL;
124         const char *node_desc = NULL;
125         size_t i;
126
127         for (i = 0; i < elt->pathlen; i++) {
128                 node = elt->path[i];
129                 if (node_help == NULL)
130                         node_help = ec_keyval_get(ec_node_attrs(node), "help");
131                 if (node_desc == NULL)
132                         node_desc = ec_node_desc(node);
133         }
134
135         if (node_help == NULL)
136                 node_help = "";
137         if (node_desc == NULL)
138                 return NULL;
139
140         if (asprintf(&help, "%-20s %s", node_desc, node_help) < 0)
141                 return NULL;
142
143         return help;
144 }
145
146 static int show_help(int ignore, int invoking_key)
147 {
148         const struct ec_completed_node *compnode;
149 //      const struct ec_completed_match *item;
150 //      struct ec_completed_iter *iter;
151         struct ec_completed *c;
152         struct ec_parsed *p;
153         char *line;
154         unsigned int count, i;
155         char **helps = NULL;
156         int match = 0;
157
158         (void)ignore;
159         (void)invoking_key;
160
161         line = strdup(rl_line_buffer);
162         if (line == NULL)
163                 return 1;
164
165         /* check if the current line matches */
166         p = ec_node_parse(commands, line);
167         if (ec_parsed_matches(p))
168                 match = 1;
169         ec_parsed_free(p);
170
171         /* complete at current cursor position */
172         line[rl_point] = '\0';
173         c = ec_node_complete(commands, line);
174         //ec_completed_dump(stdout, c);
175         free(line);
176         if (c == NULL)
177                 return 1;
178
179 #if 0 // old method
180         count = ec_completed_count(c, EC_MATCH | EC_NO_MATCH |
181                                 EC_PARTIAL_MATCH);
182         helps = calloc(count + match + 1, sizeof(char *));
183         if (helps == NULL)
184                 return 1;
185
186         if (match)
187                 helps[1] = "<return>";
188
189         iter = ec_completed_iter(c, EC_MATCH | EC_NO_MATCH |
190                                 EC_PARTIAL_MATCH);
191         if (iter == NULL)
192                 goto fail;
193
194         /* strangely, rl_display_match_list() expects first index at 1 */
195         for (i = match + 1, item = ec_completed_iter_next(iter);
196              i < count + match + 1 && item != NULL;
197              i++, item = ec_completed_iter_next(iter)) {
198                 helps[i] = get_node_help(item);
199         }
200 #else
201         count = 0;
202         TAILQ_FOREACH(compnode, &c->nodes, next) {
203                 if (TAILQ_EMPTY(&compnode->matches))
204                         continue;
205                 count++;
206         }
207
208         helps = calloc(count + match + 1, sizeof(char *));
209         if (helps == NULL)
210                 return 1;
211
212         if (match)
213                 helps[1] = "<return>";
214
215         i = match + 1;
216         TAILQ_FOREACH(compnode, &c->nodes, next) {
217                 if (TAILQ_EMPTY(&compnode->matches))
218                         continue;
219                 // we should pass compnode instead
220                 helps[i++] = get_node_help(TAILQ_FIRST(&compnode->matches)); //XXX
221         }
222 #endif
223
224         ec_completed_dump(stdout, c);
225         ec_completed_free(c);
226
227         rl_display_match_list(helps, count + match, 1000); /* XXX 1000 */
228
229         rl_forced_update_display();
230
231         return 0;
232
233 //fail:
234         free(helps);
235         // free helps[n] XXX
236         return 1;
237 }
238
239 static int create_commands(void)
240 {
241         struct ec_node *cmdlist = NULL, *cmd = NULL;
242
243         cmdlist = ec_node("or", NULL);
244         if (cmdlist == NULL)
245                 goto fail;
246
247
248         cmd = EC_NODE_SEQ(NULL,
249                 ec_node_str(NULL, "hello"),
250                 EC_NODE_OR("name",
251                         ec_node_str("john", "john"),
252                         ec_node_str(NULL, "johnny"),
253                         ec_node_str(NULL, "mike")
254                 ),
255                 ec_node_option(NULL, ec_node_int("int", 0, 10, 10))
256         );
257         if (cmd == NULL)
258                 goto fail;
259         ec_keyval_set(ec_node_attrs(cmd), "help",
260                 "say hello to someone several times", NULL);
261         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "john")),
262                 "help", "specific help for john", NULL);
263         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")),
264                 "help", "the name of the person", NULL);
265         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "int")),
266                 "help", "an integer", NULL);
267         if (ec_node_or_add(cmdlist, cmd) < 0)
268                 goto fail;
269
270
271         cmd = EC_NODE_CMD(NULL, "good morning name [count]",
272                         EC_NODE_CMD("name", "bob|bobby|michael"),
273                         ec_node_int("count", 0, 10, 10));
274         if (cmd == NULL)
275                 goto fail;
276         ec_keyval_set(ec_node_attrs(cmd), "help",
277                 "say good morning to someone several times", NULL);
278         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), "help",
279                 "the person to greet", NULL);
280         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "count")), "help",
281                 "how many times to greet", NULL);
282         if (ec_node_or_add(cmdlist, cmd) < 0)
283                 goto fail;
284
285
286         cmd = EC_NODE_CMD(NULL,
287                         "buy potatoes,carrots,pumpkins");
288         if (cmd == NULL)
289                 goto fail;
290         ec_keyval_set(ec_node_attrs(cmd), "help",
291                 "buy some vegetables", NULL);
292         if (ec_node_or_add(cmdlist, cmd) < 0)
293                 goto fail;
294
295
296         cmd = EC_NODE_CMD(NULL, "eat vegetables",
297                         ec_node_many("vegetables",
298                                 EC_NODE_OR(NULL,
299                                         ec_node_str(NULL, "potatoes"),
300                                         ec_node_once(NULL,
301                                                 ec_node_str(NULL, "carrots")),
302                                         ec_node_once(NULL,
303                                                 ec_node_str(NULL, "pumpkins"))),
304                         1, 0));
305         if (cmd == NULL)
306                 goto fail;
307         ec_keyval_set(ec_node_attrs(cmd), "help",
308                 "eat vegetables (take some more potatoes)", NULL);
309         if (ec_node_or_add(cmdlist, cmd) < 0)
310                 goto fail;
311
312
313         cmd = EC_NODE_SEQ(NULL,
314                 ec_node_str(NULL, "bye")
315         );
316         ec_keyval_set(ec_node_attrs(cmd), "help", "say bye", NULL);
317         if (ec_node_or_add(cmdlist, cmd) < 0)
318                 goto fail;
319
320
321         cmd = EC_NODE_SEQ(NULL,
322                 ec_node_str(NULL, "load"),
323                 ec_node("file", NULL)
324         );
325         ec_keyval_set(ec_node_attrs(cmd), "help", "load a file", NULL);
326         if (ec_node_or_add(cmdlist, cmd) < 0)
327                 goto fail;
328
329
330         commands = ec_node_sh_lex(NULL, cmdlist);
331         if (commands == NULL)
332                 goto fail;
333
334         return 0;
335
336  fail:
337         fprintf(stderr, "cannot initialize nodes\n");
338         ec_node_free(cmdlist);
339         return -1;
340 }
341
342 int main(void)
343 {
344         struct ec_parsed *p;
345         char *line;
346
347         if (create_commands() < 0)
348                 return 1;
349
350         rl_bind_key('?', show_help);
351         rl_attempted_completion_function = my_attempted_completion;
352
353         while (1) {
354                 line = readline("> ");
355                 if (line == NULL)
356                         break;
357
358                 p = ec_node_parse(commands, line);
359                 ec_parsed_dump(stdout, p);
360                 add_history(line);
361                 ec_parsed_free(p);
362         }
363
364
365         ec_node_free(commands);
366         return 0;
367
368 }