add nextflow d30e48d
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package nextflow.ui.console
|
||||
|
||||
import java.util.jar.JarFile
|
||||
import java.util.zip.ZipException
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import nextflow.plugin.BasePlugin
|
||||
import org.codehaus.groovy.reflection.CachedClass
|
||||
import org.codehaus.groovy.reflection.ClassInfo
|
||||
import org.codehaus.groovy.runtime.m12n.ExtensionModuleScanner
|
||||
import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl
|
||||
import org.pf4j.PluginClassLoader
|
||||
import org.pf4j.PluginWrapper
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
/**
|
||||
* Nextflow plugin for Console extension
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
@CompileStatic
|
||||
class ConsolePlugin extends BasePlugin {
|
||||
|
||||
private static Logger log = LoggerFactory.getLogger(ConsolePlugin)
|
||||
|
||||
ConsolePlugin(PluginWrapper wrapper) {
|
||||
super(wrapper)
|
||||
}
|
||||
|
||||
@Override
|
||||
void start() {
|
||||
super.start()
|
||||
// The console UI requires some Groovy extensions method that are not loaded automatically
|
||||
// using the plugin system classloader.
|
||||
// see
|
||||
// - org.apache.groovy.swing.extensions.SwingExtensions
|
||||
// - META-INF/groovy/org.apache.groovy.runtime.ExtensionModule in the 'groovy-swing' JAR
|
||||
loadExtensions()
|
||||
}
|
||||
|
||||
protected void loadExtensions() {
|
||||
if( wrapper.getPluginClassLoader() !instanceof PluginClassLoader )
|
||||
return
|
||||
|
||||
final pcl = (PluginClassLoader) wrapper.getPluginClassLoader()
|
||||
for( URL it : pcl.getURLs() ) {
|
||||
log.trace "Checking console lib for groovy module extensions: $it"
|
||||
processCategoryMethods( wrapper.pluginClassLoader, new File(it.getFile()) )
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install extension method dynamically. Taken from {@link groovy.grape.GrapeIvy#processCategoryMethods(java.lang.ClassLoader, java.io.File)}
|
||||
*
|
||||
* @param loader Plugin class loader
|
||||
* @param file Extension library jar file
|
||||
*/
|
||||
private void processCategoryMethods(ClassLoader loader, File file) {
|
||||
// register extension methods if jar
|
||||
if (file.name.toLowerCase().endsWith('.jar')) {
|
||||
def mcRegistry = GroovySystem.metaClassRegistry
|
||||
if (mcRegistry instanceof MetaClassRegistryImpl) {
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
def entry = jar.getEntry(ExtensionModuleScanner.MODULE_META_INF_FILE)
|
||||
if (!entry) {
|
||||
entry = jar.getEntry(ExtensionModuleScanner.LEGACY_MODULE_META_INF_FILE)
|
||||
}
|
||||
if (entry) {
|
||||
Properties props = new Properties()
|
||||
|
||||
try (InputStream is = jar.getInputStream(entry)) {
|
||||
props.load(is)
|
||||
}
|
||||
|
||||
Map<CachedClass, List<MetaMethod>> metaMethods = new HashMap<CachedClass, List<MetaMethod>>()
|
||||
mcRegistry.registerExtensionModuleFromProperties(props, loader, metaMethods)
|
||||
// add old methods to the map
|
||||
metaMethods.each { CachedClass c, List<MetaMethod> methods ->
|
||||
// GROOVY-5543: if a module was loaded using grab, there are chances that subclasses
|
||||
// have their own ClassInfo, and we must change them as well!
|
||||
Set<CachedClass> classesToBeUpdated = [c].toSet()
|
||||
ClassInfo.onAllClassInfo { ClassInfo info ->
|
||||
if (c.theClass.isAssignableFrom(info.cachedClass.theClass)) {
|
||||
classesToBeUpdated << info.cachedClass
|
||||
}
|
||||
}
|
||||
classesToBeUpdated*.addNewMopMethods(methods)
|
||||
}
|
||||
}
|
||||
} catch (ZipException zipException) {
|
||||
throw new RuntimeException("Grape could not load jar '$file'", zipException)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package nextflow.ui.console
|
||||
|
||||
import javax.swing.UIManager
|
||||
import java.awt.Taskbar
|
||||
import java.awt.Toolkit
|
||||
|
||||
import groovy.util.logging.Slf4j
|
||||
import nextflow.cli.CliOptions
|
||||
import nextflow.util.LoggerHelper
|
||||
import org.codehaus.groovy.runtime.StackTraceUtils
|
||||
|
||||
/**
|
||||
* Implement the {@link ConsoleExtension} to launch the NF console app.
|
||||
*
|
||||
* See {@link nextflow.cli.CmdConsole#run()}
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
@Slf4j
|
||||
class ConsoleRunner implements ConsoleExtension {
|
||||
|
||||
/**
|
||||
* Nextflow REPL entry point
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
@Override
|
||||
void run(String... args) {
|
||||
CliOptions opts = new CliOptions()
|
||||
opts.logFile = '.nextflow-console.log'
|
||||
new LoggerHelper(opts).setup()
|
||||
|
||||
if (args.length == 2 && args[1] == '--help') {
|
||||
println 'usage: nextflow console [filename]'
|
||||
return
|
||||
}
|
||||
|
||||
// full stack trace should not be logged to the output window - GROOVY-4663
|
||||
java.util.logging.Logger.getLogger(StackTraceUtils.STACK_LOG_NAME).useParentHandlers = false
|
||||
|
||||
//when starting via main set the look and feel to system
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
|
||||
loadDockIcon()
|
||||
|
||||
final console = new Nextflow(ConsoleRunner.getClassLoader())
|
||||
console.useScriptClassLoaderForScriptExecution = true
|
||||
console.run()
|
||||
if (args.length == 2)
|
||||
try {
|
||||
console.loadScriptFile(args[1] as File)
|
||||
}
|
||||
catch( IOException e ) {
|
||||
log.warn("Can't open script file: ${args[1]}" )
|
||||
}
|
||||
}
|
||||
|
||||
static void loadDockIcon() {
|
||||
try {
|
||||
final URL imageResource = ConsoleRunner.getResource("/nextflow-icon.png");
|
||||
final defaultToolkit = Toolkit.getDefaultToolkit()
|
||||
final image = defaultToolkit.getImage(imageResource)
|
||||
final taskbar = Taskbar.getTaskbar()
|
||||
//set icon for mac os (and other systems which do support this method)
|
||||
taskbar.setIconImage(image)
|
||||
}
|
||||
catch (final UnsupportedOperationException e) {
|
||||
log.debug("Unable to config console icons [1] - cause: ${e.message}")
|
||||
}
|
||||
catch (final SecurityException e) {
|
||||
log.debug("Unable to config console icons [2] - cause: ${e.message}")
|
||||
}
|
||||
catch (Throwable e) {
|
||||
log.debug("Unable to configure console icon [3]", e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package nextflow.ui.console
|
||||
|
||||
import javax.swing.*
|
||||
import javax.swing.filechooser.FileFilter
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
import groovy.console.ui.Console
|
||||
import groovy.console.ui.OutputTransforms
|
||||
import groovy.transform.ThreadInterrupt
|
||||
import groovy.util.logging.Slf4j
|
||||
import nextflow.NF
|
||||
import nextflow.Session
|
||||
import nextflow.cli.CliOptions
|
||||
import nextflow.cli.CmdInfo
|
||||
import nextflow.cli.CmdRun
|
||||
import nextflow.config.ConfigBuilder
|
||||
import nextflow.script.ScriptBinding
|
||||
import nextflow.script.ScriptFile
|
||||
import nextflow.script.parser.v1.ScriptLoaderV1
|
||||
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
|
||||
/**
|
||||
* Implement a REPL console for Nextflow DSL based on Groovy console
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
@Slf4j
|
||||
class Nextflow extends Console {
|
||||
|
||||
static public final TITLE = 'Nextflow REPL console'
|
||||
|
||||
static {
|
||||
|
||||
Console.groovyFileFilter = new NextflowFileFilter()
|
||||
Console.frameConsoleDelegates = [
|
||||
rootContainerDelegate:{
|
||||
frame(
|
||||
title: TITLE,
|
||||
//location: [100,100], // in groovy 2.0 use platform default location
|
||||
iconImage: imageIcon('/nextflow-icon.png').image,
|
||||
defaultCloseOperation: JFrame.DISPOSE_ON_CLOSE,
|
||||
) {
|
||||
try {
|
||||
current.locationByPlatform = true
|
||||
} catch (Exception e) {
|
||||
current.location = [100, 100] // for 1.4 compatibility
|
||||
}
|
||||
containingWindows += current
|
||||
}
|
||||
},
|
||||
menuBarDelegate: {arg-> current.JMenuBar = build(arg)}
|
||||
];
|
||||
}
|
||||
|
||||
private Map scriptConfig
|
||||
|
||||
Nextflow(ClassLoader loader) {
|
||||
super(loader, new ScriptBinding())
|
||||
this.scriptConfig = createScriptConfig()
|
||||
}
|
||||
|
||||
protected Map createScriptConfig() {
|
||||
final script = scriptFile as Path
|
||||
final base = script ? script.parent : Paths.get('.')
|
||||
|
||||
// create the config object
|
||||
return new ConfigBuilder()
|
||||
.setOptions( new CliOptions() )
|
||||
.setBaseDir(base)
|
||||
.setCmdRun( new CmdRun() )
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Groovy Shell to interpret Nextflow scripts
|
||||
*
|
||||
* @param parent
|
||||
* @param binding
|
||||
*/
|
||||
@Override
|
||||
void newScript(ClassLoader parent, Binding binding) {
|
||||
assert parent
|
||||
|
||||
if( NF.getSyntaxParserVersion() != 'v1' )
|
||||
log.warn "The Nextflow console only supports the v1 syntax parser -- ignoring NXF_SYNTAX_PARSER setting"
|
||||
final parser = new ScriptLoaderV1(parent)
|
||||
config = parser.getConfig()
|
||||
|
||||
if (threadInterrupt)
|
||||
config.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt))
|
||||
|
||||
parser.setBinding((ScriptBinding)binding)
|
||||
shell = parser.getInterpreter()
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearContext(EventObject evt = null) {
|
||||
final binding = new ScriptBinding()
|
||||
newScript(null, binding)
|
||||
// reload output transforms
|
||||
binding.variables._outputTransforms = OutputTransforms.loadOutputTransforms()
|
||||
}
|
||||
|
||||
@Override
|
||||
void runScript(EventObject event = null) {
|
||||
runWith { super.runScript(event) }
|
||||
}
|
||||
|
||||
@Override
|
||||
void runSelectedScript(EventObject event = null) {
|
||||
runWith { super.runSelectedScript(event) }
|
||||
}
|
||||
|
||||
private runWith( Closure launcher ) {
|
||||
|
||||
def script = scriptFile ? new ScriptFile((File)scriptFile) : null
|
||||
def path = scriptFile as Path
|
||||
def session = new Session(scriptConfig).init(script)
|
||||
def binding = (ScriptBinding)shell.getContext()
|
||||
|
||||
binding.setSession(session)
|
||||
binding.setScriptPath(path)
|
||||
|
||||
beforeExecution = {
|
||||
session.start()
|
||||
}
|
||||
afterExecution = {
|
||||
session.fireDataflowNetwork()
|
||||
session.await()
|
||||
session.destroy()
|
||||
}
|
||||
|
||||
launcher.call()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the window title
|
||||
*/
|
||||
@Override
|
||||
void updateTitle() {
|
||||
if (frame.properties.containsKey('title')) {
|
||||
if (scriptFile != null) {
|
||||
frame.title = scriptFile.name + (dirty?' * ':'') + ' - ' + TITLE
|
||||
} else {
|
||||
frame.title = TITLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a customized about dialog box
|
||||
*
|
||||
* @param evt
|
||||
*/
|
||||
void showAbout(EventObject evt = null) {
|
||||
def pane = swing.optionPane()
|
||||
pane.setMessage('REPL Console for evaluating Nextflow scripts\n\n' + CmdInfo.getInfo(0))
|
||||
def dialog = pane.createDialog(frame, 'About ' + TITLE)
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
def finishNormal(Object result) {
|
||||
// Take down the wait/cancel dialog
|
||||
history[-1].result = result
|
||||
statusLabel.text = 'Execution complete.'
|
||||
|
||||
if( !visualizeScriptResults )
|
||||
return
|
||||
|
||||
if (result != null) {
|
||||
appendOutputNl('Result: ', promptStyle)
|
||||
def obj = OutputTransforms.transformResult(result, shell.context._outputTransforms)
|
||||
|
||||
// multi-methods are magical!
|
||||
appendOutput(obj, resultStyle)
|
||||
} else {
|
||||
statusLabel.text = 'Execution complete. Result was null.'
|
||||
}
|
||||
bindResults()
|
||||
if (detachedOutput) {
|
||||
prepareOutputWindow()
|
||||
showOutputWindow()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter supported script files in open dialog
|
||||
*/
|
||||
private static class NextflowFileFilter extends FileFilter {
|
||||
private static final SOURCE_EXTENSIONS = ['*.nf', '*.groovy']
|
||||
private static final SOURCE_EXT_DESC = SOURCE_EXTENSIONS.join(',')
|
||||
|
||||
boolean accept(File f) {
|
||||
if (f.isDirectory()) {
|
||||
return true
|
||||
}
|
||||
SOURCE_EXTENSIONS.find {it == getExtension(f)} ? true : false
|
||||
}
|
||||
|
||||
String getDescription() {
|
||||
"Nextflow scripts ($SOURCE_EXT_DESC)"
|
||||
}
|
||||
|
||||
static String getExtension(f) {
|
||||
def ext = null;
|
||||
def s = f.getName()
|
||||
def i = s.lastIndexOf('.')
|
||||
if (i > 0 && i < s.length() - 1) {
|
||||
ext = s.substring(i).toLowerCase()
|
||||
}
|
||||
"*$ext"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package nextflow.ui.console;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.CoreConstants;
|
||||
import ch.qos.logback.core.LayoutBase;
|
||||
|
||||
/**
|
||||
* Simplified logger layout used to output in the console window area
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
public class SimpleConsoleLayout extends LayoutBase<ILoggingEvent> {
|
||||
|
||||
@Override
|
||||
public String doLayout(ILoggingEvent event) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(128);
|
||||
if( event.getLevel() == Level.INFO ) {
|
||||
buffer .append(event.getFormattedMessage()) .append(CoreConstants.LINE_SEPARATOR);
|
||||
}
|
||||
|
||||
else {
|
||||
buffer
|
||||
.append( event.getLevel().toString() ) .append(": ")
|
||||
.append(event.getFormattedMessage())
|
||||
.append(CoreConstants.LINE_SEPARATOR);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
|
||||
}
|
||||
}
|
||||
BIN
nextflow/plugins/nf-console/src/resources/nextflow-icon.png
Normal file
BIN
nextflow/plugins/nf-console/src/resources/nextflow-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2013-2026, Seqera Labs
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package nextflow.ui.console
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
||||
*/
|
||||
class ConsoleRunnerTest extends Specification{
|
||||
|
||||
def 'should load nextflow icon' () {
|
||||
expect:
|
||||
ConsoleRunner.getResource("/nextflow-icon.png")
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user