2.3. 表达式#

If you need to manipulate input parameters, include the requirement InlineJavascriptRequirement and then anywhere a parameter reference is legal you can provide a fragment of Javascript that will be evaluated by the CWL runner.

重要

JavaScript 表达式应当仅在绝对必要时使用。要处理文件名、扩展名、路径等,请优先考虑能否使用 basenamenamerootnameextFile 内置属性。对此,请参阅优良习惯一览

expression.cwl#
#!/usr/bin/env cwl-runner
cwlVersion: v1.2
class: CommandLineTool
baseCommand: echo

requirements:
  InlineJavascriptRequirement: {}

inputs: []
outputs:
  example_out:
    type: stdout
stdout: output.txt
arguments:
  - prefix: -A
    valueFrom: $(1+1)
  - prefix: -B
    valueFrom: $("/foo/bar/baz".split('/').slice(-1)[0])
  - prefix: -C
    valueFrom: |
      ${
        var r = [];
        for (var i = 10; i >= 1; i--) {
          r.push(i);
        }
        return r;
      }

由于此工具不需要任何 inputs(输入),因此运行的时候可以使用一个几乎为空的作业文件:

empty.yml#
{}

empty.yml 的内容是一个空 JSON 对象的描述。JSON 对象描述是用大括号 {} 围起来的,因此,表示空对象仅需一对空括号就够了。

然后我们可以运行 expression.cwl:

运行 expression.cwl#
$ cwltool expression.cwl empty.yml
INFO /home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/envs/latest/bin/cwltool 3.1.20240508115724
INFO Resolved 'expression.cwl' to 'file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/expression.cwl'
INFO [job expression.cwl] /tmp/tfw_58kp$ echo \
    -A \
    2 \
    -B \
    baz \
    -C \
    10 \
    9 \
    8 \
    7 \
    6 \
    5 \
    4 \
    3 \
    2 \
    1 > /tmp/tfw_58kp/output.txt
INFO [job expression.cwl] completed success
{
    "example_out": {
        "location": "file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/output.txt",
        "basename": "output.txt",
        "class": "File",
        "checksum": "sha1$a739a6ff72d660d32111265e508ed2fc91f01a7c",
        "size": 36,
        "path": "/home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/output.txt"
    }
}INFO Final process status is success
$ cat output.txt
-A 2 -B baz -C 10 9 8 7 6 5 4 3 2 1

请注意,如上例所示,requirements(要求)可通过映射语法来提供:

requirements:
  InlineJavascriptRequirement: {}

也可以作为一个数组,每个元素均用 - 标记(本例中只有一个 class: InlineJavascriptRequirement ),就和描述额外命令行参数的语法一样。

requirements:
  - class: InlineJavascriptRequirement

哪里可以使用 JavaScript 表达式?

JavaScript 表达式如同参数引用一样,只能用于某些特定字段,即:

2.3.1. 通过 expressionLib 使用外部库和内联 JavaScript 代码#

The requirement InlineJavascriptRequirement supports an expressionLib attribute that allows users to load external JavaScript files, or to provide inline JavaScript code.

添加到 expressionLib 属性的条目将由 CWL 运行程序的 JavaScript 引擎进行语法分析。这可以用来纳入外部文件中的代码,或创建可在 CWL 文件其他部分调用的 JavaScript 函数。

备注

版本 1.0 到 1.2 的 CWL 标准中写道,在 CWL 表达式中唯一有效的 JavaScript 版本是 ECMAScript 5.1 . 也就是说,在 CWL 文件中纳入或写入的所有 JS 代码都必须符合该版本。

例如,我们可以使用 InlineJavascriptRequirements, 在 expressionLib 下编写内联 JavaScript 函数,让它能在 CWL 文件其他部分调用:

hello-world-expressionlib-inline.cwl#
cwlVersion: v1.2
class: CommandLineTool
requirements:
  - class: InlineJavascriptRequirement
    expressionLib:
      - |
        /**
         * Capitalize each word passed. Will split the text by spaces.
         * For instance, given "hello world", it returns "Hello World".
         *
         * @param {String} message - The input message.
         * @return {String} the message with each word with its initial letter capitalized.
         */
        function capitalizeWords (message) {
          if (message === undefined || message === null || typeof message !== 'string' || message.trim().length === 0) {
            return '';
          }
          return message
            .split(' ')
            .map(function (token) {
              return token.charAt(0).toUpperCase() + token.slice(1);
            })
            .join(' ');
        }

baseCommand: echo

inputs:
  message:
    type: string

arguments: [$( capitalizeWords(inputs.message) )]

outputs: []

运行此 CWL 工作流将调用这个 JavaScript 函数,使得 echo 命令以大写首字母排印输入的“报文”:

运行 hello-world-expressionlib-inline.cwl.#
$ cwltool hello-world-expressionlib-inline.cwl --message "hello world"
INFO /home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/envs/latest/bin/cwltool 3.1.20240508115724
INFO Resolved 'hello-world-expressionlib-inline.cwl' to 'file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/hello-world-expressionlib-inline.cwl'
INFO [job hello-world-expressionlib-inline.cwl] /tmp/gxebkv_i$ echo \
    'Hello World'
Hello World
INFO [job hello-world-expressionlib-inline.cwl] completed success
{}INFO Final process status is success

我们来将 capitalizeWords 函数移动到外部文件 custom-functions.js, 并将它导入我们的 CWL 文件:

custom-functions.js#
/**
 * Capitalize each word passed. Will split the text by spaces.
 * For instance, given "hello world", it returns "Hello World".
 *
 * @param {String} message - The input message.
 * @return {String} the message with each word with its initial letter capitalized.
 */
function capitalizeWords (message) {
  if (message === undefined || message === null || typeof message !== 'string' || message.trim().length === 0) {
    return '';
  }
  return message
    .split(' ')
    .map(function (token) {
      return token.charAt(0).toUpperCase() + token.slice(1);
    })
    .join(' ');
}
hello-world-expressionlib-external.cwl#
cwlVersion: v1.2
class: CommandLineTool
requirements:
  - class: InlineJavascriptRequirement
    expressionLib:
      - { $include: custom-functions.js }

baseCommand: echo

inputs:
  message:
    type: string

arguments: [$( capitalizeWords(inputs.message) )]

outputs: []

文件 custom-functions.js 通过 $include: custom-functions.js 语句纳入到这个 CWL 文件里,使这个 JavaScript 文件中定义的函数和变量可在 CWL 文件其他部分使用。

运行 hello-world-expressionlib-external.cwl.#
$ cwltool hello-world-expressionlib-external.cwl --message "hello world"
INFO /home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/envs/latest/bin/cwltool 3.1.20240508115724
INFO Resolved 'hello-world-expressionlib-external.cwl' to 'file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/hello-world-expressionlib-external.cwl'
INFO [job hello-world-expressionlib-external.cwl] /tmp/tj95w76o$ echo \
    'Hello World'
Hello World
INFO [job hello-world-expressionlib-external.cwl] completed success
{}INFO Final process status is success

最后请注意,CWL 文件中可以同时运用内联和外部 JavaScript 代码。下面是最后一个例子,这里我们向 expressionLib 属性中加入新函数 createHelloWorldMessage, 调用外部文件 custom-functions.js 中的 capitalizeWords 函数。

hello-world-expressionlib.cwl#
cwlVersion: v1.2
class: CommandLineTool
requirements:
  - class: InlineJavascriptRequirement
    expressionLib:
      - { $include: custom-functions.js }
      - |
        /**
         * A merely illustrative example function that uses a function
         * from the included custom-functions.js file to create a
         * Hello World message.
         *
         * @param {Object} message - CWL document input message
         */
        var createHelloWorldMessage = function (message) {
          return capitalizeWords(message);
        };

baseCommand: echo

inputs:
  message:
    type: string

arguments: [$( createHelloWorldMessage(inputs.message) )]

outputs: []
运行 hello-world-expressionlib.cwl.#
$ cwltool hello-world-expressionlib.cwl --message "hello world"
INFO /home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/envs/latest/bin/cwltool 3.1.20240508115724
INFO Resolved 'hello-world-expressionlib.cwl' to 'file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/hello-world-expressionlib.cwl'
INFO [job hello-world-expressionlib.cwl] /tmp/g2oxtegg$ echo \
    'Hello World'
Hello World
INFO [job hello-world-expressionlib.cwl] completed success
{}INFO Final process status is success

备注

$include 语句可以用来添加位于本地或远程的文件,并且接受相对路径和绝对路径。详情请参见 CWL 规约中关于 $include 的内容。