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:
David Lamparter 2021-08-26 11:43:08 +02:00
parent 3e324ff419
commit 90c8406c20
12 changed files with 181 additions and 9 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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];}
%%

View file

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

View file

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

View file

@ -210,8 +210,8 @@ static PyObject *graph_to_pyobj(struct wrap_graph *wgraph,
/* plumbing types */
item(FORK_TKN) item(JOIN_TKN) item(START_TKN)
item(END_TKN) default
: wrap->type = "???";
item(END_TKN) item(NEG_ONLY_TKN) default
: wrap->type = "???";
}
wrap->deprecated = (tok->attr == CMD_ATTR_DEPRECATED);

View file

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

View file

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

View file

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

View file

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