/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.modules.ruby.merbproject; import org.netbeans.modules.ruby.rubyproject.rake.RakeSupport; import java.awt.Toolkit; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import org.netbeans.modules.gsf.api.DeclarationFinder.DeclarationLocation; import org.netbeans.modules.ruby.platform.execution.RubyExecutionDescriptor; import org.netbeans.api.ruby.platform.RubyInstallation; import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.ruby.platform.RubyPlatform; import org.netbeans.api.extexecution.ExecutionService; import org.netbeans.api.extexecution.print.LineConvertor; import org.netbeans.api.extexecution.print.LineConvertors; import org.netbeans.modules.ruby.merbproject.ui.customizer.MerbProjectProperties; import org.netbeans.modules.ruby.platform.execution.RubyLineConvertorFactory; import org.netbeans.modules.ruby.platform.execution.RubyProcessCreator; import org.netbeans.modules.ruby.rubyproject.AutoTestSupport; //import org.netbeans.modules.ruby.rubyproject.GotoTest; import org.netbeans.modules.ruby.rubyproject.RSpecSupport; import org.netbeans.modules.ruby.rubyproject.RubyBaseActionProvider; import org.netbeans.modules.ruby.rubyproject.RubyFileLocator; import org.netbeans.modules.ruby.rubyproject.RubyProjectUtil; import org.netbeans.modules.ruby.rubyproject.SharedRubyProjectProperties; import org.netbeans.modules.ruby.rubyproject.TestNotifierLineConvertor; import org.netbeans.modules.ruby.rubyproject.UpdateHelper; import org.netbeans.modules.ruby.rubyproject.rake.RakeRunner; import org.netbeans.modules.ruby.rubyproject.spi.TestRunner; import org.netbeans.modules.ruby.rubyproject.ui.customizer.RubyProjectProperties; import org.netbeans.spi.project.ui.support.DefaultProjectOperations; import org.openide.ErrorManager; import org.openide.LifecycleManager; import org.openide.awt.HtmlBrowser; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.Lookup; import org.openide.util.NbBundle; /** * Action provider of the Ruby project. */ public final class MerbActionProvider extends RubyBaseActionProvider { /** * Standard command for running the IRB console on a project */ public static final String COMMAND_IRB_CONSOLE = "irb-console"; // NOI18N // Commands available from Ruby project private static final String[] supportedActions = { COMMAND_BUILD, COMMAND_CLEAN, COMMAND_REBUILD, COMMAND_AUTOTEST, COMMAND_RDOC, COMMAND_IRB_CONSOLE, COMMAND_RUN, COMMAND_RUN_SINGLE, COMMAND_DEBUG, COMMAND_DEBUG_SINGLE, COMMAND_TEST, COMMAND_RSPEC, COMMAND_TEST_SINGLE, COMMAND_DEBUG_TEST_SINGLE, COMMAND_DELETE, COMMAND_COPY, COMMAND_MOVE, COMMAND_RENAME, }; private final static String[] MIME_TYPES = new String[] { RubyInstallation.RUBY_MIME_TYPE }; final MerbProject project; public MerbActionProvider(MerbProject project, UpdateHelper updateHelper) { super(project, updateHelper); this.project = project; } @Override protected FileObject[] getSourceRoots() { return project.getSourceRoots().getRoots(); } @Override protected FileObject[] getTestSourceRoots() { return project.getTestSourceRoots().getRoots(); } @Override protected String[] getMimeTypes() { return MIME_TYPES; } public String[] getSupportedActions() { return supportedActions; } public RubyExecutionDescriptor getScriptDescriptor(File pwd, FileObject fileObject, String target, String displayName, final Lookup context, final boolean debug, LineConvertor... extraConvertors) { String rubyOptions = SharedRubyProjectProperties.getRubyOptions(project); String includePath = RubyProjectUtil.getLoadPath(project); if (rubyOptions != null) { rubyOptions = includePath + " " + rubyOptions; // NOI18N } else { rubyOptions = includePath; } FileObject[] srcPath = project.getSourceRoots().getRoots(); FileObject[] testPath = project.getTestSourceRoots().getRoots(); // Locate the target and specify it by full path. // This is necessary because JRuby and Ruby don't locate the script from the load // path it seems. if (!new File(target).exists() && srcPath != null && srcPath.length > 0) { boolean found = false; // Prefer the first match for (FileObject root : srcPath) { FileObject fo = root.getFileObject(target); if (fo != null) { target = FileUtil.toFile(fo).getAbsolutePath(); found = true; break; } } if (!found && testPath != null) { for (FileObject root : testPath) { FileObject fo = root.getFileObject(target); if (fo != null) { target = FileUtil.toFile(fo).getAbsolutePath(); break; } } } } // For Rails, the execution directory should be the RAILS_ROOT directory if (pwd == null) { pwd = FileUtil.toFile(project.getProjectDirectory()); } String classPath = project.evaluator().getProperty(SharedRubyProjectProperties.JAVAC_CLASSPATH); String jvmArgs = project.evaluator().getProperty(SharedRubyProjectProperties.JVM_ARGS); RubyExecutionDescriptor desc = new RubyExecutionDescriptor(getPlatform(), displayName, pwd, target); desc.debug(debug); desc.showSuspended(true); desc.allowInput(); desc.initialArgs(rubyOptions); desc.jvmArguments(jvmArgs); desc.classPath(classPath); desc.additionalArgs(getApplicationArguments()); desc.fileLocator(new RubyFileLocator(context, project)); desc.addStandardRecognizers(); desc.addOutConvertor(LineConvertors.filePattern(desc.getFileLocator(), RubyLineConvertorFactory.RUBY_TEST_OUTPUT, RubyLineConvertorFactory.EXT_RE ,1,2)); desc.addErrConvertor(LineConvertors.filePattern(desc.getFileLocator(), RubyLineConvertorFactory.RUBY_TEST_OUTPUT, RubyLineConvertorFactory.EXT_RE ,1,2)); if (extraConvertors != null) { for (LineConvertor extra : extraConvertors) { desc.addOutConvertor(extra); } } return desc; } private void openIrbConsole(Lookup context) { RubyPlatform platform = getPlatform(); String irbPath = platform.findExecutable("irb"); // NOI18N if (irbPath == null) { Toolkit.getDefaultToolkit().beep(); return; } String displayName = NbBundle.getMessage(MerbActionProvider.class, "CTL_IrbTopComponent"); File pwd = FileUtil.toFile(project.getProjectDirectory()); String classPath = project.evaluator().getProperty(RubyProjectProperties.JAVAC_CLASSPATH); RubyExecutionDescriptor desc = new RubyExecutionDescriptor(platform, displayName, pwd, irbPath). showSuspended(false). showProgress(false). classPath(classPath). allowInput(). additionalArgs("--simple-prompt", "--noreadline"). // NOI18N //additionalArgs(getApplicationArguments()). fileLocator(new RubyFileLocator(context, project)). addStandardRecognizers(); RubyProcessCreator rpc = new RubyProcessCreator(desc, getSourceEncoding()); ExecutionService.newService(rpc, desc.toExecutionDescriptor(), displayName).run(); } public void invokeAction(final String command, final Lookup context) throws IllegalArgumentException { // Initialize the configuration: find a way to pass this to the launched child process! //RubyConfigurationProvider.Config c = context.lookup(RubyConfigurationProvider.Config.class); //if (c != null) { // String config; // if (c.name != null) { // config = c.name; // } else { // // Invalid but overrides any valid setting in config.properties. // config = ""; // } // Properties p = new Properties(); // p.setProperty(RubyConfigurationProvider.PROP_CONFIG, config); // TODO: Somehow pass the properties to the launched process, and have it digest it //} RubyPlatform platform = RubyPlatform.platformFor(project); assert platform != null : "Action '" + command + "' should be disabled when platform is invalid"; // TODO Check for valid installation of Ruby and Rake if (COMMAND_RUN.equals(command) || COMMAND_DEBUG.equals(command)) { if (!platform.isValid(true)) { return; } // Save all files first LifecycleManager.getDefault().saveAll(); runApp(COMMAND_DEBUG.equals(command)); return; } else if (COMMAND_RUN_SINGLE.equals(command) || COMMAND_DEBUG_SINGLE.equals(command)) { if (!platform.isValid(true)) { return; } FileObject file = getCurrentFile(context); if (RakeSupport.isRakeFile(file)) { if (!platform.hasValidRake(true)) { return; } // Save all files first - this rake file could be accessing other files LifecycleManager.getDefault().saveAll(); RakeRunner runner = new RakeRunner(project); runner.setRakeFile(file); runner.setFileLocator(new RubyFileLocator(context, project)); runner.showWarnings(true); runner.setDebug(COMMAND_DEBUG_SINGLE.equals(command)); runner.run(); return; } RSpecSupport rspec = new RSpecSupport(project); if (rspec.isRSpecInstalled() && RSpecSupport.isSpecFile(file)) { // Save all files first - this rake file could be accessing other files LifecycleManager.getDefault().saveAll(); TestRunner rspecRunner = getTestRunner(TestRunner.TestType.RSPEC); if (rspecRunner != null) { rspecRunner.runTest(file, COMMAND_DEBUG_SINGLE.equals(command)); } else { rspec.runRSpec(null, file, file.getName(), new RubyFileLocator(context, project), true, COMMAND_DEBUG_SINGLE.equals(command)); } return; } saveFile(file); //String target = FileUtil.getRelativePath(getRoot(project.getSourceRoots().getRoots(),file), file); if (file.getName().endsWith("_test")) { // NOI18N // Run test normally - don't pop up browser TestRunner testRunner = getTestRunner(TestRunner.TestType.TEST_UNIT); if (testRunner != null) { testRunner.getInstance().runTest(file, COMMAND_DEBUG_SINGLE.equals(command)); return; } } runRubyScript(file, FileUtil.toFile(file).getAbsolutePath(), file.getNameExt(), context, COMMAND_DEBUG_SINGLE.equals(command), (LineConvertor) null); return; } else if (COMMAND_REBUILD.equals(command) || COMMAND_BUILD.equals(command) || COMMAND_CLEAN.equals(command)) { RakeRunner runner = new RakeRunner(project); runner.showWarnings(true); if (COMMAND_REBUILD.equals(command)) { runner.run("clean", "gem"); // NOI18N } else if (COMMAND_BUILD.equals(command)) { runner.run("gem"); // NOI18N } else { // if(COMMAND_CLEAN.equals(command)) { runner.run("clean"); // NOI18N } return; } if (COMMAND_RDOC.equals(command)) { LifecycleManager.getDefault().saveAll(); File pwd = FileUtil.toFile(project.getProjectDirectory()); Runnable showBrowser = new Runnable() { public void run() { // TODO - wait for the file to be created // Open brower on the doc directory FileObject doc = project.getProjectDirectory().getFileObject("doc"); // NOI18N if (doc != null) { FileObject index = doc.getFileObject("index.html"); // NOI18N if (index != null) { try { URL url = FileUtil.toFile(index).toURI().toURL(); HtmlBrowser.URLDisplayer.getDefault().showURL(url); } catch (MalformedURLException ex) { ErrorManager.getDefault().notify(ex); } } } } }; RubyFileLocator fileLocator = new RubyFileLocator(context, project); String displayName = NbBundle.getMessage(MerbActionProvider.class, "RubyDocumentation"); RubyExecutionDescriptor desc = new RubyExecutionDescriptor(platform, displayName, pwd). additionalArgs("-r", "rdoc/rdoc", "-e", "begin; r = RDoc::RDoc.new; r.document(ARGV); end"). // NOI18N fileLocator(fileLocator). postBuild(showBrowser). addStandardRecognizers(); RubyProcessCreator rpc = new RubyProcessCreator(desc, getSourceEncoding()); ExecutionService.newService(rpc, desc.toExecutionDescriptor(), displayName).run(); return; } if (COMMAND_AUTOTEST.equals(command)) { if (AutoTestSupport.isInstalled(project)) { AutoTestSupport support = new AutoTestSupport(context, project, getSourceEncoding()); support.setClassPath(project.evaluator().getProperty(RubyProjectProperties.JAVAC_CLASSPATH)); support.start(); } return; } if (COMMAND_TEST_SINGLE.equals(command) || COMMAND_DEBUG_TEST_SINGLE.equals(command)) { if (!platform.isValid(true)) { return; } // Run test normally - don't pop up browser FileObject file = getCurrentFile(context); if (file == null) { return; } saveFile(file); // If we try to "test" a file that has a corresponding test file, // run/debug the test file instead // DeclarationLocation location = new GotoTest().findTest(file, -1); DeclarationLocation location = DeclarationLocation.NONE; if (location != DeclarationLocation.NONE) { file = location.getFileObject(); // Save the test file too saveFile(file); } boolean isDebug = COMMAND_DEBUG_TEST_SINGLE.equals(command); RSpecSupport rspec = new RSpecSupport(project); if (rspec.isRSpecInstalled() && RSpecSupport.isSpecFile(file)) { TestRunner rspecRunner = getTestRunner(TestRunner.TestType.RSPEC); if (rspecRunner != null) { rspecRunner.runTest(file, isDebug); } else { rspec.runRSpec(null, file, file.getName(), new RubyFileLocator(context, project), true, isDebug); } return; } TestRunner testRunner = getTestRunner(TestRunner.TestType.TEST_UNIT); if (testRunner != null) { testRunner.getInstance().runTest(file, isDebug); } else { runRubyScript(file, FileUtil.toFile(file).getAbsolutePath(), file.getNameExt(), context, isDebug, new TestNotifierLineConvertor(true, true)); } } if (COMMAND_TEST.equals(command)) { TestRunner testRunner = getTestRunner(TestRunner.TestType.TEST_UNIT); boolean testTaskExist = RakeSupport.getRakeTask(project, TEST_TASK_NAME) != null; if (testTaskExist) { File pwd = FileUtil.toFile(project.getProjectDirectory()); RakeRunner runner = new RakeRunner(project); runner.setPWD(pwd); runner.setFileLocator(new RubyFileLocator(context, project)); runner.showWarnings(true); runner.setDebug(COMMAND_DEBUG_SINGLE.equals(command)); runner.run(TEST_TASK_NAME); } else if (testRunner != null) { testRunner.getInstance().runAllTests(project, false); } return; } if (COMMAND_RSPEC.equals(command)) { boolean rspecTaskExists = RakeSupport.getRakeTask(project, RSPEC_TASK_NAME) != null; TestRunner testRunner = getTestRunner(TestRunner.TestType.RSPEC); if (rspecTaskExists) { File pwd = FileUtil.toFile(project.getProjectDirectory()); RakeRunner runner = new RakeRunner(project); runner.setPWD(pwd); runner.setFileLocator(new RubyFileLocator(context, project)); runner.showWarnings(true); runner.run(RSPEC_TASK_NAME); // NOI18N } else if (testRunner != null) { testRunner.getInstance().runAllTests(project, false); } return; } if (COMMAND_IRB_CONSOLE.equals(command)) { openIrbConsole(context); return; } if (COMMAND_DELETE.equals(command)) { DefaultProjectOperations.performDefaultDeleteOperation(project); return; } if (COMMAND_COPY.equals(command)) { DefaultProjectOperations.performDefaultCopyOperation(project); return; } if (COMMAND_MOVE.equals(command)) { DefaultProjectOperations.performDefaultMoveOperation(project); return; } if (COMMAND_RENAME.equals(command)) { DefaultProjectOperations.performDefaultRenameOperation(project, null); return; } } public boolean isActionEnabled( String command, Lookup context ) { if (getPlatform() == null) { return false; } if ( command.equals( COMMAND_COMPILE_SINGLE ) ) { return findSourcesAndPackages( context, project.getSourceRoots().getRoots()) != null || findSourcesAndPackages( context, project.getTestSourceRoots().getRoots()) != null; } else if (command.equals(COMMAND_RUN_SINGLE) || command.equals(COMMAND_DEBUG_SINGLE)) { if (RakeSupport.isRakeFileSelected(context)) { return true; } FileObject fos[] = findSources(context); if (fos != null && fos.length == 1) { return true; } fos = findTestSources(context); return fos != null && fos.length == 1; } else { // other actions are global return true; } } protected FileObject[] findSourcesAndPackages (Lookup context, FileObject srcDir) { if (srcDir != null) { FileObject[] files = findSelectedFiles(context, srcDir, null, true); // NOI18N //Check if files are either packages or Ruby files if (files != null) { for (int i = 0; i < files.length; i++) { if (!files[i].isFolder() && files[i].getMIMEType().equals(RubyInstallation.RUBY_MIME_TYPE)) { return null; } } } return files; } else { return null; } } private FileObject[] findSourcesAndPackages (Lookup context, FileObject[] srcRoots) { for (int i=0; i<srcRoots.length; i++) { FileObject[] result = findSourcesAndPackages(context, srcRoots[i]); if (result != null) { return result; } } return null; } // From the ant module - ActionUtils. // However, I've modified it to do its search based on mime type rather than // file suffixes (since some Ruby files do not use a .rb extension and are // discovered based on the initial shebang line) private File getSourceFolder() { // Default to using the project source directory FileObject[] srcPath = project.getSourceRoots().getRoots(); if (srcPath != null && srcPath.length > 0) { return FileUtil.toFile(srcPath[0]); } else { return FileUtil.toFile(project.getProjectDirectory()); } } private TestRunner getTestRunner(TestRunner.TestType testType) { Collection<? extends TestRunner> testRunners = Lookup.getDefault().lookupAll(TestRunner.class); for (TestRunner each : testRunners) { if (each.supports(testType)) { return each; } } return null; } private void runApp(boolean debug) { RubyPlatform platform = RubyPlatform.platformFor(project); final String merb = platform.findExecutable("merb"); //NOI18N String msgKey = debug ? "DebugMerbApp" : "RunMerbApp"; //NOI18N String displayName = NbBundle.getMessage(MerbProjectGenerator.class, msgKey, ProjectUtils.getInformation(project).getDisplayName()); File pwd = FileUtil.toFile(project.getProjectDirectory()); RubyExecutionDescriptor desc = new RubyExecutionDescriptor(platform, displayName, pwd, merb); String args = project.evaluator().getProperty(MerbProjectProperties.APPLICATION_ARGS); if (args != null) { desc.additionalArgs(args); } desc.debug(debug); RubyProcessCreator rpc = new RubyProcessCreator(desc); ExecutionService.newService(rpc, desc.toExecutionDescriptor(), displayName).run(); } }