DSL 2
Nextflow提供了一个语法扩展,它实现了模块库的定义,并简化了复杂数据分析管道的编写。
要启用这个特性,你需要在你的工作流脚本的开头定义以下指令:
nextflow.enable.dsl=2
Function
Nextflow允许在工作流脚本中使用以下语法定义自定义函数:
def <function name> ( arg1, arg, .. ) {
<function body>
}
def foo() {
'Hello world'
}
def bar(alpha, omega) {
alpha + omega
}
上面的代码片段定义了两个简单的函数,可以在工作流脚本中调用它们,foo()返回Hello world字符串,bar(10,20)返回两个参数的和。
Tip
函数隐式返回最后一个求值语句的结果。
关键字return可用于显式退出返回指定值的函数。例如:
def fib( x ) {
if( x <= 1 )
return x
else
fib(x-1) + fib(x-2)
}
Process
Process定义
新的DSL将流程的定义与其调用分开。流程定义遵循流程文档中描述的常用语法。唯一的区别是必须省略from和into通道声明。
然后,可以将流程作为workflow作用域中的函数调用,将预期的输入通道作为参数传递,就像它是一个自定义函数一样。例如:
nextflow.enable.dsl=2
process foo {
output:
path 'foo.txt'
script:
"""
your_command > foo.txt
"""
}
process bar {
input:
path x
output:
path 'bar.txt'
script:
"""
another_command $x > bar.txt
"""
}
workflow {
data = channel.fromPath('/some/path/*.txt')
foo()
bar(data)
}
Warning
流程组件在相同的工作流上下文中只能被调用一次。
Process合成
可以组合具有匹配输入-输出声明的流程,以便将第一个流程的输出作为输入传递给下一个流程。考虑到前面的过程定义,可以这样写:
workflow {
bar(foo())
}
流程输出
还可以使用各自流程对象的out属性访问流程输出。例如:
workflow {
foo()
bar(foo.out)
bar.out.view()
}
当进程定义两个或多个输出通道时,可以使用数组元素操作符(例如out[0]、out[1]等)或使用命名输出(见下文)来访问每一个输出通道。
流程输出命名
流程输出定义允许使用emit选项来定义一个名称标识符,该标识符可用于在外部作用域中引用通道。例如:
process foo {
output:
path '*.bam', emit: samples_bam
'''
your_command --here
'''
}
workflow {
foo()
foo.out.samples_bam.view()
}
流程标准输出命名
进程可以使用emit选项命名标准输出:
process sayHello {
input:
val cheers
output:
stdout emit: verbiage
script:
"""
echo -n $cheers
"""
}
workflow {
things = channel.of('Hello world!', 'Yo, dude!', 'Duck!')
sayHello(things)
sayHello.out.verbiage.view()
}
工作流
工作流定义
workflow关键字允许定义包含调用一个或多个流程和操作符的子工作流组件:
workflow my_pipeline {
foo()
bar( foo.out.collect() )
}
例如,上面的代码片段定义了一个名为my_pipeline的工作流组件,可以从另一个工作流组件定义中调用该组件作为任何其他函数或流程,例如my_pipeline()。
工作流参数
工作流组件可以访问外部作用域中定义的任何变量和参数:
params.data = '/some/data/file'
workflow my_pipeline {
if( params.data )
bar(params.data)
else
bar(foo())
}
工作流输入
工作流组件可以使用take关键字声明一个或多个输入通道。例如:
workflow my_pipeline {
take: data
main:
foo(data)
bar(foo.out)
}
Warning
当使用take关键字时,需要用main关键字标识工作流主体的开始部分。
然后,可以在工作流调用语句中将输入指定为参数:
workflow {
my_pipeline( channel.from('/some/data') )
}
Note
工作流输入定义为通道数据结构。如果提供了基本的数据类型,例如。数字、字符串、列表等。
工作流输出
工作流组件可以使用emit关键字声明一个或多个输出通道。例如:
workflow my_pipeline {
main:
foo(data)
bar(foo.out)
emit:
bar.out
}
然后,可以使用out属性访问my_pipeline执行的结果,例如:my_pipeline.out。当声明了多个输出通道时,按照Process输出定义的描述,使用数组括号符号来访问每个输出组件。
或者,可以使用在emit声明中赋值的标识符名称访问输出通道:
workflow my_pipeline {
main:
foo(data)
bar(foo.out)
emit:
my_data = bar.out
}
然后,可以使用my_pipeline.out.my_data访问上述代码片段的结果。
隐式工作流
没有声明任何名称的工作流定义被假定为主工作流,并隐式执行。因此,它是工作流应用程序的入口点。
Note
当脚本作为模块包含时,隐式工作流定义将被忽略。这允许编写工作流脚本,它既可以用作库模块,也可以用作应用程序脚本。
Tip
另一个工作流条目可以使用-entry命令行选项指定。
工作流组合
在您的脚本中定义的工作流或由模块包含导入的工作流可以作为应用程序中的任何其他流程调用和组合。
workflow flow1 {
take: data
main:
foo(data)
bar(foo.out)
emit:
bar.out
}
workflow flow2 {
take: data
main:
foo(data)
baz(foo.out)
emit:
baz.out
}
workflow {
take: data
main:
flow1(data)
flow2(flow1.out)
}
Note
嵌套的工作流执行确定了隐式作用域。因此,相同的流程可以在两个不同的工作流作用域中调用,例如上面的代码片段中的foo在flow1和flow2中使用。工作流执行路径以及进程名定义了进程的完全限定名,用来区分两个不同的进程调用,如上面例子中的flow1:foo和flow2:foo。
Tip
流程完全限定名可以用作下一个流中的有效流程选择器。配置文件,它的优先级高于进程简单名称。
模块
新的DSL允许在工作流应用程序之间包含和共享定义模块脚本。
模块可以包含上述部分所述的功能、过程和工作流定义。
模块的include
模块脚本中定义的组件可以使用include关键字导入到另一个Nextflow脚本中。
例如:
include { foo } from './some/module'
workflow {
data = channel.fromPath('/some/data/*.txt')
foo(data)
}
上面的代码片段包括一个名为foo的进程,它在主执行上下文中的模块脚本中定义,因此它可以在工作流范围内调用。
Nextflow隐式查找脚本文件./some/module.nf,根据包含的脚本位置解析路径。
Note
相对路径必须以./前缀开头。
多个include
Nextflow脚本允许包含任意数量的模块。当需要从某个模块脚本中包含多个组件时,可以在同一个包含中使用花括号符号指定组件名称,如下所示:
include { foo; bar } from './some/module'
workflow {
data = channel.fromPath('/some/data/*.txt')
foo(data)
bar(data)
}
模块别名
当包含一个模块组件时,可以指定一个名称别名。这允许在脚本中使用不同的名称多次包含和调用相同的组件。例如:
include { foo } from './some/module'
include { foo as bar } from './other/module'
workflow {
foo(some_data)
bar(other_data)
}
当包括来自相同模块脚本的多个组件时,情况也是一样的,如下所示:
include { foo; foo as bar } from './some/module'
workflow {
foo(some_data)
bar(other_data)
}
模块参数
模块脚本可以使用与Nextflow工作流脚本相同的语法定义一个或多个参数:
params.foo = 'Hello'
params.bar = 'world!'
def sayHello() {
println "$params.foo $params.bar"
}
然后,从包含上下文继承参数。例如:
params.foo = 'Hola'
params.bar = 'Mundo'
include {sayHello} from './some/module'
workflow {
sayHello()
}
上面的代码片段打印:
Hola Mundo
Note
该模块继承include语句之前定义的参数(即截止调用这个模块,使用最新定义的参数),因此以后设置的任何参数都将被忽略。
Tip
在脚本的开头,在任何include声明之前定义所有管道参数
addParams选项可用于扩展模块参数而不影响外部作用域。例如:
include {sayHello} from './some/module' addParams(foo: 'Ciao')
workflow {
sayHello()
}
上面的代码片段打印:
Ciao world!
最后,include选项参数允许指定一个或多个参数,而不从外部环境继承任何值。
通道分叉
使用新的DSL,当连接两个或多个consumers时,Nextflow通道将自动分叉。
Channel
.from('Hello','Hola','Ciao')
.set{ cheers }
cheers
.map{ it.toUpperCase() }
.view()
cheers
.map{ it.reverse() }
.view()
这同样适用于流程执行的结果(通道)。因此,流程输出可以由两个或多个流程使用,而不需要使用into操作符将它们派生出来,从而使工作流脚本的编写更加流畅和可读。
管道
管道操作符
Nextflow流程和操作符可以使用|管道操作符组合。例如:
process foo {
input: val data
output: val result
exec:
result = "$data world"
}
workflow {
channel.from('Hello','Hola','Ciao') | foo | map { it.toUpperCase() } | view
}
与操作符
&和操作符允许输入两个或多个具有相同通道内容的进程,例如:
process foo {
input: val data
output: val result
exec:
result = "$data world"
}
process bar {
input: val data
output: val result
exec:
result = data.toUpperCase()
}
workflow {
channel.from('Hello') | map { it.reverse() } | (foo & bar) | mix | view
}
在上面的代码片段中,发送Hello的通道使用了映射管道,该映射可以反转字符串值。然后,结果被传递给并行执行的foo和bar进程。结果是一对通道,使用mix操作符将其内容合并到单个通道中。最后,使用视图操作符打印结果。
Tip
break-line操作符\可用于在多行上分割长管道连接。
上面的代码片段可以写成如下所示:
workflow {
channel.from('Hello') \
| map { it.reverse() } \
| (foo & bar) \
| mix \
| view
}