From eede2aacf64154bf89eacac31a8a5f0cc1e4d968 Mon Sep 17 00:00:00 2001 From: Allan-QLB <68638598+Allan-QLB@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:56:00 +0800 Subject: [PATCH] Improve token parsing and better support control operators such as pipe and redirections (#2658) --- .../core/shell/cli/impl/CliTokenImpl.java | 73 +-- .../core/shell/cli/impl/CliTokenImplTest.java | 420 ++++++++++++++++-- 2 files changed, 435 insertions(+), 58 deletions(-) diff --git a/core/src/main/java/com/taobao/arthas/core/shell/cli/impl/CliTokenImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/cli/impl/CliTokenImpl.java index ca956e556..175f4cd82 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/cli/impl/CliTokenImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/cli/impl/CliTokenImpl.java @@ -11,6 +11,9 @@ import java.util.List; * @author Julien Viet */ public class CliTokenImpl implements CliToken { + private static final String PIPE = "|"; + private static final String REDIRECT = ">"; + private static final String REDIRECT_APPEND = ">>"; final boolean text; final String raw; @@ -71,7 +74,7 @@ public class CliTokenImpl implements CliToken { tokenize(s, 0, tokens); - tokens = correctPipeChar(tokens); + tokens = adjustTokensForSpecialSymbols(tokens); return tokens; } @@ -87,35 +90,53 @@ public class CliTokenImpl implements CliToken { * unsupported: * 3) thread|grep xxx * 4) trace -E classA|classB methodA|methodB|grep classA - * @param tokens - * @return + * + * Also handles redirection of '>' and '>>' in the similar way + * + * @param tokens original tokens + * @return adjusted tokens */ - private static List correctPipeChar(List tokens) { - List newTokens = new ArrayList(tokens.size()+4); - for (CliToken token : tokens) { - String tokenValue = token.value(); - if (tokenValue.length()>1 && tokenValue.endsWith("|")) { - //split last char '|' - tokenValue = tokenValue.substring(0, tokenValue.length()-1); - String rawValue = token.raw(); - rawValue = rawValue.substring(0, rawValue.length()-1); - newTokens.add(new CliTokenImpl(token.isText(), rawValue, tokenValue)); - //add '|' char - newTokens.add(new CliTokenImpl(true, "|", "|")); + private static List adjustTokensForSpecialSymbols(List tokens) { + return separateLeadingAndTailingSymbol(tokens, PIPE, REDIRECT_APPEND, REDIRECT); + } - } else if (tokenValue.length()>1 && tokenValue.startsWith("|")) { - //add '|' char - newTokens.add(new CliTokenImpl(true, "|", "|")); - //remove first char '|' - tokenValue = tokenValue.substring(1); - String rawValue = token.raw(); - rawValue = rawValue.substring(1); - newTokens.add(new CliTokenImpl(token.isText(), rawValue, tokenValue)); - } else { - newTokens.add(token); + private static List separateLeadingAndTailingSymbol(List tokens, String... symbols) { + List adjustedTokens = new ArrayList<>(); + for (CliToken token : tokens) { + String value = token.value(); + String raw = token.raw(); + boolean handled = false; + for (String symbol : symbols) { + if (value.equals(symbol)) { + break; + } else if (value.endsWith(symbol)) { + handled = true; + int lastIndexOfSymbol = raw.lastIndexOf(symbol); + adjustedTokens.add(new CliTokenImpl( + token.isText(), + raw.substring(0, lastIndexOfSymbol), + value.substring(0, value.length() - symbol.length()) + )); + adjustedTokens.add(new CliTokenImpl(true, raw.substring(lastIndexOfSymbol), symbol)); + break; + } else if (value.startsWith(symbol)) { + handled = true; + int firstIndexOfSymbol = raw.indexOf(symbol); + adjustedTokens.add(new CliTokenImpl(true, + raw.substring(0, firstIndexOfSymbol + symbol.length()), symbol)); + adjustedTokens.add(new CliTokenImpl( + token.isText(), + raw.substring(firstIndexOfSymbol + symbol.length()), + value.substring(symbol.length()) + )); + break; + } + } + if (!handled) { + adjustedTokens.add(token); } } - return newTokens; + return adjustedTokens; } private static void tokenize(String s, int index, List builder) { diff --git a/core/src/test/java/com/taobao/arthas/core/shell/cli/impl/CliTokenImplTest.java b/core/src/test/java/com/taobao/arthas/core/shell/cli/impl/CliTokenImplTest.java index fbaeb1b23..8e347d3ad 100644 --- a/core/src/test/java/com/taobao/arthas/core/shell/cli/impl/CliTokenImplTest.java +++ b/core/src/test/java/com/taobao/arthas/core/shell/cli/impl/CliTokenImplTest.java @@ -4,7 +4,7 @@ import com.taobao.arthas.core.shell.cli.CliToken; import org.junit.Assert; import org.junit.Test; -import java.util.Iterator; +import java.util.ArrayList; import java.util.List; public class CliTokenImplTest { @@ -25,14 +25,86 @@ public class CliTokenImplTest { @Test public void testSupportedPipeCharWithoutRegex() { String[] expectedTextTokenValue = new String[]{"thread", "|", "grep", "xxx"}; - List actualTokens = CliTokenImpl.tokenize("thread| grep xxx"); - assertEquals(expectedTextTokenValue, actualTokens); + String cmd = "thread| grep xxx"; + List actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread | grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread |grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread'|' grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread '|' grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread '|'grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread\"|\" grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread \"|\" grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread \"|\"grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"thread| grep", "xxx"}; + cmd = "thread'| 'grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread\"| \"grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"thread |grep", "xxx"}; + cmd = "thread' |'grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "thread\" |\"grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"thread \"|\"grep", "xxx"}; + cmd = "thread' \"|\"'grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"thread '|'grep", "xxx"}; + cmd = "thread\" '|'\"grep xxx"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); - actualTokens = CliTokenImpl.tokenize("thread | grep xxx"); - assertEquals(expectedTextTokenValue, actualTokens); - actualTokens = CliTokenImpl.tokenize("thread |grep xxx"); - assertEquals(expectedTextTokenValue, actualTokens); } /** @@ -51,14 +123,85 @@ public class CliTokenImplTest { @Test public void testSupportedPipeCharWithRegex() { String[] expectedTextTokenValue = new String[]{"trace", "-E", "classA|classB", "methodA|methodB", "|", "grep", "classA"}; - List actualTokens = CliTokenImpl.tokenize("trace -E classA|classB methodA|methodB| grep classA"); - assertEquals(expectedTextTokenValue, actualTokens); + String cmd = "trace -E classA|classB methodA|methodB| grep classA"; + List actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); - actualTokens = CliTokenImpl.tokenize("trace -E classA|classB methodA|methodB | grep classA"); - assertEquals(expectedTextTokenValue, actualTokens); + cmd = "trace -E classA|classB methodA|methodB | grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); - actualTokens = CliTokenImpl.tokenize("trace -E classA|classB methodA|methodB |grep classA"); - assertEquals(expectedTextTokenValue, actualTokens); + cmd = "trace -E classA|classB methodA|methodB |grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "trace -E classA|classB methodA|methodB'|' grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "trace -E classA|classB methodA|methodB '|' grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "trace -E classA|classB methodA|methodB '|'grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + + cmd = "trace -E classA|classB methodA|methodB\"|\" grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "trace -E classA|classB methodA|methodB \"|\" grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "trace -E classA|classB methodA|methodB \"|\"grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"trace", "-E", "classA|classB", "methodA|methodB| grep", "classA"}; + cmd = "trace -E classA|classB methodA|methodB'| 'grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "trace -E classA|classB methodA|methodB\"| \"grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"trace", "-E", "classA|classB", "methodA|methodB |grep", "classA"}; + cmd = "trace -E classA|classB methodA|methodB' |'grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "trace -E classA|classB methodA|methodB\" |\"grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"trace", "-E", "classA|classB", "methodA|methodB '|'grep", "classA"}; + cmd = "trace -E classA|classB methodA|methodB\" '|'\"grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"trace", "-E", "classA|classB", "methodA|methodB \"|\"grep", "classA"}; + cmd = "trace -E classA|classB methodA|methodB' \"|\"'grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); } /** @@ -77,34 +220,247 @@ public class CliTokenImplTest { @Test public void testUnSupportedPipeChar() { String[] expectedTextTokenValue = new String[]{"thread|grep", "xxx"}; - List actualTokens = CliTokenImpl.tokenize("thread|grep xxx"); - assertEquals(expectedTextTokenValue, actualTokens); + String cmd = "thread|grep xxx"; + List actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); expectedTextTokenValue = new String[]{"trace", "-E", "classA|classB", "methodA|methodB|grep", "classA"}; - actualTokens = CliTokenImpl.tokenize("trace -E classA|classB methodA|methodB|grep classA"); - assertEquals(expectedTextTokenValue, actualTokens); + cmd = "trace -E classA|classB methodA|methodB|grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); expectedTextTokenValue = new String[]{"trace", "-E", "classA|classB", "|", "methodA|methodB", "|", "grep", "classA"}; - actualTokens = CliTokenImpl.tokenize("trace -E classA|classB| methodA|methodB | grep classA"); - assertEquals(expectedTextTokenValue, actualTokens); + cmd = "trace -E classA|classB| methodA|methodB | grep classA"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); } - private void assertEquals(String[] expectedTextTokenValue, List actualTokens) { - removeBlankToken(actualTokens); - for (int i = 0; i < expectedTextTokenValue.length; i++) { - Assert.assertEquals(expectedTextTokenValue[i], actualTokens.get(i).value()); - } + @Test + public void testSeparateRedirect() { + String[] expectedTextTokenValue = new String[]{"jad", "aaa", ">", "bbb"}; + String cmd = "jad aaa> bbb"; + List actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa > bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa >bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa'>' bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa '>' bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa '>'bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa\">\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + + cmd = "jad aaa \">\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa \">\"bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa >bbb"}; + + cmd = "jad aaa' >'bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa\" >\"bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa> bbb"}; + + cmd = "jad aaa\"> \"bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa'> 'bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa\\r", ">", "bbb"}; + + cmd = "jad aaa'\\r>' bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa\"\\r>\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa'", ">", "bbb"}; + cmd = "jad aaa\"'>\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa'>'", "bbb"}; + cmd = "jad aaa\"'>'\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa\">\"", "bbb"}; + cmd = "jad aaa'\">\"' bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + } - private void removeBlankToken(List cliTokens) { - CliToken blankToken = new CliTokenImpl(false, " "); - Iterator it = cliTokens.iterator(); - while (it.hasNext()) { - CliToken token = it.next(); - if (blankToken.equals(token)) { - it.remove(); - } + @Test + public void testSeparateRedirectAppend() { + String[] expectedTextTokenValue = new String[]{"jad", "aaa", ">>", "bbb"}; + String cmd = "jad aaa>> bbb"; + List actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa >> bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa >>bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa'>>' bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa '>>' bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa '>>'bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa\">>\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + + cmd = "jad aaa \">>\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa \">>\"bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa >>bbb"}; + + cmd = "jad aaa' >>'bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa\" >>\"bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa>> bbb"}; + + cmd = "jad aaa\">> \"bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa'>> 'bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa\\r", ">>", "bbb"}; + + cmd = "jad aaa'\\r>>' bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + cmd = "jad aaa\"\\r>>\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa'", ">>", "bbb"}; + cmd = "jad aaa\"'>>\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa'>>'", "bbb"}; + cmd = "jad aaa\"'>>'\" bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + + expectedTextTokenValue = new String[]{"jad", "aaa\">>\"", "bbb"}; + cmd = "jad aaa'\">>\"' bbb"; + actualTokens = CliTokenImpl.tokenize(cmd); + assertEqualsIgnoreBlank(expectedTextTokenValue, actualTokens); + Assert.assertEquals(cmd, concatRaw(actualTokens)); + } + + private void assertEqualsIgnoreBlank(String[] expectedTextTokenValue, List actualTokens) { + Assert.assertArrayEquals(expectedTextTokenValue, removeBlankToken(actualTokens)); + } + + private static String[] removeBlankToken(List cliTokens) { + List copy = new ArrayList<>(cliTokens); + return copy.stream() + .filter(token -> !token.isBlank()) + .map(CliToken::value) + .toArray(String[]::new); + } + + private static String concatRaw(List tokens) { + StringBuilder builder = new StringBuilder(); + for (CliToken token : tokens) { + builder.append(token.raw()); } + return builder.toString(); } }