@@ -149,6 +149,8 @@ class DefaultNodeVisitor extends AbstractNodeVisitor {
149149 private List <OptionNode > invalidOptionNodes = new ArrayList <>();
150150 private List <ArgumentResult > argumentResults = new ArrayList <>();
151151 private int commandArgumentPos = 0 ;
152+ private int optionPos = -1 ;
153+ private long expectedOptionCount ;
152154
153155 DefaultNodeVisitor (CommandModel commandModel , ConversionService conversionService , ParserConfig config ) {
154156 this .commandModel = commandModel ;
@@ -166,31 +168,27 @@ protected ParseResult buildResult() {
166168 messageResults .addAll (commonMessageResults );
167169 messageResults .addAll (validateOptionIsValid (registration ));
168170
169-
170- // add options with default values
171- Set <CommandOption > resolvedOptions1 = optionResults .stream ()
171+ // we should already have options defined with arguments.
172+ // go through positional arguments and fill using those and
173+ // then fill from option default values.
174+ Set <CommandOption > resolvedOptions = optionResults .stream ()
172175 .map (or -> or .option ())
173176 .collect (Collectors .toSet ());
174- registration .getOptions ().stream ()
175- .filter (o -> o .getDefaultValue () != null )
176- .filter (o -> !resolvedOptions1 .contains (o ))
177- .forEach (o -> {
178- resolvedOptions1 .add (o );
179- Object value = convertOptionType (o , o .getDefaultValue ());
180- optionResults .add (OptionResult .of (o , value ));
181- });
182177
178+ // get sorted list by position as we later match by order
183179 List <CommandOption > optionsForArguments = registration .getOptions ().stream ()
184- .filter (o -> !resolvedOptions1 .contains (o ))
180+ .filter (o -> !resolvedOptions .contains (o ))
185181 .filter (o -> o .getPosition () > -1 )
186182 .sorted (Comparator .comparingInt (o -> o .getPosition ()))
187183 .collect (Collectors .toList ());
188184
185+ // leftover arguments to match into needed options
189186 List <String > argumentValues = argumentResults .stream ()
190187 .sorted (Comparator .comparingInt (ar -> ar .position ()))
191188 .map (ar -> ar .value ())
192189 .collect (Collectors .toList ());
193190
191+ // try to find matching arguments
194192 int i = 0 ;
195193 for (CommandOption o : optionsForArguments ) {
196194 int aMax = o .getArityMax ();
@@ -202,11 +200,17 @@ protected ParseResult buildResult() {
202200
203201 List <String > asdf = argumentValues .subList (i , j );
204202 if (asdf .isEmpty ()) {
205- optionResults .add (OptionResult .of (o , null ));
203+ // don't arguments so only add if we know
204+ // it's going to get added later via default value
205+ if (o .getDefaultValue () == null ) {
206+ resolvedOptions .add (o );
207+ optionResults .add (OptionResult .of (o , null ));
208+ }
206209 }
207210 else {
208211 Object toConvertValue = asdf .size () == 1 ? asdf .get (0 ) : asdf ;
209212 Object value = convertOptionType (o , toConvertValue );
213+ resolvedOptions .add (o );
210214 optionResults .add (OptionResult .of (o , value ));
211215 }
212216
@@ -216,6 +220,16 @@ protected ParseResult buildResult() {
216220 i = j ;
217221 }
218222
223+ // possibly fill in from default values
224+ registration .getOptions ().stream ()
225+ .filter (o -> o .getDefaultValue () != null )
226+ .filter (o -> !resolvedOptions .contains (o ))
227+ .forEach (o -> {
228+ resolvedOptions .add (o );
229+ Object value = convertOptionType (o , o .getDefaultValue ());
230+ optionResults .add (OptionResult .of (o , value ));
231+ });
232+
219233 // can only validate after optionResults has been populated
220234 messageResults .addAll (validateOptionNotMissing (registration ));
221235 }
@@ -234,6 +248,7 @@ protected void onExitDirectiveNode(DirectiveNode node) {
234248
235249 @ Override
236250 protected void onEnterRootCommandNode (CommandNode node ) {
251+ expectedOptionCount = optionCountInCommand (node );
237252 resolvedCommmand .add (node .getCommand ());
238253 }
239254
@@ -243,6 +258,7 @@ protected void onExitRootCommandNode(CommandNode node) {
243258
244259 @ Override
245260 protected void onEnterCommandNode (CommandNode node ) {
261+ expectedOptionCount = optionCountInCommand (node );
246262 resolvedCommmand .add (node .getCommand ());
247263 }
248264
@@ -254,6 +270,7 @@ protected void onExitCommandNode(CommandNode node) {
254270
255271 @ Override
256272 protected void onEnterOptionNode (OptionNode node ) {
273+ optionPos ++;
257274 commandArgumentPos = 0 ;
258275 currentOptions .clear ();
259276 currentOptionArgument .clear ();
@@ -307,16 +324,40 @@ protected void onExitOptionNode(OptionNode node) {
307324 int max = currentOption .getArityMax () > 0 ? currentOption .getArityMax () : Integer .MAX_VALUE ;
308325 max = Math .min (max , currentOptionArgument .size ());
309326 List <String > toUse = currentOptionArgument .subList (0 , max );
327+ List <String > toUnused = currentOptionArgument .subList (max , currentOptionArgument .size ());
328+ toUnused .forEach (a -> {
329+ argumentResults .add (ArgumentResult .of (a , commandArgumentPos ++));
330+ });
310331
311- if (currentOption .getArityMin () > -1 && currentOptionArgument .size () < currentOption .getArityMin ()) {
312- String arg = currentOption .getLongNames ()[0 ];
313- commonMessageResults .add (MessageResult .of (ParserMessage .NOT_ENOUGH_OPTION_ARGUMENTS , 0 , arg ,
314- currentOptionArgument .size ()));
332+ // if we're not in a last option
333+ // "--arg1 a b --arg2 c" vs "--arg1 a --arg2 b c"
334+ // we know to impose restriction to argument count,
335+ // last option is different as we can't really fail
336+ // because number of argument to eat dependes on arity
337+ // and rest would go back to positional args.
338+ if (optionPos + 1 < expectedOptionCount ) {
339+ if (currentOption .getArityMin () > -1 && currentOptionArgument .size () < currentOption .getArityMin ()) {
340+ String arg = currentOption .getLongNames ()[0 ];
341+ commonMessageResults .add (MessageResult .of (ParserMessage .NOT_ENOUGH_OPTION_ARGUMENTS , 0 , arg ,
342+ currentOptionArgument .size ()));
343+ }
344+ else if (currentOption .getArityMax () > -1 && currentOptionArgument .size () > currentOption .getArityMax ()) {
345+ String arg = currentOption .getLongNames ()[0 ];
346+ commonMessageResults .add (MessageResult .of (ParserMessage .TOO_MANY_OPTION_ARGUMENTS , 0 , arg ,
347+ currentOption .getArityMax ()));
348+ }
315349 }
316- else if (currentOption .getArityMax () > -1 && currentOptionArgument .size () > currentOption .getArityMax ()) {
317- String arg = currentOption .getLongNames ()[0 ];
318- commonMessageResults .add (MessageResult .of (ParserMessage .TOO_MANY_OPTION_ARGUMENTS , 0 , arg ,
319- currentOption .getArityMax ()));
350+ else {
351+ if (currentOption .getArityMin () > -1 && toUse .size () < currentOption .getArityMin ()) {
352+ String arg = currentOption .getLongNames ()[0 ];
353+ commonMessageResults .add (MessageResult .of (ParserMessage .NOT_ENOUGH_OPTION_ARGUMENTS , 0 , arg ,
354+ toUse .size ()));
355+ }
356+ else if (currentOption .getArityMax () > -1 && toUse .size () > currentOption .getArityMax ()) {
357+ String arg = currentOption .getLongNames ()[0 ];
358+ commonMessageResults .add (MessageResult .of (ParserMessage .TOO_MANY_OPTION_ARGUMENTS , 0 , arg ,
359+ currentOption .getArityMax ()));
360+ }
320361 }
321362
322363 Object value = null ;
@@ -415,5 +456,12 @@ private List<MessageResult> validateOptionIsValid(CommandRegistration registrati
415456 })
416457 .collect (Collectors .toList ());
417458 }
459+
460+ private static long optionCountInCommand (CommandNode node ) {
461+ if (node == null ) {
462+ return 0 ;
463+ }
464+ return node .getChildren ().stream ().filter (n -> n instanceof OptionNode ).count ();
465+ }
418466 }
419467}
0 commit comments