雑に始める CWL！

# はじめに

真面目に CWL を書いてみたい場合には、公式で提供している [User Guide](https://www.commonwl.org/user_guide/) や、[日本の CWL コミュニティが公開している CWL ファイル](https://github.com/pitagora-galaxy/cwl)が参考になると思います。

この記事ではそうではなく、「CWL の細かい文法とかどうでもいい。とりあえず今動いてるツールをそれっぽく CWL 化して CWL のワークフローから使えるようにしたい」という方向けに、できるだけ細かい文法要素に踏み込まずにツールの定義ファイルを CWL で書く例を示します。

# 注意
- 本記事では、ツールのオプションをうまいこと扱う方法については書きません。様々なオプションを入力パラメータとして与えたい場合には、まじめに CWL を書きましょう。

# CWL 化したいツール

以下のツールの定義ファイルを CWL で書くことを考えます。
このコマンドは、引数で与えられたファイルの先頭数行を、標準出力に表示します。
出力する行数は `-n` で与えられます。

```console
$ head -n5 foobar.txt
```

- `-n5` の `5` と、`foobar.txt` を入力パラメータとして与えたい。
- `head` コマンドの標準出力を、ツールの出力オブジェクトとして取得できるようにしたい。
- 他のオプションは知らぬ。

## ツール自体の動作サンプル

```console
$ man head > manhead.txt
$ head -n5 manhead.txt

HEAD(1)                   BSD General Commands Manual                  HEAD(1)

NAME
     head -- display first lines of a file
```

# 作成

- まず以下を `head.cwl` としてコピーします。

```yaml:head.cwl
cwlVersion: v1.0
class: CommandLineTool
baseCommand: []
arguments: []
inputs: []
outputs: []
```

- `baseCommand` に、先程のコマンドの先頭部分、それ以降を `arguments` に追加します。

```yaml:head.cwl
cwlVersion: v1.0
class: CommandLineTool
baseCommand: [head]
arguments: [-n5, foobar.txt]
inputs: []
outputs: []
```

- 入力パラメータを `inputs` に追加します。今回は表示したいファイルである `source`、先頭 `n` 行を表す `nlines` を導入します。

```yaml:head.cwl
cwlVersion: v1.0
class: CommandLineTool
baseCommand: [head]
arguments: [-n5, foobar.txt]
inputs:
  - id: source
  - id: nlines
outputs: []
```

- 各入力パラメータに型を追加します。`source` はファイルなので `File`、`nlines` は整数値を取るので `int` です。

```yaml:head.cwl
cwlVersion: v1.0
class: CommandLineTool
baseCommand: [head]
arguments: [-n5, foobar.txt]
inputs:
  - id: source
    type: File
  - id: nlines
    type: int
outputs: []
```

- `arguments` に `5` や `foobar.txt` が残ったままなので、それぞれ `nlines` と `source` の値に置き換えるようにします。これは `$(inputs.idフィールド名)` という記法で実現できます。

```yaml:head.cwl
cwlVersion: v1.0
class: CommandLineTool
baseCommand: [head]
arguments: [-n$(inputs.nlines), $(inputs.source)]
inputs:
  - id: source
    type: File
  - id: nlines
    type: int
outputs: []
```

- 出力パラメータを追加します。今回はとりあえず `out` という名前をつけます。

```yaml:head.cwl
cwlVersion: v1.0
class: CommandLineTool
baseCommand: [head]
arguments: [-n$(inputs.nlines), $(inputs.source)]
inputs:
  - id: source
    type: File
  - id: nlines
    type: int
outputs:
  - id: out
```

- 入力パラメータの時と同様に、出力パラメータに型を付けます。標準出力を取得したい場合には `stdout` 型を使えばいいです。


```yaml:head.cwl
cwlVersion: v1.0
class: CommandLineTool
baseCommand: [head]
arguments: [-n$(inputs.nlines), $(inputs.source)]
inputs:
  - id: source
    type: File
  - id: nlines
    type: int
outputs:
  - id: out
    type: stdout
```

- できあがり！

# 動かしてみる

試しに `source` が `manhead.txt`、`nlines` が `5` の場合を試してみます。

```console
$ man head > manhead.txt
$ cwltool head.cwl --source manhead.txt --nlines 5
...
[job head.cwl] completed success
{
    "out": {
        "location": "file:///Users/tom-tan/147d6ad946430f35c2aafbff6c5604d43b30aa14",
        "basename": "147d6ad946430f35c2aafbff6c5604d43b30aa14",
        "class": "File",
        "checksum": "sha1$30c2a20de147871db76e237705ac274277504428",
        "size": 145,
        "path": "/Users/tom-tan/147d6ad946430f35c2aafbff6c5604d43b30aa14"
    }
}
Final process status is success
$ cat 147d6ad946430f35c2aafbff6c5604d43b30aa14

HEAD(1)                   BSD General Commands Manual                  HEAD(1)

NAME
     head -- display first lines of a file
```

- `[job head.cwl] completed success` 以下に、出力オブジェクトが表示されます。
    - 今回の場合、`out` として `147d6ad9464...` という名前のファイルオブジェクトがあるのがわかります。
- 実際に `cat` コマンドで確認すると、[ツール自体の動作サンプル](#ツール自体の動作サンプル)と同様の内容が得られたことがわかります。

# 次のステップ
## 出力ファイル名がひどい
`stdout` フィールドで標準出力を保存するファイル名を指定できます。

```yaml:head.cwl
...
outputs:
  - id: out
    type: stdout
stdout: output.txt # この行を追加
```

## `source` のファイル名が `manhead.txt` の時には、出力ファイル名を `manhead-head.txt` にしたい
`$()` 記法を活用すればいいです。`File` オブジェクトの `nameroot` フィールドを使うことで、`manhead.txt` から `manhead` のみを取り出すことができます。

```yaml:head.cwl
...
outputs:
  - id: out
    type: stdout
stdout: $(inputs.source.nameroot)-head.txt # この行を変更
```

## Docker で動かしたい

`DockerRequirement` というものを使います。また`dockerPull` で、利用したい docker イメージ名を指定します。

```yaml:head.cwl
...
outputs:
  - id: out
    type: stdout
stdout: $(inputs.source.nameroot)-head.txt
# 以下を追記
requirements:
  - class: DockerRequirement
    dockerPull: debian:latest
```

volume mount などの細かい部分は、CWL の処理系がうまいことやってくれます。

# おわり
今回はツール定義を CWL で書く方法を示しました。
これで CWL 書き放題ですね！！！

# おまけ
[続編](https://qiita.com/tm_tn/items/83ce4c826135d78ba98f)書きました。