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: "<" `selector_seq_seq` ">" `varname_token`
|
||||||
: "{" `selector_seq_seq` "}" `varname_token`
|
: "{" `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_seq_seq: `selector_seq_seq` "|" `selector_token_seq`
|
||||||
: `selector_token_seq`
|
: `selector_token_seq`
|
||||||
selector_token_seq: `selector_token_seq` `selector_token`
|
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.
|
provide mutual exclusion. User input matches at most one option.
|
||||||
- ``[square brackets]`` -- Contains sequences of tokens that can be omitted.
|
- ``[square brackets]`` -- Contains sequences of tokens that can be omitted.
|
||||||
``[<a|b>]`` can be shortened to ``[a|b]``.
|
``[<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
|
- ``{curly|braces}`` -- similar to angle brackets, but instead of mutual
|
||||||
exclusion, curly braces indicate that one or more of the pipe-separated
|
exclusion, curly braces indicate that one or more of the pipe-separated
|
||||||
sequences may be provided in any order.
|
sequences may be provided in any order.
|
||||||
|
|
|
@ -74,6 +74,7 @@ const struct message tokennames[] = {
|
||||||
item(JOIN_TKN),
|
item(JOIN_TKN),
|
||||||
item(START_TKN),
|
item(START_TKN),
|
||||||
item(END_TKN),
|
item(END_TKN),
|
||||||
|
item(NEG_ONLY_TKN),
|
||||||
{0},
|
{0},
|
||||||
};
|
};
|
||||||
/* clang-format on */
|
/* clang-format on */
|
||||||
|
|
|
@ -388,6 +388,7 @@ static void cmd_node_names(struct graph_node *gn, struct graph_node *join,
|
||||||
|
|
||||||
case START_TKN:
|
case START_TKN:
|
||||||
case JOIN_TKN:
|
case JOIN_TKN:
|
||||||
|
case NEG_ONLY_TKN:
|
||||||
/* "<foo|bar> WORD" -> word is not "bar" or "foo" */
|
/* "<foo|bar> WORD" -> word is not "bar" or "foo" */
|
||||||
prevname = NULL;
|
prevname = NULL;
|
||||||
break;
|
break;
|
||||||
|
@ -511,6 +512,9 @@ void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf)
|
||||||
case JOIN_TKN:
|
case JOIN_TKN:
|
||||||
color = "#ddaaff";
|
color = "#ddaaff";
|
||||||
break;
|
break;
|
||||||
|
case NEG_ONLY_TKN:
|
||||||
|
color = "#ffddaa";
|
||||||
|
break;
|
||||||
case WORD_TKN:
|
case WORD_TKN:
|
||||||
color = "#ffffff";
|
color = "#ffffff";
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -64,6 +64,7 @@ enum cmd_token_type {
|
||||||
JOIN_TKN, // marks subgraph end
|
JOIN_TKN, // marks subgraph end
|
||||||
START_TKN, // first token in line
|
START_TKN, // first token in line
|
||||||
END_TKN, // last token in line
|
END_TKN, // last token in line
|
||||||
|
NEG_ONLY_TKN, // filter token, match if "no ..." command
|
||||||
|
|
||||||
SPECIAL_TKN = FORK_TKN,
|
SPECIAL_TKN = FORK_TKN,
|
||||||
};
|
};
|
||||||
|
|
|
@ -82,6 +82,7 @@ RANGE \({NUMBER}[ ]?\-[ ]?{NUMBER}\)
|
||||||
{VARIABLE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return VARIABLE;}
|
{VARIABLE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return VARIABLE;}
|
||||||
{WORD} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return WORD;}
|
{WORD} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return WORD;}
|
||||||
{RANGE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return RANGE;}
|
{RANGE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return RANGE;}
|
||||||
|
!\[ {yylval->string = NULL; return EXCL_BRACKET;}
|
||||||
. {return yytext[0];}
|
. {return yytext[0];}
|
||||||
%%
|
%%
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack");
|
||||||
|
|
||||||
/* matcher helper prototypes */
|
/* matcher helper prototypes */
|
||||||
static int add_nexthops(struct list *, struct graph_node *,
|
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,
|
static enum matcher_rv command_match_r(struct graph_node *, vector,
|
||||||
unsigned int, struct graph_node **,
|
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 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,
|
enum matcher_rv command_match(struct graph *cmdgraph, vector vline,
|
||||||
struct list **argv, const struct cmd_element **el)
|
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
|
// get all possible nexthops
|
||||||
struct list *next = list_new();
|
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
|
// determine the best match
|
||||||
for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) {
|
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
|
// pointer to next input token to match
|
||||||
char *input_token;
|
char *input_token;
|
||||||
|
bool neg = is_neg(vline, 0);
|
||||||
|
|
||||||
struct list *
|
struct list *
|
||||||
current =
|
current =
|
||||||
|
@ -363,7 +371,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline,
|
||||||
|
|
||||||
// add all children of start node to list
|
// add all children of start node to list
|
||||||
struct graph_node *start = vector_slot(graph->nodes, 0);
|
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;
|
unsigned int idx;
|
||||||
for (idx = 0; idx < vector_active(vline) && next->count > 0; 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);
|
listnode_add(next, newstack);
|
||||||
} else if (matchtype >= minmatch)
|
} else if (matchtype >= minmatch)
|
||||||
add_nexthops(next, gstack[0], gstack,
|
add_nexthops(next, gstack[0], gstack,
|
||||||
idx + 1);
|
idx + 1, neg);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
trace_matcher("no_match\n");
|
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!
|
* output, instead of direct node pointers!
|
||||||
*/
|
*/
|
||||||
static int add_nexthops(struct list *list, struct graph_node *node,
|
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;
|
int added = 0;
|
||||||
struct graph_node *child;
|
struct graph_node *child;
|
||||||
|
@ -494,8 +502,13 @@ static int add_nexthops(struct list *list, struct graph_node *node,
|
||||||
if (j != stackpos)
|
if (j != stackpos)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token->type == NEG_ONLY_TKN && !neg)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (token->type >= SPECIAL_TKN && token->type != END_TKN) {
|
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 {
|
} else {
|
||||||
if (stack) {
|
if (stack) {
|
||||||
nextstack = XMALLOC(
|
nextstack = XMALLOC(
|
||||||
|
|
|
@ -105,6 +105,9 @@
|
||||||
%token <string> MAC
|
%token <string> MAC
|
||||||
%token <string> MAC_PREFIX
|
%token <string> MAC_PREFIX
|
||||||
|
|
||||||
|
/* special syntax, value is irrelevant */
|
||||||
|
%token <string> EXCL_BRACKET
|
||||||
|
|
||||||
/* union types for parsed rules */
|
/* union types for parsed rules */
|
||||||
%type <node> start
|
%type <node> start
|
||||||
%type <node> literal_token
|
%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
|
#undef scanner
|
||||||
|
|
|
@ -210,8 +210,8 @@ static PyObject *graph_to_pyobj(struct wrap_graph *wgraph,
|
||||||
|
|
||||||
/* plumbing types */
|
/* plumbing types */
|
||||||
item(FORK_TKN) item(JOIN_TKN) item(START_TKN)
|
item(FORK_TKN) item(JOIN_TKN) item(START_TKN)
|
||||||
item(END_TKN) default
|
item(END_TKN) item(NEG_ONLY_TKN) default
|
||||||
: wrap->type = "???";
|
: wrap->type = "???";
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap->deprecated = (tok->attr == CMD_ATTR_DEPRECATED);
|
wrap->deprecated = (tok->attr == CMD_ATTR_DEPRECATED);
|
||||||
|
|
|
@ -40,6 +40,8 @@ DUMMY_DEFUN(cmd12, "alt a A.B.C.D");
|
||||||
DUMMY_DEFUN(cmd13, "alt a X:X::X:X");
|
DUMMY_DEFUN(cmd13, "alt a X:X::X:X");
|
||||||
DUMMY_DEFUN(cmd14,
|
DUMMY_DEFUN(cmd14,
|
||||||
"pat g { foo A.B.C.D$foo|foo|bar X:X::X:X$bar| baz } [final]");
|
"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"
|
#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, &cmd13_cmd);
|
||||||
}
|
}
|
||||||
install_element(ENABLE_NODE, &cmd14_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);
|
install_element(ENABLE_NODE, &magic_test_cmd);
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,23 @@ pat f
|
||||||
pat f foo
|
pat f foo
|
||||||
pat f key
|
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 a?b
|
||||||
alt a 1 .2?.3.4
|
alt a 1 .2?.3.4
|
||||||
alt a 1 :2? ::?3
|
alt a 1 :2? ::?3
|
||||||
|
|
|
@ -147,7 +147,7 @@ test# papat
|
||||||
% Command incomplete.
|
% Command incomplete.
|
||||||
test# pat
|
test# pat
|
||||||
a b c d e f
|
a b c d e f
|
||||||
g
|
g h
|
||||||
test# pat
|
test# pat
|
||||||
% Command incomplete.
|
% Command incomplete.
|
||||||
test#
|
test#
|
||||||
|
@ -263,6 +263,100 @@ cmd10 with 3 args.
|
||||||
[01] f@(null): f
|
[01] f@(null): f
|
||||||
[02] key@(null): key
|
[02] key@(null): key
|
||||||
test#
|
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
|
||||||
test# alt a a
|
test# alt a a
|
||||||
WORD 02
|
WORD 02
|
||||||
|
|
|
@ -61,9 +61,22 @@ void permute(struct graph_node *start)
|
||||||
struct cmd_token *stok = start->data;
|
struct cmd_token *stok = start->data;
|
||||||
struct graph_node *gnn;
|
struct graph_node *gnn;
|
||||||
struct listnode *ln;
|
struct listnode *ln;
|
||||||
|
bool is_neg = false;
|
||||||
|
|
||||||
// recursive dfs
|
// recursive dfs
|
||||||
listnode_add(position, start);
|
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++) {
|
for (unsigned int i = 0; i < vector_active(start->to); i++) {
|
||||||
struct graph_node *gn = vector_slot(start->to, i);
|
struct graph_node *gn = vector_slot(start->to, i);
|
||||||
struct cmd_token *tok = gn->data;
|
struct cmd_token *tok = gn->data;
|
||||||
|
@ -82,6 +95,9 @@ void permute(struct graph_node *start)
|
||||||
fprintf(stdout, "\n");
|
fprintf(stdout, "\n");
|
||||||
} else {
|
} else {
|
||||||
bool skip = false;
|
bool skip = false;
|
||||||
|
|
||||||
|
if (tok->type == NEG_ONLY_TKN && !is_neg)
|
||||||
|
continue;
|
||||||
if (stok->type == FORK_TKN && tok->type != FORK_TKN)
|
if (stok->type == FORK_TKN && tok->type != FORK_TKN)
|
||||||
for (ALL_LIST_ELEMENTS_RO(position, ln, gnn))
|
for (ALL_LIST_ELEMENTS_RO(position, ln, gnn))
|
||||||
if (gnn == gn) {
|
if (gnn == gn) {
|
||||||
|
|
Loading…
Reference in a new issue