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