]>
Commit | Line | Data |
---|---|---|
1ab84bf3 QY |
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. | |
1ab84bf3 QY |
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 | |
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 | |
24 | * 02111-1307, USA. | |
25 | */ | |
26 | ||
51e156b3 | 27 | #include "command.h" |
fa133e00 | 28 | #include "memory_vty.h" |
1eb5e8dc | 29 | #include "graph.h" |
0a22b979 | 30 | #include "linklist.h" |
9d0662e0 | 31 | #include "command_match.h" |
51e156b3 QY |
32 | |
33 | #define GRAMMAR_STR "CLI grammar sandbox\n" | |
34 | ||
fa133e00 DL |
35 | DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc") |
36 | ||
37 | #define MAXDEPTH 64 | |
38 | ||
97c45dae | 39 | /** headers **/ |
1ab84bf3 | 40 | void |
1eb5e8dc | 41 | grammar_sandbox_init (void); |
1ab84bf3 | 42 | void |
fa133e00 | 43 | pretty_print_graph (struct vty *vty, struct graph_node *, int, int, struct graph_node **, size_t); |
818a5168 DL |
44 | static void |
45 | pretty_print_dot (FILE *ofd, unsigned opts, struct graph_node *start, | |
b285cf4b DL |
46 | struct graph_node **stack, size_t stackpos, |
47 | struct graph_node **visited, size_t *visitpos); | |
500b1a5b | 48 | void |
fa133e00 | 49 | init_cmdgraph (struct vty *, struct graph **); |
1ab84bf3 | 50 | |
97c45dae | 51 | /** shim interface commands **/ |
af2567b6 | 52 | struct graph *nodegraph = NULL, *nodegraph_free = NULL; |
340a2b4a | 53 | |
9d0662e0 QY |
54 | DEFUN (grammar_test, |
55 | grammar_test_cmd, | |
fa133e00 | 56 | "grammar parse LINE...", |
9d0662e0 | 57 | GRAMMAR_STR |
fa133e00 | 58 | "parse a command\n" |
9d0662e0 QY |
59 | "command to pass to new parser\n") |
60 | { | |
58749582 | 61 | int idx_command = 2; |
ff35126c | 62 | // make a string from tokenized command line |
58749582 | 63 | char *command = argv_concat (argv, argc, idx_command); |
1ab84bf3 | 64 | |
ff35126c | 65 | // create cmd_element for parser |
1eb5e8dc | 66 | struct cmd_element *cmd = XCALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_element)); |
500b1a5b | 67 | cmd->string = command; |
fa133e00 | 68 | cmd->doc = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n"; |
e648e61a | 69 | cmd->func = NULL; |
51e156b3 | 70 | |
1ab84bf3 | 71 | // parse the command and install it into the command graph |
de8f7a39 DL |
72 | struct graph *graph = graph_new(); |
73 | struct cmd_token *token = new_cmd_token (START_TKN, CMD_ATTR_NORMAL, NULL, NULL); | |
74 | graph_new_node (graph, token, (void (*)(void *)) &del_cmd_token); | |
75 | ||
76 | command_parse_format (graph, cmd); | |
77 | cmd_merge_graphs (nodegraph, graph, +1); | |
1ab84bf3 | 78 | |
9d0662e0 QY |
79 | return CMD_SUCCESS; |
80 | } | |
81 | ||
eceb1066 QY |
82 | DEFUN (grammar_test_complete, |
83 | grammar_test_complete_cmd, | |
fa133e00 | 84 | "grammar complete COMMAND...", |
9d0662e0 | 85 | GRAMMAR_STR |
eceb1066 | 86 | "attempt to complete input on DFA\n" |
fa133e00 | 87 | "command to complete\n") |
9d0662e0 | 88 | { |
58749582 DW |
89 | int idx_command = 2; |
90 | char *cmdstr = argv_concat (argv, argc, idx_command); | |
fa133e00 DL |
91 | if (!cmdstr) |
92 | return CMD_SUCCESS; | |
93 | ||
1ab84bf3 | 94 | vector command = cmd_make_strvec (cmdstr); |
14878121 DL |
95 | if (!command) |
96 | { | |
97 | XFREE (MTYPE_TMP, cmdstr); | |
98 | return CMD_SUCCESS; | |
99 | } | |
1ab84bf3 | 100 | |
ff35126c | 101 | // generate completions of user input |
795d0278 | 102 | struct list *completions; |
ff35126c | 103 | enum matcher_rv result = command_complete (nodegraph, command, &completions); |
18be0e59 | 104 | |
1ab84bf3 | 105 | // print completions or relevant error message |
ca90e051 | 106 | if (!MATCHER_ERROR(result)) |
1ab84bf3 | 107 | { |
ee761cc0 | 108 | vector comps = completions_to_vec (completions); |
fa133e00 | 109 | struct cmd_token *tkn; |
ff35126c QY |
110 | |
111 | // calculate length of longest tkn->text in completions | |
ee761cc0 QY |
112 | unsigned int width = 0, i = 0; |
113 | for (i = 0; i < vector_active (comps); i++) { | |
114 | tkn = vector_slot (comps, i); | |
115 | unsigned int len = strlen (tkn->text); | |
116 | width = len > width ? len : width; | |
ff35126c QY |
117 | } |
118 | ||
119 | // print completions | |
ee761cc0 QY |
120 | for (i = 0; i < vector_active (comps); i++) { |
121 | tkn = vector_slot (comps, i); | |
fa133e00 | 122 | vty_out (vty, " %-*s %s%s", width, tkn->text, tkn->desc, VTY_NEWLINE); |
ee761cc0 | 123 | } |
795d0278 QY |
124 | |
125 | for (i = 0; i < vector_active (comps); i++) | |
fa133e00 | 126 | del_cmd_token ((struct cmd_token *) vector_slot (comps, i)); |
795d0278 | 127 | vector_free (comps); |
1ab84bf3 | 128 | } |
18be0e59 | 129 | else |
fa133e00 | 130 | vty_out (vty, "%% No match%s", VTY_NEWLINE); |
1ab84bf3 QY |
131 | |
132 | // free resources | |
1eb5e8dc | 133 | list_delete (completions); |
ff35126c | 134 | cmd_free_strvec (command); |
14878121 | 135 | XFREE (MTYPE_TMP, cmdstr); |
18be0e59 | 136 | |
9d0662e0 | 137 | return CMD_SUCCESS; |
340a2b4a QY |
138 | } |
139 | ||
eceb1066 QY |
140 | DEFUN (grammar_test_match, |
141 | grammar_test_match_cmd, | |
fa133e00 | 142 | "grammar match COMMAND...", |
eceb1066 QY |
143 | GRAMMAR_STR |
144 | "attempt to match input on DFA\n" | |
fa133e00 | 145 | "command to match\n") |
eceb1066 | 146 | { |
58749582 | 147 | int idx_command = 2; |
fa133e00 | 148 | if (argv[2]->arg[0] == '#') |
4427e9b3 QY |
149 | return CMD_SUCCESS; |
150 | ||
58749582 | 151 | char *cmdstr = argv_concat(argv, argc, idx_command); |
14878121 DL |
152 | if (!cmdstr) |
153 | return CMD_SUCCESS; | |
1ab84bf3 | 154 | vector command = cmd_make_strvec (cmdstr); |
14878121 DL |
155 | if (!command) |
156 | { | |
157 | XFREE (MTYPE_TMP, cmdstr); | |
158 | return CMD_SUCCESS; | |
159 | } | |
6ce82b63 | 160 | |
de9d7e4f | 161 | struct list *argvv = NULL; |
fa133e00 | 162 | const struct cmd_element *element = NULL; |
55589d30 | 163 | enum matcher_rv result = command_match (nodegraph, command, &argvv, &element); |
1ab84bf3 QY |
164 | |
165 | // print completions or relevant error message | |
166 | if (element) | |
167 | { | |
fa133e00 | 168 | vty_out (vty, "Matched: %s%s", element->string, VTY_NEWLINE); |
1ab84bf3 | 169 | struct listnode *ln; |
fa133e00 | 170 | struct cmd_token *token; |
97f13f08 | 171 | for (ALL_LIST_ELEMENTS_RO(argvv,ln,token)) |
fa133e00 | 172 | vty_out (vty, "%s -- %s%s", token->text, token->arg, VTY_NEWLINE); |
1eb5e8dc | 173 | |
fa133e00 | 174 | vty_out (vty, "func: %p%s", element->func, VTY_NEWLINE); |
1ab84bf3 QY |
175 | |
176 | list_delete (argvv); | |
177 | } | |
de9d7e4f | 178 | else { |
1ab84bf3 | 179 | assert(MATCHER_ERROR(result)); |
6ce82b63 QY |
180 | switch (result) { |
181 | case MATCHER_NO_MATCH: | |
fa133e00 | 182 | vty_out (vty, "%% Unknown command%s", VTY_NEWLINE); |
6ce82b63 QY |
183 | break; |
184 | case MATCHER_INCOMPLETE: | |
fa133e00 | 185 | vty_out (vty, "%% Incomplete command%s", VTY_NEWLINE); |
6ce82b63 QY |
186 | break; |
187 | case MATCHER_AMBIGUOUS: | |
fa133e00 | 188 | vty_out (vty, "%% Ambiguous command%s", VTY_NEWLINE); |
6ce82b63 QY |
189 | break; |
190 | default: | |
fa133e00 | 191 | vty_out (vty, "%% Unknown error%s", VTY_NEWLINE); |
6ce82b63 QY |
192 | break; |
193 | } | |
de9d7e4f QY |
194 | } |
195 | ||
1ab84bf3 | 196 | // free resources |
795d0278 | 197 | cmd_free_strvec (command); |
14878121 | 198 | XFREE (MTYPE_TMP, cmdstr); |
1ab84bf3 | 199 | |
eceb1066 QY |
200 | return CMD_SUCCESS; |
201 | } | |
202 | ||
1ab84bf3 QY |
203 | /** |
204 | * Testing shim to test docstrings | |
205 | */ | |
206 | DEFUN (grammar_test_doc, | |
207 | grammar_test_doc_cmd, | |
208 | "grammar test docstring", | |
209 | GRAMMAR_STR | |
210 | "Test function for docstring\n" | |
211 | "Command end\n") | |
212 | { | |
213 | // create cmd_element with docstring | |
1eb5e8dc | 214 | struct cmd_element *cmd = XCALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_element)); |
ff35126c QY |
215 | cmd->string = XSTRDUP (MTYPE_CMD_TOKENS, "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG"); |
216 | cmd->doc = XSTRDUP (MTYPE_CMD_TOKENS, | |
217 | "Test stuff\n" | |
1ab84bf3 QY |
218 | "docstring thing\n" |
219 | "first example\n" | |
220 | "second example\n" | |
221 | "follow\n" | |
222 | "random range\n" | |
223 | "end thingy\n" | |
224 | "variable\n" | |
225 | "optional variable\n" | |
226 | "optional set\n" | |
227 | "optional lol\n" | |
ff35126c | 228 | "vararg!\n"); |
1ab84bf3 | 229 | cmd->func = NULL; |
1ab84bf3 QY |
230 | |
231 | // parse element | |
55589d30 | 232 | command_parse_format (nodegraph, cmd); |
1ab84bf3 QY |
233 | |
234 | return CMD_SUCCESS; | |
235 | } | |
236 | ||
237 | /** | |
238 | * Debugging command to print command graph | |
239 | */ | |
240 | DEFUN (grammar_test_show, | |
241 | grammar_test_show_cmd, | |
fa133e00 | 242 | "grammar show [doc]", |
1ab84bf3 | 243 | GRAMMAR_STR |
fa133e00 DL |
244 | "print current accumulated DFA\n" |
245 | "include docstrings\n") | |
1ab84bf3 | 246 | { |
fa133e00 DL |
247 | struct graph_node *stack[MAXDEPTH]; |
248 | ||
1ab84bf3 | 249 | if (!nodegraph) |
fa133e00 | 250 | vty_out(vty, "nodegraph uninitialized\r\n"); |
1ab84bf3 | 251 | else |
fa133e00 | 252 | pretty_print_graph (vty, vector_slot (nodegraph->nodes, 0), 0, argc >= 3, stack, 0); |
1ab84bf3 QY |
253 | return CMD_SUCCESS; |
254 | } | |
51e156b3 | 255 | |
818a5168 DL |
256 | DEFUN (grammar_test_dot, |
257 | grammar_test_dot_cmd, | |
258 | "grammar dotfile OUTNAME", | |
259 | GRAMMAR_STR | |
260 | "print current graph for dot\n" | |
261 | ".dot filename\n") | |
262 | { | |
263 | struct graph_node *stack[MAXDEPTH]; | |
264 | struct graph_node *visited[MAXDEPTH*MAXDEPTH]; | |
265 | size_t vpos = 0; | |
266 | ||
267 | if (!nodegraph) { | |
268 | vty_out(vty, "nodegraph uninitialized\r\n"); | |
269 | return CMD_SUCCESS; | |
270 | } | |
271 | FILE *ofd = fopen(argv[2]->arg, "w"); | |
272 | if (!ofd) { | |
273 | vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno)); | |
274 | return CMD_SUCCESS; | |
275 | } | |
276 | ||
277 | fprintf(ofd, "digraph {\n graph [ rankdir = LR ];\n node [ fontname = \"Fira Mono\", fontsize = 9 ];\n\n"); | |
278 | pretty_print_dot (ofd, 0, | |
b285cf4b DL |
279 | vector_slot (nodegraph->nodes, 0), |
280 | stack, 0, visited, &vpos); | |
818a5168 DL |
281 | fprintf(ofd, "}\n"); |
282 | fclose(ofd); | |
283 | return CMD_SUCCESS; | |
284 | } | |
285 | ||
0a22b979 DL |
286 | struct cmd_permute_item |
287 | { | |
288 | char *cmd; | |
289 | struct cmd_element *el; | |
290 | }; | |
291 | ||
292 | static void | |
293 | cmd_permute_free (void *arg) | |
294 | { | |
295 | struct cmd_permute_item *i = arg; | |
296 | XFREE (MTYPE_TMP, i->cmd); | |
297 | XFREE (MTYPE_TMP, i); | |
298 | } | |
299 | ||
300 | static int | |
301 | cmd_permute_cmp (void *a, void *b) | |
302 | { | |
303 | struct cmd_permute_item *aa = a, *bb = b; | |
304 | return strcmp (aa->cmd, bb->cmd); | |
305 | } | |
306 | ||
307 | static void | |
308 | 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 | { | |
318 | sprintf (appendp, "%s ", tok->text); | |
319 | appendp += strlen (appendp); | |
320 | } | |
321 | else if (tok->type == END_TKN) | |
322 | { | |
323 | struct cmd_permute_item *i = XMALLOC (MTYPE_TMP, sizeof (*i)); | |
324 | i->el = ((struct graph_node *)vector_slot (gn->to, 0))->data; | |
325 | i->cmd = XSTRDUP (MTYPE_TMP, cmd); | |
326 | i->cmd[strlen(cmd) - 1] = '\0'; | |
327 | listnode_add_sort (out, i); | |
328 | return; | |
329 | } | |
330 | ||
331 | if (++stackpos == MAXDEPTH) | |
332 | return; | |
333 | ||
334 | for (i = 0; i < vector_active (gn->to); i++) | |
335 | { | |
336 | struct graph_node *gnext = vector_slot (gn->to, i); | |
337 | for (j = 0; j < stackpos; j++) | |
338 | if (stack[j] == gnext) | |
339 | break; | |
340 | if (j != stackpos) | |
341 | continue; | |
342 | ||
343 | stack[stackpos] = gnext; | |
344 | *appendp = '\0'; | |
345 | cmd_graph_permute (out, stack, stackpos, cmd); | |
346 | } | |
347 | } | |
348 | ||
349 | static struct list * | |
350 | cmd_graph_permutations (struct graph *graph) | |
351 | { | |
352 | char accumulate[2048] = ""; | |
353 | struct graph_node *stack[MAXDEPTH]; | |
354 | ||
355 | struct list *rv = list_new (); | |
356 | rv->cmp = cmd_permute_cmp; | |
357 | rv->del = cmd_permute_free; | |
358 | stack[0] = vector_slot (graph->nodes, 0); | |
359 | cmd_graph_permute (rv, stack, 0, accumulate); | |
360 | return rv; | |
361 | } | |
362 | ||
363 | extern vector cmdvec; | |
364 | ||
365 | DEFUN (grammar_findambig, | |
366 | grammar_findambig_cmd, | |
367 | "grammar find-ambiguous [{printall|nodescan}]", | |
368 | GRAMMAR_STR | |
369 | "Find ambiguous commands\n" | |
370 | "Print all permutations\n" | |
371 | "Scan all nodes\n") | |
372 | { | |
373 | struct list *commands; | |
374 | struct cmd_permute_item *prev = NULL, *cur = NULL; | |
375 | struct listnode *ln; | |
376 | int i, printall, scan, scannode = 0; | |
37f9f36e | 377 | int ambig = 0; |
0a22b979 DL |
378 | |
379 | i = 0; | |
380 | printall = argv_find (argv, argc, "printall", &i); | |
381 | i = 0; | |
382 | scan = argv_find (argv, argc, "nodescan", &i); | |
383 | ||
384 | if (scan && nodegraph_free) | |
385 | { | |
386 | graph_delete_graph (nodegraph_free); | |
387 | nodegraph_free = NULL; | |
388 | } | |
389 | ||
390 | if (!scan && !nodegraph) | |
391 | { | |
392 | vty_out(vty, "nodegraph uninitialized\r\n"); | |
393 | return CMD_WARNING; | |
394 | } | |
395 | ||
396 | do { | |
397 | if (scan) | |
398 | { | |
399 | struct cmd_node *cnode = vector_slot (cmdvec, scannode++); | |
400 | if (!cnode) | |
401 | continue; | |
402 | nodegraph = cnode->cmdgraph; | |
403 | if (!nodegraph) | |
404 | continue; | |
405 | vty_out (vty, "scanning node %d%s", scannode - 1, VTY_NEWLINE); | |
406 | } | |
407 | ||
408 | commands = cmd_graph_permutations (nodegraph); | |
409 | prev = NULL; | |
410 | for (ALL_LIST_ELEMENTS_RO (commands, ln, cur)) | |
411 | { | |
412 | int same = prev && !strcmp (prev->cmd, cur->cmd); | |
413 | if (printall && !same) | |
90e40ab0 | 414 | vty_out (vty, "'%s' [%x]%s", cur->cmd, cur->el->daemon, VTY_NEWLINE); |
0a22b979 DL |
415 | if (same) |
416 | { | |
417 | vty_out (vty, "'%s' AMBIGUOUS:%s", cur->cmd, VTY_NEWLINE); | |
418 | vty_out (vty, " %s%s '%s'%s", prev->el->name, VTY_NEWLINE, prev->el->string, VTY_NEWLINE); | |
419 | vty_out (vty, " %s%s '%s'%s", cur->el->name, VTY_NEWLINE, cur->el->string, VTY_NEWLINE); | |
420 | vty_out (vty, "%s", VTY_NEWLINE); | |
37f9f36e | 421 | ambig++; |
0a22b979 DL |
422 | } |
423 | prev = cur; | |
424 | } | |
425 | list_delete (commands); | |
37f9f36e DL |
426 | |
427 | vty_out (vty, "%s", VTY_NEWLINE); | |
0a22b979 DL |
428 | } while (scan && scannode < LINK_PARAMS_NODE); |
429 | ||
37f9f36e DL |
430 | vty_out (vty, "%d ambiguous commands found.%s", ambig, VTY_NEWLINE); |
431 | ||
0a22b979 DL |
432 | if (scan) |
433 | nodegraph = NULL; | |
37f9f36e | 434 | return ambig == 0 ? CMD_SUCCESS : CMD_WARNING; |
0a22b979 DL |
435 | } |
436 | ||
500b1a5b QY |
437 | DEFUN (grammar_init_graph, |
438 | grammar_init_graph_cmd, | |
fa133e00 | 439 | "grammar init", |
500b1a5b QY |
440 | GRAMMAR_STR |
441 | "(re)initialize graph\n") | |
442 | { | |
af2567b6 DL |
443 | if (nodegraph_free) |
444 | graph_delete_graph (nodegraph_free); | |
445 | nodegraph_free = NULL; | |
446 | ||
fa133e00 | 447 | init_cmdgraph (vty, &nodegraph); |
500b1a5b QY |
448 | return CMD_SUCCESS; |
449 | } | |
450 | ||
af2567b6 DL |
451 | DEFUN (grammar_access, |
452 | grammar_access_cmd, | |
453 | "grammar access (0-65535)", | |
454 | GRAMMAR_STR | |
455 | "access node graph\n" | |
456 | "node number\n") | |
457 | { | |
458 | if (nodegraph_free) | |
459 | graph_delete_graph (nodegraph_free); | |
460 | nodegraph_free = NULL; | |
461 | ||
462 | struct cmd_node *cnode; | |
463 | ||
464 | cnode = vector_slot (cmdvec, atoi (argv[2]->arg)); | |
465 | if (!cnode) | |
466 | { | |
467 | vty_out (vty, "%% no such node%s", VTY_NEWLINE); | |
468 | return CMD_WARNING; | |
469 | } | |
470 | ||
471 | vty_out (vty, "node %d%s", (int)cnode->node, VTY_NEWLINE); | |
472 | nodegraph = cnode->cmdgraph; | |
473 | return CMD_SUCCESS; | |
474 | } | |
475 | ||
1ab84bf3 | 476 | /* this is called in vtysh.c to set up the testing shim */ |
fa133e00 DL |
477 | void grammar_sandbox_init(void) { |
478 | init_cmdgraph (NULL, &nodegraph); | |
1eb5e8dc QY |
479 | |
480 | // install all enable elements | |
478bdaeb | 481 | install_element (ENABLE_NODE, &grammar_test_cmd); |
340a2b4a | 482 | install_element (ENABLE_NODE, &grammar_test_show_cmd); |
818a5168 | 483 | install_element (ENABLE_NODE, &grammar_test_dot_cmd); |
9d0662e0 | 484 | install_element (ENABLE_NODE, &grammar_test_match_cmd); |
eceb1066 | 485 | install_element (ENABLE_NODE, &grammar_test_complete_cmd); |
0aa2c2ff | 486 | install_element (ENABLE_NODE, &grammar_test_doc_cmd); |
0a22b979 | 487 | install_element (ENABLE_NODE, &grammar_findambig_cmd); |
500b1a5b | 488 | install_element (ENABLE_NODE, &grammar_init_graph_cmd); |
af2567b6 | 489 | install_element (ENABLE_NODE, &grammar_access_cmd); |
51e156b3 | 490 | } |
1ab84bf3 | 491 | |
fa133e00 DL |
492 | #define item(x) { x, #x } |
493 | struct message tokennames[] = { | |
494 | item(WORD_TKN), // words | |
495 | item(VARIABLE_TKN), // almost anything | |
496 | item(RANGE_TKN), // integer range | |
497 | item(IPV4_TKN), // IPV4 addresses | |
498 | item(IPV4_PREFIX_TKN), // IPV4 network prefixes | |
499 | item(IPV6_TKN), // IPV6 prefixes | |
500 | item(IPV6_PREFIX_TKN), // IPV6 network prefixes | |
501 | ||
502 | /* plumbing types */ | |
0bf5b1cb DL |
503 | item(FORK_TKN), |
504 | item(JOIN_TKN), | |
fa133e00 DL |
505 | item(START_TKN), // first token in line |
506 | item(END_TKN), // last token in line | |
507 | { 0, NULL } | |
508 | }; | |
509 | size_t tokennames_max = array_size(tokennames); | |
500b1a5b | 510 | |
97f13f08 QY |
511 | /** |
512 | * Pretty-prints a graph, assuming it is a tree. | |
513 | * | |
514 | * @param start the node to take as the root | |
515 | * @param level indent level for recursive calls, always pass 0 | |
516 | */ | |
1ab84bf3 | 517 | void |
fa133e00 | 518 | pretty_print_graph (struct vty *vty, struct graph_node *start, int level, |
b285cf4b | 519 | int desc, struct graph_node **stack, size_t stackpos) |
1ab84bf3 QY |
520 | { |
521 | // print this node | |
fa133e00 DL |
522 | char tokennum[32]; |
523 | struct cmd_token *tok = start->data; | |
524 | ||
525 | snprintf(tokennum, sizeof(tokennum), "%d?", tok->type); | |
526 | vty_out(vty, "%s", LOOKUP_DEF(tokennames, tok->type, tokennum)); | |
527 | if (tok->text) | |
528 | vty_out(vty, ":\"%s\"", tok->text); | |
529 | if (desc) | |
530 | vty_out(vty, " ?'%s'", tok->desc); | |
531 | vty_out(vty, " "); | |
532 | ||
533 | if (stackpos == MAXDEPTH) | |
534 | { | |
535 | vty_out(vty, " -aborting! (depth limit)%s", VTY_NEWLINE); | |
536 | return; | |
537 | } | |
538 | stack[stackpos++] = start; | |
1ab84bf3 | 539 | |
fa133e00 | 540 | int numto = desc ? 2 : vector_active (start->to); |
97f13f08 | 541 | if (numto) |
1ab84bf3 | 542 | { |
97f13f08 | 543 | if (numto > 1) |
fa133e00 | 544 | vty_out(vty, "%s", VTY_NEWLINE); |
97f13f08 | 545 | for (unsigned int i = 0; i < vector_active (start->to); i++) |
b84f1d85 | 546 | { |
97f13f08 QY |
547 | struct graph_node *adj = vector_slot (start->to, i); |
548 | // if we're listing multiple children, indent! | |
549 | if (numto > 1) | |
550 | for (int j = 0; j < level+1; j++) | |
fa133e00 | 551 | vty_out(vty, " "); |
97f13f08 QY |
552 | // if this node is a vararg, just print * |
553 | if (adj == start) | |
fa133e00 DL |
554 | vty_out(vty, "*"); |
555 | else if (((struct cmd_token *)adj->data)->type == END_TKN) | |
556 | vty_out(vty, "--END%s", VTY_NEWLINE); | |
557 | else { | |
558 | size_t k; | |
559 | for (k = 0; k < stackpos; k++) | |
560 | if (stack[k] == adj) { | |
561 | vty_out(vty, "<<loop@%zu %s", k, VTY_NEWLINE); | |
562 | break; | |
563 | } | |
564 | if (k == stackpos) | |
565 | pretty_print_graph (vty, adj, numto > 1 ? level+1 : level, desc, stack, stackpos); | |
566 | } | |
567 | } | |
1ab84bf3 QY |
568 | } |
569 | else | |
fa133e00 | 570 | vty_out(vty, "%s", VTY_NEWLINE); |
1eb5e8dc QY |
571 | } |
572 | ||
818a5168 DL |
573 | static void |
574 | pretty_print_dot (FILE *ofd, unsigned opts, struct graph_node *start, | |
b285cf4b DL |
575 | struct graph_node **stack, size_t stackpos, |
576 | struct graph_node **visited, size_t *visitpos) | |
818a5168 DL |
577 | { |
578 | // print this node | |
579 | char tokennum[32]; | |
580 | struct cmd_token *tok = start->data; | |
581 | const char *color; | |
582 | ||
583 | for (size_t i = 0; i < (*visitpos); i++) | |
584 | if (visited[i] == start) | |
585 | return; | |
586 | visited[(*visitpos)++] = start; | |
587 | if ((*visitpos) == MAXDEPTH*MAXDEPTH) | |
588 | return; | |
589 | ||
590 | snprintf(tokennum, sizeof(tokennum), "%d?", tok->type); | |
591 | fprintf(ofd, " n%016llx [ shape=box, label=<", (unsigned long long)start); | |
592 | ||
593 | fprintf(ofd, "<b>%s</b>", LOOKUP_DEF(tokennames, tok->type, tokennum)); | |
594 | if (tok->attr == CMD_ATTR_DEPRECATED) | |
595 | fprintf(ofd, " (d)"); | |
596 | else if (tok->attr == CMD_ATTR_HIDDEN) | |
597 | fprintf(ofd, " (h)"); | |
598 | if (tok->text) { | |
599 | if (tok->type == WORD_TKN) | |
600 | fprintf(ofd, "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"", tok->text); | |
601 | else | |
602 | fprintf(ofd, "<br/>%s", tok->text); | |
603 | } | |
604 | /* if (desc) | |
605 | fprintf(ofd, " ?'%s'", tok->desc); */ | |
606 | switch (tok->type) { | |
607 | case START_TKN: color = "#ccffcc"; break; | |
608 | case FORK_TKN: color = "#aaddff"; break; | |
609 | case JOIN_TKN: color = "#ddaaff"; break; | |
610 | case WORD_TKN: color = "#ffffff"; break; | |
611 | default: color = "#ffffff"; break; | |
612 | } | |
613 | fprintf(ofd, ">, style = filled, fillcolor = \"%s\" ];\n", color); | |
614 | ||
615 | if (stackpos == MAXDEPTH) | |
616 | return; | |
617 | stack[stackpos++] = start; | |
618 | ||
619 | for (unsigned int i = 0; i < vector_active (start->to); i++) | |
620 | { | |
621 | struct graph_node *adj = vector_slot (start->to, i); | |
622 | // if this node is a vararg, just print * | |
623 | if (adj == start) { | |
624 | fprintf(ofd, " n%016llx -> n%016llx;\n", | |
b285cf4b DL |
625 | (unsigned long long)start, |
626 | (unsigned long long)start); | |
818a5168 | 627 | } else if (((struct cmd_token *)adj->data)->type == END_TKN) { |
b285cf4b | 628 | //struct cmd_token *et = adj->data; |
818a5168 | 629 | fprintf(ofd, " n%016llx -> end%016llx;\n", |
b285cf4b DL |
630 | (unsigned long long)start, |
631 | (unsigned long long)adj); | |
632 | fprintf(ofd, " end%016llx [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n", | |
633 | (unsigned long long)adj); | |
818a5168 DL |
634 | } else { |
635 | fprintf(ofd, " n%016llx -> n%016llx;\n", | |
b285cf4b DL |
636 | (unsigned long long)start, |
637 | (unsigned long long)adj); | |
818a5168 DL |
638 | size_t k; |
639 | for (k = 0; k < stackpos; k++) | |
640 | if (stack[k] == adj) | |
641 | break; | |
642 | if (k == stackpos) { | |
643 | pretty_print_dot (ofd, opts, adj, stack, stackpos, visited, visitpos); | |
644 | } | |
645 | } | |
646 | } | |
647 | } | |
648 | ||
649 | ||
1eb5e8dc | 650 | /** stuff that should go in command.c + command.h */ |
97c45dae | 651 | void |
fa133e00 | 652 | init_cmdgraph (struct vty *vty, struct graph **graph) |
97c45dae QY |
653 | { |
654 | // initialize graph, add start noe | |
655 | *graph = graph_new (); | |
af2567b6 | 656 | nodegraph_free = *graph; |
fa133e00 | 657 | struct cmd_token *token = new_cmd_token (START_TKN, 0, NULL, NULL); |
97c45dae | 658 | graph_new_node (*graph, token, (void (*)(void *)) &del_cmd_token); |
fa133e00 DL |
659 | if (vty) |
660 | vty_out (vty, "initialized graph%s", VTY_NEWLINE); | |
97c45dae | 661 | } |