' for more information on a specific command.");
+ }
+
+ protected void printStackTrace(Exception ex) {
+ Log.error("");
+ Log.error(ex);
+ Log.error("");
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoArgumentsException.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoArgumentsException.java
new file mode 100644
index 0000000000..116b7463f6
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoArgumentsException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli;
+
+/**
+ * Exception used to indicate that no arguemnts were specified.
+ *
+ * @author Phillip Webb
+ */
+class NoArgumentsException extends CommandException {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoHelpCommandArgumentsException.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoHelpCommandArgumentsException.java
new file mode 100644
index 0000000000..5bbdd043bf
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoHelpCommandArgumentsException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli;
+
+/**
+ * Exception used to when the help command is called without arguments.
+ *
+ * @author Phillip Webb
+ */
+public class NoHelpCommandArgumentsException extends CommandException {
+
+ private static final long serialVersionUID = 1L;
+
+ public NoHelpCommandArgumentsException() {
+ super(Option.SHOW_USAGE, Option.HIDE_MESSAGE);
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoSuchCommandException.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoSuchCommandException.java
index 02c52f7e9d..5e473ab2a1 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoSuchCommandException.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/NoSuchCommandException.java
@@ -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.
@@ -17,17 +17,16 @@
package org.springframework.boot.cli;
/**
- * Exception thrown when an unknown command is specified.
+ * Exception used when a command is not found.
*
* @author Phillip Webb
*/
-class NoSuchCommandException extends SpringCliException {
+public class NoSuchCommandException extends CommandException {
private static final long serialVersionUID = 1L;
public NoSuchCommandException(String name) {
- super(String.format("%1$s: '%2$s' is not a valid command. See '%1$s help'.",
- SpringCli.CLI_APP, name));
+ super(String.format("'%1$s' is not a valid command. See 'help'.", name));
}
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/SpringCli.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/SpringCli.java
index 7aec4592b9..d9e28281a8 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/SpringCli.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/SpringCli.java
@@ -16,428 +16,45 @@
package org.springframework.boot.cli;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
import java.util.ServiceLoader;
-import java.util.Set;
-import org.springframework.boot.cli.command.AbstractCommand;
+import org.springframework.boot.cli.command.CommandFactory;
+import org.springframework.boot.cli.command.core.HelpCommand;
+import org.springframework.boot.cli.command.core.VersionCommand;
+import org.springframework.boot.cli.command.shell.ShellCommand;
/**
* Spring Command Line Interface. This is the main entry-point for the Spring command line
- * application. This class will parse input arguments and delegate to a suitable
- * {@link Command} implementation based on the first argument.
- *
- *
- * The '-d' and '--debug' switches are handled by this class, however, most argument
- * parsing is left to the {@link Command} implementation.
+ * application.
*
* @author Phillip Webb
* @see #main(String...)
- * @see SpringCliException
- * @see Command
+ * @see CommandRunner
*/
public class SpringCli {
- public static final String CLI_APP = "spring";
-
- private static final Set NO_EXCEPTION_OPTIONS = EnumSet
- .noneOf(SpringCliException.Option.class);
-
- private List commands = new ArrayList();
-
- private String displayName = CLI_APP + " ";
-
- private Map commandMap = new HashMap();
-
- /**
- * Create a new {@link SpringCli} implementation with the default set of commands.
- */
- public SpringCli(String... args) {
- for (CommandFactory factory : ServiceLoader.load(CommandFactory.class)) {
- for (Command command : factory.getCommands(this)) {
- register(command);
- }
- }
- addBaseCommands();
- }
-
- /**
- * Set the command available to the CLI. Primarily used to support testing. NOTE: The
- * 'help' command will be automatically provided in addition to this list.
- * @param commands the commands to add
- */
- public void setCommands(List extends Command> commands) {
- this.commandMap.clear();
- this.commands = new ArrayList(commands);
- for (Command command : commands) {
- this.commandMap.put(command.getName(), command);
- }
- addBaseCommands();
- }
-
- protected void addBaseCommands() {
- HelpCommand help = new HelpCommand();
- this.commands.add(0, help);
- this.commandMap.put(help.getName(), help);
- HintCommand hint = new HintCommand();
- this.commands.add(hint);
- this.commandMap.put(hint.getName(), hint);
- }
-
- /**
- * Run the CLI and handle and errors.
- * @param args the input arguments
- * @return a return status code (non boot is used to indicate an error)
- */
- public int runAndHandleErrors(String... args) {
- System.setProperty("java.awt.headless", Boolean.toString(true));
- String[] argsWithoutDebugFlags = removeDebugFlags(args);
- boolean debug = argsWithoutDebugFlags.length != args.length;
- try {
- run(argsWithoutDebugFlags);
- return 0;
- }
- catch (NoArgumentsException ex) {
- showUsage();
- return 1;
- }
- catch (Exception ex) {
- return handleError(debug, ex);
- }
- }
-
- private int handleError(boolean debug, Exception ex) {
- Set options = NO_EXCEPTION_OPTIONS;
- if (ex instanceof SpringCliException) {
- options = ((SpringCliException) ex).getOptions();
- }
- if (!(ex instanceof NoHelpCommandArgumentsException)) {
- errorMessage(ex.getMessage());
- }
- if (options.contains(SpringCliException.Option.SHOW_USAGE)) {
- showUsage();
- }
- if (debug || options.contains(SpringCliException.Option.STACK_TRACE)) {
- printStackTrace(ex);
- }
- return 1;
- }
-
- /**
- * The name of this tool when printed by the help command.
- *
- * @param displayName the displayName to set
- */
- public void setDisplayName(String displayName) {
- this.displayName = displayName == null || displayName.length() == 0
- || 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
- * @throws Exception
- */
- protected void run(String... args) throws Exception {
- if (args.length == 0) {
- throw new NoArgumentsException();
- }
- String commandName = args[0];
- String[] commandArguments = Arrays.copyOfRange(args, 1, args.length);
- Command command = find(commandName);
- if (command == null) {
- throw new NoSuchCommandException(commandName);
- }
- command.run(commandArguments);
- }
-
- public final Command find(String name) {
- return this.commandMap.get(name);
- }
-
- public void register(Command command) {
- String name = command.getName();
- Command existing = find(name);
- int index = this.commands.size() - 1;
- index = index >= 0 ? index : 0;
- if (existing != null) {
- index = this.commands.indexOf(existing);
- this.commands.set(index, command);
- }
- else {
- this.commands.add(index, command);
- }
- this.commandMap.put(name, command);
- }
-
- public void unregister(String name) {
- this.commands.remove(find(name));
- }
-
- public List getCommands() {
- return Collections.unmodifiableList(this.commands);
- }
-
- protected void showUsage() {
- Log.infoPrint("usage: " + this.displayName);
- for (Command command : this.commands) {
- if (command.isOptionCommand()) {
- Log.infoPrint("[--" + command.getName() + "] ");
- }
- }
- Log.info("");
- Log.info(" []");
- Log.info("");
- Log.info("Available commands are:");
- for (Command command : this.commands) {
- if (!command.isOptionCommand() && !(command instanceof HintCommand)) {
- String usageHelp = command.getUsageHelp();
- String description = command.getDescription();
- Log.info(String.format("\n %1$s %2$-15s\n %3$s", command.getName(),
- (usageHelp == null ? "" : usageHelp), (description == null ? ""
- : description)));
- }
- }
- Log.info("");
- Log.info("Common options:");
- Log.info(String.format("\n %1$s %2$-15s\n %3$s", "-d, --debug",
- "Verbose mode",
- "Print additional status information for the command you are running"));
- Log.info("");
- Log.info("");
- Log.info("See '" + this.displayName
- + "help ' for more information on a specific command.");
- }
-
- protected void errorMessage(String message) {
- Log.error(message == null ? "Unexpected error" : message);
- }
-
- protected void printStackTrace(Exception ex) {
- Log.error("");
- Log.error(ex);
- Log.error("");
- }
-
- private String[] removeDebugFlags(String[] args) {
- List rtn = new ArrayList(args.length);
- for (String arg : args) {
- if (!("-d".equals(arg) || "--debug".equals(arg))) {
- rtn.add(arg);
- }
- }
- return rtn.toArray(new String[rtn.size()]);
- }
-
- /**
- * Internal {@link Command} used for 'help' requests.
- */
- private class HelpCommand extends AbstractCommand {
-
- public HelpCommand() {
- super("help", "Get help on commands", true);
- }
-
- @Override
- public String getUsageHelp() {
- return "command";
- }
-
- @Override
- public String getHelp() {
- return null;
- }
-
- @Override
- public Collection getOptionsHelp() {
- List help = new ArrayList();
- for (final Command command : SpringCli.this.commands) {
- if (!(command instanceof HelpCommand)
- && !(command instanceof HintCommand)) {
- help.add(new OptionHelp() {
-
- @Override
- public Set getOptions() {
- return Collections.singleton(command.getName());
- }
-
- @Override
- public String getUsageHelp() {
- return "";
- }
- });
- }
- }
- return help;
- }
-
- @Override
- public void run(String... args) throws Exception {
- if (args.length == 0) {
- throw new NoHelpCommandArgumentsException();
- }
- String commandName = args[0];
- for (Command command : SpringCli.this.commands) {
- if (command.getName().equals(commandName)) {
- Log.info(SpringCli.this.displayName + command.getName() + " - "
- + command.getDescription());
- Log.info("");
- if (command.getUsageHelp() != null) {
- Log.info("usage: " + SpringCli.this.displayName
- + command.getName() + " " + command.getUsageHelp());
- Log.info("");
- }
- if (command.getHelp() != null) {
- Log.info(command.getHelp());
- }
- return;
- }
- }
- throw new NoSuchCommandException(commandName);
- }
-
- }
-
- /**
- * Provides hints for shell auto-completion. Expects to be called with the current
- * index followed by a list of arguments already typed.
- */
- private class HintCommand extends AbstractCommand {
-
- public HintCommand() {
- super("hint", "Provides hints for shell auto-completion");
- }
-
- @Override
- public void run(String... args) throws Exception {
- try {
- int index = (args.length == 0 ? 0 : Integer.valueOf(args[0]) - 1);
- List arguments = new ArrayList(args.length);
- for (int i = 2; i < args.length; i++) {
- arguments.add(args[i]);
- }
- String starting = "";
- if (index < arguments.size()) {
- starting = arguments.remove(index);
- }
- if (index == 0) {
- showCommandHints(starting);
- }
- else if ((arguments.size() > 0) && (starting.length() > 0)) {
- String command = arguments.remove(0);
- showCommandOptionHints(command,
- Collections.unmodifiableList(arguments), starting);
- }
- }
- catch (Exception ex) {
- // Swallow and provide no hints
- }
- }
-
- private void showCommandHints(String starting) {
- for (Command command : SpringCli.this.commands) {
- if (isHintMatch(command, starting)) {
- Log.info(command.getName() + " " + command.getDescription());
- }
- }
- }
-
- private boolean isHintMatch(Command command, String starting) {
- if (command instanceof HintCommand) {
- return false;
- }
- return command.getName().startsWith(starting)
- || (command.isOptionCommand() && ("--" + command.getName())
- .startsWith(starting));
- }
-
- private void showCommandOptionHints(String commandName,
- List specifiedArguments, String starting) {
- Command command = find(commandName);
- if (command != null) {
- for (OptionHelp help : command.getOptionsHelp()) {
- if (!alreadyUsed(help, specifiedArguments)) {
- for (String option : help.getOptions()) {
- if (option.startsWith(starting)) {
- Log.info(option + " " + help.getUsageHelp());
- }
- }
- }
- }
- }
- }
-
- private boolean alreadyUsed(OptionHelp help, List specifiedArguments) {
- for (String argument : specifiedArguments) {
- if (help.getOptions().contains(argument)) {
- return true;
- }
- }
- return false;
- }
- }
-
- static class NoHelpCommandArgumentsException extends SpringCliException {
-
- private static final long serialVersionUID = 1L;
-
- public NoHelpCommandArgumentsException() {
- super(Option.SHOW_USAGE);
- }
-
- }
-
- static class NoArgumentsException extends SpringCliException {
-
- private static final long serialVersionUID = 1L;
-
- }
-
- /**
- * The main CLI entry-point.
- * @param args CLI arguments
- */
public static void main(String... args) {
- String[] init = new String[1];
- int index = 0;
- String arg = args[0];
- if (arg.startsWith("--init")) {
- if (arg.contains("=") || args.length < 2) {
- init[0] = arg;
- index = 1;
- }
- else {
- init[0] = arg + "=" + args[1];
- index = 2;
- }
- }
- if (index > 0) {
- String[] newargs = new String[args.length - index];
- System.arraycopy(args, index, newargs, 0, newargs.length);
- args = newargs;
- }
- else {
- init = new String[0];
- }
- int exitCode = new SpringCli(init).runAndHandleErrors(args);
+ System.setProperty("java.awt.headless", Boolean.toString(true));
+
+ CommandRunner runner = new CommandRunner("spring");
+ runner.addHelpCommand();
+ addServiceLoaderCommands(runner);
+ runner.addCommand(new ShellCommand());
+ runner.addHintCommand();
+ runner.setOptionCommands(HelpCommand.class, VersionCommand.class);
+
+ int exitCode = runner.runAndHandleErrors(args);
if (exitCode != 0) {
System.exit(exitCode);
}
}
+
+ private static void addServiceLoaderCommands(CommandRunner runner) {
+ ServiceLoader factories = ServiceLoader.load(
+ CommandFactory.class, runner.getClass().getClassLoader());
+ for (CommandFactory factory : factories) {
+ runner.addCommands(factory.getCommands());
+ }
+ }
+
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/AbstractCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/AbstractCommand.java
index d04c2b4a59..850b21face 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/AbstractCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/AbstractCommand.java
@@ -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.
@@ -19,9 +19,6 @@ package org.springframework.boot.cli.command;
import java.util.Collection;
import java.util.Collections;
-import org.springframework.boot.cli.Command;
-import org.springframework.boot.cli.OptionHelp;
-
/**
* Abstract {@link Command} implementation.
*
@@ -34,27 +31,14 @@ public abstract class AbstractCommand implements Command {
private final String description;
- private final boolean optionCommand;
-
/**
* Create a new {@link AbstractCommand} instance.
* @param name the name of the command
* @param description the command description
*/
protected AbstractCommand(String name, String description) {
- this(name, description, false);
- }
-
- /**
- * Create a new {@link AbstractCommand} instance.
- * @param name the name of the command
- * @param description the command description
- * @param optionCommand if this command is an option command
- */
- protected AbstractCommand(String name, String description, boolean optionCommand) {
this.name = name;
this.description = description;
- this.optionCommand = optionCommand;
}
@Override
@@ -62,11 +46,6 @@ public abstract class AbstractCommand implements Command {
return this.name;
}
- @Override
- public boolean isOptionCommand() {
- return this.optionCommand;
- }
-
@Override
public String getDescription() {
return this.description;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/Command.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/Command.java
similarity index 82%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/Command.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/Command.java
index d9f3c46057..29dce3902c 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/Command.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/Command.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli;
+package org.springframework.boot.cli.command;
import java.util.Collection;
@@ -37,13 +37,6 @@ public interface Command {
*/
String getDescription();
- /**
- * Returns {@code true} if this is an 'option command'. An option command is a special
- * type of command that usually makes more sense to present as if it is an option. For
- * example '--version'.
- */
- boolean isOptionCommand();
-
/**
* Returns usage help for the command. This should be a simple one-line string
* describing basic usage. e.g. '[options] <file>'. Do not include the name of
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/CommandFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandFactory.java
similarity index 86%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/CommandFactory.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandFactory.java
index 5126c830be..2dcbc5350d 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/CommandFactory.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandFactory.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli;
+package org.springframework.boot.cli.command;
import java.util.Collection;
import java.util.ServiceLoader;
@@ -31,6 +31,6 @@ public interface CommandFactory {
* Returns the CLI {@link Command}s.
* @return The commands
*/
- Collection getCommands(SpringCli cli);
+ Collection getCommands();
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java
index ee4368e150..1ea62a9aaa 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/DefaultCommandFactory.java
@@ -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,14 +16,15 @@
package org.springframework.boot.cli.command;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
-import org.springframework.boot.cli.Command;
-import org.springframework.boot.cli.CommandFactory;
-import org.springframework.boot.cli.SpringCli;
+import org.springframework.boot.cli.command.core.VersionCommand;
+import org.springframework.boot.cli.command.grab.CleanCommand;
+import org.springframework.boot.cli.command.grab.GrabCommand;
+import org.springframework.boot.cli.command.run.RunCommand;
+import org.springframework.boot.cli.command.test.TestCommand;
/**
* Default implementation of {@link CommandFactory}.
@@ -33,25 +34,12 @@ import org.springframework.boot.cli.SpringCli;
public class DefaultCommandFactory implements CommandFactory {
private static final List DEFAULT_COMMANDS = Arrays. asList(
- new VersionCommand(), new CleanCommand(), new TestCommand(),
- new GrabCommand());
-
- private Collection commands;
+ new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand(),
+ new CleanCommand());
@Override
- public Collection getCommands(SpringCli cli) {
- if (this.commands == null) {
- synchronized (this) {
- if (this.commands == null) {
- this.commands = new ArrayList(DEFAULT_COMMANDS);
- RunCommand run = new RunCommand();
- ShellCommand shell = new ShellCommand(cli);
- this.commands.add(run);
- this.commands.add(shell);
- }
- }
- }
- return this.commands;
+ public Collection getCommands() {
+ return DEFAULT_COMMANDS;
}
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHandler.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHandler.java
index 7150e85a6c..45d18cecaf 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHandler.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHandler.java
@@ -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.
@@ -38,8 +38,6 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpecBuilder;
-import org.springframework.boot.cli.OptionHelp;
-
/**
* Delegate used by {@link OptionParsingCommand} to parse options and run the command.
*
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/OptionHelp.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHelp.java
similarity index 89%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/OptionHelp.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHelp.java
index 8fbe5d0910..fb62221870 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/OptionHelp.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionHelp.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli;
+package org.springframework.boot.cli.command;
import java.util.Set;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java
index 4a1be95f92..769400ce52 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java
@@ -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.
@@ -18,9 +18,6 @@ package org.springframework.boot.cli.command;
import java.util.Collection;
-import org.springframework.boot.cli.Command;
-import org.springframework.boot.cli.OptionHelp;
-
/**
* Base class for a {@link Command} that parse options using an {@link OptionHandler}.
*
@@ -33,12 +30,7 @@ public abstract class OptionParsingCommand extends AbstractCommand {
private OptionHandler handler;
protected OptionParsingCommand(String name, String description, OptionHandler handler) {
- this(name, description, false, handler);
- }
-
- protected OptionParsingCommand(String name, String description,
- boolean optionCommand, OptionHandler handler) {
- super(name, description, optionCommand);
+ super(name, description);
this.handler = handler;
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ShellCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ShellCommand.java
deleted file mode 100644
index fe112f67a5..0000000000
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/ShellCommand.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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.
- * You may obtain a copy of the License at
- *
- * http://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.boot.cli.command;
-
-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;
-
-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;
-
-/**
- * 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
- * @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;
-
- private String prompt = this.defaultPrompt;
-
- private Stack prompts = new Stack();
-
- public ShellCommand(SpringCli springCli) {
- super("shell", "Start a nested shell");
- this.springCli = springCli;
- }
-
- @Override
- public void run(String... args) throws Exception {
- enhance(this.springCli);
-
- InputStream sysin = System.in;
- PrintStream systemOut = System.out;
- PrintStream systemErr = System.err;
-
- ConsoleReader consoleReader = createConsoleReader();
- printBanner();
-
- PrintStream out = new PrintStream(new ConsoleReaderOutputStream(consoleReader));
- System.setIn(consoleReader.getInput());
- System.setOut(out);
- System.setErr(out);
-
- try {
- runReadLoop(consoleReader, systemOut, systemErr);
- }
- finally {
- System.setIn(sysin);
- System.setOut(systemOut);
- System.setErr(systemErr);
- consoleReader.shutdown();
- }
- }
-
- protected void enhance(SpringCli cli) {
- 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);
- // Prevent exceptions if user types !foo. If anyone knows how to process those
- // exceptions, please help out and write some code.
- reader.setExpandEvents(false);
- reader.setCompletionHandler(new CandidateListCompletionHandler());
- return reader;
- }
-
- 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, 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.setPrompt(null);
- 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 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, systemOut, systemErr);
- }
- }
- }
- }
-
- 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 parseArgs(String line) {
- List parts = new ArrayList();
- 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 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 args, PrintStream systemOut,
- PrintStream systemErr) {
- if (!getName().equals(args.get(0))) {
- PrintStream out = System.out;
- PrintStream err = System.err;
- System.setOut(systemOut);
- System.setErr(systemErr);
- try {
- this.springCli.runAndHandleErrors(args.toArray(new String[args.size()]));
- }
- finally {
- System.setOut(out);
- System.setErr(err);
- }
- }
- }
-
- public void pushPrompt(String prompt) {
- this.prompts.push(this.prompt);
- this.prompt = prompt;
- }
-
- public String popPrompt() {
- if (this.prompts.isEmpty()) {
- this.prompt = this.defaultPrompt;
- }
- else {
- this.prompt = this.prompts.pop();
- }
- return this.prompt;
- }
-
- private static class ConsoleReaderOutputStream extends OutputStream {
-
- private ConsoleReader consoleReader;
-
- public ConsoleReaderOutputStream(ConsoleReader consoleReader) {
- this.consoleReader = consoleReader;
- }
-
- @Override
- public void write(int b) throws IOException {
- this.consoleReader.getOutput().write(b);
- }
-
- }
-}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java
new file mode 100644
index 0000000000..2987a8449f
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.core;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.boot.cli.CommandRunner;
+import org.springframework.boot.cli.Log;
+import org.springframework.boot.cli.NoHelpCommandArgumentsException;
+import org.springframework.boot.cli.NoSuchCommandException;
+import org.springframework.boot.cli.command.AbstractCommand;
+import org.springframework.boot.cli.command.Command;
+import org.springframework.boot.cli.command.OptionHelp;
+
+/**
+ * Internal {@link Command} used for 'help' requests.
+ *
+ * @author Phillip Webb
+ */
+public class HelpCommand extends AbstractCommand {
+
+ private final CommandRunner commandRunner;
+
+ public HelpCommand(CommandRunner commandRunner) {
+ super("help", "Get help on commands");
+ this.commandRunner = commandRunner;
+ }
+
+ @Override
+ public String getUsageHelp() {
+ return "command";
+ }
+
+ @Override
+ public String getHelp() {
+ return null;
+ }
+
+ @Override
+ public Collection getOptionsHelp() {
+ List help = new ArrayList();
+ for (final Command command : this.commandRunner) {
+ if (isHelpShown(command)) {
+ help.add(new OptionHelp() {
+
+ @Override
+ public Set getOptions() {
+ return Collections.singleton(command.getName());
+ }
+
+ @Override
+ public String getUsageHelp() {
+ return command.getDescription();
+ }
+
+ });
+ }
+ }
+ return help;
+ }
+
+ private boolean isHelpShown(Command command) {
+ if (command instanceof HelpCommand || command instanceof HintCommand) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ if (args.length == 0) {
+ throw new NoHelpCommandArgumentsException();
+ }
+ String commandName = args[0];
+ for (Command command : this.commandRunner) {
+ if (command.getName().equals(commandName)) {
+ Log.info(this.commandRunner.getName() + command.getName() + " - "
+ + command.getDescription());
+ Log.info("");
+ if (command.getUsageHelp() != null) {
+ Log.info("usage: " + this.commandRunner.getName() + command.getName()
+ + " " + command.getUsageHelp());
+ Log.info("");
+ }
+ if (command.getHelp() != null) {
+ Log.info(command.getHelp());
+ }
+ return;
+ }
+ }
+ throw new NoSuchCommandException(commandName);
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HintCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HintCommand.java
new file mode 100644
index 0000000000..1a46e54098
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HintCommand.java
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.core;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.boot.cli.CommandRunner;
+import org.springframework.boot.cli.Log;
+import org.springframework.boot.cli.command.AbstractCommand;
+import org.springframework.boot.cli.command.Command;
+import org.springframework.boot.cli.command.OptionHelp;
+
+/**
+ * Internal {@link Command} to provide hints for shell auto-completion. Expects to be
+ * called with the current index followed by a list of arguments already typed.
+ *
+ * @author Phillip Webb
+ */
+public class HintCommand extends AbstractCommand {
+
+ private final CommandRunner commandRunner;
+
+ public HintCommand(CommandRunner commandRunner) {
+ super("hint", "Provides hints for shell auto-completion");
+ this.commandRunner = commandRunner;
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ try {
+ int index = (args.length == 0 ? 0 : Integer.valueOf(args[0]) - 1);
+ List arguments = new ArrayList(args.length);
+ for (int i = 2; i < args.length; i++) {
+ arguments.add(args[i]);
+ }
+ String starting = "";
+ if (index < arguments.size()) {
+ starting = arguments.remove(index);
+ }
+ if (index == 0) {
+ showCommandHints(starting);
+ }
+ else if ((arguments.size() > 0) && (starting.length() > 0)) {
+ String command = arguments.remove(0);
+ showCommandOptionHints(command, Collections.unmodifiableList(arguments),
+ starting);
+ }
+ }
+ catch (Exception ex) {
+ // Swallow and provide no hints
+ }
+ }
+
+ private void showCommandHints(String starting) {
+ for (Command command : this.commandRunner) {
+ if (isHintMatch(command, starting)) {
+ Log.info(command.getName() + " " + command.getDescription());
+ }
+ }
+ }
+
+ private boolean isHintMatch(Command command, String starting) {
+ if (command instanceof HintCommand) {
+ return false;
+ }
+ return command.getName().startsWith(starting)
+ || (this.commandRunner.isOptionCommand(command) && ("--" + command
+ .getName()).startsWith(starting));
+ }
+
+ private void showCommandOptionHints(String commandName,
+ List specifiedArguments, String starting) {
+ Command command = this.commandRunner.findCommand(commandName);
+ if (command != null) {
+ for (OptionHelp help : command.getOptionsHelp()) {
+ if (!alreadyUsed(help, specifiedArguments)) {
+ for (String option : help.getOptions()) {
+ if (option.startsWith(starting)) {
+ Log.info(option + " " + help.getUsageHelp());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean alreadyUsed(OptionHelp help, List specifiedArguments) {
+ for (String argument : specifiedArguments) {
+ if (help.getOptions().contains(argument)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/VersionCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/VersionCommand.java
similarity index 77%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/VersionCommand.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/VersionCommand.java
index b6c2a668f2..c58d1ac424 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/VersionCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/VersionCommand.java
@@ -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.
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.command;
+package org.springframework.boot.cli.command.core;
-import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.Log;
+import org.springframework.boot.cli.command.AbstractCommand;
+import org.springframework.boot.cli.command.Command;
/**
* {@link Command} to display the 'version' number.
@@ -27,7 +28,7 @@ import org.springframework.boot.cli.Log;
public class VersionCommand extends AbstractCommand {
public VersionCommand() {
- super("version", "Show the version", true);
+ super("version", "Show the version");
}
@Override
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CleanCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/CleanCommand.java
similarity index 94%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CleanCommand.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/CleanCommand.java
index 814cd63fa7..e92e23cb73 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CleanCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/CleanCommand.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.command;
+package org.springframework.boot.cli.command.grab;
import java.io.File;
import java.util.ArrayList;
@@ -23,8 +23,10 @@ import java.util.List;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
-import org.springframework.boot.cli.Command;
import org.springframework.boot.cli.Log;
+import org.springframework.boot.cli.command.Command;
+import org.springframework.boot.cli.command.OptionHandler;
+import org.springframework.boot.cli.command.OptionParsingCommand;
/**
* {@link Command} to 'clean' up grapes, removing cached dependencies and forcing a
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/GrabCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/GrabCommand.java
similarity index 85%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/GrabCommand.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/GrabCommand.java
index 25f4c690e7..881d74eb73 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/GrabCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/GrabCommand.java
@@ -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.
@@ -14,13 +14,16 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.command;
+package org.springframework.boot.cli.command.grab;
import java.util.List;
import joptsimple.OptionSet;
-import org.springframework.boot.cli.Command;
+import org.springframework.boot.cli.command.Command;
+import org.springframework.boot.cli.command.CompilerOptionHandler;
+import org.springframework.boot.cli.command.OptionParsingCommand;
+import org.springframework.boot.cli.command.SourceOptions;
import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.GroovyCompilerConfigurationAdapter;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java
similarity index 92%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java
index d99b9edb6e..759d81f46a 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.command;
+package org.springframework.boot.cli.command.run;
import java.awt.Desktop;
import java.io.File;
@@ -24,13 +24,14 @@ import java.util.logging.Level;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
-import org.springframework.boot.cli.Command;
+import org.springframework.boot.cli.command.Command;
+import org.springframework.boot.cli.command.CompilerOptionHandler;
+import org.springframework.boot.cli.command.OptionParsingCommand;
+import org.springframework.boot.cli.command.SourceOptions;
import org.springframework.boot.cli.compiler.GroovyCompilerConfigurationAdapter;
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
-import org.springframework.boot.cli.runner.SpringApplicationRunner;
-import org.springframework.boot.cli.runner.SpringApplicationRunnerConfiguration;
import static java.util.Arrays.asList;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunner.java
similarity index 98%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunner.java
index 72ffa954fc..dfd25dc21b 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunner.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.runner;
+package org.springframework.boot.cli.command.run;
import java.io.File;
import java.lang.reflect.Method;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunnerConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunnerConfiguration.java
similarity index 91%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunnerConfiguration.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunnerConfiguration.java
index 1c31131001..426630d752 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunnerConfiguration.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/SpringApplicationRunnerConfiguration.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.runner;
+package org.springframework.boot.cli.command.run;
import java.util.logging.Level;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/AnsiString.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/AnsiString.java
new file mode 100644
index 0000000000..1a0b3f43cf
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/AnsiString.java
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.shell;
+
+import jline.Terminal;
+
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.AnsiRenderer.Code;
+
+/**
+ * Simple utitliy class to build an ANSI string when supported by the {@link Terminal}.
+ *
+ * @author Phillip Webb
+ */
+class AnsiString {
+
+ private final Terminal terminal;
+
+ private StringBuilder value = new StringBuilder();
+
+ /**
+ * Create a new {@link AnsiString} for the given {@link Terminal}.
+ * @param terminal the terminal used to test if {@link Terminal#isAnsiSupported() ANSI
+ * is supported}.
+ */
+ AnsiString(Terminal terminal) {
+ this.terminal = terminal;
+ }
+
+ /**
+ * Append text with the given ANSI codes
+ * @param text the text to append
+ * @param codes the ANSI codes
+ * @return this string
+ */
+ AnsiString append(String text, Code... codes) {
+ if (codes.length == 0 || !isAnsiSupported()) {
+ this.value.append(text);
+ return this;
+ }
+ Ansi ansi = Ansi.ansi();
+ for (Code code : codes) {
+ ansi = applyCode(ansi, code);
+ }
+ this.value.append(ansi.a(text).reset().toString());
+ return this;
+ }
+
+ private Ansi applyCode(Ansi ansi, Code code) {
+ if (code.isColor()) {
+ if (code.isBackground()) {
+ return ansi.bg(code.getColor());
+ }
+ return ansi.fg(code.getColor());
+ }
+ return ansi.a(code.getAttribute());
+ }
+
+ private boolean isAnsiSupported() {
+ return this.terminal.isAnsiSupported();
+ }
+
+ @Override
+ public String toString() {
+ return this.value.toString();
+ }
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ClearCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ClearCommand.java
new file mode 100644
index 0000000000..9eb94fd4e9
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ClearCommand.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.shell;
+
+import jline.console.ConsoleReader;
+
+import org.springframework.boot.cli.command.AbstractCommand;
+
+/**
+ * Clear the {@link Shell} screen.
+ *
+ * @author Dave Syer
+ * @author Phillip Webb
+ */
+class ClearCommand extends AbstractCommand {
+
+ private final ConsoleReader consoleReader;
+
+ public ClearCommand(ConsoleReader consoleReader) {
+ super("clear", "Clear the screen");
+ this.consoleReader = consoleReader;
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ this.consoleReader.setPrompt("");
+ this.consoleReader.clearScreen();
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandCompleter.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/CommandCompleter.java
similarity index 78%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandCompleter.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/CommandCompleter.java
index be1512b998..682c90a6cd 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandCompleter.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/CommandCompleter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.command;
+package org.springframework.boot.cli.command.shell;
import java.io.IOException;
import java.util.ArrayList;
@@ -23,15 +23,16 @@ import java.util.List;
import java.util.Map;
import jline.console.ConsoleReader;
+import jline.console.completer.AggregateCompleter;
import jline.console.completer.ArgumentCompleter;
+import jline.console.completer.ArgumentCompleter.ArgumentDelimiter;
import jline.console.completer.Completer;
-import jline.console.completer.NullCompleter;
+import jline.console.completer.FileNameCompleter;
import jline.console.completer.StringsCompleter;
-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.boot.cli.command.Command;
+import org.springframework.boot.cli.command.OptionHelp;
/**
* JLine {@link Completer} for Spring Boot {@link Command}s.
@@ -41,26 +42,29 @@ import org.springframework.boot.cli.SpringCli;
*/
public class CommandCompleter extends StringsCompleter {
- private final Map optionCompleters = new HashMap();
+ private final Map commandCompleters = new HashMap();
private List commands = new ArrayList();
private ConsoleReader console;
- public CommandCompleter(ConsoleReader consoleReader, SpringCli cli) {
+ public CommandCompleter(ConsoleReader consoleReader,
+ ArgumentDelimiter argumentDelimiter, Iterable commands) {
this.console = consoleReader;
- this.commands.addAll(cli.getCommands());
List names = new ArrayList();
- for (Command command : this.commands) {
+ for (Command command : commands) {
+ this.commands.add(command);
names.add(command.getName());
List options = new ArrayList();
for (OptionHelp optionHelp : command.getOptionsHelp()) {
options.addAll(optionHelp.getOptions());
}
- StringsCompleter commandCompleter = new StringsCompleter(command.getName());
- StringsCompleter optionsCompleter = new StringsCompleter(options);
- this.optionCompleters.put(command.getName(), new ArgumentCompleter(
- commandCompleter, optionsCompleter, new NullCompleter()));
+ AggregateCompleter arguementCompleters = new AggregateCompleter(
+ new StringsCompleter(options), new FileNameCompleter());
+ ArgumentCompleter argumentCompleter = new ArgumentCompleter(
+ argumentDelimiter, arguementCompleters);
+ argumentCompleter.setStrict(false);
+ this.commandCompleters.put(command.getName(), argumentCompleter);
}
getStrings().addAll(names);
}
@@ -77,7 +81,7 @@ public class CommandCompleter extends StringsCompleter {
printUsage(command);
break;
}
- Completer completer = this.optionCompleters.get(command.getName());
+ Completer completer = this.commandCompleters.get(command.getName());
if (completer != null) {
completionIndex = completer.complete(buffer, cursor, candidates);
break;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/EscapeAwareWhiteSpaceArgumentDelimiter.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/EscapeAwareWhiteSpaceArgumentDelimiter.java
new file mode 100644
index 0000000000..51c6567114
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/EscapeAwareWhiteSpaceArgumentDelimiter.java
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.shell;
+
+import jline.console.completer.ArgumentCompleter.ArgumentList;
+import jline.console.completer.ArgumentCompleter.WhitespaceArgumentDelimiter;
+
+/**
+ * Escape ware variant of {@link WhitespaceArgumentDelimiter}.
+ *
+ * @author Phillip Webb
+ */
+class EscapeAwareWhiteSpaceArgumentDelimiter extends WhitespaceArgumentDelimiter {
+
+ @Override
+ public boolean isEscaped(CharSequence buffer, int pos) {
+ return (isEscapeChar(buffer, pos - 1));
+ }
+
+ private boolean isEscapeChar(CharSequence buffer, int pos) {
+ if (pos >= 0) {
+ for (char c : getEscapeChars()) {
+ if (buffer.charAt(pos) == c) {
+ return !isEscapeChar(buffer, pos - 1);
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isQuoted(CharSequence buffer, int pos) {
+ int closingQuote = searchBackwards(buffer, pos - 1, getQuoteChars());
+ if (closingQuote == -1) {
+ return false;
+ }
+ int openingQuote = searchBackwards(buffer, closingQuote - 1,
+ buffer.charAt(closingQuote));
+ if (openingQuote == -1) {
+ return true;
+ }
+ return isQuoted(buffer, openingQuote - 1);
+ }
+
+ private int searchBackwards(CharSequence buffer, int pos, char... chars) {
+ while (pos >= 0) {
+ for (char c : chars) {
+ if (buffer.charAt(pos) == c && !isEscaped(buffer, pos)) {
+ return pos;
+ }
+ }
+ pos--;
+ }
+ return -1;
+ }
+
+ public String[] parseArguments(String line) {
+ ArgumentList delimit = delimit(line, 0);
+ return cleanArguments(delimit.getArguments());
+ }
+
+ private String[] cleanArguments(String[] arguments) {
+ String[] cleanArguments = new String[arguments.length];
+ for (int i = 0; i < arguments.length; i++) {
+ cleanArguments[i] = cleanArgument(arguments[i]);
+ }
+ return cleanArguments;
+ }
+
+ private String cleanArgument(String argument) {
+ for (char c : getQuoteChars()) {
+ String quote = String.valueOf(c);
+ if (argument.startsWith(quote) && argument.endsWith(quote)) {
+ return replaceEscapes(argument.substring(1, argument.length() - 1));
+ }
+ }
+ return replaceEscapes(argument);
+ }
+
+ private String replaceEscapes(String string) {
+ string = string.replace("\\ ", " ");
+ string = string.replace("\\\\", "\\");
+ string = string.replace("\\t", "\t");
+ string = string.replace("\\\"", "\"");
+ string = string.replace("\\\'", "\'");
+ return string;
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/StopCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ExitCommand.java
similarity index 55%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/StopCommand.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ExitCommand.java
index c2cf308ade..298b42e53e 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/StopCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ExitCommand.java
@@ -14,28 +14,25 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.command;
+package org.springframework.boot.cli.command.shell;
-import org.springframework.boot.cli.Command;
+import org.springframework.boot.cli.command.AbstractCommand;
+import org.springframework.boot.cli.command.Command;
/**
- * {@link Command} to stop an application started from the {@link ShellCommand shell}.
+ * {@link Command} to quit the {@link Shell}.
*
- * @author Jon Brisbin
+ * @author Phillip Webb
*/
-public class StopCommand extends AbstractCommand {
+class ExitCommand 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;
+ public ExitCommand() {
+ super("exit", "Quit the embedded shell");
}
@Override
- public void run(String... strings) throws Exception {
- this.runCmd.stop();
+ public void run(String... args) throws Exception {
+ throw new ShellExitException();
}
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java
new file mode 100644
index 0000000000..414f3b64bd
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.shell;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.boot.cli.command.Command;
+import org.springframework.boot.cli.command.OptionHelp;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Decorate an existing command to run it by forking the current java process.
+ *
+ * @author Phillip Webb
+ */
+class ForkProcessCommand extends RunProcessCommand {
+
+ private static final String MAIN_CLASS = "org.springframework.boot.loader.JarLauncher";
+
+ private final Command command;
+
+ public ForkProcessCommand(Command command) {
+ super(getJavaCommand());
+ this.command = command;
+ }
+
+ @Override
+ public String getName() {
+ return this.command.getName();
+ }
+
+ @Override
+ public String getDescription() {
+ return this.command.getDescription();
+ }
+
+ @Override
+ public String getUsageHelp() {
+ return this.command.getUsageHelp();
+ }
+
+ @Override
+ public String getHelp() {
+ return this.command.getHelp();
+ }
+
+ @Override
+ public Collection getOptionsHelp() {
+ return this.command.getOptionsHelp();
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ List fullArgs = new ArrayList();
+ fullArgs.add("-cp");
+ fullArgs.add(System.getProperty("java.class.path"));
+ fullArgs.add(MAIN_CLASS);
+ fullArgs.add(this.command.getName());
+ fullArgs.addAll(Arrays.asList(args));
+ run(fullArgs);
+ }
+
+ private static String getJavaCommand() {
+ String javaHome = System.getProperty("java.home");
+ Assert.state(StringUtils.hasLength(javaHome),
+ "Unable to find java command to fork process");
+ try {
+ return getJavaCommand(javaHome).getCanonicalPath();
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ private static File getJavaCommand(String javaHome) {
+ File bin = new File(new File(javaHome), "bin");
+ File command = new File(bin, "java.exe");
+ command = (command.exists() ? command : new File(bin, "java"));
+ Assert.state(command.exists(), "Unable to find java in " + javaHome);
+ return command;
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/PromptCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/PromptCommand.java
similarity index 73%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/PromptCommand.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/PromptCommand.java
index f1b95867f8..e6a8605465 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/PromptCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/PromptCommand.java
@@ -14,34 +14,35 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.command;
+package org.springframework.boot.cli.command.shell;
-import org.springframework.boot.cli.Command;
+import org.springframework.boot.cli.command.AbstractCommand;
+import org.springframework.boot.cli.command.Command;
/**
- * {@link Command} to change the {@link ShellCommand shell} prompt.
+ * {@link Command} to change the {@link Shell} prompt.
*
* @author Dave Syer
*/
public class PromptCommand extends AbstractCommand {
- private final ShellCommand runCmd;
+ private final Shell shell;
- public PromptCommand(ShellCommand runCmd) {
+ public PromptCommand(Shell shell) {
super("prompt", "Change the prompt used with the current 'shell' command. "
+ "Execute with no arguments to return to the previous value.");
- this.runCmd = runCmd;
+ this.shell = shell;
}
@Override
public void run(String... strings) throws Exception {
if (strings.length > 0) {
for (String string : strings) {
- this.runCmd.pushPrompt(string + " ");
+ this.shell.pushPrompt(string + " ");
}
}
else {
- this.runCmd.popPrompt();
+ this.shell.popPrompt();
}
}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/RunProcessCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/RunProcessCommand.java
new file mode 100644
index 0000000000..3412c3cb78
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/RunProcessCommand.java
@@ -0,0 +1,150 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.shell;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.springframework.boot.cli.command.AbstractCommand;
+import org.springframework.boot.cli.command.Command;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Special {@link Command} used to run a process from the shell. NOTE: this command is not
+ * directly installed into the shell.
+ *
+ * @author Phillip Webb
+ */
+class RunProcessCommand extends AbstractCommand {
+
+ private static final Method INHERIT_IO_METHOD = ReflectionUtils.findMethod(
+ ProcessBuilder.class, "inheritIO");
+
+ private static final long JUST_ENDED_LIMIT = 500;
+
+ private final String[] command;
+
+ private volatile Process process;
+
+ private volatile long endTime;
+
+ public RunProcessCommand(String... command) {
+ super(null, null);
+ this.command = command;
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ run(Arrays.asList(args));
+ }
+
+ protected void run(Collection args) throws IOException {
+ ProcessBuilder builder = new ProcessBuilder(this.command);
+ builder.command().addAll(args);
+ builder.redirectErrorStream(true);
+ boolean inheritedIO = inheritIO(builder);
+ try {
+ this.process = builder.start();
+ if (!inheritedIO) {
+ redirectOutput(this.process);
+ }
+ try {
+ this.process.waitFor();
+ }
+ catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ finally {
+ this.endTime = System.currentTimeMillis();
+ this.process = null;
+ }
+ }
+
+ private boolean inheritIO(ProcessBuilder builder) {
+ try {
+ INHERIT_IO_METHOD.invoke(builder);
+ return true;
+ }
+ catch (Exception ex) {
+ return false;
+ }
+ }
+
+ private void redirectOutput(Process process) {
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(
+ process.getInputStream()));
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ String line = reader.readLine();
+ while (line != null) {
+ System.out.println(line);
+ line = reader.readLine();
+ }
+ reader.close();
+ }
+ catch (Exception e) {
+ }
+ };
+ }.start();
+ }
+
+ /**
+ * @return the running process or {@code null}
+ */
+ public Process getRunningProcess() {
+ return this.process;
+ }
+
+ /**
+ * @return {@code true} if the process was stopped.
+ */
+ public boolean handleSigInt() {
+
+ // if the process has just ended, probably due to this SIGINT, consider handled.
+ if (hasJustEnded()) {
+ return true;
+ }
+
+ // destroy the running process
+ Process process = this.process;
+ if (process != null) {
+ try {
+ process.destroy();
+ process.waitFor();
+ this.process = null;
+ return true;
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ return false;
+ }
+
+ public boolean hasJustEnded() {
+ return System.currentTimeMillis() < (this.endTime + JUST_ENDED_LIMIT);
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/Shell.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/Shell.java
new file mode 100644
index 0000000000..cfb6e23462
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/Shell.java
@@ -0,0 +1,261 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.shell;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.Stack;
+
+import jline.console.ConsoleReader;
+import jline.console.completer.CandidateListCompletionHandler;
+
+import org.fusesource.jansi.AnsiRenderer.Code;
+import org.springframework.boot.cli.CommandRunner;
+import org.springframework.boot.cli.command.Command;
+import org.springframework.boot.cli.command.CommandFactory;
+import org.springframework.boot.cli.command.core.VersionCommand;
+import org.springframework.util.StringUtils;
+
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+/**
+ * A shell 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
+ * @author Phillip Webb
+ */
+@SuppressWarnings("restriction")
+public class Shell {
+
+ private static final Set> NON_FORKED_COMMANDS;
+ static {
+ Set> nonForked = new HashSet>();
+ nonForked.add(VersionCommand.class);
+ NON_FORKED_COMMANDS = Collections.unmodifiableSet(nonForked);
+ }
+
+ private static final Signal SIG_INT = new Signal("INT");
+
+ private static final String DEFAULT_PROMPT = "$ ";
+
+ private ShellCommandRunner commandRunner;
+
+ private ConsoleReader consoleReader;
+
+ private EscapeAwareWhiteSpaceArgumentDelimiter argumentDelimiter = new EscapeAwareWhiteSpaceArgumentDelimiter();
+
+ private Stack prompts = new Stack();
+
+ /**
+ * Create a new {@link Shell} instance.
+ * @throws IOException
+ */
+ public Shell() throws IOException {
+ attachSignalHandler();
+ this.consoleReader = new ConsoleReader();
+ this.commandRunner = createCommandRunner();
+ initializeConsoleReader();
+ }
+
+ private ShellCommandRunner createCommandRunner() {
+ ShellCommandRunner runner = new ShellCommandRunner();
+ runner.addHelpCommand();
+ runner.addCommands(getCommands());
+ runner.addAliases("exit", "quit");
+ runner.addAliases("help", "?");
+ runner.addAliases("clear", "cls");
+ return runner;
+ }
+
+ private Iterable getCommands() {
+ List commands = new ArrayList();
+ ServiceLoader factories = ServiceLoader.load(
+ CommandFactory.class, getClass().getClassLoader());
+ for (CommandFactory factory : factories) {
+ for (Command command : factory.getCommands()) {
+ commands.add(convertToForkCommand(command));
+ }
+ }
+ commands.add(new PromptCommand(this));
+ commands.add(new ClearCommand(this.consoleReader));
+ commands.add(new ExitCommand());
+ return commands;
+ }
+
+ private Command convertToForkCommand(Command command) {
+ for (Class> nonForked : NON_FORKED_COMMANDS) {
+ if (nonForked.isInstance(command)) {
+ return command;
+ }
+ }
+ return new ForkProcessCommand(command);
+ }
+
+ private void initializeConsoleReader() {
+ this.consoleReader.setHistoryEnabled(true);
+ this.consoleReader.setBellEnabled(false);
+ this.consoleReader.setExpandEvents(false);
+ this.consoleReader.addCompleter(new CommandCompleter(this.consoleReader,
+ this.argumentDelimiter, this.commandRunner));
+ this.consoleReader.setCompletionHandler(new CandidateListCompletionHandler());
+ }
+
+ private void attachSignalHandler() {
+ Signal.handle(SIG_INT, new SignalHandler() {
+ @Override
+ public void handle(sun.misc.Signal signal) {
+ handleSigInt();
+ }
+ });
+ }
+
+ /**
+ * Push a new prompt to be used by the shell.
+ * @param prompt the prompt
+ * @see #popPrompt()
+ */
+ public void pushPrompt(String prompt) {
+ this.prompts.push(prompt);
+ }
+
+ /**
+ * Pop a previously pushed prompt, returning to the previous value.
+ * @see #pushPrompt(String)
+ */
+ public void popPrompt() {
+ this.prompts.pop();
+ }
+
+ /**
+ * Run the shell until the user exists.
+ * @throws Exception on error
+ */
+ public void run() throws Exception {
+ printBanner();
+ try {
+ runInputLoop();
+ }
+ catch (Exception ex) {
+ if (!(ex instanceof ShellExitException)) {
+ throw ex;
+ }
+ }
+ }
+
+ private void printBanner() {
+ String version = ShellCommand.class.getPackage().getImplementationVersion();
+ version = (version == null ? "" : " (v" + version + ")");
+ System.out.println(ansi("Spring Boot", Code.BOLD).append(version, Code.FAINT));
+ System.out.println(ansi("Hit TAB to complete. Type 'help' and hit "
+ + "RETURN for help, and 'exit' to quit."));
+ }
+
+ private void runInputLoop() throws Exception {
+ while (true) {
+ String line = this.consoleReader.readLine(getPrompt());
+ while (line.endsWith("\\")) {
+ line = line.substring(0, line.length() - 1);
+ line += this.consoleReader.readLine("> ");
+ }
+ if (StringUtils.hasLength(line)) {
+ String[] args = this.argumentDelimiter.parseArguments(line);
+ this.commandRunner.runAndHandleErrors(args);
+ }
+ }
+ }
+
+ private String getPrompt() {
+ String prompt = this.prompts.isEmpty() ? DEFAULT_PROMPT : this.prompts.peek();
+ return ansi(prompt, Code.FG_BLUE).toString();
+ }
+
+ private AnsiString ansi(String text, Code... codes) {
+ return new AnsiString(this.consoleReader.getTerminal()).append(text, codes);
+ }
+
+ /**
+ * Final handle an interrup signal (CTRL-C)
+ */
+ protected void handleSigInt() {
+ if (this.commandRunner.handleSigInt()) {
+ return;
+ }
+ System.out.println("\nThanks for using Spring Boot");
+ System.exit(1);
+ }
+
+ /**
+ * Extension of {@link CommandRunner} to deal with {@link RunProcessCommand}s and
+ * aliases.
+ */
+ private class ShellCommandRunner extends CommandRunner {
+
+ private volatile Command lastCommand;
+
+ private final Map aliases = new HashMap();
+
+ public ShellCommandRunner() {
+ super(null);
+ }
+
+ public void addAliases(String command, String... aliases) {
+ for (String alias : aliases) {
+ this.aliases.put(alias, command);
+ }
+ }
+
+ @Override
+ public Command findCommand(String name) {
+ if (name.startsWith("!")) {
+ return new RunProcessCommand(name.substring(1));
+ }
+ if (this.aliases.containsKey(name)) {
+ name = this.aliases.get(name);
+ }
+ return super.findCommand(name);
+ }
+
+ @Override
+ protected void beforeRun(Command command) {
+ this.lastCommand = command;
+ }
+
+ @Override
+ protected void afterRun(Command command) {
+ }
+
+ public boolean handleSigInt() {
+ Command command = this.lastCommand;
+ if (command != null && command instanceof RunProcessCommand) {
+ return ((RunProcessCommand) command).handleSigInt();
+ }
+ return false;
+ }
+
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellCommand.java
new file mode 100644
index 0000000000..e22cc664f2
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellCommand.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.shell;
+
+import org.springframework.boot.cli.command.AbstractCommand;
+import org.springframework.boot.cli.command.Command;
+
+/**
+ * {@link Command} to start a nested REPL shell.
+ *
+ * @author Phillip Webb
+ * @see Shell
+ */
+public class ShellCommand extends AbstractCommand {
+
+ public ShellCommand() {
+ super("shell", "Start a nested shell");
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ new Shell().run();
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellExitException.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellExitException.java
new file mode 100644
index 0000000000..983d5f1cf8
--- /dev/null
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellExitException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.shell;
+
+import org.springframework.boot.cli.CommandException;
+
+/**
+ * Exception used to stop the {@link Shell}.
+ *
+ * @author Phillip Webb
+ */
+public class ShellExitException extends CommandException {
+
+ private static final long serialVersionUID = 1L;
+
+ public ShellExitException() {
+ super(Option.RETHROW);
+ }
+
+}
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestCommand.java
similarity index 84%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestCommand.java
index 260d3bfbb9..28eacdf522 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestCommand.java
@@ -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.
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.command;
+package org.springframework.boot.cli.command.test;
import joptsimple.OptionSet;
-import org.springframework.boot.cli.Command;
+import org.springframework.boot.cli.command.Command;
+import org.springframework.boot.cli.command.CompilerOptionHandler;
+import org.springframework.boot.cli.command.OptionParsingCommand;
+import org.springframework.boot.cli.command.SourceOptions;
import org.springframework.boot.cli.compiler.GroovyCompilerConfigurationAdapter;
-import org.springframework.boot.cli.testrunner.TestRunner;
-import org.springframework.boot.cli.testrunner.TestRunnerConfiguration;
/**
* {@link Command} to run a groovy test script or scripts.
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestRunner.java
similarity index 97%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestRunner.java
index 2871902d75..8929b2452c 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestRunner.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.testrunner;
+package org.springframework.boot.cli.command.test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunnerConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestRunnerConfiguration.java
similarity index 88%
rename from spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunnerConfiguration.java
rename to spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestRunnerConfiguration.java
index c3389115fa..d3e5433065 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunnerConfiguration.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestRunnerConfiguration.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.cli.testrunner;
+package org.springframework.boot.cli.command.test;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/groovy/DelegateTestRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/DelegateTestRunner.java
index 09eb0c3265..194f2d8f34 100644
--- a/spring-boot-cli/src/main/java/org/springframework/boot/groovy/DelegateTestRunner.java
+++ b/spring-boot-cli/src/main/java/org/springframework/boot/groovy/DelegateTestRunner.java
@@ -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.
@@ -18,7 +18,7 @@ package org.springframework.boot.groovy;
import org.junit.internal.TextListener;
import org.junit.runner.JUnitCore;
-import org.springframework.boot.cli.testrunner.TestRunner;
+import org.springframework.boot.cli.command.test.TestRunner;
/**
* Delegate test runner to launch tests in user application classpath.
diff --git a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.CommandFactory b/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.command.CommandFactory
similarity index 100%
rename from spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.CommandFactory
rename to spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.command.CommandFactory
diff --git a/spring-boot-cli/src/test/java/cli/command/CustomCommandFactory.java b/spring-boot-cli/src/test/java/cli/command/CustomCommandFactory.java
index 0c0231a97a..fa830a3a38 100644
--- a/spring-boot-cli/src/test/java/cli/command/CustomCommandFactory.java
+++ b/spring-boot-cli/src/test/java/cli/command/CustomCommandFactory.java
@@ -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.
@@ -19,9 +19,8 @@ package cli.command;
import java.util.Collection;
import java.util.Collections;
-import org.springframework.boot.cli.Command;
-import org.springframework.boot.cli.CommandFactory;
-import org.springframework.boot.cli.SpringCli;
+import org.springframework.boot.cli.command.Command;
+import org.springframework.boot.cli.command.CommandFactory;
/**
* @author Dave Syer
@@ -29,7 +28,7 @@ import org.springframework.boot.cli.SpringCli;
public class CustomCommandFactory implements CommandFactory {
@Override
- public Collection getCommands(SpringCli cli) {
+ public Collection getCommands() {
return Collections. singleton(new CustomCommand());
}
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java
index f11e2fe37f..02f5ce81fb 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java
@@ -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.
@@ -34,11 +34,11 @@ import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.boot.OutputCapture;
import org.springframework.boot.cli.command.AbstractCommand;
-import org.springframework.boot.cli.command.CleanCommand;
-import org.springframework.boot.cli.command.GrabCommand;
import org.springframework.boot.cli.command.OptionParsingCommand;
-import org.springframework.boot.cli.command.RunCommand;
-import org.springframework.boot.cli.command.TestCommand;
+import org.springframework.boot.cli.command.grab.CleanCommand;
+import org.springframework.boot.cli.command.grab.GrabCommand;
+import org.springframework.boot.cli.command.run.RunCommand;
+import org.springframework.boot.cli.command.test.TestCommand;
/**
* {@link TestRule} that can be used to invoke CLI commands.
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SpringCliTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CommandRunnerTests.java
similarity index 63%
rename from spring-boot-cli/src/test/java/org/springframework/boot/cli/SpringCliTests.java
rename to spring-boot-cli/src/test/java/org/springframework/boot/cli/CommandRunnerTests.java
index d9f0ff3e5e..2704d32b0f 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SpringCliTests.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CommandRunnerTests.java
@@ -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,7 +16,6 @@
package org.springframework.boot.cli;
-import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
@@ -27,28 +26,26 @@ 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 org.springframework.boot.cli.command.Command;
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;
import static org.mockito.Mockito.verify;
/**
- * Tests for {@link SpringCli}.
+ * Tests for {@link CommandRunner}.
*
* @author Phillip Webb
* @author Dave Syer
*/
-public class SpringCliTests {
+public class CommandRunnerTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
- private SpringCli cli;
+ private CommandRunner commandRunner;
@Mock
private Command regularCommand;
@@ -72,98 +69,87 @@ public class SpringCliTests {
public void setup() {
this.loader = Thread.currentThread().getContextClassLoader();
MockitoAnnotations.initMocks(this);
- this.cli = new SpringCli() {
+ this.commandRunner = new CommandRunner("spring") {
@Override
protected void showUsage() {
- SpringCliTests.this.calls.add(Call.SHOW_USAGE);
+ CommandRunnerTests.this.calls.add(Call.SHOW_USAGE);
super.showUsage();
};
@Override
- protected void errorMessage(String message) {
- SpringCliTests.this.calls.add(Call.ERROR_MESSAGE);
- super.errorMessage(message);
+ protected boolean errorMessage(String message) {
+ CommandRunnerTests.this.calls.add(Call.ERROR_MESSAGE);
+ return super.errorMessage(message);
}
@Override
protected void printStackTrace(Exception ex) {
- SpringCliTests.this.calls.add(Call.PRINT_STACK_TRACE);
+ CommandRunnerTests.this.calls.add(Call.PRINT_STACK_TRACE);
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.shellCommand));
+ this.commandRunner.addCommand(this.regularCommand);
+ this.commandRunner.addHelpCommand();
+ this.commandRunner.addHintCommand();
}
@Test
public void runWithoutArguments() throws Exception {
this.thrown.expect(NoArgumentsException.class);
- this.cli.run();
+ this.commandRunner.run();
}
@Test
public void runCommand() throws Exception {
- this.cli.run("command", "--arg1", "arg2");
+ this.commandRunner.run("command", "--arg1", "arg2");
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 - 1, 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);
- this.cli.run("missing");
+ this.commandRunner.run("missing");
}
@Test
public void handlesSuccess() throws Exception {
- int status = this.cli.runAndHandleErrors("command");
+ int status = this.commandRunner.runAndHandleErrors("command");
assertThat(status, equalTo(0));
assertThat(this.calls, equalTo((Set) EnumSet.noneOf(Call.class)));
}
@Test
public void handlesNoSuchCommand() throws Exception {
- int status = this.cli.runAndHandleErrors("missing");
+ int status = this.commandRunner.runAndHandleErrors("missing");
assertThat(status, equalTo(1));
assertThat(this.calls, equalTo((Set) EnumSet.of(Call.ERROR_MESSAGE)));
}
@Test
- public void handlesRegularException() throws Exception {
- willThrow(new RuntimeException()).given(this.regularCommand).run();
- int status = this.cli.runAndHandleErrors("command");
+ public void handlesRegularExceptionWithMessage() throws Exception {
+ willThrow(new RuntimeException("With Message")).given(this.regularCommand).run();
+ int status = this.commandRunner.runAndHandleErrors("command");
assertThat(status, equalTo(1));
assertThat(this.calls, equalTo((Set) EnumSet.of(Call.ERROR_MESSAGE)));
}
+ @Test
+ public void handlesRegularExceptionWithoutMessage() throws Exception {
+ willThrow(new NullPointerException()).given(this.regularCommand).run();
+ int status = this.commandRunner.runAndHandleErrors("command");
+ assertThat(status, equalTo(1));
+ assertThat(this.calls, equalTo((Set) EnumSet.of(Call.ERROR_MESSAGE,
+ Call.PRINT_STACK_TRACE)));
+ }
+
@Test
public void handlesExceptionWithDashD() throws Exception {
willThrow(new RuntimeException()).given(this.regularCommand).run();
- int status = this.cli.runAndHandleErrors("command", "-d");
+ int status = this.commandRunner.runAndHandleErrors("command", "-d");
assertThat(status, equalTo(1));
assertThat(this.calls, equalTo((Set) EnumSet.of(Call.ERROR_MESSAGE,
Call.PRINT_STACK_TRACE)));
@@ -172,7 +158,7 @@ public class SpringCliTests {
@Test
public void handlesExceptionWithDashDashDebug() throws Exception {
willThrow(new RuntimeException()).given(this.regularCommand).run();
- int status = this.cli.runAndHandleErrors("command", "--debug");
+ int status = this.commandRunner.runAndHandleErrors("command", "--debug");
assertThat(status, equalTo(1));
assertThat(this.calls, equalTo((Set) EnumSet.of(Call.ERROR_MESSAGE,
Call.PRINT_STACK_TRACE)));
@@ -181,26 +167,25 @@ public class SpringCliTests {
@Test
public void exceptionMessages() throws Exception {
assertThat(new NoSuchCommandException("name").getMessage(),
- equalTo(SpringCli.CLI_APP + ": 'name' is not a valid command. See '"
- + SpringCli.CLI_APP + " help'."));
+ equalTo("'name' is not a valid command. See 'help'."));
}
@Test
public void help() throws Exception {
- this.cli.run("help", "command");
+ this.commandRunner.run("help", "command");
verify(this.regularCommand).getHelp();
}
@Test
public void helpNoCommand() throws Exception {
this.thrown.expect(NoHelpCommandArgumentsException.class);
- this.cli.run("help");
+ this.commandRunner.run("help");
}
@Test
public void helpUnknownCommand() throws Exception {
this.thrown.expect(NoSuchCommandException.class);
- this.cli.run("help", "missing");
+ this.commandRunner.run("help", "missing");
}
private static enum Call {
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/GrabCommandIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/GrabCommandIntegrationTests.java
index 408f7197da..3970b125ab 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/GrabCommandIntegrationTests.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/GrabCommandIntegrationTests.java
@@ -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.
@@ -22,7 +22,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.springframework.boot.cli.command.GrabCommand;
+import org.springframework.boot.cli.command.grab.GrabCommand;
import org.springframework.util.FileSystemUtils;
import static org.junit.Assert.assertTrue;
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/GrapesCleaner.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/GrapesCleaner.java
index 244724eaa0..58e4f30e3d 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/GrapesCleaner.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/GrapesCleaner.java
@@ -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.
@@ -21,7 +21,7 @@ import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
-import org.springframework.boot.cli.command.CleanCommand;
+import org.springframework.boot.cli.command.grab.CleanCommand;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java
index 7abc230927..ff0304b48f 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/TestCommandIntegrationTests.java
@@ -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.
@@ -22,8 +22,8 @@ import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
-import org.springframework.boot.cli.command.CleanCommand;
-import org.springframework.boot.cli.command.TestCommand;
+import org.springframework.boot.cli.command.grab.CleanCommand;
+import org.springframework.boot.cli.command.test.TestCommand;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/shell/EscapeAwareWhiteSpaceArgumentDelimiterTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/shell/EscapeAwareWhiteSpaceArgumentDelimiterTests.java
new file mode 100644
index 0000000000..536ef394f4
--- /dev/null
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/shell/EscapeAwareWhiteSpaceArgumentDelimiterTests.java
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://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.boot.cli.command.shell;
+
+import jline.console.completer.ArgumentCompleter.ArgumentList;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for {@link EscapeAwareWhiteSpaceArgumentDelimiter}.
+ *
+ * @author Phillip Webb
+ */
+public class EscapeAwareWhiteSpaceArgumentDelimiterTests {
+
+ private EscapeAwareWhiteSpaceArgumentDelimiter delimiter = new EscapeAwareWhiteSpaceArgumentDelimiter();
+
+ @Test
+ public void simple() throws Exception {
+ String s = "one two";
+ assertThat(this.delimiter.delimit(s, 0).getArguments(), equalTo(new String[] {
+ "one", "two" }));
+ assertThat(this.delimiter.parseArguments(s),
+ equalTo(new String[] { "one", "two" }));
+ assertThat(this.delimiter.isDelimiter(s, 2), equalTo(false));
+ assertThat(this.delimiter.isDelimiter(s, 3), equalTo(true));
+ assertThat(this.delimiter.isDelimiter(s, 4), equalTo(false));
+ }
+
+ @Test
+ public void escaped() throws Exception {
+ String s = "o\\ ne two";
+ assertThat(this.delimiter.delimit(s, 0).getArguments(), equalTo(new String[] {
+ "o\\ ne", "two" }));
+ assertThat(this.delimiter.parseArguments(s),
+ equalTo(new String[] { "o ne", "two" }));
+ assertThat(this.delimiter.isDelimiter(s, 2), equalTo(false));
+ assertThat(this.delimiter.isDelimiter(s, 3), equalTo(false));
+ assertThat(this.delimiter.isDelimiter(s, 4), equalTo(false));
+ assertThat(this.delimiter.isDelimiter(s, 5), equalTo(true));
+ }
+
+ @Test
+ public void quoted() throws Exception {
+ String s = "'o ne' 't w o'";
+ assertThat(this.delimiter.delimit(s, 0).getArguments(), equalTo(new String[] {
+ "'o ne'", "'t w o'" }));
+ assertThat(this.delimiter.parseArguments(s), equalTo(new String[] { "o ne",
+ "t w o" }));
+ }
+
+ @Test
+ public void doubleQuoted() throws Exception {
+ String s = "\"o ne\" \"t w o\"";
+ assertThat(this.delimiter.delimit(s, 0).getArguments(), equalTo(new String[] {
+ "\"o ne\"", "\"t w o\"" }));
+ assertThat(this.delimiter.parseArguments(s), equalTo(new String[] { "o ne",
+ "t w o" }));
+ }
+
+ @Test
+ public void nestedQuotes() throws Exception {
+ String s = "\"o 'n''e\" 't \"w o'";
+ assertThat(this.delimiter.delimit(s, 0).getArguments(), equalTo(new String[] {
+ "\"o 'n''e\"", "'t \"w o'" }));
+ assertThat(this.delimiter.parseArguments(s), equalTo(new String[] { "o 'n''e",
+ "t \"w o" }));
+ }
+
+ @Test
+ public void escapedQuotes() throws Exception {
+ String s = "\\'a b";
+ ArgumentList argumentList = this.delimiter.delimit(s, 0);
+ assertThat(argumentList.getArguments(), equalTo(new String[] { "\\'a", "b" }));
+ assertThat(this.delimiter.parseArguments(s), equalTo(new String[] { "'a", "b" }));
+ }
+
+ @Test
+ public void escapes() throws Exception {
+ String s = "\\ \\\\.\\\\\\t";
+ assertThat(this.delimiter.parseArguments(s), equalTo(new String[] { " \\.\\\t" }));
+
+ }
+}
diff --git a/spring-boot-parent/pom.xml b/spring-boot-parent/pom.xml
index 18f6e40b86..6959b15e91 100644
--- a/spring-boot-parent/pom.xml
+++ b/spring-boot-parent/pom.xml
@@ -29,6 +29,11 @@
+
+ jline
+ jline
+ 2.11
+
net.sf.jopt-simple
jopt-simple