Skip to content

Commit eb4c66b

Browse files
committed
release 1.7
1 parent 3f20465 commit eb4c66b

File tree

7 files changed

+76
-64
lines changed

7 files changed

+76
-64
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ non-options do not stop option parsing, so options and non-options can be in any
2121
* Short args can be written `-n1` or `-n 1` style.
2222
* Long args can be written `--num=1` or `--num 1` style.
2323
* A long flag is written `--zap`, a short flag `-z`.
24+
* Grouping is possible, as in 'tar -xzf d.tgz'.
2425
* "Non-options", like in `rm foo.txt`: Use `@OtherTokens`
2526
* "End of option scanning", like in `rm -- foo.txt`: Use `@EverythingAfter("--")`
2627

@@ -149,7 +150,7 @@ If you're not familiar with `rm`'s `--` option, try `echo >>-f` and deleting the
149150
<dependency>
150151
<groupId>com.github.h908714124</groupId>
151152
<artifactId>jbock</artifactId>
152-
<version>1.6</version>
153+
<version>1.7</version>
153154
<scope>provided</scope>
154155
</dependency>
155156
````

core/src/main/java/net/jbock/compiler/Analyser.java

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import static javax.lang.model.element.Modifier.PRIVATE;
2626
import static javax.lang.model.element.Modifier.PUBLIC;
2727
import static javax.lang.model.element.Modifier.STATIC;
28-
import static net.jbock.compiler.Option.constructorArgumentsForJavadoc;
2928

3029
final class Analyser {
3130

@@ -56,6 +55,7 @@ final class Analyser {
5655
private final MethodSpec readOption;
5756
private final MethodSpec checkConflict;
5857
private final MethodSpec readArgument;
58+
private final MethodSpec removeFirstFlag;
5959

6060
private final FieldSpec longNames;
6161
private final FieldSpec shortNames;
@@ -83,6 +83,7 @@ private Analyser(Constructor constructor) {
8383
this.optionMapType = ParameterizedTypeName.get(ClassName.get(Map.class),
8484
option.optionClass, listOfArgumentType);
8585
this.readArgument = readArgumentMethod();
86+
this.removeFirstFlag = removeFirstFlagMethod();
8687
this.optMap = FieldSpec.builder(optionMapType, "optMap")
8788
.addModifiers(PRIVATE, FINAL)
8889
.build();
@@ -95,7 +96,21 @@ private Analyser(Constructor constructor) {
9596
this.readOption = readOptionMethod(keysClass, longNames, shortNames, option.optionClass);
9697
this.checkConflict = checkConflictMethod(optionMapType, option.optionClass, optionTypeClass, optionType);
9798
this.read = readMethod(keysClass, readOption, readArgument, optionMapType,
98-
option.optionClass, optionType, optionTypeClass, checkConflict);
99+
option.optionClass, optionType, optionTypeClass, checkConflict, removeFirstFlag);
100+
}
101+
102+
private static MethodSpec removeFirstFlagMethod() {
103+
ParameterSpec token = ParameterSpec.builder(STRING, "token").build();
104+
return MethodSpec.methodBuilder("removeFirstFlag")
105+
.beginControlFlow("if ($N.length() <= 2 || $N.startsWith($S))",
106+
token, token, "--")
107+
.addStatement("return null")
108+
.endControlFlow()
109+
.addStatement("return $S + $N.substring(2)", "-", token)
110+
.addParameter(token)
111+
.addModifiers(PRIVATE, STATIC)
112+
.returns(STRING)
113+
.build();
99114
}
100115

101116
private static MethodSpec checkConflictMethod(TypeName optionMapType, ClassName optionClass,
@@ -136,6 +151,7 @@ TypeSpec analyse() {
136151
.addMethod(readOption)
137152
.addMethod(readArgument)
138153
.addMethod(checkConflict)
154+
.addMethod(removeFirstFlag)
139155
.addModifiers(PUBLIC, FINAL)
140156
.build();
141157
}
@@ -177,21 +193,10 @@ private MethodSpec parseMethod() {
177193
read, token, names, optMap, otherTokens, it)
178194
.endControlFlow()
179195
.addStatement("return new $T($N, $N, $N)", binderClass, optMap, otherTokens, rest);
180-
TypeName originalClass = constructor.enclosingType;
181196
return MethodSpec.methodBuilder("parse")
182197
.addParameter(ARGS)
183198
.addCode(builder.build())
184199
.addException(IllegalArgumentException.class)
185-
.addJavadoc("Parses the command line arguments and performs basic validation.\n" +
186-
"\n" +
187-
"@param args command line arguments\n" +
188-
"@throws $T if the input is invalid or ambiguous\n" +
189-
"@return a binder for constructing {@link $T}\n" +
190-
"\n" +
191-
"@see $T#$T($L)\n",
192-
IllegalArgumentException.class,
193-
constructor.enclosingType,
194-
originalClass, originalClass, constructorArgumentsForJavadoc(constructor))
195200
.returns(binderClass)
196201
.addModifiers(PUBLIC, STATIC)
197202
.build();
@@ -267,7 +272,10 @@ private static MethodSpec readMethod(ClassName keysClass,
267272
MethodSpec readArgument,
268273
TypeName optionMapType,
269274
ClassName optionClass,
270-
FieldSpec optionType, ClassName optionTypeClass, MethodSpec checkConflict) {
275+
FieldSpec optionType,
276+
ClassName optionTypeClass,
277+
MethodSpec checkConflict,
278+
MethodSpec removeFirstFlag) {
271279
ParameterSpec names = ParameterSpec.builder(keysClass, "names").build();
272280
ParameterSpec optMap = ParameterSpec.builder(optionMapType, "optMap").build();
273281
ParameterSpec otherTokens = ParameterSpec.builder(STRING_LIST, "otherTokens").build();
@@ -287,12 +295,23 @@ private static MethodSpec readMethod(ClassName keysClass,
287295
.addStatement("$N($N, $N, $N)", checkConflict, optMap, option, token)
288296
.addStatement("$T $N = $N.computeIfAbsent($N, $N -> new $T<>())",
289297
bucket.type, bucket, optMap, option, ignore, ArrayList.class)
290-
.beginControlFlow("if ($N.$N == $T.$L)", option, optionType, optionTypeClass, OptionType.FLAG)
291-
.add("// add some non-null string to represent the flag\n")
298+
.beginControlFlow("while ($N.$N == $T.$L)", option, optionType, optionTypeClass, OptionType.FLAG)
292299
.addStatement("$N.add($S)", bucket, "t")
293-
.addStatement("return")
300+
.addStatement("$N = $N($N)", token, removeFirstFlag, token)
301+
.beginControlFlow("if ($N == null)", token)
302+
.addStatement("break")
303+
.endControlFlow()
304+
.addStatement("$N = $N($N, $N)", option, readOption, names, token)
305+
.beginControlFlow("if ($N == null)", option)
306+
.addStatement("throw new $T($S)", IllegalArgumentException.class,
307+
"invalid token")
308+
.endControlFlow()
309+
.addStatement("$N = $N.computeIfAbsent($N, $N -> new $T<>())",
310+
bucket, optMap, option, ignore, ArrayList.class)
294311
.endControlFlow()
295-
.addStatement("$N.add($N($N, $N))", bucket, readArgument, token, it);
312+
.beginControlFlow("if ($N.type != $T.$L)", option, optionTypeClass, OptionType.FLAG)
313+
.addStatement("$N.add($N($N, $N))", bucket, readArgument, token, it)
314+
.endControlFlow();
296315
//@formatter:on
297316
return MethodSpec.methodBuilder("read")
298317
.addParameters(Arrays.asList(token, names, optMap, otherTokens, it))

core/src/main/java/net/jbock/compiler/Binder.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ TypeSpec define() {
5959
.addMethod(privateConstructor())
6060
.addMethod(bindMethod())
6161
.addMethod(otherTokensMethod())
62-
.addJavadoc("Parsed arguments, ready to be passed to the constructor.\n\n" +
63-
"@see $T#$T($L)\n", originalClass, originalClass,
64-
Option.constructorArgumentsForJavadoc(constructor))
6562
.build();
6663
}
6764

@@ -91,32 +88,17 @@ private MethodSpec bindMethod() {
9188
}
9289
}
9390
builder.add(");\n");
94-
TypeName originalClass = constructor.enclosingType;
95-
StringBuilder javadoc = new StringBuilder();
96-
javadoc.append("Invokes the constructor.\n")
97-
.append("\n");
98-
for (TypeName thrownType : constructor.thrownTypes) {
99-
javadoc.append("@throws ").append(thrownType.toString()).append("\n");
100-
}
101-
javadoc.append("@return an instance of {@link $T}\n");
10291
return MethodSpec.methodBuilder("bind")
10392
.addCode(builder.build())
10493
.addExceptions(constructor.thrownTypes)
10594
.addModifiers(PUBLIC)
106-
.addJavadoc(javadoc.toString(), originalClass)
10795
.returns(constructor.enclosingType)
10896
.build();
10997
}
11098

11199
private MethodSpec otherTokensMethod() {
112100
return MethodSpec.methodBuilder("otherTokens")
113101
.addStatement("return $N", otherTokens)
114-
.addJavadoc("Collection of all unbound tokens.\n" +
115-
"Unless @OtherTokens is used in the constructor,\n" +
116-
"a good practice is to verify that this list is empty\n" +
117-
"before invoking {@link #bind()}.\n" +
118-
"\n" +
119-
"@return tokens that the parser ignored, an unmodifiable list\n")
120102
.returns(otherTokens.type)
121103
.addModifiers(PUBLIC)
122104
.build();

core/src/main/java/net/jbock/compiler/Option.java

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ static String constructorArgumentsForJavadoc(Constructor constructor) {
9090
}
9191

9292
TypeSpec define() {
93-
TypeSpec.Builder builder = TypeSpec.enumBuilder(optionClass)
94-
.addJavadoc("The enum constants correspond to the constructor arguments.\n");
93+
TypeSpec.Builder builder = TypeSpec.enumBuilder(optionClass);
9594
for (int i = 0; i < constructor.parameters.size(); i++) {
9695
Param param = constructor.parameters.get(i);
9796
String[] desc = getText(param.description);
@@ -153,9 +152,6 @@ private static MethodSpec shortNameMethod() {
153152
return MethodSpec.methodBuilder(SHORT_NAME.name)
154153
.addStatement("return $T.toString($N, null)", Objects.class, SHORT_NAME)
155154
.returns(STRING)
156-
.addJavadoc("The short name is exactly one character in length, or null.\n" +
157-
"\n" +
158-
"@return short name, without the '-'; possibly null\n")
159155
.addModifiers(PUBLIC)
160156
.build();
161157
}
@@ -164,9 +160,6 @@ private static MethodSpec longNameMethod() {
164160
return MethodSpec.methodBuilder(LONG_NAME.name)
165161
.addStatement("return $N", LONG_NAME)
166162
.returns(LONG_NAME.type)
167-
.addJavadoc("The long name is at least one character in length, or null.\n" +
168-
"\n" +
169-
"@return long name, without the '--'; possibly null\n")
170163
.addModifiers(PUBLIC)
171164
.build();
172165
}
@@ -175,15 +168,13 @@ private MethodSpec descriptionMethod() {
175168
return MethodSpec.methodBuilder(DESCRIPTION.name)
176169
.addStatement("return $N", DESCRIPTION)
177170
.returns(DESCRIPTION.type)
178-
.addJavadoc("@return description lines\n")
179171
.addModifiers(PUBLIC)
180172
.build();
181173
}
182174

183175
private static MethodSpec descriptionParameterMethod() {
184176
return MethodSpec.methodBuilder(ARGUMENT_NAME.name)
185177
.addStatement("return $N", ARGUMENT_NAME)
186-
.addJavadoc("@return example parameter name, possibly null\n")
187178
.returns(ARGUMENT_NAME.type)
188179
.addModifiers(PUBLIC)
189180
.build();
@@ -192,7 +183,6 @@ private static MethodSpec descriptionParameterMethod() {
192183
private MethodSpec typeMethod() {
193184
return MethodSpec.methodBuilder(optionType.name)
194185
.addStatement("return $N", optionType)
195-
.addJavadoc("@return option type\n")
196186
.returns(optionType.type)
197187
.addModifiers(PUBLIC)
198188
.build();
@@ -242,10 +232,6 @@ private static MethodSpec descriptionBlockMethod() {
242232
.addParameter(indent)
243233
.returns(Analyser.STRING)
244234
.addCode(builder.build())
245-
.addJavadoc("Get the argument description.\n" +
246-
"\n" +
247-
"@param indent number of space characters to indent the description with\n" +
248-
"@return printable description\n")
249235
.build();
250236
}
251237

@@ -264,10 +250,6 @@ private MethodSpec describeMethod() {
264250
.addModifiers(PUBLIC)
265251
.returns(Analyser.STRING)
266252
.addParameter(indent)
267-
.addJavadoc("Convenience method to get a formatted description of the argument.\n" +
268-
"\n" +
269-
"@param indent number of space characters to indent the description with\n" +
270-
"@return printable description\n")
271253
.addCode(codeBlock)
272254
.build();
273255
}
@@ -310,9 +292,6 @@ private static MethodSpec describeNamesMethod(FieldSpec optionType, ClassName op
310292
.addModifiers(PUBLIC)
311293
.returns(Analyser.STRING)
312294
.addCode(builder.build())
313-
.addJavadoc("Get a basic description of the argument name and type.\n" +
314-
"\n" +
315-
"@return printable description\n")
316295
.build();
317296
}
318297
}

core/src/main/java/net/jbock/compiler/OptionType.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ static TypeSpec define(ClassName optionTypeClass) {
1515
builder.addEnumConstant(optionType.name());
1616
}
1717
return builder.addModifiers(PUBLIC)
18-
.addJavadoc("The possible option types.\n")
1918
.build();
2019
}
2120
}

examples/src/main/java/net/zerobuilder/examples/gradle/GradleMan.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ final class GradleMan {
1616
final List<String> file;
1717
final String dir;
1818
final boolean cmos;
19+
final boolean verbose;
1920

2021
static final class Foo {
2122
final String bar;
@@ -41,10 +42,13 @@ static final class Foo {
4142
String dir,
4243
@ShortName('c')
4344
@Description("cmos flag")
44-
boolean cmos) throws IOException, NullPointerException {
45+
boolean cmos,
46+
@ShortName('v')
47+
boolean verbose) throws IOException, NullPointerException {
4548
this.message = message;
4649
this.file = file;
4750
this.dir = dir;
4851
this.cmos = cmos;
52+
this.verbose = verbose;
4953
}
5054
}

examples/src/test/java/net/zerobuilder/examples/gradle/GradleManTest.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import java.util.stream.Collectors;
1313

1414
import static java.util.Arrays.asList;
15-
import static java.util.Collections.singletonList;
1615
import static org.hamcrest.CoreMatchers.nullValue;
1716
import static org.hamcrest.core.Is.is;
1817
import static org.junit.Assert.assertThat;
@@ -159,10 +158,39 @@ public void testNonsense() throws Exception {
159158
assertThat(binder.otherTokens().size(), is(2));
160159
}
161160

161+
@Test
162+
public void testFlagWithTrailingGarbage() throws Exception {
163+
exception.expect(IllegalArgumentException.class);
164+
GradleMan_Parser.parse(new String[]{"-c1"});
165+
}
166+
167+
@Test
168+
public void testDoubleFlag() throws Exception {
169+
GradleMan gradleMan = GradleMan_Parser.parse(new String[]{"-cv"}).bind();
170+
assertThat(gradleMan.cmos, is(true));
171+
assertThat(gradleMan.verbose, is(true));
172+
}
173+
174+
@Test
175+
public void testDoubleFlagWithAttachedOption() throws Exception {
176+
GradleMan gradleMan = GradleMan_Parser.parse(new String[]{"-cvmhello"}).bind();
177+
assertThat(gradleMan.cmos, is(true));
178+
assertThat(gradleMan.verbose, is(true));
179+
assertThat(gradleMan.message, is("hello"));
180+
}
181+
182+
@Test
183+
public void testDoubleFlagWithDetachedOption() throws Exception {
184+
GradleMan gradleMan = GradleMan_Parser.parse(new String[]{"-cvm", "hello"}).bind();
185+
assertThat(gradleMan.cmos, is(true));
186+
assertThat(gradleMan.verbose, is(true));
187+
assertThat(gradleMan.message, is("hello"));
188+
}
189+
162190
@Test
163191
public void testOptions() throws Exception {
164192
Option[] options = Option.values();
165-
assertThat(options.length, is(4));
193+
assertThat(options.length, is(5));
166194
assertThat(Arrays.stream(options)
167195
.filter(o -> o.type() != OptionType.FLAG)
168196
.map(Option::longName)
@@ -180,7 +208,7 @@ public void testOptions() throws Exception {
180208
.map(Option::shortName)
181209
.filter(Objects::nonNull)
182210
.collect(Collectors.toSet()),
183-
is(new HashSet<>(singletonList("c"))));
211+
is(new HashSet<>(asList("c", "v"))));
184212
}
185213

186214
@Test

0 commit comments

Comments
 (0)