85958d0e4a9e9917b83c607e5e3f0574f2f13e7b
[libcmdline.git] / src / lib / cmdline_parse.c
1 /*
2  * Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
3  * All rights reserved.
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 #include <stdio.h>
29 #include <string.h>
30 #include <inttypes.h>
31 #include <ctype.h>
32
33 #include <netinet/in.h>
34
35 #include "cmdline_parse.h"
36 #include "cmdline.h"
37
38 //#define CMDLINE_DEBUG
39 //#define debug_printf printf
40 #define debug_printf(args...) do {} while(0)
41
42 /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our
43  * own. */
44 static int
45 isblank2(char c)
46 {
47         if (c == ' ' || 
48             c == '\t' )
49                 return 1;
50         return 0;
51 }
52
53 static int
54 isendofline(char c)
55 {
56         if (c == '\n' || 
57             c == '\r' )
58                 return 1;
59         return 0;
60 }
61
62 static int
63 iscomment(char c)
64 {
65         if (c == '#')
66                 return 1;
67         return 0;
68 }
69
70 int
71 cmdline_isendoftoken(char c)
72 {
73         if (!c || iscomment(c) || isblank2(c) || isendofline(c))
74                 return 1;
75         return 0;
76 }
77
78 static unsigned int
79 nb_common_chars(const char * s1, const char * s2)
80 {
81         unsigned int i=0;
82
83         while (*s1==*s2 && *s1 && *s2) {
84                 s1++;
85                 s2++;
86                 i++;
87         }
88         return i;
89 }
90
91 /** 
92  * try to match the buffer with an instruction (only the first
93  * nb_match_token tokens if != 0). Return 0 if we match all the
94  * tokens, else the number of matched tokens, else -1.
95  */
96 static int
97 match_inst(cmdline_parse_inst_t *inst, const char *buf, unsigned int nb_match_token, 
98            void * result_buf)
99 {
100         unsigned int token_num=0;
101         cmdline_parse_token_hdr_t * token_p;
102         unsigned int i=0;
103         int n = 0;
104         struct cmdline_token_hdr token_hdr;
105
106         token_p = inst->tokens[token_num];
107         if (token_p)
108                 memcpy(&token_hdr, token_p, sizeof(token_hdr));
109         
110         /* check if we match all tokens of inst */
111         while (token_p && (!nb_match_token || i<nb_match_token)) {
112                 debug_printf("TK\n");
113                 /* skip spaces */
114                 while (isblank2(*buf)) {
115                         buf++;
116                 }
117                 
118                 /* end of buf */
119                 if ( isendofline(*buf) || iscomment(*buf) )
120                         break;
121                 
122                 n = token_hdr.ops->parse(token_p, buf, (result_buf ? result_buf+token_hdr.offset : NULL));
123                 if ( n < 0 )
124                         break;
125                 debug_printf("TK parsed (len=%d)\n", n);
126                 i++;
127                 buf += n;
128                 
129                 token_num ++;
130                 token_p = inst->tokens[token_num];
131                 if (token_p)
132                         memcpy(&token_hdr, token_p, sizeof(token_hdr));
133         }
134         
135         /* does not match */
136         if (i==0)
137                 return -1;
138         
139         /* in case we want to match a specific num of token */
140         if (nb_match_token) {
141                 if (i == nb_match_token) {
142                         return 0;
143                 }
144                 return i;
145         }
146
147         /* we don't match all the tokens */
148         if (token_p) {
149                 return i;
150         }
151
152         /* are there are some tokens more */
153         while (isblank2(*buf)) {
154                 buf++;
155         }
156         
157         /* end of buf */
158         if ( isendofline(*buf) || iscomment(*buf) )
159                 return 0;
160
161         /* garbage after inst */
162         return i;
163 }
164
165
166 int
167 cmdline_parse(struct cmdline *cl, const char * buf)
168 {
169         unsigned int inst_num=0;
170         cmdline_parse_inst_t *inst;
171         const char *curbuf;
172         char result_buf[BUFSIZ]; /* XXX align, size zé in broblém */
173         void (*f)(void *, struct cmdline *, void *) = NULL;
174         void *data = NULL;
175         int comment = 0;
176         int linelen = 0;
177         int parse_it = 0;
178         int err = CMDLINE_PARSE_NOMATCH;
179         int tok;
180         cmdline_parse_ctx_t *ctx = cl->ctx;
181 #ifdef CMDLINE_DEBUG
182         char debug_buf[BUFSIZ];
183 #endif
184
185         /* 
186          * - look if the buffer contains at least one line
187          * - look if line contains only spaces or comments 
188          * - count line length
189          */
190         curbuf = buf;
191         while (! isendofline(*curbuf)) {
192                 if ( *curbuf == '\0' ) {
193                         debug_printf("Incomplete buf (len=%d)\n", linelen);
194                         return 0;
195                 }
196                 if ( iscomment(*curbuf) ) {
197                         comment = 1;
198                 }
199                 if ( ! isblank2(*curbuf) && ! comment) {
200                         parse_it = 1;
201                 }
202                 curbuf++;
203                 linelen++;
204         }
205
206         /* skip all endofline chars */
207         while (isendofline(buf[linelen])) {
208                 linelen++;
209         }
210
211         /* empty line */
212         if ( parse_it == 0 ) {
213                 debug_printf("Empty line (len=%d)\n", linelen);
214                 return linelen;
215         }
216
217 #ifdef CMDLINE_DEBUG
218         snprintf(debug_buf, (linelen>64 ? 64 : linelen), "%s", buf);
219         debug_printf("Parse line : len=%d, <%s>\n", linelen, debug_buf);
220 #endif
221
222         /* parse it !! */
223         inst = ctx[inst_num];
224         while (inst) {
225                 debug_printf("INST %d\n", inst_num);
226
227                 /* fully parsed */
228                 tok = match_inst(inst, buf, 0, result_buf);
229
230                 if (tok > 0) /* we matched at least one token */
231                         err = CMDLINE_PARSE_BAD_ARGS;
232
233                 else if (!tok) {
234                         debug_printf("INST fully parsed\n");
235                         /* skip spaces */
236                         while (isblank2(*curbuf)) {
237                                 curbuf++;
238                         }
239                         
240                         /* if end of buf -> there is no garbage after inst */
241                         if (isendofline(*curbuf) || iscomment(*curbuf)) {
242                                 if (!f) {
243                                         memcpy(&f, &inst->f, sizeof(f));
244                                         memcpy(&data, &inst->data, sizeof(data));
245                                 }
246                                 else {
247                                         /* more than 1 inst matches */
248                                         err = CMDLINE_PARSE_AMBIGUOUS;
249                                         f=NULL;
250                                         debug_printf("Ambiguous cmd\n");
251                                         break;
252                                 }
253                         }
254                 }
255                         
256                 inst_num ++;
257                 inst = ctx[inst_num];
258         }
259         
260         /* call func */
261         if (f) {
262                 f(result_buf, cl, data);
263         }
264
265         /* no match */
266         else {
267                 debug_printf("No match err=%d\n", err);
268                 return err;
269         }
270         
271         return linelen;
272 }
273
274 int 
275 cmdline_complete(struct cmdline *cl, const char *buf, int *state, 
276                  char *dst, unsigned int size)
277 {
278         const char *incomplete_token = buf;
279         unsigned int inst_num = 0;
280         cmdline_parse_inst_t *inst;
281         cmdline_parse_token_hdr_t *token_p;
282         struct cmdline_token_hdr token_hdr;
283         char tmpbuf[64], completion_buf[64];
284         unsigned int incomplete_token_len;
285         int completion_len = -1;
286         int nb_token = 0;
287         unsigned int i, n;
288         int l;
289         unsigned int nb_completable;
290         unsigned int nb_non_completable;
291         unsigned int local_state=0;
292         char *help_str;
293         cmdline_parse_ctx_t *ctx = cl->ctx;
294
295         debug_printf("%s called\n", __FUNCTION__);
296         /* count the number of complete token to parse */
297         for (i=0 ; buf[i] ; i++) {
298                 if (!isblank2(buf[i]) && isblank2(buf[i+1]))
299                         nb_token++;
300                 if (isblank2(buf[i]) && !isblank2(buf[i+1]))
301                         incomplete_token = buf+i+1;
302         }
303         incomplete_token_len = strlen(incomplete_token);
304
305         /* first call -> do a first pass */
306         if (*state <= 0) {
307                 debug_printf("try complete <%s>\n", buf);
308                 debug_printf("there is %d complete tokens, <%s> is incomplete\n", nb_token, incomplete_token);
309
310                 nb_completable = 0;
311                 nb_non_completable = 0;
312                 
313                 inst = ctx[inst_num];
314                 while (inst) {
315                         /* parse the first tokens of the inst */
316                         if (nb_token && match_inst(inst, buf, nb_token, NULL))
317                                 goto next;
318                         
319                         debug_printf("instruction match \n");
320                         token_p = inst->tokens[nb_token];
321                         if (token_p)
322                                 memcpy(&token_hdr, token_p, sizeof(token_hdr));
323
324                         /* non completable */
325                         if (!token_p || 
326                             !token_hdr.ops->complete_get_nb || 
327                             !token_hdr.ops->complete_get_elt || 
328                             (n = token_hdr.ops->complete_get_nb(token_p)) == 0) {
329                                 nb_non_completable++;
330                                 goto next;
331                         }
332
333                         debug_printf("%d choices for this token\n", n);
334                         for (i=0 ; i<n ; i++) {
335                                 if (token_hdr.ops->complete_get_elt(token_p, i, tmpbuf, sizeof(tmpbuf)) < 0)
336                                         continue;
337                                 strcat(tmpbuf, " "); /* we have at least room for one char */
338                                 debug_printf("   choice <%s>\n", tmpbuf);
339                                 /* does the completion match the beginning of the word ? */
340                                 if (!strncmp(incomplete_token, tmpbuf, incomplete_token_len)) {
341                                         if (completion_len == -1) {
342                                                 strcpy(completion_buf, tmpbuf+incomplete_token_len);
343                                                 completion_len = strlen(tmpbuf+incomplete_token_len);
344                                                 
345                                         }
346                                         else {
347                                                 completion_len = nb_common_chars(completion_buf, 
348                                                                                  tmpbuf+incomplete_token_len);
349                                                 completion_buf[completion_len] = 0;
350                                         }
351                                         nb_completable++;
352                                 }
353                         }               
354                 next:
355                         inst_num ++;
356                         inst = ctx[inst_num];
357                 }
358
359                 debug_printf("total choices %d for this completion\n", nb_completable);
360
361                 /* no possible completion */
362                 if (nb_completable == 0 && nb_non_completable == 0)
363                         return 0;
364                 
365                 /* if multichoice is not required */
366                 if (*state == 0 && incomplete_token_len > 0) {
367                         /* one or several choices starting with the
368                            same chars */
369                         if (completion_len > 0) { 
370                                 if (completion_len + 1 > size)
371                                         return 0;
372                                 
373                                 strcpy(dst, completion_buf);
374                                 return 2;
375                         }
376                 }
377         }
378
379         /* init state correctly */
380         if (*state == -1)
381                 *state = 0;
382
383         debug_printf("Multiple choice STATE=%d\n", *state);
384
385         inst_num = 0;
386         inst = ctx[inst_num];
387         while (inst) {
388                 /* we need to redo it */
389                 inst = ctx[inst_num];
390                 
391                 if (nb_token && match_inst(inst, buf, nb_token, NULL))
392                         goto next2;
393                 
394                 token_p = inst->tokens[nb_token];
395                 if (token_p)
396                         memcpy(&token_hdr, token_p, sizeof(token_hdr));
397
398                 /* one choice for this token */
399                 if (!token_p || 
400                     !token_hdr.ops->complete_get_nb || 
401                     !token_hdr.ops->complete_get_elt || 
402                     (n = token_hdr.ops->complete_get_nb(token_p)) == 0) {
403                         if (local_state < *state) {
404                                 local_state++;
405                                 goto next2;
406                         }
407                         (*state)++;
408                         if (token_p && token_hdr.ops->get_help) {
409                                 token_hdr.ops->get_help(token_p, tmpbuf, sizeof(tmpbuf));
410                                 help_str = inst->help_str;
411                                 if (help_str)
412                                         snprintf(dst, size, "[%s]: %s", tmpbuf, help_str);
413                                 else
414                                         snprintf(dst, size, "[%s]: No help", tmpbuf);
415                         }
416                         else {
417                                 snprintf(dst, size, "[RETURN]");
418                         }
419                         return 1;
420                 }
421
422                 /* several choices */
423                 for (i=0 ; i<n ; i++) {
424                         if (token_hdr.ops->complete_get_elt(token_p, i, tmpbuf, sizeof(tmpbuf)) < 0)
425                                 continue;
426                         strcat(tmpbuf, " "); /* we have at least room for one char */
427                         debug_printf("   choice <%s>\n", tmpbuf);
428                         /* does the completion match the beginning of the word ? */
429                         if (!strncmp(incomplete_token, tmpbuf, incomplete_token_len)) {
430                                 if (local_state < *state) {
431                                         local_state++;
432                                         continue;
433                                 }
434                                 (*state)++;
435                                 l=snprintf(dst, size, "%s", tmpbuf);
436                                 if (l>=0 && token_hdr.ops->get_help) {
437                                         token_hdr.ops->get_help(token_p, tmpbuf, sizeof(tmpbuf));
438                                         help_str = inst->help_str;
439                                         if (help_str)
440                                                 snprintf(dst+l, size-l, "[%s]: %s", tmpbuf, help_str);
441                                         else
442                                                 snprintf(dst+l, size-l, "[%s]: No help", tmpbuf);
443                                 }
444                                                               
445                                 return 1;
446                         }
447                 }
448         next2:
449                 inst_num ++;
450                 inst = ctx[inst_num];
451         }
452         return 0;
453 }
454