Skip to content

Commit

Permalink
[#738][#595] Bugfixes, updated tests
Browse files Browse the repository at this point in the history
  • Loading branch information
remkop committed Jul 2, 2019
1 parent bdd4b81 commit 53f1bea
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 70 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Expand Up @@ -34,6 +34,7 @@ Picocli follows [semantic versioning](http://semver.org/).
- [#742] Bugfix: Default values prevent correct parsing in argument groups. Thanks to [Andreas Deininger](https://github.com/deining) for raising this.
- [#759] Bugfix: Correct tracing when custom end-of-option delimiter is matched on the command line.
- [#738] Bugfix: `setTrimQuotes` does not trim quotes from option names. Thanks to [Judd Gaddie](https://github.com/juddgaddie) for raising this.
- [#595] Support for quoted arguments containing nested quoted substrings, allowing end-users to control how values are split in parts when a `split` regex is defined.
- [#751] Build: Make build more portable.
- [#753] Doc: Improve documentation for multi-value fields: mention the `split` attribute. Thanks to [feinstein](https://github.com/feinstein).
- [#740] Doc: Update user manual to replace `parse` examples with `parseArgs`.
Expand Down
56 changes: 29 additions & 27 deletions src/main/java/picocli/CommandLine.java
Expand Up @@ -7376,6 +7376,7 @@ private static String[] splitRespectingQuotedStrings(String value, int limit, Pa
temp.setLength(0);
}
}
escaping = false;
break;
default: escaping = false; break;
}
Expand Down Expand Up @@ -7409,15 +7410,15 @@ private static String restoreQuotedValues(String part, Queue<String> quotedValue
if (!escaping) {
inQuote = !inQuote;
if (!inQuote) { result.append(quotedValues.remove()); }
skip = parser.trimQuotes();
// skip = parser.trimQuotes();
}
break;
default: escaping = false; break;
}
if (!skip) { result.appendCodePoint(ch); }
skip = false;
}
return result.toString();
return parser.trimQuotes() ? smartUnquote(result.toString()) : result.toString();
}

protected boolean equalsImpl(ArgSpec other) {
Expand Down Expand Up @@ -11010,8 +11011,11 @@ private void processArguments(List<CommandLine> parsedCommands,
return;
}
String originalArg = args.pop();
String arg = smartUnquote(originalArg);
if (tracer.isDebug()) {tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse(copy(args)));}
String arg = smartUnquoteIfEnabled(originalArg);
if (tracer.isDebug()) {
if (arg == originalArg) { tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse(copy(args))); }
else { tracer.debug("Processing argument '%s' (trimmed from '%s'). Remainder=%s%n", arg, originalArg, reverse(copy(args))); }
}

// Double-dash separates options from positional arguments.
// If found, then interpret the remaining args as positional parameters.
Expand Down Expand Up @@ -11304,7 +11308,8 @@ private int applyValueToSingleValuedField(ArgSpec argSpec,
Set<ArgSpec> initialized,
String argDescription) throws Exception {
boolean noMoreValues = args.isEmpty();
String value = args.isEmpty() ? null : unquote(args.pop()); // unquote the value
String value = args.isEmpty() ? null : args.pop();
if (commandSpec.parser().trimQuotes()) {value = unquote(value);}
Range arity = argSpec.arity().isUnspecified ? derivedArity : argSpec.arity(); // #509
if (arity.max == 0 && !arity.isUnspecified && lookBehind == LookBehind.ATTACHED_WITH_SEPARATOR) { // #509
throw new MaxValuesExceededException(CommandLine.this, optionDescription("", argSpec, 0) +
Expand Down Expand Up @@ -11472,7 +11477,7 @@ private void consumeMapArguments(ArgSpec argSpec,

Map<Object, Object> typedValuesAtPosition = new LinkedHashMap<Object, Object>();
parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
if (!canConsumeOneMapArgument(argSpec, arity, consumed, args.peek(), classes, keyConverter, valueConverter, argDescription)) {
if (!canConsumeOneMapArgument(argSpec, lookBehind, arity, consumed, args.peek(), classes, keyConverter, valueConverter, argDescription)) {
break; // leave empty map at argSpec.typedValueAtPosition[currentPosition] so we won't try to consume that position again
}
consumeOneMapArgument(argSpec, lookBehind, arity, consumed, args.pop(), classes, keyConverter, valueConverter, typedValuesAtPosition, i, argDescription);
Expand All @@ -11492,7 +11497,7 @@ private void consumeOneMapArgument(ArgSpec argSpec,
int index,
String argDescription) throws Exception {
if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, arg); }
String[] values = unquoteAndSplit(argSpec, arity, consumed, arg);
String[] values = unquoteAndSplit(argSpec, lookBehind, arity, consumed, arg);
for (String value : values) {
String[] keyValue = splitKeyValue(argSpec, value);
Object mapKey = tryConvert(argSpec, index, keyConverter, keyValue[0], classes[0]);
Expand All @@ -11506,22 +11511,18 @@ private void consumeOneMapArgument(ArgSpec argSpec,
parseResultBuilder.addOriginalStringValue(argSpec, arg);
}

private String[] unquoteAndSplit(ArgSpec argSpec, Range arity, int consumed, String arg) {
String raw = smartUnquote(arg);
private String[] unquoteAndSplit(ArgSpec argSpec, LookBehind lookBehind, Range arity, int consumed, String arg) {
String raw = lookBehind.isAttached() ? arg : smartUnquoteIfEnabled(arg); // if attached, we already trimmed quotes once
// String raw = smartUnquoteIfEnabled(arg);
String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
if (raw.equals(arg)) { // not unquoted yet
for (int i = 0; i < values.length; i++) {
values[i] = unquote(values[i]);
}
}
return values;
}

private boolean canConsumeOneMapArgument(ArgSpec argSpec, Range arity, int consumed,
private boolean canConsumeOneMapArgument(ArgSpec argSpec, LookBehind lookBehind, Range arity, int consumed,
String arg, Class<?>[] classes,
ITypeConverter<?> keyConverter, ITypeConverter<?> valueConverter,
String argDescription) {
String[] values = unquoteAndSplit(argSpec, arity, consumed, arg);
String[] values = unquoteAndSplit(argSpec, lookBehind, arity, consumed, arg);
try {
for (String value : values) {
String[] keyValue = splitKeyValue(argSpec, value);
Expand Down Expand Up @@ -11664,7 +11665,7 @@ private List<Object> consumeArguments(ArgSpec argSpec,
if (!varargCanConsumeNextValue(argSpec, args.peek())) { break; }
List<Object> typedValuesAtPosition = new ArrayList<Object>();
parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
if (!canConsumeOneArgument(argSpec, arity, consumed, args.peek(), type, argDescription)) {
if (!canConsumeOneArgument(argSpec, lookBehind, arity, consumed, args.peek(), type, argDescription)) {
break; // leave empty list at argSpec.typedValueAtPosition[currentPosition] so we won't try to consume that position again
}
consumeOneArgument(argSpec, lookBehind, arity, consumed, args.pop(), type, typedValuesAtPosition, i, argDescription);
Expand Down Expand Up @@ -11726,7 +11727,7 @@ private int consumeOneArgument(ArgSpec argSpec,
int index,
String argDescription) {
if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, arg); }
String[] values = unquoteAndSplit(argSpec, arity, consumed, arg);
String[] values = unquoteAndSplit(argSpec, lookBehind, arity, consumed, arg);
ITypeConverter<?> converter = getTypeConverter(type, argSpec, 0);
for (int j = 0; j < values.length; j++) {
Object stronglyTypedValue = tryConvert(argSpec, index, converter, values[j], type);
Expand All @@ -11739,11 +11740,11 @@ private int consumeOneArgument(ArgSpec argSpec,
parseResultBuilder.addOriginalStringValue(argSpec, arg);
return ++index;
}
private boolean canConsumeOneArgument(ArgSpec argSpec, Range arity, int consumed, String arg, Class<?> type, String argDescription) {
private boolean canConsumeOneArgument(ArgSpec argSpec, LookBehind lookBehind, Range arity, int consumed, String arg, Class<?> type, String argDescription) {
if (char[].class.equals(argSpec.auxiliaryTypes()[0]) || char[].class.equals(argSpec.type())) { return true; }
ITypeConverter<?> converter = getTypeConverter(type, argSpec, 0);
try {
String[] values = unquoteAndSplit(argSpec, arity, consumed, arg);
String[] values = unquoteAndSplit(argSpec, lookBehind, arity, consumed, arg);
// if (!argSpec.acceptsValues(values.length, commandSpec.parser())) {
// tracer.debug("$s would split into %s values but %s cannot accept that many values.%n", arg, values.length, argDescription);
// return false;
Expand Down Expand Up @@ -11963,8 +11964,11 @@ String positionDesc(ArgSpec arg) {
}

/** Return the unquoted value if the value contains no nested quotes, otherwise, return the value as is. */
String smartUnquote(String value) {
String smartUnquoteIfEnabled(String value) {
if (value == null || !commandSpec.parser().trimQuotes()) { return value; }
return smartUnquote(value);
}
static String smartUnquote(String value) {
String unquoted = unquote(value);
if (unquoted == value) { return value; }
StringBuilder result = new StringBuilder();
Expand All @@ -11977,20 +11981,18 @@ String smartUnquote(String value) {
slashCount++;
break;
case '\"':
if (slashCount == 0) { requote = true; }
// if the unquoted value contains an unescaped quote, we should not convert escaped quotes into quotes
if (slashCount == 0) { return value; }
slashCount = 0;
break;
default: slashCount = 0; break;
}
if ((slashCount & 1) == 0) { result.appendCodePoint(ch); }
}
if (requote) {
result.append('"').insert(0, '"');
}
return result.toString();
}
private String unquote(String value) {
if (value == null || !commandSpec.parser().trimQuotes()) { return value; }
private static String unquote(String value) {
if (value == null) { return value; }
return (value.length() > 1 && value.startsWith("\"") && value.endsWith("\""))
? value.substring(1, value.length() - 1)
: value;
Expand Down

0 comments on commit 53f1bea

Please sign in to comment.