Polish embedded REPL shell
This commit is contained in:
@@ -32,102 +32,109 @@ import org.springframework.boot.cli.Command;
|
||||
import org.springframework.boot.cli.Log;
|
||||
import org.springframework.boot.cli.OptionHelp;
|
||||
import org.springframework.boot.cli.SpringCli;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* JLine {@link Completer} for Spring Boot {@link Command}s.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class CommandCompleter extends StringsCompleter {
|
||||
|
||||
private final Map<String, Completer> optionCompleters = new HashMap<String, Completer>();
|
||||
|
||||
private List<Command> commands = new ArrayList<Command>();
|
||||
|
||||
private ConsoleReader console;
|
||||
private String lastBuffer;
|
||||
|
||||
public CommandCompleter(ConsoleReader console, SpringCli cli) {
|
||||
this.console = console;
|
||||
|
||||
public CommandCompleter(ConsoleReader consoleReader, SpringCli cli) {
|
||||
this.console = consoleReader;
|
||||
this.commands.addAll(cli.getCommands());
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (Command c : this.commands) {
|
||||
names.add(c.getName());
|
||||
List<String> opts = new ArrayList<String>();
|
||||
for (OptionHelp optHelp : c.getOptionsHelp()) {
|
||||
opts.addAll(optHelp.getOptions());
|
||||
for (Command command : this.commands) {
|
||||
names.add(command.getName());
|
||||
List<String> options = new ArrayList<String>();
|
||||
for (OptionHelp optionHelp : command.getOptionsHelp()) {
|
||||
options.addAll(optionHelp.getOptions());
|
||||
}
|
||||
this.optionCompleters.put(c.getName(), new ArgumentCompleter(
|
||||
new StringsCompleter(c.getName()), new StringsCompleter(opts),
|
||||
new NullCompleter()));
|
||||
StringsCompleter commandCompleter = new StringsCompleter(command.getName());
|
||||
StringsCompleter optionsCompleter = new StringsCompleter(options);
|
||||
this.optionCompleters.put(command.getName(), new ArgumentCompleter(
|
||||
commandCompleter, optionsCompleter, new NullCompleter()));
|
||||
}
|
||||
getStrings().addAll(names);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
|
||||
int i = super.complete(buffer, cursor, candidates);
|
||||
if (buffer.indexOf(' ') < 1) {
|
||||
return i;
|
||||
}
|
||||
String name = buffer.substring(0, buffer.indexOf(' '));
|
||||
if ("".equals(name.trim())) {
|
||||
return i;
|
||||
}
|
||||
for (Command c : this.commands) {
|
||||
if (!c.getName().equals(name)) {
|
||||
continue;
|
||||
}
|
||||
if (buffer.equals(this.lastBuffer)) {
|
||||
this.lastBuffer = buffer;
|
||||
try {
|
||||
this.console.println();
|
||||
this.console.println("Usage:");
|
||||
this.console.println(c.getName() + " " + c.getUsageHelp());
|
||||
List<List<String>> rows = new ArrayList<List<String>>();
|
||||
int maxSize = 0;
|
||||
for (OptionHelp optHelp : c.getOptionsHelp()) {
|
||||
List<String> cols = new ArrayList<String>();
|
||||
for (String s : optHelp.getOptions()) {
|
||||
cols.add(s);
|
||||
}
|
||||
String opts = StringUtils
|
||||
.collectionToDelimitedString(cols, " | ");
|
||||
if (opts.length() > maxSize) {
|
||||
maxSize = opts.length();
|
||||
}
|
||||
cols.clear();
|
||||
cols.add(opts);
|
||||
cols.add(optHelp.getUsageHelp());
|
||||
rows.add(cols);
|
||||
int completionIndex = super.complete(buffer, cursor, candidates);
|
||||
int spaceIndex = buffer.indexOf(' ');
|
||||
String commandName = (spaceIndex == -1) ? "" : buffer.substring(0, spaceIndex);
|
||||
if (!"".equals(commandName.trim())) {
|
||||
for (Command command : this.commands) {
|
||||
if (command.getName().equals(commandName)) {
|
||||
if (cursor == buffer.length() && buffer.endsWith(" ")) {
|
||||
printUsage(command);
|
||||
break;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder("\t");
|
||||
for (List<String> row : rows) {
|
||||
String col1 = row.get(0);
|
||||
String col2 = row.get(1);
|
||||
for (int j = 0; j < (maxSize - col1.length()); j++) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(col1).append(": ").append(col2);
|
||||
this.console.println(sb.toString());
|
||||
sb = new StringBuilder("\t");
|
||||
Completer completer = this.optionCompleters.get(command.getName());
|
||||
if (completer != null) {
|
||||
completionIndex = completer.complete(buffer, cursor, candidates);
|
||||
break;
|
||||
}
|
||||
|
||||
this.console.drawLine();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Log.error(e.getMessage() + " (" + e.getClass().getName() + ")");
|
||||
}
|
||||
}
|
||||
Completer completer = this.optionCompleters.get(c.getName());
|
||||
if (null != completer) {
|
||||
i = completer.complete(buffer, cursor, candidates);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.lastBuffer = buffer;
|
||||
return i;
|
||||
return completionIndex;
|
||||
}
|
||||
|
||||
private void printUsage(Command command) {
|
||||
try {
|
||||
int maxOptionsLength = 0;
|
||||
List<OptionHelpLine> optionHelpLines = new ArrayList<OptionHelpLine>();
|
||||
for (OptionHelp optionHelp : command.getOptionsHelp()) {
|
||||
OptionHelpLine optionHelpLine = new OptionHelpLine(optionHelp);
|
||||
optionHelpLines.add(optionHelpLine);
|
||||
maxOptionsLength = Math.max(maxOptionsLength, optionHelpLine.getOptions()
|
||||
.length());
|
||||
}
|
||||
|
||||
this.console.println();
|
||||
this.console.println("Usage:");
|
||||
this.console.println(command.getName() + " " + command.getUsageHelp());
|
||||
for (OptionHelpLine optionHelpLine : optionHelpLines) {
|
||||
this.console.println(String.format("\t%" + maxOptionsLength + "s: %s",
|
||||
optionHelpLine.getOptions(), optionHelpLine.getUsage()));
|
||||
}
|
||||
this.console.drawLine();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Log.error(e.getMessage() + " (" + e.getClass().getName() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private static class OptionHelpLine {
|
||||
|
||||
private final String options;
|
||||
|
||||
private final String usage;
|
||||
|
||||
public OptionHelpLine(OptionHelp optionHelp) {
|
||||
StringBuffer options = new StringBuffer();
|
||||
for (String option : optionHelp.getOptions()) {
|
||||
options.append(options.length() == 0 ? "" : ", ");
|
||||
options.append(option);
|
||||
}
|
||||
this.options = options.toString();
|
||||
this.usage = optionHelp.getUsageHelp();
|
||||
}
|
||||
|
||||
public String getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public String getUsage() {
|
||||
return this.usage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 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.
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.springframework.boot.cli.command;
|
||||
|
||||
import org.springframework.boot.cli.Command;
|
||||
|
||||
/**
|
||||
* {@link Command} to change the {@link ShellCommand shell} prompt.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class PromptCommand extends AbstractCommand {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 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.
|
||||
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
@@ -38,9 +39,13 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ShellCommand extends AbstractCommand {
|
||||
|
||||
private static final Method PROCESS_BUILDER_INHERIT_IO_METHOD = ReflectionUtils
|
||||
.findMethod(ProcessBuilder.class, "inheritIO");
|
||||
|
||||
private String defaultPrompt = "$ ";
|
||||
|
||||
private SpringCli springCli;
|
||||
@@ -50,157 +55,60 @@ public class ShellCommand extends AbstractCommand {
|
||||
private Stack<String> prompts = new Stack<String>();
|
||||
|
||||
public ShellCommand(SpringCli springCli) {
|
||||
super("shell", "Start a nested shell (REPL).");
|
||||
super("shell", "Start a nested shell");
|
||||
this.springCli = springCli;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
|
||||
enhance(this.springCli);
|
||||
|
||||
final ConsoleReader console = new ConsoleReader();
|
||||
console.addCompleter(new CommandCompleter(console, this.springCli));
|
||||
console.setHistoryEnabled(true);
|
||||
console.setCompletionHandler(new CandidateListCompletionHandler());
|
||||
|
||||
final InputStream sysin = System.in;
|
||||
final PrintStream sysout = System.out;
|
||||
final PrintStream syserr = System.err;
|
||||
InputStream sysin = System.in;
|
||||
PrintStream systemOut = System.out;
|
||||
PrintStream systemErr = System.err;
|
||||
|
||||
ConsoleReader consoleReader = createConsoleReader();
|
||||
printBanner();
|
||||
|
||||
System.setIn(console.getInput());
|
||||
PrintStream out = new PrintStream(new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
console.getOutput().write(b);
|
||||
}
|
||||
});
|
||||
PrintStream out = new PrintStream(new ConsoleReaderOutputStream(consoleReader));
|
||||
System.setIn(consoleReader.getInput());
|
||||
System.setOut(out);
|
||||
System.setErr(out);
|
||||
|
||||
String line;
|
||||
StringBuffer data = new StringBuffer();
|
||||
|
||||
try {
|
||||
|
||||
while (null != (line = console.readLine(this.prompt))) {
|
||||
if ("quit".equals(line.trim())) {
|
||||
break;
|
||||
}
|
||||
else if ("clear".equals(line.trim())) {
|
||||
console.clearScreen();
|
||||
continue;
|
||||
}
|
||||
List<String> parts = new ArrayList<String>();
|
||||
|
||||
if (line.contains("<<")) {
|
||||
int startMultiline = line.indexOf("<<");
|
||||
data.append(line.substring(startMultiline + 2));
|
||||
String contLine;
|
||||
while (null != (contLine = console.readLine("... "))) {
|
||||
if ("".equals(contLine.trim())) {
|
||||
break;
|
||||
}
|
||||
data.append(contLine);
|
||||
}
|
||||
line = line.substring(0, startMultiline);
|
||||
}
|
||||
|
||||
String lineToParse = line.trim();
|
||||
if (lineToParse.startsWith("!")) {
|
||||
lineToParse = lineToParse.substring(1).trim();
|
||||
}
|
||||
String[] segments = StringUtils.delimitedListToStringArray(lineToParse,
|
||||
" ");
|
||||
StringBuffer sb = new StringBuffer();
|
||||
boolean swallowWhitespace = false;
|
||||
for (String s : segments) {
|
||||
if ("".equals(s)) {
|
||||
continue;
|
||||
}
|
||||
if (s.startsWith("\"")) {
|
||||
swallowWhitespace = true;
|
||||
sb.append(s.substring(1));
|
||||
}
|
||||
else if (s.endsWith("\"")) {
|
||||
swallowWhitespace = false;
|
||||
sb.append(" ").append(s.substring(0, s.length() - 1));
|
||||
parts.add(sb.toString());
|
||||
sb = new StringBuffer();
|
||||
}
|
||||
else {
|
||||
if (!swallowWhitespace) {
|
||||
parts.add(s);
|
||||
}
|
||||
else {
|
||||
sb.append(" ").append(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
parts.add(sb.toString());
|
||||
}
|
||||
if (data.length() > 0) {
|
||||
parts.add(data.toString());
|
||||
data = new StringBuffer();
|
||||
}
|
||||
|
||||
if (parts.size() > 0) {
|
||||
if (line.trim().startsWith("!")) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder(parts);
|
||||
if (isJava7()) {
|
||||
inheritIO(pb);
|
||||
}
|
||||
pb.environment().putAll(System.getenv());
|
||||
Process process = pb.start();
|
||||
if (!isJava7()) {
|
||||
ProcessGroovyMethods.consumeProcessOutput(process,
|
||||
(OutputStream) sysout, (OutputStream) syserr);
|
||||
}
|
||||
process.waitFor();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!getName().equals(parts.get(0))) {
|
||||
this.springCli.runAndHandleErrors(parts
|
||||
.toArray(new String[parts.size()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runReadLoop(consoleReader, systemOut, systemErr);
|
||||
}
|
||||
finally {
|
||||
|
||||
System.setIn(sysin);
|
||||
System.setOut(sysout);
|
||||
System.setErr(syserr);
|
||||
|
||||
console.shutdown();
|
||||
|
||||
System.setOut(systemOut);
|
||||
System.setErr(systemErr);
|
||||
consoleReader.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
protected void enhance(SpringCli cli) {
|
||||
String name = cli.getDisplayName().trim();
|
||||
this.defaultPrompt = name + "> ";
|
||||
this.defaultPrompt = cli.getDisplayName().trim() + "> ";
|
||||
this.prompt = this.defaultPrompt;
|
||||
cli.setDisplayName("");
|
||||
|
||||
RunCommand run = (RunCommand) cli.find("run");
|
||||
if (run != null) {
|
||||
StopCommand stop = new StopCommand(run);
|
||||
cli.register(stop);
|
||||
}
|
||||
|
||||
PromptCommand prompt = new PromptCommand(this);
|
||||
cli.register(prompt);
|
||||
}
|
||||
|
||||
private ConsoleReader createConsoleReader() throws IOException {
|
||||
ConsoleReader reader = new ConsoleReader();
|
||||
reader.addCompleter(new CommandCompleter(reader, this.springCli));
|
||||
reader.setHistoryEnabled(true);
|
||||
reader.setCompletionHandler(new CandidateListCompletionHandler());
|
||||
return reader;
|
||||
}
|
||||
|
||||
protected void printBanner() {
|
||||
String version = ShellCommand.class.getPackage().getImplementationVersion();
|
||||
version = (version == null ? "" : " (v" + version + ")");
|
||||
@@ -209,6 +117,129 @@ public class ShellCommand extends AbstractCommand {
|
||||
+ "RETURN for help, and 'quit' to exit.");
|
||||
}
|
||||
|
||||
private void runReadLoop(final ConsoleReader consoleReader,
|
||||
final PrintStream systemOut, final PrintStream systemErr) throws IOException {
|
||||
StringBuffer data = new StringBuffer();
|
||||
while (true) {
|
||||
String line = consoleReader.readLine(this.prompt);
|
||||
|
||||
if (line == null || "quit".equals(line.trim()) || "exit".equals(line.trim())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ("clear".equals(line.trim())) {
|
||||
consoleReader.clearScreen();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.contains("<<")) {
|
||||
int startMultiline = line.indexOf("<<");
|
||||
data.append(line.substring(startMultiline + 2));
|
||||
line = line.substring(0, startMultiline);
|
||||
readMultiLineData(consoleReader, data);
|
||||
}
|
||||
|
||||
line = line.trim();
|
||||
boolean isLaunchProcessCommand = line.startsWith("!");
|
||||
if (isLaunchProcessCommand) {
|
||||
line = line.substring(1);
|
||||
}
|
||||
|
||||
List<String> args = parseArgs(line);
|
||||
if (data.length() > 0) {
|
||||
args.add(data.toString());
|
||||
data.setLength(0);
|
||||
}
|
||||
if (args.size() > 0) {
|
||||
if (isLaunchProcessCommand) {
|
||||
launchProcess(args, systemOut, systemErr);
|
||||
}
|
||||
else {
|
||||
runCommand(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readMultiLineData(final ConsoleReader consoleReader, StringBuffer data)
|
||||
throws IOException {
|
||||
while (true) {
|
||||
String line = consoleReader.readLine("... ");
|
||||
if (line == null || "".equals(line.trim())) {
|
||||
return;
|
||||
}
|
||||
data.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> parseArgs(String line) {
|
||||
List<String> parts = new ArrayList<String>();
|
||||
String[] segments = StringUtils.delimitedListToStringArray(line, " ");
|
||||
StringBuffer part = new StringBuffer();
|
||||
boolean swallowWhitespace = false;
|
||||
for (String segment : segments) {
|
||||
if ("".equals(segment)) {
|
||||
continue;
|
||||
}
|
||||
if (segment.startsWith("\"")) {
|
||||
swallowWhitespace = true;
|
||||
part.append(segment.substring(1));
|
||||
}
|
||||
else if (segment.endsWith("\"")) {
|
||||
swallowWhitespace = false;
|
||||
part.append(" ").append(segment.substring(0, segment.length() - 1));
|
||||
parts.add(part.toString());
|
||||
part = new StringBuffer();
|
||||
}
|
||||
else {
|
||||
if (!swallowWhitespace) {
|
||||
parts.add(segment);
|
||||
}
|
||||
else {
|
||||
part.append(" ").append(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (part.length() > 0) {
|
||||
parts.add(part.toString());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
private void launchProcess(List<String> parts, final PrintStream sysout,
|
||||
final PrintStream syserr) {
|
||||
try {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(parts);
|
||||
if (isJava7()) {
|
||||
inheritIO(processBuilder);
|
||||
}
|
||||
processBuilder.environment().putAll(System.getenv());
|
||||
Process process = processBuilder.start();
|
||||
if (!isJava7()) {
|
||||
ProcessGroovyMethods.consumeProcessOutput(process, (OutputStream) sysout,
|
||||
(OutputStream) syserr);
|
||||
}
|
||||
process.waitFor();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJava7() {
|
||||
return PROCESS_BUILDER_INHERIT_IO_METHOD != null;
|
||||
}
|
||||
|
||||
private void inheritIO(ProcessBuilder processBuilder) {
|
||||
ReflectionUtils.invokeMethod(PROCESS_BUILDER_INHERIT_IO_METHOD, processBuilder);
|
||||
}
|
||||
|
||||
private void runCommand(List<String> args) {
|
||||
if (!getName().equals(args.get(0))) {
|
||||
this.springCli.runAndHandleErrors(args.toArray(new String[args.size()]));
|
||||
}
|
||||
}
|
||||
|
||||
public void pushPrompt(String prompt) {
|
||||
this.prompts.push(this.prompt);
|
||||
this.prompt = prompt;
|
||||
@@ -224,16 +255,18 @@ public class ShellCommand extends AbstractCommand {
|
||||
return this.prompt;
|
||||
}
|
||||
|
||||
private void inheritIO(ProcessBuilder pb) {
|
||||
ReflectionUtils.invokeMethod(
|
||||
ReflectionUtils.findMethod(ProcessBuilder.class, "inheritIO"), pb);
|
||||
}
|
||||
private static class ConsoleReaderOutputStream extends OutputStream {
|
||||
|
||||
private boolean isJava7() {
|
||||
if (ReflectionUtils.findMethod(ProcessBuilder.class, "inheritIO") != null) {
|
||||
return true;
|
||||
private ConsoleReader consoleReader;
|
||||
|
||||
public ConsoleReaderOutputStream(ConsoleReader consoleReader) {
|
||||
this.consoleReader = consoleReader;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
this.consoleReader.getOutput().write(b);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 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.
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.springframework.boot.cli.command;
|
||||
|
||||
import org.springframework.boot.cli.Command;
|
||||
|
||||
/**
|
||||
* {@link Command} to stop an application started from the {@link ShellCommand shell}.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class StopCommand extends AbstractCommand {
|
||||
@@ -25,8 +28,8 @@ public class StopCommand extends AbstractCommand {
|
||||
private final RunCommand runCmd;
|
||||
|
||||
public StopCommand(RunCommand runCmd) {
|
||||
super("stop",
|
||||
"Stop the currently-running application started with the 'run' command.");
|
||||
super("stop", "Stop the currently-running application started with "
|
||||
+ "the 'run' command.");
|
||||
this.runCmd = runCmd;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user