Skip to content

Commit

Permalink
Use string array in ShellRunner
Browse files Browse the repository at this point in the history
- In ShellRunner interface replace use of boot's ApplicationArguments
  into plain string array.
- Deprecate old methods with some fallbacks to give
  time for users to do updates.
- NonInteractiveShellRunner contains one breaking change due to its
  public api to set function doing conversion from ApplicationArguments
  which was potentially used via customizer hooks.
- Deprecations planned to get removed in 3.4.x.
- Fixes #1057
  • Loading branch information
jvalkeal committed May 2, 2024
1 parent aaa7d87 commit 71960e5
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021-2022 the original author or authors.
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -56,6 +56,27 @@ public DefaultShellApplicationRunner(List<ShellRunner> shellRunners) {
@Override
public void run(ApplicationArguments args) throws Exception {
log.debug("Checking shell runners {}", shellRunners);

// Handle new ShellRunner api
String[] sourceArgs = args.getSourceArgs();
boolean canRun = false;
for (ShellRunner runner : shellRunners) {
try {
canRun = runner.run(sourceArgs);
} catch (Exception e) {
break;
}
if (canRun) {
break;
}
}

if (canRun) {
// new api handled execution
return;
}

// Handle old deprecated ShellRunner api
Optional<ShellRunner> optional = shellRunners.stream()
.filter(sh -> sh.canRun(args))
.findFirst();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,16 +27,41 @@ public interface ShellRunner {
/**
* Checks if a particular shell runner can execute.
*
* For {@link #canRun(ApplicationArguments)} and
* {@link #run(ApplicationArguments)} prefer {@link #run(String[])}.
*
* @param args the application arguments
* @return true if shell runner can execute
*/
boolean canRun(ApplicationArguments args);
@Deprecated(since = "3.3.0", forRemoval = true)
default boolean canRun(ApplicationArguments args) {
return false;
}

/**
* Execute application.
*
* For {@link #canRun(ApplicationArguments)} and
* {@link #run(ApplicationArguments)} prefer {@link #run(String[])}.
*
* @param args the application argumets
* @throws Exception in errors
*/
void run(ApplicationArguments args) throws Exception;
@Deprecated(since = "3.3.0", forRemoval = true)
default void run(ApplicationArguments args) throws Exception {
throw new UnsupportedOperationException("Should get implemented together with canRun");
}

/**
* Execute {@code ShellRunner} with given args. Return value indicates if run
* operation happened and no further runners should be used.
*
* @param args the raw arguments
* @return true if run execution happened
* @throws Exception possible error during run
*/
default boolean run(String[] args) throws Exception {
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.jline.reader.UserInterruptException;
import org.jline.utils.AttributedString;

import org.springframework.boot.ApplicationArguments;
import org.springframework.core.annotation.Order;
import org.springframework.shell.ExitRequest;
import org.springframework.shell.Input;
Expand Down Expand Up @@ -67,14 +66,10 @@ public InteractiveShellRunner(LineReader lineReader, PromptProvider promptProvid
}

@Override
public void run(ApplicationArguments args) throws Exception {
public boolean run(String[] args) throws Exception {
shellContext.setInteractionMode(InteractionMode.INTERACTIVE);
InputProvider inputProvider = new JLineInputProvider(lineReader, promptProvider);
shell.run(inputProvider);
}

@Override
public boolean canRun(ApplicationArguments args) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021-2023 the original author or authors.
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -25,7 +25,6 @@
import org.jline.reader.Parser;
import org.jline.reader.impl.DefaultParser;

import org.springframework.boot.ApplicationArguments;
import org.springframework.core.annotation.Order;
import org.springframework.shell.Input;
import org.springframework.shell.InputProvider;
Expand All @@ -34,6 +33,7 @@
import org.springframework.shell.Utils;
import org.springframework.shell.context.InteractionMode;
import org.springframework.shell.context.ShellContext;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
Expand Down Expand Up @@ -65,8 +65,8 @@ public class NonInteractiveShellRunner implements ShellRunner {
private static final String SINGLE_QUOTE = "\'";
private static final String DOUBLE_QUOTE = "\"";

private Function<ApplicationArguments, List<String>> commandsFromInputArgs = args -> {
if (args.getSourceArgs().length == 0) {
private Function<String[], List<String>> commandsFromArgs = args -> {
if (ObjectUtils.isEmpty(args)) {
if (StringUtils.hasText(primaryCommand)) {
Collections.singletonList(primaryCommand);
}
Expand All @@ -75,7 +75,7 @@ public class NonInteractiveShellRunner implements ShellRunner {
}
}
// re-quote if needed having whitespace
String raw = Arrays.stream(args.getSourceArgs())
String raw = Arrays.stream(args)
.map(a -> {
if (!isQuoted(a) && StringUtils.containsWhitespace(a)) {
return "\"" + a + "\"";
Expand Down Expand Up @@ -113,12 +113,12 @@ public NonInteractiveShellRunner(Shell shell, ShellContext shellContext, String
/**
* Sets the function that creates the command() to run from the input application arguments.
*
* @param commandsFromInputArgs function that takes input application arguments and creates zero or more commands
* @param commandsFromArgs function that takes input application arguments and creates zero or more commands
* where each command is a string that specifies the command and options
* (eg. 'history --file myHistory.txt')
*/
public void setCommandsFromInputArgs(Function<ApplicationArguments, List<String>> commandsFromInputArgs) {
this.commandsFromInputArgs = commandsFromInputArgs;
public void setCommandsFromArgs(Function<String[], List<String>> commandsFromArgs) {
this.commandsFromArgs = commandsFromArgs;
}

/**
Expand All @@ -131,19 +131,18 @@ public void setLineParser(Parser lineParser) {
}

@Override
public boolean canRun(ApplicationArguments args) {
return !commandsFromInputArgs.apply(args).isEmpty();
}

@Override
public void run(ApplicationArguments args) throws Exception {
shellContext.setInteractionMode(InteractionMode.NONINTERACTIVE);
List<String> commands = this.commandsFromInputArgs.apply(args);
public boolean run(String[] args) throws Exception {
List<String> commands = commandsFromArgs.apply(args);
if (commands.isEmpty()) {
return false;
}
List<ParsedLine> parsedLines = commands.stream()
.map(rawCommandLine -> lineParser.parse(rawCommandLine, rawCommandLine.length() + 1))
.collect(Collectors.toList());
MultiParsedLineInputProvider inputProvider = new MultiParsedLineInputProvider(parsedLines);
shellContext.setInteractionMode(InteractionMode.NONINTERACTIVE);
shell.run(inputProvider);
return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.jline.reader.Parser;

import org.springframework.boot.ApplicationArguments;
import org.springframework.core.annotation.Order;
import org.springframework.shell.Shell;
import org.springframework.shell.ShellRunner;
import org.springframework.util.ObjectUtils;

/**
* A {@link ShellRunner} that looks for process arguments that start with {@literal @}, which are then interpreted as
Expand Down Expand Up @@ -59,19 +60,16 @@ public ScriptShellRunner(Parser parser, Shell shell) {
}

@Override
public boolean canRun(ApplicationArguments args) {
String[] sourceArgs = args.getSourceArgs();
if (sourceArgs.length > 0 && sourceArgs[0].startsWith("@") && sourceArgs[0].length() > 1) {
return true;
public boolean run(String[] args) throws Exception {
String[] sourceArgs = args;
if (ObjectUtils.isEmpty(sourceArgs)) {
return false;
}
if (!(sourceArgs[0].startsWith("@") && sourceArgs[0].length() > 1)) {
return false;
}
return false;
}

//tag::documentation[]

@Override
public void run(ApplicationArguments args) throws Exception {
List<File> scriptsToRun = args.getNonOptionArgs().stream()
List<File> scriptsToRun = Arrays.asList(args).stream()
.filter(s -> s.startsWith("@"))
.map(s -> new File(s.substring(1)))
.collect(Collectors.toList());
Expand All @@ -82,7 +80,8 @@ public void run(ApplicationArguments args) throws Exception {
shell.run(inputProvider);
}
}

return true;
}
//end::documentation[]

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
* Copyright 2022-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.jline;

import java.io.ByteArrayOutputStream;
Expand All @@ -14,6 +29,8 @@
import org.jline.utils.AttributedStyle;
import org.junit.jupiter.api.Test;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.DefaultApplicationArguments;
import org.springframework.shell.ExitRequest;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -132,4 +149,24 @@ public void testExitWithCtrlD() throws Exception {
readThread.join();
writeThread.join();
}


@Test
void oldApiCanRunReturnFalse() {
InteractiveShellRunner runner = new InteractiveShellRunner(null, null, null, null);
assertThat(runner.canRun(ofApplicationArguments())).isFalse();
}

@Test
void oldApiRunThrows() {
InteractiveShellRunner runner = new InteractiveShellRunner(null, null, null, null);
assertThatThrownBy(() -> {
runner.run(ofApplicationArguments());
});
}

private static ApplicationArguments ofApplicationArguments(String... args) {
return new DefaultApplicationArguments(args);
}

}
Loading

0 comments on commit 71960e5

Please sign in to comment.