From: David Lamparter Date: Sat, 19 Nov 2016 10:57:08 +0000 (+0100) Subject: lib: cli: autocomplete variables X-Git-Tag: reindent-master-before~165 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=70d44c5cd4f28f9d08a24f519d859885d92e2a13;p=mirror%2Ffrr.git lib: cli: autocomplete variables Shows known values in the appropriate naming domain when the user hits or . This patch only works in the telnet CLI, the next patch adds vtysh support. Included completions: - interface names - route-map names - prefix-list names Signed-off-by: David Lamparter --- diff --git a/lib/command.c b/lib/command.c index 0e19a3dfee..5b4c63fa95 100644 --- a/lib/command.c +++ b/lib/command.c @@ -46,6 +46,7 @@ DEFINE_MTYPE( LIB, HOST, "Host config") DEFINE_MTYPE( LIB, STRVEC, "String vector") +DEFINE_MTYPE( LIB, COMPLETION, "Completion item") /* Command vector which includes some level of command lists. Normally each daemon maintains each own cmdvec. */ @@ -678,6 +679,54 @@ cmd_describe_command (vector vline, struct vty *vty, int *status) return cmd_complete_command_real (vline, vty, status); } +static struct list *varhandlers = NULL; + +void +cmd_variable_complete (struct cmd_token *token, const char *arg, vector comps) +{ + struct listnode *ln; + const struct cmd_variable_handler *cvh; + size_t i, argsz; + vector tmpcomps; + + tmpcomps = arg ? vector_init (VECTOR_MIN_SIZE) : comps; + + for (ALL_LIST_ELEMENTS_RO(varhandlers, ln, cvh)) + { + if (cvh->tokenname && strcmp(cvh->tokenname, token->text)) + continue; + if (cvh->varname && (!token->varname || strcmp(cvh->varname, token->varname))) + continue; + cvh->completions(tmpcomps, token); + break; + } + + if (!arg) + return; + + argsz = strlen(arg); + for (i = vector_active(tmpcomps); i; i--) + { + char *item = vector_slot(tmpcomps, i - 1); + if (strlen(item) >= argsz + && !strncmp(item, arg, argsz)) + vector_set(comps, item); + else + XFREE(MTYPE_COMPLETION, item); + } + vector_free(tmpcomps); +} + +void +cmd_variable_handler_register (const struct cmd_variable_handler *cvh) +{ + if (!varhandlers) + return; + + for (; cvh->completions; cvh++) + listnode_add(varhandlers, (void *)cvh); +} + /** * Generate possible tab-completions for the given input. This function only * returns results that would result in a valid command if used as Readline @@ -719,7 +768,12 @@ cmd_complete_command (vector vline, struct vty *vty, int *status) { struct cmd_token *token = vector_slot (initial_comps, i); if (token->type == WORD_TKN) - vector_set (comps, token); + vector_set (comps, XSTRDUP (MTYPE_COMPLETION, token->text)); + else if (IS_VARYING_TOKEN(token->type)) + { + const char *ref = vector_lookup(vline, vector_active (vline) - 1); + cmd_variable_complete (token, ref, comps); + } } vector_free (initial_comps); @@ -741,9 +795,7 @@ cmd_complete_command (vector vline, struct vty *vty, int *status) unsigned int i; for (i = 0; i < vector_active (comps); i++) { - struct cmd_token *token = vector_slot (comps, i); - ret[i] = XSTRDUP (MTYPE_TMP, token->text); - vector_unset (comps, i); + ret[i] = vector_slot (comps, i); } // set the last element to NULL, because this array is used in // a Readline completion_generator function which expects NULL @@ -2394,6 +2446,8 @@ cmd_init (int terminal) { qobj_init (); + varhandlers = list_new (); + /* Allocate initial top vector of commands. */ cmdvec = vector_init (VECTOR_MIN_SIZE); diff --git a/lib/command.h b/lib/command.h index 4531ec9a17..1aca8b4ae1 100644 --- a/lib/command.h +++ b/lib/command.h @@ -32,6 +32,7 @@ #include "command_graph.h" DECLARE_MTYPE(HOST) +DECLARE_MTYPE(COMPLETION) /* for test-commands.c */ DECLARE_MTYPE(STRVEC) @@ -391,4 +392,12 @@ extern int cmd_banner_motd_file (const char *); /* struct host global, ick */ extern struct host host; +struct cmd_variable_handler { + const char *tokenname, *varname; + void (*completions)(vector out, struct cmd_token *token); +}; + +extern void cmd_variable_complete (struct cmd_token *token, const char *arg, vector comps); +extern void cmd_variable_handler_register (const struct cmd_variable_handler *cvh); + #endif /* _ZEBRA_COMMAND_H */ diff --git a/lib/command_graph.h b/lib/command_graph.h index 11cea9bd42..595508d5ce 100644 --- a/lib/command_graph.h +++ b/lib/command_graph.h @@ -61,6 +61,8 @@ enum cmd_token_type SPECIAL_TKN = FORK_TKN, }; +#define IS_VARYING_TOKEN(x) ((x) >= VARIABLE_TKN && (x) < FORK_TKN) + /* Command attributes */ enum { diff --git a/lib/if.c b/lib/if.c index ecb7463168..3fbf2df6a0 100644 --- a/lib/if.c +++ b/lib/if.c @@ -1126,6 +1126,36 @@ ifaddr_ipv4_lookup (struct in_addr *addr, ifindex_t ifindex) } #endif /* ifaddr_ipv4_table */ +static void if_autocomplete(vector comps, struct cmd_token *token) +{ + struct interface *ifp; + struct listnode *ln; + struct vrf *vrf = NULL; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) + { + for (ALL_LIST_ELEMENTS_RO(vrf->iflist, ln, ifp)) + vector_set (comps, XSTRDUP (MTYPE_COMPLETION, ifp->name)); + } + +} + +static const struct cmd_variable_handler if_var_handlers[] = { + { + /* "interface NAME" */ + .varname = "interface", + .completions = if_autocomplete + }, { + .tokenname = "IFNAME", + .completions = if_autocomplete + }, { + .tokenname = "INTERFACE", + .completions = if_autocomplete + }, { + .completions = NULL + } +}; + /* Initialize interface list. */ void if_init (struct list **intf_list) @@ -1136,6 +1166,8 @@ if_init (struct list **intf_list) #endif /* ifaddr_ipv4_table */ (*intf_list)->cmp = (int (*)(void *, void *))if_cmp_func; + + cmd_variable_handler_register(if_var_handlers); } void diff --git a/lib/plist.c b/lib/plist.c index 3714969696..8f59c0c058 100644 --- a/lib/plist.c +++ b/lib/plist.c @@ -3156,6 +3156,40 @@ config_write_prefix_ipv4 (struct vty *vty) return config_write_prefix_afi (AFI_IP, vty); } +static void +plist_autocomplete_afi (afi_t afi, vector comps, struct cmd_token *token) +{ + struct prefix_list *plist; + struct prefix_master *master; + + master = prefix_master_get (afi, 0); + if (master == NULL) + return; + + for (plist = master->str.head; plist; plist = plist->next) + vector_set (comps, XSTRDUP (MTYPE_COMPLETION, plist->name)); + for (plist = master->num.head; plist; plist = plist->next) + vector_set (comps, XSTRDUP (MTYPE_COMPLETION, plist->name)); +} + +static void +plist_autocomplete(vector comps, struct cmd_token *token) +{ + plist_autocomplete_afi (AFI_IP, comps, token); + plist_autocomplete_afi (AFI_IP6, comps, token); +} + +static const struct cmd_variable_handler plist_var_handlers[] = { + { + /* "prefix-list WORD" */ + .varname = "prefix_list", + .completions = plist_autocomplete + }, { + .completions = NULL + } +}; + + static void prefix_list_init_ipv4 (void) { @@ -3275,6 +3309,8 @@ prefix_list_init_ipv6 (void) void prefix_list_init () { + cmd_variable_handler_register(plist_var_handlers); + prefix_list_init_ipv4 (); prefix_list_init_ipv6 (); } diff --git a/lib/routemap.c b/lib/routemap.c index cd34ffaae5..482155987d 100644 --- a/lib/routemap.c +++ b/lib/routemap.c @@ -2001,7 +2001,7 @@ DEFUN (match_interface, DEFUN (no_match_interface, no_match_interface_cmd, - "no match interface [INTERFACE]", + "no match interface [WORD]", NO_STR MATCH_STR "Match first hop interface of route\n" @@ -2958,6 +2958,30 @@ route_map_finish (void) route_map_master_hash = NULL; } +static void rmap_autocomplete(vector comps, struct cmd_token *token) +{ + struct route_map *map; + + for (map = route_map_master.head; map; map = map->next) + vector_set (comps, XSTRDUP (MTYPE_COMPLETION, map->name)); +} + +static const struct cmd_variable_handler rmap_var_handlers[] = { + { + /* "route-map WORD" */ + .varname = "route_map", + .completions = rmap_autocomplete + }, { + .tokenname = "ROUTEMAP_NAME", + .completions = rmap_autocomplete + }, { + .tokenname = "RMAP_NAME", + .completions = rmap_autocomplete + }, { + .completions = NULL + } +}; + /* Initialization of route map vector. */ void route_map_init (void) @@ -2973,6 +2997,8 @@ route_map_init (void) route_map_dep_hash[i] = hash_create(route_map_dep_hash_make_key, route_map_dep_hash_cmp); + cmd_variable_handler_register(rmap_var_handlers); + /* Install route map top node. */ install_node (&rmap_node, route_map_config_write); diff --git a/lib/vty.c b/lib/vty.c index a8e54a57de..83dc0106fb 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -955,14 +955,14 @@ vty_complete_command (struct vty *vty) vty_backward_pure_word (vty); vty_insert_word_overwrite (vty, matched[0]); vty_self_insert (vty, ' '); - XFREE (MTYPE_TMP, matched[0]); + XFREE (MTYPE_COMPLETION, matched[0]); break; case CMD_COMPLETE_MATCH: vty_prompt (vty); vty_redraw_line (vty); vty_backward_pure_word (vty); vty_insert_word_overwrite (vty, matched[0]); - XFREE (MTYPE_TMP, matched[0]); + XFREE (MTYPE_COMPLETION, matched[0]); break; case CMD_COMPLETE_LIST_MATCH: for (i = 0; matched[i] != NULL; i++) @@ -970,7 +970,7 @@ vty_complete_command (struct vty *vty) if (i != 0 && ((i % 6) == 0)) vty_out (vty, "%s", VTY_NEWLINE); vty_out (vty, "%-10s ", matched[i]); - XFREE (MTYPE_TMP, matched[i]); + XFREE (MTYPE_COMPLETION, matched[i]); } vty_out (vty, "%s", VTY_NEWLINE); @@ -1109,6 +1109,26 @@ vty_describe_command (struct vty *vty) else vty_describe_fold (vty, width, desc_width, token); + if (IS_VARYING_TOKEN(token->type)) + { + const char *ref = vector_slot(vline, vector_active(vline) - 1); + + vector varcomps = vector_init (VECTOR_MIN_SIZE); + cmd_variable_complete (token, ref, varcomps); + + if (vector_active(varcomps) > 0) + { + vty_out(vty, " "); + for (size_t j = 0; j < vector_active (varcomps); j++) + { + char *item = vector_slot (varcomps, j); + vty_out(vty, " %s", item); + XFREE(MTYPE_COMPLETION, item); + } + vty_out(vty, "%s", VTY_NEWLINE); + } + vector_free(varcomps); + } #if 0 vty_out (vty, " %-*s %s%s", width desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,