From 6fffa783c857c320d3f6f8dfc97ed35e4cdb437a Mon Sep 17 00:00:00 2001 From: Rob Vesse Date: Mon, 15 Apr 2019 17:13:56 +0100 Subject: [PATCH] More work on positional argument support (#91) - Capture positional arguments during parsing - Add PositionalArgumentMetadata variant to ArgumentsRestriction methods - Modify ParserUtils to also provide injection of positional arguments - Clean up some deprecation warnings --- .../annotations/PositionalArgument.java | 4 +- .../help/suggester/SuggestCommand.java | 4 +- .../rvesse/airline/model/CommandMetadata.java | 6 +- .../rvesse/airline/model/MetadataLoader.java | 40 +++--- ...a.java => PositionalArgumentMetadata.java} | 20 +-- .../airline/parser/AbstractCommandParser.java | 21 ++-- .../rvesse/airline/parser/ParseResult.java | 6 +- .../rvesse/airline/parser/ParseState.java | 117 ++++++++++++------ .../rvesse/airline/parser/ParserUtil.java | 66 ++++++---- .../AbstractCommonRestriction.java | 16 +++ .../restrictions/ArgumentsRestriction.java | 41 ++++++ .../common/IsRequiredRestriction.java | 4 +- .../types/DefaultTypeConverterProvider.java | 6 + .../airline/types/TypeConverterProvider.java | 13 ++ .../numerics/DefaultNumericConverter.java | 6 + .../partial/TestPartialRestriction.java | 32 ++--- 16 files changed, 279 insertions(+), 123 deletions(-) rename airline-core/src/main/java/com/github/rvesse/airline/model/{ArgumentMetadata.java => PositionalArgumentMetadata.java} (93%) diff --git a/airline-core/src/main/java/com/github/rvesse/airline/annotations/PositionalArgument.java b/airline-core/src/main/java/com/github/rvesse/airline/annotations/PositionalArgument.java index 2bbb56812..85853a192 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/annotations/PositionalArgument.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/annotations/PositionalArgument.java @@ -18,7 +18,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; -import com.github.rvesse.airline.model.ArgumentMetadata; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.types.DefaultTypeConverterProvider; import com.github.rvesse.airline.types.TypeConverterProvider; @@ -65,7 +65,7 @@ * the argument definitions are compatible. *

* See - * {@link ArgumentMetadata#override(ArgumentMetadata, ArgumentMetadata)} + * {@link PositionalArgumentMetadata#override(PositionalArgumentMetadata, PositionalArgumentMetadata)} * for legal overrides *

*

diff --git a/airline-core/src/main/java/com/github/rvesse/airline/help/suggester/SuggestCommand.java b/airline-core/src/main/java/com/github/rvesse/airline/help/suggester/SuggestCommand.java index 449ceacaa..22d857286 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/help/suggester/SuggestCommand.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/help/suggester/SuggestCommand.java @@ -24,6 +24,7 @@ import com.github.rvesse.airline.model.GlobalMetadata; import com.github.rvesse.airline.model.MetadataLoader; import com.github.rvesse.airline.model.OptionMetadata; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.model.SuggesterMetadata; import com.github.rvesse.airline.parser.ParseState; import com.github.rvesse.airline.parser.suggester.SuggestionParser; @@ -79,7 +80,8 @@ public Iterable generateSuggestions() { } Suggester suggester = createInstance(suggesterMetadata.getSuggesterClass(), - Collections. emptyList(), null, null, null, + Collections. emptyList(), null, + Collections. emptyList(), null, null, null, suggesterMetadata.getMetadataInjections(), AirlineUtils.unmodifiableMapCopy(bindings)); return suggester.suggest(); diff --git a/airline-core/src/main/java/com/github/rvesse/airline/model/CommandMetadata.java b/airline-core/src/main/java/com/github/rvesse/airline/model/CommandMetadata.java index 943d763da..433dca973 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/model/CommandMetadata.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/model/CommandMetadata.java @@ -34,7 +34,7 @@ public class CommandMetadata { private final List groupOptions; private final List commandOptions; private final OptionMetadata defaultOption; - private final List positionalArgs; + private final List positionalArgs; private final ArgumentsMetadata arguments; private final List metadataInjections; private final Class type; @@ -50,7 +50,7 @@ public CommandMetadata(String name, Iterable groupOptions, Iterable commandOptions, OptionMetadata defaultOption, - List positionalArguments, + List positionalArguments, ArgumentsMetadata arguments, Iterable metadataInjections, Class type, @@ -151,7 +151,7 @@ public OptionMetadata getDefaultOption() { return defaultOption; } - public List getPositionalArguments() { + public List getPositionalArguments() { return positionalArgs; } diff --git a/airline-core/src/main/java/com/github/rvesse/airline/model/MetadataLoader.java b/airline-core/src/main/java/com/github/rvesse/airline/model/MetadataLoader.java index ee4c53f0d..8da72bb6b 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/model/MetadataLoader.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/model/MetadataLoader.java @@ -52,7 +52,7 @@ import javax.inject.Inject; -import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.ArrayUtils; @@ -254,7 +254,7 @@ public static GlobalMetadata loadGlobal(Class cliClass, ParserMetadata } // Maybe a top level group we've already seen - CommandGroupMetadata group = CollectionUtils.find(groups, new GroupFinder(groupName)); + CommandGroupMetadata group = IterableUtils.find(groups, new GroupFinder(groupName)); if (group == null) { // Maybe a sub-group we've already seen group = subGroups.get(subGroupPath); @@ -750,7 +750,7 @@ public static void loadInjectionMetadata(Class type, InjectionMetadata inject List restrictions = collectArgumentRestrictions(field, false); //@formatter:off - injectionMetadata.positionalArgs.add(new ArgumentMetadata(positionalArgumentAnnotation.position(), + injectionMetadata.positionalArgs.add(new PositionalArgumentMetadata(positionalArgumentAnnotation.position(), title, positionalArgumentAnnotation.description(), positionalArgumentAnnotation.sealed(), @@ -940,12 +940,12 @@ private static void tryOverrideOptions(Map, OptionMetadata> optionIn optionIndex.put(names, merged); } - private static List overridePositionalArgumentSet(List args) { + private static List overridePositionalArgumentSet(List args) { args = ListUtils.unmodifiableList(args); - Map argsIndex = new HashMap<>(); + Map argsIndex = new HashMap<>(); int maxIndex = -1; - for (ArgumentMetadata arg : args) { + for (PositionalArgumentMetadata arg : args) { maxIndex = Math.max(maxIndex, arg.getZeroBasedPosition()); if (argsIndex.containsKey(arg.getZeroBasedPosition())) { @@ -956,7 +956,7 @@ private static List overridePositionalArgumentSet(List posArgs = new ArrayList<>(maxIndex); + List posArgs = new ArrayList<>(maxIndex); for (int i = 0; i < maxIndex; i++) { posArgs.set(i, argsIndex.get(i)); if (posArgs.get(i) == null) { @@ -969,13 +969,13 @@ private static List overridePositionalArgumentSet(List argsIndex, - ArgumentMetadata parent) { + private static void tryOverridePositionalArgument(Map argsIndex, + PositionalArgumentMetadata parent) { // As the metadata is extracted from the deepest class in the hierarchy // going upwards we need to treat the passed option as the parent and // the pre-existing option definition as the child - ArgumentMetadata child = argsIndex.get(parent.getZeroBasedPosition()); + PositionalArgumentMetadata child = argsIndex.get(parent.getZeroBasedPosition()); Accessor parentField = parent.getAccessors().iterator().next(); Accessor childField = child.getAccessors().iterator().next(); @@ -999,7 +999,7 @@ private static void tryOverridePositionalArgument(Map parentField, childField, parent.getZeroBasedPosition(), parent.getTitle())); // Attempt overriding, this will error if the overriding is not possible - ArgumentMetadata merged = ArgumentMetadata.override(parent, child); + PositionalArgumentMetadata merged = PositionalArgumentMetadata.override(parent, child); argsIndex.put(parent.getZeroBasedPosition(), merged); } @@ -1017,7 +1017,7 @@ public static void loadCommandsIntoGroupsByAnnotation(List allC // now add the command to any groupNames specified in the Command // annotation for (String groupName : command.getGroupNames()) { - CommandGroupMetadata group = CollectionUtils.find(commandGroups, new GroupFinder(groupName)); + CommandGroupMetadata group = IterableUtils.find(commandGroups, new GroupFinder(groupName)); if (group != null) { // Add to existing top level group group.addCommand(command); @@ -1030,7 +1030,7 @@ public static void loadCommandsIntoGroupsByAnnotation(List allC for (int i = 0; i < groups.length; i++) { if (i == 0) { // Find/create the necessary top level group - subGroup = CollectionUtils.find(commandGroups, new GroupFinder(groups[i])); + subGroup = IterableUtils.find(commandGroups, new GroupFinder(groups[i])); if (subGroup == null) { subGroup = new CommandGroupMetadata(groups[i], "", false, Collections. emptyList(), @@ -1040,7 +1040,7 @@ Collections. emptyList(), null, } } else { // Find/create the next sub-group - CommandGroupMetadata nextSubGroup = CollectionUtils.find(subGroup.getSubGroups(), + CommandGroupMetadata nextSubGroup = IterableUtils.find(subGroup.getSubGroups(), new GroupFinder(groups[i])); if (nextSubGroup == null) { nextSubGroup = new CommandGroupMetadata(groups[i], "", false, @@ -1096,7 +1096,7 @@ private static void createGroupsFromAnnotations(List allCommand // load default command if needed if (!groupAnno.defaultCommand().equals(Group.NO_DEFAULT.class)) { defaultCommandClass = groupAnno.defaultCommand(); - defaultCommand = CollectionUtils.find(allCommands, new CommandTypeFinder(defaultCommandClass)); + defaultCommand = IterableUtils.find(allCommands, new CommandTypeFinder(defaultCommandClass)); if (null == defaultCommand) { defaultCommand = loadCommand(defaultCommandClass, baseHelpSections); newCommands.add(defaultCommand); @@ -1107,7 +1107,7 @@ private static void createGroupsFromAnnotations(List allCommand List groupCommands = new ArrayList(groupAnno.commands().length); CommandMetadata groupCommand = null; for (Class commandClass : groupAnno.commands()) { - groupCommand = CollectionUtils.find(allCommands, new CommandTypeFinder(commandClass)); + groupCommand = IterableUtils.find(allCommands, new CommandTypeFinder(commandClass)); if (null == groupCommand) { groupCommand = loadCommand(commandClass, baseHelpSections); newCommands.add(groupCommand); @@ -1117,7 +1117,7 @@ private static void createGroupsFromAnnotations(List allCommand // Find the group metadata // May already exist as a top level group - CommandGroupMetadata groupMetadata = CollectionUtils.find(commandGroups, + CommandGroupMetadata groupMetadata = IterableUtils.find(commandGroups, new GroupFinder(groupAnno.name())); if (groupMetadata == null) { // Not a top level group @@ -1167,7 +1167,7 @@ protected static void buildGroupsHierarchy(List commandGro for (int i = 0; i < groups.length - 1; i++) { if (i == 0) { // Should be a top level group - parentGroup = CollectionUtils.find(commandGroups, new GroupFinder(groups[i])); + parentGroup = IterableUtils.find(commandGroups, new GroupFinder(groups[i])); if (parentGroup == null) { // Top level parent group does not exist so create empty // top level group @@ -1179,7 +1179,7 @@ Collections. emptyList(), null, } } else { // Should be a sub-group of the current parent - CommandGroupMetadata nextParent = CollectionUtils.find(parentGroup.getSubGroups(), + CommandGroupMetadata nextParent = IterableUtils.find(parentGroup.getSubGroups(), new GroupFinder(groups[i])); if (nextParent == null) { // Next parent group does not exist so create empty @@ -1206,7 +1206,7 @@ private static class InjectionMetadata { private List groupOptions = new ArrayList<>(); private List commandOptions = new ArrayList<>(); private OptionMetadata defaultOption = null; - private List positionalArgs = new ArrayList<>(); + private List positionalArgs = new ArrayList<>(); private List arguments = new ArrayList<>(); private List metadataInjections = new ArrayList<>(); diff --git a/airline-core/src/main/java/com/github/rvesse/airline/model/ArgumentMetadata.java b/airline-core/src/main/java/com/github/rvesse/airline/model/PositionalArgumentMetadata.java similarity index 93% rename from airline-core/src/main/java/com/github/rvesse/airline/model/ArgumentMetadata.java rename to airline-core/src/main/java/com/github/rvesse/airline/model/PositionalArgumentMetadata.java index aa9b8ee50..bf2566bd7 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/model/ArgumentMetadata.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/model/PositionalArgumentMetadata.java @@ -33,7 +33,7 @@ import org.apache.commons.collections4.SetUtils; import org.apache.commons.lang3.StringUtils; -public class ArgumentMetadata { +public class PositionalArgumentMetadata { private final int position; private final String title, description; private final boolean sealed, overrides; @@ -42,7 +42,7 @@ public class ArgumentMetadata { private final TypeConverterProvider provider; /** - * Creates new argument metadata + * Creates new positional argument metadata * * @param position * Zero based position index @@ -58,7 +58,7 @@ public class ArgumentMetadata { * Field to modify */ //@formatter:off - public ArgumentMetadata(int position, String title, + public PositionalArgumentMetadata(int position, String title, String description, boolean sealed, boolean overrides, Iterable restrictions, @@ -85,13 +85,13 @@ public ArgumentMetadata(int position, String title, this.accessors = SetUtils.unmodifiableSet(Collections.singleton(new Accessor(path))); } - public ArgumentMetadata(Iterable arguments) { + public PositionalArgumentMetadata(Iterable arguments) { if (arguments == null) throw new NullPointerException("arguments cannot be null"); if (!arguments.iterator().hasNext()) throw new IllegalArgumentException("arguments cannot be empty"); - ArgumentMetadata first = arguments.iterator().next(); + PositionalArgumentMetadata first = arguments.iterator().next(); this.sealed = first.sealed; this.overrides = first.overrides; @@ -102,7 +102,7 @@ public ArgumentMetadata(Iterable arguments) { this.provider = first.provider; Set accessors = new HashSet<>(); - for (ArgumentMetadata other : arguments) { + for (PositionalArgumentMetadata other : arguments) { if (!first.equals(other)) throw new IllegalArgumentException( String.format("Conflicting arguments definitions: %s, %s", first, other)); @@ -179,7 +179,7 @@ public boolean equals(Object o) { return false; } - ArgumentMetadata that = (ArgumentMetadata) o; + PositionalArgumentMetadata that = (PositionalArgumentMetadata) o; if (this.position != that.position) return false; @@ -232,7 +232,7 @@ public String toString() { * Child * @return Merged metadata */ - public static ArgumentMetadata override(ArgumentMetadata parent, ArgumentMetadata child) { + public static PositionalArgumentMetadata override(PositionalArgumentMetadata parent, PositionalArgumentMetadata child) { // Cannot change position if (parent.position != child.position) throw new IllegalArgumentException( @@ -277,9 +277,9 @@ public static ArgumentMetadata override(ArgumentMetadata parent, ArgumentMetadat throw new IllegalArgumentException( String.format("Cannot override positional argument %d (%s) unless child argument sets overrides to true", parent.position, parent.title)); - ArgumentMetadata merged; + PositionalArgumentMetadata merged; //@formatter:off - merged = new ArgumentMetadata(child.position, + merged = new PositionalArgumentMetadata(child.position, child.title, child.description, child.sealed, diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/AbstractCommandParser.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/AbstractCommandParser.java index a301133f1..cd31bfa2d 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/parser/AbstractCommandParser.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/AbstractCommandParser.java @@ -16,6 +16,7 @@ package com.github.rvesse.airline.parser; import com.github.rvesse.airline.Context; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.model.ArgumentsMetadata; import com.github.rvesse.airline.model.CommandGroupMetadata; import com.github.rvesse.airline.model.CommandMetadata; @@ -32,7 +33,7 @@ import java.util.List; -import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.Predicate; import org.apache.commons.collections4.iterators.PeekingIterator; @@ -166,7 +167,7 @@ protected ParseState parseCommandOptionsAndArguments(PeekingIterator while (tokens.hasNext()) { state = parseOptions(tokens, state, command.getCommandOptions()); - state = parseArgs(state, tokens, command.getArguments(), command.getDefaultOption()); + state = parseArgs(state, tokens, command.getPositionalArguments(), command.getArguments(), command.getDefaultOption()); } return state; } @@ -179,7 +180,7 @@ protected ParseState parseGroup(PeekingIterator tokens, ParseState ? new AbbreviatedGroupFinder(tokens.peek(), state.getGlobal().getCommandGroups()) : new GroupFinder(tokens.peek()); //@formatter:on - CommandGroupMetadata group = CollectionUtils.find(state.getGlobal().getCommandGroups(), findGroupPredicate); + CommandGroupMetadata group = IterableUtils.find(state.getGlobal().getCommandGroups(), findGroupPredicate); if (group != null) { tokens.next(); state = state.withGroup(group).pushContext(Context.GROUP); @@ -192,7 +193,7 @@ protected ParseState parseGroup(PeekingIterator tokens, ParseState ? new AbbreviatedGroupFinder(tokens.peek(), state.getGroup().getSubGroups()) : new GroupFinder(tokens.peek()); //@formatter:on - group = CollectionUtils.find(state.getGroup().getSubGroups(), findGroupPredicate); + group = IterableUtils.find(state.getGroup().getSubGroups(), findGroupPredicate); if (group != null) { tokens.next(); state = state.withGroup(group).pushContext(Context.GROUP); @@ -247,7 +248,7 @@ private ParseState parseOptions(PeekingIterator tokens, ParseState return state; } - private ParseState parseArgs(ParseState state, PeekingIterator tokens, ArgumentsMetadata arguments, + private ParseState parseArgs(ParseState state, PeekingIterator tokens, List positionalArgs, ArgumentsMetadata arguments, OptionMetadata defaultOption) { String sep = state.getParserConfiguration().getArgumentsSeparator(); @@ -260,21 +261,21 @@ private ParseState parseArgs(ParseState state, PeekingIterator tok // Default option can't possibly apply at this point because we // saw the arguments separator while (tokens.hasNext()) { - state = parseArg(state, tokens, arguments, null); + state = parseArg(state, tokens, positionalArgs, arguments, null); } } else { - state = parseArg(state, tokens, arguments, defaultOption); + state = parseArg(state, tokens, positionalArgs, arguments, defaultOption); } } return state; } - private ParseState parseArg(ParseState state, PeekingIterator tokens, ArgumentsMetadata arguments, + private ParseState parseArg(ParseState state, PeekingIterator tokens, List positionalArgs, ArgumentsMetadata arguments, OptionMetadata defaultOption) { - if (arguments != null) { + if (arguments != null || positionalArgs.size() > 0) { // Argument - state = state.withArgument(arguments, tokens.next()); + state = state.withArgument(positionalArgs, arguments, tokens.next()); } else if (defaultOption != null) { // Default Option state = state.pushContext(Context.OPTION).withOption(defaultOption); diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseResult.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseResult.java index 7cf744d92..8e5dbd7e0 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseResult.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseResult.java @@ -112,11 +112,13 @@ public T getCommand() { if (state.getGlobal() != null) { // Create instance return createInstance(command.getType(), command.getAllOptions(), state.getParsedOptions(), - command.getArguments(), state.getParsedArguments(), command.getMetadataInjections(), bindings, + command.getPositionalArguments(), state.getParsedPositionalArguments(), command.getArguments(), + state.getParsedArguments(), command.getMetadataInjections(), bindings, state.getParserConfiguration().getCommandFactory()); } else { return createInstance(command.getType(), command.getAllOptions(), state.getParsedOptions(), - command.getArguments(), state.getParsedArguments(), command.getMetadataInjections(), bindings, + command.getPositionalArguments(), state.getParsedPositionalArguments(), command.getArguments(), + state.getParsedArguments(), command.getMetadataInjections(), bindings, state.getParserConfiguration().getCommandFactory()); } diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseState.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseState.java index 1fe6fb008..a9a973ddc 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseState.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseState.java @@ -17,6 +17,7 @@ import com.github.rvesse.airline.Context; import com.github.rvesse.airline.builder.ParserBuilder; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.model.ArgumentsMetadata; import com.github.rvesse.airline.model.CommandGroupMetadata; import com.github.rvesse.airline.model.CommandMetadata; @@ -35,6 +36,12 @@ import org.apache.commons.lang3.tuple.Pair; +/** + * Used to track the state of the parser + * + * @param + * Command type + */ public class ParseState { private final List locationStack; private final GlobalMetadata global; @@ -42,13 +49,15 @@ public class ParseState { private final CommandGroupMetadata group; private final CommandMetadata command; private final List> parsedOptions; + private final List> parsedPositionalArgs; private final List parsedArguments; private final OptionMetadata currentOption; private final List unparsedInput; ParseState(GlobalMetadata global, ParserMetadata parserConfig, CommandGroupMetadata group, CommandMetadata command, List> parsedOptions, List locationStack, - List parsedArguments, OptionMetadata currentOption, List unparsedInput) { + List> parsedPositionalArgs, List parsedArguments, + OptionMetadata currentOption, List unparsedInput) { this.global = global; if (global != null) { this.parserConfig = global.getParserConfiguration(); @@ -61,6 +70,7 @@ public class ParseState { this.command = command; this.parsedOptions = parsedOptions; this.locationStack = locationStack; + this.parsedPositionalArgs = parsedPositionalArgs; this.parsedArguments = parsedArguments; this.currentOption = currentOption; this.unparsedInput = unparsedInput; @@ -68,23 +78,23 @@ public class ParseState { public static ParseState newInstance() { return new ParseState(null, null, null, null, new ArrayList>(), - Collections. emptyList(), Collections. emptyList(), null, - Collections. emptyList()); + Collections. emptyList(), new ArrayList>(), + Collections. emptyList(), null, Collections. emptyList()); } public ParseState pushContext(Context location) { List locations = AirlineUtils.listCopy(this.locationStack); locations.add(location); - return new ParseState(global, parserConfig, group, command, parsedOptions, locations, parsedArguments, - currentOption, unparsedInput); + return new ParseState(global, parserConfig, group, command, parsedOptions, locations, parsedPositionalArgs, + parsedArguments, currentOption, unparsedInput); } public ParseState popContext() { List locationStack = AirlineUtils .unmodifiableListCopy(this.locationStack.subList(0, this.locationStack.size() - 1)); - return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, parsedArguments, - currentOption, unparsedInput); + return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, + parsedPositionalArgs, parsedArguments, currentOption, unparsedInput); } public ParseState withOptionValue(OptionMetadata option, String rawValue) { @@ -114,8 +124,8 @@ public ParseState withOptionValue(OptionMetadata option, String rawValue) { List> newOptions = AirlineUtils.listCopy(parsedOptions); newOptions.add(Pair.of(option, value)); - return new ParseState(global, parserConfig, group, command, newOptions, locationStack, parsedArguments, - currentOption, unparsedInput); + return new ParseState(global, parserConfig, group, command, newOptions, locationStack, + parsedPositionalArgs, parsedArguments, currentOption, unparsedInput); } catch (ParseException e) { this.parserConfig.getErrorHandler().handleError(e); @@ -123,40 +133,55 @@ public ParseState withOptionValue(OptionMetadata option, String rawValue) { newUnparsed.add(rawValue); return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, - parsedArguments, currentOption, newUnparsed); + parsedPositionalArgs, parsedArguments, currentOption, newUnparsed); } } public ParseState withGlobal(GlobalMetadata global) { - return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, parsedArguments, - currentOption, unparsedInput); + return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, + parsedPositionalArgs, parsedArguments, currentOption, unparsedInput); } public ParseState withConfiguration(ParserMetadata parserConfig) { - return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, parsedArguments, - currentOption, unparsedInput); + return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, + parsedPositionalArgs, parsedArguments, currentOption, unparsedInput); } public ParseState withGroup(CommandGroupMetadata group) { - return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, parsedArguments, - currentOption, unparsedInput); + return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, + parsedPositionalArgs, parsedArguments, currentOption, unparsedInput); } public ParseState withCommand(CommandMetadata command) { - return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, parsedArguments, - currentOption, unparsedInput); + return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, + parsedPositionalArgs, parsedArguments, currentOption, unparsedInput); } public ParseState withOption(OptionMetadata option) { - return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, parsedArguments, - option, unparsedInput); + return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, + parsedPositionalArgs, parsedArguments, option, unparsedInput); } - public ParseState withArgument(ArgumentsMetadata arguments, String rawValue) { + public ParseState withArgument(List positionalArgs, ArgumentsMetadata arguments, + String rawValue) { + // Are we still parsing positional arguments or are we on non-positional + // arguments? + boolean positional = positionalArgs.size() > 0 && parsedArguments.size() < positionalArgs.size(); + int posIndex = positional ? parsedArguments.size() : -1; + if (!positional && arguments == null) { + return withUnparsedInput(rawValue); + } + PositionalArgumentMetadata posArg = positionalArgs.get(posIndex); + List restrictions = positional ? posArg.getRestrictions() : arguments.getRestrictions(); + // Pre-validate - for (ArgumentsRestriction restriction : arguments.getRestrictions()) { + for (ArgumentsRestriction restriction : restrictions) { try { - restriction.preValidate(this, arguments, rawValue); + if (positional) { + restriction.preValidate(this, posArg, rawValue); + } else { + restriction.preValidate(this, arguments, rawValue); + } } catch (ParseException e) { this.parserConfig.getErrorHandler().handleError(e); } @@ -164,23 +189,38 @@ public ParseState withArgument(ArgumentsMetadata arguments, String rawValue) // Convert value try { - TypeConverter converter = arguments.getTypeConverterProvider().getTypeConverter(arguments, this); - Object value = converter.convert(arguments.getTitle().get(0), arguments.getJavaType(), rawValue); + TypeConverter converter = positional ? posArg.getTypeConverterProvider().getTypeConverter(posArg, this) + : arguments.getTypeConverterProvider().getTypeConverter(arguments, this); + Object value = converter.convert(positional ? posArg.getTitle() : arguments.getTitle().get(0), + arguments.getJavaType(), rawValue); // Post-validate - for (ArgumentsRestriction restriction : arguments.getRestrictions()) { + for (ArgumentsRestriction restriction : restrictions) { try { - restriction.postValidate(this, arguments, value); + if (positional) { + restriction.postValidate(this, posArg, value); + } else { + restriction.postValidate(this, arguments, value); + } } catch (ParseException e) { this.parserConfig.getErrorHandler().handleError(e); } } - List newArguments = AirlineUtils.listCopy(parsedArguments); - newArguments.add(value); + if (positional) { + List> newPosArgs = AirlineUtils.listCopy(parsedPositionalArgs); + newPosArgs.add(Pair.of(posArg, value)); + + return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, newPosArgs, + parsedArguments, currentOption, unparsedInput); + } else { + + List newArguments = AirlineUtils.listCopy(parsedArguments); + newArguments.add(value); - return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, newArguments, - currentOption, unparsedInput); + return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, + parsedPositionalArgs, newArguments, currentOption, unparsedInput); + } } catch (ParseException e) { this.parserConfig.getErrorHandler().handleError(e); @@ -188,7 +228,7 @@ public ParseState withArgument(ArgumentsMetadata arguments, String rawValue) newUnparsed.add(rawValue); return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, - parsedArguments, currentOption, newUnparsed); + parsedPositionalArgs, parsedArguments, currentOption, newUnparsed); } } @@ -196,15 +236,16 @@ public ParseState withUnparsedInput(String input) { List newUnparsedInput = AirlineUtils.listCopy(unparsedInput); newUnparsedInput.add(input); - return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, parsedArguments, - currentOption, newUnparsedInput); + return new ParseState(global, parserConfig, group, command, parsedOptions, locationStack, + parsedPositionalArgs, parsedArguments, currentOption, newUnparsedInput); } @Override public String toString() { return "ParseState{" + "locationStack=" + locationStack + ", global=" + global + ", group=" + group - + ", command=" + command + ", parsedOptions=" + parsedOptions + ", parsedArguments=" + parsedArguments - + ", currentOption=" + currentOption + ", unparsedInput=" + unparsedInput + '}'; + + ", command=" + command + ", parsedOptions=" + parsedOptions + ", parsedPositionalArguments=" + + parsedPositionalArgs + ", parsedArguments=" + parsedArguments + ", currentOption=" + currentOption + + ", unparsedInput=" + unparsedInput + '}'; } public Context getLocation() { @@ -234,6 +275,10 @@ public OptionMetadata getCurrentOption() { public List> getParsedOptions() { return parsedOptions; } + + public List> getParsedPositionalArguments() { + return parsedPositionalArgs; + } public List getParsedArguments() { return parsedArguments; diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/ParserUtil.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/ParserUtil.java index 5c7b7c478..ca3964e1b 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/parser/ParserUtil.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/ParserUtil.java @@ -20,6 +20,7 @@ import com.github.rvesse.airline.DefaultCommandFactory; import com.github.rvesse.airline.model.ArgumentsMetadata; import com.github.rvesse.airline.model.OptionMetadata; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.parser.errors.ParseException; import com.github.rvesse.airline.parser.resources.ResourceLocator; @@ -28,7 +29,6 @@ import java.util.List; import java.util.Map; -import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.tuple.Pair; public class ParserUtil { @@ -45,41 +45,60 @@ public static T createInstance(Class type) { } public static T createInstance(Class type, Iterable options, - List> parsedOptions, ArgumentsMetadata arguments, + List> parsedOptions, Iterable positionalArguments, + List> parsedPositionalArguments, ArgumentsMetadata arguments, Iterable parsedArguments, Iterable metadataInjection, Map, Object> bindings) { - return createInstance(type, options, parsedOptions, arguments, parsedArguments, metadataInjection, bindings, - new DefaultCommandFactory()); + return createInstance(type, options, parsedOptions, positionalArguments, parsedPositionalArguments, arguments, + parsedArguments, metadataInjection, bindings, new DefaultCommandFactory()); } public static T injectOptions(T commandInstance, Iterable options, - List> parsedOptions, ArgumentsMetadata arguments, + List> parsedOptions, Iterable positionalArguments, + List> parsedPositionalArguments, ArgumentsMetadata arguments, Iterable parsedArguments, Iterable metadataInjection, Map, Object> bindings) { // inject options - for (OptionMetadata option : options) { - List values = new ArrayList<>(); - for (Pair parsedOption : parsedOptions) { - if (option.equals(parsedOption.getLeft())) - values.add(parsedOption.getRight()); + if (options != null) { + for (OptionMetadata option : options) { + List values = new ArrayList<>(); + if (parsedOptions != null) { + for (Pair parsedOption : parsedOptions) { + if (option.equals(parsedOption.getLeft())) + values.add(parsedOption.getRight()); + } + } + if (values != null && !values.isEmpty()) { + for (Accessor accessor : option.getAccessors()) { + accessor.addValues(commandInstance, values); + } + } } - if (values != null && !values.isEmpty()) { - for (Accessor accessor : option.getAccessors()) { - accessor.addValues(commandInstance, values); + } + + // Inject positional arguments + if (parsedPositionalArguments != null) { + for (Pair posArg : parsedPositionalArguments) { + PositionalArgumentMetadata argMeta = posArg.getLeft(); + Object value = posArg.getRight(); + for (Accessor accessor : argMeta.getAccessors()) { + accessor.addValues(commandInstance, Collections.singletonList(value)); } } } - // inject args + // Inject additional arguments if (arguments != null && parsedArguments != null) { for (Accessor accessor : arguments.getAccessors()) { accessor.addValues(commandInstance, parsedArguments); } } - for (Accessor accessor : metadataInjection) { - Object injectee = bindings.get(accessor.getJavaType()); + if (metadataInjection != null) { + for (Accessor accessor : metadataInjection) { + Object injectee = bindings.get(accessor.getJavaType()); - if (injectee != null) { - accessor.addValues(commandInstance, ListUtils.unmodifiableList(Collections.singletonList(injectee))); + if (injectee != null) { + accessor.addValues(commandInstance, Collections.singletonList(injectee)); + } } } @@ -87,20 +106,21 @@ public static T injectOptions(T commandInstance, Iterable op } public static T createInstance(Class type, Iterable options, - List> parsedOptions, ArgumentsMetadata arguments, + List> parsedOptions, Iterable positionalArguments, + List> parsedPositionalArguments, ArgumentsMetadata arguments, Iterable parsedArguments, Iterable metadataInjection, Map, Object> bindings, CommandFactory commandFactory) { // create the command instance T commandInstance = (T) commandFactory.createInstance(type); - return injectOptions(commandInstance, options, parsedOptions, arguments, parsedArguments, metadataInjection, - bindings); + return injectOptions(commandInstance, options, parsedOptions, positionalArguments, parsedPositionalArguments, + arguments, parsedArguments, metadataInjection, bindings); } - + public static ResourceLocator[] createResourceLocators(Class[] locatorClasses) { ResourceLocator[] locators = new ResourceLocator[locatorClasses.length]; int i = 0; - for (Class locatorClass :locatorClasses) { + for (Class locatorClass : locatorClasses) { ResourceLocator locator = ParserUtil.createInstance(locatorClass); locators[i] = locator; i++; diff --git a/airline-core/src/main/java/com/github/rvesse/airline/restrictions/AbstractCommonRestriction.java b/airline-core/src/main/java/com/github/rvesse/airline/restrictions/AbstractCommonRestriction.java index 7f607e4c4..5bb00ab7d 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/restrictions/AbstractCommonRestriction.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/restrictions/AbstractCommonRestriction.java @@ -17,6 +17,7 @@ import com.github.rvesse.airline.model.ArgumentsMetadata; import com.github.rvesse.airline.model.OptionMetadata; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.parser.ParseState; public abstract class AbstractCommonRestriction implements OptionRestriction, ArgumentsRestriction { @@ -50,6 +51,21 @@ public void postValidate(ParseState state, ArgumentsMetadata arguments, O public void finalValidate(ParseState state, ArgumentsMetadata arguments) { // Does no validation } + + @Override + public void preValidate(ParseState state, PositionalArgumentMetadata arguments, String value) { + // Does no validation + } + + @Override + public void postValidate(ParseState state, PositionalArgumentMetadata arguments, Object value) { + // Does no validation + } + + @Override + public void finalValidate(ParseState state, PositionalArgumentMetadata arguments) { + // Does no validation + } public static String getArgumentTitle(ParseState state, ArgumentsMetadata arguments) { // Use empty string if no appropriate meta-data available diff --git a/airline-core/src/main/java/com/github/rvesse/airline/restrictions/ArgumentsRestriction.java b/airline-core/src/main/java/com/github/rvesse/airline/restrictions/ArgumentsRestriction.java index d7c767473..711a6de5e 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/restrictions/ArgumentsRestriction.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/restrictions/ArgumentsRestriction.java @@ -16,6 +16,7 @@ package com.github.rvesse.airline.restrictions; import com.github.rvesse.airline.model.ArgumentsMetadata; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.parser.ParseState; /** @@ -65,4 +66,44 @@ public interface ArgumentsRestriction { * Arguments meta-data */ public abstract void finalValidate(ParseState state, ArgumentsMetadata arguments); + + /** + * Method that is called before Airline attempts to convert a string + * argument received into a strongly typed Java value + * + * @param state + * Parser state + * @param arguments + * Arguments meta-data + * @param value + * String value + */ + public abstract void preValidate(ParseState state, PositionalArgumentMetadata arguments, String value); + + /** + * Method that is called after Airline has converted a string argument + * received into a strongly typed Java value + * + * @param state + * Parser state + * @param arguments + * Arguments meta-data + * @param value + * Strongly typed value + */ + public abstract void postValidate(ParseState state, PositionalArgumentMetadata arguments, Object value); + + /** + * Method that is called after Airline has completed parsing + *

+ * This can be used to implement restrictions that require the final parser + * state to process + *

+ * + * @param state + * Parser state + * @param arguments + * Arguments meta-data + */ + public abstract void finalValidate(ParseState state, PositionalArgumentMetadata arguments); } \ No newline at end of file diff --git a/airline-core/src/main/java/com/github/rvesse/airline/restrictions/common/IsRequiredRestriction.java b/airline-core/src/main/java/com/github/rvesse/airline/restrictions/common/IsRequiredRestriction.java index 3c1aff4fa..1c26560a3 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/restrictions/common/IsRequiredRestriction.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/restrictions/common/IsRequiredRestriction.java @@ -15,7 +15,7 @@ */ package com.github.rvesse.airline.restrictions.common; -import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.IterableUtils; import com.github.rvesse.airline.model.ArgumentsMetadata; import com.github.rvesse.airline.model.OptionMetadata; @@ -33,7 +33,7 @@ public class IsRequiredRestriction extends AbstractCommonRestriction { @Override public void finalValidate(ParseState state, OptionMetadata option) { - if (CollectionUtils.find(state.getParsedOptions(), new ParsedOptionFinder(option)) == null) + if (IterableUtils.find(state.getParsedOptions(), new ParsedOptionFinder(option)) == null) throw new ParseOptionMissingException(AirlineUtils.first(option.getOptions())); } diff --git a/airline-core/src/main/java/com/github/rvesse/airline/types/DefaultTypeConverterProvider.java b/airline-core/src/main/java/com/github/rvesse/airline/types/DefaultTypeConverterProvider.java index 010a8c6c8..b55c337eb 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/types/DefaultTypeConverterProvider.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/types/DefaultTypeConverterProvider.java @@ -15,6 +15,7 @@ */ package com.github.rvesse.airline.types; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.model.ArgumentsMetadata; import com.github.rvesse.airline.model.OptionMetadata; import com.github.rvesse.airline.model.ParserMetadata; @@ -40,4 +41,9 @@ public TypeConverter getTypeConverter(ArgumentsMetadata arguments, ParseStat return state.getParserConfiguration().getTypeConverter(); } + @Override + public TypeConverter getTypeConverter(PositionalArgumentMetadata argumentMetadata, ParseState state) { + return state.getParserConfiguration().getTypeConverter(); + } + } diff --git a/airline-core/src/main/java/com/github/rvesse/airline/types/TypeConverterProvider.java b/airline-core/src/main/java/com/github/rvesse/airline/types/TypeConverterProvider.java index 1a3c42aff..932910454 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/types/TypeConverterProvider.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/types/TypeConverterProvider.java @@ -15,6 +15,7 @@ */ package com.github.rvesse.airline.types; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.model.ArgumentsMetadata; import com.github.rvesse.airline.model.OptionMetadata; import com.github.rvesse.airline.parser.ParseState; @@ -48,4 +49,16 @@ public interface TypeConverterProvider { * @return Type converter */ public abstract TypeConverter getTypeConverter(ArgumentsMetadata arguments, ParseState state); + + /** + * Gets the type converter to use for the given positional argument and + * parser state + * + * @param argumentMetadata + * Positional argument metadata + * @param parseState + * Parser state + * @return Type converter + */ + public abstract TypeConverter getTypeConverter(PositionalArgumentMetadata argumentMetadata, ParseState parseState); } diff --git a/airline-core/src/main/java/com/github/rvesse/airline/types/numerics/DefaultNumericConverter.java b/airline-core/src/main/java/com/github/rvesse/airline/types/numerics/DefaultNumericConverter.java index 5fab319f1..73abb441c 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/types/numerics/DefaultNumericConverter.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/types/numerics/DefaultNumericConverter.java @@ -15,6 +15,7 @@ */ package com.github.rvesse.airline.types.numerics; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.model.ArgumentsMetadata; import com.github.rvesse.airline.model.OptionMetadata; import com.github.rvesse.airline.parser.ParseState; @@ -34,6 +35,11 @@ public TypeConverter getTypeConverter(OptionMetadata option, ParseState s public TypeConverter getTypeConverter(ArgumentsMetadata arguments, ParseState state) { return new DefaultTypeConverter(this); } + + @Override + public TypeConverter getTypeConverter(PositionalArgumentMetadata argument, ParseState state) { + return new DefaultTypeConverter(this); + } @Override public ConvertResult tryConvertNumerics(String name, Class type, String value) { diff --git a/airline-core/src/test/java/com/github/rvesse/airline/restrictions/partial/TestPartialRestriction.java b/airline-core/src/test/java/com/github/rvesse/airline/restrictions/partial/TestPartialRestriction.java index 80d9f2897..8fbfb273f 100644 --- a/airline-core/src/test/java/com/github/rvesse/airline/restrictions/partial/TestPartialRestriction.java +++ b/airline-core/src/test/java/com/github/rvesse/airline/restrictions/partial/TestPartialRestriction.java @@ -29,6 +29,7 @@ import com.github.rvesse.airline.annotations.OptionType; import com.github.rvesse.airline.model.ArgumentsMetadata; import com.github.rvesse.airline.model.OptionMetadata; +import com.github.rvesse.airline.model.PositionalArgumentMetadata; import com.github.rvesse.airline.parser.ParseState; import com.github.rvesse.airline.parser.errors.ParseArgumentsIllegalValueException; import com.github.rvesse.airline.parser.errors.ParseRestrictionViolatedException; @@ -53,11 +54,12 @@ public void partial_notblank_01() throws NoSuchFieldException, SecurityException restrictions.add(restriction); List fields = Collections.singletonList(PartialUnannotated.class.getField("args")); + List posArgs = Collections.emptyList(); ArgumentsMetadata arguments = new ArgumentsMetadata(titles, "", restrictions, null, fields); ParseState state = ParseState.newInstance(); - state = state.withArgument(arguments, "text"); - state = state.withArgument(arguments, ""); + state = state.withArgument(posArgs, arguments, "text"); + state = state.withArgument(posArgs, arguments, ""); restriction.finalValidate(state, arguments); } @@ -77,7 +79,7 @@ public void partial_notblank_02() throws NoSuchFieldException, SecurityException ParseState state = ParseState.newInstance(); // Should fail restriction because first argument cannot be blank - state = state.withArgument(arguments, ""); + state = state.withArgument(Collections. emptyList(), arguments, ""); } @Test @@ -91,11 +93,12 @@ public void partial_allowed_values_01() throws NoSuchFieldException, SecurityExc restrictions.add(restriction); List fields = Collections.singletonList(PartialUnannotated.class.getField("args")); + List posArgs = Collections.emptyList(); ArgumentsMetadata arguments = new ArgumentsMetadata(titles, "", restrictions, null, fields); ParseState state = ParseState.newInstance(); - state = state.withArgument(arguments, "foo"); - state = state.withArgument(arguments, "bar"); + state = state.withArgument(posArgs, arguments, "foo"); + state = state.withArgument(posArgs, arguments, "bar"); restriction.finalValidate(state, arguments); } @@ -116,7 +119,7 @@ public void partial_allowed_values_02() throws NoSuchFieldException, SecurityExc ParseState state = ParseState.newInstance(); // Should fail restriction because first argument is restricted to a set // of values - state.withArgument(arguments, "bar"); + state.withArgument(Collections. emptyList(), arguments, "bar"); } @Test @@ -130,11 +133,12 @@ public void partial_allowed_raw_values_01() throws NoSuchFieldException, Securit restrictions.add(restriction); List fields = Collections.singletonList(PartialUnannotated.class.getField("args")); + List posArgs = Collections.emptyList(); ArgumentsMetadata arguments = new ArgumentsMetadata(titles, "", restrictions, null, fields); ParseState state = ParseState.newInstance(); - state = state.withArgument(arguments, "foo"); - state = state.withArgument(arguments, "bar"); + state = state.withArgument(posArgs, arguments, "foo"); + state = state.withArgument(posArgs, arguments, "bar"); restriction.finalValidate(state, arguments); } @@ -155,7 +159,7 @@ public void partial_allowed_raw_values_02() throws NoSuchFieldException, Securit ParseState state = ParseState.newInstance(); // Should fail restriction because first argument is restricted to a set // of values - state.withArgument(arguments, "bar"); + state.withArgument(Collections. emptyList(), arguments, "bar"); } @Test @@ -201,7 +205,7 @@ public void partial_required_01() throws NoSuchFieldException, SecurityException public void partial_annotated_notblank_01() throws NoSuchFieldException, SecurityException { SingleCommand parser = SingleCommand.singleCommand(PartialAnnotated.class); PartialAnnotated cmd = parser.parse(new String[] { "--kvp", "text", "" }); - + Assert.assertEquals(cmd.kvps.size(), 2); Assert.assertEquals(cmd.kvps.get(0), "text"); Assert.assertEquals(cmd.kvps.get(1), ""); @@ -212,23 +216,23 @@ public void partial_annotated_notblank_02() throws NoSuchFieldException, Securit SingleCommand parser = SingleCommand.singleCommand(PartialAnnotated.class); parser.parse(new String[] { "--kvp", "", "text" }); } - + @Test public void partials_annotated_01() { SingleCommand parser = SingleCommand.singleCommand(PartialsAnnotated.class); PartialsAnnotated cmd = parser.parse(new String[] { "--kvp", "server", "remote.com" }); - + Assert.assertEquals(cmd.kvps.size(), 2); Assert.assertEquals(cmd.kvps.get(0), "server"); Assert.assertEquals(cmd.kvps.get(1), "remote.com"); } - + @Test(expectedExceptions = ParseRestrictionViolatedException.class) public void partials_annotated_02() { SingleCommand parser = SingleCommand.singleCommand(PartialsAnnotated.class); parser.parse(new String[] { "--kvp", "other", "remote.com" }); } - + @Test(expectedExceptions = ParseRestrictionViolatedException.class) public void partials_annotated_03() { SingleCommand parser = SingleCommand.singleCommand(PartialsAnnotated.class);