diff --git a/src/main/java/org/springframework/shell/core/Tokenizer.java b/src/main/java/org/springframework/shell/core/Tokenizer.java index 890ad8f1..5cabe74a 100644 --- a/src/main/java/org/springframework/shell/core/Tokenizer.java +++ b/src/main/java/org/springframework/shell/core/Tokenizer.java @@ -73,7 +73,7 @@ public class Tokenizer { } private void eatWhiteSpace() { - while (pos < buffer.length && buffer[pos] == ' ') { + while (lookAhead(' ')) { pos++; } } @@ -92,10 +92,23 @@ public class Tokenizer { } /** - * Consume either {@code --key=value} or just {@code value}, eating extra spaces. + * Return true if the remaining buffer matches the given String (return false if there is not enough input). + */ + private boolean lookAhead(char... toMatch) { + for (int i = 0; i < toMatch.length; i++) { + if (pos + i >= buffer.length || buffer[pos + i] != toMatch[i]) { + return false; + } + } + return true; + + } + + /** + * Consume either {@code --key[=value]} or just {@code value}, eating extra spaces. */ private void eatKeyValuePair() { - if (buffer[pos] == '-' && pos + 1 < buffer.length && buffer[pos + 1] == '-') { + if (lookAhead('-', '-')) { pos += 2; eatKeyEqualsValue(); } @@ -113,6 +126,9 @@ public class Tokenizer { } } + /** + * Eat a value that may be enclosed in some delimiters. + */ private String eatValue() { StringBuilder sb = new StringBuilder(); char endDelimiter = ' '; @@ -124,7 +140,7 @@ public class Tokenizer { lastValueDelimiter = endDelimiter; lastValueStartOffset = pos; while (pos < buffer.length && buffer[pos] != endDelimiter) { - if (buffer[pos] == ESCAPE_CHAR && pos + 1 < buffer.length && buffer[pos + 1] == endDelimiter) { + if (lookAhead(ESCAPE_CHAR, endDelimiter)) { sb.append(endDelimiter); pos += 2; continue; @@ -191,25 +207,32 @@ public class Tokenizer { } /** - * Consume a full @code{--key value} pair *unless* we're at the very end, in which case allow for @code {--key}, - * using "" for the value. + * Consume a full {@code --key value} pair *unless* + * + * in which case allow for just {@code --key}, using "" for the value. */ private void eatKeyEqualsValue() { String key = eatKey(); eatWhiteSpace(); String value; - if (pos < buffer.length) { - value = eatValue(); - } - else { + // We're at the very end or it's a valueless option + // Make last* fields consistent + if (pos >= buffer.length || lookAhead('-', '-')) { lastValueDelimiter = ' '; lastValueStartOffset = pos; value = ""; } - if (!key.equals("") || !key.equals(value)) { - // Don't store the ""="" that would result from having a pending " --" at the end - store(key, value); + else { + value = eatValue(); } + // Don't store the ""="" that would result from having a pending " --" at the end + if (key.equals("") && value.equals("")) { + return; + } + store(key, value); } private String eatKey() { diff --git a/src/test/java/org/springframework/shell/core/TokenizerTests.java b/src/test/java/org/springframework/shell/core/TokenizerTests.java index e8cae0f6..3c0bb7fc 100644 --- a/src/test/java/org/springframework/shell/core/TokenizerTests.java +++ b/src/test/java/org/springframework/shell/core/TokenizerTests.java @@ -152,6 +152,36 @@ public class TokenizerTests { assertEquals(singletonMap("foo", "bar"), result); } + @Test + public void testKeyWithDefaultValueAtEnd() { + Map result = tokenize("--foo bar --recursive"); + Map expected = new HashMap(); + expected.put("foo", "bar"); + expected.put("recursive", ""); + assertEquals(expected, result); + } + + @Test + public void testKeyWithDefaultValueNotAtEnd() { + Map result = tokenize("--foo bar --recursive --wizz blow"); + Map expected = new HashMap(); + expected.put("foo", "bar"); + expected.put("recursive", ""); + expected.put("wizz", "blow"); + assertEquals(expected, result); + } + + @Test + public void testValueThatStartsWithDashDashStillSupportedIfQuoted() { + Map result = tokenize("--foo bar --recursive \"--wizz\" blow"); + Map expected = new HashMap(); + expected.put("foo", "bar"); + expected.put("recursive", "--wizz"); + expected.put("", "blow"); + assertEquals(expected, result); + + } + private Map tokenize(String what) { return new Tokenizer(what).getTokens(); }