Skip to content

Commit d5eb4c3

Browse files
committed
Refine core APIs
- Make interactive shell runner independent of JLine - Introduce an interactive shell based on the JVM's system console - Simplify input providing APIs - Improve package cohesion - Update Spring Boot support
1 parent 3258772 commit d5eb4c3

39 files changed

+1005
-807
lines changed

spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistryAutoConfiguration.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,19 @@ public class CommandRegistryAutoConfiguration {
4646

4747
@Bean
4848
@ConditionalOnMissingBean
49-
CommandRegistry commandRegistry(ApplicationContext applicationContext) {
49+
public CommandRegistry commandRegistry(ApplicationContext applicationContext) {
5050
CommandRegistry commandRegistry = new CommandRegistry();
5151
registerProgrammaticCommands(applicationContext, commandRegistry);
5252
registerAnnotatedCommands(applicationContext, commandRegistry);
5353
return commandRegistry;
5454
}
5555

56-
private static void registerProgrammaticCommands(ApplicationContext applicationContext,
57-
CommandRegistry commandRegistry) {
56+
private void registerProgrammaticCommands(ApplicationContext applicationContext, CommandRegistry commandRegistry) {
5857
Map<String, Command> commandBeans = applicationContext.getBeansOfType(Command.class);
5958
commandBeans.values().forEach(commandRegistry::registerCommand);
6059
}
6160

62-
private static void registerAnnotatedCommands(ApplicationContext applicationContext,
63-
CommandRegistry commandRegistry) {
61+
private void registerAnnotatedCommands(ApplicationContext applicationContext, CommandRegistry commandRegistry) {
6462
Map<String, Object> springBootApps = applicationContext.getBeansWithAnnotation(SpringBootApplication.class);
6563
Class<?> mainClass = AopUtils.getTargetClass(springBootApps.values().iterator().next());
6664
String mainPackage = ClassUtils.getPackageName(mainClass);

spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/JLineShellAutoConfiguration.java

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-present the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,33 +17,147 @@
1717
package org.springframework.shell.boot;
1818

1919
import java.io.IOException;
20+
import java.nio.file.Path;
21+
import java.nio.file.Paths;
22+
import java.util.regex.Pattern;
2023

24+
import org.apache.commons.logging.Log;
25+
import org.apache.commons.logging.LogFactory;
26+
import org.jline.reader.Highlighter;
27+
import org.jline.reader.LineReader;
28+
import org.jline.reader.LineReaderBuilder;
2129
import org.jline.reader.Parser;
2230
import org.jline.reader.impl.history.DefaultHistory;
2331
import org.jline.terminal.Terminal;
2432
import org.jline.terminal.TerminalBuilder;
2533
import org.jline.terminal.TerminalBuilder.SystemOutput;
2634
import org.jline.utils.AttributedString;
35+
import org.jline.utils.AttributedStringBuilder;
2736
import org.jline.utils.AttributedStyle;
2837

2938
import org.springframework.beans.factory.BeanCreationException;
3039
import org.springframework.beans.factory.ObjectProvider;
40+
import org.springframework.beans.factory.annotation.Value;
3141
import org.springframework.boot.autoconfigure.AutoConfiguration;
3242
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
43+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
44+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3345
import org.springframework.context.annotation.Bean;
3446
import org.springframework.context.annotation.Configuration;
47+
import org.springframework.context.event.ContextClosedEvent;
48+
import org.springframework.context.event.EventListener;
49+
import org.springframework.shell.core.command.Command;
50+
import org.springframework.shell.core.command.CommandRegistry;
51+
import org.springframework.shell.core.config.UserConfigPathProvider;
3552
import org.springframework.shell.core.jline.ExtendedDefaultParser;
3653
import org.springframework.shell.core.jline.PromptProvider;
54+
import org.springframework.util.StringUtils;
3755

3856
/**
3957
* Shell implementation using JLine to capture input and trigger completions.
4058
*
4159
* @author Eric Bottard
4260
* @author Florent Biville
61+
* @author Mahmoud Ben Hassine
4362
*/
4463
@AutoConfiguration
64+
@EnableConfigurationProperties(SpringShellProperties.class)
65+
@ConditionalOnProperty(prefix = "spring.shell.interactive.type", name = "jline", havingValue = "true")
4566
public class JLineShellAutoConfiguration {
4667

68+
private final static Log log = LogFactory.getLog(JLineShellAutoConfiguration.class);
69+
70+
private Terminal terminal;
71+
72+
private Parser parser;
73+
74+
private CommandRegistry commandRegistry;
75+
76+
private org.jline.reader.History jLineHistory;
77+
78+
@Value("${spring.application.name:spring-shell}.log")
79+
private String fallbackHistoryFileName = "spring-shell.log";
80+
81+
private SpringShellProperties springShellProperties;
82+
83+
private UserConfigPathProvider userConfigPathProvider;
84+
85+
public JLineShellAutoConfiguration(Terminal terminal, Parser parser, CommandRegistry commandRegistry,
86+
org.jline.reader.History jLineHistory, SpringShellProperties springShellProperties,
87+
UserConfigPathProvider userConfigPathProvider) {
88+
this.terminal = terminal;
89+
this.parser = parser;
90+
this.commandRegistry = commandRegistry;
91+
this.jLineHistory = jLineHistory;
92+
this.springShellProperties = springShellProperties;
93+
this.userConfigPathProvider = userConfigPathProvider;
94+
}
95+
96+
@EventListener
97+
public void onContextClosedEvent(ContextClosedEvent event) throws IOException {
98+
jLineHistory.save();
99+
}
100+
101+
@Bean
102+
public LineReader lineReader() {
103+
LineReaderBuilder lineReaderBuilder = LineReaderBuilder.builder()
104+
.terminal(terminal)
105+
.appName("Spring Shell")
106+
.history(jLineHistory)
107+
.highlighter(new Highlighter() {
108+
109+
@Override
110+
public AttributedString highlight(LineReader reader, String buffer) {
111+
int l = 0;
112+
String best = null;
113+
for (Command command : commandRegistry.getCommands()) {
114+
if (buffer.startsWith(command.getName()) && command.getName().length() > l) {
115+
l = command.getName().length();
116+
best = command.getName();
117+
}
118+
}
119+
if (best != null) {
120+
return new AttributedStringBuilder(buffer.length()).append(best, AttributedStyle.BOLD)
121+
.append(buffer.substring(l))
122+
.toAttributedString();
123+
}
124+
else {
125+
return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
126+
}
127+
}
128+
129+
@Override
130+
public void setErrorPattern(Pattern errorPattern) {
131+
}
132+
133+
@Override
134+
public void setErrorIndex(int errorIndex) {
135+
}
136+
})
137+
.parser(parser);
138+
139+
LineReader lineReader = lineReaderBuilder.build();
140+
if (this.springShellProperties.getHistory().isEnabled()) {
141+
// Discover history location
142+
Path userConfigPath = this.userConfigPathProvider.provide();
143+
log.debug("Resolved userConfigPath " + userConfigPath);
144+
String historyFileName = this.springShellProperties.getHistory().getName();
145+
if (!StringUtils.hasText(historyFileName)) {
146+
historyFileName = fallbackHistoryFileName;
147+
}
148+
log.debug("Resolved historyFileName " + historyFileName);
149+
String historyPath = userConfigPath.resolve(historyFileName).toAbsolutePath().toString();
150+
log.debug("Resolved historyPath " + historyPath);
151+
// set history file
152+
lineReader.setVariable(LineReader.HISTORY_FILE, Paths.get(historyPath));
153+
}
154+
lineReader.unsetOpt(LineReader.Option.INSERT_TAB); // This allows completion on an
155+
// empty buffer, rather than
156+
// inserting a tab
157+
jLineHistory.attach(lineReader);
158+
return lineReader;
159+
}
160+
47161
@Bean(destroyMethod = "close")
48162
public Terminal terminal(ObjectProvider<TerminalCustomizer> customizers) {
49163
try {

spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/LineReaderAutoConfiguration.java

Lines changed: 0 additions & 147 deletions
This file was deleted.

0 commit comments

Comments
 (0)