Skip to content

Commit

Permalink
Merge branch 'master' into #121-autocomplete
Browse files Browse the repository at this point in the history
  • Loading branch information
remkop committed Jul 9, 2017
2 parents efafa55 + 27560dc commit 8e20136
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jdk:
before_script:
- chmod +x gradlew
script:
- ./gradlew check
- ./gradlew check --info
- ./gradlew jacocoTestReport
after_success:
- bash <(curl -s https://codecov.io/bash)
5 changes: 5 additions & 0 deletions RELEASE-NOTES.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
= picocli Release Notes

== 0.9.8 - Bugfix and enhancements release for public review. API may change.

* #146 Show underlying error when type conversion fails
* #147 Toggle boolean flags instead of setting to `true`

== 0.9.7 - Bugfix and enhancements release for public review. API may change.

* #127 Added support for nested sub-subcommands
Expand Down
4 changes: 3 additions & 1 deletion docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The below example shows options with one or more names, options that take an opt
----
class Tar {
@Option(names = "-c", description = "create a new archive")
boolean extract;
boolean create;
@Option(names = { "-f", "--file" }, paramLabel = "ARCHIVE", description = "the archive file")
File archive;
Expand Down Expand Up @@ -1110,6 +1110,8 @@ Note that the `CommandLine.run` convenience method cannot be used with subcomman


=== Boolean Parameters
By default the value of a boolean field is toggled to its logical negative when the field's option is specified on the command line.

It is possible to let end users explicitly specify "true" or "false" as a parameter for a boolean option by defining an explicit <<Arity>> attribute. A boolean option with `arity = "0..1"` accepts zero to one parameters, `arity = "1"` means the option _must_ have one parameter. For example:

[source, java]
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -1483,7 +1483,8 @@ private int applyValueToSingleValuedField(Field field,
if (value != null) {
args.push(value); // we don't consume the value
}
value = "true"; // just specifying the option name sets the boolean to true
Boolean currentValue = (Boolean) field.get(command);
value = String.valueOf(currentValue == null ? true : !currentValue); // #147 toggle existing boolean value
}
}
if (noMoreValues && value == null) {
Expand Down Expand Up @@ -1642,7 +1643,7 @@ private Object tryConvert(Field field, int index, ITypeConverter<?> converter, S
} catch (ParameterException ex) {
throw new ParameterException(ex.getMessage() + optionDescription(" for ", field, index));
} catch (Exception other) {
String desc = optionDescription(" for ", field, index);
String desc = optionDescription(" for ", field, index) + ": " + other;
throw new ParameterException("Could not convert '" + value + "' to " + type.getSimpleName() + desc, other);
}
}
Expand Down Expand Up @@ -3597,7 +3598,7 @@ public ParameterException(String msg, Exception ex) {
private static ParameterException create(Exception ex, String arg, int i, String[] args) {
String next = args.length < i + 1 ? "" : " " + args[i + 1];
String msg = ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage()
+ " while processing option[" + i + "] '" + arg + next + "'.";
+ " while processing option[" + i + "] '" + arg + next + "': " + ex.toString();
return new ParameterException(msg, ex);
}
}
Expand Down
128 changes: 87 additions & 41 deletions src/test/java/picocli/CommandLineTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ public void testByteFieldsAreDecimal() {
CommandLine.populateCommand(new SupportedTypes(), "-byte", "0x1F", "-Byte", "0x0F");
fail("Should fail on hex input");
} catch (ParameterException expected) {
assertEquals("Could not convert '0x1F' to byte for option '-byte'", expected.getMessage());
assertEquals("Could not convert '0x1F' to byte for option '-byte'" +
": java.lang.NumberFormatException: For input string: \"0x1F\"", expected.getMessage());
}
}
@Test
Expand Down Expand Up @@ -254,7 +255,8 @@ public void testShortFieldsAreDecimal() {
CommandLine.populateCommand(new SupportedTypes(), "-short", "0xFF", "-Short", "0x6FFE");
fail("Should fail on hex input");
} catch (ParameterException expected) {
assertEquals("Could not convert '0xFF' to short for option '-short'", expected.getMessage());
assertEquals("Could not convert '0xFF' to short for option '-short'" +
": java.lang.NumberFormatException: For input string: \"0xFF\"", expected.getMessage());
}
}
@Test
Expand Down Expand Up @@ -286,7 +288,8 @@ public void testIntFieldsAreDecimal() {
CommandLine.populateCommand(new SupportedTypes(), "-int", "0xFF", "-Integer", "0xFFFF");
fail("Should fail on hex input");
} catch (ParameterException expected) {
assertEquals("Could not convert '0xFF' to int for option '-int'", expected.getMessage());
assertEquals("Could not convert '0xFF' to int for option '-int'" +
": java.lang.NumberFormatException: For input string: \"0xFF\"", expected.getMessage());
}
}
@Test
Expand Down Expand Up @@ -318,7 +321,8 @@ public void testLongFieldsAreDecimal() {
CommandLine.populateCommand(new SupportedTypes(), "-long", "0xAABBCC", "-Long", "0xAABBCCDD");
fail("Should fail on hex input");
} catch (ParameterException expected) {
assertEquals("Could not convert '0xAABBCC' to long for option '-long'", expected.getMessage());
assertEquals("Could not convert '0xAABBCC' to long for option '-long'" +
": java.lang.NumberFormatException: For input string: \"0xAABBCC\"", expected.getMessage());
}
}
@Test
Expand Down Expand Up @@ -354,7 +358,8 @@ public void testSingleValueFieldDefaultMinArityIsOne() {
CommandLine.populateCommand(new SupportedTypes(), "-Long", "-boolean");
fail("should fail");
} catch (ParameterException ex) {
assertEquals("Could not convert '-boolean' to Long for option '-Long'", ex.getMessage());
assertEquals("Could not convert '-boolean' to Long for option '-Long'" +
": java.lang.NumberFormatException: For input string: \"-boolean\"", ex.getMessage());
}
}
@Test
Expand Down Expand Up @@ -439,39 +444,57 @@ public void testCharConverterInvalidError() throws ParseException {
}
@Test
public void testNumberConvertersInvalidError() {
parseInvalidValue("-Byte", "aa");
parseInvalidValue("-byte", "aa");
parseInvalidValue("-Short", "aa");
parseInvalidValue("-short", "aa");
parseInvalidValue("-Integer", "aa");
parseInvalidValue("-int", "aa");
parseInvalidValue("-Long", "aa");
parseInvalidValue("-long", "aa");
parseInvalidValue("-Float", "aa");
parseInvalidValue("-float", "aa");
parseInvalidValue("-Double", "aa");
parseInvalidValue("-double", "aa");
parseInvalidValue("-BigDecimal", "aa");
parseInvalidValue("-BigInteger", "aa");
}
@Test
public void testDomainObjectConvertersInvalidError() {
parseInvalidValue("-URL", ":::");
parseInvalidValue("-URI", ":::");
parseInvalidValue("-Charset", "aa");
parseInvalidValue("-InetAddress", "::a?*!a");
parseInvalidValue("-Pattern", "[[(aa");
parseInvalidValue("-UUID", "aa");
}

private void parseInvalidValue(String option, String value) {
parseInvalidValue("-Byte", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-byte", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-Short", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-short", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-Integer", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-int", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-Long", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-long", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-Float", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-float", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-Double", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-double", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
parseInvalidValue("-BigDecimal", "aa", ": java.lang.NumberFormatException");
parseInvalidValue("-BigInteger", "aa", ": java.lang.NumberFormatException: For input string: \"aa\"");
}
@Test
public void testURLConvertersInvalidError() {
parseInvalidValue("-URL", ":::", ": java.net.MalformedURLException: no protocol: :::");
}
@Test
public void testURIConvertersInvalidError() {
parseInvalidValue("-URI", ":::", ": java.net.URISyntaxException: Expected scheme name at index 0: :::");
}
@Test
public void testCharsetConvertersInvalidError() {
parseInvalidValue("-Charset", "aa", ": java.nio.charset.UnsupportedCharsetException: aa");
}
@Test
public void testInetAddressConvertersInvalidError() {
parseInvalidValue("-InetAddress", "%$::a?*!a", ": java.net.UnknownHostException: %$::a?*!a");
}
@Test
public void testUUIDConvertersInvalidError() {
parseInvalidValue("-UUID", "aa", ": java.lang.IllegalArgumentException: Invalid UUID string: aa");
}
@Test
public void testRegexPatternConverterInvalidError() {
parseInvalidValue("-Pattern", "[[(aa", String.format(": java.util.regex.PatternSyntaxException: Unclosed character class near index 4%n" +
"[[(aa%n" +
" ^"));
}

private void parseInvalidValue(String option, String value, String errorMessage) {
try {
CommandLine.populateCommand(new SupportedTypes(), option, value);
fail("Invalid format " + value + " was accepted for " + option);
} catch (ParameterException expected) {
} catch (ParameterException actual) {
String type = option.substring(1);
assertEquals("Could not convert '" + value + "' to " + type
+ " for option '" + option + "'", expected.getMessage());
String expected = "Could not convert '" + value + "' to " + type + " for option '" + option + "'" + errorMessage;
assertTrue("expected:<" + expected + "> but was:<" + actual.getMessage() + ">",
actual.getMessage().startsWith(actual.getMessage()));
}
}

Expand Down Expand Up @@ -518,7 +541,8 @@ public void testEnumTypeConversionFailsForInvalidInput() {
CommandLine.populateCommand(new EnumParams(), "-timeUnit", "xyz");
fail("Accepted invalid timeunit");
} catch (Exception ex) {
assertEquals("Could not convert 'xyz' to TimeUnit for option '-timeUnit'", ex.getMessage());
assertEquals("Could not convert 'xyz' to TimeUnit for option '-timeUnit'" +
": java.lang.IllegalArgumentException: No enum constant java.util.concurrent.TimeUnit.xyz", ex.getMessage());
}
}
@Ignore("Requires #14 case-insensitive enum parsing")
Expand All @@ -537,7 +561,8 @@ public void testEnumArrayTypeConversionFailsForInvalidInput() {
CommandLine.populateCommand(new EnumParams(), "-timeUnitArray", "a", "b");
fail("Accepted invalid timeunit");
} catch (Exception ex) {
assertEquals("Could not convert 'a' to TimeUnit[] for option '-timeUnitArray' at index 0 (timeUnitArray)", ex.getMessage());
assertEquals("Could not convert 'a' to TimeUnit[] for option '-timeUnitArray' at index 0 (timeUnitArray)" +
": java.lang.IllegalArgumentException: No enum constant java.util.concurrent.TimeUnit.a", ex.getMessage());
}
}
@Test
Expand All @@ -546,7 +571,9 @@ public void testEnumListTypeConversionFailsForInvalidInput() {
CommandLine.populateCommand(new EnumParams(), "-timeUnitList", "DAYS", "b", "c");
fail("Accepted invalid timeunit");
} catch (Exception ex) {
assertEquals("Could not convert 'b' to TimeUnit for option '-timeUnitList' at index 1 (timeUnitList)", ex.getMessage());
assertEquals("Could not convert 'b' to TimeUnit for option '-timeUnitList' at index 1 (timeUnitList)" +
": java.lang.IllegalArgumentException: No enum constant java.util.concurrent.TimeUnit.b",
ex.getMessage());
}
}

Expand Down Expand Up @@ -1805,14 +1832,16 @@ public void testGnuLongOptionsWithVariousSeparatorsOnlyAndNoValue() {
params = CommandLine.populateCommand(new VariousPrefixCharacters(), "--dash=".split(" "));
fail("int option (with sep but no value) needs arg");
} catch (ParameterException ex) {
assertEquals("Could not convert '' to int for option '-d'", ex.getMessage());
assertEquals("Could not convert '' to int for option '-d'" +
": java.lang.NumberFormatException: For input string: \"\"", ex.getMessage());
}

try {
params = CommandLine.populateCommand(new VariousPrefixCharacters(), "--dash= /4".split(" "));
fail("int option (with sep but no value, followed by other option) needs arg");
} catch (ParameterException ex) {
assertEquals("Could not convert '' to int for option '-d'", ex.getMessage());
assertEquals("Could not convert '' to int for option '-d'" +
": java.lang.NumberFormatException: For input string: \"\"", ex.getMessage());
}
}

Expand Down Expand Up @@ -2611,7 +2640,7 @@ public void run() {
String result = baos.toString("UTF8");
assertFalse(runWasCalled[0]);
assertEquals(String.format(
"Could not convert 'not a number' to int for option '-number'%n" +
"Could not convert 'not a number' to int for option '-number': java.lang.NumberFormatException: For input string: \"not a number\"%n" +
"Usage: <main class> [-number=<number>]%n" +
" -number=<number>%n"), result);
}
Expand Down Expand Up @@ -2705,7 +2734,7 @@ class App {
assertEquals("2", ((App) commandLine.getCommand()).string);

commandLine = new CommandLine(new App()).setOverwrittenOptionsAllowed(true);
commandLine.parse("-v", "--verbose");
commandLine.parse("-v", "--verbose", "-v"); // F -> T -> F -> T
assertEquals(true, ((App) commandLine.getCommand()).bool);
}

Expand All @@ -2728,4 +2757,21 @@ class A {
}
commandLine.parse("-u", "foo", "-p", "abc");
}

@Test
public void testToggleBooleanValue() {
class App {
@Option(names = "-a") boolean primitiveFalse = false;
@Option(names = "-b") boolean primitiveTrue = true;
@Option(names = "-c") Boolean objectFalse = false;
@Option(names = "-d") Boolean objectTrue = true;
@Option(names = "-e") Boolean objectNull = null;
}
App app = CommandLine.populateCommand(new App(), "-a -b -c -d -e".split(" "));
assertTrue(app.primitiveFalse);
assertFalse(app.primitiveTrue);
assertTrue(app.objectFalse);
assertFalse(app.objectTrue);
assertTrue(app.objectNull);
}
}

0 comments on commit 8e20136

Please sign in to comment.