Refactor CLI internals for REPL shell
Numerous updates to the Spring CLI, primarily for better embedded REPL
shell support:
* Refactor the CLI application to help separate concerts between the
main CLI and the embedded shell. Both the CLI and embedded shell now
delegate to a new `CommandRunner` to handle running commands. The
runner can be configured differently depending depending on need.
For example, the embedded shell adds the 'prompt' and 'clear'
commands.
* Most `Command` implementations have been moved to sub-packages so that
they can be co-located with the classes that they use.
* Option commands are now only used in the CLI, the embedded shell
does not user them and details have been removed from the Command
interface.
* The REPL shell has been significantly refactored to:
- Support CTRL-C to cancel the running process. This is supported
when running external commands and most internal commands.
- Fork a new JVM when running commands (primarily for CTRL-C support
but also for potential memory and classpath issues)
- Change the "continue" trigger from `<<` to `\`
- Support command completion of files
- Add ANSI color output
- Provide 'help' support for internal commands (such as 'clear')
- Remove the now redundant `stop` command
Fixes gh-227
This commit is contained in:
@@ -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.
|
||||
@@ -23,41 +23,41 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* Runtime exception wrapper that defines additional {@link Option}s that are understood
|
||||
* by the {@link SpringCli}.
|
||||
* by the {@link CommandRunner}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SpringCliException extends RuntimeException {
|
||||
public class CommandException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final EnumSet<Option> options;
|
||||
|
||||
/**
|
||||
* Create a new {@link SpringCliException} with the specified options.
|
||||
* Create a new {@link CommandException} with the specified options.
|
||||
* @param options the exception options
|
||||
*/
|
||||
public SpringCliException(Option... options) {
|
||||
public CommandException(Option... options) {
|
||||
this.options = asEnumSet(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SpringCliException} with the specified options.
|
||||
* Create a new {@link CommandException} with the specified options.
|
||||
* @param message the exception message to display to the user
|
||||
* @param options the exception options
|
||||
*/
|
||||
public SpringCliException(String message, Option... options) {
|
||||
public CommandException(String message, Option... options) {
|
||||
super(message);
|
||||
this.options = asEnumSet(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SpringCliException} with the specified options.
|
||||
* Create a new {@link CommandException} with the specified options.
|
||||
* @param message the exception message to display to the user
|
||||
* @param cause the underlying cause
|
||||
* @param options the exception options
|
||||
*/
|
||||
public SpringCliException(String message, Throwable cause, Option... options) {
|
||||
public CommandException(String message, Throwable cause, Option... options) {
|
||||
super(message, cause);
|
||||
this.options = asEnumSet(options);
|
||||
}
|
||||
@@ -70,17 +70,22 @@ public class SpringCliException extends RuntimeException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options a set of options that are understood by the {@link SpringCli}.
|
||||
* Returns options a set of options that are understood by the {@link CommandRunner}.
|
||||
*/
|
||||
public Set<Option> getOptions() {
|
||||
return Collections.unmodifiableSet(this.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific options understood by the {@link SpringCli}.
|
||||
* Specific options understood by the {@link CommandRunner}.
|
||||
*/
|
||||
public static enum Option {
|
||||
|
||||
/**
|
||||
* Hide the exception message.
|
||||
*/
|
||||
HIDE_MESSAGE,
|
||||
|
||||
/**
|
||||
* Print basic CLI usage information.
|
||||
*/
|
||||
@@ -89,7 +94,12 @@ public class SpringCliException extends RuntimeException {
|
||||
/**
|
||||
* Print the stack-trace of the exception.
|
||||
*/
|
||||
STACK_TRACE
|
||||
STACK_TRACE,
|
||||
|
||||
/**
|
||||
* Re-throw the exception rather than dealing with it.
|
||||
*/
|
||||
RETHROW
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.core.HelpCommand;
|
||||
import org.springframework.boot.cli.command.core.HintCommand;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Main class used to run {@link Command}s.
|
||||
*
|
||||
* @see #addCommand(Command)
|
||||
* @see CommandRunner#runAndHandleErrors(String[])
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class CommandRunner implements Iterable<Command> {
|
||||
|
||||
private static final Set<CommandException.Option> NO_EXCEPTION_OPTIONS = EnumSet
|
||||
.noneOf(CommandException.Option.class);
|
||||
|
||||
private final String name;
|
||||
|
||||
private final List<Command> commands = new ArrayList<Command>();
|
||||
|
||||
private Class<?>[] optionCommandClasses = {};
|
||||
|
||||
/**
|
||||
* Create a new {@link CommandRunner} instance.
|
||||
* @param name the name of the runner or {@code null}
|
||||
*/
|
||||
public CommandRunner(String name) {
|
||||
this.name = (StringUtils.hasLength(name) ? name + " " : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the runner or an empty string. Non-empty names will include a
|
||||
* trailing space character so that they can be used as a prefix.
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 'help' support.
|
||||
*/
|
||||
public void addHelpCommand() {
|
||||
this.commands.add(new HelpCommand(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 'hint' support for command line completion.
|
||||
*/
|
||||
public void addHintCommand() {
|
||||
this.commands.add(new HintCommand(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified commands.
|
||||
* @param commands the commands to add
|
||||
*/
|
||||
public void addCommands(Iterable<Command> commands) {
|
||||
Assert.notNull(commands, "Commands must not be null");
|
||||
for (Command command : commands) {
|
||||
addCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified command.
|
||||
* @param command the command to add.
|
||||
*/
|
||||
public void addCommand(Command command) {
|
||||
Assert.notNull(command, "Command must not be null");
|
||||
this.commands.add(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command classes which should be considered option commands. 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'.
|
||||
* @param commandClasses the classes of option commands.
|
||||
* @see #isOptionCommand(Command)
|
||||
*/
|
||||
public void setOptionCommands(Class<?>... commandClasses) {
|
||||
Assert.notNull(commandClasses, "CommandClasses must not be null");
|
||||
this.optionCommandClasses = commandClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the specified command is an option command.
|
||||
* @param command the command to test
|
||||
* @return {@code true} if the command is an option command
|
||||
* @see #setOptionCommands(Class...)
|
||||
*/
|
||||
public boolean isOptionCommand(Command command) {
|
||||
for (Class<?> optionCommandClass : this.optionCommandClasses) {
|
||||
if (optionCommandClass.isInstance(command)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Command> iterator() {
|
||||
return getCommands().iterator();
|
||||
}
|
||||
|
||||
protected final List<Command> getCommands() {
|
||||
return Collections.unmodifiableList(this.commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a command by name.
|
||||
* @param name the name of the command
|
||||
* @return the command or {@code null} if not found
|
||||
*/
|
||||
public Command findCommand(String name) {
|
||||
for (Command candidate : this.commands) {
|
||||
String candidateName = candidate.getName();
|
||||
if (candidateName.equals(name)
|
||||
|| (isOptionCommand(candidate) && ("--" + candidateName).equals(name))) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the appropriate 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) {
|
||||
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 String[] removeDebugFlags(String[] args) {
|
||||
List<String> rtn = new ArrayList<String>(args.length);
|
||||
for (String arg : args) {
|
||||
if (!("-d".equals(arg) || "--debug".equals(arg))) {
|
||||
rtn.add(arg);
|
||||
}
|
||||
}
|
||||
return rtn.toArray(new String[rtn.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = findCommand(commandName);
|
||||
if (command == null) {
|
||||
throw new NoSuchCommandException(commandName);
|
||||
}
|
||||
beforeRun(command);
|
||||
try {
|
||||
command.run(commandArguments);
|
||||
}
|
||||
finally {
|
||||
afterRun(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass hook called before a command is run.
|
||||
* @param command the command about to run
|
||||
*/
|
||||
protected void beforeRun(Command command) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass hook called after a command has run.
|
||||
* @param command the command that has run
|
||||
*/
|
||||
protected void afterRun(Command command) {
|
||||
}
|
||||
|
||||
private int handleError(boolean debug, Exception ex) {
|
||||
Set<CommandException.Option> options = NO_EXCEPTION_OPTIONS;
|
||||
if (ex instanceof CommandException) {
|
||||
options = ((CommandException) ex).getOptions();
|
||||
if (options.contains(CommandException.Option.RETHROW)) {
|
||||
throw (CommandException) ex;
|
||||
}
|
||||
}
|
||||
boolean couldNotShowMessage = false;
|
||||
if (!options.contains(CommandException.Option.HIDE_MESSAGE)) {
|
||||
couldNotShowMessage = !errorMessage(ex.getMessage());
|
||||
}
|
||||
if (options.contains(CommandException.Option.SHOW_USAGE)) {
|
||||
showUsage();
|
||||
}
|
||||
if (debug || couldNotShowMessage
|
||||
|| options.contains(CommandException.Option.STACK_TRACE)) {
|
||||
printStackTrace(ex);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected boolean errorMessage(String message) {
|
||||
Log.error(message == null ? "Unexpected error" : message);
|
||||
return message != null;
|
||||
}
|
||||
|
||||
protected void showUsage() {
|
||||
Log.infoPrint("usage: " + this.name);
|
||||
for (Command command : this.commands) {
|
||||
if (isOptionCommand(command)) {
|
||||
Log.infoPrint("[--" + command.getName() + "] ");
|
||||
}
|
||||
}
|
||||
Log.info("");
|
||||
Log.info(" <command> [<args>]");
|
||||
Log.info("");
|
||||
Log.info("Available commands are:");
|
||||
for (Command command : this.commands) {
|
||||
if (!isOptionCommand(command)) {
|
||||
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.name
|
||||
+ "help <command>' for more information on a specific command.");
|
||||
}
|
||||
|
||||
protected void printStackTrace(Exception ex) {
|
||||
Log.error("");
|
||||
Log.error(ex);
|
||||
Log.error("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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<SpringCliException.Option> NO_EXCEPTION_OPTIONS = EnumSet
|
||||
.noneOf(SpringCliException.Option.class);
|
||||
|
||||
private List<Command> commands = new ArrayList<Command>();
|
||||
|
||||
private String displayName = CLI_APP + " ";
|
||||
|
||||
private Map<String, Command> commandMap = new HashMap<String, Command>();
|
||||
|
||||
/**
|
||||
* 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<Command>(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<SpringCliException.Option> 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<Command> 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(" <command> [<args>]");
|
||||
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 <command>' 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<String> rtn = new ArrayList<String>(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<OptionHelp> getOptionsHelp() {
|
||||
List<OptionHelp> help = new ArrayList<OptionHelp>();
|
||||
for (final Command command : SpringCli.this.commands) {
|
||||
if (!(command instanceof HelpCommand)
|
||||
&& !(command instanceof HintCommand)) {
|
||||
help.add(new OptionHelp() {
|
||||
|
||||
@Override
|
||||
public Set<String> 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<String> arguments = new ArrayList<String>(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<String> 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<String> 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<CommandFactory> factories = ServiceLoader.load(
|
||||
CommandFactory.class, runner.getClass().getClassLoader());
|
||||
for (CommandFactory factory : factories) {
|
||||
runner.addCommands(factory.getCommands());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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<Command> getCommands(SpringCli cli);
|
||||
Collection<Command> getCommands();
|
||||
|
||||
}
|
||||
@@ -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<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
|
||||
new VersionCommand(), new CleanCommand(), new TestCommand(),
|
||||
new GrabCommand());
|
||||
|
||||
private Collection<Command> commands;
|
||||
new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand(),
|
||||
new CleanCommand());
|
||||
|
||||
@Override
|
||||
public Collection<Command> getCommands(SpringCli cli) {
|
||||
if (this.commands == null) {
|
||||
synchronized (this) {
|
||||
if (this.commands == null) {
|
||||
this.commands = new ArrayList<Command>(DEFAULT_COMMANDS);
|
||||
RunCommand run = new RunCommand();
|
||||
ShellCommand shell = new ShellCommand(cli);
|
||||
this.commands.add(run);
|
||||
this.commands.add(shell);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.commands;
|
||||
public Collection<Command> getCommands() {
|
||||
return DEFAULT_COMMANDS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String> prompts = new Stack<String>();
|
||||
|
||||
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<String> args = parseArgs(line);
|
||||
if (data.length() > 0) {
|
||||
args.add(data.toString());
|
||||
data.setLength(0);
|
||||
}
|
||||
if (args.size() > 0) {
|
||||
if (isLaunchProcessCommand) {
|
||||
launchProcess(args, systemOut, systemErr);
|
||||
}
|
||||
else {
|
||||
runCommand(args, 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<String> parseArgs(String line) {
|
||||
List<String> parts = new ArrayList<String>();
|
||||
String[] segments = StringUtils.delimitedListToStringArray(line, " ");
|
||||
StringBuffer part = new StringBuffer();
|
||||
boolean swallowWhitespace = false;
|
||||
for (String segment : segments) {
|
||||
if ("".equals(segment)) {
|
||||
continue;
|
||||
}
|
||||
if (segment.startsWith("\"")) {
|
||||
swallowWhitespace = true;
|
||||
part.append(segment.substring(1));
|
||||
}
|
||||
else if (segment.endsWith("\"")) {
|
||||
swallowWhitespace = false;
|
||||
part.append(" ").append(segment.substring(0, segment.length() - 1));
|
||||
parts.add(part.toString());
|
||||
part = new StringBuffer();
|
||||
}
|
||||
else {
|
||||
if (!swallowWhitespace) {
|
||||
parts.add(segment);
|
||||
}
|
||||
else {
|
||||
part.append(" ").append(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (part.length() > 0) {
|
||||
parts.add(part.toString());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
private void launchProcess(List<String> parts, final PrintStream sysout,
|
||||
final PrintStream syserr) {
|
||||
try {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(parts);
|
||||
if (isJava7()) {
|
||||
inheritIO(processBuilder);
|
||||
}
|
||||
processBuilder.environment().putAll(System.getenv());
|
||||
Process process = processBuilder.start();
|
||||
if (!isJava7()) {
|
||||
ProcessGroovyMethods.consumeProcessOutput(process, (OutputStream) sysout,
|
||||
(OutputStream) syserr);
|
||||
}
|
||||
process.waitFor();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJava7() {
|
||||
return PROCESS_BUILDER_INHERIT_IO_METHOD != null;
|
||||
}
|
||||
|
||||
private void inheritIO(ProcessBuilder processBuilder) {
|
||||
ReflectionUtils.invokeMethod(PROCESS_BUILDER_INHERIT_IO_METHOD, processBuilder);
|
||||
}
|
||||
|
||||
private void runCommand(List<String> args, 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<OptionHelp> getOptionsHelp() {
|
||||
List<OptionHelp> help = new ArrayList<OptionHelp>();
|
||||
for (final Command command : this.commandRunner) {
|
||||
if (isHelpShown(command)) {
|
||||
help.add(new OptionHelp() {
|
||||
|
||||
@Override
|
||||
public Set<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String> arguments = new ArrayList<String>(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<String> 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<String> specifiedArguments) {
|
||||
for (String argument : specifiedArguments) {
|
||||
if (help.getOptions().contains(argument)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, Completer> optionCompleters = new HashMap<String, Completer>();
|
||||
private final Map<String, Completer> commandCompleters = new HashMap<String, Completer>();
|
||||
|
||||
private List<Command> commands = new ArrayList<Command>();
|
||||
|
||||
private ConsoleReader console;
|
||||
|
||||
public CommandCompleter(ConsoleReader consoleReader, SpringCli cli) {
|
||||
public CommandCompleter(ConsoleReader consoleReader,
|
||||
ArgumentDelimiter argumentDelimiter, Iterable<Command> commands) {
|
||||
this.console = consoleReader;
|
||||
this.commands.addAll(cli.getCommands());
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (Command command : this.commands) {
|
||||
for (Command command : commands) {
|
||||
this.commands.add(command);
|
||||
names.add(command.getName());
|
||||
List<String> options = new ArrayList<String>();
|
||||
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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<OptionHelp> getOptionsHelp() {
|
||||
return this.command.getOptionsHelp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
List<String> fullArgs = new ArrayList<String>();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Class<?>> NON_FORKED_COMMANDS;
|
||||
static {
|
||||
Set<Class<?>> nonForked = new HashSet<Class<?>>();
|
||||
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<String> prompts = new Stack<String>();
|
||||
|
||||
/**
|
||||
* 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<Command> getCommands() {
|
||||
List<Command> commands = new ArrayList<Command>();
|
||||
ServiceLoader<CommandFactory> 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<String, String> aliases = new HashMap<String, String>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user