/*
* Copyright 2012-2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.jboss.forge.shell;
import static org.mvel2.DataConversion.addConversionHandler;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole;
import org.jboss.forge.ForgeEnvironment;
import org.jboss.forge.project.Project;
import org.jboss.forge.project.dependencies.Dependency;
import org.jboss.forge.project.facets.JavaSourceFacet;
import org.jboss.forge.project.services.ResourceFactory;
import org.jboss.forge.resources.DirectoryResource;
import org.jboss.forge.resources.Resource;
import org.jboss.forge.resources.java.JavaResource;
import org.jboss.forge.shell.buffers.ConsoleInputSession;
import org.jboss.forge.shell.buffers.JLineScreenBuffer;
import org.jboss.forge.shell.command.CommandMetadata;
import org.jboss.forge.shell.command.PluginMetadata;
import org.jboss.forge.shell.command.PromptTypeConverter;
import org.jboss.forge.shell.command.convert.BooleanConverter;
import org.jboss.forge.shell.command.convert.DependencyIdConverter;
import org.jboss.forge.shell.command.convert.FileConverter;
import org.jboss.forge.shell.command.convert.URLConverter;
import org.jboss.forge.shell.command.fshparser.FSHRuntime;
import org.jboss.forge.shell.completer.CompletedCommandHolder;
import org.jboss.forge.shell.completer.OptionAwareCompletionHandler;
import org.jboss.forge.shell.completer.PluginCommandCompleter;
import org.jboss.forge.shell.console.jline.Terminal;
import org.jboss.forge.shell.console.jline.TerminalFactory;
import org.jboss.forge.shell.console.jline.console.ConsoleReader;
import org.jboss.forge.shell.console.jline.console.completer.AggregateCompleter;
import org.jboss.forge.shell.console.jline.console.completer.Completer;
import org.jboss.forge.shell.console.jline.console.history.MemoryHistory;
import org.jboss.forge.shell.events.AcceptUserInput;
import org.jboss.forge.shell.events.PreShutdown;
import org.jboss.forge.shell.events.Shutdown;
import org.jboss.forge.shell.events.Startup;
import org.jboss.forge.shell.exceptions.AbortedException;
import org.jboss.forge.shell.exceptions.CommandExecutionException;
import org.jboss.forge.shell.exceptions.CommandParserException;
import org.jboss.forge.shell.exceptions.EndOfStreamException;
import org.jboss.forge.shell.exceptions.PluginExecutionException;
import org.jboss.forge.shell.exceptions.ShellExecutionException;
import org.jboss.forge.shell.integration.BufferManager;
import org.jboss.forge.shell.integration.KeyListener;
import org.jboss.forge.shell.plugins.builtin.Echo;
import org.jboss.forge.shell.project.CurrentProject;
import org.jboss.forge.shell.spi.CommandInterceptor;
import org.jboss.forge.shell.spi.TriggeredAction;
import org.jboss.forge.shell.util.Booleans;
import org.jboss.forge.shell.util.Files;
import org.jboss.forge.shell.util.GeneralUtils;
import org.jboss.forge.shell.util.JavaPathspecParser;
import org.jboss.forge.shell.util.OSUtils;
import org.jboss.forge.shell.util.ResourceUtil;
import org.jboss.forge.shell.util.Streams;
import org.jboss.weld.environment.se.bindings.Parameters;
import org.mvel2.ConversionHandler;
/**
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*/
@ApplicationScoped
public class ShellImpl extends AbstractShellPrompt implements Shell
{
static final String DEFAULT_PROMPT = "[\\c{green}$PROJECT_NAME\\c] \\c{blue}\\W\\c \\c{green}\\$\\c ";
static final String DEFAULT_PROMPT_NO_PROJ = "[\\c{red}no project\\c] \\c{blue}\\W\\c \\c{red}\\$\\c ";
public static final String PROP_FORGE_CONFIG_DIR = "FORGE_CONFIG_DIR";
public static final String PROP_PROMPT = "PROMPT";
public static final String PROP_PROMPT_NO_PROJ = "PROMPT_NOPROJ";
public static final String PROP_DEFAULT_PLUGIN_REPO = "DEFAULT_PLUGIN_REPO";
public static final String DEFAULT_PLUGIN_REPO = "https://raw.github.com/forge/plugin-repository/master/repository.yaml";
private static final String PROP_ACCEPT_DEFAULTS = "ACCEPT_DEFAULTS";
public static final String PROP_VERBOSE = "VERBOSE";
public static final String PROP_HISTORY = "HISTORY";
public static final String PROP_EXCEPTION_HANDLING = "EXCEPTION_HANDLING";
public static final String PROP_FORGE_VERSION = "FORGE_VERSION";
static final String NO_INIT_SYSTEM_PROPERTY = "forge.debug.no_auto_init_streams";
static final String FORGE_HOME_SYSTEM_PROPERTY = "forge.home";
static final String PROP_IGNORE_EOF = "IGNOREEOF";
static final int DEFAULT_IGNORE_EOF = 1;
public static final String FORGE_CONFIG_DIR;
static {
FORGE_CONFIG_DIR = System.getProperty(
PROP_FORGE_CONFIG_DIR,
OSUtils.getDefaultForgePath());
}
public static final String FORGE_COMMAND_HISTORY_FILE = "cmd_history";
public static final String FORGE_CONFIG_FILE = "config";
public static final String OFFLINE_FLAG = "OFFLINE";
@Inject
@Parameters
private List<String> parameters;
@Inject
private BeanManager manager;
@Inject
private CurrentProject projectContext;
@Inject
ResourceFactory resourceFactory;
private Resource<?> lastResource;
@Inject
private FSHRuntime fshRuntime;
@Inject
PromptTypeConverter promptTypeConverter;
@Inject
private CompletedCommandHolder commandHolder;
@Inject
private ForgeEnvironment environment;
private ConsoleReader reader;
private Completer completer;
private boolean pretend = false;
private boolean exitRequested = false;
private ConsoleInputSession inputPipe;
private OutputStream outputStream;
private OutputStream historyOutstream;
private BufferManager screenBuffer;
// overrides
private InputStream _redirectedStream;
private List<String> _historyOverride;
private enum BufferingMode
{
Direct, Buffering
}
private final BufferingMode bufferingMode = BufferingMode.Direct;
private final boolean colorEnabled = Boolean.getBoolean("forge.shell.colorEnabled");
private final ConversionHandler resourceConversionHandler = new ConversionHandler()
{
@Override
@SuppressWarnings("rawtypes")
public Resource[] convertFrom(final Object obl)
{
return GeneralUtils.parseSystemPathspec(resourceFactory, lastResource, getCurrentResource(),
obl instanceof String[] ? (String[]) obl : new String[] { obl.toString() });
}
@SuppressWarnings("rawtypes")
@Override
public boolean canConvertFrom(final Class aClass)
{
return true;
}
};
private final ConversionHandler javaResourceConversionHandler = new ConversionHandler()
{
@Override
public JavaResource[] convertFrom(final Object obj)
{
if (getCurrentProject().hasFacet(JavaSourceFacet.class))
{
String[] strings = obj instanceof String[] ? (String[]) obj : new String[] { obj.toString() };
List<Resource<?>> resources = new ArrayList<Resource<?>>();
for (String string : strings)
{
resources.addAll(new JavaPathspecParser(getCurrentProject().getFacet(JavaSourceFacet.class),
string).resolve());
}
List<JavaResource> filtered = new ArrayList<JavaResource>();
for (Resource<?> resource : resources)
{
if (resource instanceof JavaResource)
{
filtered.add((JavaResource) resource);
}
}
JavaResource[] result = new JavaResource[filtered.size()];
result = filtered.toArray(result);
return result;
}
else
return null;
}
@SuppressWarnings("rawtypes")
@Override
public boolean canConvertFrom(final Class aClass)
{
return true;
}
};
private boolean executing;
@Inject
private ShellConfig shellConfig;
@Inject
private Instance<CommandInterceptor> commandInterceptors;
@Inject
private Instance<TriggeredAction> triggeredActions;
@Inject
private IgnoreEofKeyListener ignoreEOF;
void init(@Observes final Startup event, final PluginCommandCompleter pluginCompleter) throws Exception
{
BooleanConverter booleanConverter = new BooleanConverter();
addConversionHandler(boolean.class, booleanConverter);
addConversionHandler(Boolean.class, booleanConverter);
addConversionHandler(File.class, new FileConverter());
addConversionHandler(Dependency.class, new DependencyIdConverter());
addConversionHandler(URL.class, new URLConverter());
addConversionHandler(JavaResource[].class, javaResourceConversionHandler);
addConversionHandler(JavaResource.class, new ConversionHandler()
{
@Override
public Object convertFrom(final Object obj)
{
JavaResource[] res = (JavaResource[]) javaResourceConversionHandler.convertFrom(obj);
if (res.length > 1)
{
throw new RuntimeException("ambiguous paths");
}
else if (res.length == 0)
{
if (getCurrentProject().hasFacet(JavaSourceFacet.class))
{
JavaSourceFacet java = getCurrentProject().getFacet(JavaSourceFacet.class);
try
{
JavaResource resource = java.getJavaResource(obj.toString());
return resource;
}
catch (FileNotFoundException e)
{
throw new RuntimeException(e);
}
}
return null;
}
else
{
return res[0];
}
}
@Override
@SuppressWarnings("rawtypes")
public boolean canConvertFrom(final Class type)
{
return javaResourceConversionHandler.canConvertFrom(type);
}
});
addConversionHandler(Resource[].class, resourceConversionHandler);
addConversionHandler(Resource.class, new ConversionHandler()
{
@Override
public Object convertFrom(final Object o)
{
Resource<?>[] res = (Resource<?>[]) resourceConversionHandler.convertFrom(o);
if (res.length > 1)
{
throw new RuntimeException("ambiguous paths");
}
else if (res.length == 0)
{
return ResourceUtil.parsePathspec(resourceFactory, getCurrentResource(), o.toString()).get(0);
}
else
{
return res[0];
}
}
@Override
@SuppressWarnings("rawtypes")
public boolean canConvertFrom(final Class aClass)
{
return resourceConversionHandler.canConvertFrom(aClass);
}
});
configureOSTerminal();
this.completer = new AggregateCompleter(pluginCompleter);
initReaderAndStreams();
initParameters();
if (event.isRestart())
{
// suppress the MOTD if this is a restart.
environment.setProperty("NO_MOTD", true);
}
else
{
environment.setProperty("NO_MOTD", false);
}
environment.setProperty("OS_NAME", OSUtils.getOsName());
environment.setProperty(PROP_FORGE_CONFIG_DIR, FORGE_CONFIG_DIR);
environment.setProperty(PROP_PROMPT, "> ");
environment.setProperty(PROP_PROMPT_NO_PROJ, "> ");
if (Boolean.getBoolean("forge.offline") == true)
{
environment.setProperty(OFFLINE_FLAG, true);
}
else
{
environment.setProperty(OFFLINE_FLAG, false);
}
shellConfig.loadHistory(this);
shellConfig.loadConfig(this);
initSignalHandlers();
if (!isNoInitMode())
this.registerKeyListener(ignoreEOF);
/*
* Do this last so that we don't fire off plugin events before the shell has booted (Causing all kinds of
* wonderful issues)
*/
projectContext.setCurrentResource(resourceFactory.getResourceFrom(event.getWorkingDirectory()));
environment.setProperty("CWD", getCurrentDirectory().getFullyQualifiedName());
environment.setProperty("SHELL", this);
}
private void initSignalHandlers()
{
try
{
// check to see if we have something to work with.
Class.forName("sun.misc.SignalHandler");
SigHandler.init(this);
}
catch (ClassNotFoundException e)
{
// signal trapping not supported. Oh well, switch to a Sun-based JVM, loser!
}
}
@Override
public void writeToHistory(final String command)
{
if (isHistoryEnabled())
{
try
{
for (int i = 0; i < command.length(); i++)
{
historyOutstream.write(command.charAt(i));
}
historyOutstream.write(OSUtils.getLineSeparator().getBytes());
historyOutstream.flush();
}
catch (IOException e)
{
}
}
}
@Override
public boolean isHistoryEnabled()
{
Object s = environment.getProperty(PROP_HISTORY);
return Booleans.toBooleanValue(s);
}
@Override
public void setHistoryEnabled(final boolean verbose)
{
environment.setProperty(PROP_VERBOSE, String.valueOf(verbose));
}
@Override
public boolean isAcceptDefaults()
{
Object s = environment.getProperty(PROP_ACCEPT_DEFAULTS);
return Booleans.toBooleanValue(s);
}
@Override
public void setAcceptDefaults(final boolean accept)
{
environment.setProperty(PROP_ACCEPT_DEFAULTS, accept);
}
@Override
public void setHistoryOutputStream(final OutputStream stream)
{
historyOutstream = stream;
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
try
{
historyOutstream.flush();
historyOutstream.close();
}
catch (Exception e)
{
}
}
});
}
@Override
public void setHistory(final List<String> lines)
{
if (isNoInitMode())
{
_historyOverride = lines;
return;
}
_setHistory(lines);
}
private void _setHistory(final List<String> lines)
{
MemoryHistory history = new MemoryHistory();
for (String line : lines)
{
history.add(line);
}
reader.setHistory(history);
}
boolean isNoInitMode()
{
return Boolean.getBoolean(NO_INIT_SYSTEM_PROPERTY);
}
private void initReaderAndStreams() throws IOException
{
boolean noInitMode = isNoInitMode();
if ((_redirectedStream == null) && noInitMode)
{
return;
}
if ((inputPipe == null) && (_redirectedStream == null))
{
inputPipe = new ConsoleInputSession(System.in, environment.isEmbedded());
}
if (outputStream == null)
{
outputStream = System.out;
}
Terminal terminal;
if (environment.isEmbedded())
{
terminal = new IdeTerminal();
}
else if (OSUtils.isWindows())
{
final OutputStream ansiOut = AnsiConsole.wrapOutputStream(outputStream);
TerminalFactory.configure(TerminalFactory.Type.WINDOWS);
terminal = TerminalFactory.get();
final OutputStreamWriter writer = new OutputStreamWriter(ansiOut, System.getProperty(
"jline.WindowsTerminal.output.encoding", System.getProperty("file.encoding")));
outputStream = new OutputStream()
{
@Override
public void write(final int b) throws IOException
{
writer.write(b);
writer.flush();
}
};
}
else
{
terminal = TerminalFactory.get();
}
this.screenBuffer = new JLineScreenBuffer(terminal, outputStream);
this.reader = new ConsoleReader(_redirectedStream == null ? inputPipe.getExternalInputStream()
: _redirectedStream, this, null, terminal);
this.reader.setHistoryEnabled(true);
this.reader.setBellEnabled(false);
for (TriggeredAction action : triggeredActions)
{
this.reader.addTriggeredAction(action.getTrigger(), action.getListener());
}
if (noInitMode)
{
if (_historyOverride != null)
{
_setHistory(_historyOverride);
}
}
this.reader.addCompleter(completer);
this.reader.setCompletionHandler(new OptionAwareCompletionHandler(commandHolder, this));
}
private void initParameters()
{
if (!isVerbose())
{
environment.setProperty(PROP_VERBOSE, String.valueOf(parameters.contains("--verbose")));
}
environment.setProperty(PROP_EXCEPTION_HANDLING,
String.valueOf(parameters.contains("--disableExceptionHandlers") != true));
if (parameters.contains("--pretend"))
{
pretend = true;
}
if ((parameters != null) && !parameters.isEmpty())
{
// this is where we will initialize other parameters... e.g. accepting
// a path
}
}
void teardown(@Observes final Shutdown shutdown, final Event<PreShutdown> preShutdown)
{
preShutdown.fire(new PreShutdown(shutdown.getStatus()));
exitRequested = true;
if (inputPipe != null)
{
inputPipe.stop();
}
}
void doShell(@Observes final AcceptUserInput event) throws Exception
{
String line;
reader.setPrompt(getPrompt());
while (!exitRequested)
{
try
{
line = readLine();
if (line != null)
{
if (!"".equals(line.trim()))
{
writeToHistory(line);
execute(line);
flush();
}
reader.setPrompt(getPrompt());
}
}
catch (Exception e)
{
handleException(e);
}
}
}
private void handleException(final Exception original) throws Exception
{
if (!isExceptionHandlingEnabled())
{
Throwable root = original;
while ((root.getCause() != null) && !root.getCause().equals(root))
{
root = root.getCause();
}
if (root instanceof Exception)
throw (Exception) root;
else
throw new RuntimeException(root);
}
try
{
// unwrap any aborted exceptions
Throwable cause = original;
while (cause.getCause() != null)
{
if (cause instanceof AbortedException)
throw (AbortedException) cause;
cause = cause.getCause();
}
throw cause;
}
catch (AbortedException e)
{
ShellMessages.info(this, "Aborted.");
if (isVerbose())
{
e.printStackTrace();
}
}
catch (CommandExecutionException e)
{
ShellMessages.error(this, formatSourcedError(e.getCommand()) + e.getMessage());
if (isVerbose())
{
e.printStackTrace();
}
}
catch (CommandParserException e)
{
ShellMessages.error(this, formatSourcedError(e.getCommand()) + e.getMessage());
if (isVerbose())
{
e.printStackTrace();
}
}
catch (PluginExecutionException e)
{
ShellMessages.error(this, formatSourcedError(e.getPlugin()) + e.getMessage());
if (isVerbose())
{
e.printStackTrace();
}
}
catch (ShellExecutionException e)
{
ShellMessages.error(this, e.getMessage());
if (isVerbose())
{
e.printStackTrace();
}
}
catch (Throwable e)
{
if (!isVerbose())
{
ShellMessages.error(this, "Exception encountered: " + e.getMessage()
+ " (type \"set VERBOSE true\" to enable stack traces)");
}
else
{
ShellMessages.error(this, "Exception encountered: (type \"set VERBOSE false\" to disable stack traces)");
e.printStackTrace();
}
}
}
private String formatSourcedError(final PluginMetadata plugin)
{
return (plugin == null ? "" : ("[" + plugin.toString() + "] "));
}
private String formatSourcedError(final CommandMetadata cmd)
{
String out;
if (cmd != null)
{
out = cmd.getParent().getName();
if (!cmd.isDefault())
out += " " + cmd.getName();
out = "[" + out + "] ";
}
else
out = "";
return out;
}
@Override
public String readLine() throws IOException
{
return readLine(null);
}
@Override
public String readLine(final Character mask) throws IOException
{
String line;
if (mask != null)
{
line = reader.readLine(mask);
}
else
{
line = reader.readLine();
}
if ((line != null) && !OSUtils.getLineSeparator().equals(line))
{
write(OSUtils.getLineSeparator().getBytes());
}
flush();
return line;
}
@Override
public int scan()
{
try
{
return reader.readVirtualKey();
}
catch (IOException e)
{
return -1;
}
}
@Override
public void clearLine()
{
print(new Ansi().eraseLine(Ansi.Erase.ALL).toString());
}
@Override
public void cursorLeft(final int x)
{
print(new Ansi().cursorLeft(x).toString());
}
private final Object executorLock = new Object();
private class ExecutorThread extends Thread
{
private final String run;
private ExecutorThread(final String run)
{
this.run = run;
}
@Override
public void run()
{
fshRuntime.run(run);
}
}
private volatile Thread executorThread;
@Override
public void execute(String line) throws Exception
{
synchronized (executorLock)
{
try
{
executing = true;
if (!interruptedState)
{
for (CommandInterceptor interceptor : commandInterceptors)
{
line = interceptor.intercept(line);
}
executorThread = new ExecutorThread(line);
executorThread.run();
executorThread.join();
}
}
catch (Exception e)
{
handleException(e);
}
finally
{
executing = false;
interruptedState = false;
}
}
}
private volatile boolean interruptedState = false;
public void interrupt()
{
if (executorThread != null)
{
executorThread.interrupt();
try
{
inputPipe.interruptPipe();
}
catch (Exception e)
{
//
}
print("[killed]");
interruptedState = true;
}
}
@Override
public void execute(final File file) throws Exception
{
StringBuilder buf = new StringBuilder();
InputStream instream = new BufferedInputStream(new FileInputStream(file));
try
{
byte[] b = new byte[25];
int read;
while ((read = instream.read(b)) != -1)
{
for (int i = 0; i < read; i++)
{
buf.append((char) b[i]);
}
}
instream.close();
execute(buf.toString());
}
finally
{
ignoreEOF.reset();
instream.close();
}
}
@Override
public void execute(final File file, final String... args) throws Exception
{
StringBuilder buf = new StringBuilder();
String funcName = (file.getName().replaceAll("\\.", "_") + "_" + hashCode()).replaceAll("\\-", "M");
buf.append("def ").append(funcName).append('(');
if (args != null)
{
for (int i = 0; i < args.length; i++)
{
buf.append("_").append(i);
if ((i + 1) < args.length)
{
buf.append(", ");
}
}
}
buf.append(") {\n");
if (args != null)
{
buf.append("@_vararg = new String[").append(args.length).append("];\n");
for (int i = 0; i < args.length; i++)
{
buf.append("@_vararg[").append(i).append("] = ")
.append("_").append(i).append(";\n");
}
}
InputStream instream = new BufferedInputStream(new FileInputStream(file));
try
{
buf.append(Streams.toString(instream));
buf.append("\n}; \n@").append(funcName).append('(');
if (args != null)
{
for (int i = 0; i < args.length; i++)
{
buf.append("\"").append(args[i].replaceAll("\\\"", "\\\\\\\"")).append("\"");
if ((i + 1) < args.length)
{
buf.append(", ");
}
}
}
buf.append(");\n");
execute(buf.toString());
}
finally
{
environment.removeProperty(funcName);
instream.close();
}
}
/*
* Shell Print Methods
*/
@Override
public void printlnVerbose(final String line)
{
if ((line != null) && isVerbose())
{
screenBuffer.write(OSUtils.getLineSeparator().getBytes());
}
}
@Override
public void print(final String output)
{
if (output != null)
{
screenBuffer.write(output);
}
}
@Override
public void println(final String line)
{
if (line != null)
{
screenBuffer.write(line);
screenBuffer.write(OSUtils.getLineSeparator().getBytes());
}
}
@Override
public void println()
{
try
{
screenBuffer.write(OSUtils.getLineSeparator().getBytes());
_flushBuffer();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
@Override
public void print(final ShellColor color, final String output)
{
print(renderColor(color, output));
}
@Override
public void println(final ShellColor color, final String output)
{
println(renderColor(color, output));
}
@Override
public void printlnVerbose(final ShellColor color, final String output)
{
printlnVerbose(renderColor(color, output));
}
@Override
public String renderColor(final ShellColor color, final String output)
{
if (!colorEnabled)
{
return output;
}
Ansi ansi = new Ansi();
// Handle the darker Window consoles by increasing intensity
if(OSUtils.isWindows() && !environment.isEmbedded())
{
ansi.a(Ansi.Attribute.INTENSITY_BOLD);
}
switch (color)
{
case BLACK:
ansi.fg(Ansi.Color.BLACK);
break;
case BLUE:
ansi.fg(Ansi.Color.BLUE);
break;
case CYAN:
ansi.fg(Ansi.Color.CYAN);
break;
case GREEN:
ansi.fg(Ansi.Color.GREEN);
break;
case MAGENTA:
ansi.fg(Ansi.Color.MAGENTA);
break;
case RED:
ansi.fg(Ansi.Color.RED);
break;
case WHITE:
ansi.fg(Ansi.Color.WHITE);
break;
case YELLOW:
ansi.fg(Ansi.Color.YELLOW);
break;
case BOLD:
ansi.a(Ansi.Attribute.INTENSITY_BOLD);
break;
case ITALIC:
ansi.a(Ansi.Attribute.ITALIC);
ansi.a(Ansi.Attribute.INTENSITY_FAINT);
break;
case UNDERLINE:
ansi.a(Ansi.Attribute.UNDERLINE);
break;
case STRIKEOUT:
ansi.a(Ansi.Attribute.STRIKETHROUGH_ON);
break;
default:
return output;
}
return ansi.render(output).reset().toString();
}
@Override
public void write(final int b)
{
screenBuffer.write(b);
}
@Override
public synchronized void write(final byte b)
{
screenBuffer.write(b);
}
@Override
public void write(final byte[] b)
{
screenBuffer.write(b);
}
@Override
public void write(final byte[] b, final int offset, final int length)
{
screenBuffer.write(b, offset, length);
}
private void _flushBuffer() throws IOException
{
if (bufferingMode == BufferingMode.Direct)
{
reader.flush();
}
}
@Override
public void clear()
{
print(new Ansi().cursor(0, 0).eraseScreen().toString());
}
@Override
public boolean isExceptionHandlingEnabled()
{
Object s = environment.getProperty(PROP_EXCEPTION_HANDLING);
return Booleans.toBooleanValue(s);
}
@Override
public void setExceptionHandlingEnabled(final boolean enabled)
{
environment.setProperty(PROP_EXCEPTION_HANDLING, String.valueOf(enabled));
}
@Override
public boolean isVerbose()
{
Object s = environment.getProperty(PROP_VERBOSE);
return Booleans.toBooleanValue(s);
}
@Override
public void setVerbose(final boolean verbose)
{
environment.setProperty(PROP_VERBOSE, String.valueOf(verbose));
}
@Override
public boolean isPretend()
{
return pretend;
}
@Override
public boolean isExecuting()
{
return executing;
}
@Override
public void setInputStream(final InputStream is) throws IOException
{
// throw new UnsupportedOperationException("not allowed");
this.inputPipe = null;
this._redirectedStream = is;
initReaderAndStreams();
}
@Override
public void setOutputStream(final OutputStream stream) throws IOException
{
this.outputStream = stream;
initReaderAndStreams();
}
@Override
public void setDefaultPrompt()
{
setPrompt("");
}
@Override
public void setPrompt(final String prompt)
{
environment.setProperty(PROP_PROMPT, prompt);
}
@Override
public String getPrompt()
{
if (projectContext.getCurrent() != null)
{
return Echo.echo(this, Echo.promptExpressionParser(this, (String) environment.getProperty(PROP_PROMPT)));
}
else
{
return Echo.echo(this,
Echo.promptExpressionParser(this, (String) environment.getProperty(PROP_PROMPT_NO_PROJ)));
}
}
@Override
public DirectoryResource getCurrentDirectory()
{
Resource<?> r = getCurrentResource();
return ResourceUtil.getContextDirectory(r);
}
@Override
public DirectoryResource getConfigDir()
{
return resourceFactory.getResourceFrom(new File((String) environment.getProperty(PROP_FORGE_CONFIG_DIR))).reify(
DirectoryResource.class);
}
@Override
public Resource<?> getCurrentResource()
{
Resource<?> result = this.projectContext.getCurrentResource();
if (result == null)
{
result = this.resourceFactory.getResourceFrom(Files.getWorkingDirectory());
environment.setProperty("CWD", result.getFullyQualifiedName());
}
return result;
}
@Override
@SuppressWarnings("unchecked")
public Class<? extends Resource<?>> getCurrentResourceScope()
{
return (Class<? extends Resource<?>>) getCurrentResource().getClass();
}
@Override
public void setCurrentResource(final Resource<?> resource)
{
lastResource = getCurrentResource();
projectContext.setCurrentResource(resource);
environment.setProperty("CWD", resource.getFullyQualifiedName());
}
@Override
public Project getCurrentProject()
{
return this.projectContext.getCurrent();
}
@Override
public int getHeight()
{
return screenBuffer.getHeight();
}
@Override
public int getAbsoluteHeight()
{
return reader.getTerminal().getHeight();
}
@Override
public int getWidth()
{
return screenBuffer.getWidth();
}
public String escapeCode(final int code, final String value)
{
return new Ansi().a(value).fg(Ansi.Color.BLUE).toString();
}
private class ReturnValueThread<V> extends Thread
{
private V value;
private final Callable<V> caller;
private Throwable exception;
private ReturnValueThread(final Callable<V> caller)
{
this.caller = caller;
}
@Override
public void run()
{
try
{
value = caller.call();
}
catch (Exception e)
{
try
{
exception = e;
handleException(e);
}
catch (Exception e2)
{
// hmmm
}
}
}
public V getValue()
{
return value;
}
public Throwable getException()
{
return exception;
}
}
@Override
public String promptWithCompleter(String message, final Completer tempCompleter)
{
synchronized (executorLock)
{
if (!message.isEmpty() && message.matches("^.*\\S$"))
{
message = message + " ";
}
message = renderColor(ShellColor.CYAN, " ? ") + message;
try
{
reader.removeCompleter(this.completer);
if (tempCompleter != null)
{
reader.addCompleter(tempCompleter);
}
reader.setHistoryEnabled(false);
reader.setPrompt(message);
flush();
Callable<String> call = new Callable<String>()
{
@Override
public String call() throws Exception
{
return readLine();
}
};
ReturnValueThread<String> thread = new ReturnValueThread<String>(call);
executorThread = thread;
thread.run();
thread.join();
if (interruptedState)
{
throw new AbortedException();
}
flush();
if (thread.getException() instanceof EndOfStreamException)
{
throw (EndOfStreamException) thread.getException();
}
return thread.getValue();
}
catch (InterruptedException e)
{
throw new RuntimeException("[killed]");
}
finally
{
if (tempCompleter != null)
{
reader.removeCompleter(tempCompleter);
}
reader.addCompleter(this.completer);
reader.setHistoryEnabled(true);
reader.setPrompt("");
interruptedState = false;
}
}
}
@Override
public String promptSecret(String message)
{
if (!message.isEmpty() && message.matches("^.*\\S$"))
{
message = message + " ";
}
message = renderColor(ShellColor.CYAN, " ? ") + message;
try
{
reader.removeCompleter(this.completer);
reader.setHistoryEnabled(false);
reader.setPrompt(message);
flush();
String line = readLine('*');
flush();
return line;
}
catch (IOException e)
{
throw new IllegalStateException("Shell input stream failure", e);
}
finally
{
reader.addCompleter(this.completer);
reader.setHistoryEnabled(true);
reader.setPrompt("");
}
}
@Override
public <T> Set<T> promptMultiSelectWithWildcard(String wildcard, String message, Map<String, T> options)
{
if ((options == null) || options.isEmpty())
{
throw new IllegalArgumentException(
"promptMultiSelect() Cannot ask user to select from a list of nothing. Ensure you have values in your options list.");
}
if (options.containsKey(""))
{
throw new IllegalArgumentException("empty labels not allowed");
}
if (options.containsKey(wildcard))
{
throw new IllegalArgumentException("wildcard label duplicated among option labels");
}
if (wildcard != null && wildcard.trim().isEmpty())
{
throw new IllegalArgumentException("blank/empty wildcard labels not allowed");
}
final Map<String, String> fullOptions = new LinkedHashMap<String, String>();
for (String key : options.keySet())
{
fullOptions.put(key, key);
}
if (wildcard != null)
{
fullOptions.put(wildcard, wildcard);
}
fullOptions.put("", null);
println(message);
final Set<T> result = new LinkedHashSet<T>();
StringBuilder prompt = new StringBuilder("select (");
if (wildcard != null)
{
prompt.append('"').append(wildcard).append('"').append(" = all values; ");
}
prompt.append("empty = done)");
while (true)
{
final String choice = promptChoice(prompt.toString(), fullOptions);
if (choice == null)
{
break;
}
if (choice.equals(wildcard))
{
result.addAll(options.values());
break;
}
result.add(options.get(choice));
fullOptions.remove(choice);
if (result.size() == options.size())
{
break;
}
}
return Collections.unmodifiableSet(result);
}
@Override
protected PromptTypeConverter getPromptTypeConverter()
{
return promptTypeConverter;
}
@Override
protected ResourceFactory getResourceFactory()
{
return resourceFactory;
}
@Override
public void setAnsiSupported(final boolean value)
{
if (value != isAnsiSupported())
{
try
{
if (value)
{
configureOSTerminal();
}
else
{
TerminalFactory.configure(TerminalFactory.Type.NONE);
TerminalFactory.reset();
}
initReaderAndStreams();
}
catch (IOException e)
{
throw new RuntimeException("Failed to reset Terminal instance for ANSI configuration", e);
}
}
}
@Override
public void bufferingMode()
{
screenBuffer.bufferOnlyMode();
}
@Override
public void directWriteMode()
{
screenBuffer.directWriteMode();
}
@Override
public void flush()
{
screenBuffer.flushBuffer();
}
@Override
public void registerBufferManager(final BufferManager manager)
{
screenBuffer = manager;
}
@Override
public BufferManager getBufferManager()
{
return screenBuffer;
}
@Override
public void registerKeyListener(final KeyListener keyListener)
{
reader.registerKeyListener(keyListener);
}
private void configureOSTerminal() throws IOException
{
if (OSUtils.isLinux() || OSUtils.isOSX())
{
TerminalFactory.configure(TerminalFactory.Type.UNIX);
TerminalFactory.reset();
}
else if (OSUtils.isWindows())
{
TerminalFactory.configure(TerminalFactory.Type.WINDOWS);
TerminalFactory.reset();
}
else
{
TerminalFactory.configure(TerminalFactory.Type.NONE);
TerminalFactory.reset();
}
initReaderAndStreams();
}
@Override
public boolean isAnsiSupported()
{
return (reader != null) && reader.getTerminal().isAnsiSupported();
}
@Override
public ForgeEnvironment getEnvironment()
{
return environment;
}
@Override
BeanManager getBeanManager()
{
return manager;
}
public ConsoleReader getReader()
{
return reader;
}
}