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
22 * along with GNU Zebra; see the file COPYING. If not, write to the Free
23 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
28 #include "memory_vty.h"
31 #include "command_match.h"
33 #define GRAMMAR_STR "CLI grammar sandbox\n"
35 DEFINE_MTYPE_STATIC(LIB
, CMD_TOKENS
, "Command desc")
40 void grammar_sandbox_init(void);
41 void pretty_print_graph(struct vty
*vty
, struct graph_node
*, int, int,
42 struct graph_node
**, size_t);
43 static void pretty_print_dot(FILE *ofd
, unsigned opts
, struct graph_node
*start
,
44 struct graph_node
**stack
, size_t stackpos
,
45 struct graph_node
**visited
, size_t *visitpos
);
46 void init_cmdgraph(struct vty
*, struct graph
**);
48 /** shim interface commands **/
49 struct graph
*nodegraph
= NULL
, *nodegraph_free
= NULL
;
53 "grammar parse LINE...",
56 "command to pass to new parser\n")
59 // make a string from tokenized command line
60 char *command
= argv_concat(argv
, argc
, idx_command
);
62 // create cmd_element for parser
63 struct cmd_element
*cmd
=
64 XCALLOC(MTYPE_CMD_TOKENS
, sizeof(struct cmd_element
));
65 cmd
->string
= command
;
67 "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n";
70 // parse the command and install it into the command graph
71 struct graph
*graph
= graph_new();
72 struct cmd_token
*token
=
73 new_cmd_token(START_TKN
, CMD_ATTR_NORMAL
, NULL
, NULL
);
74 graph_new_node(graph
, token
, (void (*)(void *)) & del_cmd_token
);
76 command_parse_format(graph
, cmd
);
77 cmd_merge_graphs(nodegraph
, graph
, +1);
82 DEFUN (grammar_test_complete
,
83 grammar_test_complete_cmd
,
84 "grammar complete COMMAND...",
86 "attempt to complete input on DFA\n"
87 "command to complete\n")
90 char *cmdstr
= argv_concat(argv
, argc
, idx_command
);
94 vector command
= cmd_make_strvec(cmdstr
);
96 XFREE(MTYPE_TMP
, cmdstr
);
100 // generate completions of user input
101 struct list
*completions
;
102 enum matcher_rv result
=
103 command_complete(nodegraph
, command
, &completions
);
105 // print completions or relevant error message
106 if (!MATCHER_ERROR(result
)) {
107 vector comps
= completions_to_vec(completions
);
108 struct cmd_token
*tkn
;
110 // calculate length of longest tkn->text in completions
111 unsigned int width
= 0, i
= 0;
112 for (i
= 0; i
< vector_active(comps
); i
++) {
113 tkn
= vector_slot(comps
, i
);
114 unsigned int len
= strlen(tkn
->text
);
115 width
= len
> width
? len
: width
;
119 for (i
= 0; i
< vector_active(comps
); i
++) {
120 tkn
= vector_slot(comps
, i
);
121 vty_out(vty
, " %-*s %s%s", width
, tkn
->text
,
122 tkn
->desc
, VTY_NEWLINE
);
125 for (i
= 0; i
< vector_active(comps
); i
++)
127 (struct cmd_token
*)vector_slot(comps
, i
));
130 vty_out(vty
, "%% No match%s", VTY_NEWLINE
);
133 list_delete(completions
);
134 cmd_free_strvec(command
);
135 XFREE(MTYPE_TMP
, cmdstr
);
140 DEFUN (grammar_test_match
,
141 grammar_test_match_cmd
,
142 "grammar match COMMAND...",
144 "attempt to match input on DFA\n"
145 "command to match\n")
148 if (argv
[2]->arg
[0] == '#')
151 char *cmdstr
= argv_concat(argv
, argc
, idx_command
);
154 vector command
= cmd_make_strvec(cmdstr
);
156 XFREE(MTYPE_TMP
, cmdstr
);
160 struct list
*argvv
= NULL
;
161 const struct cmd_element
*element
= NULL
;
162 enum matcher_rv result
=
163 command_match(nodegraph
, command
, &argvv
, &element
);
165 // print completions or relevant error message
167 vty_out(vty
, "Matched: %s%s", element
->string
, VTY_NEWLINE
);
169 struct cmd_token
*token
;
170 for (ALL_LIST_ELEMENTS_RO(argvv
, ln
, token
))
171 vty_out(vty
, "%s -- %s%s", token
->text
, token
->arg
,
174 vty_out(vty
, "func: %p%s", element
->func
, VTY_NEWLINE
);
178 assert(MATCHER_ERROR(result
));
180 case MATCHER_NO_MATCH
:
181 vty_out(vty
, "%% Unknown command%s", VTY_NEWLINE
);
183 case MATCHER_INCOMPLETE
:
184 vty_out(vty
, "%% Incomplete command%s", VTY_NEWLINE
);
186 case MATCHER_AMBIGUOUS
:
187 vty_out(vty
, "%% Ambiguous command%s", VTY_NEWLINE
);
190 vty_out(vty
, "%% Unknown error%s", VTY_NEWLINE
);
196 cmd_free_strvec(command
);
197 XFREE(MTYPE_TMP
, cmdstr
);
203 * Testing shim to test docstrings
205 DEFUN (grammar_test_doc
,
206 grammar_test_doc_cmd
,
207 "grammar test docstring",
209 "Test function for docstring\n"
212 // create cmd_element with docstring
213 struct cmd_element
*cmd
=
214 XCALLOC(MTYPE_CMD_TOKENS
, sizeof(struct cmd_element
));
215 cmd
->string
= XSTRDUP(
217 "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
218 cmd
->doc
= XSTRDUP(MTYPE_CMD_TOKENS
,
227 "optional variable\n"
234 command_parse_format(nodegraph
, cmd
);
240 * Debugging command to print command graph
242 DEFUN (grammar_test_show
,
243 grammar_test_show_cmd
,
244 "grammar show [doc]",
246 "print current accumulated DFA\n"
247 "include docstrings\n")
249 struct graph_node
*stack
[MAXDEPTH
];
252 vty_out(vty
, "nodegraph uninitialized\r\n");
254 pretty_print_graph(vty
, vector_slot(nodegraph
->nodes
, 0), 0,
255 argc
>= 3, stack
, 0);
259 DEFUN (grammar_test_dot
,
260 grammar_test_dot_cmd
,
261 "grammar dotfile OUTNAME",
263 "print current graph for dot\n"
266 struct graph_node
*stack
[MAXDEPTH
];
267 struct graph_node
*visited
[MAXDEPTH
* MAXDEPTH
];
271 vty_out(vty
, "nodegraph uninitialized\r\n");
274 FILE *ofd
= fopen(argv
[2]->arg
, "w");
276 vty_out(vty
, "%s: %s\r\n", argv
[2]->arg
, strerror(errno
));
281 "digraph {\n graph [ rankdir = LR ];\n node [ fontname = \"Fira Mono\", fontsize = 9 ];\n\n");
282 pretty_print_dot(ofd
, 0, vector_slot(nodegraph
->nodes
, 0), stack
, 0,
289 struct cmd_permute_item
{
291 struct cmd_element
*el
;
294 static void cmd_permute_free(void *arg
)
296 struct cmd_permute_item
*i
= arg
;
297 XFREE(MTYPE_TMP
, i
->cmd
);
301 static int cmd_permute_cmp(void *a
, void *b
)
303 struct cmd_permute_item
*aa
= a
, *bb
= b
;
304 return strcmp(aa
->cmd
, bb
->cmd
);
307 static void cmd_graph_permute(struct list
*out
, struct graph_node
**stack
,
308 size_t stackpos
, char *cmd
)
310 struct graph_node
*gn
= stack
[stackpos
];
311 struct cmd_token
*tok
= gn
->data
;
312 char *appendp
= cmd
+ strlen(cmd
);
315 if (tok
->type
< SPECIAL_TKN
) {
316 sprintf(appendp
, "%s ", tok
->text
);
317 appendp
+= strlen(appendp
);
318 } else if (tok
->type
== END_TKN
) {
319 struct cmd_permute_item
*i
= XMALLOC(MTYPE_TMP
, sizeof(*i
));
320 i
->el
= ((struct graph_node
*)vector_slot(gn
->to
, 0))->data
;
321 i
->cmd
= XSTRDUP(MTYPE_TMP
, cmd
);
322 i
->cmd
[strlen(cmd
) - 1] = '\0';
323 listnode_add_sort(out
, i
);
327 if (++stackpos
== MAXDEPTH
)
330 for (i
= 0; i
< vector_active(gn
->to
); i
++) {
331 struct graph_node
*gnext
= vector_slot(gn
->to
, i
);
332 for (j
= 0; j
< stackpos
; j
++)
333 if (stack
[j
] == gnext
)
338 stack
[stackpos
] = gnext
;
340 cmd_graph_permute(out
, stack
, stackpos
, cmd
);
344 static struct list
*cmd_graph_permutations(struct graph
*graph
)
346 char accumulate
[2048] = "";
347 struct graph_node
*stack
[MAXDEPTH
];
349 struct list
*rv
= list_new();
350 rv
->cmp
= cmd_permute_cmp
;
351 rv
->del
= cmd_permute_free
;
352 stack
[0] = vector_slot(graph
->nodes
, 0);
353 cmd_graph_permute(rv
, stack
, 0, accumulate
);
357 extern vector cmdvec
;
359 DEFUN (grammar_findambig
,
360 grammar_findambig_cmd
,
361 "grammar find-ambiguous [{printall|nodescan}]",
363 "Find ambiguous commands\n"
364 "Print all permutations\n"
367 struct list
*commands
;
368 struct cmd_permute_item
*prev
= NULL
, *cur
= NULL
;
370 int i
, printall
, scan
, scannode
= 0;
374 printall
= argv_find(argv
, argc
, "printall", &i
);
376 scan
= argv_find(argv
, argc
, "nodescan", &i
);
378 if (scan
&& nodegraph_free
) {
379 graph_delete_graph(nodegraph_free
);
380 nodegraph_free
= NULL
;
383 if (!scan
&& !nodegraph
) {
384 vty_out(vty
, "nodegraph uninitialized\r\n");
390 struct cmd_node
*cnode
=
391 vector_slot(cmdvec
, scannode
++);
394 nodegraph
= cnode
->cmdgraph
;
397 vty_out(vty
, "scanning node %d%s", scannode
- 1,
401 commands
= cmd_graph_permutations(nodegraph
);
403 for (ALL_LIST_ELEMENTS_RO(commands
, ln
, cur
)) {
404 int same
= prev
&& !strcmp(prev
->cmd
, cur
->cmd
);
405 if (printall
&& !same
)
406 vty_out(vty
, "'%s' [%x]%s", cur
->cmd
,
407 cur
->el
->daemon
, VTY_NEWLINE
);
409 vty_out(vty
, "'%s' AMBIGUOUS:%s", cur
->cmd
,
411 vty_out(vty
, " %s%s '%s'%s", prev
->el
->name
,
412 VTY_NEWLINE
, prev
->el
->string
,
414 vty_out(vty
, " %s%s '%s'%s", cur
->el
->name
,
415 VTY_NEWLINE
, cur
->el
->string
,
417 vty_out(vty
, "%s", VTY_NEWLINE
);
422 list_delete(commands
);
424 vty_out(vty
, "%s", VTY_NEWLINE
);
425 } while (scan
&& scannode
< LINK_PARAMS_NODE
);
427 vty_out(vty
, "%d ambiguous commands found.%s", ambig
, VTY_NEWLINE
);
431 return ambig
== 0 ? CMD_SUCCESS
: CMD_WARNING
;
434 DEFUN (grammar_init_graph
,
435 grammar_init_graph_cmd
,
438 "(re)initialize graph\n")
441 graph_delete_graph(nodegraph_free
);
442 nodegraph_free
= NULL
;
444 init_cmdgraph(vty
, &nodegraph
);
448 DEFUN (grammar_access
,
450 "grammar access (0-65535)",
452 "access node graph\n"
456 graph_delete_graph(nodegraph_free
);
457 nodegraph_free
= NULL
;
459 struct cmd_node
*cnode
;
461 cnode
= vector_slot(cmdvec
, atoi(argv
[2]->arg
));
463 vty_out(vty
, "%% no such node%s", VTY_NEWLINE
);
467 vty_out(vty
, "node %d%s", (int)cnode
->node
, VTY_NEWLINE
);
468 nodegraph
= cnode
->cmdgraph
;
472 /* this is called in vtysh.c to set up the testing shim */
473 void grammar_sandbox_init(void)
475 init_cmdgraph(NULL
, &nodegraph
);
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
);
489 #define item(x) { x, #x }
490 struct message tokennames
[] = {item(WORD_TKN
), // words
491 item(VARIABLE_TKN
), // almost anything
492 item(RANGE_TKN
), // integer range
493 item(IPV4_TKN
), // IPV4 addresses
494 item(IPV4_PREFIX_TKN
), // IPV4 network prefixes
495 item(IPV6_TKN
), // IPV6 prefixes
496 item(IPV6_PREFIX_TKN
), // IPV6 network prefixes
501 item(START_TKN
), // first token in line
502 item(END_TKN
), // last token in line
506 * Pretty-prints a graph, assuming it is a tree.
508 * @param start the node to take as the root
509 * @param level indent level for recursive calls, always pass 0
511 void pretty_print_graph(struct vty
*vty
, struct graph_node
*start
, int level
,
512 int desc
, struct graph_node
**stack
, size_t stackpos
)
516 struct cmd_token
*tok
= start
->data
;
518 snprintf(tokennum
, sizeof(tokennum
), "%d?", tok
->type
);
519 vty_out(vty
, "%s", lookup_msg(tokennames
, tok
->type
, NULL
));
521 vty_out(vty
, ":\"%s\"", tok
->text
);
523 vty_out(vty
, " ?'%s'", tok
->desc
);
526 if (stackpos
== MAXDEPTH
) {
527 vty_out(vty
, " -aborting! (depth limit)%s", VTY_NEWLINE
);
530 stack
[stackpos
++] = start
;
532 int numto
= desc
? 2 : vector_active(start
->to
);
535 vty_out(vty
, "%s", VTY_NEWLINE
);
536 for (unsigned int i
= 0; i
< vector_active(start
->to
); i
++) {
537 struct graph_node
*adj
= vector_slot(start
->to
, i
);
538 // if we're listing multiple children, indent!
540 for (int j
= 0; j
< level
+ 1; j
++)
542 // if this node is a vararg, just print *
545 else if (((struct cmd_token
*)adj
->data
)->type
547 vty_out(vty
, "--END%s", VTY_NEWLINE
);
550 for (k
= 0; k
< stackpos
; k
++)
551 if (stack
[k
] == adj
) {
552 vty_out(vty
, "<<loop@%zu %s", k
,
559 numto
> 1 ? level
+ 1 : level
,
560 desc
, stack
, stackpos
);
564 vty_out(vty
, "%s", VTY_NEWLINE
);
567 static void pretty_print_dot(FILE *ofd
, unsigned opts
, struct graph_node
*start
,
568 struct graph_node
**stack
, size_t stackpos
,
569 struct graph_node
**visited
, size_t *visitpos
)
573 struct cmd_token
*tok
= start
->data
;
576 for (size_t i
= 0; i
< (*visitpos
); i
++)
577 if (visited
[i
] == start
)
579 visited
[(*visitpos
)++] = start
;
580 if ((*visitpos
) == MAXDEPTH
* MAXDEPTH
)
583 snprintf(tokennum
, sizeof(tokennum
), "%d?", tok
->type
);
584 fprintf(ofd
, " n%p [ shape=box, label=<", start
);
586 fprintf(ofd
, "<b>%s</b>", lookup_msg(tokennames
, tok
->type
, NULL
));
587 if (tok
->attr
== CMD_ATTR_DEPRECATED
)
588 fprintf(ofd
, " (d)");
589 else if (tok
->attr
== CMD_ATTR_HIDDEN
)
590 fprintf(ofd
, " (h)");
592 if (tok
->type
== WORD_TKN
)
594 "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"",
597 fprintf(ofd
, "<br/>%s", tok
->text
);
600 fprintf(ofd, " ?'%s'", tok->desc); */
618 fprintf(ofd
, ">, style = filled, fillcolor = \"%s\" ];\n", color
);
620 if (stackpos
== MAXDEPTH
)
622 stack
[stackpos
++] = start
;
624 for (unsigned int i
= 0; i
< vector_active(start
->to
); i
++) {
625 struct graph_node
*adj
= vector_slot(start
->to
, i
);
626 // if this node is a vararg, just print *
628 fprintf(ofd
, " n%p -> n%p;\n", start
, start
);
629 } else if (((struct cmd_token
*)adj
->data
)->type
== END_TKN
) {
630 // struct cmd_token *et = adj->data;
631 fprintf(ofd
, " n%p -> end%p;\n", start
, adj
);
633 " end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n",
636 fprintf(ofd
, " n%p -> n%p;\n", start
, adj
);
638 for (k
= 0; k
< stackpos
; k
++)
642 pretty_print_dot(ofd
, opts
, adj
, stack
,
643 stackpos
, visited
, visitpos
);
650 /** stuff that should go in command.c + command.h */
651 void init_cmdgraph(struct vty
*vty
, struct graph
**graph
)
653 // initialize graph, add start noe
654 *graph
= graph_new();
655 nodegraph_free
= *graph
;
656 struct cmd_token
*token
= new_cmd_token(START_TKN
, 0, NULL
, NULL
);
657 graph_new_node(*graph
, token
, (void (*)(void *)) & del_cmd_token
);
659 vty_out(vty
, "initialized graph%s", VTY_NEWLINE
);