lib: cli: autocomplete variables

Shows known values in the appropriate naming domain when the user hits
<?> or <Tab>.  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 <equinox@opensourcerouting.org>
This commit is contained in:
David Lamparter 2016-11-19 11:57:08 +01:00 committed by Quentin Young
parent c09c46ae3c
commit 70d44c5cd4
7 changed files with 187 additions and 8 deletions

View File

@ -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);

View File

@ -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 */

View File

@ -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
{

View File

@ -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

View File

@ -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 ();
}

View File

@ -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);

View File

@ -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,