b147e8a3e2fca4dc9dbefb0f5590fa799b93e271
[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
51 static struct ec_node *commands;
52
53 static char *my_completion_entry(const char *s, int state)
54 {
55         static struct ec_completed *c;
56         static struct ec_completed_iter *iter;
57         static const struct ec_completed_item *item;
58         char *out_string;
59
60
61         if (state == 0) {
62                 char *line;
63
64                 ec_completed_free(c);
65
66                 line = strdup(rl_line_buffer);
67                 if (line == NULL)
68                         return NULL;
69                 line[rl_point] = '\0';
70
71                 c = ec_node_complete(commands, line);
72                 free(line);
73                 if (c == NULL)
74                         return NULL;
75
76                 ec_completed_iter_free(iter);
77                 iter = ec_completed_iter(c, EC_MATCH);
78                 if (iter == NULL)
79                         return NULL;
80         }
81
82         item = ec_completed_iter_next(iter);
83         if (item == NULL)
84                 return NULL;
85
86         if (asprintf(&out_string, "%s%s", s, item->add) < 0)
87                 return NULL;
88
89         return out_string;
90 }
91
92 static char **my_attempted_completion(const char *text, int start, int end)
93 {
94         (void)start;
95         (void)end;
96
97         /* remove default file completion */
98         rl_attempted_completion_over = 1;
99
100         return rl_completion_matches(text, my_completion_entry);
101 }
102
103 /* this function builds the help string */
104 static char *get_node_help(const struct ec_completed_item *item)
105 {
106         const struct ec_node *node;
107         char *help = NULL;
108         const char *node_help = NULL;
109         const char *node_desc = NULL;
110         size_t i;
111
112         for (i = 0; i < item->pathlen; i++) {
113                 node = item->path[i];
114                 if (node_help == NULL)
115                         node_help = ec_keyval_get(ec_node_attrs(node), "help");
116                 if (node_desc == NULL)
117                         node_desc = ec_node_desc(node);
118         }
119
120         if (node_help == NULL)
121                 node_help = "";
122         if (node_desc == NULL)
123                 return NULL;
124
125         if (asprintf(&help, "%-20s %s", node_desc, node_help) < 0)
126                 return NULL;
127
128         return help;
129 }
130
131 static int show_help(int ignore, int invoking_key)
132 {
133         const struct ec_completed_item *item;
134         struct ec_completed_iter *iter;
135         struct ec_completed *c;
136         struct ec_parsed *p;
137         char *line;
138         unsigned int count, i;
139         char **helps = NULL;
140         int match = 0;
141
142         (void)ignore;
143         (void)invoking_key;
144
145         line = strdup(rl_line_buffer);
146         if (line == NULL)
147                 return 1;
148
149         /* check if the current line matches */
150         p = ec_node_parse(commands, line);
151         if (ec_parsed_matches(p))
152                 match = 1;
153         ec_parsed_free(p);
154
155         /* complete at current cursor position */
156         line[rl_point] = '\0';
157         c = ec_node_complete(commands, line);
158         //ec_completed_dump(stdout, c);
159         free(line);
160         if (c == NULL)
161                 return 1;
162
163         count = ec_completed_count(c, EC_MATCH | EC_NO_MATCH);
164         helps = calloc(count + match + 1, sizeof(char *));
165         if (helps == NULL)
166                 return 1;
167
168         if (match)
169                 helps[1] = "<return>";
170
171         iter = ec_completed_iter(c, EC_MATCH | EC_NO_MATCH);
172         if (iter == NULL)
173                 goto fail;
174
175         /* strangely, rl_display_match_list() expects first index at 1 */
176         for (i = match + 1, item = ec_completed_iter_next(iter);
177              i < count + match + 1 && item != NULL;
178              i++, item = ec_completed_iter_next(iter)) {
179                 helps[i] = get_node_help(item);
180         }
181
182         ec_completed_free(c);
183
184         rl_display_match_list(helps, count + match, 1000); /* XXX 1000 */
185
186         rl_forced_update_display();
187
188         return 0;
189
190 fail:
191         free(helps);
192         // free helps[n] XXX
193         return 1;
194 }
195
196 static int create_commands(void)
197 {
198         struct ec_node *cmdlist = NULL, *cmd = NULL;
199
200         cmdlist = ec_node("or", NULL);
201         if (cmdlist == NULL)
202                 goto fail;
203
204
205         cmd = EC_NODE_SEQ(NULL,
206                 ec_node_str(NULL, "hello"),
207                 EC_NODE_OR("name",
208                         ec_node_str("john", "john"),
209                         ec_node_str(NULL, "johnny"),
210                         ec_node_str(NULL, "mike")
211                 ),
212                 ec_node_option(NULL, ec_node_int("int", 0, 10, 10))
213         );
214         if (cmd == NULL)
215                 goto fail;
216         ec_keyval_set(ec_node_attrs(cmd), "help",
217                 "say hello to someone several times", NULL);
218         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "john")),
219                 "help", "specific help for john", NULL);
220         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")),
221                 "help", "the name of the person", NULL);
222         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "int")),
223                 "help", "an integer", NULL);
224         if (ec_node_or_add(cmdlist, cmd) < 0)
225                 goto fail;
226
227
228         cmd = EC_NODE_CMD(NULL, "good morning name [count]",
229                         EC_NODE_CMD("name", "bob|bobby|michael"),
230                         ec_node_int("count", 0, 10, 10));
231         if (cmd == NULL)
232                 goto fail;
233         ec_keyval_set(ec_node_attrs(cmd), "help",
234                 "say good morning to someone several times", NULL);
235         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "name")), "help",
236                 "the person to greet", NULL);
237         ec_keyval_set(ec_node_attrs(ec_node_find(cmd, "count")), "help",
238                 "how many times to greet", NULL);
239         if (ec_node_or_add(cmdlist, cmd) < 0)
240                 goto fail;
241
242
243         cmd = EC_NODE_CMD(NULL,
244                         "buy potatoes,carrots,pumpkins");
245         if (cmd == NULL)
246                 goto fail;
247         ec_keyval_set(ec_node_attrs(cmd), "help",
248                 "buy some vegetables", NULL);
249         if (ec_node_or_add(cmdlist, cmd) < 0)
250                 goto fail;
251
252
253         cmd = EC_NODE_CMD(NULL, "eat vegetables",
254                         ec_node_many("vegetables",
255                                 EC_NODE_OR(NULL,
256                                         ec_node_str(NULL, "potatoes"),
257                                         ec_node_once(NULL,
258                                                 ec_node_str(NULL, "carrots")),
259                                         ec_node_once(NULL,
260                                                 ec_node_str(NULL, "pumpkins"))),
261                         1, 0));
262         if (cmd == NULL)
263                 goto fail;
264         ec_keyval_set(ec_node_attrs(cmd), "help",
265                 "eat vegetables (take some more potatoes)", NULL);
266         if (ec_node_or_add(cmdlist, cmd) < 0)
267                 goto fail;
268
269
270         cmd = EC_NODE_SEQ(NULL,
271                 ec_node_str(NULL, "bye")
272         );
273         ec_keyval_set(ec_node_attrs(cmd), "help", "say bye", NULL);
274         if (ec_node_or_add(cmdlist, cmd) < 0)
275                 goto fail;
276
277
278         commands = ec_node_sh_lex(NULL, cmdlist);
279         if (commands == NULL)
280                 goto fail;
281
282         return 0;
283
284  fail:
285         fprintf(stderr, "cannot initialize nodes\n");
286         ec_node_free(cmdlist);
287         return -1;
288 }
289
290 int main(void)
291 {
292         struct ec_parsed *p;
293 //      const char *name;
294         char *line;
295
296
297         if (create_commands() < 0)
298                 return 1;
299
300         rl_bind_key('?', show_help);
301         rl_attempted_completion_function = my_attempted_completion;
302
303         while (1) {
304                 line = readline("> ");
305                 if (line == NULL)
306                         break;
307
308                 p = ec_node_parse(commands, line);
309                 ec_parsed_dump(stdout, p);
310                 add_history(line);
311                 ec_parsed_free(p);
312         }
313
314
315         ec_node_free(commands);
316         return 0;
317
318 }