Skip to content

Commit bc0b4a1

Browse files
committed
Re-quote whitespace
- NonInteractiveShellRunner has a trouble where incoming argument loses info about "quoted" string which is handled by OS terminal. - Add re-quoting in presense of a whitespace so that jline parser can detect it correctly. - Backport #567 - Fixes #570
1 parent 3e5eac7 commit bc0b4a1

File tree

2 files changed

+75
-3
lines changed

2 files changed

+75
-3
lines changed

spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.shell.jline;
1717

18+
import java.util.Arrays;
1819
import java.util.Collections;
1920
import java.util.List;
2021
import java.util.function.Function;
@@ -33,6 +34,7 @@
3334
import org.springframework.shell.Utils;
3435
import org.springframework.shell.context.InteractionMode;
3536
import org.springframework.shell.context.ShellContext;
37+
import org.springframework.util.StringUtils;
3638

3739
/**
3840
* A {@link ShellRunner} that executes commands without entering interactive shell mode.
@@ -58,9 +60,32 @@ public class NonInteractiveShellRunner implements ShellRunner {
5860

5961
private Parser lineParser;
6062

61-
private Function<ApplicationArguments, List<String>> commandsFromInputArgs = args -> args
62-
.getSourceArgs().length == 0 ? Collections.emptyList()
63-
: Collections.singletonList(String.join(" ", args.getSourceArgs()));
63+
private static final String SINGLE_QUOTE = "\'";
64+
private static final String DOUBLE_QUOTE = "\"";
65+
66+
private Function<ApplicationArguments, List<String>> commandsFromInputArgs = args -> {
67+
if (args.getSourceArgs().length == 0) {
68+
return Collections.emptyList();
69+
}
70+
// re-quote if needed having whitespace
71+
String raw = Arrays.stream(args.getSourceArgs())
72+
.map(a -> {
73+
if (!isQuoted(a) && StringUtils.containsWhitespace(a)) {
74+
return "\"" + a + "\"";
75+
}
76+
return a;
77+
})
78+
.collect(Collectors.joining(" "));
79+
return Collections.singletonList(raw);
80+
};
81+
82+
private static boolean isQuoted(String str) {
83+
if (str == null) {
84+
return false;
85+
}
86+
return str.startsWith(SINGLE_QUOTE) && str.endsWith(SINGLE_QUOTE)
87+
|| str.startsWith(DOUBLE_QUOTE) && str.endsWith(DOUBLE_QUOTE);
88+
}
6489

6590
public NonInteractiveShellRunner(Shell shell, ShellContext shellContext) {
6691
this.shell = shell;

spring-shell-core/src/test/java/org/springframework/shell/jline/NonInteractiveShellRunnerTests.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,27 @@
1616
package org.springframework.shell.jline;
1717

1818
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.extension.ExtendWith;
20+
import org.mockito.ArgumentCaptor;
21+
import org.mockito.InjectMocks;
22+
import org.mockito.Mockito;
23+
import org.mockito.Spy;
24+
import org.mockito.junit.jupiter.MockitoExtension;
1925

2026
import org.springframework.boot.DefaultApplicationArguments;
27+
import org.springframework.shell.InputProvider;
28+
import org.springframework.shell.Shell;
29+
import org.springframework.shell.context.DefaultShellContext;
2130

2231
import static org.assertj.core.api.Assertions.assertThat;
2332

33+
@ExtendWith(MockitoExtension.class)
2434
public class NonInteractiveShellRunnerTests {
2535

36+
@Spy
37+
@InjectMocks
38+
private Shell shell;
39+
2640
@Test
2741
public void testEmptyArgsDontRun() {
2842
NonInteractiveShellRunner runner = new NonInteractiveShellRunner(null, null);
@@ -36,4 +50,37 @@ public void testNonEmptyArgsRun() {
3650
DefaultApplicationArguments args = new DefaultApplicationArguments("hi");
3751
assertThat(runner.canRun(args)).isTrue();
3852
}
53+
54+
@Test
55+
public void shouldQuoteWithWhitespace() throws Exception {
56+
NonInteractiveShellRunner runner = new NonInteractiveShellRunner(shell, new DefaultShellContext());
57+
DefaultApplicationArguments args = new DefaultApplicationArguments("foo bar");
58+
ArgumentCaptor<InputProvider> valueCapture = ArgumentCaptor.forClass(InputProvider.class);
59+
Mockito.doNothing().when(shell).run(valueCapture.capture());
60+
runner.run(args);
61+
InputProvider value = valueCapture.getValue();
62+
assertThat(value.readInput().rawText()).isEqualTo("\"foo bar\"");
63+
}
64+
65+
@Test
66+
public void shouldNotQuoteIfQuoted() throws Exception {
67+
NonInteractiveShellRunner runner = new NonInteractiveShellRunner(shell, new DefaultShellContext());
68+
DefaultApplicationArguments args = new DefaultApplicationArguments("'foo bar'");
69+
ArgumentCaptor<InputProvider> valueCapture = ArgumentCaptor.forClass(InputProvider.class);
70+
Mockito.doNothing().when(shell).run(valueCapture.capture());
71+
runner.run(args);
72+
InputProvider value = valueCapture.getValue();
73+
assertThat(value.readInput().rawText()).isEqualTo("'foo bar'");
74+
}
75+
76+
@Test
77+
public void shouldNotQuoteWithoutWhitespace() throws Exception {
78+
NonInteractiveShellRunner runner = new NonInteractiveShellRunner(shell, new DefaultShellContext());
79+
DefaultApplicationArguments args = new DefaultApplicationArguments("foobar");
80+
ArgumentCaptor<InputProvider> valueCapture = ArgumentCaptor.forClass(InputProvider.class);
81+
Mockito.doNothing().when(shell).run(valueCapture.capture());
82+
runner.run(args);
83+
InputProvider value = valueCapture.getValue();
84+
assertThat(value.readInput().rawText()).isEqualTo("foobar");
85+
}
3986
}

0 commit comments

Comments
 (0)