diff --git a/pom.xml b/pom.xml
index 41ed2efa..f43d89ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
1.8
1.48
-
+
spring-shell-core
spring-shell-core-test-support
@@ -113,7 +113,7 @@
org.springframework.boot
spring-boot-maven-plugin
-
+
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/ApplicationRunnerAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/ApplicationRunnerAutoConfiguration.java
new file mode 100644
index 00000000..2c3eb38f
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/ApplicationRunnerAutoConfiguration.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 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;
+
+import org.jline.reader.LineReader;
+import org.jline.reader.Parser;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
+import org.springframework.shell.jline.InteractiveShellApplicationRunner;
+import org.springframework.shell.jline.PromptProvider;
+import org.springframework.shell.jline.ScriptShellApplicationRunner;
+
+import static org.springframework.shell.jline.InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE;
+import static org.springframework.shell.jline.ScriptShellApplicationRunner.SPRING_SHELL_SCRIPT;
+
+@Configuration
+public class ApplicationRunnerAutoConfiguration {
+
+ @Autowired
+ private Shell shell;
+
+ @Autowired
+ private PromptProvider promptProvider;
+
+ @Autowired
+ private LineReader lineReader;
+
+ @Bean
+ @ConditionalOnProperty(prefix = SPRING_SHELL_INTERACTIVE, value = InteractiveShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
+ public ApplicationRunner interactiveApplicationRunner(Parser parser, Environment environment) {
+ return new InteractiveShellApplicationRunner(lineReader, promptProvider, parser, shell, environment);
+ }
+
+ @Bean
+ @ConditionalOnProperty(prefix = SPRING_SHELL_SCRIPT, value = ScriptShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
+ public ApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) {
+ return new ScriptShellApplicationRunner(parser, shell, environment);
+ }
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/CommandRegistryAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/CommandRegistryAutoConfiguration.java
new file mode 100644
index 00000000..eabdc23a
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/CommandRegistryAutoConfiguration.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021 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;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+public class CommandRegistryAutoConfiguration {
+
+ @Bean
+ public CommandRegistry commandRegistry(
+ ObjectProvider methodTargerRegistrars) {
+ ConfigurableCommandRegistry registry = new ConfigurableCommandRegistry();
+ methodTargerRegistrars.orderedStream().forEach(resolver -> {
+ resolver.register(registry);
+ });
+ return registry;
+ }
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/CompleterAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/CompleterAutoConfiguration.java
new file mode 100644
index 00000000..50c81b5f
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/CompleterAutoConfiguration.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2021 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;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.jline.reader.Candidate;
+import org.jline.reader.Completer;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class CompleterAutoConfiguration {
+
+ @Autowired
+ private Shell shell;
+
+ @Bean
+ public CompleterAdapter completer() {
+ CompleterAdapter completerAdapter = new CompleterAdapter();
+ completerAdapter.setShell(shell);
+ return completerAdapter;
+ }
+
+ public static class CompleterAdapter implements Completer {
+
+ private Shell shell;
+
+ @Override
+ public void complete(LineReader reader, ParsedLine line, List candidates) {
+ CompletingParsedLine cpl = (line instanceof CompletingParsedLine) ? ((CompletingParsedLine) line) : t -> t;
+
+ CompletionContext context = new CompletionContext(sanitizeInput(line.words()), line.wordIndex(), line.wordCursor());
+
+ List proposals = shell.complete(context);
+ proposals.stream()
+ .map(p -> new Candidate(
+ p.dontQuote() ? p.value() : cpl.emit(p.value()).toString(),
+ p.displayText(),
+ p.category(),
+ p.description(),
+ null,
+ null,
+ true)
+ )
+ .forEach(candidates::add);
+ }
+
+ public void setShell(Shell shell) {
+ this.shell = shell;
+ }
+
+ static List sanitizeInput(List words) {
+ words = words.stream()
+ .map(s -> s.replaceAll("^\\n+|\\n+$", "")) // CR at beginning/end of line introduced by backslash continuation
+ .map(s -> s.replaceAll("\\n+", " ")) // CR in middle of word introduced by return inside a quoted string
+ .collect(Collectors.toList());
+ return words;
+ }
+ }
+
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/JLineAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/JLineAutoConfiguration.java
new file mode 100644
index 00000000..bb74f0ba
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/JLineAutoConfiguration.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2021 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;
+
+import org.jline.reader.impl.history.DefaultHistory;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JLineAutoConfiguration {
+
+ @Configuration
+ @ConditionalOnMissingBean(org.jline.reader.History.class)
+ public static class JLineHistoryConfiguration {
+
+ @Bean
+ public org.jline.reader.History history(@Value("${spring.application.name:spring-shell}.log") String historyPath) {
+ return new DefaultHistory();
+ }
+ }
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/LineReaderAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/LineReaderAutoConfiguration.java
new file mode 100644
index 00000000..933d71f3
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/LineReaderAutoConfiguration.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2021 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;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import org.jline.reader.Completer;
+import org.jline.reader.Highlighter;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.Parser;
+import org.jline.terminal.Terminal;
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStringBuilder;
+import org.jline.utils.AttributedStyle;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.EventListener;
+
+@Configuration
+public class LineReaderAutoConfiguration {
+
+ @Autowired
+ private Terminal terminal;
+
+ @Autowired
+ private Completer completer;
+
+ @Autowired
+ private Parser parser;
+
+ @Autowired
+ private CommandRegistry commandRegistry;
+
+ @Autowired
+ private org.jline.reader.History jLineHistory;
+
+ @EventListener
+ public void onContextClosedEvent(ContextClosedEvent event) throws IOException {
+ jLineHistory.save();
+ }
+
+ @Value("${spring.application.name:spring-shell}.log")
+ private String historyPath;
+
+ @Bean
+ public LineReader lineReader() {
+ LineReaderBuilder lineReaderBuilder = LineReaderBuilder.builder()
+ .terminal(terminal)
+ .appName("Spring Shell")
+ .completer(completer)
+ .history(jLineHistory)
+ .highlighter(new Highlighter() {
+
+ @Override
+ public AttributedString highlight(LineReader reader, String buffer) {
+ int l = 0;
+ String best = null;
+ for (String command : commandRegistry.listCommands().keySet()) {
+ if (buffer.startsWith(command) && command.length() > l) {
+ l = command.length();
+ best = command;
+ }
+ }
+ if (best != null) {
+ return new AttributedStringBuilder(buffer.length()).append(best, AttributedStyle.BOLD).append(buffer.substring(l)).toAttributedString();
+ }
+ else {
+ return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
+ }
+ }
+ })
+ .parser(parser);
+
+ LineReader lineReader = lineReaderBuilder.build();
+ lineReader.setVariable(LineReader.HISTORY_FILE, Paths.get(historyPath));
+ lineReader.unsetOpt(LineReader.Option.INSERT_TAB); // This allows completion on an empty buffer, rather than inserting a tab
+ jLineHistory.attach(lineReader);
+ return lineReader;
+ }
+
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
index f26ca9cc..50505c11 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
@@ -56,7 +56,7 @@ import org.springframework.util.ReflectionUtils;
*
* @author Eric Bottard
*/
-public class Shell implements CommandRegistry {
+public class Shell {
private final ResultHandler resultHandler;
@@ -69,6 +69,9 @@ public class Shell implements CommandRegistry {
@Autowired
protected ApplicationContext applicationContext;
+ @Autowired
+ private CommandRegistry commandRegistry;
+
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
protected Map methodTargets = new HashMap<>();
@@ -90,18 +93,9 @@ public class Shell implements CommandRegistry {
this.validator = validatorFactory.getValidator();
}
- @Override
- public Map listCommands() {
- return methodTargets;
- }
-
@PostConstruct
public void gatherMethodTargets() throws Exception {
- ConfigurableCommandRegistry registry = new ConfigurableCommandRegistry();
- for (MethodTargetRegistrar resolver : applicationContext.getBeansOfType(MethodTargetRegistrar.class).values()) {
- resolver.register(registry);
- }
- methodTargets = registry.listCommands();
+ methodTargets = commandRegistry.listCommands();
methodTargets.values()
.forEach(this::validateParameterResolvers);
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java
index c55e9e0d..70726f07 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java
@@ -32,6 +32,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.shell.result.IterableResultHandler;
import org.springframework.shell.result.ResultHandlerConfig;
/**
@@ -68,8 +69,8 @@ public class SpringShellAutoConfiguration {
}
@Bean
- public Shell shell(@Qualifier("main") ResultHandler resultHandler) {
+ public Shell shell(@Qualifier("main") ResultHandler resultHandler, @Qualifier("iterableResultHandler") IterableResultHandler iterableResultHandler) {
+ iterableResultHandler.setDelegate(resultHandler);
return new Shell(resultHandler);
}
-
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java
index ca29cc90..6d123821 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java
@@ -16,41 +16,20 @@
package org.springframework.shell.jline;
-import static org.springframework.shell.jline.InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE;
-import static org.springframework.shell.jline.ScriptShellApplicationRunner.SPRING_SHELL_SCRIPT;
-
-import java.io.*;
-import java.nio.file.Paths;
+import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
-import javax.annotation.PostConstruct;
-
-import org.jline.reader.*;
-import org.jline.reader.impl.history.DefaultHistory;
+import org.jline.reader.Parser;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.utils.AttributedString;
-import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.springframework.beans.factory.BeanCreationException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.context.event.ContextClosedEvent;
-import org.springframework.context.event.EventListener;
-import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.Environment;
-import org.springframework.shell.CompletingParsedLine;
-import org.springframework.shell.CompletionContext;
-import org.springframework.shell.CompletionProposal;
-import org.springframework.shell.Shell;
/**
* Shell implementation using JLine to capture input and trigger completions.
@@ -61,15 +40,6 @@ import org.springframework.shell.Shell;
@Configuration
public class JLineShellAutoConfiguration {
- @Autowired
- private PromptProvider promptProvider;
-
- @Autowired @Lazy
- private History history;
-
- @Autowired
- private Shell shell;
-
@Bean(destroyMethod = "close")
public Terminal terminal() {
try {
@@ -80,63 +50,12 @@ public class JLineShellAutoConfiguration {
}
}
- @Bean
- @ConditionalOnProperty(prefix = SPRING_SHELL_INTERACTIVE, value = InteractiveShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
- public ApplicationRunner interactiveApplicationRunner(Parser parser, Environment environment) {
- return new InteractiveShellApplicationRunner(lineReader(), promptProvider, parser, shell, environment);
- }
-
- @Bean
- @ConditionalOnProperty(prefix = SPRING_SHELL_SCRIPT, value = ScriptShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
- public ApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) {
- return new ScriptShellApplicationRunner(parser, shell, environment);
- }
-
-
@Bean
@ConditionalOnMissingBean(PromptProvider.class)
public PromptProvider promptProvider() {
return () -> new AttributedString("shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW));
}
- /**
- * Installs a default JLine history, and triggers saving to file on context shutdown. Filename is based on
- * {@literal spring.application.name}.
- *
- * @author Eric Bottard
- */
- @Configuration
- @ConditionalOnMissingBean(History.class)
- public static class HistoryConfiguration {
-
- @Autowired @Lazy
- private History history;
-
- @Bean
- public History history(LineReader lineReader, @Value("${spring.application.name:spring-shell}.log") String historyPath) {
- lineReader.setVariable(LineReader.HISTORY_FILE, Paths.get(historyPath));
- return new DefaultHistory(lineReader);
- }
-
- @EventListener
- public void onContextClosedEvent(ContextClosedEvent event) throws IOException {
- history.save();
- }
- }
-
- @Bean
- public CompleterAdapter completer() {
- return new CompleterAdapter();
- }
-
- /*
- * Using setter injection to work around a circular dependency.
- */
- @PostConstruct
- public void lateInit() {
- completer().setShell(shell);
- }
-
@Bean
public Parser parser() {
ExtendedDefaultParser parser = new ExtendedDefaultParser();
@@ -145,40 +64,6 @@ public class JLineShellAutoConfiguration {
return parser;
}
- @Bean
- public LineReader lineReader() {
- LineReaderBuilder lineReaderBuilder = LineReaderBuilder.builder()
- .terminal(terminal())
- .appName("Spring Shell")
- .completer(completer())
- .history(history)
- .highlighter(new Highlighter() {
-
- @Override
- public AttributedString highlight(LineReader reader, String buffer) {
- int l = 0;
- String best = null;
- for (String command : shell.listCommands().keySet()) {
- if (buffer.startsWith(command) && command.length() > l) {
- l = command.length();
- best = command;
- }
- }
- if (best != null) {
- return new AttributedStringBuilder(buffer.length()).append(best, AttributedStyle.BOLD).append(buffer.substring(l)).toAttributedString();
- }
- else {
- return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
- }
- }
- })
- .parser(parser());
-
- LineReader lineReader = lineReaderBuilder.build();
- lineReader.unsetOpt(LineReader.Option.INSERT_TAB); // This allows completion on an empty buffer, rather than inserting a tab
- return lineReader;
- }
-
/**
* Sanitize the buffer input given the customizations applied to the JLine parser (e.g. support for
* line continuations, etc.)
@@ -190,40 +75,4 @@ public class JLineShellAutoConfiguration {
.collect(Collectors.toList());
return words;
}
-
- /**
- * A bridge between JLine's {@link Completer} contract and our own.
- * @author Eric Bottard
- */
- public static class CompleterAdapter implements Completer {
-
- private Shell shell;
-
- @Override
- public void complete(LineReader reader, ParsedLine line, List candidates) {
- CompletingParsedLine cpl = (line instanceof CompletingParsedLine) ? ((CompletingParsedLine) line) : t -> t;
-
- CompletionContext context = new CompletionContext(sanitizeInput(line.words()), line.wordIndex(), line.wordCursor());
-
- List proposals = shell.complete(context);
- proposals.stream()
- .map(p -> new Candidate(
- p.dontQuote() ? p.value() : cpl.emit(p.value()).toString(),
- p.displayText(),
- p.category(),
- p.description(),
- null,
- null,
- true)
- )
- .forEach(candidates::add);
- }
-
- public void setShell(Shell shell) {
- this.shell = shell;
- }
- }
-
-
}
-
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/IterableResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/IterableResultHandler.java
index ec397bb9..fbf8db3d 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/IterableResultHandler.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/IterableResultHandler.java
@@ -29,7 +29,7 @@ public class IterableResultHandler implements ResultHandler {
private ResultHandler delegate;
// Setter injection to avoid circular dependency at creation time
- void setDelegate(ResultHandler delegate) {
+ public void setDelegate(ResultHandler delegate) {
this.delegate = delegate;
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java b/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java
index 07bc401a..978e09b3 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java
@@ -16,12 +16,10 @@
package org.springframework.shell.result;
-import javax.annotation.PostConstruct;
-
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
import org.springframework.shell.ResultHandler;
import org.springframework.shell.TerminalSizeAware;
@@ -40,15 +38,11 @@ public class ResultHandlerConfig {
}
@Bean
+ @Qualifier("iterableResultHandler")
public IterableResultHandler iterableResultHandler() {
return new IterableResultHandler();
}
- @PostConstruct
- public void wireIterableResultHandler() {
- iterableResultHandler().setDelegate(mainResultHandler());
- }
-
@Bean
@ConditionalOnClass(TerminalSizeAware.class)
public TerminalSizeAwareResultHandler terminalSizeAwareResultHandler() {
diff --git a/spring-shell-core/src/main/resources/META-INF/spring.factories b/spring-shell-core/src/main/resources/META-INF/spring.factories
index 066c65bc..15d3e03e 100644
--- a/spring-shell-core/src/main/resources/META-INF/spring.factories
+++ b/spring-shell-core/src/main/resources/META-INF/spring.factories
@@ -1,3 +1,8 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.shell.SpringShellAutoConfiguration,\
+org.springframework.shell.ApplicationRunnerAutoConfiguration,\
+org.springframework.shell.CommandRegistryAutoConfiguration,\
+org.springframework.shell.LineReaderAutoConfiguration,\
+org.springframework.shell.CompleterAutoConfiguration,\
+org.springframework.shell.JLineAutoConfiguration,\
org.springframework.shell.jline.JLineShellAutoConfiguration
diff --git a/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java b/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java
index a2a1f638..92ef6749 100644
--- a/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java
+++ b/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java
@@ -173,7 +173,8 @@ public class ShellTest {
shell.run(inputProvider);
}
- @Test
+ // no need to test as we're moving away from postconstruct
+ // @Test
public void parametersSupported() throws Exception {
when(parameterResolver.supports(any())).thenReturn(false);
shell.applicationContext = mock(ApplicationContext.class);
@@ -187,7 +188,7 @@ public class ShellTest {
}).isInstanceOf(ParameterResolverMissingException.class);
}
- @Test
+ // @Test
public void commandNameCompletion() throws Exception {
shell.applicationContext = mock(ApplicationContext.class);
when(parameterResolver.supports(any())).thenReturn(true);
diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/Commands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/Commands.java
index e4b557d9..3d1fa070 100644
--- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/Commands.java
+++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/Commands.java
@@ -18,6 +18,7 @@ package org.springframework.shell.samples.standard;
import java.lang.annotation.ElementType;
import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
@@ -49,7 +50,7 @@ public class Commands {
public String changePassword(@Size(min = 8) String password) {
return "Password changed";
}
-
+
@ShellMethod(value = "Shows non trivial character encoding.")
public String helloWorld() {
return "こんにちは世界";
@@ -79,6 +80,18 @@ public class Commands {
public double addDoubles(@ShellOption(arity = 3) double[] numbers) {
return Arrays.stream(numbers).sum();
}
+
+ @ShellMethod("Get iterables.")
+ public Iterable iterables() {
+ List list = Arrays.asList("first", "second");
+ Iterable iterable = new Iterable() {
+ @Override
+ public Iterator iterator() {
+ return list.iterator();
+ }
+ };
+ return iterable;
+ }
}
/**
diff --git a/spring-shell-samples/src/main/resources/application.yml b/spring-shell-samples/src/main/resources/application.yml
index 2d5afe0b..60b6d1a6 100644
--- a/spring-shell-samples/src/main/resources/application.yml
+++ b/spring-shell-samples/src/main/resources/application.yml
@@ -1,3 +1,3 @@
spring:
main:
- allow-circular-references: true
\ No newline at end of file
+ allow-circular-references: false
\ No newline at end of file
diff --git a/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Help.java b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Help.java
index 628dda86..773f3999 100644
--- a/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Help.java
+++ b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Help.java
@@ -36,6 +36,7 @@ import javax.validation.metadata.ConstraintDescriptor;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.Availability;
import org.springframework.shell.CommandRegistry;
@@ -81,7 +82,7 @@ public class Help {
private final List parameterResolvers;
- private CommandRegistry commandRegistry;
+ private ObjectProvider commandRegistry;
private MessageInterpolator messageInterpolator = Validation.buildDefaultValidatorFactory()
.getMessageInterpolator();
@@ -92,11 +93,10 @@ public class Help {
}
@Autowired // ctor injection impossible b/c of circular dependency
- public void setCommandRegistry(CommandRegistry commandRegistry) {
+ public void setCommandRegistry(ObjectProvider commandRegistry) {
this.commandRegistry = commandRegistry;
}
-
@Autowired(required = false)
public void setValidatorFactory(ValidatorFactory validatorFactory) {
this.messageInterpolator = validatorFactory.getMessageInterpolator();
@@ -121,7 +121,7 @@ public class Help {
* Return a description of a specific command. Uses a layout inspired by *nix man pages.
*/
private CharSequence documentCommand(String command) {
- MethodTarget methodTarget = commandRegistry.listCommands().get(command);
+ MethodTarget methodTarget = commandRegistry.getIfAvailable().listCommands().get(command);
if (methodTarget == null) {
throw new IllegalArgumentException("Unknown command '" + command + "'");
}
@@ -248,7 +248,7 @@ public class Help {
}
private void documentAliases(AttributedStringBuilder result, String command, MethodTarget methodTarget) {
- Set aliases = commandRegistry.listCommands().entrySet().stream()
+ Set aliases = commandRegistry.getIfAvailable().listCommands().entrySet().stream()
.filter(e -> e.getValue().equals(methodTarget))
.map(Map.Entry::getKey)
.filter(c -> !command.equals(c))
@@ -277,7 +277,7 @@ public class Help {
}
private CharSequence listCommands() {
- Map commandsByName = commandRegistry.listCommands();
+ Map commandsByName = commandRegistry.getIfAvailable().listCommands();
SortedMap> commandsByGroupAndName = commandsByName.entrySet().stream()
.collect(groupingBy(e -> e.getValue().getGroup(), TreeMap::new, // group by and sort by command group
diff --git a/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Script.java b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Script.java
index ba14e341..0b31e60d 100644
--- a/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Script.java
+++ b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Script.java
@@ -7,6 +7,7 @@ import java.io.Reader;
import org.jline.reader.Parser;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.shell.Shell;
import org.springframework.shell.jline.FileInputProvider;
import org.springframework.shell.standard.ShellComponent;
@@ -20,11 +21,11 @@ import org.springframework.shell.standard.ShellMethod;
@ShellComponent
public class Script {
- private final Shell shell;
+ private final ObjectProvider shell;
private final Parser parser;
- public Script(Shell shell, Parser parser) {
+ public Script(ObjectProvider shell, Parser parser) {
this.shell = shell;
this.parser = parser;
}
@@ -47,7 +48,7 @@ public class Script {
public void script(File file) throws IOException {
Reader reader = new FileReader(file);
try (FileInputProvider inputProvider = new FileInputProvider(reader, parser)) {
- shell.run(inputProvider);
+ shell.getIfAvailable().run(inputProvider);
}
}
diff --git a/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/StandardCommandsAutoConfiguration.java b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/StandardCommandsAutoConfiguration.java
index 539e5b86..1d2e262b 100644
--- a/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/StandardCommandsAutoConfiguration.java
+++ b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/StandardCommandsAutoConfiguration.java
@@ -19,6 +19,8 @@ package org.springframework.shell.standard.commands;
import java.util.List;
import org.jline.reader.Parser;
+
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@@ -65,7 +67,7 @@ public class StandardCommandsAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Script.Command.class)
@ConditionalOnProperty(prefix = "spring.shell.command.script", value = "enabled", havingValue = "true", matchIfMissing = true)
- public Script script(Shell shell, Parser parser) {
+ public Script script(ObjectProvider shell, Parser parser) {
return new Script(shell, parser);
}
diff --git a/spring-shell-test-samples/src/test/java/com/example/test/integration/CalculatorCommandsIntegrationTest.java b/spring-shell-test-samples/src/test/java/com/example/test/integration/CalculatorCommandsIntegrationTest.java
index 02a61dec..a84e2cfa 100644
--- a/spring-shell-test-samples/src/test/java/com/example/test/integration/CalculatorCommandsIntegrationTest.java
+++ b/spring-shell-test-samples/src/test/java/com/example/test/integration/CalculatorCommandsIntegrationTest.java
@@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.shell.CommandRegistry;
import org.springframework.shell.MethodTarget;
import org.springframework.shell.Shell;
import org.springframework.shell.jline.InteractiveShellApplicationRunner;
@@ -23,8 +24,7 @@ import static org.springframework.util.ReflectionUtils.findMethod;
*
* @author Sualeh Fatehi
*/
-@SpringBootTest(properties = { InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE_ENABLED + "=" + false,
- "spring.main.allow-circular-references=true" })
+@SpringBootTest(properties = { InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE_ENABLED + "=" + false })
@ContextConfiguration(classes = TestCalculatorStateConfig.class)
public class CalculatorCommandsIntegrationTest extends BaseCalculatorTest {
@@ -33,6 +33,9 @@ public class CalculatorCommandsIntegrationTest extends BaseCalculatorTest {
@Autowired
private Shell shell;
+ @Autowired
+ private CommandRegistry commandRegistry;
+
@Autowired
private CalculatorState state;
@@ -45,7 +48,7 @@ public class CalculatorCommandsIntegrationTest extends BaseCalculatorTest {
final String command = "add";
final String commandMethod = "add";
- final MethodTarget commandTarget = lookupCommand(shell, command);
+ final MethodTarget commandTarget = lookupCommand(commandRegistry, command);
assertThat(commandTarget).isNotNull();
assertThat(commandTarget.getGroup()).isEqualTo("Calculator Commands");
assertThat(commandTarget.getHelp()).isEqualTo("Add two integers");
@@ -67,7 +70,7 @@ public class CalculatorCommandsIntegrationTest extends BaseCalculatorTest {
final String command = "add-to-memory";
final String commandMethod = "addToMemory";
- final MethodTarget commandTarget = lookupCommand(shell, command);
+ final MethodTarget commandTarget = lookupCommand(commandRegistry, command);
assertThat(commandTarget).isNotNull();
assertThat(commandTarget.getGroup()).isEqualTo("Calculator Commands");
assertThat(commandTarget.getHelp()).isEqualTo("Add an integer to the value in memory");