Skip to content

Commit 4aa5c28

Browse files
committed
Parse combined short options
WIP. Error handling is actual missing.
1 parent 79b4451 commit 4aa5c28

2 files changed

Lines changed: 158 additions & 12 deletions

File tree

de.tototec.cmdoption/src/main/java/de/tototec/cmdoption/CmdlineParser.java

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import de.tototec.cmdoption.handler.PutIntoMapHandler;
3636
import de.tototec.cmdoption.handler.StringFieldHandler;
3737
import de.tototec.cmdoption.handler.StringMethodHandler;
38+
import de.tototec.cmdoption.internal.F0;
3839
import de.tototec.cmdoption.internal.F1;
3940
import de.tototec.cmdoption.internal.FList;
4041
import de.tototec.cmdoption.internal.I18n;
@@ -114,6 +115,8 @@ public class CmdlineParser {
114115

115116
private Optional<String> argsFromFilePrefix = Optional.some("@");
116117

118+
private Optional<String> aggregateShortOptionsWithPrefix = Optional.none();
119+
117120
protected CmdlineParser(final CmdlineParser parent, final String commandName, final Object commandObject) {
118121
this.parent = parent;
119122
debugAllowed = parent.debugAllowed;
@@ -359,9 +362,35 @@ public List<String> apply(final String arg) {
359362

360363
boolean helpDetected = false;
361364

362-
// Actually iterate over the command line elements
363-
for (int index = 0; index < cmdline.length; ++index) {
364-
final String param = cmdline[index];
365+
final String aggregatePrefix = aggregateShortOptionsWithPrefix.getOrElse(new F0<String>() {
366+
@Override
367+
public String apply() {
368+
return "";
369+
}
370+
});
371+
final LinkedHashMap<String, OptionHandle> shortOptionMap = new LinkedHashMap<String, OptionHandle>();
372+
final int aggregatePrefixSize = aggregatePrefix.length();
373+
if (aggregateShortOptionsWithPrefix.isDefined()) {
374+
final int expectedSize = 1 + aggregatePrefixSize;
375+
for (final Entry<String, OptionHandle> oh : quickOptionMap.entrySet()) {
376+
if (oh.getKey().startsWith(aggregatePrefix) && oh.getKey().length() == expectedSize) {
377+
shortOptionMap.put(oh.getKey().substring(aggregatePrefixSize), oh.getValue());
378+
}
379+
}
380+
}
381+
382+
int index = -1;
383+
String[] rest = cmdline;
384+
385+
while (rest.length > index + 1) {
386+
if (index >= 0) {
387+
rest = Arrays.copyOfRange(rest, ++index, rest.length);
388+
}
389+
index = 0;
390+
391+
// Actually iterate over the command line elements
392+
// for (int index = 0; index < cmdline.length; ++index) {
393+
final String param = rest[index];
365394
if (parseOptions && stopOption.equals(param)) {
366395
parseOptions = false;
367396

@@ -380,20 +409,20 @@ public List<String> apply(final String arg) {
380409
helpDetected = true;
381410
}
382411

383-
if (cmdline.length <= index + optionHandle.getArgsCount()) {
412+
if (rest.length <= index + optionHandle.getArgsCount()) {
384413
final PreparedI18n msg = i18n.preparetr(
385414
"Missing arguments(s): {0}. Option \"{1}\" requires {2} arguments, but you gave {3}.",
386415
FList.mkString(
387-
Arrays.asList(optionHandle.getArgs()).subList(cmdline.length - index - 1,
416+
Arrays.asList(optionHandle.getArgs()).subList(rest.length - index - 1,
388417
optionHandle.getArgsCount()),
389418
", "),
390419
param, optionHandle
391420
.getArgsCount(),
392-
cmdline.length - index - 1);
421+
rest.length - index - 1);
393422
throw new CmdlineParserException(msg.notr(), msg.tr());
394423
}
395424
// slurp next cmdline arguments into option arguments
396-
final String[] optionArgs = Arrays.copyOfRange(cmdline, index + 1,
425+
final String[] optionArgs = Arrays.copyOfRange(rest, index + 1,
397426
index + 1 + optionHandle.getArgsCount());
398427
index += optionHandle.getArgsCount();
399428

@@ -427,9 +456,40 @@ public List<String> apply(final String arg) {
427456
}
428457
// Delegate parsing of the rest of the cmdline to the command
429458
commandHandle.getCmdlineParser().parse(dryrun, detectHelpAndSkipValidation,
430-
Arrays.copyOfRange(cmdline, index + 1, cmdline.length));
459+
Arrays.copyOfRange(rest, index + 1, rest.length));
431460
// Stop parsing
432461
break;
462+
} else if (parseOptions
463+
&& aggregateShortOptionsWithPrefix.isDefined()
464+
&& param.startsWith(aggregatePrefix)
465+
&& param.length() > aggregatePrefixSize + 1) {
466+
// Found an aggregated short option
467+
final char[] singleOptions = param.substring(aggregatePrefixSize).toCharArray();
468+
// rewrite the cmdline
469+
final List<String> rewritten = new LinkedList<String>();
470+
int procCount = 1;
471+
for (final char c : singleOptions) {
472+
final OptionHandle oh = shortOptionMap.get(String.valueOf(c));
473+
if (oh == null) {
474+
// FIXME: unsupported aggregation found
475+
}
476+
if(rest.length < procCount + oh.getArgsCount()) {
477+
// FIXME: missing args detected
478+
}
479+
// add as standalone short option
480+
rewritten.add(aggregatePrefix + c);
481+
for (int i = 0; i < oh.getArgsCount(); ++i) {
482+
// slurp args from cmdline
483+
rewritten.add(rest[i + procCount]);
484+
++procCount;
485+
}
486+
}
487+
// re-interate parsing with the modified command line
488+
final String[] newRest = Arrays.copyOfRange(rest, procCount, rest.length);
489+
rewritten.addAll(Arrays.asList(newRest));
490+
rest = rewritten.toArray(new String[0]);
491+
index = -1;
492+
continue;
433493

434494
} else if (parameter == null && defaultCommandName != null
435495
&& quickCommandMap.containsKey(defaultCommandName)) {
@@ -442,24 +502,24 @@ public List<String> apply(final String arg) {
442502
}
443503
// Delegate parsing of the rest of the cmdline to the command
444504
commandHandle.getCmdlineParser().parse(dryrun, detectHelpAndSkipValidation,
445-
Arrays.copyOfRange(cmdline, index, cmdline.length));
505+
Arrays.copyOfRange(rest, index, rest.length));
446506
// Stop parsing
447507
break;
448508

449509
} else if (parameter != null) {
450510
// Found a parameter
451511
optionCount.put(parameter, optionCount.get(parameter) + 1);
452512

453-
if (cmdline.length <= index + parameter.getArgsCount() - 1) {
454-
final int countOfGivenParams = cmdline.length - index;
513+
if (rest.length <= index + parameter.getArgsCount() - 1) {
514+
final int countOfGivenParams = rest.length - index;
455515
final PreparedI18n msg = i18n.preparetr(
456516
"Missing arguments: {0} Parameter requires {1} arguments, but you gave {2}.",
457517
Arrays.asList(parameter.getArgs()).subList(countOfGivenParams, parameter.getArgsCount()),
458518
parameter.getArgsCount(), countOfGivenParams);
459519
throw new CmdlineParserException(msg.notr(), msg.tr());
460520
}
461521
// slurp next cmdline arguments into option arguments
462-
final String[] optionArgs = Arrays.copyOfRange(cmdline, index, index + parameter.getArgsCount());
522+
final String[] optionArgs = Arrays.copyOfRange(rest, index, index + parameter.getArgsCount());
463523
// -1, because index gets increased by one at end of for-loop
464524
index += parameter.getArgsCount() - 1;
465525

@@ -704,6 +764,8 @@ protected void validateOptions() {
704764
}
705765
}
706766
}
767+
// TODO: Ensure, there are no long options, starting with the aggregated short
768+
// option prefix, and when, disable this feature
707769
}
708770

709771
protected boolean isVisible(final Class<?> baseClass, final Member element) {
@@ -1025,4 +1087,12 @@ public void setReadArgsFromFilePrefix(final String prefix) {
10251087
}
10261088
}
10271089

1090+
public void setAggregateShortOptionsWithPrefix(final String prefix) {
1091+
if (prefix == null || prefix.trim().isEmpty()) {
1092+
aggregateShortOptionsWithPrefix = Optional.none();
1093+
} else {
1094+
aggregateShortOptionsWithPrefix = Optional.some(prefix.trim());
1095+
}
1096+
}
1097+
10281098
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package de.tototec.cmdoption;
2+
3+
import static de.tobiasroeser.lambdatest.Expect.expectEquals;
4+
import static de.tobiasroeser.lambdatest.Expect.expectFalse;
5+
import static de.tobiasroeser.lambdatest.Expect.expectTrue;
6+
7+
import de.tobiasroeser.lambdatest.testng.FreeSpec;
8+
9+
public class AggregateShortOptionsTest extends FreeSpec {
10+
11+
public static class Config {
12+
@CmdOption(names = { "-f", "--file" }, args = { "FILE" })
13+
String file = null;
14+
15+
@CmdOption(names = { "-l" })
16+
boolean formatLong = false;
17+
18+
@CmdOption(names = { "-s", "--size" })
19+
boolean showSize = false;
20+
}
21+
22+
public AggregateShortOptionsTest() {
23+
24+
test("Setting all short options separate should work (reference test)", () -> {
25+
final Config config = new Config();
26+
final CmdlineParser cp = new CmdlineParser(config);
27+
cp.setAggregateShortOptionsWithPrefix("-");
28+
cp.parse(new String[] { "-l", "-s", "-f", "file.txt" });
29+
expectTrue(config.formatLong);
30+
expectTrue(config.showSize);
31+
expectEquals(config.file, "file.txt");
32+
});
33+
34+
test("Aggregation is not supported when disabled", () -> {
35+
final Config config = new Config();
36+
final CmdlineParser cp = new CmdlineParser(config);
37+
cp.setAggregateShortOptionsWithPrefix(null);
38+
intercept(CmdlineParserException.class, "Unsupported option or parameter found: -ls", () -> {
39+
cp.parse(new String[] { "-ls" });
40+
});
41+
});
42+
43+
test("Combining two short options without args should work", () -> {
44+
final Config config = new Config();
45+
final CmdlineParser cp = new CmdlineParser(config);
46+
cp.setAggregateShortOptionsWithPrefix("-");
47+
cp.parse(new String[] { "-ls" });
48+
expectTrue(config.formatLong);
49+
expectTrue(config.showSize);
50+
expectEquals(config.file, null);
51+
});
52+
53+
test("Combining two short options with args at end should work", () -> {
54+
final Config config = new Config();
55+
final CmdlineParser cp = new CmdlineParser(config);
56+
cp.setAggregateShortOptionsWithPrefix("-");
57+
cp.parse(new String[] { "-lf", "file.txt" });
58+
expectTrue(config.formatLong);
59+
expectFalse(config.showSize);
60+
expectEquals(config.file, "file.txt");
61+
});
62+
63+
test("Combining two short options with args in between should work", () -> {
64+
final Config config = new Config();
65+
final CmdlineParser cp = new CmdlineParser(config);
66+
cp.setAggregateShortOptionsWithPrefix("-");
67+
cp.parse(new String[] { "-lfs", "file.txt" });
68+
expectTrue(config.formatLong);
69+
expectTrue(config.showSize);
70+
expectEquals(config.file, "file.txt");
71+
});
72+
73+
74+
}
75+
76+
}

0 commit comments

Comments
 (0)