diff --git a/src/main/java/io/airlift/airline/HelpOption.java b/src/main/java/io/airlift/airline/HelpOption.java index 281cabde9..685c4e7ba 100644 --- a/src/main/java/io/airlift/airline/HelpOption.java +++ b/src/main/java/io/airlift/airline/HelpOption.java @@ -19,4 +19,28 @@ public boolean showHelpIfRequested() } return help; } + + /** + * Run the runnable if there are no parse errors and if help was not requested + */ + public void runOrShowHelp(ParseResult parseResult, Runnable runnable) { + if (parseResult.hasErrors()) { + help = true; + System.out.println(parseResult.getErrorMessage()); + } + + if (help) { + Help.help(commandMetadata); + return; + } + + runnable.run(); + } + + /** + * Run the runnable if help was not requested + */ + public void runOrShowHelp(Runnable runnable) { + runOrShowHelp(new ParseResult(), runnable); + } } diff --git a/src/main/java/io/airlift/airline/ParseResult.java b/src/main/java/io/airlift/airline/ParseResult.java new file mode 100644 index 000000000..6f11fe65f --- /dev/null +++ b/src/main/java/io/airlift/airline/ParseResult.java @@ -0,0 +1,36 @@ +package io.airlift.airline; + +import com.google.common.collect.ImmutableList; + +import java.util.LinkedList; +import java.util.List; + +public class ParseResult { + private List errors = new LinkedList(); + + public void addError(ParseException error) { + errors.add(error); + } + + public boolean hasErrors() { + return errors.size() != 0; + } + + public List getErrors() { + return ImmutableList.copyOf(errors); + } + + public String getErrorMessage() { + if (errors.size() >= 1) { + StringBuffer sb = new StringBuffer(); + sb.append("ERROR: Encountered problems parsing arguments:\n"); + for (ParseException error : errors) { + sb.append(" - ").append(error.getMessage()).append("\n"); + } + sb.append("\n"); + return sb.toString(); + } else { + throw new IllegalStateException("There are no errors to build a message for."); + } + } +} diff --git a/src/main/java/io/airlift/airline/SingleCommand.java b/src/main/java/io/airlift/airline/SingleCommand.java index 8dc5d116d..548168129 100644 --- a/src/main/java/io/airlift/airline/SingleCommand.java +++ b/src/main/java/io/airlift/airline/SingleCommand.java @@ -57,12 +57,31 @@ public C parse(String... args) } public C parse(Iterable args) + { + ParseResult parseResult = new ParseResult(); + C command = parse(parseResult, args); + + // For backward compatibility we fail fast here. Given that exceptions aren't a great way to handle user errors + // since there can be multiple we may consider deprecating this approach. + if (parseResult.hasErrors()) { + throw parseResult.getErrors().get(0); + } + + return command; + } + + public C parse(ParseResult parseResult, String... args) + { + return parse(parseResult, ImmutableList.copyOf(args)); + } + + public C parse(ParseResult parseResult, Iterable args) { checkNotNull(args, "args is null"); - + Parser parser = new Parser(); ParseState state = parser.parseCommand(commandMetadata, args); - validate(state); + validate(state, parseResult); CommandMetadata command = state.getCommand(); @@ -75,35 +94,35 @@ public C parse(Iterable args) ImmutableMap., Object>of(CommandMetadata.class, commandMetadata)); } - private void validate(ParseState state) + private void validate(ParseState state, ParseResult parseResult) { CommandMetadata command = state.getCommand(); if (command == null) { List unparsedInput = state.getUnparsedInput(); if (unparsedInput.isEmpty()) { - throw new ParseCommandMissingException(); + parseResult.addError(new ParseCommandMissingException()); } else { - throw new ParseCommandUnrecognizedException(unparsedInput); + parseResult.addError(new ParseCommandUnrecognizedException(unparsedInput)); } } ArgumentsMetadata arguments = command.getArguments(); if (state.getParsedArguments().isEmpty() && arguments != null && arguments.isRequired()) { - throw new ParseArgumentsMissingException(arguments.getTitle()); + parseResult.addError(new ParseArgumentsMissingException(arguments.getTitle())); } if (!state.getUnparsedInput().isEmpty()) { - throw new ParseArgumentsUnexpectedException(state.getUnparsedInput()); + parseResult.addError(new ParseArgumentsUnexpectedException(state.getUnparsedInput())); } if (state.getLocation() == Context.OPTION) { - throw new ParseOptionMissingValueException(state.getCurrentOption().getTitle()); + parseResult.addError(new ParseOptionMissingValueException(state.getCurrentOption().getTitle())); } for (OptionMetadata option : command.getAllOptions()) { if (option.isRequired() && !state.getParsedOptions().containsKey(option)) { - throw new ParseOptionMissingException(option.getOptions().iterator().next()); + parseResult.addError(new ParseOptionMissingException(option.getOptions().iterator().next())); } } } diff --git a/src/test/java/io/airlift/airline/SingleCommandTest.java b/src/test/java/io/airlift/airline/SingleCommandTest.java index 5439f6802..6d91bbd90 100644 --- a/src/test/java/io/airlift/airline/SingleCommandTest.java +++ b/src/test/java/io/airlift/airline/SingleCommandTest.java @@ -42,13 +42,13 @@ import org.testng.annotations.Test; import javax.inject.Inject; - import java.math.BigDecimal; import java.util.Arrays; import java.util.List; import static com.google.common.base.Predicates.compose; import static com.google.common.base.Predicates.equalTo; +import static com.google.common.base.Predicates.instanceOf; import static com.google.common.collect.Iterables.find; import static io.airlift.airline.SingleCommand.singleCommand; import static org.testng.Assert.assertTrue; @@ -244,12 +244,27 @@ public void requiredMainParameters() singleCommand(ArgsRequired.class).parse(); } + @Test + public void requiredMainParamertersWithParseResult() { + ParseResult result = new ParseResult(); + singleCommand(ArgsRequired.class).parse(result); + assertParseResultHasErrorOfType(result, ParseException.class); + } + @Test(expectedExceptions = ParseException.class, expectedExceptionsMessageRegExp = ".*option.*missing.*") public void requiredOptions() { singleCommand(OptionsRequired.class).parse(); } + @Test + public void requiredOptionsWithParseResult() + { + ParseResult result = new ParseResult(); + singleCommand(OptionsRequired.class).parse(result); + assertParseResultHasErrorOfType(result, ParseException.class); + } + @Test public void ignoresOptionalOptions() { @@ -388,4 +403,10 @@ public static class CommandTest public Boolean interactive = false; } + + private void assertParseResultHasErrorOfType(ParseResult result, Class clazz) { + Assert.assertEquals(result.hasErrors(), true); + String errorMessage = String.format("Expected ParseResult to contain at least one instance of: '%s'", clazz); + Assert.assertNotNull(find(result.getErrors(), instanceOf(clazz)), errorMessage); + } }