forked from Mirror/frr
lib: add ![...]
syntax for easy "no" forms
This allows defining a CLI command like this: `[no] some setting ![VALUE]` with VALUE being optional for the "no" form, but required for the positive form. It's just a `[...]` where the empty branch can only be taken for commands starting with `no`. Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
This commit is contained in:
parent
3e324ff419
commit
90c8406c20
|
@ -139,6 +139,7 @@ by the parser.
|
|||
selector: "<" `selector_seq_seq` ">" `varname_token`
|
||||
: "{" `selector_seq_seq` "}" `varname_token`
|
||||
: "[" `selector_seq_seq` "]" `varname_token`
|
||||
: "![" `selector_seq_seq` "]" `varname_token`
|
||||
selector_seq_seq: `selector_seq_seq` "|" `selector_token_seq`
|
||||
: `selector_token_seq`
|
||||
selector_token_seq: `selector_token_seq` `selector_token`
|
||||
|
@ -218,6 +219,10 @@ one-or-more selection and repetition.
|
|||
provide mutual exclusion. User input matches at most one option.
|
||||
- ``[square brackets]`` -- Contains sequences of tokens that can be omitted.
|
||||
``[<a|b>]`` can be shortened to ``[a|b]``.
|
||||
- ``![exclamation square brackets]`` -- same as ``[square brackets]``, but
|
||||
only allow skipping the contents if the command input starts with ``no``.
|
||||
(For cases where the positive command needs a parameter, but the parameter
|
||||
is optional for the negative case.)
|
||||
- ``{curly|braces}`` -- similar to angle brackets, but instead of mutual
|
||||
exclusion, curly braces indicate that one or more of the pipe-separated
|
||||
sequences may be provided in any order.
|
||||
|
|
|
@ -74,6 +74,7 @@ const struct message tokennames[] = {
|
|||
item(JOIN_TKN),
|
||||
item(START_TKN),
|
||||
item(END_TKN),
|
||||
item(NEG_ONLY_TKN),
|
||||
{0},
|
||||
};
|
||||
/* clang-format on */
|
||||
|
|
|
@ -388,6 +388,7 @@ static void cmd_node_names(struct graph_node *gn, struct graph_node *join,
|
|||
|
||||
case START_TKN:
|
||||
case JOIN_TKN:
|
||||
case NEG_ONLY_TKN:
|
||||
/* "<foo|bar> WORD" -> word is not "bar" or "foo" */
|
||||
prevname = NULL;
|
||||
break;
|
||||
|
@ -511,6 +512,9 @@ void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf)
|
|||
case JOIN_TKN:
|
||||
color = "#ddaaff";
|
||||
break;
|
||||
case NEG_ONLY_TKN:
|
||||
color = "#ffddaa";
|
||||
break;
|
||||
case WORD_TKN:
|
||||
color = "#ffffff";
|
||||
break;
|
||||
|
|
|
@ -64,6 +64,7 @@ enum cmd_token_type {
|
|||
JOIN_TKN, // marks subgraph end
|
||||
START_TKN, // first token in line
|
||||
END_TKN, // last token in line
|
||||
NEG_ONLY_TKN, // filter token, match if "no ..." command
|
||||
|
||||
SPECIAL_TKN = FORK_TKN,
|
||||
};
|
||||
|
|
|
@ -82,6 +82,7 @@ RANGE \({NUMBER}[ ]?\-[ ]?{NUMBER}\)
|
|||
{VARIABLE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return VARIABLE;}
|
||||
{WORD} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return WORD;}
|
||||
{RANGE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return RANGE;}
|
||||
!\[ {yylval->string = NULL; return EXCL_BRACKET;}
|
||||
. {return yytext[0];}
|
||||
%%
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack");
|
|||
|
||||
/* matcher helper prototypes */
|
||||
static int add_nexthops(struct list *, struct graph_node *,
|
||||
struct graph_node **, size_t);
|
||||
struct graph_node **, size_t, bool);
|
||||
|
||||
static enum matcher_rv command_match_r(struct graph_node *, vector,
|
||||
unsigned int, struct graph_node **,
|
||||
|
@ -79,6 +79,13 @@ static enum match_type match_variable(struct cmd_token *, const char *);
|
|||
|
||||
static enum match_type match_mac(const char *, bool);
|
||||
|
||||
static bool is_neg(vector vline, size_t idx)
|
||||
{
|
||||
if (idx >= vector_active(vline))
|
||||
return false;
|
||||
return !strcmp(vector_slot(vline, idx), "no");
|
||||
}
|
||||
|
||||
enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
|
||||
struct list **argv, const struct cmd_element **el)
|
||||
{
|
||||
|
@ -248,7 +255,7 @@ static enum matcher_rv command_match_r(struct graph_node *start, vector vline,
|
|||
|
||||
// get all possible nexthops
|
||||
struct list *next = list_new();
|
||||
add_nexthops(next, start, NULL, 0);
|
||||
add_nexthops(next, start, NULL, 0, is_neg(vline, 1));
|
||||
|
||||
// determine the best match
|
||||
for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) {
|
||||
|
@ -349,6 +356,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline,
|
|||
{
|
||||
// pointer to next input token to match
|
||||
char *input_token;
|
||||
bool neg = is_neg(vline, 0);
|
||||
|
||||
struct list *
|
||||
current =
|
||||
|
@ -363,7 +371,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline,
|
|||
|
||||
// add all children of start node to list
|
||||
struct graph_node *start = vector_slot(graph->nodes, 0);
|
||||
add_nexthops(next, start, &start, 0);
|
||||
add_nexthops(next, start, &start, 0, neg);
|
||||
|
||||
unsigned int idx;
|
||||
for (idx = 0; idx < vector_active(vline) && next->count > 0; idx++) {
|
||||
|
@ -428,7 +436,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline,
|
|||
listnode_add(next, newstack);
|
||||
} else if (matchtype >= minmatch)
|
||||
add_nexthops(next, gstack[0], gstack,
|
||||
idx + 1);
|
||||
idx + 1, neg);
|
||||
break;
|
||||
default:
|
||||
trace_matcher("no_match\n");
|
||||
|
@ -478,7 +486,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline,
|
|||
* output, instead of direct node pointers!
|
||||
*/
|
||||
static int add_nexthops(struct list *list, struct graph_node *node,
|
||||
struct graph_node **stack, size_t stackpos)
|
||||
struct graph_node **stack, size_t stackpos, bool neg)
|
||||
{
|
||||
int added = 0;
|
||||
struct graph_node *child;
|
||||
|
@ -494,8 +502,13 @@ static int add_nexthops(struct list *list, struct graph_node *node,
|
|||
if (j != stackpos)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token->type == NEG_ONLY_TKN && !neg)
|
||||
continue;
|
||||
|
||||
if (token->type >= SPECIAL_TKN && token->type != END_TKN) {
|
||||
added += add_nexthops(list, child, stack, stackpos);
|
||||
added +=
|
||||
add_nexthops(list, child, stack, stackpos, neg);
|
||||
} else {
|
||||
if (stack) {
|
||||
nextstack = XMALLOC(
|
||||
|
|
|
@ -105,6 +105,9 @@
|
|||
%token <string> MAC
|
||||
%token <string> MAC_PREFIX
|
||||
|
||||
/* special syntax, value is irrelevant */
|
||||
%token <string> EXCL_BRACKET
|
||||
|
||||
/* union types for parsed rules */
|
||||
%type <node> start
|
||||
%type <node> literal_token
|
||||
|
@ -372,6 +375,19 @@ selector: '[' selector_seq_seq ']' varname_token
|
|||
}
|
||||
;
|
||||
|
||||
/* ![option] productions */
|
||||
selector: EXCL_BRACKET selector_seq_seq ']' varname_token
|
||||
{
|
||||
struct graph_node *neg_only = new_token_node (ctx, NEG_ONLY_TKN, NULL, NULL);
|
||||
|
||||
$$ = $2;
|
||||
graph_add_edge ($$.start, neg_only);
|
||||
graph_add_edge (neg_only, $$.end);
|
||||
cmd_token_varname_set ($2.end->data, $4);
|
||||
XFREE (MTYPE_LEX, $4);
|
||||
}
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
#undef scanner
|
||||
|
|
|
@ -210,7 +210,7 @@ static PyObject *graph_to_pyobj(struct wrap_graph *wgraph,
|
|||
|
||||
/* plumbing types */
|
||||
item(FORK_TKN) item(JOIN_TKN) item(START_TKN)
|
||||
item(END_TKN) default
|
||||
item(END_TKN) item(NEG_ONLY_TKN) default
|
||||
: wrap->type = "???";
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,8 @@ DUMMY_DEFUN(cmd12, "alt a A.B.C.D");
|
|||
DUMMY_DEFUN(cmd13, "alt a X:X::X:X");
|
||||
DUMMY_DEFUN(cmd14,
|
||||
"pat g { foo A.B.C.D$foo|foo|bar X:X::X:X$bar| baz } [final]");
|
||||
DUMMY_DEFUN(cmd15, "no pat g ![ WORD ]");
|
||||
DUMMY_DEFUN(cmd16, "[no] pat h {foo ![A.B.C.D$foo]|bar X:X::X:X$bar} final");
|
||||
|
||||
#include "tests/lib/cli/test_cli_clippy.c"
|
||||
|
||||
|
@ -81,5 +83,7 @@ void test_init(int argc, char **argv)
|
|||
install_element(ENABLE_NODE, &cmd13_cmd);
|
||||
}
|
||||
install_element(ENABLE_NODE, &cmd14_cmd);
|
||||
install_element(ENABLE_NODE, &cmd15_cmd);
|
||||
install_element(ENABLE_NODE, &cmd16_cmd);
|
||||
install_element(ENABLE_NODE, &magic_test_cmd);
|
||||
}
|
||||
|
|
|
@ -74,6 +74,23 @@ pat f
|
|||
pat f foo
|
||||
pat f key
|
||||
|
||||
no pat g
|
||||
no pat g test
|
||||
no pat g test more
|
||||
|
||||
pat h foo ?1.2.3.4 final
|
||||
no pat h foo ?1.2.3.4 final
|
||||
pat h foo final
|
||||
no pat h foo final
|
||||
pat h bar final
|
||||
no pat h bar final
|
||||
pat h bar 1::2 final
|
||||
no pat h bar 1::2 final
|
||||
pat h bar 1::2 foo final
|
||||
no pat h bar 1::2 foo final
|
||||
pat h bar 1::2 foo 1.2.3.4 final
|
||||
no pat h bar 1::2 foo 1.2.3.4 final
|
||||
|
||||
alt a a?b
|
||||
alt a 1 .2?.3.4
|
||||
alt a 1 :2? ::?3
|
||||
|
|
|
@ -147,7 +147,7 @@ test# papat
|
|||
% Command incomplete.
|
||||
test# pat
|
||||
a b c d e f
|
||||
g
|
||||
g h
|
||||
test# pat
|
||||
% Command incomplete.
|
||||
test#
|
||||
|
@ -263,6 +263,100 @@ cmd10 with 3 args.
|
|||
[01] f@(null): f
|
||||
[02] key@(null): key
|
||||
test#
|
||||
test# no pat g
|
||||
cmd15 with 3 args.
|
||||
[00] no@(null): no
|
||||
[01] pat@(null): pat
|
||||
[02] g@(null): g
|
||||
test# no pat g test
|
||||
cmd15 with 4 args.
|
||||
[00] no@(null): no
|
||||
[01] pat@(null): pat
|
||||
[02] g@(null): g
|
||||
[03] WORD@g: test
|
||||
test# no pat g test more
|
||||
% [NONE] Unknown command: no pat g test more
|
||||
test#
|
||||
test# pat h foo
|
||||
A.B.C.D 04
|
||||
test# pat h foo 1.2.3.4 final
|
||||
cmd16 with 5 args.
|
||||
[00] pat@(null): pat
|
||||
[01] h@(null): h
|
||||
[02] foo@(null): foo
|
||||
[03] A.B.C.D@foo: 1.2.3.4
|
||||
[04] final@(null): final
|
||||
test# no pat h foo
|
||||
A.B.C.D 04
|
||||
bar 05
|
||||
final 07
|
||||
test# no pat h foo 1.2.3.4 final
|
||||
cmd16 with 6 args.
|
||||
[00] no@no: no
|
||||
[01] pat@(null): pat
|
||||
[02] h@(null): h
|
||||
[03] foo@(null): foo
|
||||
[04] A.B.C.D@foo: 1.2.3.4
|
||||
[05] final@(null): final
|
||||
test# pat h foo final
|
||||
% [NONE] Unknown command: pat h foo final
|
||||
test# no pat h foo final
|
||||
cmd16 with 5 args.
|
||||
[00] no@no: no
|
||||
[01] pat@(null): pat
|
||||
[02] h@(null): h
|
||||
[03] foo@(null): foo
|
||||
[04] final@(null): final
|
||||
test# pat h bar final
|
||||
% [NONE] Unknown command: pat h bar final
|
||||
test# no pat h bar final
|
||||
% [NONE] Unknown command: no pat h bar final
|
||||
test# pat h bar 1::2 final
|
||||
cmd16 with 5 args.
|
||||
[00] pat@(null): pat
|
||||
[01] h@(null): h
|
||||
[02] bar@(null): bar
|
||||
[03] X:X::X:X@bar: 1::2
|
||||
[04] final@(null): final
|
||||
test# no pat h bar 1::2 final
|
||||
cmd16 with 6 args.
|
||||
[00] no@no: no
|
||||
[01] pat@(null): pat
|
||||
[02] h@(null): h
|
||||
[03] bar@(null): bar
|
||||
[04] X:X::X:X@bar: 1::2
|
||||
[05] final@(null): final
|
||||
test# pat h bar 1::2 foo final
|
||||
% [NONE] Unknown command: pat h bar 1::2 foo final
|
||||
test# no pat h bar 1::2 foo final
|
||||
cmd16 with 7 args.
|
||||
[00] no@no: no
|
||||
[01] pat@(null): pat
|
||||
[02] h@(null): h
|
||||
[03] bar@(null): bar
|
||||
[04] X:X::X:X@bar: 1::2
|
||||
[05] foo@(null): foo
|
||||
[06] final@(null): final
|
||||
test# pat h bar 1::2 foo 1.2.3.4 final
|
||||
cmd16 with 7 args.
|
||||
[00] pat@(null): pat
|
||||
[01] h@(null): h
|
||||
[02] bar@(null): bar
|
||||
[03] X:X::X:X@bar: 1::2
|
||||
[04] foo@(null): foo
|
||||
[05] A.B.C.D@foo: 1.2.3.4
|
||||
[06] final@(null): final
|
||||
test# no pat h bar 1::2 foo 1.2.3.4 final
|
||||
cmd16 with 8 args.
|
||||
[00] no@no: no
|
||||
[01] pat@(null): pat
|
||||
[02] h@(null): h
|
||||
[03] bar@(null): bar
|
||||
[04] X:X::X:X@bar: 1::2
|
||||
[05] foo@(null): foo
|
||||
[06] A.B.C.D@foo: 1.2.3.4
|
||||
[07] final@(null): final
|
||||
test#
|
||||
test# alt a
|
||||
test# alt a a
|
||||
WORD 02
|
||||
|
|
|
@ -61,9 +61,22 @@ void permute(struct graph_node *start)
|
|||
struct cmd_token *stok = start->data;
|
||||
struct graph_node *gnn;
|
||||
struct listnode *ln;
|
||||
bool is_neg = false;
|
||||
|
||||
// recursive dfs
|
||||
listnode_add(position, start);
|
||||
|
||||
for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) {
|
||||
struct cmd_token *tok = gnn->data;
|
||||
|
||||
if (tok->type == WORD_TKN && !strcmp(tok->text, "no")) {
|
||||
is_neg = true;
|
||||
break;
|
||||
}
|
||||
if (tok->type < SPECIAL_TKN)
|
||||
break;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < vector_active(start->to); i++) {
|
||||
struct graph_node *gn = vector_slot(start->to, i);
|
||||
struct cmd_token *tok = gn->data;
|
||||
|
@ -82,6 +95,9 @@ void permute(struct graph_node *start)
|
|||
fprintf(stdout, "\n");
|
||||
} else {
|
||||
bool skip = false;
|
||||
|
||||
if (tok->type == NEG_ONLY_TKN && !is_neg)
|
||||
continue;
|
||||
if (stok->type == FORK_TKN && tok->type != FORK_TKN)
|
||||
for (ALL_LIST_ELEMENTS_RO(position, ln, gnn))
|
||||
if (gnn == gn) {
|
||||
|
|
Loading…
Reference in a new issue