initial commit
This commit is contained in:
151
src/main/java/org/springframework/shell/Bootstrap.java
Normal file
151
src/main/java/org/springframework/shell/Bootstrap.java
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2011 VMware, Inc. All rights reserved. -- VMware Confidential
|
||||
*/
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
//import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.roo.shell.AbstractShell;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.roo.shell.Converter;
|
||||
import org.springframework.roo.shell.ExitShellRequest;
|
||||
import org.springframework.roo.shell.Shell;
|
||||
import org.springframework.roo.shell.event.ShellStatus;
|
||||
import org.springframework.roo.support.logging.HandlerUtils;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
//import ch.qos.logback.classic.LoggerContext;
|
||||
//import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
//import ch.qos.logback.core.util.StatusPrinter;
|
||||
|
||||
/**
|
||||
* Main class, needs some cleanup
|
||||
*
|
||||
* @author vnagaraja
|
||||
*/
|
||||
public class Bootstrap {
|
||||
|
||||
private static Bootstrap bootstrap;
|
||||
//TODO using JLineShellComponenet to override and get access to "getSimpleParser" method on the shell - look into autowire...and move back to reference Shell interface.
|
||||
private JLineShellComponent shell;
|
||||
private ConfigurableApplicationContext ctx;
|
||||
private static StopWatch sw = new StopWatch("Spring Sehll");
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
sw.start();
|
||||
SimpleShellCommandLineOptions options = SimpleShellCommandLineOptions.parseCommandLine(args);
|
||||
|
||||
for (Map.Entry<String, String> entry : options.extraSystemProperties.entrySet()) {
|
||||
System.setProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
ExitShellRequest exitShellRequest;
|
||||
try {
|
||||
bootstrap = new Bootstrap(options.applicationContextLocation);
|
||||
exitShellRequest = bootstrap.run(options.executeThenQuit);
|
||||
} catch (RuntimeException t) {
|
||||
throw t;
|
||||
} finally {
|
||||
HandlerUtils.flushAllHandlers(Logger.getLogger(""));
|
||||
}
|
||||
|
||||
System.exit(exitShellRequest.getExitCode());
|
||||
}
|
||||
|
||||
public Bootstrap(String applicationContextLocation) throws IOException {
|
||||
|
||||
//setupLogging();
|
||||
|
||||
Assert.hasText(applicationContextLocation, "Application context location required");
|
||||
|
||||
ctx = new ClassPathXmlApplicationContext(applicationContextLocation);
|
||||
|
||||
|
||||
Map<String, JLineShellComponent> shells = ctx.getBeansOfType(JLineShellComponent.class);
|
||||
|
||||
//Assert.isTrue(shells.size() == 1, "Exactly one Shell was required, but " + shells.size() + " found");
|
||||
//shell = shells.values().iterator().next();
|
||||
|
||||
shell = new JLineShellComponent();
|
||||
|
||||
|
||||
|
||||
Map<String, CommandMarker> commands = ctx.getBeansOfType(CommandMarker.class);
|
||||
|
||||
for (CommandMarker command : commands.values()) {
|
||||
System.out.println("Registgering command " + command);
|
||||
shell.getSimpleParser().add(command);
|
||||
}
|
||||
|
||||
Map<String, Converter> converters = ctx.getBeansOfType(Converter.class);
|
||||
|
||||
for (Converter converter : converters.values()) {
|
||||
System.out.println("Registgering converter " + converter);
|
||||
shell.getSimpleParser().add(converter);
|
||||
}
|
||||
|
||||
|
||||
shell.start();
|
||||
//TODO use listener and latch..
|
||||
while(true) {
|
||||
//System.out.println("shell status = " + shell.getShellStatus().getStatus());
|
||||
if (shell.getShellStatus().getStatus().equals(ShellStatus.Status.USER_INPUT)) {
|
||||
break;
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected ExitShellRequest run(String[] executeThenQuit) {
|
||||
|
||||
ExitShellRequest exitShellRequest;
|
||||
|
||||
if (null != executeThenQuit) {
|
||||
boolean successful = false;
|
||||
exitShellRequest = ExitShellRequest.FATAL_EXIT;
|
||||
|
||||
for(String cmd : executeThenQuit) {
|
||||
successful = shell.executeCommand(cmd);
|
||||
if(!successful)
|
||||
break;
|
||||
}
|
||||
|
||||
//if all commands were successful, set the normal exit status
|
||||
if (successful) {
|
||||
exitShellRequest = ExitShellRequest.NORMAL_EXIT;
|
||||
}
|
||||
} else {
|
||||
shell.promptLoop();
|
||||
exitShellRequest = shell.getExitShellRequest();
|
||||
if (exitShellRequest == null) {
|
||||
// shouldn't really happen, but we'll fallback to this anyway
|
||||
exitShellRequest = ExitShellRequest.NORMAL_EXIT;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.close();
|
||||
sw.stop();
|
||||
if (shell.isDevelopmentMode()) {
|
||||
System.out.println("Total execution time: " + sw.getLastTaskTimeMillis() + " ms");
|
||||
}
|
||||
return exitShellRequest;
|
||||
}
|
||||
|
||||
}
|
||||
19
src/main/java/org/springframework/shell/ExampleService.java
Normal file
19
src/main/java/org/springframework/shell/ExampleService.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
/**
|
||||
* {@link Service} with hard-coded input data.
|
||||
*/
|
||||
@Component
|
||||
public class ExampleService implements Service {
|
||||
|
||||
/**
|
||||
* Reads next record from input
|
||||
*/
|
||||
public String getMessage() {
|
||||
return "Hello world!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jline.Completor;
|
||||
|
||||
import org.springframework.roo.shell.Completion;
|
||||
import org.springframework.roo.shell.Parser;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of JLine's {@link Completor} interface that delegates to a {@link Parser}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.0
|
||||
*/
|
||||
public class JLineCompletorAdapter implements Completor {
|
||||
|
||||
// Fields
|
||||
private final Parser parser;
|
||||
|
||||
public JLineCompletorAdapter(final Parser parser) {
|
||||
Assert.notNull(parser, "Parser required");
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public int complete(final String buffer, final int cursor, final List candidates) {
|
||||
int result;
|
||||
try {
|
||||
JLineLogHandler.cancelRedrawProhibition();
|
||||
List<Completion> completions = new ArrayList<Completion>();
|
||||
result = parser.completeAdvanced(buffer, cursor, completions);
|
||||
for (Completion completion : completions) {
|
||||
candidates.add(new jline.Completion(completion.getValue(), completion.getFormattedValue(), completion.getHeading()));
|
||||
}
|
||||
} finally {
|
||||
JLineLogHandler.prohibitRedraw();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
213
src/main/java/org/springframework/shell/JLineLogHandler.java
Normal file
213
src/main/java/org/springframework/shell/JLineLogHandler.java
Normal file
@@ -0,0 +1,213 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import jline.ANSIBuffer;
|
||||
import jline.ConsoleReader;
|
||||
|
||||
import org.springframework.roo.shell.ShellPromptAccessor;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
import org.springframework.roo.support.util.IOUtils;
|
||||
import org.springframework.roo.support.util.OsUtils;
|
||||
import org.springframework.roo.support.util.StringUtils;
|
||||
|
||||
/**
|
||||
* JDK logging {@link Handler} that emits log messages to a JLine {@link ConsoleReader}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.0
|
||||
*/
|
||||
public class JLineLogHandler extends Handler {
|
||||
|
||||
// Constants
|
||||
private static final boolean BRIGHT_COLORS = Boolean.getBoolean("roo.bright");
|
||||
|
||||
// Fields
|
||||
private ConsoleReader reader;
|
||||
private ShellPromptAccessor shellPromptAccessor;
|
||||
private static ThreadLocal<Boolean> redrawProhibit = new ThreadLocal<Boolean>();
|
||||
private static String lastMessage;
|
||||
private static boolean includeThreadName = false;
|
||||
private boolean ansiSupported;
|
||||
private String userInterfaceThreadName;
|
||||
private static boolean suppressDuplicateMessages = true;
|
||||
|
||||
public JLineLogHandler(final ConsoleReader reader, final ShellPromptAccessor shellPromptAccessor) {
|
||||
Assert.notNull(reader, "Console reader required");
|
||||
Assert.notNull(shellPromptAccessor, "Shell prompt accessor required");
|
||||
this.reader = reader;
|
||||
this.shellPromptAccessor = shellPromptAccessor;
|
||||
this.userInterfaceThreadName = Thread.currentThread().getName();
|
||||
this.ansiSupported = reader.getTerminal().isANSISupported();
|
||||
|
||||
setFormatter(new Formatter() {
|
||||
@Override
|
||||
public String format(final LogRecord record) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (record.getMessage() != null) {
|
||||
sb.append(record.getMessage()).append(StringUtils.LINE_SEPARATOR);
|
||||
}
|
||||
if (record.getThrown() != null) {
|
||||
PrintWriter pw = null;
|
||||
try {
|
||||
StringWriter sw = new StringWriter();
|
||||
pw = new PrintWriter(sw);
|
||||
record.getThrown().printStackTrace(pw);
|
||||
sb.append(sw.toString());
|
||||
} catch (Exception ex) {
|
||||
} finally {
|
||||
IOUtils.closeQuietly(pw);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {}
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {}
|
||||
|
||||
public static void prohibitRedraw() {
|
||||
redrawProhibit.set(true);
|
||||
}
|
||||
|
||||
public static void cancelRedrawProhibition() {
|
||||
redrawProhibit.remove();
|
||||
}
|
||||
|
||||
public static void setIncludeThreadName(final boolean include) {
|
||||
includeThreadName = include;
|
||||
}
|
||||
|
||||
public static void resetMessageTracking() {
|
||||
lastMessage = null; // see ROO-251
|
||||
}
|
||||
|
||||
public static boolean isSuppressDuplicateMessages() {
|
||||
return suppressDuplicateMessages;
|
||||
}
|
||||
|
||||
public static void setSuppressDuplicateMessages(final boolean suppressDuplicateMessages) {
|
||||
JLineLogHandler.suppressDuplicateMessages = suppressDuplicateMessages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(final LogRecord record) {
|
||||
try {
|
||||
// Avoid repeating the same message that displayed immediately before the current message (ROO-30, ROO-1873)
|
||||
String toDisplay = toDisplay(record);
|
||||
if (toDisplay.equals(lastMessage) && suppressDuplicateMessages) {
|
||||
return;
|
||||
}
|
||||
lastMessage = toDisplay;
|
||||
|
||||
StringBuffer buffer = reader.getCursorBuffer().getBuffer();
|
||||
int cursor = reader.getCursorBuffer().cursor;
|
||||
if (reader.getCursorBuffer().length() > 0) {
|
||||
// The user has semi-typed something, so put a new line in so the debug message is separated
|
||||
reader.printNewline();
|
||||
|
||||
// We need to cancel whatever they typed (it's reset later on), so the line appears empty
|
||||
reader.getCursorBuffer().setBuffer(new StringBuffer());
|
||||
reader.getCursorBuffer().cursor = 0;
|
||||
}
|
||||
|
||||
// This ensures nothing is ever displayed when redrawing the line
|
||||
reader.setDefaultPrompt("");
|
||||
reader.redrawLine();
|
||||
|
||||
// Now restore the line formatting settings back to their original
|
||||
reader.setDefaultPrompt(shellPromptAccessor.getShellPrompt());
|
||||
|
||||
reader.getCursorBuffer().setBuffer(buffer);
|
||||
reader.getCursorBuffer().cursor = cursor;
|
||||
|
||||
reader.printString(toDisplay);
|
||||
|
||||
Boolean prohibitingRedraw = redrawProhibit.get();
|
||||
if (prohibitingRedraw == null) {
|
||||
reader.redrawLine();
|
||||
}
|
||||
|
||||
reader.flushConsole();
|
||||
} catch (Exception e) {
|
||||
reportError("Could not publish log message", e, Level.SEVERE.intValue());
|
||||
}
|
||||
}
|
||||
|
||||
private String toDisplay(final LogRecord event) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String threadName;
|
||||
String eventString;
|
||||
if (includeThreadName && !userInterfaceThreadName.equals(Thread.currentThread().getName()) && !"".equals(Thread.currentThread().getName())) {
|
||||
threadName = "[" + Thread.currentThread().getName() + "]";
|
||||
|
||||
// Build an event string that will indent nicely given the left hand side now contains a thread name
|
||||
StringBuilder lineSeparatorAndIndentingString = new StringBuilder();
|
||||
for (int i = 0; i <= threadName.length(); i++) {
|
||||
lineSeparatorAndIndentingString.append(" ");
|
||||
}
|
||||
|
||||
eventString = " " + getFormatter().format(event).replace(StringUtils.LINE_SEPARATOR, StringUtils.LINE_SEPARATOR + lineSeparatorAndIndentingString.toString());
|
||||
if (eventString.endsWith(lineSeparatorAndIndentingString.toString())) {
|
||||
eventString = eventString.substring(0, eventString.length() - lineSeparatorAndIndentingString.length());
|
||||
}
|
||||
} else {
|
||||
threadName = "";
|
||||
eventString = getFormatter().format(event);
|
||||
}
|
||||
|
||||
if (ansiSupported) {
|
||||
if (event.getLevel().intValue() >= Level.SEVERE.intValue()) {
|
||||
sb.append(getANSIBuffer().reverse(threadName).red(eventString));
|
||||
} else if (event.getLevel().intValue() >= Level.WARNING.intValue()) {
|
||||
sb.append(getANSIBuffer().reverse(threadName).magenta(eventString));
|
||||
} else if (event.getLevel().intValue() >= Level.INFO.intValue()) {
|
||||
sb.append(getANSIBuffer().reverse(threadName).green(eventString));
|
||||
} else {
|
||||
sb.append(getANSIBuffer().reverse(threadName).append(eventString));
|
||||
}
|
||||
} else {
|
||||
sb.append(threadName).append(eventString);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes text brighter if requested through system property 'roo.bright' and
|
||||
* works around issue on Windows in using reverse() in combination with the
|
||||
* Jansi lib, which leaves its 'negative' flag set unless reset explicitly.
|
||||
*
|
||||
* @return new patched ANSIBuffer
|
||||
*/
|
||||
static ANSIBuffer getANSIBuffer() {
|
||||
final char esc = (char) 27;
|
||||
return new ANSIBuffer() {
|
||||
@Override
|
||||
public ANSIBuffer reverse(final String str) {
|
||||
if (OsUtils.isWindows()) {
|
||||
return super.reverse(str).append(ANSICodes.attrib(esc));
|
||||
}
|
||||
return super.reverse(str);
|
||||
};
|
||||
@Override
|
||||
public ANSIBuffer attrib(final String str, final int code) {
|
||||
if (BRIGHT_COLORS && 30 <= code && code <= 37) {
|
||||
// This is a color code: add a 'bright' code
|
||||
return append(esc + "[" + code + ";1m").append(str).append(ANSICodes.attrib(0));
|
||||
}
|
||||
return super.attrib(str, code);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
498
src/main/java/org/springframework/shell/JLineShell.java
Normal file
498
src/main/java/org/springframework/shell/JLineShell.java
Normal file
@@ -0,0 +1,498 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import jline.ANSIBuffer;
|
||||
import jline.ANSIBuffer.ANSICodes;
|
||||
import jline.ConsoleReader;
|
||||
import jline.WindowsTerminal;
|
||||
|
||||
import org.springframework.roo.shell.AbstractShell;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.roo.shell.ExitShellRequest;
|
||||
import org.springframework.roo.shell.Shell;
|
||||
import org.springframework.roo.shell.event.ShellStatus;
|
||||
import org.springframework.roo.shell.event.ShellStatus.Status;
|
||||
import org.springframework.roo.shell.event.ShellStatusListener;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
import org.springframework.roo.support.util.ClassUtils;
|
||||
import org.springframework.roo.support.util.FileCopyUtils;
|
||||
import org.springframework.roo.support.util.IOUtils;
|
||||
import org.springframework.roo.support.util.OsUtils;
|
||||
import org.springframework.roo.support.util.StringUtils;
|
||||
import org.springframework.shell.commands.HintCommands;
|
||||
|
||||
|
||||
/**
|
||||
* Uses the feature-rich <a href="http://sourceforge.net/projects/jline/">JLine</a> library to provide an interactive shell.
|
||||
*
|
||||
* <p>
|
||||
* Due to Windows' lack of color ANSI services out-of-the-box, this implementation automatically detects the classpath
|
||||
* presence of <a href="http://jansi.fusesource.org/">Jansi</a> and uses it if present. This library is not necessary
|
||||
* for *nix machines, which support colour ANSI without any special effort. This implementation has been written to
|
||||
* use reflection in order to avoid hard dependencies on Jansi.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.0
|
||||
*/
|
||||
public abstract class JLineShell extends AbstractShell implements CommandMarker, Shell, Runnable {
|
||||
|
||||
// Constants
|
||||
private static final String ANSI_CONSOLE_CLASSNAME = "org.fusesource.jansi.AnsiConsole";
|
||||
private static final boolean JANSI_AVAILABLE = ClassUtils.isPresent(ANSI_CONSOLE_CLASSNAME, JLineShell.class.getClassLoader());
|
||||
private static final boolean APPLE_TERMINAL = Boolean.getBoolean("is.apple.terminal");
|
||||
private static final char ESCAPE = 27;
|
||||
private static final String BEL = "\007";
|
||||
|
||||
//TODO make configurable
|
||||
private static final String COMMAND_LINE_PROMPT = "spring>";
|
||||
|
||||
// Fields
|
||||
private ConsoleReader reader;
|
||||
private boolean developmentMode = false;
|
||||
private FileWriter fileLog;
|
||||
private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
protected ShellStatusListener statusListener; // ROO-836
|
||||
/** key: slot name, value: flashInfo instance */
|
||||
private final Map<String, FlashInfo> flashInfoMap = new HashMap<String, FlashInfo>();
|
||||
/** key: row number, value: eraseLineFromPosition */
|
||||
private final Map<Integer, Integer> rowErasureMap = new HashMap<Integer, Integer>();
|
||||
private boolean shutdownHookFired = false; // ROO-1599
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
if (JANSI_AVAILABLE && OsUtils.isWindows()) {
|
||||
try {
|
||||
reader = createAnsiWindowsReader();
|
||||
} catch (Exception e) {
|
||||
// Try again using default ConsoleReader constructor
|
||||
logger.warning("Can't initialize jansi AnsiConsole, falling back to default: " + e);
|
||||
}
|
||||
}
|
||||
if (reader == null) {
|
||||
reader = new ConsoleReader();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new IllegalStateException("Cannot start console class", ioe);
|
||||
}
|
||||
|
||||
setPromptPath(null);
|
||||
|
||||
JLineLogHandler handler = new JLineLogHandler(reader, this);
|
||||
JLineLogHandler.prohibitRedraw(); // Affects this thread only
|
||||
Logger mainLogger = Logger.getLogger("");
|
||||
removeHandlers(mainLogger);
|
||||
mainLogger.addHandler(handler);
|
||||
|
||||
reader.addCompletor(new JLineCompletorAdapter(getParser()));
|
||||
|
||||
reader.setBellEnabled(true);
|
||||
if (Boolean.getBoolean("jline.nobell")) {
|
||||
reader.setBellEnabled(false);
|
||||
}
|
||||
|
||||
// reader.setDebug(new PrintWriter(new FileWriter("writer.debug", true)));
|
||||
|
||||
openFileLogIfPossible();
|
||||
|
||||
// Try to build previous command history from the project's log
|
||||
try {
|
||||
String logFileContents = FileCopyUtils.copyToString(new File("log.roo"));
|
||||
String[] logEntries = logFileContents.split(StringUtils.LINE_SEPARATOR);
|
||||
// LIFO
|
||||
for (String logEntry : logEntries) {
|
||||
if (!logEntry.startsWith("//")) {
|
||||
reader.getHistory().addToHistory(logEntry);
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
|
||||
flashMessageRenderer();
|
||||
|
||||
logger.info(version(null));
|
||||
|
||||
flash(Level.FINE, "Spring Roo " + versionInfo(), Shell.WINDOW_TITLE_SLOT);
|
||||
|
||||
|
||||
//TODO make this configurable
|
||||
logger.info("Welcome to Spring Shell. For assistance press " + completionKeys + " or type \"hint\" then hit ENTER.");
|
||||
|
||||
String startupNotifications = getStartupNotifications();
|
||||
if (StringUtils.hasText(startupNotifications)) {
|
||||
logger.info(startupNotifications);
|
||||
}
|
||||
|
||||
setShellStatus(Status.STARTED);
|
||||
|
||||
// Monitor CTRL+C initiated shutdowns (ROO-1599)
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
public void run() {
|
||||
shutdownHookFired = true;
|
||||
// We don't need to closeShell(), as the shutdown hook in o.s.r.bootstrap.Main calls stop() which calls JLineShellComponent.deactivate() and that calls closeShell()
|
||||
}
|
||||
}, "Spring Roo JLine Shutdown Hook"));
|
||||
|
||||
// Handle any "execute-then-quit" operation
|
||||
String rooArgs = System.getProperty("roo.args");
|
||||
if (rooArgs != null && !"".equals(rooArgs)) {
|
||||
setShellStatus(Status.USER_INPUT);
|
||||
boolean success = executeCommand(rooArgs);
|
||||
if (exitShellRequest == null) {
|
||||
// The command itself did not specify an exit shell code, so we'll fall back to something sensible here
|
||||
executeCommand("quit"); // ROO-839
|
||||
exitShellRequest = success ? ExitShellRequest.NORMAL_EXIT : ExitShellRequest.FATAL_EXIT;
|
||||
}
|
||||
setShellStatus(Status.SHUTTING_DOWN);
|
||||
} else {
|
||||
// Normal RPEL processing
|
||||
promptLoop();
|
||||
}
|
||||
}
|
||||
|
||||
public String getStartupNotifications() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void removeHandlers(final Logger l) {
|
||||
Handler[] handlers = l.getHandlers();
|
||||
if (handlers != null && handlers.length > 0) {
|
||||
for (Handler h : handlers) {
|
||||
l.removeHandler(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPromptPath(final String path) {
|
||||
setPromptPath(path, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPromptPath(final String path, final boolean overrideStyle) {
|
||||
if (reader.getTerminal().isANSISupported()) {
|
||||
ANSIBuffer ansi = JLineLogHandler.getANSIBuffer();
|
||||
if (path == null || "".equals(path)) {
|
||||
shellPrompt = ansi.yellow(COMMAND_LINE_PROMPT).toString();
|
||||
} else {
|
||||
if (overrideStyle) {
|
||||
ansi.append(path);
|
||||
} else {
|
||||
ansi.cyan(path);
|
||||
}
|
||||
shellPrompt = ansi.yellow(" " + COMMAND_LINE_PROMPT).toString();
|
||||
}
|
||||
} else {
|
||||
// The superclass will do for this non-ANSI terminal
|
||||
super.setPromptPath(path);
|
||||
}
|
||||
|
||||
// The shellPrompt is now correct; let's ensure it now gets used
|
||||
reader.setDefaultPrompt(JLineShell.shellPrompt);
|
||||
}
|
||||
|
||||
private ConsoleReader createAnsiWindowsReader() throws Exception {
|
||||
// Get decorated OutputStream that parses ANSI-codes
|
||||
final PrintStream ansiOut = (PrintStream) ClassUtils.forName(ANSI_CONSOLE_CLASSNAME, JLineShell.class.getClassLoader()).getMethod("out").invoke(null);
|
||||
WindowsTerminal ansiTerminal = new WindowsTerminal() {
|
||||
@Override
|
||||
public boolean isANSISupported() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
ansiTerminal.initializeTerminal();
|
||||
// Make sure to reset the original shell's colors on shutdown by closing the stream
|
||||
statusListener = new ShellStatusListener() {
|
||||
public void onShellStatusChange(final ShellStatus oldStatus, final ShellStatus newStatus) {
|
||||
if (newStatus.getStatus().equals(Status.SHUTTING_DOWN)) {
|
||||
ansiOut.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
addShellStatusListener(statusListener);
|
||||
|
||||
return new ConsoleReader(new FileInputStream(FileDescriptor.in), new PrintWriter(new OutputStreamWriter(ansiOut,
|
||||
// Default to Cp850 encoding for Windows console output (ROO-439)
|
||||
System.getProperty("jline.WindowsTerminal.output.encoding", "Cp850"))), null, ansiTerminal);
|
||||
}
|
||||
|
||||
private void flashMessageRenderer() {
|
||||
if (!reader.getTerminal().isANSISupported()) {
|
||||
return;
|
||||
}
|
||||
// Setup a thread to ensure flash messages are displayed and cleared correctly
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
while (!shellStatus.getStatus().equals(Status.SHUTTING_DOWN) && !shutdownHookFired) {
|
||||
synchronized (flashInfoMap) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
Set<String> toRemove = new HashSet<String>();
|
||||
for (String slot : flashInfoMap.keySet()) {
|
||||
FlashInfo flashInfo = flashInfoMap.get(slot);
|
||||
|
||||
if (flashInfo.flashMessageUntil < now) {
|
||||
// Message has expired, so clear it
|
||||
toRemove.add(slot);
|
||||
doAnsiFlash(flashInfo.rowNumber, Level.ALL, "");
|
||||
} else {
|
||||
// The expiration time for this message has not been reached, so preserve it
|
||||
doAnsiFlash(flashInfo.rowNumber, flashInfo.flashLevel, flashInfo.flashMessage);
|
||||
}
|
||||
}
|
||||
for (String slot : toRemove) {
|
||||
flashInfoMap.remove(slot);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "Spring Roo JLine Flash Message Manager");
|
||||
t.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flash(final Level level, final String message, final String slot) {
|
||||
Assert.notNull(level, "Level is required for a flash message");
|
||||
Assert.notNull(message, "Message is required for a flash message");
|
||||
Assert.hasText(slot, "Slot name must be specified for a flash message");
|
||||
|
||||
if (Shell.WINDOW_TITLE_SLOT.equals(slot)) {
|
||||
if (reader != null && reader.getTerminal().isANSISupported()) {
|
||||
// We can probably update the window title, as requested
|
||||
if (StringUtils.isBlank(message)) {
|
||||
System.out.println("No text");
|
||||
}
|
||||
|
||||
ANSIBuffer buff = JLineLogHandler.getANSIBuffer();
|
||||
buff.append(ESCAPE + "]0;").append(message).append(BEL);
|
||||
String stg = buff.toString();
|
||||
try {
|
||||
reader.printString(stg);
|
||||
reader.flushConsole();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if ((reader != null && !reader.getTerminal().isANSISupported())) {
|
||||
super.flash(level, message, slot);
|
||||
return;
|
||||
}
|
||||
synchronized (flashInfoMap) {
|
||||
FlashInfo flashInfo = flashInfoMap.get(slot);
|
||||
|
||||
if ("".equals(message)) {
|
||||
// Request to clear the message, but give the user some time to read it first
|
||||
if (flashInfo == null) {
|
||||
// We didn't have a record of displaying it in the first place, so just quit
|
||||
return;
|
||||
}
|
||||
flashInfo.flashMessageUntil = System.currentTimeMillis() + 1500;
|
||||
} else {
|
||||
// Display this message displayed until further notice
|
||||
if (flashInfo == null) {
|
||||
// Find a row for this new slot; we basically take the first line number we discover
|
||||
flashInfo = new FlashInfo();
|
||||
flashInfo.rowNumber = Integer.MAX_VALUE;
|
||||
outer: for (int i = 1; i < Integer.MAX_VALUE; i++) {
|
||||
for (FlashInfo existingFlashInfo : flashInfoMap.values()) {
|
||||
if (existingFlashInfo.rowNumber == i) {
|
||||
// Veto, let's try the new candidate row number
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
// If we got to here, nobody owns this row number, so use it
|
||||
flashInfo.rowNumber = i;
|
||||
break outer;
|
||||
}
|
||||
|
||||
// Store it
|
||||
flashInfoMap.put(slot, flashInfo);
|
||||
}
|
||||
// Populate the instance with the latest data
|
||||
flashInfo.flashMessageUntil = Long.MAX_VALUE;
|
||||
flashInfo.flashLevel = level;
|
||||
flashInfo.flashMessage = message;
|
||||
|
||||
// Display right now
|
||||
doAnsiFlash(flashInfo.rowNumber, flashInfo.flashLevel, flashInfo.flashMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Externally synchronized via the two calling methods having a mutex on flashInfoMap
|
||||
private void doAnsiFlash(final int row, final Level level, final String message) {
|
||||
ANSIBuffer buff = JLineLogHandler.getANSIBuffer();
|
||||
if (APPLE_TERMINAL) {
|
||||
buff.append(ESCAPE + "7");
|
||||
} else {
|
||||
buff.append(ANSICodes.save());
|
||||
}
|
||||
|
||||
// Figure out the longest line we're presently displaying (or were) and erase the line from that position
|
||||
int mostFurtherLeftColNumber = Integer.MAX_VALUE;
|
||||
for (Integer candidate : rowErasureMap.values()) {
|
||||
if (candidate < mostFurtherLeftColNumber) {
|
||||
mostFurtherLeftColNumber = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (mostFurtherLeftColNumber == Integer.MAX_VALUE) {
|
||||
// There is nothing to erase
|
||||
} else {
|
||||
buff.append(ANSICodes.gotoxy(row, mostFurtherLeftColNumber));
|
||||
buff.append(ANSICodes.clreol()); // Clear what was present on the line
|
||||
}
|
||||
|
||||
if (("".equals(message))) {
|
||||
// They want the line blank; we've already achieved this if needed via the erasing above
|
||||
// Just need to record we no longer care about this line the next time doAnsiFlash is invoked
|
||||
rowErasureMap.remove(row);
|
||||
} else {
|
||||
if (shutdownHookFired) {
|
||||
return; // ROO-1599
|
||||
}
|
||||
// They want some message displayed
|
||||
int startFrom = reader.getTermwidth() - message.length() + 1;
|
||||
if (startFrom < 1) {
|
||||
startFrom = 1;
|
||||
}
|
||||
buff.append(ANSICodes.gotoxy(row, startFrom));
|
||||
buff.reverse(message);
|
||||
// Record we want to erase from this positioning next time (so we clean up after ourselves)
|
||||
rowErasureMap.put(row, startFrom);
|
||||
}
|
||||
if (APPLE_TERMINAL) {
|
||||
buff.append(ESCAPE + "8");
|
||||
} else {
|
||||
buff.append(ANSICodes.restore());
|
||||
}
|
||||
|
||||
String stg = buff.toString();
|
||||
try {
|
||||
reader.printString(stg);
|
||||
reader.flushConsole();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
public void promptLoop() {
|
||||
setShellStatus(Status.USER_INPUT);
|
||||
String line;
|
||||
|
||||
try {
|
||||
while (exitShellRequest == null && ((line = reader.readLine()) != null)) {
|
||||
JLineLogHandler.resetMessageTracking();
|
||||
setShellStatus(Status.USER_INPUT);
|
||||
|
||||
if ("".equals(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
executeCommand(line);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new IllegalStateException("Shell line reading failure", ioe);
|
||||
}
|
||||
|
||||
setShellStatus(Status.SHUTTING_DOWN);
|
||||
}
|
||||
|
||||
public void setDevelopmentMode(final boolean developmentMode) {
|
||||
JLineLogHandler.setIncludeThreadName(developmentMode);
|
||||
JLineLogHandler.setSuppressDuplicateMessages(!developmentMode); // We want to see duplicate messages during development time (ROO-1873)
|
||||
this.developmentMode = developmentMode;
|
||||
}
|
||||
|
||||
public boolean isDevelopmentMode() {
|
||||
return this.developmentMode;
|
||||
}
|
||||
|
||||
private void openFileLogIfPossible() {
|
||||
try {
|
||||
fileLog = new FileWriter("log.roo", true);
|
||||
// First write, so let's record the date and time of the first user command
|
||||
fileLog.write("// Spring Roo " + versionInfo() + " log opened at " + df.format(new Date()) + "\n");
|
||||
fileLog.flush();
|
||||
} catch (IOException ignoreIt) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void logCommandToOutput(final String processedLine) {
|
||||
if (fileLog == null) {
|
||||
openFileLogIfPossible();
|
||||
if (fileLog == null) {
|
||||
// Still failing, so give up
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
fileLog.write(processedLine + "\n"); // Unix line endings only from Roo
|
||||
fileLog.flush(); // So tail -f will show it's working
|
||||
if (getExitShellRequest() != null) {
|
||||
// Shutting down, so close our file (we can always reopen it later if needed)
|
||||
fileLog.write("// Spring Roo " + versionInfo() + " log closed at " + df.format(new Date()) + "\n");
|
||||
IOUtils.closeQuietly(fileLog);
|
||||
fileLog = null;
|
||||
}
|
||||
} catch (IOException ignoreIt) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the "roo.home" from the system property, falling back to the current working directory if missing.
|
||||
*
|
||||
* @return the 'roo.home' system property
|
||||
*/
|
||||
@Override
|
||||
protected String getHomeAsString() {
|
||||
String rooHome = System.getProperty("roo.home");
|
||||
if (rooHome == null) {
|
||||
try {
|
||||
rooHome = new File(".").getCanonicalPath();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return rooHome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called by a subclass before deactivating the shell.
|
||||
*/
|
||||
protected void closeShell() {
|
||||
// Notify we're closing down (normally our status is already shutting_down, but if it was a CTRL+C via the o.s.r.bootstrap.Main hook)
|
||||
setShellStatus(Status.SHUTTING_DOWN);
|
||||
if (statusListener != null) {
|
||||
removeShellStatusListener(statusListener);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FlashInfo {
|
||||
String flashMessage;
|
||||
long flashMessageUntil;
|
||||
Level flashLevel;
|
||||
int rowNumber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import static org.springframework.roo.support.util.StringUtils.LINE_SEPARATOR;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.roo.shell.CliCommand;
|
||||
import org.springframework.roo.shell.CliOption;
|
||||
import org.springframework.roo.shell.ExecutionStrategy;
|
||||
import org.springframework.roo.shell.Parser;
|
||||
import org.springframework.roo.shell.SimpleParser;
|
||||
|
||||
/**
|
||||
* Launcher for {@link JLineShell}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.1
|
||||
*/
|
||||
public class JLineShellComponent extends JLineShell implements Lifecycle {
|
||||
|
||||
private volatile boolean running = false;
|
||||
|
||||
// Fields
|
||||
private ExecutionStrategy executionStrategy = new SimpleExecutionStrategy(); //ProcessManagerHostedExecutionStrategy is not what i think we need outside of Roo.
|
||||
private SimpleParser parser = new SimpleParser();
|
||||
|
||||
// Dont' need this, used to get twitter status.
|
||||
//@Reference private UrlInputStreamService urlInputStreamService;
|
||||
//
|
||||
|
||||
|
||||
public SimpleParser getSimpleParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
Thread thread = new Thread(this, "Spring Roo JLine Shell");
|
||||
thread.start();
|
||||
running=true;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
closeShell();
|
||||
running=false;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<URL> findResources(final String path) {
|
||||
// For an OSGi bundle search, we add the root prefix to the given path
|
||||
throw new UnsupportedOperationException("TODO: need to use standard classpath search");
|
||||
//return OSGiUtils.findEntriesByPath(context.getBundleContext(), OSGiUtils.ROOT_PATH + path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExecutionStrategy getExecutionStrategy() {
|
||||
return executionStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parser getParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStartupNotifications() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@CliCommand(value = { "version" }, help = "Displays shell version")
|
||||
public String version(@CliOption(key = "", help = "Special version flags") final String extra) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(" _____ _ ").append(LINE_SEPARATOR);
|
||||
sb.append("/ ___| (_)").append(LINE_SEPARATOR);
|
||||
sb.append("\\ `--, _ __ _ __ _ _ __ __ _ ").append(LINE_SEPARATOR);
|
||||
sb.append(" `--. \\ '_ \\| '__| | '_ \\ / _` |").append(LINE_SEPARATOR);
|
||||
sb.append("/\\__/ / |_) | | | | | | | (_| |").append(LINE_SEPARATOR);
|
||||
sb.append("\\____/| .__/|_| |_|_| |_|\\__, |").append(LINE_SEPARATOR);
|
||||
sb.append(" | | __/ |").append(LINE_SEPARATOR);
|
||||
sb.append(" |_| |___/ ").append(" ").append(versionInfo()).append(LINE_SEPARATOR);
|
||||
sb.append(LINE_SEPARATOR);
|
||||
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
7
src/main/java/org/springframework/shell/Service.java
Normal file
7
src/main/java/org/springframework/shell/Service.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
public interface Service {
|
||||
|
||||
String getMessage();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import org.springframework.roo.shell.ExecutionStrategy;
|
||||
import org.springframework.roo.shell.ParseResult;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
import org.springframework.roo.support.util.ReflectionUtils;
|
||||
|
||||
public class SimpleExecutionStrategy implements ExecutionStrategy {
|
||||
|
||||
private final Class<?> mutex = SimpleExecutionStrategy.class;
|
||||
|
||||
public Object execute(ParseResult parseResult) throws RuntimeException {
|
||||
Assert.notNull(parseResult, "Parse result required");
|
||||
synchronized (mutex) {
|
||||
Assert.isTrue(isReadyForCommands(), "ProcessManagerHostedExecutionStrategy not yet ready for commands");
|
||||
return ReflectionUtils.invokeMethod(parseResult.getMethod(), parseResult.getInstance(), parseResult.getArguments());
|
||||
/*
|
||||
Assert.isTrue(isReadyForCommands(), "ProcessManagerHostedExecutionStrategy not yet ready for commands");
|
||||
return processManager.execute(new CommandCallback<Object>() {
|
||||
public Object callback() {
|
||||
return ReflectionUtils.invokeMethod(parseResult.getMethod(), parseResult.getInstance(), parseResult.getArguments());
|
||||
}
|
||||
});*/
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReadyForCommands() {
|
||||
// TODO Auto-generated method stub
|
||||
return true;
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2011 VMware, Inc. All rights reserved. -- VMware Confidential
|
||||
*/
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
/**
|
||||
* Not really used much, but keeping for future use
|
||||
*
|
||||
* @author vnagaraja
|
||||
*/
|
||||
public class SimpleShellCommandLineOptions {
|
||||
|
||||
public static final String DEFAULT_APP_CTX = "classpath*:/META-INF/spring/app-context.xml";
|
||||
String applicationContextLocation = DEFAULT_APP_CTX;
|
||||
String[] executeThenQuit = null;
|
||||
Map<String, String> extraSystemProperties = new HashMap<String, String>();
|
||||
|
||||
public static SimpleShellCommandLineOptions parseCommandLine(String[] args) throws IOException {
|
||||
SimpleShellCommandLineOptions options = new SimpleShellCommandLineOptions();
|
||||
List<String> commands = new ArrayList<String>();
|
||||
int i = 0;
|
||||
while (i < args.length) {
|
||||
String arg = args[i++];
|
||||
if (arg.equals("-environment")) {
|
||||
String environment = args[i++];
|
||||
options.extraSystemProperties.put("napa.application.profile", environment);
|
||||
} else if (arg.equals("-ctx")) {
|
||||
options.applicationContextLocation = args[i++];
|
||||
} else if (arg.equals("-cmdfile")) {
|
||||
File f = new File(args[i++]);
|
||||
commands.addAll(FileUtils.readLines(f));
|
||||
} else {
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (; i < args.length; i++) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(args[i]);
|
||||
}
|
||||
|
||||
if(sb.length()>0) {
|
||||
String[] cmdLineCommands = sb.toString().split(";");
|
||||
for(String s : cmdLineCommands) {
|
||||
//add any command line commands after the commands loaded from the file
|
||||
commands.add(s.trim());
|
||||
}
|
||||
}
|
||||
|
||||
if(commands.size()>0)
|
||||
options.executeThenQuit = commands.toArray(new String[commands.size()]);
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import org.springframework.roo.shell.CliCommand;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.roo.shell.ExitShellRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class EssentialCommands implements CommandMarker {
|
||||
|
||||
@CliCommand(value={"exit", "quit"}, help="Exits the shell")
|
||||
public ExitShellRequest quit() {
|
||||
return ExitShellRequest.NORMAL_EXIT;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import org.springframework.roo.shell.CliAvailabilityIndicator;
|
||||
import org.springframework.roo.shell.CliCommand;
|
||||
import org.springframework.roo.shell.CliOption;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class HelloWorldCommands implements CommandMarker {
|
||||
|
||||
|
||||
@CliAvailabilityIndicator({"hw help"})
|
||||
public boolean isCommandAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@CliCommand(value = "hw echo", help = "Print a hello world message")
|
||||
public void config(
|
||||
@CliOption(key = { "message" }, mandatory = true, help = "The hello world message") final String message) {
|
||||
System.out.println("Hello world " + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.roo.shell.CliCommand;
|
||||
import org.springframework.roo.shell.CliOption;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class HintCommands implements CommandMarker {
|
||||
|
||||
// Fields
|
||||
@Autowired private HintOperations hintOperations;
|
||||
|
||||
@CliCommand(value = "hint", help = "Provides step-by-step hints and context-sensitive guidance")
|
||||
public String hint(
|
||||
@CliOption(key = { "topic", "" }, mandatory = false, unspecifiedDefaultValue = "", optionContext = "disable-string-converter,topics", help = "The topic for which advice should be provided") final String topic) {
|
||||
|
||||
return hintOperations.hint(topic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.roo.shell.Completion;
|
||||
import org.springframework.roo.shell.Converter;
|
||||
import org.springframework.roo.shell.MethodTarget;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* {@link Converter} for {@link String} that understands the "topics" option context.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.1
|
||||
*/
|
||||
@Component
|
||||
public class HintConverter implements Converter<String> {
|
||||
|
||||
// Fields
|
||||
@Autowired private HintOperations hintOperations;
|
||||
|
||||
public String convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
|
||||
for (String currentTopic : hintOperations.getCurrentTopics()) {
|
||||
completions.add(new Completion(currentTopic));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supports(final Class<?> requiredType, final String optionContext) {
|
||||
return String.class.isAssignableFrom(requiredType) && optionContext.contains("topics");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import java.util.SortedSet;
|
||||
|
||||
public interface HintOperations {
|
||||
|
||||
String hint(String topic);
|
||||
|
||||
SortedSet<String> getCurrentTopics();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.springframework.roo.shell.AbstractShell;
|
||||
import org.springframework.roo.support.util.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ResourceBundleHintOperations implements HintOperations {
|
||||
|
||||
private static ResourceBundle bundle = ResourceBundle.getBundle(HintCommands.class.getName());
|
||||
|
||||
public String hint(String topic) {
|
||||
if (StringUtils.isBlank(topic)) {
|
||||
topic = determineTopic();
|
||||
}
|
||||
try {
|
||||
String message = bundle.getString(topic);
|
||||
return message.replace("\r", StringUtils.LINE_SEPARATOR).replace("${completion_key}", AbstractShell.completionKeys);
|
||||
} catch (MissingResourceException exception) {
|
||||
return "Cannot find topic '" + topic + "'";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SortedSet<String> getCurrentTopics() {
|
||||
SortedSet<String> result = new TreeSet<String>();
|
||||
String topic = determineTopic();
|
||||
if ("general".equals(topic)) {
|
||||
for (Enumeration<String> keys = bundle.getKeys(); keys.hasMoreElements();) {
|
||||
result.add(keys.nextElement());
|
||||
}
|
||||
// result.addAll(bundle.keySet()); ResourceBundle.keySet() method in JDK 6+
|
||||
} else {
|
||||
result.add(topic);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String determineTopic() {
|
||||
return "start";
|
||||
//return "general";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.shell.converters;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.roo.shell.Shell;
|
||||
import org.springframework.roo.shell.converters.FileConverter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
//@Component
|
||||
public class SimpleFileConverter extends FileConverter {
|
||||
@Autowired private Shell shell;
|
||||
|
||||
@Override
|
||||
protected File getWorkingDirectory() {
|
||||
return shell.getHome();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user