diff options
| -rw-r--r-- | doc/developer/cli.rst | 5 | ||||
| -rw-r--r-- | lib/command.c | 1 | ||||
| -rw-r--r-- | lib/command_graph.c | 4 | ||||
| -rw-r--r-- | lib/command_graph.h | 1 | ||||
| -rw-r--r-- | lib/command_lex.l | 1 | ||||
| -rw-r--r-- | lib/command_match.c | 25 | ||||
| -rw-r--r-- | lib/command_parse.y | 16 | ||||
| -rw-r--r-- | lib/command_py.c | 4 | ||||
| -rw-r--r-- | tests/lib/cli/test_cli.c | 4 | ||||
| -rw-r--r-- | tests/lib/cli/test_cli.in | 17 | ||||
| -rw-r--r-- | tests/lib/cli/test_cli.refout.in | 96 | ||||
| -rw-r--r-- | tools/permutations.c | 16 | 
12 files changed, 181 insertions, 9 deletions
diff --git a/doc/developer/cli.rst b/doc/developer/cli.rst index edabe61d92..a24a4ecb7e 100644 --- a/doc/developer/cli.rst +++ b/doc/developer/cli.rst @@ -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. diff --git a/lib/command.c b/lib/command.c index 422544b70b..3537585cc4 100644 --- a/lib/command.c +++ b/lib/command.c @@ -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 */ diff --git a/lib/command_graph.c b/lib/command_graph.c index c6c3840455..15c8302e63 100644 --- a/lib/command_graph.c +++ b/lib/command_graph.c @@ -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; diff --git a/lib/command_graph.h b/lib/command_graph.h index 2754dca67d..c20c9874c2 100644 --- a/lib/command_graph.h +++ b/lib/command_graph.h @@ -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,  }; diff --git a/lib/command_lex.l b/lib/command_lex.l index 9c096995f5..ec366ce7e1 100644 --- a/lib/command_lex.l +++ b/lib/command_lex.l @@ -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];}  %% diff --git a/lib/command_match.c b/lib/command_match.c index 5703510148..56a7ae422b 100644 --- a/lib/command_match.c +++ b/lib/command_match.c @@ -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( diff --git a/lib/command_parse.y b/lib/command_parse.y index f5e42cc304..3e2cdc79af 100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@ -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 diff --git a/lib/command_py.c b/lib/command_py.c index 7f19008fbf..37b4dec7be 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -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); diff --git a/tests/lib/cli/test_cli.c b/tests/lib/cli/test_cli.c index 8dba1e29f0..f8d74018dd 100644 --- a/tests/lib/cli/test_cli.c +++ b/tests/lib/cli/test_cli.c @@ -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);  } diff --git a/tests/lib/cli/test_cli.in b/tests/lib/cli/test_cli.in index 5c146ef984..bd685a6231 100644 --- a/tests/lib/cli/test_cli.in +++ b/tests/lib/cli/test_cli.in @@ -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 diff --git a/tests/lib/cli/test_cli.refout.in b/tests/lib/cli/test_cli.refout.in index 8f9959cc47..8a9eaf38fb 100644 --- a/tests/lib/cli/test_cli.refout.in +++ b/tests/lib/cli/test_cli.refout.in @@ -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
 diff --git a/tools/permutations.c b/tools/permutations.c index f51d4a4ec9..b280cc15b1 100644 --- a/tools/permutations.c +++ b/tools/permutations.c @@ -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) {  | 
