Skip to content

Commit

Permalink
[#322] Add defaultValue attribute to @option and @parameters annota…
Browse files Browse the repository at this point in the history
…tion.

Closes #322
  • Loading branch information
remkop committed Jun 29, 2018
1 parent 88b053a commit fd98ff6
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 17 deletions.
3 changes: 3 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class InjectExample implements Runnable {
```




### <a name="3.2.0-lenient-parse"></a> Lenient Parse Mode

Expand All @@ -172,7 +173,9 @@ No features have been promoted in this picocli release.
- [#393] New feature: Add support for JLine completers.
- [#395] New feature: Allow embedding default values anywhere in description for `@Option` or `@Parameters`.
- [#259] New Feature: Added `@Inject` annotation to inject `CommandSpec` into application field.
- [#182] New Feature: Add support for annotating methods with `@Option` and `@Parameters`.
- [#398] Enhancement: Allow `@PicocliScript` annotation on Groovy script `@Field` variables instead of just on imports.
- [#322] Enhancement: Add `defaultValue` attribute to @Option and @Parameters annotation.

## <a name="3.2.0-deprecated"></a> Deprecations
No features were deprecated in this release.
Expand Down
39 changes: 26 additions & 13 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,11 @@ private static class NoCompletionCandidates implements Iterable<String> {
*/
boolean hidden() default false;

/** Returns the default value of this option, before splitting and type conversion.
* @return a String that (after type conversion) will be used as the value for this option if no value was specified on the command line
* @since 3.2 */
String defaultValue() default "__no_default_value__";

/** Use this attribute to control for a specific option whether its default value should be shown in the usage
* help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()}
* is set {@code true} on the command. Use this attribute to specify whether the default value
Expand Down Expand Up @@ -2094,6 +2099,11 @@ private static class NoCompletionCandidates implements Iterable<String> {
*/
boolean hidden() default false;

/** Returns the default value of this positional parameter, before splitting and type conversion.
* @return a String that (after type conversion) will be used as the value for this positional parameter if no value was specified on the command line
* @since 3.2 */
String defaultValue() default "__no_default_value__";

/** Use this attribute to control for a specific positional parameter whether its default value should be shown in the usage
* help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()}
* is set {@code true} on the command. Use this attribute to specify whether the default value
Expand Down Expand Up @@ -2511,20 +2521,16 @@ public <T> T create(Class<T> cls) throws Exception {
}
private static ITypeConverter<?>[] createConverter(IFactory factory, Class<? extends ITypeConverter<?>>[] classes) {
ITypeConverter<?>[] result = new ITypeConverter<?>[classes.length];
for (int i = 0; i < classes.length; i++) {
try {
result[i] = factory.create(classes[i]);
} catch (Exception ex) {
throw new InitializationException("Could not instantiate " + classes[i] + ": " + ex, ex);
}
}
for (int i = 0; i < classes.length; i++) { result[i] = create(factory, classes[i]); }
return result;
}
public static IVersionProvider createVersionProvider(IFactory factory, Class<? extends IVersionProvider> cls) {
try { return factory.create(cls); }
catch (Exception ex) { throw new InitializationException("Could not instantiate " + cls + ": " + ex, ex); }
return create(factory, cls);
}
public static Iterable<String> createCompletionCandidates(IFactory factory, Class<? extends Iterable<String>> cls) {
return create(factory, cls);
}
public static <T> T create(IFactory factory, Class<T> cls) {
try { return factory.create(cls); }
catch (Exception ex) { throw new InitializationException("Could not instantiate " + cls + ": " + ex, ex); }
}
Expand Down Expand Up @@ -3532,6 +3538,7 @@ void initFrom(ParserSpec settings) {
public abstract static class ArgSpec {
static final String DESCRIPTION_VARIABLE_DEFAULT_VALUE = "${DEFAULT-VALUE}";
static final String DESCRIPTION_VARIABLE_COMPLETION_CANDIDATES = "${COMPLETION-CANDIDATES}";
private static final String NO_DEFAULT_VALUE = "__no_default_value__";

// help-related fields
private final boolean hidden;
Expand Down Expand Up @@ -3936,8 +3943,8 @@ abstract static class Builder<T extends Builder<T>> {

/** Sets the default value of this option or positional parameter to the specified value, and returns this builder.
* Before parsing the command line, the result of {@linkplain #splitRegex() splitting} and {@linkplain #converters() type converting}
* this default value is applied to the option or positional parameter. A value of {@code null} means no default. */
public T defaultValue(String defaultValue) { this.defaultValue = defaultValue; return self(); }
* this default value is applied to the option or positional parameter. A value of {@code null} or {@code "__no_default_value__"} means no default. */
public T defaultValue(String defaultValue) { this.defaultValue = NO_DEFAULT_VALUE.equals(defaultValue) ? null : defaultValue; return self(); }

/** Sets the initial value of this option or positional parameter to the specified value, and returns this builder.
* If {@link #hasInitialValue()} is true, the option will be reset to the initial value before parsing (regardless
Expand Down Expand Up @@ -4407,10 +4414,14 @@ static CommandSpec extractCommandSpec(Object command, IFactory factory, boolean
Object instance = command;
Class<?> cls = command.getClass();
String commandClassName = cls.getName();
if (command instanceof Class && ((Class) command).isInterface()) {
if (command instanceof Class) {
cls = (Class) command;
commandClassName = cls.getName();
instance = Proxy.newProxyInstance(cls.getClassLoader(), new Class[] {cls}, new PicocliInvocationHandler());
if (cls.isInterface()) {
instance = Proxy.newProxyInstance(cls.getClassLoader(), new Class[]{cls}, new PicocliInvocationHandler());
} else {
instance = DefaultFactory.create(factory, cls);
}
}

CommandSpec result = CommandSpec.wrapWithoutInspection(Assert.notNull(instance, "command"));
Expand Down Expand Up @@ -4642,6 +4653,7 @@ static OptionSpec extractOptionSpec(TypedMember member, IFactory factory) {
builder.paramLabel(inferLabel(option.paramLabel(), member.name(), member.getType(), elementTypes));
builder.splitRegex(option.split());
builder.hidden(option.hidden());
builder.defaultValue(option.defaultValue());
builder.converters(DefaultFactory.createConverter(factory, option.converter()));
return builder.build();
}
Expand All @@ -4661,6 +4673,7 @@ static PositionalParamSpec extractPositionalParamSpec(TypedMember member, IFacto
builder.paramLabel(inferLabel(parameters.paramLabel(), member.name(), member.getType(), elementTypes));
builder.splitRegex(parameters.split());
builder.hidden(parameters.hidden());
builder.defaultValue(parameters.defaultValue());
builder.converters(DefaultFactory.createConverter(factory, parameters.converter()));
builder.showDefaultValue(parameters.showDefaultValue());
if (!NoCompletionCandidates.class.equals(parameters.completionCandidates())) {
Expand Down
91 changes: 89 additions & 2 deletions src/test/java/picocli/CommandLineAnnotatedMethodImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.junit.Test;
import org.junit.contrib.java.lang.system.ProvideSystemProperty;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

Expand Down Expand Up @@ -33,7 +34,7 @@ static class Primitives {
}

@Test
public void testPrimitiveDefaultValues() {
public void testPrimitivesWithoutDefaultValues() {
Primitives primitives = CommandLine.populateCommand(new Primitives());
assertFalse(primitives.aBoolean);
assertEquals(0, primitives.aByte);
Expand All @@ -44,6 +45,36 @@ public void testPrimitiveDefaultValues() {
assertEquals(0, primitives.aDouble, 0.0001);
}

static class PrimitivesWithDefault {
boolean aBoolean;
byte aByte;
short aShort;
int anInt;
long aLong;
float aFloat;
double aDouble;

@Option(names = "-b", defaultValue = "true") void setBoolean(boolean val) { aBoolean = val; }
@Option(names = "-y", defaultValue = "11") void setByte(byte val) { aByte = val; }
@Option(names = "-s", defaultValue = "12") void setShort(short val) { aShort = val; }
@Option(names = "-i", defaultValue = "13") void setInt(int val) { anInt = val; }
@Option(names = "-l", defaultValue = "14") void setLong(long val) { aLong = val; }
@Option(names = "-f", defaultValue = "15.5") void setFloat(float val) { aFloat = val; }
@Option(names = "-d", defaultValue = "16.6") void setDouble(double val) { aDouble = val; }
}

@Test
public void testPrimitivesWithDefaultValues() {
PrimitivesWithDefault primitives = CommandLine.populateCommand(new PrimitivesWithDefault());
assertTrue(primitives.aBoolean);
assertEquals(11, primitives.aByte);
assertEquals((short) 12, primitives.aShort);
assertEquals(13, primitives.anInt);
assertEquals(14, primitives.aLong);
assertEquals(15.5, primitives.aFloat, 0.0001);
assertEquals(16.6, primitives.aDouble, 0.0001);
}

@Test
public void testPrimitives() {
String[] args = "-b -y1 -s2 -i3 -l4 -f5 -d6".split(" ");
Expand All @@ -57,6 +88,62 @@ public void testPrimitives() {
assertEquals(6, primitives.aDouble, 0.0001);
}

static class ObjectsWithDefaults {
Boolean aBoolean;
Byte aByte;
Short aShort;
Integer anInt;
Long aLong;
Float aFloat;
Double aDouble;
BigDecimal aBigDecimal;
String aString;
List<String> aList;
Map<Integer, Double> aMap;
SortedSet<Short> aSet;

@Option(names = "-b", defaultValue = "true") void setBoolean(Boolean val) { aBoolean = val; }
@Option(names = "-y", defaultValue = "123") void setByte(Byte val) { aByte = val; }
@Option(names = "-s", defaultValue = "11") void setShort(Short val) { aShort = val; }
@Option(names = "-i", defaultValue = "12") void setInt(Integer val) { anInt = val; }
@Option(names = "-l", defaultValue = "13") void setLong(Long val) { aLong = val; }
@Option(names = "-f", defaultValue = "14.4") void setFloat(Float val) { aFloat = val; }
@Option(names = "-d", defaultValue = "15.5") void setDouble(Double val) { aDouble = val; }

@Option(names = "-bigint", defaultValue = "16.6") void setBigDecimal(BigDecimal val) { aBigDecimal = val; }
@Option(names = "-string", defaultValue = "abc") void setString(String val) { aString = val; }
@Option(names = "-list", defaultValue = "a,b,c", split = ",") void setList(List<String> val) { aList = val; }

@Option(names = "-map", defaultValue = "1=1,2=2,3=3", split = ",")
void setMap(Map<Integer, Double> val) { aMap = val; }

@Option(names = "-set", defaultValue = "1,2,3", split = ",")
void setSortedSet(SortedSet<Short> val) { aSet = val; }
}

@Test
public void testObjectsWithDefaultValues() {
CommandLine cmd = new CommandLine(ObjectsWithDefaults.class);
cmd.parse();
ObjectsWithDefaults objects = cmd.getCommand();
assertTrue(objects.aBoolean);
assertEquals(Byte.valueOf((byte) 123), objects.aByte);
assertEquals(Short.valueOf((short) 11), objects.aShort);
assertEquals(Integer.valueOf(12), objects.anInt);
assertEquals(Long.valueOf(13), objects.aLong);
assertEquals(14.4f, objects.aFloat, 0.0001);
assertEquals(15.5d, objects.aDouble, 0.0001);
assertEquals(new BigDecimal("16.6"), objects.aBigDecimal);
assertEquals("abc", objects.aString);
assertEquals(Arrays.asList("a", "b", "c"), objects.aList);
Map<Integer, Double> map = new HashMap<Integer, Double>();
map.put(1, 1.0);
map.put(2, 2.0);
map.put(3, 3.0);
assertEquals(map, objects.aMap);
assertEquals(new TreeSet<Short>(Arrays.asList((short)1, (short)2, (short)3)), objects.aSet);
}

static class Objects {
Boolean aBoolean;
Byte aByte;
Expand Down Expand Up @@ -91,7 +178,7 @@ static class Objects {
}

@Test
public void testObjectsDefaultValues() {
public void testObjectsWithoutDefaultValues() {
Objects objects = CommandLine.populateCommand(new Objects());
assertNull(objects.aBoolean);
assertNull(objects.aByte);
Expand Down
105 changes: 103 additions & 2 deletions src/test/java/picocli/CommandLineAnnotatedMethodSpecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.junit.*;
import org.junit.contrib.java.lang.system.ProvideSystemProperty;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -43,15 +44,39 @@ interface Primitives {
double aDouble();
}

interface PrimitivesWithDefault {
@Option(names = "-b", defaultValue = "true")
boolean aBoolean();

@Option(names = "-y", defaultValue = "11")
byte aByte();

@Option(names = "-s", defaultValue = "12")
short aShort();

@Option(names = "-i", defaultValue = "13")
int anInt();

@Option(names = "-l", defaultValue = "14")
long aLong();

@Option(names = "-f", defaultValue = "15.5")
float aFloat();

@Option(names = "-d", defaultValue = "16.6")
double aDouble();
}

@Test
public void testInterfaceIsInstantiated() {
CommandLine cmd = new CommandLine(Primitives.class);
assertTrue(cmd.getCommand() instanceof Primitives);
}

@Test
public void testPrimitiveDefaultValues() {
public void testPrimitiveWithoutDefaultValues() {
CommandLine cmd = new CommandLine(Primitives.class);
cmd.parse();
Primitives primitives = cmd.getCommand();
assertFalse(primitives.aBoolean());
assertEquals(0, primitives.aByte());
Expand All @@ -62,6 +87,20 @@ public void testPrimitiveDefaultValues() {
assertEquals(0, primitives.aDouble(), 0.0001);
}

@Test
public void testPrimitivesWithDefaultValues() {
CommandLine cmd = new CommandLine(PrimitivesWithDefault.class);
cmd.parse();
PrimitivesWithDefault primitives = cmd.getCommand();
assertTrue(primitives.aBoolean());
assertEquals(11, primitives.aByte());
assertEquals((short) 12, primitives.aShort());
assertEquals(13, primitives.anInt());
assertEquals(14, primitives.aLong());
assertEquals(15.5f, primitives.aFloat(), 0.0001);
assertEquals(16.6d, primitives.aDouble(), 0.0001);
}

@Test
public void testPrimitives() {
CommandLine cmd = new CommandLine(Primitives.class);
Expand Down Expand Up @@ -114,9 +153,48 @@ interface Objects {
SortedSet<Short> getSortedSet();
}

interface ObjectsWithDefault {
@Option(names = "-b", defaultValue = "true")
Boolean aBoolean();

@Option(names = "-y", defaultValue = "123")
Byte aByte();

@Option(names = "-s", defaultValue = "11")
Short aShort();

@Option(names = "-i", defaultValue = "12")
Integer anInt();

@Option(names = "-l", defaultValue = "13")
Long aLong();

@Option(names = "-f", defaultValue = "14.4")
Float aFloat();

@Option(names = "-d", defaultValue = "15.5")
Double aDouble();

@Option(names = "-bigint", defaultValue = "16.6")
BigDecimal aBigDecimal();

@Option(names = "-string", defaultValue = "abc")
String aString();

@Option(names = "-list", defaultValue = "a,b,c", split = ",")
List<String> getList();

@Option(names = "-map", defaultValue = "1=1,2=2,3=3", split = ",")
Map<Integer, Double> getMap();

@Option(names = "-set", defaultValue = "1,2,3", split = ",")
SortedSet<Short> getSortedSet();
}

@Test
public void testObjectsDefaultValues() {
public void testObjectsWithoutDefaultValues() {
CommandLine cmd = new CommandLine(Objects.class);
cmd.parse();
Objects objects = cmd.getCommand();
assertFalse(objects.aBoolean());
assertEquals(Byte.valueOf((byte) 0), objects.aByte());
Expand All @@ -132,6 +210,29 @@ public void testObjectsDefaultValues() {
assertNull(objects.getSortedSet());
}

@Test
public void testObjectsWithDefaultValues() {
CommandLine cmd = new CommandLine(ObjectsWithDefault.class);
cmd.parse();
ObjectsWithDefault objects = cmd.getCommand();
assertTrue(objects.aBoolean());
assertEquals(Byte.valueOf((byte) 123), objects.aByte());
assertEquals(Short.valueOf((short) 11), objects.aShort());
assertEquals(Integer.valueOf(12), objects.anInt());
assertEquals(Long.valueOf(13), objects.aLong());
assertEquals(14.4f, objects.aFloat(), 0.0001);
assertEquals(15.5d, objects.aDouble(), 0.0001);
assertEquals(new BigDecimal("16.6"), objects.aBigDecimal());
assertEquals("abc", objects.aString());
assertEquals(Arrays.asList("a", "b", "c"), objects.getList());
Map<Integer, Double> map = new HashMap<Integer, Double>();
map.put(1, 1.0);
map.put(2, 2.0);
map.put(3, 3.0);
assertEquals(map, objects.getMap());
assertEquals(new TreeSet<Short>(Arrays.asList((short)1, (short)2, (short)3)), objects.getSortedSet());
}

@Test
public void testObjects() {
CommandLine cmd = new CommandLine(Objects.class);
Expand Down

0 comments on commit fd98ff6

Please sign in to comment.