diff --git a/src/_includes/cwl/expressions/custom-functions.js b/src/_includes/cwl/expressions/custom-functions.js new file mode 100644 index 00000000..6e9d05f7 --- /dev/null +++ b/src/_includes/cwl/expressions/custom-functions.js @@ -0,0 +1,18 @@ +/** + * 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(' '); +} diff --git a/src/_includes/cwl/expressions/hello-world-expressionlib-external.cwl b/src/_includes/cwl/expressions/hello-world-expressionlib-external.cwl new file mode 100644 index 00000000..9cc416af --- /dev/null +++ b/src/_includes/cwl/expressions/hello-world-expressionlib-external.cwl @@ -0,0 +1,16 @@ +cwlVersion: v1.2 +class: CommandLineTool +requirements: + - class: InlineJavascriptRequirement + expressionLib: + - { $include: custom-functions.js } + +baseCommand: echo + +inputs: + message: + type: string + +arguments: [$( capitalizeWords(inputs.message) )] + +outputs: [] diff --git a/src/_includes/cwl/expressions/hello-world-expressionlib-inline.cwl b/src/_includes/cwl/expressions/hello-world-expressionlib-inline.cwl new file mode 100644 index 00000000..113b65f7 --- /dev/null +++ b/src/_includes/cwl/expressions/hello-world-expressionlib-inline.cwl @@ -0,0 +1,34 @@ +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: [] diff --git a/src/_includes/cwl/expressions/hello-world-expressionlib.cwl b/src/_includes/cwl/expressions/hello-world-expressionlib.cwl new file mode 100644 index 00000000..21ea73a2 --- /dev/null +++ b/src/_includes/cwl/expressions/hello-world-expressionlib.cwl @@ -0,0 +1,27 @@ +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: [] diff --git a/src/topics/expressions.md b/src/topics/expressions.md index 31b3ee9e..eebc027b 100644 --- a/src/topics/expressions.md +++ b/src/topics/expressions.md @@ -11,6 +11,7 @@ When manipulating file names, extensions, paths etc, consider whether one of the [built in `File` properties][file-prop] like `basename`, `nameroot`, `nameext`, etc, could be used instead. See the [list of best practices](best-practices.md). +``` ```{literalinclude} /_includes/cwl/expressions/expression.cwl :language: cwl @@ -112,7 +113,91 @@ only in certain fields. These are: [file-prop]: https://www.commonwl.org/v1.0/CommandLineTool.html#File +## Using external libraries and inline JavaScript code with `expressionLib` + +The requirement `InlineJavascriptRequirement` supports an `expressionLib` attribute +that allows users to load external JavaScript files, or to provide inline JavaScript +code. + +Entries added to the `expressionLib` attribute are parsed with the JavaScript engine +of a CWL runner. This can be used to include external files or to create JavaScript +functions that can be called in other parts of the CWL document. + +```{note} + +The CWL standards (versions 1.0 through 1.2) [states](https://www.commonwl.org/v1.0/CommandLineTool.html#Expressions) + that the only version of JavaScript valid in CWL expressions is +[ECMAScript 5.1](https://262.ecma-international.org/5.1/). This means that any +code that you include or write in your CWL Document must be compliant with +ECMAScript 5.1. +``` + +For example, we can use `InlineJavascriptRequirement` and write a JavaScript function +inline in `expressionLib`. That function can then be used in other parts of the +CWL document: + +```{literalinclude} /_includes/cwl/expressions/hello-world-expressionlib-inline.cwl +:language: cwl +:caption: "`hello-world-expressionlib-inline.cwl`" +:name: "`hello-world-expressionlib-inline.cwl`" +:emphasize-lines: 5, 14, 32 +``` + +Running this CWL workflow will invoke the JavaScript function and result in +the `echo` command printing the input message with capital initial letters: + +```{runcmd} cwltool hello-world-expressionlib-inline.cwl --message "hello world" +:caption: "Running `hello-world-expressionlib-inline.cwl`." +:name: running-hell-world-expressionlib-inline-cwl +:working-directory: src/_includes/cwl/expressions/ +``` + +Let's move the `capitalizeWords` function to an external file, `custom-functions.js`, and +import it in our CWL document: + +```{literalinclude} /_includes/cwl/expressions/custom-functions.js +:language: javascript +:caption: "`custom-functions.js`" +:name: "`custom-functions.js`" +``` + +```{literalinclude} /_includes/cwl/expressions/hello-world-expressionlib-external.cwl +:language: cwl +:caption: "`hello-world-expressionlib-external.cwl`" +:name: "`hello-world-expressionlib-external.cwl`" +:emphasize-lines: 5-6, 14 +``` + +The `custom-functions.js` file is included in the CWL document with the `$include: custom-functions.js` +statement. That makes the functions and variables available to be used in other parts of +the CWL document. + +```{runcmd} cwltool hello-world-expressionlib-external.cwl --message "hello world" +:caption: "Running `hello-world-expressionlib-external.cwl`." +:name: running-hell-world-expressionlib-external-cwl +:working-directory: src/_includes/cwl/expressions/ +``` -% TODO -% - (maybe not before other concepts? move this to after inputs/outputs/etc?) -% - External libraries and expressionLib - https://github.com/common-workflow-language/user_guide/issues/126 +Finally, note that you can have both inline and external JavaScript code in your +CWL document. In this final example we have added another entry to the `expressionLib` +attribute with the new function `createHelloWorldMessage`, that calls the `capitalizeWords` +function from the external file `custom-functions.js`. + +```{literalinclude} /_includes/cwl/expressions/hello-world-expressionlib.cwl +:language: cwl +:caption: "`hello-world-expressionlib.cwl`" +:name: "`hello-world-expressionlib.cwl`" +:emphasize-lines: 5-17, 25 +``` + +```{runcmd} cwltool hello-world-expressionlib.cwl --message "hello world" +:caption: "Running `hello-world-expressionlib.cwl`." +:name: running-hell-world-expressionlib-cwl +:working-directory: src/_includes/cwl/expressions/ +``` + +```{note} +The `$include` statement can be used to include a file from the local disk or from a remote location. +It works with both relative and absolute paths. Read the [text about `$include`](https://www.commonwl.org/v1.0/SchemaSalad.html#Include) +from the CWL specification to learn more about it. +```