Add ShellCommand and friends

This commit is contained in:
Dave Syer 2013-12-23 17:33:37 +00:00
parent c94fb7fc53
commit cccb5d4610
8 changed files with 407 additions and 7 deletions

View File

@ -14,6 +14,7 @@
<main.basedir>${basedir}/..</main.basedir>
<start-class>org.springframework.boot.cli.SpringCli</start-class>
<spring.profiles.active>default</spring.profiles.active>
<jline.version>2.11</jline.version>
</properties>
<profiles>
<profile>
@ -49,6 +50,11 @@
</dependencyManagement>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>jline</groupId>
<artifactId>jline</artifactId>
<version>${jline.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>

View File

@ -123,11 +123,12 @@ public class SpringCli {
* @throws Exception
*/
protected void run(String... args) throws Exception {
if (args.length == 0) {
throw new NoArgumentsException();
String commandName = "shell";
if (args.length > 0) {
commandName = args[0];
}
String commandName = args[0];
String[] commandArguments = Arrays.copyOfRange(args, 1, args.length);
String[] commandArguments = args.length > 1 ? Arrays.copyOfRange(args, 1,
args.length) : new String[0];
Command command = find(commandName);
if (command == null) {
throw new NoSuchCommandException(commandName);

View File

@ -0,0 +1,121 @@
package org.springframework.boot.cli.command;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import jline.console.ConsoleReader;
import jline.console.completer.ArgumentCompleter;
import jline.console.completer.Completer;
import jline.console.completer.NullCompleter;
import jline.console.completer.StringsCompleter;
import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.CommandFactory;
import org.springframework.boot.cli.Log;
import org.springframework.boot.cli.OptionHelp;
import org.springframework.boot.cli.SpringCli;
import org.springframework.util.StringUtils;
/**
* @author Jon Brisbin
*/
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;
for(CommandFactory fac : ServiceLoader.load(CommandFactory.class, getClass().getClassLoader())) {
commands.addAll(fac.getCommands(cli));
}
List<String> names = new ArrayList<String>();
for(Command c : commands) {
names.add(c.getName());
List<String> opts = new ArrayList<String>();
for(OptionHelp optHelp : c.getOptionsHelp()) {
opts.addAll(optHelp.getOptions());
}
optionCompleters.put(c.getName(), new ArgumentCompleter(
new StringsCompleter(c.getName()),
new StringsCompleter(opts),
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 : commands) {
if(!c.getName().equals(name)) {
continue;
}
if(buffer.equals(lastBuffer)) {
lastBuffer = buffer;
try {
console.println();
console.println("Usage:");
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);
}
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);
console.println(sb.toString());
sb = new StringBuilder("\t");
}
console.drawLine();
} catch(IOException e) {
Log.error(e.getMessage() + " (" + e.getClass().getName() + ")");
}
}
Completer completer = optionCompleters.get(c.getName());
if(null != completer) {
i = completer.complete(buffer, cursor, candidates);
break;
}
}
lastBuffer = buffer;
return i;
}
}

View File

@ -16,6 +16,7 @@
package org.springframework.boot.cli.command;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@ -32,12 +33,21 @@ import org.springframework.boot.cli.SpringCli;
public class DefaultCommandFactory implements CommandFactory {
private static final List<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
new VersionCommand(), new RunCommand(), new CleanCommand(),
new TestCommand(), new GrabCommand());
new VersionCommand(), new CleanCommand(), new TestCommand(),
new GrabCommand());
@Override
public Collection<Command> getCommands(SpringCli cli) {
return DEFAULT_COMMANDS;
Collection<Command> commands = new ArrayList<Command>(DEFAULT_COMMANDS);
RunCommand run = new RunCommand();
StopCommand stop = new StopCommand(run);
ShellCommand shell = new ShellCommand(cli);
PromptCommand prompt = new PromptCommand(shell);
commands.add(run);
commands.add(stop);
commands.add(shell);
commands.add(prompt);
return commands;
}
}

View File

@ -0,0 +1,28 @@
package org.springframework.boot.cli.command;
import org.springframework.boot.cli.command.AbstractCommand;
/**
* @author Dave Syer
*/
public class PromptCommand extends AbstractCommand {
private final ShellCommand runCmd;
public PromptCommand(ShellCommand runCmd) {
super("prompt", "Change the prompt used with the current 'shell' command. Execute with no arguments to return to the previous value.");
this.runCmd = runCmd;
}
@Override
public void run(String... strings) throws Exception {
if (strings.length > 0) {
for (String string : strings) {
runCmd.pushPrompt(string + " ");
}
} else {
runCmd.popPrompt();
}
}
}

View File

@ -0,0 +1,20 @@
package org.springframework.boot.cli.command;
import java.io.IOException;
import org.springframework.boot.cli.SpringCli;
/**
* @author Dave Syer
*/
public class Shell {
public static void main(String... args) throws IOException {
if (args.length == 0) {
SpringCli.main("shell"); // right into the REPL by default
} else {
SpringCli.main(args);
}
}
}

View File

@ -0,0 +1,191 @@
package org.springframework.boot.cli.command;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import jline.console.ConsoleReader;
import jline.console.completer.CandidateListCompletionHandler;
import org.codehaus.groovy.runtime.ProcessGroovyMethods;
import org.springframework.boot.cli.SpringCli;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* @author Jon Brisbin
* @author Dave Syer
*/
public class ShellCommand extends AbstractCommand {
private static final String DEFAULT_PROMPT = "$ ";
private SpringCli springCli;
private String prompt = DEFAULT_PROMPT;
private Stack<String> prompts = new Stack<String>();
public ShellCommand(SpringCli springCli) {
super("shell", "Start a nested shell (REPL).");
this.springCli = springCli;
}
@Override
public void run(String... args) throws Exception {
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;
System.setIn(console.getInput());
PrintStream out = new PrintStream(new OutputStream() {
@Override
public void write(int b) throws IOException {
console.getOutput().write(b);
}
});
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()]));
}
}
}
}
}
finally {
System.setIn(sysin);
System.setOut(sysout);
System.setErr(syserr);
console.shutdown();
}
}
public void pushPrompt(String prompt) {
this.prompts.push(this.prompt);
this.prompt = prompt;
}
public String popPrompt() {
if (this.prompts.isEmpty()) {
this.prompt = DEFAULT_PROMPT;
}
else {
this.prompt = this.prompts.pop();
}
return this.prompt;
}
private void inheritIO(ProcessBuilder pb) {
ReflectionUtils.invokeMethod(
ReflectionUtils.findMethod(ProcessBuilder.class, "inheritIO"), pb);
}
private boolean isJava7() {
if (ReflectionUtils.findMethod(ProcessBuilder.class, "inheritIO") != null) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,23 @@
package org.springframework.boot.cli.command;
import org.springframework.boot.cli.command.AbstractCommand;
import org.springframework.boot.cli.command.RunCommand;
/**
* @author Jon Brisbin
*/
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.");
this.runCmd = runCmd;
}
@Override
public void run(String... strings) throws Exception {
runCmd.stop();
}
}