]> git.proxmox.com Git - mirror_frr.git/blob - lib/grammar_sandbox.c
Merge pull request #2034 from vincentbernat/fix/rfc8365-auto-rt
[mirror_frr.git] / lib / grammar_sandbox.c
1 /*
2 * Testing shim and API examples for the new CLI backend.
3 *
4 * This unit defines a number of commands in the old engine that can
5 * be used to test and interact with the new engine.
6 * --
7 * Copyright (C) 2016 Cumulus Networks, Inc.
8 *
9 * This file is part of GNU Zebra.
10 *
11 * GNU Zebra is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by the
13 * Free Software Foundation; either version 2, or (at your option) any
14 * later version.
15 *
16 * GNU Zebra is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; see the file COPYING; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 */
25
26 #include "command.h"
27 #include "memory_vty.h"
28 #include "graph.h"
29 #include "linklist.h"
30 #include "command_match.h"
31
32 #define GRAMMAR_STR "CLI grammar sandbox\n"
33
34 DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc")
35
36 /** headers **/
37 void grammar_sandbox_init(void);
38 void pretty_print_graph(struct vty *vty, struct graph_node *, int, int,
39 struct graph_node **, size_t);
40 void init_cmdgraph(struct vty *, struct graph **);
41
42 /** shim interface commands **/
43 struct graph *nodegraph = NULL, *nodegraph_free = NULL;
44
45 #define check_nodegraph() \
46 do { \
47 if (!nodegraph) { \
48 vty_out(vty, "nodegraph not initialized\n"); \
49 return CMD_WARNING; \
50 } \
51 } while (0)
52
53 DEFUN (grammar_test,
54 grammar_test_cmd,
55 "grammar parse LINE...",
56 GRAMMAR_STR
57 "parse a command\n"
58 "command to pass to new parser\n")
59 {
60 check_nodegraph();
61
62 int idx_command = 2;
63 // make a string from tokenized command line
64 char *command = argv_concat(argv, argc, idx_command);
65
66 // create cmd_element for parser
67 struct cmd_element *cmd =
68 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
69 cmd->string = command;
70 cmd->doc =
71 "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n";
72 cmd->func = NULL;
73
74 // parse the command and install it into the command graph
75 struct graph *graph = graph_new();
76 struct cmd_token *token =
77 cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
78 graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
79
80 cmd_graph_parse(graph, cmd);
81 cmd_graph_merge(nodegraph, graph, +1);
82
83 return CMD_SUCCESS;
84 }
85
86 DEFUN (grammar_test_complete,
87 grammar_test_complete_cmd,
88 "grammar complete COMMAND...",
89 GRAMMAR_STR
90 "attempt to complete input on DFA\n"
91 "command to complete\n")
92 {
93 check_nodegraph();
94
95 int idx_command = 2;
96 char *cmdstr = argv_concat(argv, argc, idx_command);
97 if (!cmdstr)
98 return CMD_SUCCESS;
99
100 vector command = cmd_make_strvec(cmdstr);
101 if (!command) {
102 XFREE(MTYPE_TMP, cmdstr);
103 return CMD_SUCCESS;
104 }
105
106 // generate completions of user input
107 struct list *completions;
108 enum matcher_rv result =
109 command_complete(nodegraph, command, &completions);
110
111 // print completions or relevant error message
112 if (!MATCHER_ERROR(result)) {
113 vector comps = completions_to_vec(completions);
114 struct cmd_token *tkn;
115
116 // calculate length of longest tkn->text in completions
117 unsigned int width = 0, i = 0;
118 for (i = 0; i < vector_active(comps); i++) {
119 tkn = vector_slot(comps, i);
120 unsigned int len = strlen(tkn->text);
121 width = len > width ? len : width;
122 }
123
124 // print completions
125 for (i = 0; i < vector_active(comps); i++) {
126 tkn = vector_slot(comps, i);
127 vty_out(vty, " %-*s %s\n", width, tkn->text,
128 tkn->desc);
129 }
130
131 for (i = 0; i < vector_active(comps); i++)
132 cmd_token_del(
133 (struct cmd_token *)vector_slot(comps, i));
134 vector_free(comps);
135 } else
136 vty_out(vty, "%% No match\n");
137
138 // free resources
139 list_delete_and_null(&completions);
140 cmd_free_strvec(command);
141 XFREE(MTYPE_TMP, cmdstr);
142
143 return CMD_SUCCESS;
144 }
145
146 DEFUN (grammar_test_match,
147 grammar_test_match_cmd,
148 "grammar match COMMAND...",
149 GRAMMAR_STR
150 "attempt to match input on DFA\n"
151 "command to match\n")
152 {
153 check_nodegraph();
154
155 int idx_command = 2;
156 if (argv[2]->arg[0] == '#')
157 return CMD_SUCCESS;
158
159 char *cmdstr = argv_concat(argv, argc, idx_command);
160 if (!cmdstr)
161 return CMD_SUCCESS;
162 vector command = cmd_make_strvec(cmdstr);
163 if (!command) {
164 XFREE(MTYPE_TMP, cmdstr);
165 return CMD_SUCCESS;
166 }
167
168 struct list *argvv = NULL;
169 const struct cmd_element *element = NULL;
170 enum matcher_rv result =
171 command_match(nodegraph, command, &argvv, &element);
172
173 // print completions or relevant error message
174 if (element) {
175 vty_out(vty, "Matched: %s\n", element->string);
176 struct listnode *ln;
177 struct cmd_token *token;
178 for (ALL_LIST_ELEMENTS_RO(argvv, ln, token))
179 vty_out(vty, "%s -- %s\n", token->text, token->arg);
180
181 vty_out(vty, "func: %p\n", element->func);
182
183 list_delete_and_null(&argvv);
184 } else {
185 assert(MATCHER_ERROR(result));
186 switch (result) {
187 case MATCHER_NO_MATCH:
188 vty_out(vty, "%% Unknown command\n");
189 break;
190 case MATCHER_INCOMPLETE:
191 vty_out(vty, "%% Incomplete command\n");
192 break;
193 case MATCHER_AMBIGUOUS:
194 vty_out(vty, "%% Ambiguous command\n");
195 break;
196 default:
197 vty_out(vty, "%% Unknown error\n");
198 break;
199 }
200 }
201
202 // free resources
203 cmd_free_strvec(command);
204 XFREE(MTYPE_TMP, cmdstr);
205
206 return CMD_SUCCESS;
207 }
208
209 /**
210 * Testing shim to test docstrings
211 */
212 DEFUN (grammar_test_doc,
213 grammar_test_doc_cmd,
214 "grammar test docstring",
215 GRAMMAR_STR
216 "Test function for docstring\n"
217 "Command end\n")
218 {
219 check_nodegraph();
220
221 // create cmd_element with docstring
222 struct cmd_element *cmd =
223 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
224 cmd->string = XSTRDUP(
225 MTYPE_CMD_TOKENS,
226 "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
227 cmd->doc = XSTRDUP(MTYPE_CMD_TOKENS,
228 "Test stuff\n"
229 "docstring thing\n"
230 "first example\n"
231 "second example\n"
232 "follow\n"
233 "random range\n"
234 "end thingy\n"
235 "variable\n"
236 "optional variable\n"
237 "optional set\n"
238 "optional lol\n"
239 "vararg!\n");
240 cmd->func = NULL;
241
242 // parse element
243 cmd_graph_parse(nodegraph, cmd);
244
245 return CMD_SUCCESS;
246 }
247
248 /**
249 * Debugging command to print command graph
250 */
251 DEFUN (grammar_test_show,
252 grammar_test_show_cmd,
253 "grammar show [doc]",
254 GRAMMAR_STR
255 "print current accumulated DFA\n"
256 "include docstrings\n")
257 {
258 check_nodegraph();
259
260 struct graph_node *stack[CMD_ARGC_MAX];
261 pretty_print_graph(vty, vector_slot(nodegraph->nodes, 0), 0, argc >= 3,
262 stack, 0);
263 return CMD_SUCCESS;
264 }
265
266 DEFUN (grammar_test_dot,
267 grammar_test_dot_cmd,
268 "grammar dotfile OUTNAME",
269 GRAMMAR_STR
270 "print current graph for dot\n"
271 ".dot filename\n")
272 {
273 check_nodegraph();
274 FILE *ofd = fopen(argv[2]->arg, "w");
275
276 if (!ofd) {
277 vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno));
278 return CMD_SUCCESS;
279 }
280
281 char *dot = cmd_graph_dump_dot(nodegraph);
282
283 fprintf(ofd, "%s", dot);
284 fclose(ofd);
285 XFREE(MTYPE_TMP, dot);
286
287 return CMD_SUCCESS;
288 }
289
290 struct cmd_permute_item {
291 char *cmd;
292 struct cmd_element *el;
293 };
294
295 static void cmd_permute_free(void *arg)
296 {
297 struct cmd_permute_item *i = arg;
298 XFREE(MTYPE_TMP, i->cmd);
299 XFREE(MTYPE_TMP, i);
300 }
301
302 static int cmd_permute_cmp(void *a, void *b)
303 {
304 struct cmd_permute_item *aa = a, *bb = b;
305 return strcmp(aa->cmd, bb->cmd);
306 }
307
308 static void cmd_graph_permute(struct list *out, struct graph_node **stack,
309 size_t stackpos, char *cmd)
310 {
311 struct graph_node *gn = stack[stackpos];
312 struct cmd_token *tok = gn->data;
313 char *appendp = cmd + strlen(cmd);
314 size_t i, j;
315
316 if (tok->type < SPECIAL_TKN) {
317 sprintf(appendp, "%s ", tok->text);
318 appendp += strlen(appendp);
319 } else if (tok->type == END_TKN) {
320 struct cmd_permute_item *i = XMALLOC(MTYPE_TMP, sizeof(*i));
321 i->el = ((struct graph_node *)vector_slot(gn->to, 0))->data;
322 i->cmd = XSTRDUP(MTYPE_TMP, cmd);
323 i->cmd[strlen(cmd) - 1] = '\0';
324 listnode_add_sort(out, i);
325 return;
326 }
327
328 if (++stackpos == CMD_ARGC_MAX)
329 return;
330
331 for (i = 0; i < vector_active(gn->to); i++) {
332 struct graph_node *gnext = vector_slot(gn->to, i);
333 for (j = 0; j < stackpos; j++)
334 if (stack[j] == gnext)
335 break;
336 if (j != stackpos)
337 continue;
338
339 stack[stackpos] = gnext;
340 *appendp = '\0';
341 cmd_graph_permute(out, stack, stackpos, cmd);
342 }
343 }
344
345 static struct list *cmd_graph_permutations(struct graph *graph)
346 {
347 char accumulate[2048] = "";
348 struct graph_node *stack[CMD_ARGC_MAX];
349
350 struct list *rv = list_new();
351 rv->cmp = cmd_permute_cmp;
352 rv->del = cmd_permute_free;
353 stack[0] = vector_slot(graph->nodes, 0);
354 cmd_graph_permute(rv, stack, 0, accumulate);
355 return rv;
356 }
357
358 extern vector cmdvec;
359
360 DEFUN (grammar_findambig,
361 grammar_findambig_cmd,
362 "grammar find-ambiguous [{printall|nodescan}]",
363 GRAMMAR_STR
364 "Find ambiguous commands\n"
365 "Print all permutations\n"
366 "Scan all nodes\n")
367 {
368 struct list *commands;
369 struct cmd_permute_item *prev = NULL, *cur = NULL;
370 struct listnode *ln;
371 int i, printall, scan, scannode = 0;
372 int ambig = 0;
373
374 i = 0;
375 printall = argv_find(argv, argc, "printall", &i);
376 i = 0;
377 scan = argv_find(argv, argc, "nodescan", &i);
378
379 if (scan && nodegraph_free) {
380 graph_delete_graph(nodegraph_free);
381 nodegraph_free = NULL;
382 }
383
384 if (!scan && !nodegraph) {
385 vty_out(vty, "nodegraph uninitialized\r\n");
386 return CMD_WARNING_CONFIG_FAILED;
387 }
388
389 do {
390 if (scan) {
391 struct cmd_node *cnode =
392 vector_slot(cmdvec, scannode++);
393 if (!cnode)
394 continue;
395 nodegraph = cnode->cmdgraph;
396 if (!nodegraph)
397 continue;
398 vty_out(vty, "scanning node %d (%s)\n", scannode - 1,
399 node_names[scannode - 1]);
400 }
401
402 commands = cmd_graph_permutations(nodegraph);
403 prev = NULL;
404 for (ALL_LIST_ELEMENTS_RO(commands, ln, cur)) {
405 int same = prev && !strcmp(prev->cmd, cur->cmd);
406 if (printall && !same)
407 vty_out(vty, "'%s' [%x]\n", cur->cmd,
408 cur->el->daemon);
409 if (same) {
410 vty_out(vty, "'%s' AMBIGUOUS:\n", cur->cmd);
411 vty_out(vty, " %s\n '%s'\n", prev->el->name,
412 prev->el->string);
413 vty_out(vty, " %s\n '%s'\n", cur->el->name,
414 cur->el->string);
415 vty_out(vty, "\n");
416 ambig++;
417 }
418 prev = cur;
419 }
420 list_delete_and_null(&commands);
421
422 vty_out(vty, "\n");
423 } while (scan && scannode < LINK_PARAMS_NODE);
424
425 vty_out(vty, "%d ambiguous commands found.\n", ambig);
426
427 if (scan)
428 nodegraph = NULL;
429 return ambig == 0 ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED;
430 }
431
432 DEFUN (grammar_init_graph,
433 grammar_init_graph_cmd,
434 "grammar init",
435 GRAMMAR_STR
436 "(re)initialize graph\n")
437 {
438 if (nodegraph_free)
439 graph_delete_graph(nodegraph_free);
440 nodegraph_free = NULL;
441
442 init_cmdgraph(vty, &nodegraph);
443 return CMD_SUCCESS;
444 }
445
446 DEFUN (grammar_access,
447 grammar_access_cmd,
448 "grammar access (0-65535)",
449 GRAMMAR_STR
450 "access node graph\n"
451 "node number\n")
452 {
453 if (nodegraph_free)
454 graph_delete_graph(nodegraph_free);
455 nodegraph_free = NULL;
456
457 struct cmd_node *cnode;
458
459 cnode = vector_slot(cmdvec, atoi(argv[2]->arg));
460 if (!cnode) {
461 vty_out(vty, "%% no such node\n");
462 return CMD_WARNING_CONFIG_FAILED;
463 }
464
465 vty_out(vty, "node %d\n", (int)cnode->node);
466 nodegraph = cnode->cmdgraph;
467 return CMD_SUCCESS;
468 }
469
470 /* this is called in vtysh.c to set up the testing shim */
471 void grammar_sandbox_init(void)
472 {
473 // install all enable elements
474 install_element(ENABLE_NODE, &grammar_test_cmd);
475 install_element(ENABLE_NODE, &grammar_test_show_cmd);
476 install_element(ENABLE_NODE, &grammar_test_dot_cmd);
477 install_element(ENABLE_NODE, &grammar_test_match_cmd);
478 install_element(ENABLE_NODE, &grammar_test_complete_cmd);
479 install_element(ENABLE_NODE, &grammar_test_doc_cmd);
480 install_element(ENABLE_NODE, &grammar_findambig_cmd);
481 install_element(ENABLE_NODE, &grammar_init_graph_cmd);
482 install_element(ENABLE_NODE, &grammar_access_cmd);
483 }
484
485 /**
486 * Pretty-prints a graph, assuming it is a tree.
487 *
488 * @param start the node to take as the root
489 * @param level indent level for recursive calls, always pass 0
490 */
491 void pretty_print_graph(struct vty *vty, struct graph_node *start, int level,
492 int desc, struct graph_node **stack, size_t stackpos)
493 {
494 // print this node
495 char tokennum[32];
496 struct cmd_token *tok = start->data;
497
498 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
499 vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL));
500 if (tok->text)
501 vty_out(vty, ":\"%s\"", tok->text);
502 if (tok->varname)
503 vty_out(vty, " => %s", tok->varname);
504 if (desc)
505 vty_out(vty, " ?'%s'", tok->desc);
506 vty_out(vty, " ");
507
508 if (stackpos == CMD_ARGC_MAX) {
509 vty_out(vty, " -aborting! (depth limit)\n");
510 return;
511 }
512 stack[stackpos++] = start;
513
514 int numto = desc ? 2 : vector_active(start->to);
515 if (numto) {
516 if (numto > 1)
517 vty_out(vty, "\n");
518 for (unsigned int i = 0; i < vector_active(start->to); i++) {
519 struct graph_node *adj = vector_slot(start->to, i);
520 // if we're listing multiple children, indent!
521 if (numto > 1)
522 for (int j = 0; j < level + 1; j++)
523 vty_out(vty, " ");
524 // if this node is a vararg, just print *
525 if (adj == start)
526 vty_out(vty, "*");
527 else if (((struct cmd_token *)adj->data)->type
528 == END_TKN)
529 vty_out(vty, "--END\n");
530 else {
531 size_t k;
532 for (k = 0; k < stackpos; k++)
533 if (stack[k] == adj) {
534 vty_out(vty, "<<loop@%zu \n",
535 k);
536 break;
537 }
538 if (k == stackpos)
539 pretty_print_graph(
540 vty, adj,
541 numto > 1 ? level + 1 : level,
542 desc, stack, stackpos);
543 }
544 }
545 } else
546 vty_out(vty, "\n");
547 }
548
549 /** stuff that should go in command.c + command.h */
550 void init_cmdgraph(struct vty *vty, struct graph **graph)
551 {
552 // initialize graph, add start noe
553 *graph = graph_new();
554 nodegraph_free = *graph;
555 struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
556 graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del);
557 if (vty)
558 vty_out(vty, "initialized graph\n");
559 }