package org.radrails.rails.ui.console; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.Launch; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.swt.graphics.Image; import org.radrails.rails.internal.core.RailsPlugin; import org.radrails.rails.internal.ui.console.IRailsShellConstants; import org.radrails.rails.internal.ui.console.RailsShellCompletionProposal; import org.radrails.rails.ui.RailsUILog; import org.radrails.rails.ui.RailsUIPlugin; import org.rubypeople.rdt.core.RubyCore; import org.rubypeople.rdt.core.util.Util; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.RubyPluginImages; import org.rubypeople.rdt.launching.IRubyLaunchConfigurationConstants; import org.rubypeople.rdt.launching.ITerminal; import org.rubypeople.rdt.launching.RubyRuntime; import org.rubypeople.rdt.ui.viewsupport.ImageDescriptorRegistry; import com.aptana.ide.core.IdeLog; public abstract class RailsShellCommandProvider { private static final String CONSOLE_ENCODING = "UTF-8"; private String fRunMode; private IProject fProject; public abstract List<ICompletionProposal> getCompletionProposals(String prefix, List<String> tokens, int offset); protected ICompletionProposal createProposal(String string, int offset, String token) { return createProposal(string, "", offset, token); } public abstract Set<String> commandsHandled(); protected ICompletionProposal createProposal(String string, String description, int offset, String token) { if (token != null && !string.startsWith(token)) return null; ImageDescriptorRegistry registry = RubyPlugin.getImageDescriptorRegistry(); Image image = registry.get(RubyPluginImages.DESC_MISC_PUBLIC); String display = string; if (description != null && description.trim().length() > 0) { display += " - " + description; } return new RailsShellCompletionProposal(string, offset - token.length(), token.length(), string.length(), image, display, null, description); } public abstract void run(final ITerminal shell, final String command); protected String getRunMode() { return fRunMode; } protected IProject getProject() { return fProject; } protected String getArgs(String command) { if (command.startsWith("sudo ")) { command = command.substring(5); } int index = command.indexOf(' '); String parameters = ""; if (index != -1) { parameters = command.substring(index).trim(); } return parameters; } /** * Does a project need to be selected to run this command? * * @return */ public boolean projectNeedsToBeSelected() { return false; } public void initialize(IProject project, String runMode) { this.fProject = project; this.fRunMode = runMode; } protected String getLastToken(String prefix, List<String> tokens) { if (prefix != null && prefix.endsWith(" ")) return ""; if (tokens.size() > 0) return tokens.get(tokens.size() - 1); return ""; } protected void launchInsideShell(ITerminal shell, String command) { launchInsideShell(shell, command, null); } protected void launchInsideShell(ITerminal shell, String command, Map<String, String> env) { launchInsideShell(shell, command, env, null); } protected void launchInsideShell(ITerminal shell, String command, Map<String, String> env, Map<String, Object> attrs) { String file = getFile(command); String workingDirectory = getRailsRoot().makeAbsolute().toOSString(); String fullFilePath = getFileIfExists(file, workingDirectory); if (fullFilePath == null) { shell.write(IDebugUIConstants.ID_STANDARD_ERROR_STREAM, "Unknown command '" + command + "'. Type 'help' to show the list of available commands.\n"); shell.write(IDebugUIConstants.ID_STANDARD_INPUT_STREAM, IRailsShellConstants.PROMPT); return; } // If this is the special provider that handles all non-specific commands, check the command to see if we might // need to launch as non-ruby process. if (handlesAll() && launchAsNonRubyProcess(file, fullFilePath, command)) return; try { ILaunchConfigurationWorkingCopy wc = RubyRuntime.createBasicLaunch(fullFilePath, getArgs(command), fProject, workingDirectory); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_USE_TERMINAL, IRailsShellConstants.TERMINAL_ID); if (env != null && !env.isEmpty()) { wc.setAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, true); wc.setAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, env); } if (attrs != null && !attrs.isEmpty()) { for (Map.Entry<String, Object> entry : attrs.entrySet()) { if (entry.getValue() instanceof List) { wc.setAttribute(entry.getKey(), (List) entry.getValue()); } else if (entry.getValue() instanceof Map) { wc.setAttribute(entry.getKey(), (Map) entry.getValue()); } else if (entry.getValue() instanceof String) { wc.setAttribute(entry.getKey(), (String) entry.getValue()); } else if (entry.getValue() instanceof Integer) { wc.setAttribute(entry.getKey(), (Integer) entry.getValue()); } else if (entry.getValue() instanceof Boolean) { wc.setAttribute(entry.getKey(), (Boolean) entry.getValue()); } } } wc.doSave().launch(fRunMode, new NullProgressMonitor()); } catch (CoreException e) { RailsUILog.log(e); } } /** * tries to launch the file as a non ruby launch (if it appears that it should be launched that way). * * @param file * @param fullFilePath * @param command * @return boolean indicating whether we attempted to launch as non-ruby (if false, try launching as ruby process). */ private boolean launchAsNonRubyProcess(String file, String fullFilePath, String command) { IPath path = RubyCore.checkSystemPath(file); if (path == null || !path.toOSString().equals(fullFilePath)) return false; if (looksLikeRubyFile(fullFilePath)) return false; try { // TODO Include env vars! Process p = Runtime.getRuntime().exec(command, null, getProject().getLocation().toFile()); Launch launch = new Launch(null, getRunMode(), null); launch.setAttribute(IRubyLaunchConfigurationConstants.ATTR_USE_TERMINAL, IRailsShellConstants.TERMINAL_ID); IProcess process = DebugPlugin.newProcess(launch, p, command); launch.addProcess(process); DebugPlugin.getDefault().getLaunchManager().addLaunch(launch); } catch (IOException e) { IdeLog.logError(RailsUIPlugin.getInstance(), e.getMessage(), e); } return true; } // FIXME Where should this method go? public static String getFileIfExists(String file, String workingDirectory, IProject project) { if (project != null) { IFile actualFile = project.getFile(file); if (actualFile.exists()) { return actualFile.getLocation().toOSString(); } actualFile = project.getFile(file + ".rb"); if (actualFile.exists()) { actualFile.getLocation().toOSString(); } } File rubyFile = null; if (workingDirectory != null) { // Also check relative to working directory IPath path = new Path(workingDirectory).append(file); rubyFile = path.toFile(); if (rubyFile != null && rubyFile.exists()) { return rubyFile.getAbsolutePath(); } // append ".rb" path = new Path(workingDirectory).append(file + ".rb"); rubyFile = path.toFile(); if (rubyFile != null && rubyFile.exists()) { return rubyFile.getAbsolutePath(); } } // Check in bin script locations! // TODO What if we found it in PATH? It might not be a ruby script! Can we sniff the file and see? rubyFile = RailsPlugin.getInstance().findBinScript(file, null); if (rubyFile != null && rubyFile.exists()) { return rubyFile.getAbsolutePath(); } return null; } private String getFileIfExists(String file, String workingDirectory) { return getFileIfExists(file, workingDirectory, fProject); } protected ILaunch runInNewConsole(ITerminal shell, String command) { try { ILaunchConfigurationWorkingCopy wc = RubyRuntime.createBasicLaunch(getFile(command), getArgs(command), getProject(), getRailsRoot().makeAbsolute().toOSString()); Map<String, String> map = new HashMap<String, String>(); map.put(IRubyLaunchConfigurationConstants.ATTR_RUBY_COMMAND, "ruby"); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE_SPECIFIC_ATTRS_MAP, map); wc.setAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING, CONSOLE_ENCODING); ILaunch launch = wc.doSave().launch(fRunMode, new NullProgressMonitor()); shell.write(IDebugUIConstants.ID_STANDARD_INPUT_STREAM, IRailsShellConstants.PROMPT); return launch; } catch (CoreException e) { RailsUILog.log(e); } return null; } private String getFile(String command) { int index = command.indexOf(' '); String file = command; if (index != -1) { file = command.substring(0, index); } return file; } private IPath getRailsRoot() { IPath railsRoot = RailsPlugin.findRailsRoot(fProject); if (railsRoot == null || railsRoot.segmentCount() == 0) { railsRoot = fProject.getLocation(); } else { railsRoot = fProject.getLocation().append(railsRoot); } return railsRoot; } public boolean handlesAll() { return false; } /** * Sniff to see if file looks like a ruby file. * * @param fullFilePath * @return */ private boolean looksLikeRubyFile(String fullFilePath) { if (fullFilePath.endsWith(".rb") || fullFilePath.endsWith(".rbw")) return true; FileInputStream fis = null; try { File file = new File(fullFilePath); fis = new FileInputStream(file); char[] c = Util.getInputStreamAsCharArray(fis, 1024, null); String contents = new String(c); return contents.contains("rubygems") || (contents.contains("#!") && contents.contains("ruby")); } catch (IOException e) { // ignore } finally { try { if (fis != null) fis.close(); } catch (IOException e) { // ignore } } return false; } }