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")
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
**);
42 /** shim interface commands **/
43 struct graph
*nodegraph
= NULL
, *nodegraph_free
= NULL
;
45 #define check_nodegraph() \
48 vty_out(vty, "nodegraph not initialized\n"); \
55 "grammar parse LINE...",
58 "command to pass to new parser\n")
63 // make a string from tokenized command line
64 char *command
= argv_concat(argv
, argc
, idx_command
);
66 // create cmd_element for parser
67 struct cmd_element
*cmd
=
68 XCALLOC(MTYPE_CMD_TOKENS
, sizeof(struct cmd_element
));
69 cmd
->string
= command
;
71 "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n";
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
);
80 cmd_graph_parse(graph
, cmd
);
81 cmd_graph_merge(nodegraph
, graph
, +1);
86 DEFUN (grammar_test_complete
,
87 grammar_test_complete_cmd
,
88 "grammar complete COMMAND...",
90 "attempt to complete input on DFA\n"
91 "command to complete\n")
96 char *cmdstr
= argv_concat(argv
, argc
, idx_command
);
100 vector command
= cmd_make_strvec(cmdstr
);
102 XFREE(MTYPE_TMP
, cmdstr
);
106 // generate completions of user input
107 struct list
*completions
;
108 enum matcher_rv result
=
109 command_complete(nodegraph
, command
, &completions
);
111 // print completions or relevant error message
112 if (!MATCHER_ERROR(result
)) {
113 vector comps
= completions_to_vec(completions
);
114 struct cmd_token
*tkn
;
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
;
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
,
131 for (i
= 0; i
< vector_active(comps
); i
++)
133 (struct cmd_token
*)vector_slot(comps
, i
));
136 vty_out(vty
, "%% No match\n");
139 list_delete_and_null(&completions
);
140 cmd_free_strvec(command
);
141 XFREE(MTYPE_TMP
, cmdstr
);
146 DEFUN (grammar_test_match
,
147 grammar_test_match_cmd
,
148 "grammar match COMMAND...",
150 "attempt to match input on DFA\n"
151 "command to match\n")
156 if (argv
[2]->arg
[0] == '#')
159 char *cmdstr
= argv_concat(argv
, argc
, idx_command
);
162 vector command
= cmd_make_strvec(cmdstr
);
164 XFREE(MTYPE_TMP
, cmdstr
);
168 struct list
*argvv
= NULL
;
169 const struct cmd_element
*element
= NULL
;
170 enum matcher_rv result
=
171 command_match(nodegraph
, command
, &argvv
, &element
);
173 // print completions or relevant error message
175 vty_out(vty
, "Matched: %s\n", element
->string
);
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
);
181 vty_out(vty
, "func: %p\n", element
->func
);
183 list_delete_and_null(&argvv
);
185 assert(MATCHER_ERROR(result
));
187 case MATCHER_NO_MATCH
:
188 vty_out(vty
, "%% Unknown command\n");
190 case MATCHER_INCOMPLETE
:
191 vty_out(vty
, "%% Incomplete command\n");
193 case MATCHER_AMBIGUOUS
:
194 vty_out(vty
, "%% Ambiguous command\n");
197 vty_out(vty
, "%% Unknown error\n");
203 cmd_free_strvec(command
);
204 XFREE(MTYPE_TMP
, cmdstr
);
210 * Testing shim to test docstrings
212 DEFUN (grammar_test_doc
,
213 grammar_test_doc_cmd
,
214 "grammar test docstring",
216 "Test function for docstring\n"
221 // create cmd_element with docstring
222 struct cmd_element
*cmd
=
223 XCALLOC(MTYPE_CMD_TOKENS
, sizeof(struct cmd_element
));
224 cmd
->string
= XSTRDUP(
226 "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
227 cmd
->doc
= XSTRDUP(MTYPE_CMD_TOKENS
,
236 "optional variable\n"
243 cmd_graph_parse(nodegraph
, cmd
);
249 * Debugging command to print command graph
251 DEFUN (grammar_test_show
,
252 grammar_test_show_cmd
,
253 "grammar show [doc]",
255 "print current accumulated DFA\n"
256 "include docstrings\n")
260 struct graph_node
*stack
[CMD_ARGC_MAX
];
261 pretty_print_graph(vty
, vector_slot(nodegraph
->nodes
, 0), 0, argc
>= 3,
266 DEFUN (grammar_test_dot
,
267 grammar_test_dot_cmd
,
268 "grammar dotfile OUTNAME",
270 "print current graph for dot\n"
274 FILE *ofd
= fopen(argv
[2]->arg
, "w");
277 vty_out(vty
, "%s: %s\r\n", argv
[2]->arg
, strerror(errno
));
281 char *dot
= cmd_graph_dump_dot(nodegraph
);
283 fprintf(ofd
, "%s", dot
);
285 XFREE(MTYPE_TMP
, dot
);
290 struct cmd_permute_item
{
292 struct cmd_element
*el
;
295 static void cmd_permute_free(void *arg
)
297 struct cmd_permute_item
*i
= arg
;
298 XFREE(MTYPE_TMP
, i
->cmd
);
302 static int cmd_permute_cmp(void *a
, void *b
)
304 struct cmd_permute_item
*aa
= a
, *bb
= b
;
305 return strcmp(aa
->cmd
, bb
->cmd
);
308 static void cmd_graph_permute(struct list
*out
, struct graph_node
**stack
,
309 size_t stackpos
, char *cmd
)
311 struct graph_node
*gn
= stack
[stackpos
];
312 struct cmd_token
*tok
= gn
->data
;
313 char *appendp
= cmd
+ strlen(cmd
);
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
);
328 if (++stackpos
== CMD_ARGC_MAX
)
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
)
339 stack
[stackpos
] = gnext
;
341 cmd_graph_permute(out
, stack
, stackpos
, cmd
);
345 static struct list
*cmd_graph_permutations(struct graph
*graph
)
347 char accumulate
[2048] = "";
348 struct graph_node
*stack
[CMD_ARGC_MAX
];
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
);
358 extern vector cmdvec
;
360 DEFUN (grammar_findambig
,
361 grammar_findambig_cmd
,
362 "grammar find-ambiguous [{printall|nodescan}]",
364 "Find ambiguous commands\n"
365 "Print all permutations\n"
368 struct list
*commands
;
369 struct cmd_permute_item
*prev
= NULL
, *cur
= NULL
;
371 int i
, printall
, scan
, scannode
= 0;
375 printall
= argv_find(argv
, argc
, "printall", &i
);
377 scan
= argv_find(argv
, argc
, "nodescan", &i
);
379 if (scan
&& nodegraph_free
) {
380 graph_delete_graph(nodegraph_free
);
381 nodegraph_free
= NULL
;
384 if (!scan
&& !nodegraph
) {
385 vty_out(vty
, "nodegraph uninitialized\r\n");
386 return CMD_WARNING_CONFIG_FAILED
;
391 struct cmd_node
*cnode
=
392 vector_slot(cmdvec
, scannode
++);
395 nodegraph
= cnode
->cmdgraph
;
398 vty_out(vty
, "scanning node %d (%s)\n", scannode
- 1,
399 node_names
[scannode
- 1]);
402 commands
= cmd_graph_permutations(nodegraph
);
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
,
410 vty_out(vty
, "'%s' AMBIGUOUS:\n", cur
->cmd
);
411 vty_out(vty
, " %s\n '%s'\n", prev
->el
->name
,
413 vty_out(vty
, " %s\n '%s'\n", cur
->el
->name
,
420 list_delete_and_null(&commands
);
423 } while (scan
&& scannode
< LINK_PARAMS_NODE
);
425 vty_out(vty
, "%d ambiguous commands found.\n", ambig
);
429 return ambig
== 0 ? CMD_SUCCESS
: CMD_WARNING_CONFIG_FAILED
;
432 DEFUN (grammar_init_graph
,
433 grammar_init_graph_cmd
,
436 "(re)initialize graph\n")
439 graph_delete_graph(nodegraph_free
);
440 nodegraph_free
= NULL
;
442 init_cmdgraph(vty
, &nodegraph
);
446 DEFUN (grammar_access
,
448 "grammar access (0-65535)",
450 "access node graph\n"
454 graph_delete_graph(nodegraph_free
);
455 nodegraph_free
= NULL
;
457 struct cmd_node
*cnode
;
459 cnode
= vector_slot(cmdvec
, atoi(argv
[2]->arg
));
461 vty_out(vty
, "%% no such node\n");
462 return CMD_WARNING_CONFIG_FAILED
;
465 vty_out(vty
, "node %d\n", (int)cnode
->node
);
466 nodegraph
= cnode
->cmdgraph
;
470 /* this is called in vtysh.c to set up the testing shim */
471 void grammar_sandbox_init(void)
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
);
486 * Pretty-prints a graph, assuming it is a tree.
488 * @param start the node to take as the root
489 * @param level indent level for recursive calls, always pass 0
491 void pretty_print_graph(struct vty
*vty
, struct graph_node
*start
, int level
,
492 int desc
, struct graph_node
**stack
, size_t stackpos
)
496 struct cmd_token
*tok
= start
->data
;
498 snprintf(tokennum
, sizeof(tokennum
), "%d?", tok
->type
);
499 vty_out(vty
, "%s", lookup_msg(tokennames
, tok
->type
, NULL
));
501 vty_out(vty
, ":\"%s\"", tok
->text
);
503 vty_out(vty
, " => %s", tok
->varname
);
505 vty_out(vty
, " ?'%s'", tok
->desc
);
508 if (stackpos
== CMD_ARGC_MAX
) {
509 vty_out(vty
, " -aborting! (depth limit)\n");
512 stack
[stackpos
++] = start
;
514 int numto
= desc
? 2 : vector_active(start
->to
);
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!
522 for (int j
= 0; j
< level
+ 1; j
++)
524 // if this node is a vararg, just print *
527 else if (((struct cmd_token
*)adj
->data
)->type
529 vty_out(vty
, "--END\n");
532 for (k
= 0; k
< stackpos
; k
++)
533 if (stack
[k
] == adj
) {
534 vty_out(vty
, "<<loop@%zu \n",
541 numto
> 1 ? level
+ 1 : level
,
542 desc
, stack
, stackpos
);
549 /** stuff that should go in command.c + command.h */
550 void init_cmdgraph(struct vty
*vty
, struct graph
**graph
)
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
);
558 vty_out(vty
, "initialized graph\n");