diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java index 20cccf58..daa247bf 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java @@ -15,6 +15,7 @@ */ package org.springframework.shell.jline; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -33,6 +34,7 @@ import org.springframework.shell.ShellRunner; import org.springframework.shell.Utils; import org.springframework.shell.context.InteractionMode; import org.springframework.shell.context.ShellContext; +import org.springframework.util.StringUtils; /** * A {@link ShellRunner} that executes commands without entering interactive shell mode. @@ -58,9 +60,32 @@ public class NonInteractiveShellRunner implements ShellRunner { private Parser lineParser; - private Function> commandsFromInputArgs = args -> args - .getSourceArgs().length == 0 ? Collections.emptyList() - : Collections.singletonList(String.join(" ", args.getSourceArgs())); + private static final String SINGLE_QUOTE = "\'"; + private static final String DOUBLE_QUOTE = "\""; + + private Function> commandsFromInputArgs = args -> { + if (args.getSourceArgs().length == 0) { + return Collections.emptyList(); + } + // re-quote if needed having whitespace + String raw = Arrays.stream(args.getSourceArgs()) + .map(a -> { + if (!isQuoted(a) && StringUtils.containsWhitespace(a)) { + return "\"" + a + "\""; + } + return a; + }) + .collect(Collectors.joining(" ")); + return Collections.singletonList(raw); + }; + + private static boolean isQuoted(String str) { + if (str == null) { + return false; + } + return str.startsWith(SINGLE_QUOTE) && str.endsWith(SINGLE_QUOTE) + || str.startsWith(DOUBLE_QUOTE) && str.endsWith(DOUBLE_QUOTE); + } public NonInteractiveShellRunner(Shell shell, ShellContext shellContext) { this.shell = shell; diff --git a/spring-shell-core/src/test/java/org/springframework/shell/jline/NonInteractiveShellRunnerTests.java b/spring-shell-core/src/test/java/org/springframework/shell/jline/NonInteractiveShellRunnerTests.java index 8f04ab36..7d2fcdac 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/jline/NonInteractiveShellRunnerTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/jline/NonInteractiveShellRunnerTests.java @@ -16,13 +16,27 @@ package org.springframework.shell.jline; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.DefaultApplicationArguments; +import org.springframework.shell.InputProvider; +import org.springframework.shell.Shell; +import org.springframework.shell.context.DefaultShellContext; import static org.assertj.core.api.Assertions.assertThat; +@ExtendWith(MockitoExtension.class) public class NonInteractiveShellRunnerTests { + @Spy + @InjectMocks + private Shell shell; + @Test public void testEmptyArgsDontRun() { NonInteractiveShellRunner runner = new NonInteractiveShellRunner(null, null); @@ -36,4 +50,37 @@ public class NonInteractiveShellRunnerTests { DefaultApplicationArguments args = new DefaultApplicationArguments("hi"); assertThat(runner.canRun(args)).isTrue(); } + + @Test + public void shouldQuoteWithWhitespace() throws Exception { + NonInteractiveShellRunner runner = new NonInteractiveShellRunner(shell, new DefaultShellContext()); + DefaultApplicationArguments args = new DefaultApplicationArguments("foo bar"); + ArgumentCaptor valueCapture = ArgumentCaptor.forClass(InputProvider.class); + Mockito.doNothing().when(shell).run(valueCapture.capture()); + runner.run(args); + InputProvider value = valueCapture.getValue(); + assertThat(value.readInput().rawText()).isEqualTo("\"foo bar\""); + } + + @Test + public void shouldNotQuoteIfQuoted() throws Exception { + NonInteractiveShellRunner runner = new NonInteractiveShellRunner(shell, new DefaultShellContext()); + DefaultApplicationArguments args = new DefaultApplicationArguments("'foo bar'"); + ArgumentCaptor valueCapture = ArgumentCaptor.forClass(InputProvider.class); + Mockito.doNothing().when(shell).run(valueCapture.capture()); + runner.run(args); + InputProvider value = valueCapture.getValue(); + assertThat(value.readInput().rawText()).isEqualTo("'foo bar'"); + } + + @Test + public void shouldNotQuoteWithoutWhitespace() throws Exception { + NonInteractiveShellRunner runner = new NonInteractiveShellRunner(shell, new DefaultShellContext()); + DefaultApplicationArguments args = new DefaultApplicationArguments("foobar"); + ArgumentCaptor valueCapture = ArgumentCaptor.forClass(InputProvider.class); + Mockito.doNothing().when(shell).run(valueCapture.capture()); + runner.run(args); + InputProvider value = valueCapture.getValue(); + assertThat(value.readInput().rawText()).isEqualTo("foobar"); + } }