Change command set when entering shell

Fixes gh-147
pull/184/head
Dave Syer 11 years ago
parent d50d378c33
commit de82557bd8

@ -129,6 +129,15 @@ public class SpringCli {
|| displayName.endsWith(" ") ? displayName : displayName + " ";
}
/**
* The name of this tool when printed by the help command.
*
* @return the displayName
*/
public String getDisplayName() {
return this.displayName;
}
/**
* Parse the arguments and run a suitable command.
* @param args the arguments
@ -148,10 +157,11 @@ public class SpringCli {
command.run(commandArguments);
}
protected final Command find(String name) {
public final Command find(String name) {
for (Command candidate : this.commands) {
if (candidate.getName().equals(name)
|| (candidate.isOptionCommand() && ("--" + candidate.getName())
String candidateName = candidate.getName();
if (candidateName.equals(name)
|| (candidate.isOptionCommand() && ("--" + candidateName)
.equals(name))) {
return candidate;
}
@ -159,6 +169,25 @@ public class SpringCli {
return null;
}
public void register(Command command) {
Command existing = find(command.getName());
int index = this.commands.indexOf(find("hint")) - 1;
index = index >= 0 ? index : 0;
if (existing != null) {
index = this.commands.indexOf(existing);
this.commands.remove(index);
}
this.commands.add(index, command);
}
public void unregister(String name) {
this.commands.remove(find(name));
}
public List<Command> getCommands() {
return Collections.unmodifiableList(this.commands);
}
protected void showUsage() {
Log.infoPrint("usage: " + this.displayName);
for (Command command : this.commands) {
@ -384,4 +413,5 @@ public class SpringCli {
System.exit(exitCode);
}
}
}

@ -5,7 +5,6 @@ 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;
@ -14,7 +13,6 @@ 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;
@ -26,62 +24,59 @@ import org.springframework.util.StringUtils;
public class CommandCompleter extends StringsCompleter {
private final Map<String, Completer> optionCompleters = new HashMap<String, Completer>();
private List<Command> commands = new ArrayList<Command>();
private List<Command> commands = new ArrayList<Command>();
private ConsoleReader console;
private String lastBuffer;
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));
}
this.commands.addAll(cli.getCommands());
List<String> names = new ArrayList<String>();
for(Command c : commands) {
for (Command c : this.commands) {
names.add(c.getName());
List<String> opts = new ArrayList<String>();
for(OptionHelp optHelp : c.getOptionsHelp()) {
for (OptionHelp optHelp : c.getOptionsHelp()) {
opts.addAll(optHelp.getOptions());
}
optionCompleters.put(c.getName(), new ArgumentCompleter(
new StringsCompleter(c.getName()),
new StringsCompleter(opts),
new NullCompleter()
));
this.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) {
if (buffer.indexOf(' ') < 1) {
return i;
}
String name = buffer.substring(0, buffer.indexOf(' '));
if("".equals(name.trim())) {
if ("".equals(name.trim())) {
return i;
}
for(Command c : commands) {
if(!c.getName().equals(name)) {
for (Command c : this.commands) {
if (!c.getName().equals(name)) {
continue;
}
if(buffer.equals(lastBuffer)) {
lastBuffer = buffer;
if (buffer.equals(this.lastBuffer)) {
this.lastBuffer = buffer;
try {
console.println();
console.println("Usage:");
console.println(c.getName() + " " + c.getUsageHelp());
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()) {
for (OptionHelp optHelp : c.getOptionsHelp()) {
List<String> cols = new ArrayList<String>();
for(String s : optHelp.getOptions()) {
for (String s : optHelp.getOptions()) {
cols.add(s);
}
String opts = StringUtils.collectionToDelimitedString(cols, " | ");
if(opts.length() > maxSize) {
String opts = StringUtils
.collectionToDelimitedString(cols, " | ");
if (opts.length() > maxSize) {
maxSize = opts.length();
}
cols.clear();
@ -91,30 +86,31 @@ public class CommandCompleter extends StringsCompleter {
}
StringBuilder sb = new StringBuilder("\t");
for(List<String> row : rows) {
for (List<String> row : rows) {
String col1 = row.get(0);
String col2 = row.get(1);
for(int j = 0; j < (maxSize - col1.length()); j++) {
for (int j = 0; j < (maxSize - col1.length()); j++) {
sb.append(" ");
}
sb.append(col1).append(": ").append(col2);
console.println(sb.toString());
this.console.println(sb.toString());
sb = new StringBuilder("\t");
}
console.drawLine();
} catch(IOException e) {
this.console.drawLine();
}
catch (IOException e) {
Log.error(e.getMessage() + " (" + e.getClass().getName() + ")");
}
}
Completer completer = optionCompleters.get(c.getName());
if(null != completer) {
Completer completer = this.optionCompleters.get(c.getName());
if (null != completer) {
i = completer.complete(buffer, cursor, candidates);
break;
}
}
lastBuffer = buffer;
this.lastBuffer = buffer;
return i;
}

@ -36,18 +36,22 @@ public class DefaultCommandFactory implements CommandFactory {
new VersionCommand(), new CleanCommand(), new TestCommand(),
new GrabCommand());
private Collection<Command> commands;
@Override
public Collection<Command> getCommands(SpringCli cli) {
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;
if (this.commands == null) {
synchronized (this) {
if (this.commands == null) {
this.commands = new ArrayList<Command>(DEFAULT_COMMANDS);
RunCommand run = new RunCommand();
ShellCommand shell = new ShellCommand(cli);
this.commands.add(run);
this.commands.add(shell);
}
}
}
return this.commands;
}
}

@ -17,14 +17,17 @@ import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* A shell command for Spring Boot. Drops the user into an event loop (REPL) where command
* line completion and history are available without relying on OS shell features.
*
* @author Jon Brisbin
* @author Dave Syer
*/
public class ShellCommand extends AbstractCommand {
private static final String DEFAULT_PROMPT = "$ ";
private String defaultPrompt = "$ ";
private SpringCli springCli;
private String prompt = DEFAULT_PROMPT;
private String prompt = this.defaultPrompt;
private Stack<String> prompts = new Stack<String>();
public ShellCommand(SpringCli springCli) {
@ -35,7 +38,7 @@ public class ShellCommand extends AbstractCommand {
@Override
public void run(String... args) throws Exception {
this.springCli.setDisplayName("");
enhance(this.springCli);
final ConsoleReader console = new ConsoleReader();
console.addCompleter(new CommandCompleter(console, this.springCli));
@ -165,11 +168,26 @@ public class ShellCommand extends AbstractCommand {
}
}
private void printBanner() {
protected void enhance(SpringCli cli) {
String name = cli.getDisplayName().trim();
this.defaultPrompt = name + "> ";
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);
}
protected void printBanner() {
String version = ShellCommand.class.getPackage().getImplementationVersion();
version = (version == null ? "" : " (v" + version + ")");
System.out.println("Spring Boot CLI" + version);
System.out.println("Hit TAB to complete. Type 'help' and hit RETURN for help.");
System.out
.println("Hit TAB to complete. Type 'help' and hit RETURN for help, and 'quit' to exit.");
}
public void pushPrompt(String prompt) {
@ -179,7 +197,7 @@ public class ShellCommand extends AbstractCommand {
public String popPrompt() {
if (this.prompts.isEmpty()) {
this.prompt = DEFAULT_PROMPT;
this.prompt = this.defaultPrompt;
}
else {
this.prompt = this.prompts.pop();

@ -26,10 +26,10 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.cli.SpringCli.NoArgumentsException;
import org.springframework.boot.cli.SpringCli.NoHelpCommandArgumentsException;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
@ -51,6 +51,12 @@ public class SpringCliTests {
@Mock
private Command regularCommand;
@Mock
private Command shellCommand;
@Mock
private Command anotherCommand;
private Set<Call> calls = EnumSet.noneOf(Call.class);
@Before
@ -76,15 +82,17 @@ public class SpringCliTests {
super.printStackTrace(ex);
}
};
given(this.shellCommand.getName()).willReturn("shell");
given(this.anotherCommand.getName()).willReturn("another");
given(this.regularCommand.getName()).willReturn("command");
given(this.regularCommand.getDescription()).willReturn("A regular command");
this.cli.setCommands(Arrays.asList(this.regularCommand));
this.cli.setCommands(Arrays.asList(this.regularCommand, this.shellCommand));
}
@Test
public void runWithoutArguments() throws Exception {
this.thrown.expect(NoArgumentsException.class);
this.cli.run();
verify(this.shellCommand).run();
}
@Test
@ -93,6 +101,27 @@ public class SpringCliTests {
verify(this.regularCommand).run("--arg1", "arg2");
}
@Test
public void registerCommand() throws Exception {
int before = this.cli.getCommands().size();
this.cli.register(this.anotherCommand);
assertEquals(before + 1, this.cli.getCommands().size());
// Just before the hint command
assertEquals(before - 2, this.cli.getCommands().indexOf(this.cli.find("another")));
this.cli.unregister(this.anotherCommand.getName());
assertEquals(before, this.cli.getCommands().size());
}
@Test
public void reRegisterCommand() throws Exception {
int index = this.cli.getCommands().indexOf(this.cli.find("regularCommand"));
int before = this.cli.getCommands().size();
this.cli.register(this.regularCommand);
assertEquals(before, this.cli.getCommands().size());
assertEquals(index,
this.cli.getCommands().indexOf(this.cli.find("regularCommand")));
}
@Test
public void missingCommand() throws Exception {
this.thrown.expect(NoSuchCommandException.class);
@ -106,13 +135,6 @@ public class SpringCliTests {
assertThat(this.calls, equalTo((Set<Call>) EnumSet.noneOf(Call.class)));
}
@Test
public void handlesNoArgumentsException() throws Exception {
int status = this.cli.runAndHandleErrors();
assertThat(status, equalTo(1));
assertThat(this.calls, equalTo((Set<Call>) EnumSet.of(Call.SHOW_USAGE)));
}
@Test
public void handlesNoSuchCommand() throws Exception {
int status = this.cli.runAndHandleErrors("missing");

Loading…
Cancel
Save