mirror of
https://git.proxmox.com/git/mirror_frr
synced 2026-01-26 22:09:35 +00:00
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:
parent
c09c46ae3c
commit
70d44c5cd4
@ -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);
|
||||
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
32
lib/if.c
32
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
|
||||
|
||||
36
lib/plist.c
36
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 ();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
26
lib/vty.c
26
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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user