diff --git a/lib/command.c b/lib/command.c index 2bff3b17a2..0720762da0 100644 --- a/lib/command.c +++ b/lib/command.c @@ -49,6 +49,29 @@ DEFINE_MTYPE(LIB, HOST, "Host config") DEFINE_MTYPE(LIB, STRVEC, "String vector") DEFINE_MTYPE(LIB, COMPLETION, "Completion item") +#define item(x) \ + { \ + x, #x \ + } + +/* clang-format off */ +const struct message tokennames[] = { + item(WORD_TKN), + item(VARIABLE_TKN), + item(RANGE_TKN), + item(IPV4_TKN), + item(IPV4_PREFIX_TKN), + item(IPV6_TKN), + item(IPV6_PREFIX_TKN), + item(MAC_TKN), + item(MAC_PREFIX_TKN), + item(FORK_TKN), + item(JOIN_TKN), + item(START_TKN), + item(END_TKN), + {0}, +}; + const char *node_names[] = { "auth", // AUTH_NODE, "view", // VIEW_NODE, @@ -121,6 +144,7 @@ const char *node_names[] = { "bgp ipv6 flowspec", /* BGP_FLOWSPECV6_NODE */ }; +/* clang-format on */ /* Command vector which includes some level of command lists. Normally each daemon maintains each own cmdvec. */ @@ -1566,6 +1590,21 @@ DEFUN (show_commandtree, return cmd_list_cmds(vty, argc == 3); } +DEFUN_HIDDEN(show_cli_graph, + show_cli_graph_cmd, + "show cli graph", + SHOW_STR + "CLI reflection\n" + "Dump current command space as DOT graph\n") +{ + struct cmd_node *cn = vector_slot(cmdvec, vty->node); + char *dot = cmd_graph_dump_dot(cn->cmdgraph); + + vty_out(vty, "%s\n", dot); + XFREE(MTYPE_TMP, dot); + return CMD_SUCCESS; +} + static int vty_write_config(struct vty *vty) { size_t i; @@ -2573,6 +2612,7 @@ void install_default(enum node_type node) install_element(node, &config_end_cmd); install_element(node, &config_help_cmd); install_element(node, &config_list_cmd); + install_element(node, &show_cli_graph_cmd); install_element(node, &find_cmd); install_element(node, &config_write_cmd); diff --git a/lib/command.h b/lib/command.h index bb77812350..f18de3417c 100644 --- a/lib/command.h +++ b/lib/command.h @@ -146,6 +146,7 @@ enum node_type { }; extern vector cmdvec; +extern const struct message tokennames[]; extern const char *node_names[]; /* Node which has some commands and prompt string and configuration diff --git a/lib/command_graph.c b/lib/command_graph.c index 3e8261cb3e..4555d6eaeb 100644 --- a/lib/command_graph.c +++ b/lib/command_graph.c @@ -25,6 +25,8 @@ #include #include "command_graph.h" +#include "command.h" +#include "log.h" DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens") DEFINE_MTYPE_STATIC(LIB, CMD_DESC, "Command Token Text") @@ -457,3 +459,92 @@ void cmd_graph_names(struct graph *graph) cmd_node_names(start, NULL, NULL); } + +#ifndef BUILDING_CLIPPY + +void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf) +{ + static bool wasend; + + char nbuf[512]; + struct cmd_token *tok = gn->data; + const char *color; + + if (wasend == true) { + wasend = false; + return; + } + + if (tok->type == END_TKN) { + wasend = true; + return; + } + + snprintf(nbuf, sizeof(nbuf), " n%p [ shape=box, label=<", gn); + buffer_putstr(buf, nbuf); + snprintf(nbuf, sizeof(nbuf), "%s", + lookup_msg(tokennames, tok->type, NULL)); + buffer_putstr(buf, nbuf); + if (tok->attr == CMD_ATTR_DEPRECATED) + buffer_putstr(buf, " (d)"); + else if (tok->attr == CMD_ATTR_HIDDEN) + buffer_putstr(buf, " (h)"); + if (tok->text) { + if (tok->type == WORD_TKN) + snprintf( + nbuf, sizeof(nbuf), + "
\"%s\"", + tok->text); + else + snprintf(nbuf, sizeof(nbuf), "
%s", tok->text); + buffer_putstr(buf, nbuf); + } + + switch (tok->type) { + case START_TKN: + color = "#ccffcc"; + break; + case FORK_TKN: + color = "#aaddff"; + break; + case JOIN_TKN: + color = "#ddaaff"; + break; + case WORD_TKN: + color = "#ffffff"; + break; + default: + color = "#ffffff"; + break; + } + snprintf(nbuf, sizeof(nbuf), + ">, style = filled, fillcolor = \"%s\" ];\n", color); + buffer_putstr(buf, nbuf); + + for (unsigned int i = 0; i < vector_active(gn->to); i++) { + struct graph_node *adj = vector_slot(gn->to, i); + + if (((struct cmd_token *)adj->data)->type == END_TKN) { + snprintf(nbuf, sizeof(nbuf), " n%p -> end%p;\n", gn, + adj); + buffer_putstr(buf, nbuf); + snprintf( + nbuf, sizeof(nbuf), + " end%p [ shape=box, label=, style = filled, fillcolor = \"#ffddaa\" ];\n", + adj); + } else + snprintf(nbuf, sizeof(nbuf), " n%p -> n%p;\n", gn, + adj); + + buffer_putstr(buf, nbuf); + } +} + +char *cmd_graph_dump_dot(struct graph *cmdgraph) +{ + struct graph_node *start = vector_slot(cmdgraph->nodes, 0); + + return graph_dump_dot(cmdgraph, start, cmd_graph_node_print_cb); +} + +#endif /* BUILDING_CLIPPY */ diff --git a/lib/command_graph.h b/lib/command_graph.h index ec68e284ed..82d562694c 100644 --- a/lib/command_graph.h +++ b/lib/command_graph.h @@ -116,5 +116,21 @@ extern void cmd_graph_parse(struct graph *graph, struct cmd_element *cmd); extern void cmd_graph_names(struct graph *graph); extern void cmd_graph_merge(struct graph *old, struct graph *new, int direction); +/* + * Print callback for DOT dumping. + * + * See graph.h for more details. + */ +extern void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf); +/* + * Dump command graph to DOT. + * + * cmdgraph + * A command graph to dump + * + * Returns: + * String allocated with MTYPE_TMP representing this graph + */ +char *cmd_graph_dump_dot(struct graph *cmdgraph); #endif /* _FRR_COMMAND_GRAPH_H */ diff --git a/lib/grammar_sandbox.c b/lib/grammar_sandbox.c index 41dd57b7f1..51e7a3987e 100644 --- a/lib/grammar_sandbox.c +++ b/lib/grammar_sandbox.c @@ -37,9 +37,6 @@ DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc") void grammar_sandbox_init(void); void pretty_print_graph(struct vty *vty, struct graph_node *, int, int, struct graph_node **, size_t); -static void pretty_print_dot(FILE *ofd, unsigned opts, struct graph_node *start, - struct graph_node **stack, size_t stackpos, - struct graph_node **visited, size_t *visitpos); void init_cmdgraph(struct vty *, struct graph **); /** shim interface commands **/ @@ -274,23 +271,19 @@ DEFUN (grammar_test_dot, ".dot filename\n") { check_nodegraph(); - - struct graph_node *stack[CMD_ARGC_MAX]; - struct graph_node *visited[CMD_ARGC_MAX * CMD_ARGC_MAX]; - size_t vpos = 0; - FILE *ofd = fopen(argv[2]->arg, "w"); + if (!ofd) { vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno)); return CMD_SUCCESS; } - fprintf(ofd, - "digraph {\n graph [ rankdir = LR ];\n node [ fontname = \"Fira Mono\", fontsize = 9 ];\n\n"); - pretty_print_dot(ofd, 0, vector_slot(nodegraph->nodes, 0), stack, 0, - visited, &vpos); - fprintf(ofd, "}\n"); + char *dot = cmd_graph_dump_dot(nodegraph); + + fprintf(ofd, "%s", dot); fclose(ofd); + XFREE(MTYPE_TMP, dot); + return CMD_SUCCESS; } @@ -489,24 +482,6 @@ void grammar_sandbox_init(void) install_element(ENABLE_NODE, &grammar_access_cmd); } -#define item(x) { x, #x } -struct message tokennames[] = {item(WORD_TKN), // words - item(VARIABLE_TKN), // almost anything - item(RANGE_TKN), // integer range - item(IPV4_TKN), // IPV4 addresses - item(IPV4_PREFIX_TKN), // IPV4 network prefixes - item(IPV6_TKN), // IPV6 prefixes - item(IPV6_PREFIX_TKN), // IPV6 network prefixes - item(MAC_TKN), // MAC address - item(MAC_PREFIX_TKN), // MAC address w/ mask - - /* plumbing types */ - item(FORK_TKN), - item(JOIN_TKN), - item(START_TKN), // first token in line - item(END_TKN), // last token in line - {0}}; - /** * Pretty-prints a graph, assuming it is a tree. * @@ -571,89 +546,6 @@ void pretty_print_graph(struct vty *vty, struct graph_node *start, int level, vty_out(vty, "\n"); } -static void pretty_print_dot(FILE *ofd, unsigned opts, struct graph_node *start, - struct graph_node **stack, size_t stackpos, - struct graph_node **visited, size_t *visitpos) -{ - // print this node - char tokennum[32]; - struct cmd_token *tok = start->data; - const char *color; - - for (size_t i = 0; i < (*visitpos); i++) - if (visited[i] == start) - return; - visited[(*visitpos)++] = start; - if ((*visitpos) == CMD_ARGC_MAX * CMD_ARGC_MAX) - return; - - snprintf(tokennum, sizeof(tokennum), "%d?", tok->type); - fprintf(ofd, " n%p [ shape=box, label=<", start); - - fprintf(ofd, "%s", lookup_msg(tokennames, tok->type, NULL)); - if (tok->attr == CMD_ATTR_DEPRECATED) - fprintf(ofd, " (d)"); - else if (tok->attr == CMD_ATTR_HIDDEN) - fprintf(ofd, " (h)"); - if (tok->text) { - if (tok->type == WORD_TKN) - fprintf(ofd, - "
\"%s\"", - tok->text); - else - fprintf(ofd, "
%s", tok->text); - } - /* if (desc) - fprintf(ofd, " ?'%s'", tok->desc); */ - switch (tok->type) { - case START_TKN: - color = "#ccffcc"; - break; - case FORK_TKN: - color = "#aaddff"; - break; - case JOIN_TKN: - color = "#ddaaff"; - break; - case WORD_TKN: - color = "#ffffff"; - break; - default: - color = "#ffffff"; - break; - } - fprintf(ofd, ">, style = filled, fillcolor = \"%s\" ];\n", color); - - if (stackpos == CMD_ARGC_MAX) - return; - stack[stackpos++] = start; - - for (unsigned int i = 0; i < vector_active(start->to); i++) { - struct graph_node *adj = vector_slot(start->to, i); - // if this node is a vararg, just print * - if (adj == start) { - fprintf(ofd, " n%p -> n%p;\n", start, start); - } else if (((struct cmd_token *)adj->data)->type == END_TKN) { - // struct cmd_token *et = adj->data; - fprintf(ofd, " n%p -> end%p;\n", start, adj); - fprintf(ofd, - " end%p [ shape=box, label=, style = filled, fillcolor = \"#ffddaa\" ];\n", - adj); - } else { - fprintf(ofd, " n%p -> n%p;\n", start, adj); - size_t k; - for (k = 0; k < stackpos; k++) - if (stack[k] == adj) - break; - if (k == stackpos) { - pretty_print_dot(ofd, opts, adj, stack, - stackpos, visited, visitpos); - } - } - } -} - - /** stuff that should go in command.c + command.h */ void init_cmdgraph(struct vty *vty, struct graph **graph) { diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index ec212233f6..50d6ee4589 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -43,6 +43,7 @@ #include "ns.h" #include "vrf.h" #include "libfrr.h" +#include "command_graph.h" DEFINE_MTYPE_STATIC(MVTYSH, VTYSH_CMD, "Vtysh cmd copy") @@ -2957,10 +2958,26 @@ DEFUN(find, return CMD_SUCCESS; } +DEFUN_HIDDEN(show_cli_graph_vtysh, + show_cli_graph_vtysh_cmd, + "show cli graph", + SHOW_STR + "CLI reflection\n" + "Dump current command space as DOT graph\n") +{ + struct cmd_node *cn = vector_slot(cmdvec, vty->node); + char *dot = cmd_graph_dump_dot(cn->cmdgraph); + + vty_out(vty, "%s\n", dot); + XFREE(MTYPE_TMP, dot); + return CMD_SUCCESS; +} + static void vtysh_install_default(enum node_type node) { install_element(node, &config_list_cmd); install_element(node, &find_cmd); + install_element(node, &show_cli_graph_vtysh_cmd); install_element(node, &vtysh_output_file_cmd); install_element(node, &no_vtysh_output_file_cmd); }