2 * Testing shim and API examples for the new CLI backend.
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.
7 * Copyright (C) 2016 Cumulus Networks, Inc.
9 * This file is part of GNU Zebra.
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
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.
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
27 #include "memory_vty.h"
30 #include "command_match.h"
32 #define GRAMMAR_STR "CLI grammar sandbox\n"
34 DEFINE_MTYPE_STATIC(LIB
, CMD_TOKENS
, "Command desc")
40 grammar_sandbox_init (void);
42 pretty_print_graph (struct vty
*vty
, struct graph_node
*, int, int, struct graph_node
**, size_t);
44 pretty_print_dot (FILE *ofd
, unsigned opts
, struct graph_node
*start
,
45 struct graph_node
**stack
, size_t stackpos
,
46 struct graph_node
**visited
, size_t *visitpos
);
48 init_cmdgraph (struct vty
*, struct graph
**);
50 /** shim interface commands **/
51 struct graph
*nodegraph
= NULL
, *nodegraph_free
= NULL
;
53 #define check_nodegraph() \
54 do { if (!nodegraph) { \
55 vty_out(vty, "nodegraph not initialized\n"); \
61 "grammar parse LINE...",
64 "command to pass to new parser\n")
69 // make a string from tokenized command line
70 char *command
= argv_concat (argv
, argc
, idx_command
);
72 // create cmd_element for parser
73 struct cmd_element
*cmd
= XCALLOC (MTYPE_CMD_TOKENS
, sizeof (struct cmd_element
));
74 cmd
->string
= command
;
75 cmd
->doc
= "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n";
78 // parse the command and install it into the command graph
79 struct graph
*graph
= graph_new();
80 struct cmd_token
*token
= cmd_token_new (START_TKN
, CMD_ATTR_NORMAL
, NULL
, NULL
);
81 graph_new_node (graph
, token
, (void (*)(void *)) &cmd_token_del
);
83 cmd_graph_parse (graph
, cmd
);
84 cmd_graph_merge (nodegraph
, graph
, +1);
89 DEFUN (grammar_test_complete
,
90 grammar_test_complete_cmd
,
91 "grammar complete COMMAND...",
93 "attempt to complete input on DFA\n"
94 "command to complete\n")
99 char *cmdstr
= argv_concat (argv
, argc
, idx_command
);
103 vector command
= cmd_make_strvec (cmdstr
);
106 XFREE (MTYPE_TMP
, cmdstr
);
110 // generate completions of user input
111 struct list
*completions
;
112 enum matcher_rv result
= command_complete (nodegraph
, command
, &completions
);
114 // print completions or relevant error message
115 if (!MATCHER_ERROR(result
))
117 vector comps
= completions_to_vec (completions
);
118 struct cmd_token
*tkn
;
120 // calculate length of longest tkn->text in completions
121 unsigned int width
= 0, i
= 0;
122 for (i
= 0; i
< vector_active (comps
); i
++) {
123 tkn
= vector_slot (comps
, i
);
124 unsigned int len
= strlen (tkn
->text
);
125 width
= len
> width
? len
: width
;
129 for (i
= 0; i
< vector_active (comps
); i
++) {
130 tkn
= vector_slot (comps
, i
);
131 vty_out (vty
, " %-*s %s\n", width
, tkn
->text
, tkn
->desc
);
134 for (i
= 0; i
< vector_active (comps
); i
++)
135 cmd_token_del ((struct cmd_token
*) vector_slot (comps
, i
));
139 vty_out (vty
, "%% No match\n");
142 list_delete (completions
);
143 cmd_free_strvec (command
);
144 XFREE (MTYPE_TMP
, cmdstr
);
149 DEFUN (grammar_test_match
,
150 grammar_test_match_cmd
,
151 "grammar match COMMAND...",
153 "attempt to match input on DFA\n"
154 "command to match\n")
159 if (argv
[2]->arg
[0] == '#')
162 char *cmdstr
= argv_concat(argv
, argc
, idx_command
);
165 vector command
= cmd_make_strvec (cmdstr
);
168 XFREE (MTYPE_TMP
, cmdstr
);
172 struct list
*argvv
= NULL
;
173 const struct cmd_element
*element
= NULL
;
174 enum matcher_rv result
= command_match (nodegraph
, command
, &argvv
, &element
);
176 // print completions or relevant error message
179 vty_out (vty
, "Matched: %s\n", element
->string
);
181 struct cmd_token
*token
;
182 for (ALL_LIST_ELEMENTS_RO(argvv
,ln
,token
))
183 vty_out (vty
, "%s -- %s\n", token
->text
, token
->arg
);
185 vty_out (vty
, "func: %p\n", element
->func
);
190 assert(MATCHER_ERROR(result
));
192 case MATCHER_NO_MATCH
:
193 vty_out (vty
, "%% Unknown command\n");
195 case MATCHER_INCOMPLETE
:
196 vty_out (vty
, "%% Incomplete command\n");
198 case MATCHER_AMBIGUOUS
:
199 vty_out (vty
, "%% Ambiguous command\n");
202 vty_out (vty
, "%% Unknown error\n");
208 cmd_free_strvec (command
);
209 XFREE (MTYPE_TMP
, cmdstr
);
215 * Testing shim to test docstrings
217 DEFUN (grammar_test_doc
,
218 grammar_test_doc_cmd
,
219 "grammar test docstring",
221 "Test function for docstring\n"
226 // create cmd_element with docstring
227 struct cmd_element
*cmd
= XCALLOC (MTYPE_CMD_TOKENS
, sizeof (struct cmd_element
));
228 cmd
->string
= XSTRDUP (MTYPE_CMD_TOKENS
, "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
229 cmd
->doc
= XSTRDUP (MTYPE_CMD_TOKENS
,
238 "optional variable\n"
245 cmd_graph_parse (nodegraph
, cmd
);
251 * Debugging command to print command graph
253 DEFUN (grammar_test_show
,
254 grammar_test_show_cmd
,
255 "grammar show [doc]",
257 "print current accumulated DFA\n"
258 "include docstrings\n")
262 struct graph_node
*stack
[MAXDEPTH
];
263 pretty_print_graph (vty
, vector_slot (nodegraph
->nodes
, 0), 0, argc
>= 3, stack
, 0);
267 DEFUN (grammar_test_dot
,
268 grammar_test_dot_cmd
,
269 "grammar dotfile OUTNAME",
271 "print current graph for dot\n"
276 struct graph_node
*stack
[MAXDEPTH
];
277 struct graph_node
*visited
[MAXDEPTH
*MAXDEPTH
];
280 FILE *ofd
= fopen(argv
[2]->arg
, "w");
282 vty_out(vty
, "%s: %s\r\n", argv
[2]->arg
, strerror(errno
));
286 fprintf(ofd
, "digraph {\n graph [ rankdir = LR ];\n node [ fontname = \"Fira Mono\", fontsize = 9 ];\n\n");
287 pretty_print_dot (ofd
, 0,
288 vector_slot (nodegraph
->nodes
, 0),
289 stack
, 0, visited
, &vpos
);
295 struct cmd_permute_item
298 struct cmd_element
*el
;
302 cmd_permute_free (void *arg
)
304 struct cmd_permute_item
*i
= arg
;
305 XFREE (MTYPE_TMP
, i
->cmd
);
306 XFREE (MTYPE_TMP
, i
);
310 cmd_permute_cmp (void *a
, void *b
)
312 struct cmd_permute_item
*aa
= a
, *bb
= b
;
313 return strcmp (aa
->cmd
, bb
->cmd
);
317 cmd_graph_permute (struct list
*out
, struct graph_node
**stack
,
318 size_t stackpos
, char *cmd
)
320 struct graph_node
*gn
= stack
[stackpos
];
321 struct cmd_token
*tok
= gn
->data
;
322 char *appendp
= cmd
+ strlen(cmd
);
325 if (tok
->type
< SPECIAL_TKN
)
327 sprintf (appendp
, "%s ", tok
->text
);
328 appendp
+= strlen (appendp
);
330 else if (tok
->type
== END_TKN
)
332 struct cmd_permute_item
*i
= XMALLOC (MTYPE_TMP
, sizeof (*i
));
333 i
->el
= ((struct graph_node
*)vector_slot (gn
->to
, 0))->data
;
334 i
->cmd
= XSTRDUP (MTYPE_TMP
, cmd
);
335 i
->cmd
[strlen(cmd
) - 1] = '\0';
336 listnode_add_sort (out
, i
);
340 if (++stackpos
== MAXDEPTH
)
343 for (i
= 0; i
< vector_active (gn
->to
); i
++)
345 struct graph_node
*gnext
= vector_slot (gn
->to
, i
);
346 for (j
= 0; j
< stackpos
; j
++)
347 if (stack
[j
] == gnext
)
352 stack
[stackpos
] = gnext
;
354 cmd_graph_permute (out
, stack
, stackpos
, cmd
);
359 cmd_graph_permutations (struct graph
*graph
)
361 char accumulate
[2048] = "";
362 struct graph_node
*stack
[MAXDEPTH
];
364 struct list
*rv
= list_new ();
365 rv
->cmp
= cmd_permute_cmp
;
366 rv
->del
= cmd_permute_free
;
367 stack
[0] = vector_slot (graph
->nodes
, 0);
368 cmd_graph_permute (rv
, stack
, 0, accumulate
);
372 extern vector cmdvec
;
374 DEFUN (grammar_findambig
,
375 grammar_findambig_cmd
,
376 "grammar find-ambiguous [{printall|nodescan}]",
378 "Find ambiguous commands\n"
379 "Print all permutations\n"
382 struct list
*commands
;
383 struct cmd_permute_item
*prev
= NULL
, *cur
= NULL
;
385 int i
, printall
, scan
, scannode
= 0;
389 printall
= argv_find (argv
, argc
, "printall", &i
);
391 scan
= argv_find (argv
, argc
, "nodescan", &i
);
393 if (scan
&& nodegraph_free
)
395 graph_delete_graph (nodegraph_free
);
396 nodegraph_free
= NULL
;
399 if (!scan
&& !nodegraph
)
401 vty_out(vty
, "nodegraph uninitialized\r\n");
402 return CMD_WARNING_CONFIG_FAILED
;
408 struct cmd_node
*cnode
= vector_slot (cmdvec
, scannode
++);
411 nodegraph
= cnode
->cmdgraph
;
414 vty_out (vty
, "scanning node %d\n", scannode
- 1);
417 commands
= cmd_graph_permutations (nodegraph
);
419 for (ALL_LIST_ELEMENTS_RO (commands
, ln
, cur
))
421 int same
= prev
&& !strcmp (prev
->cmd
, cur
->cmd
);
422 if (printall
&& !same
)
423 vty_out (vty
, "'%s' [%x]\n", cur
->cmd
, cur
->el
->daemon
);
426 vty_out (vty
, "'%s' AMBIGUOUS:\n", cur
->cmd
);
427 vty_out (vty
, " %s\n '%s'\n", prev
->el
->name
,
429 vty_out (vty
, " %s\n '%s'\n", cur
->el
->name
,
436 list_delete (commands
);
439 } while (scan
&& scannode
< LINK_PARAMS_NODE
);
441 vty_out (vty
, "%d ambiguous commands found.\n", ambig
);
445 return ambig
== 0 ? CMD_SUCCESS
: CMD_WARNING_CONFIG_FAILED
;
448 DEFUN (grammar_init_graph
,
449 grammar_init_graph_cmd
,
452 "(re)initialize graph\n")
455 graph_delete_graph (nodegraph_free
);
456 nodegraph_free
= NULL
;
458 init_cmdgraph (vty
, &nodegraph
);
462 DEFUN (grammar_access
,
464 "grammar access (0-65535)",
466 "access node graph\n"
470 graph_delete_graph (nodegraph_free
);
471 nodegraph_free
= NULL
;
473 struct cmd_node
*cnode
;
475 cnode
= vector_slot (cmdvec
, atoi (argv
[2]->arg
));
478 vty_out (vty
, "%% no such node\n");
479 return CMD_WARNING_CONFIG_FAILED
;
482 vty_out (vty
, "node %d\n", (int)cnode
->node
);
483 nodegraph
= cnode
->cmdgraph
;
487 /* this is called in vtysh.c to set up the testing shim */
488 void grammar_sandbox_init(void) {
489 // install all enable elements
490 install_element (ENABLE_NODE
, &grammar_test_cmd
);
491 install_element (ENABLE_NODE
, &grammar_test_show_cmd
);
492 install_element (ENABLE_NODE
, &grammar_test_dot_cmd
);
493 install_element (ENABLE_NODE
, &grammar_test_match_cmd
);
494 install_element (ENABLE_NODE
, &grammar_test_complete_cmd
);
495 install_element (ENABLE_NODE
, &grammar_test_doc_cmd
);
496 install_element (ENABLE_NODE
, &grammar_findambig_cmd
);
497 install_element (ENABLE_NODE
, &grammar_init_graph_cmd
);
498 install_element (ENABLE_NODE
, &grammar_access_cmd
);
501 #define item(x) { x, #x }
502 struct message tokennames
[] = {
503 item(WORD_TKN
), // words
504 item(VARIABLE_TKN
), // almost anything
505 item(RANGE_TKN
), // integer range
506 item(IPV4_TKN
), // IPV4 addresses
507 item(IPV4_PREFIX_TKN
), // IPV4 network prefixes
508 item(IPV6_TKN
), // IPV6 prefixes
509 item(IPV6_PREFIX_TKN
), // IPV6 network prefixes
514 item(START_TKN
), // first token in line
515 item(END_TKN
), // last token in line
520 * Pretty-prints a graph, assuming it is a tree.
522 * @param start the node to take as the root
523 * @param level indent level for recursive calls, always pass 0
526 pretty_print_graph (struct vty
*vty
, struct graph_node
*start
, int level
,
527 int desc
, struct graph_node
**stack
, size_t stackpos
)
531 struct cmd_token
*tok
= start
->data
;
533 snprintf(tokennum
, sizeof(tokennum
), "%d?", tok
->type
);
534 vty_out(vty
, "%s", lookup_msg(tokennames
, tok
->type
, NULL
));
536 vty_out(vty
, ":\"%s\"", tok
->text
);
538 vty_out(vty
, " => %s", tok
->varname
);
540 vty_out(vty
, " ?'%s'", tok
->desc
);
543 if (stackpos
== MAXDEPTH
)
545 vty_out (vty
, " -aborting! (depth limit)\n");
548 stack
[stackpos
++] = start
;
550 int numto
= desc
? 2 : vector_active (start
->to
);
555 for (unsigned int i
= 0; i
< vector_active (start
->to
); i
++)
557 struct graph_node
*adj
= vector_slot (start
->to
, i
);
558 // if we're listing multiple children, indent!
560 for (int j
= 0; j
< level
+1; j
++)
562 // if this node is a vararg, just print *
565 else if (((struct cmd_token
*)adj
->data
)->type
== END_TKN
)
566 vty_out (vty
, "--END\n");
569 for (k
= 0; k
< stackpos
; k
++)
570 if (stack
[k
] == adj
) {
571 vty_out (vty
, "<<loop@%zu \n", k
);
575 pretty_print_graph (vty
, adj
, numto
> 1 ? level
+1 : level
, desc
, stack
, stackpos
);
584 pretty_print_dot (FILE *ofd
, unsigned opts
, struct graph_node
*start
,
585 struct graph_node
**stack
, size_t stackpos
,
586 struct graph_node
**visited
, size_t *visitpos
)
590 struct cmd_token
*tok
= start
->data
;
593 for (size_t i
= 0; i
< (*visitpos
); i
++)
594 if (visited
[i
] == start
)
596 visited
[(*visitpos
)++] = start
;
597 if ((*visitpos
) == MAXDEPTH
*MAXDEPTH
)
600 snprintf(tokennum
, sizeof(tokennum
), "%d?", tok
->type
);
601 fprintf(ofd
, " n%p [ shape=box, label=<", start
);
603 fprintf(ofd
, "<b>%s</b>", lookup_msg(tokennames
, tok
->type
, NULL
));
604 if (tok
->attr
== CMD_ATTR_DEPRECATED
)
605 fprintf(ofd
, " (d)");
606 else if (tok
->attr
== CMD_ATTR_HIDDEN
)
607 fprintf(ofd
, " (h)");
609 if (tok
->type
== WORD_TKN
)
610 fprintf(ofd
, "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"", tok
->text
);
612 fprintf(ofd
, "<br/>%s", tok
->text
);
615 fprintf(ofd, " ?'%s'", tok->desc); */
617 case START_TKN
: color
= "#ccffcc"; break;
618 case FORK_TKN
: color
= "#aaddff"; break;
619 case JOIN_TKN
: color
= "#ddaaff"; break;
620 case WORD_TKN
: color
= "#ffffff"; break;
621 default: color
= "#ffffff"; break;
623 fprintf(ofd
, ">, style = filled, fillcolor = \"%s\" ];\n", color
);
625 if (stackpos
== MAXDEPTH
)
627 stack
[stackpos
++] = start
;
629 for (unsigned int i
= 0; i
< vector_active (start
->to
); i
++)
631 struct graph_node
*adj
= vector_slot (start
->to
, i
);
632 // if this node is a vararg, just print *
634 fprintf(ofd
, " n%p -> n%p;\n", start
, start
);
635 } else if (((struct cmd_token
*)adj
->data
)->type
== END_TKN
) {
636 //struct cmd_token *et = adj->data;
637 fprintf(ofd
, " n%p -> end%p;\n", start
, adj
);
638 fprintf(ofd
, " end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n", adj
);
640 fprintf(ofd
, " n%p -> n%p;\n", start
, adj
);
642 for (k
= 0; k
< stackpos
; k
++)
646 pretty_print_dot (ofd
, opts
, adj
, stack
, stackpos
, visited
, visitpos
);
653 /** stuff that should go in command.c + command.h */
655 init_cmdgraph (struct vty
*vty
, struct graph
**graph
)
657 // initialize graph, add start noe
658 *graph
= graph_new ();
659 nodegraph_free
= *graph
;
660 struct cmd_token
*token
= cmd_token_new (START_TKN
, 0, NULL
, NULL
);
661 graph_new_node (*graph
, token
, (void (*)(void *)) &cmd_token_del
);
663 vty_out (vty
, "initialized graph\n");