From ad3447012bb63799c53f2dbbcf68dfdec4cb3079 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Thu, 1 Feb 2024 10:52:07 +0000 Subject: [PATCH 1/3] Template update for nf-core/tools version 2.12.1 --- .devcontainer/devcontainer.json | 28 + .editorconfig | 32 + .gitattributes | 3 + .github/.dockstore.yml | 6 + .github/CONTRIBUTING.md | 90 +- .github/ISSUE_TEMPLATE/bug_report.md | 42 - .github/ISSUE_TEMPLATE/bug_report.yml | 50 ++ .github/ISSUE_TEMPLATE/config.yml | 7 + .github/ISSUE_TEMPLATE/feature_request.md | 24 - .github/ISSUE_TEMPLATE/feature_request.yml | 11 + .github/PULL_REQUEST_TEMPLATE.md | 25 +- .github/markdownlint.yml | 5 - .github/workflows/awsfulltest.yml | 39 + .github/workflows/awstest.yml | 33 + .github/workflows/branch.yml | 40 +- .github/workflows/ci.yml | 51 +- .github/workflows/clean-up.yml | 24 + .github/workflows/download_pipeline.yml | 67 ++ .github/workflows/fix-linting.yml | 89 ++ .github/workflows/linting.yml | 75 +- .github/workflows/linting_comment.yml | 28 + .github/workflows/release-announcements.yml | 68 ++ .gitignore | 2 +- .gitpod.yml | 22 + .nf-core.yml | 1 + .pre-commit-config.yaml | 10 + .prettierignore | 12 + .prettierrc.yml | 1 + CHANGELOG.md | 6 +- CITATIONS.md | 41 + CODE_OF_CONDUCT.md | 186 +++- Dockerfile | 13 - LICENSE | 2 +- README.md | 109 ++- assets/adaptivecard.json | 67 ++ assets/email_template.html | 5 +- assets/email_template.txt | 3 +- assets/methods_description_template.yml | 29 + assets/multiqc_config.yaml | 11 - assets/multiqc_config.yml | 13 + assets/nf-core-phaseimpute_logo.png | Bin 10842 -> 0 bytes assets/nf-core-phaseimpute_logo_light.png | Bin 0 -> 107250 bytes assets/samplesheet.csv | 3 + assets/schema_input.json | 36 + assets/sendmail_template.txt | 40 +- assets/slackreport.json | 34 + bin/check_samplesheet.py | 259 ++++++ bin/markdown_to_html.py | 100 --- bin/scrape_software_versions.py | 52 -- conf/base.config | 102 ++- conf/igenomes.config | 850 +++++++++--------- conf/modules.config | 50 ++ conf/test.config | 45 +- conf/test_full.config | 24 + docs/README.md | 16 +- docs/images/mqc_fastqc_adapter.png | Bin 0 -> 23458 bytes docs/images/mqc_fastqc_counts.png | Bin 0 -> 33918 bytes docs/images/mqc_fastqc_quality.png | Bin 0 -> 55769 bytes docs/images/nf-core-phaseimpute_logo.png | Bin 16403 -> 0 bytes docs/images/nf-core-phaseimpute_logo_dark.png | Bin 0 -> 27846 bytes .../images/nf-core-phaseimpute_logo_light.png | Bin 0 -> 23674 bytes docs/output.md | 72 +- docs/usage.md | 375 +++----- environment.yml | 15 - lib/NfcoreTemplate.groovy | 356 ++++++++ lib/Utils.groovy | 47 + lib/WorkflowMain.groovy | 77 ++ lib/WorkflowPhaseimpute.groovy | 122 +++ main.nf | 456 ++-------- modules.json | 27 + modules/local/samplesheet_check.nf | 31 + .../dumpsoftwareversions/environment.yml | 7 + .../custom/dumpsoftwareversions/main.nf | 24 + .../custom/dumpsoftwareversions/meta.yml | 37 + .../templates/dumpsoftwareversions.py | 102 +++ .../dumpsoftwareversions/tests/main.nf.test | 43 + .../tests/main.nf.test.snap | 33 + .../dumpsoftwareversions/tests/tags.yml | 2 + modules/nf-core/fastqc/environment.yml | 7 + modules/nf-core/fastqc/main.nf | 55 ++ modules/nf-core/fastqc/meta.yml | 57 ++ modules/nf-core/fastqc/tests/main.nf.test | 212 +++++ .../nf-core/fastqc/tests/main.nf.test.snap | 20 + modules/nf-core/fastqc/tests/tags.yml | 2 + modules/nf-core/multiqc/environment.yml | 7 + modules/nf-core/multiqc/main.nf | 55 ++ modules/nf-core/multiqc/meta.yml | 58 ++ modules/nf-core/multiqc/tests/main.nf.test | 83 ++ .../nf-core/multiqc/tests/main.nf.test.snap | 21 + modules/nf-core/multiqc/tests/tags.yml | 2 + nextflow.config | 330 ++++--- nextflow_schema.json | 288 ++++++ pyproject.toml | 13 + subworkflows/local/input_check.nf | 44 + tower.yml | 5 + workflows/phaseimpute.nf | 141 +++ 96 files changed, 4524 insertions(+), 1683 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .editorconfig create mode 100644 .github/.dockstore.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml delete mode 100644 .github/markdownlint.yml create mode 100644 .github/workflows/awsfulltest.yml create mode 100644 .github/workflows/awstest.yml create mode 100644 .github/workflows/clean-up.yml create mode 100644 .github/workflows/download_pipeline.yml create mode 100644 .github/workflows/fix-linting.yml create mode 100644 .github/workflows/linting_comment.yml create mode 100644 .github/workflows/release-announcements.yml create mode 100644 .gitpod.yml create mode 100644 .nf-core.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .prettierignore create mode 100644 .prettierrc.yml create mode 100644 CITATIONS.md delete mode 100644 Dockerfile create mode 100644 assets/adaptivecard.json create mode 100644 assets/methods_description_template.yml delete mode 100644 assets/multiqc_config.yaml create mode 100644 assets/multiqc_config.yml delete mode 100644 assets/nf-core-phaseimpute_logo.png create mode 100644 assets/nf-core-phaseimpute_logo_light.png create mode 100644 assets/samplesheet.csv create mode 100644 assets/schema_input.json create mode 100644 assets/slackreport.json create mode 100755 bin/check_samplesheet.py delete mode 100644 bin/markdown_to_html.py delete mode 100644 bin/scrape_software_versions.py create mode 100644 conf/modules.config create mode 100644 conf/test_full.config create mode 100755 docs/images/mqc_fastqc_adapter.png create mode 100755 docs/images/mqc_fastqc_counts.png create mode 100755 docs/images/mqc_fastqc_quality.png delete mode 100644 docs/images/nf-core-phaseimpute_logo.png create mode 100644 docs/images/nf-core-phaseimpute_logo_dark.png create mode 100644 docs/images/nf-core-phaseimpute_logo_light.png delete mode 100644 environment.yml create mode 100755 lib/NfcoreTemplate.groovy create mode 100644 lib/Utils.groovy create mode 100755 lib/WorkflowMain.groovy create mode 100755 lib/WorkflowPhaseimpute.groovy create mode 100644 modules.json create mode 100644 modules/local/samplesheet_check.nf create mode 100644 modules/nf-core/custom/dumpsoftwareversions/environment.yml create mode 100644 modules/nf-core/custom/dumpsoftwareversions/main.nf create mode 100644 modules/nf-core/custom/dumpsoftwareversions/meta.yml create mode 100755 modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py create mode 100644 modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test create mode 100644 modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap create mode 100644 modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml create mode 100644 modules/nf-core/fastqc/environment.yml create mode 100644 modules/nf-core/fastqc/main.nf create mode 100644 modules/nf-core/fastqc/meta.yml create mode 100644 modules/nf-core/fastqc/tests/main.nf.test create mode 100644 modules/nf-core/fastqc/tests/main.nf.test.snap create mode 100644 modules/nf-core/fastqc/tests/tags.yml create mode 100644 modules/nf-core/multiqc/environment.yml create mode 100644 modules/nf-core/multiqc/main.nf create mode 100644 modules/nf-core/multiqc/meta.yml create mode 100644 modules/nf-core/multiqc/tests/main.nf.test create mode 100644 modules/nf-core/multiqc/tests/main.nf.test.snap create mode 100644 modules/nf-core/multiqc/tests/tags.yml create mode 100644 nextflow_schema.json create mode 100644 pyproject.toml create mode 100644 subworkflows/local/input_check.nf create mode 100644 tower.yml create mode 100644 workflows/phaseimpute.nf diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..4ecfbfe3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,28 @@ +{ + "name": "nfcore", + "image": "nfcore/gitpod:latest", + "remoteUser": "gitpod", + "runArgs": ["--privileged"], + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "python.defaultInterpreterPath": "/opt/conda/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.formatting.autopep8Path": "/opt/conda/bin/autopep8", + "python.formatting.yapfPath": "/opt/conda/bin/yapf", + "python.linting.flake8Path": "/opt/conda/bin/flake8", + "python.linting.pycodestylePath": "/opt/conda/bin/pycodestyle", + "python.linting.pydocstylePath": "/opt/conda/bin/pydocstyle", + "python.linting.pylintPath": "/opt/conda/bin/pylint" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": ["ms-python.python", "ms-python.vscode-pylance", "nf-core.nf-core-extensionpack"] + } + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..9b990088 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,32 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_size = 4 +indent_style = space + +[*.{md,yml,yaml,html,css,scss,js}] +indent_size = 2 + +# These files are edited and tested upstream in nf-core/modules +[/modules/nf-core/**] +charset = unset +end_of_line = unset +insert_final_newline = unset +trim_trailing_whitespace = unset +indent_style = unset +indent_size = unset + +[/assets/email*] +indent_size = unset + +# ignore Readme +[README.md] +indent_style = unset + +# ignore python +[*.{py}] +indent_style = unset diff --git a/.gitattributes b/.gitattributes index 7fe55006..7a2dabc2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,4 @@ *.config linguist-language=nextflow +*.nf.test linguist-language=nextflow +modules/nf-core/** linguist-generated +subworkflows/nf-core/** linguist-generated diff --git a/.github/.dockstore.yml b/.github/.dockstore.yml new file mode 100644 index 00000000..191fabd2 --- /dev/null +++ b/.github/.dockstore.yml @@ -0,0 +1,6 @@ +# Dockstore config version, not pipeline version +version: 1.2 +workflows: + - subclass: nfl + primaryDescriptorPath: /nextflow.config + publish: True diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5acaf77a..bd157885 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -9,35 +9,40 @@ Please use the pre-filled template to save time. However, don't be put off by this template - other more general issues and suggestions are welcome! Contributions to the code are even more welcome ;) -> If you need help using or modifying nf-core/phaseimpute then the best place to ask is on the nf-core Slack [#phaseimpute](https://nfcore.slack.com/channels/phaseimpute) channel ([join our Slack here](https://nf-co.re/join/slack)). +:::info +If you need help using or modifying nf-core/phaseimpute then the best place to ask is on the nf-core Slack [#phaseimpute](https://nfcore.slack.com/channels/phaseimpute) channel ([join our Slack here](https://nf-co.re/join/slack)). +::: ## Contribution workflow If you'd like to write some code for nf-core/phaseimpute, the standard workflow is as follows: -1. Check that there isn't already an issue about your idea in the [nf-core/phaseimpute issues](https://github.com/nf-core/phaseimpute/issues) to avoid duplicating work - * If there isn't one already, please create one so that others know you're working on this +1. Check that there isn't already an issue about your idea in the [nf-core/phaseimpute issues](https://github.com/nf-core/phaseimpute/issues) to avoid duplicating work. If there isn't one already, please create one so that others know you're working on this 2. [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) the [nf-core/phaseimpute repository](https://github.com/nf-core/phaseimpute) to your GitHub account -3. Make the necessary changes / additions within your forked repository -4. Submit a Pull Request against the `dev` branch and wait for the code to be reviewed and merged +3. Make the necessary changes / additions within your forked repository following [Pipeline conventions](#pipeline-contribution-conventions) +4. Use `nf-core schema build` and add any new parameters to the pipeline JSON schema (requires [nf-core tools](https://github.com/nf-core/tools) >= 1.10). +5. Submit a Pull Request against the `dev` branch and wait for the code to be reviewed and merged If you're not used to this workflow with git, you can start with some [docs from GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests) or even their [excellent `git` resources](https://try.github.io/). ## Tests +You can optionally test your changes by running the pipeline locally. Then it is recommended to use the `debug` profile to +receive warnings about process selectors and other debug info. Example: `nextflow run . -profile debug,test,docker --outdir `. + When you create a pull request with changes, [GitHub Actions](https://github.com/features/actions) will run automatic tests. Typically, pull-requests are only fully reviewed when these tests are passing, though of course we can help out before then. There are typically two types of tests that run: -### Lint Tests +### Lint tests `nf-core` has a [set of guidelines](https://nf-co.re/developers/guidelines) which all pipelines must adhere to. To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core lint ` command. If any failures or warnings are encountered, please follow the listed URL for more documentation. -### Pipeline Tests +### Pipeline tests Each `nf-core` pipeline should be set up with a minimal set of test-data. `GitHub Actions` then runs the pipeline on this data to ensure that it exits successfully. @@ -46,12 +51,73 @@ These tests are run both with the latest available version of `Nextflow` and als ## Patch -: warning: Only in the unlikely and regretful event of a release happening with a bug. +:warning: Only in the unlikely and regretful event of a release happening with a bug. -* On your own fork, make a new branch `patch` based on `upstream/master`. -* Fix the bug, and bump version (X.Y.Z+1). -* A PR should be made on `master` from patch to directly this particular bug. +- On your own fork, make a new branch `patch` based on `upstream/master`. +- Fix the bug, and bump version (X.Y.Z+1). +- A PR should be made on `master` from patch to directly this particular bug. ## Getting help -For further information/help, please consult the [nf-core/phaseimpute documentation](https://nf-co.re/nf-core/phaseimpute/docs) and don't hesitate to get in touch on the nf-core Slack [#phaseimpute](https://nfcore.slack.com/channels/phaseimpute) channel ([join our Slack here](https://nf-co.re/join/slack)). +For further information/help, please consult the [nf-core/phaseimpute documentation](https://nf-co.re/phaseimpute/usage) and don't hesitate to get in touch on the nf-core Slack [#phaseimpute](https://nfcore.slack.com/channels/phaseimpute) channel ([join our Slack here](https://nf-co.re/join/slack)). + +## Pipeline contribution conventions + +To make the nf-core/phaseimpute code and processing logic more understandable for new contributors and to ensure quality, we semi-standardise the way the code and other contributions are written. + +### Adding a new step + +If you wish to contribute a new step, please use the following coding standards: + +1. Define the corresponding input channel into your new process from the expected previous process channel +2. Write the process block (see below). +3. Define the output channel if needed (see below). +4. Add any new parameters to `nextflow.config` with a default (see below). +5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core schema build` tool). +6. Add sanity checks and validation for all relevant parameters. +7. Perform local tests to validate that the new code works as expected. +8. If applicable, add a new test command in `.github/workflow/ci.yml`. +9. Update MultiQC config `assets/multiqc_config.yml` so relevant suffixes, file name clean up and module plots are in the appropriate order. If applicable, add a [MultiQC](https://https://multiqc.info/) module. +10. Add a description of the output files and if relevant any appropriate images from the MultiQC report to `docs/output.md`. + +### Default values + +Parameters should be initialised / defined with default values in `nextflow.config` under the `params` scope. + +Once there, use `nf-core schema build` to add to `nextflow_schema.json`. + +### Default processes resource requirements + +Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/master/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. + +The process resources can be passed on to the tool dynamically within the process with the `${task.cpu}` and `${task.memory}` variables in the `script:` block. + +### Naming schemes + +Please use the following naming schemes, to make it easy to understand what is going where. + +- initial process channel: `ch_output_from_` +- intermediate and terminal channels: `ch__for_` + +### Nextflow version bumping + +If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core bump-version --nextflow . [min-nf-version]` + +### Images and figures + +For overview images and other documents we follow the nf-core [style guidelines and examples](https://nf-co.re/developers/design_guidelines). + +## GitHub Codespaces + +This repo includes a devcontainer configuration which will create a GitHub Codespaces for Nextflow development! This is an online developer environment that runs in your browser, complete with VSCode and a terminal. + +To get started: + +- Open the repo in [Codespaces](https://github.com/nf-core/phaseimpute/codespaces) +- Tools installed + - nf-core + - Nextflow + +Devcontainer specs: + +- [DevContainer config](.devcontainer/devcontainer.json) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 294ac648..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,42 +0,0 @@ -# nf-core/phaseimpute bug report - -Hi there! - -Thanks for telling us about a problem with the pipeline. -Please delete this text and anything that's not relevant from the template below: - -## Describe the bug - -A clear and concise description of what the bug is. - -## Steps to reproduce - -Steps to reproduce the behaviour: - -1. Command line: `nextflow run ...` -2. See error: _Please provide your error message_ - -## Expected behaviour - -A clear and concise description of what you expected to happen. - -## System - -- Hardware: -- Executor: -- OS: -- Version - -## Nextflow Installation - -- Version: - -## Container engine - -- Engine: -- version: -- Image tag: - -## Additional context - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..85218a5e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,50 @@ +name: Bug report +description: Report something that is broken or incorrect +labels: bug +body: + - type: markdown + attributes: + value: | + Before you post this issue, please check the documentation: + + - [nf-core website: troubleshooting](https://nf-co.re/usage/troubleshooting) + - [nf-core/phaseimpute pipeline documentation](https://nf-co.re/phaseimpute/usage) + + - type: textarea + id: description + attributes: + label: Description of the bug + description: A clear and concise description of what the bug is. + validations: + required: true + + - type: textarea + id: command_used + attributes: + label: Command used and terminal output + description: Steps to reproduce the behaviour. Please paste the command you used to launch the pipeline and the output from your terminal. + render: console + placeholder: | + $ nextflow run ... + + Some output where something broke + + - type: textarea + id: files + attributes: + label: Relevant files + description: | + Please drag and drop the relevant files here. Create a `.zip` archive if the extension is not allowed. + Your verbose log file `.nextflow.log` is often useful _(this is a hidden file in the directory where you launched the pipeline)_ as well as custom Nextflow configuration files. + + - type: textarea + id: system + attributes: + label: System information + description: | + * Nextflow version _(eg. 23.04.0)_ + * Hardware _(eg. HPC, Desktop, Cloud)_ + * Executor _(eg. slurm, local, awsbatch)_ + * Container engine: _(e.g. Docker, Singularity, Conda, Podman, Shifter, Charliecloud, or Apptainer)_ + * OS _(eg. CentOS Linux, macOS, Linux Mint)_ + * Version of nf-core/phaseimpute _(eg. 1.1, 1.5, 1.8.2)_ diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..b88beb76 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +contact_links: + - name: Join nf-core + url: https://nf-co.re/join + about: Please join the nf-core community here + - name: "Slack #phaseimpute channel" + url: https://nfcore.slack.com/channels/phaseimpute + about: Discussion about the nf-core/phaseimpute pipeline diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index a4ec960f..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,24 +0,0 @@ -# nf-core/phaseimpute feature request - -Hi there! - -Thanks for suggesting a new feature for the pipeline! -Please delete this text and anything that's not relevant from the template below: - -## Is your feature request related to a problem? Please describe - -A clear and concise description of what the problem is. - -Ex. I'm always frustrated when [...] - -## Describe the solution you'd like - -A clear and concise description of what you want to happen. - -## Describe alternatives you've considered - -A clear and concise description of any alternative solutions or features you've considered. - -## Additional context - -Add any other context about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..038f242b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,11 @@ +name: Feature request +description: Suggest an idea for the nf-core/phaseimpute pipeline +labels: enhancement +body: + - type: textarea + id: description + attributes: + label: Description of feature + description: Please describe your suggestion for a new feature. It might help to describe a problem or use case, plus any alternatives that you have considered. + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 66e6d750..0158fbb8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,4 @@ + + ## PR checklist -- [ ] This comment contains a description of changes (with reason) +- [ ] This comment contains a description of changes (with reason). - [ ] If you've fixed a bug or added code that should be tested, add tests! -- [ ] If necessary, also make a PR on the [nf-core/phaseimpute branch on the nf-core/test-datasets repo](https://github.com/nf-core/test-datasets/pull/new/nf-core/phaseimpute) -- [ ] Ensure the test suite passes (`nextflow run . -profile test,docker`). -- [ ] Make sure your code lints (`nf-core lint .`). -- [ ] Documentation in `docs` is updated -- [ ] `CHANGELOG.md` is updated -- [ ] `README.md` is updated - -**Learn more about contributing:** [CONTRIBUTING.md](https://github.com/nf-core/phaseimpute/tree/master/.github/CONTRIBUTING.md) \ No newline at end of file +- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/phaseimpute/tree/master/.github/CONTRIBUTING.md) +- [ ] If necessary, also make a PR on the nf-core/phaseimpute _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. +- [ ] Make sure your code lints (`nf-core lint`). +- [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). +- [ ] Check for unexpected warnings in debug mode (`nextflow run . -profile debug,test,docker --outdir `). +- [ ] Usage Documentation in `docs/usage.md` is updated. +- [ ] Output Documentation in `docs/output.md` is updated. +- [ ] `CHANGELOG.md` is updated. +- [ ] `README.md` is updated (including new tool citations and authors/contributors). diff --git a/.github/markdownlint.yml b/.github/markdownlint.yml deleted file mode 100644 index 96b12a70..00000000 --- a/.github/markdownlint.yml +++ /dev/null @@ -1,5 +0,0 @@ -# Markdownlint configuration file -default: true, -line-length: false -no-duplicate-header: - siblings_only: true diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml new file mode 100644 index 00000000..02bfb85c --- /dev/null +++ b/.github/workflows/awsfulltest.yml @@ -0,0 +1,39 @@ +name: nf-core AWS full size tests +# This workflow is triggered on published releases. +# It can be additionally triggered manually with GitHub actions workflow dispatch button. +# It runs the -profile 'test_full' on AWS batch + +on: + release: + types: [published] + workflow_dispatch: +jobs: + run-tower: + name: Run AWS full tests + if: github.repository == 'nf-core/phaseimpute' + runs-on: ubuntu-latest + steps: + - name: Launch workflow via tower + uses: seqeralabs/action-tower-launch@v2 + # TODO nf-core: You can customise AWS full pipeline tests as required + # Add full size test data (but still relatively small datasets for few samples) + # on the `test_full.config` test runs with only one set of parameters + with: + workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} + access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} + compute_env: ${{ secrets.TOWER_COMPUTE_ENV }} + revision: ${{ github.sha }} + workdir: s3://${{ secrets.AWS_S3_BUCKET }}/work/phaseimpute/work-${{ github.sha }} + parameters: | + { + "hook_url": "${{ secrets.MEGATESTS_ALERTS_SLACK_HOOK_URL }}", + "outdir": "s3://${{ secrets.AWS_S3_BUCKET }}/phaseimpute/results-${{ github.sha }}" + } + profiles: test_full + + - uses: actions/upload-artifact@v4 + with: + name: Tower debug log file + path: | + tower_action_*.log + tower_action_*.json diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml new file mode 100644 index 00000000..9e6f2bbd --- /dev/null +++ b/.github/workflows/awstest.yml @@ -0,0 +1,33 @@ +name: nf-core AWS test +# This workflow can be triggered manually with the GitHub actions workflow dispatch button. +# It runs the -profile 'test' on AWS batch + +on: + workflow_dispatch: +jobs: + run-tower: + name: Run AWS tests + if: github.repository == 'nf-core/phaseimpute' + runs-on: ubuntu-latest + steps: + # Launch workflow using Tower CLI tool action + - name: Launch workflow via tower + uses: seqeralabs/action-tower-launch@v2 + with: + workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} + access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} + compute_env: ${{ secrets.TOWER_COMPUTE_ENV }} + revision: ${{ github.sha }} + workdir: s3://${{ secrets.AWS_S3_BUCKET }}/work/phaseimpute/work-${{ github.sha }} + parameters: | + { + "outdir": "s3://${{ secrets.AWS_S3_BUCKET }}/phaseimpute/results-test-${{ github.sha }}" + } + profiles: test + + - uses: actions/upload-artifact@v4 + with: + name: Tower debug log file + path: | + tower_action_*.log + tower_action_*.json diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 2de17c56..af367f8d 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -2,15 +2,43 @@ name: nf-core branch protection # This workflow is triggered on PRs to master branch on the repository # It fails when someone tries to make a PR against the nf-core `master` branch instead of `dev` on: - pull_request: - branches: - - master + pull_request_target: + branches: [master] jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - # PRs are only ok if coming from an nf-core `dev` branch or a fork `patch` branch + # PRs to the nf-core repo master branch are only ok if coming from the nf-core repo `dev` or any `patch` branches - name: Check PRs + if: github.repository == 'nf-core/phaseimpute' run: | - { [[ $(git remote get-url origin) == *nf-core/phaseimpute ]] && [[ ${GITHUB_HEAD_REF} = "dev" ]]; } || [[ ${GITHUB_HEAD_REF} == "patch" ]] + { [[ ${{github.event.pull_request.head.repo.full_name }} == nf-core/phaseimpute ]] && [[ $GITHUB_HEAD_REF == "dev" ]]; } || [[ $GITHUB_HEAD_REF == "patch" ]] + + # If the above check failed, post a comment on the PR explaining the failure + # NOTE - this doesn't currently work if the PR is coming from a fork, due to limitations in GitHub actions secrets + - name: Post PR comment + if: failure() + uses: mshick/add-pr-comment@v2 + with: + message: | + ## This PR is against the `master` branch :x: + + * Do not close this PR + * Click _Edit_ and change the `base` to `dev` + * This CI test will remain failed until you push a new commit + + --- + + Hi @${{ github.event.pull_request.user.login }}, + + It looks like this pull-request is has been made against the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) `master` branch. + The `master` branch on nf-core repositories should always contain code from the latest release. + Because of this, PRs to `master` are only allowed if they come from the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) `dev` branch. + + You do not need to close this PR, you can change the target branch to `dev` by clicking the _"Edit"_ button at the top of this page. + Note that even after this, the test will continue to show as failing until you push a new commit. + + Thanks again for your contribution! + repo-token: ${{ secrets.GITHUB_TOKEN }} + allow-repeats: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e0f6e3b..30231f6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,30 +1,43 @@ name: nf-core CI -# This workflow is triggered on pushes and PRs to the repository. -# It runs the pipeline with the minimal test dataset to check that it completes without any syntax errors -on: [push, pull_request] +# This workflow runs the pipeline with the minimal test dataset to check that it completes without any syntax errors +on: + push: + branches: + - dev + pull_request: + release: + types: [published] + +env: + NXF_ANSI_LOG: false + +concurrency: + group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" + cancel-in-progress: true jobs: test: - env: - NXF_VER: ${{ matrix.nxf_ver }} - NXF_ANSI_LOG: false + name: Run pipeline with test data + # Only run on push if this is the nf-core dev branch (merged PRs) + if: "${{ github.event_name != 'push' || (github.event_name == 'push' && github.repository == 'nf-core/phaseimpute') }}" runs-on: ubuntu-latest strategy: matrix: - # Nextflow versions: check pipeline minimum and current latest - nxf_ver: ['19.10.0', ''] + NXF_VER: + - "23.04.0" + - "latest-everything" steps: - - uses: actions/checkout@v2 + - name: Check out pipeline code + uses: actions/checkout@v4 + - name: Install Nextflow - run: | - wget -qO- get.nextflow.io | bash - sudo mv nextflow /usr/local/bin/ - - name: Pull docker image - run: | - docker pull nfcore/phaseimpute:dev - docker tag nfcore/phaseimpute:dev nfcore/phaseimpute:dev + uses: nf-core/setup-nextflow@v1 + with: + version: "${{ matrix.NXF_VER }}" + - name: Run pipeline with test data + # TODO nf-core: You can customise CI pipeline run tests as required + # For example: adding multiple test runs with different parameters + # Remember that you can parallelise this by using strategy.matrix run: | - # TODO nf-core: You can customise CI pipeline run tests as required - # (eg. adding multiple test runs with different parameters) - nextflow run ${GITHUB_WORKSPACE} -profile test,docker + nextflow run ${GITHUB_WORKSPACE} -profile test,docker --outdir ./results diff --git a/.github/workflows/clean-up.yml b/.github/workflows/clean-up.yml new file mode 100644 index 00000000..e37cfda5 --- /dev/null +++ b/.github/workflows/clean-up.yml @@ -0,0 +1,24 @@ +name: "Close user-tagged issues and PRs" +on: + schedule: + - cron: "0 0 * * 0" # Once a week + +jobs: + clean-up: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: "This issue has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment otherwise this issue will be closed in 20 days." + stale-pr-message: "This PR has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment if it is still useful." + close-issue-message: "This issue was closed because it has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor and then staled for 20 days with no activity." + days-before-stale: 30 + days-before-close: 20 + days-before-pr-close: -1 + any-of-labels: "awaiting-changes,awaiting-feedback" + exempt-issue-labels: "WIP" + exempt-pr-labels: "WIP" + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml new file mode 100644 index 00000000..8611458a --- /dev/null +++ b/.github/workflows/download_pipeline.yml @@ -0,0 +1,67 @@ +name: Test successful pipeline download with 'nf-core download' + +# Run the workflow when: +# - dispatched manually +# - when a PR is opened or reopened to master branch +# - the head branch of the pull request is updated, i.e. if fixes for a release are pushed last minute to dev. +on: + workflow_dispatch: + pull_request: + types: + - opened + branches: + - master + pull_request_target: + branches: + - master + +env: + NXF_ANSI_LOG: false + +jobs: + download: + runs-on: ubuntu-latest + steps: + - name: Install Nextflow + uses: nf-core/setup-nextflow@v1 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + architecture: "x64" + - uses: eWaterCycle/setup-singularity@v7 + with: + singularity-version: 3.8.3 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install git+https://github.com/nf-core/tools.git@dev + + - name: Get the repository name and current branch set as environment variable + run: | + echo "REPO_LOWERCASE=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + echo "REPOTITLE_LOWERCASE=$(basename ${GITHUB_REPOSITORY,,})" >> ${GITHUB_ENV} + echo "REPO_BRANCH=${GITHUB_REF#refs/heads/}" >> ${GITHUB_ENV} + + - name: Download the pipeline + env: + NXF_SINGULARITY_CACHEDIR: ./ + run: | + nf-core download ${{ env.REPO_LOWERCASE }} \ + --revision ${{ env.REPO_BRANCH }} \ + --outdir ./${{ env.REPOTITLE_LOWERCASE }} \ + --compress "none" \ + --container-system 'singularity' \ + --container-library "quay.io" -l "docker.io" -l "ghcr.io" \ + --container-cache-utilisation 'amend' \ + --download-configuration + + - name: Inspect download + run: tree ./${{ env.REPOTITLE_LOWERCASE }} + + - name: Run the downloaded pipeline + env: + NXF_SINGULARITY_CACHEDIR: ./ + NXF_SINGULARITY_HOME_MOUNT: true + run: nextflow run ./${{ env.REPOTITLE_LOWERCASE }}/$( sed 's/\W/_/g' <<< ${{ env.REPO_BRANCH }}) -stub -profile test,singularity --outdir ./results diff --git a/.github/workflows/fix-linting.yml b/.github/workflows/fix-linting.yml new file mode 100644 index 00000000..599f1160 --- /dev/null +++ b/.github/workflows/fix-linting.yml @@ -0,0 +1,89 @@ +name: Fix linting from a comment +on: + issue_comment: + types: [created] + +jobs: + fix-linting: + # Only run if comment is on a PR with the main repo, and if it contains the magic keywords + if: > + contains(github.event.comment.html_url, '/pull/') && + contains(github.event.comment.body, '@nf-core-bot fix linting') && + github.repository == 'nf-core/phaseimpute' + runs-on: ubuntu-latest + steps: + # Use the @nf-core-bot token to check out so we can push later + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + with: + token: ${{ secrets.nf_core_bot_auth_token }} + + # indication that the linting is being fixed + - name: React on comment + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ github.event.comment.id }} + reactions: eyes + + # Action runs on the issue comment, so we don't get the PR by default + # Use the gh cli to check out the PR + - name: Checkout Pull Request + run: gh pr checkout ${{ github.event.issue.number }} + env: + GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} + + # Install and run pre-commit + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 + with: + python-version: 3.11 + + - name: Install pre-commit + run: pip install pre-commit + + - name: Run pre-commit + id: pre-commit + run: pre-commit run --all-files + continue-on-error: true + + # indication that the linting has finished + - name: react if linting finished succesfully + if: steps.pre-commit.outcome == 'success' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ github.event.comment.id }} + reactions: "+1" + + - name: Commit & push changes + id: commit-and-push + if: steps.pre-commit.outcome == 'failure' + run: | + git config user.email "core@nf-co.re" + git config user.name "nf-core-bot" + git config push.default upstream + git add . + git status + git commit -m "[automated] Fix code linting" + git push + + - name: react if linting errors were fixed + id: react-if-fixed + if: steps.commit-and-push.outcome == 'success' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ github.event.comment.id }} + reactions: hooray + + - name: react if linting errors were not fixed + if: steps.commit-and-push.outcome == 'failure' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ github.event.comment.id }} + reactions: confused + + - name: react if linting errors were not fixed + if: steps.commit-and-push.outcome == 'failure' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + issue-number: ${{ github.event.issue.number }} + body: | + @${{ github.actor }} I tried to fix the linting errors, but it didn't work. Please fix them manually. + See [CI log](https://github.com/nf-core/phaseimpute/actions/runs/${{ github.run_id }}) for more details. diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 1e0827a8..81cd098e 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -1,50 +1,69 @@ name: nf-core linting # This workflow is triggered on pushes and PRs to the repository. -# It runs the `nf-core lint` and markdown lint tests to ensure that the code meets the nf-core guidelines +# It runs the `nf-core lint` and markdown lint tests to ensure +# that the code meets the nf-core guidelines. on: push: + branches: + - dev pull_request: release: types: [published] jobs: - Markdown: + pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: '10' - - name: Install markdownlint - run: npm install -g markdownlint-cli - - name: Run Markdownlint - run: markdownlint ${GITHUB_WORKSPACE} -c ${GITHUB_WORKSPACE}/.github/markdownlint.yml - YAML: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 + - uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 with: - node-version: '10' - - name: Install yaml-lint - run: npm install -g yaml-lint - - name: Run yaml-lint - run: yamllint $(find ${GITHUB_WORKSPACE} -type f -name "*.yml") + python-version: 3.11 + cache: "pip" + + - name: Install pre-commit + run: pip install pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files + nf-core: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Check out pipeline code + uses: actions/checkout@v4 + - name: Install Nextflow - run: | - wget -qO- get.nextflow.io | bash - sudo mv nextflow /usr/local/bin/ - - uses: actions/setup-python@v1 + uses: nf-core/setup-nextflow@v1 + + - uses: actions/setup-python@v5 with: - python-version: '3.6' - architecture: 'x64' + python-version: "3.11" + architecture: "x64" + - name: Install dependencies run: | python -m pip install --upgrade pip pip install nf-core + - name: Run nf-core lint - run: nf-core lint ${GITHUB_WORKSPACE} + env: + GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_COMMIT: ${{ github.event.pull_request.head.sha }} + run: nf-core -l lint_log.txt lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md + + - name: Save PR number + if: ${{ always() }} + run: echo ${{ github.event.pull_request.number }} > PR_number.txt + + - name: Upload linting log file artifact + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: linting-logs + path: | + lint_log.txt + lint_results.md + PR_number.txt diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml new file mode 100644 index 00000000..147bcd10 --- /dev/null +++ b/.github/workflows/linting_comment.yml @@ -0,0 +1,28 @@ +name: nf-core linting comment +# This workflow is triggered after the linting action is complete +# It posts an automated comment to the PR, even if the PR is coming from a fork + +on: + workflow_run: + workflows: ["nf-core linting"] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Download lint results + uses: dawidd6/action-download-artifact@v3 + with: + workflow: linting.yml + workflow_conclusion: completed + + - name: Get PR number + id: pr_number + run: echo "pr_number=$(cat linting-logs/PR_number.txt)" >> $GITHUB_OUTPUT + + - name: Post PR comment + uses: marocchino/sticky-pull-request-comment@v2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + number: ${{ steps.pr_number.outputs.pr_number }} + path: linting-logs/lint_results.md diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml new file mode 100644 index 00000000..21ac3f06 --- /dev/null +++ b/.github/workflows/release-announcements.yml @@ -0,0 +1,68 @@ +name: release-announcements +# Automatic release toot and tweet anouncements +on: + release: + types: [published] + workflow_dispatch: + +jobs: + toot: + runs-on: ubuntu-latest + steps: + - uses: rzr/fediverse-action@master + with: + access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} + host: "mstdn.science" # custom host if not "mastodon.social" (default) + # GitHub event payload + # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release + message: | + Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! + + Please see the changelog: ${{ github.event.release.html_url }} + + send-tweet: + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install dependencies + run: pip install tweepy==4.14.0 + - name: Send tweet + shell: python + run: | + import os + import tweepy + + client = tweepy.Client( + access_token=os.getenv("TWITTER_ACCESS_TOKEN"), + access_token_secret=os.getenv("TWITTER_ACCESS_TOKEN_SECRET"), + consumer_key=os.getenv("TWITTER_CONSUMER_KEY"), + consumer_secret=os.getenv("TWITTER_CONSUMER_SECRET"), + ) + tweet = os.getenv("TWEET") + client.create_tweet(text=tweet) + env: + TWEET: | + Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! + + Please see the changelog: ${{ github.event.release.html_url }} + TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} + TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} + TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} + TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} + + bsky-post: + runs-on: ubuntu-latest + steps: + - uses: zentered/bluesky-post-action@v0.1.0 + with: + post: | + Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! + + Please see the changelog: ${{ github.event.release.html_url }} + env: + BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }} + BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }} + # diff --git a/.gitignore b/.gitignore index 6354f370..5124c9ac 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,6 @@ work/ data/ results/ .DS_Store -tests/ testing/ +testing* *.pyc diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000..363d5b1d --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,22 @@ +image: nfcore/gitpod:latest +tasks: + - name: Update Nextflow and setup pre-commit + command: | + pre-commit install --install-hooks + nextflow self-update + - name: unset JAVA_TOOL_OPTIONS + command: | + unset JAVA_TOOL_OPTIONS + +vscode: + extensions: # based on nf-core.nf-core-extensionpack + - codezombiech.gitignore # Language support for .gitignore files + # - cssho.vscode-svgviewer # SVG viewer + - esbenp.prettier-vscode # Markdown/CommonMark linting and style checking for Visual Studio Code + - eamodio.gitlens # Quickly glimpse into whom, why, and when a line or code block was changed + - EditorConfig.EditorConfig # override user/workspace settings with settings found in .editorconfig files + - Gruntfuggly.todo-tree # Display TODO and FIXME in a tree view in the activity bar + - mechatroner.rainbow-csv # Highlight columns in csv files in different colors + # - nextflow.nextflow # Nextflow syntax highlighting + - oderwat.indent-rainbow # Highlight indentation level + - streetsidesoftware.code-spell-checker # Spelling checker for source code diff --git a/.nf-core.yml b/.nf-core.yml new file mode 100644 index 00000000..3805dc81 --- /dev/null +++ b/.nf-core.yml @@ -0,0 +1 @@ +repository_type: pipeline diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..af57081f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.1.0" + hooks: + - id: prettier + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python + rev: "2.7.3" + hooks: + - id: editorconfig-checker + alias: ec diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..437d763d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,12 @@ +email_template.html +adaptivecard.json +slackreport.json +.nextflow* +work/ +data/ +results/ +.DS_Store +testing/ +testing* +*.pyc +bin/ diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 00000000..c81f9a76 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1 @@ +printWidth: 120 diff --git a/CHANGELOG.md b/CHANGELOG.md index 77c0bebf..6ef061e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # nf-core/phaseimpute: Changelog -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## v1.0dev - [date] -Initial release of nf-core/phaseimpute, created with the [nf-core](http://nf-co.re/) template. +Initial release of nf-core/phaseimpute, created with the [nf-core](https://nf-co.re/) template. ### `Added` diff --git a/CITATIONS.md b/CITATIONS.md new file mode 100644 index 00000000..31f66a91 --- /dev/null +++ b/CITATIONS.md @@ -0,0 +1,41 @@ +# nf-core/phaseimpute: Citations + +## [nf-core](https://pubmed.ncbi.nlm.nih.gov/32055031/) + +> Ewels PA, Peltzer A, Fillinger S, Patel H, Alneberg J, Wilm A, Garcia MU, Di Tommaso P, Nahnsen S. The nf-core framework for community-curated bioinformatics pipelines. Nat Biotechnol. 2020 Mar;38(3):276-278. doi: 10.1038/s41587-020-0439-x. PubMed PMID: 32055031. + +## [Nextflow](https://pubmed.ncbi.nlm.nih.gov/28398311/) + +> Di Tommaso P, Chatzou M, Floden EW, Barja PP, Palumbo E, Notredame C. Nextflow enables reproducible computational workflows. Nat Biotechnol. 2017 Apr 11;35(4):316-319. doi: 10.1038/nbt.3820. PubMed PMID: 28398311. + +## Pipeline tools + +- [FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/) + + > Andrews, S. (2010). FastQC: A Quality Control Tool for High Throughput Sequence Data [Online]. + +- [MultiQC](https://pubmed.ncbi.nlm.nih.gov/27312411/) + + > Ewels P, Magnusson M, Lundin S, Käller M. MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics. 2016 Oct 1;32(19):3047-8. doi: 10.1093/bioinformatics/btw354. Epub 2016 Jun 16. PubMed PMID: 27312411; PubMed Central PMCID: PMC5039924. + +## Software packaging/containerisation tools + +- [Anaconda](https://anaconda.com) + + > Anaconda Software Distribution. Computer software. Vers. 2-2.4.0. Anaconda, Nov. 2016. Web. + +- [Bioconda](https://pubmed.ncbi.nlm.nih.gov/29967506/) + + > Grüning B, Dale R, Sjödin A, Chapman BA, Rowe J, Tomkins-Tinch CH, Valieris R, Köster J; Bioconda Team. Bioconda: sustainable and comprehensive software distribution for the life sciences. Nat Methods. 2018 Jul;15(7):475-476. doi: 10.1038/s41592-018-0046-7. PubMed PMID: 29967506. + +- [BioContainers](https://pubmed.ncbi.nlm.nih.gov/28379341/) + + > da Veiga Leprevost F, Grüning B, Aflitos SA, Röst HL, Uszkoreit J, Barsnes H, Vaudel M, Moreno P, Gatto L, Weber J, Bai M, Jimenez RC, Sachsenberg T, Pfeuffer J, Alvarez RV, Griss J, Nesvizhskii AI, Perez-Riverol Y. BioContainers: an open-source and community-driven framework for software standardization. Bioinformatics. 2017 Aug 15;33(16):2580-2582. doi: 10.1093/bioinformatics/btx192. PubMed PMID: 28379341; PubMed Central PMCID: PMC5870671. + +- [Docker](https://dl.acm.org/doi/10.5555/2600239.2600241) + + > Merkel, D. (2014). Docker: lightweight linux containers for consistent development and deployment. Linux Journal, 2014(239), 2. doi: 10.5555/2600239.2600241. + +- [Singularity](https://pubmed.ncbi.nlm.nih.gov/28494014/) + + > Kurtzer GM, Sochat V, Bauer MW. Singularity: Scientific containers for mobility of compute. PLoS One. 2017 May 11;12(5):e0177459. doi: 10.1371/journal.pone.0177459. eCollection 2017. PubMed PMID: 28494014; PubMed Central PMCID: PMC5426675. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index cf930c8a..c089ec78 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,46 +1,182 @@ -# Contributor Covenant Code of Conduct +# Code of Conduct at nf-core (v1.4) ## Our Pledge -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +In the interest of fostering an open, collaborative, and welcoming environment, we as contributors and maintainers of nf-core pledge to making participation in our projects and community a harassment-free experience for everyone, regardless of: -## Our Standards +- Age +- Ability +- Body size +- Caste +- Familial status +- Gender identity and expression +- Geographical location +- Level of experience +- Nationality and national origins +- Native language +- Neurodiversity +- Race or ethnicity +- Religion +- Sexual identity and orientation +- Socioeconomic status -Examples of behavior that contributes to creating a positive environment include: +Please note that the list above is alphabetised and is therefore not ranked in any order of preference or importance. -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +## Preamble -Examples of unacceptable behavior by participants include: +:::note +This Code of Conduct (CoC) has been drafted by Renuka Kudva, Cris Tuñí, and Michael Heuer, with input from the nf-core Core Team and Susanna Marquez from the nf-core community. "We", in this document, refers to the Safety Officers and members of the nf-core Core Team, both of whom are deemed to be members of the nf-core community and are therefore required to abide by this Code of Conduct. This document will be amended periodically to keep it up-to-date. In case of any dispute, the most current version will apply. +::: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +An up-to-date list of members of the nf-core core team can be found [here](https://nf-co.re/about). + +Our Safety Officers are Saba Nafees, Cris Tuñí, and Michael Heuer. + +nf-core is a young and growing community that welcomes contributions from anyone with a shared vision for [Open Science Policies](https://www.fosteropenscience.eu/taxonomy/term/8). Open science policies encompass inclusive behaviours and we strive to build and maintain a safe and inclusive environment for all individuals. + +We have therefore adopted this CoC, which we require all members of our community and attendees of nf-core events to adhere to in all our workspaces at all times. Workspaces include, but are not limited to, Slack, meetings on Zoom, gather.town, YouTube live etc. + +Our CoC will be strictly enforced and the nf-core team reserves the right to exclude participants who do not comply with our guidelines from our workspaces and future nf-core activities. + +We ask all members of our community to help maintain supportive and productive workspaces and to avoid behaviours that can make individuals feel unsafe or unwelcome. Please help us maintain and uphold this CoC. + +Questions, concerns, or ideas on what we can include? Contact members of the Safety Team on Slack or email safety [at] nf-co [dot] re. ## Our Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +Members of the Safety Team (the Safety Officers) are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behaviour. + +The Safety Team, in consultation with the nf-core core team, have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this CoC, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +Members of the core team or the Safety Team who violate the CoC will be required to recuse themselves pending investigation. They will not have access to any reports of the violations and will be subject to the same actions as others in violation of the CoC. + +## When and where does this Code of Conduct apply? + +Participation in the nf-core community is contingent on following these guidelines in all our workspaces and events, such as hackathons, workshops, bytesize, and collaborative workspaces on gather.town. These guidelines include, but are not limited to, the following (listed alphabetically and therefore in no order of preference): + +- Communicating with an official project email address. +- Communicating with community members within the nf-core Slack channel. +- Participating in hackathons organised by nf-core (both online and in-person events). +- Participating in collaborative work on GitHub, Google Suite, community calls, mentorship meetings, email correspondence, and on the nf-core gather.town workspace. +- Participating in workshops, training, and seminar series organised by nf-core (both online and in-person events). This applies to events hosted on web-based platforms such as Zoom, gather.town, Jitsi, YouTube live etc. +- Representing nf-core on social media. This includes both official and personal accounts. + +## nf-core cares 😊 + +nf-core's CoC and expectations of respectful behaviours for all participants (including organisers and the nf-core team) include, but are not limited to, the following (listed in alphabetical order): + +- Ask for consent before sharing another community member’s personal information (including photographs) on social media. +- Be respectful of differing viewpoints and experiences. We are all here to learn from one another and a difference in opinion can present a good learning opportunity. +- Celebrate your accomplishments! (Get creative with your use of emojis 🎉 🥳 💯 🙌 !) +- Demonstrate empathy towards other community members. (We don’t all have the same amount of time to dedicate to nf-core. If tasks are pending, don’t hesitate to gently remind members of your team. If you are leading a task, ask for help if you feel overwhelmed.) +- Engage with and enquire after others. (This is especially important given the geographically remote nature of the nf-core community, so let’s do this the best we can) +- Focus on what is best for the team and the community. (When in doubt, ask) +- Accept feedback, yet be unafraid to question, deliberate, and learn. +- Introduce yourself to members of the community. (We’ve all been outsiders and we know that talking to strangers can be hard for some, but remember we’re interested in getting to know you and your visions for open science!) +- Show appreciation and **provide clear feedback**. (This is especially important because we don’t see each other in person and it can be harder to interpret subtleties. Also remember that not everyone understands a certain language to the same extent as you do, so **be clear in your communication to be kind.**) +- Take breaks when you feel like you need them. +- Use welcoming and inclusive language. (Participants are encouraged to display their chosen pronouns on Zoom or in communication on Slack) + +## nf-core frowns on 😕 + +The following behaviours from any participants within the nf-core community (including the organisers) will be considered unacceptable under this CoC. Engaging or advocating for any of the following could result in expulsion from nf-core workspaces: + +- Deliberate intimidation, stalking or following and sustained disruption of communication among participants of the community. This includes hijacking shared screens through actions such as using the annotate tool in conferencing software such as Zoom. +- “Doxing” i.e. posting (or threatening to post) another person’s personal identifying information online. +- Spamming or trolling of individuals on social media. +- Use of sexual or discriminatory imagery, comments, jokes, or unwelcome sexual attention. +- Verbal and text comments that reinforce social structures of domination related to gender, gender identity and expression, sexual orientation, ability, physical appearance, body size, race, age, religion, or work experience. + +### Online Trolling + +The majority of nf-core interactions and events are held online. Unfortunately, holding events online comes with the risk of online trolling. This is unacceptable — reports of such behaviour will be taken very seriously and perpetrators will be excluded from activities immediately. + +All community members are **required** to ask members of the group they are working with for explicit consent prior to taking screenshots of individuals during video calls. + +## Procedures for reporting CoC violations + +If someone makes you feel uncomfortable through their behaviours or actions, report it as soon as possible. + +You can reach out to members of the Safety Team (Saba Nafees, Cris Tuñí, and Michael Heuer) on Slack. Alternatively, contact a member of the nf-core core team [nf-core core team](https://nf-co.re/about), and they will forward your concerns to the Safety Team. + +Issues directly concerning members of the Core Team or the Safety Team will be dealt with by other members of the core team and the safety manager — possible conflicts of interest will be taken into account. nf-core is also in discussions about having an ombudsperson and details will be shared in due course. -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +All reports will be handled with the utmost discretion and confidentiality. -## Scope +You can also report any CoC violations to safety [at] nf-co [dot] re. In your email report, please do your best to include: -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. +- Your contact information. +- Identifying information (e.g. names, nicknames, pseudonyms) of the participant who has violated the Code of Conduct. +- The behaviour that was in violation and the circumstances surrounding the incident. +- The approximate time of the behaviour (if different than the time the report was made). +- Other people involved in the incident, if applicable. +- If you believe the incident is ongoing. +- If there is a publicly available record (e.g. mailing list record, a screenshot). +- Any additional information. + +After you file a report, one or more members of our Safety Team will contact you to follow up on your report. + +## Who will read and handle reports + +All reports will be read and handled by the members of the Safety Team at nf-core. + +If members of the Safety Team are deemed to have a conflict of interest with a report, they will be required to recuse themselves as per our Code of Conduct and will not have access to any follow-ups. + +To keep this first report confidential from any of the Safety Team members, please submit your first report by direct messaging on Slack/direct email to any of the nf-core members you are comfortable disclosing the information to, and be explicit about which member(s) you do not consent to sharing the information with. + +## Reviewing reports + +After receiving the report, members of the Safety Team will review the incident report to determine whether immediate action is required, for example, whether there is immediate threat to participants’ safety. + +The Safety Team, in consultation with members of the nf-core core team, will assess the information to determine whether the report constitutes a Code of Conduct violation, for them to decide on a course of action. + +In the case of insufficient information, one or more members of the Safety Team may contact the reporter, the reportee, or any other attendees to obtain more information. + +Once additional information is gathered, the Safety Team will collectively review and decide on the best course of action to take, if any. The Safety Team reserves the right to not act on a report. + +## Confidentiality + +All reports, and any additional information included, are only shared with the team of safety officers (and possibly members of the core team, in case the safety officer is in violation of the CoC). We will respect confidentiality requests for the purpose of protecting victims of abuse. + +We will not name harassment victims, beyond discussions between the safety officer and members of the nf-core team, without the explicit consent of the individuals involved. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team on [Slack](https://nf-co.re/join/slack). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Actions taken by the nf-core’s Safety Team may include, but are not limited to: + +- Asking anyone to stop a behaviour. +- Asking anyone to leave the event and online spaces either temporarily, for the remainder of the event, or permanently. +- Removing access to the gather.town and Slack, either temporarily or permanently. +- Communicating to all participants to reinforce our expectations for conduct and remind what is unacceptable behaviour; this may be public for practical reasons. +- Communicating to all participants that an incident has taken place and how we will act or have acted — this may be for the purpose of letting event participants know we are aware of and dealing with the incident. +- Banning anyone from participating in nf-core-managed spaces, future events, and activities, either temporarily or permanently. +- No action. + +## Attribution and Acknowledgements + +- The [Contributor Covenant, version 1.4](http://contributor-covenant.org/version/1/4) +- The [OpenCon 2017 Code of Conduct](http://www.opencon2017.org/code_of_conduct) (CC BY 4.0 OpenCon organisers, SPARC and Right to Research Coalition) +- The [eLife innovation sprint 2020 Code of Conduct](https://sprint.elifesciences.org/code-of-conduct/) +- The [Mozilla Community Participation Guidelines v3.1](https://www.mozilla.org/en-US/about/governance/policies/participation/) (version 3.1, CC BY-SA 3.0 Mozilla) + +## Changelog + +### v1.4 - February 8th, 2022 + +- Included a new member of the Safety Team. Corrected a typographical error in the text. + +### v1.3 - December 10th, 2021 + +- Added a statement that the CoC applies to nf-core gather.town workspaces. Corrected typographical errors in the text. + +### v1.2 - November 12th, 2021 + +- Removed information specific to reporting CoC violations at the Hackathon in October 2021. -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +### v1.1 - October 14th, 2021 -## Attribution +- Updated with names of new Safety Officers and specific information for the hackathon in October 2021. -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +### v1.0 - March 15th, 2021 -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +- Complete rewrite from original [Contributor Covenant](http://contributor-covenant.org/) CoC. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b6bc3e90..00000000 --- a/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM nfcore/base:1.9 -LABEL authors="@louislenezet" \ - description="Docker image containing all software requirements for the nf-core/phaseimpute pipeline" - -# Install the conda environment -COPY environment.yml / -RUN conda env create -f /environment.yml && conda clean -a - -# Add conda installation dir to PATH (instead of doing 'conda activate') -ENV PATH /opt/conda/envs/nf-core-phaseimpute-1.0dev/bin:$PATH - -# Dump the details of the installed packages to a file for posterity -RUN conda env export --name nf-core-phaseimpute-1.0dev > nf-core-phaseimpute-1.0dev.yml diff --git a/LICENSE b/LICENSE index ac5e876f..fe6a73b8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) @louislenezet +Copyright (c) LouisLeNezet Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 87b79c24..5d0b2689 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,103 @@ -# ![nf-core/phaseimpute](docs/images/nf-core-phaseimpute_logo.png) +

+ + + nf-core/phaseimpute + +

+[![GitHub Actions CI Status](https://github.com/nf-core/phaseimpute/workflows/nf-core%20CI/badge.svg)](https://github.com/nf-core/phaseimpute/actions?query=workflow%3A%22nf-core+CI%22) +[![GitHub Actions Linting Status](https://github.com/nf-core/phaseimpute/workflows/nf-core%20linting/badge.svg)](https://github.com/nf-core/phaseimpute/actions?query=workflow%3A%22nf-core+linting%22)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/phaseimpute/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) + +[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A523.04.0-23aa62.svg)](https://www.nextflow.io/) +[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) +[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) +[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) +[![Launch on Nextflow Tower](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Nextflow%20Tower-%234256e7)](https://tower.nf/launch?pipeline=https://github.com/nf-core/phaseimpute) + +[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23phaseimpute-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/phaseimpute)[![Follow on Twitter](http://img.shields.io/badge/twitter-%40nf__core-1DA1F2?labelColor=000000&logo=twitter)](https://twitter.com/nf_core)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core) -**Nf-core pipeline for phasing and imputing genomic data.**. +## Introduction -[![GitHub Actions CI Status](https://github.com/nf-core/phaseimpute/workflows/nf-core%20CI/badge.svg)](https://github.com/nf-core/phaseimpute/actions) -[![GitHub Actions Linting Status](https://github.com/nf-core/phaseimpute/workflows/nf-core%20linting/badge.svg)](https://github.com/nf-core/phaseimpute/actions) -[![Nextflow](https://img.shields.io/badge/nextflow-%E2%89%A519.10.0-brightgreen.svg)](https://www.nextflow.io/) +**nf-core/phaseimpute** is a bioinformatics pipeline that ... -[![install with bioconda](https://img.shields.io/badge/install%20with-bioconda-brightgreen.svg)](http://bioconda.github.io/) -[![Docker](https://img.shields.io/docker/automated/nfcore/phaseimpute.svg)](https://hub.docker.com/r/nfcore/phaseimpute) + -## Introduction + + -The pipeline is built using [Nextflow](https://www.nextflow.io), a workflow tool to run tasks across multiple compute infrastructures in a very portable manner. It comes with docker containers making installation trivial and results highly reproducible. +1. Read QC ([`FastQC`](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/)) +2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/)) -## Quick Start +## Usage -i. Install [`nextflow`](https://nf-co.re/usage/installation) +> [!NOTE] +> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. -ii. Install either [`Docker`](https://docs.docker.com/engine/installation/) or [`Singularity`](https://www.sylabs.io/guides/3.0/user-guide/) for full pipeline reproducibility (please only use [`Conda`](https://conda.io/miniconda.html) as a last resort; see [docs](https://nf-co.re/usage/configuration#basic-configuration-profiles)) + -iv. Start running your own analysis! +Now, you can run the pipeline using: - + ```bash -nextflow run nf-core/phaseimpute -profile --reads '*_R{1,2}.fastq.gz' --genome GRCh37 +nextflow run nf-core/phaseimpute \ + -profile \ + --input samplesheet.csv \ + --outdir ``` -See [usage docs](docs/usage.md) for all of the available options when running the pipeline. +> [!WARNING] +> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; +> see [docs](https://nf-co.re/usage/configuration#custom-configuration-files). -## Documentation +For more details and further functionality, please refer to the [usage documentation](https://nf-co.re/phaseimpute/usage) and the [parameter documentation](https://nf-co.re/phaseimpute/parameters). -The nf-core/phaseimpute pipeline comes with documentation about the pipeline, found in the `docs/` directory: +## Pipeline output -1. [Installation](https://nf-co.re/usage/installation) -2. Pipeline configuration - * [Local installation](https://nf-co.re/usage/local_installation) - * [Adding your own system config](https://nf-co.re/usage/adding_own_config) - * [Reference genomes](https://nf-co.re/usage/reference_genomes) -3. [Running the pipeline](docs/usage.md) -4. [Output and how to interpret the results](docs/output.md) -5. [Troubleshooting](https://nf-co.re/usage/troubleshooting) - - +To see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/phaseimpute/results) tab on the nf-core website pipeline page. +For more details about the output files and reports, please refer to the +[output documentation](https://nf-co.re/phaseimpute/output). ## Credits -nf-core/phaseimpute was originally written by @louislenezet. +nf-core/phaseimpute was originally written by LouisLeNezet. + +We thank the following people for their extensive assistance in the development of this pipeline: + + ## Contributions and Support If you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md). -For further information or help, don't hesitate to get in touch on [Slack](https://nfcore.slack.com/channels/phaseimpute) (you can join with [this invite](https://nf-co.re/join/slack)). +For further information or help, don't hesitate to get in touch on the [Slack `#phaseimpute` channel](https://nfcore.slack.com/channels/phaseimpute) (you can join with [this invite](https://nf-co.re/join/slack)). + +## Citations + + + -## Citation + - - +An extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file. You can cite the `nf-core` publication as follows: @@ -73,5 +105,4 @@ You can cite the `nf-core` publication as follows: > > Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen. > -> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x). -> ReadCube: [Full Access Link](https://rdcu.be/b1GjZ) +> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x). diff --git a/assets/adaptivecard.json b/assets/adaptivecard.json new file mode 100644 index 00000000..e7079ad6 --- /dev/null +++ b/assets/adaptivecard.json @@ -0,0 +1,67 @@ +{ + "type": "message", + "attachments": [ + { + "contentType": "application/vnd.microsoft.card.adaptive", + "contentUrl": null, + "content": { + "\$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "msteams": { + "width": "Full" + }, + "type": "AdaptiveCard", + "version": "1.2", + "body": [ + { + "type": "TextBlock", + "size": "Large", + "weight": "Bolder", + "color": "<% if (success) { %>Good<% } else { %>Attention<%} %>", + "text": "nf-core/phaseimpute v${version} - ${runName}", + "wrap": true + }, + { + "type": "TextBlock", + "spacing": "None", + "text": "Completed at ${dateComplete} (duration: ${duration})", + "isSubtle": true, + "wrap": true + }, + { + "type": "TextBlock", + "text": "<% if (success) { %>Pipeline completed successfully!<% } else { %>Pipeline completed with errors. The full error message was: ${errorReport}.<% } %>", + "wrap": true + }, + { + "type": "TextBlock", + "text": "The command used to launch the workflow was as follows:", + "wrap": true + }, + { + "type": "TextBlock", + "text": "${commandLine}", + "isSubtle": true, + "wrap": true + } + ], + "actions": [ + { + "type": "Action.ShowCard", + "title": "Pipeline Configuration", + "card": { + "type": "AdaptiveCard", + "\$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "body": [ + { + "type": "FactSet", + "facts": [<% out << summary.collect{ k,v -> "{\"title\": \"$k\", \"value\" : \"$v\"}"}.join(",\n") %> + ] + } + ] + } + } + ] + } + } + ] +} diff --git a/assets/email_template.html b/assets/email_template.html index 409966b2..d416687c 100644 --- a/assets/email_template.html +++ b/assets/email_template.html @@ -1,11 +1,10 @@ - - + nf-core/phaseimpute Pipeline Report @@ -13,7 +12,7 @@ -

nf-core/phaseimpute v${version}

+

nf-core/phaseimpute ${version}

Run Name: $runName

<% if (!success){ diff --git a/assets/email_template.txt b/assets/email_template.txt index d393d5dc..58b47420 100644 --- a/assets/email_template.txt +++ b/assets/email_template.txt @@ -4,9 +4,8 @@ |\\ | |__ __ / ` / \\ |__) |__ } { | \\| | \\__, \\__/ | \\ |___ \\`-._,-`-, `._,._,' - nf-core/phaseimpute v${version} + nf-core/phaseimpute ${version} ---------------------------------------------------- - Run Name: $runName <% if (success){ diff --git a/assets/methods_description_template.yml b/assets/methods_description_template.yml new file mode 100644 index 00000000..38b29c7e --- /dev/null +++ b/assets/methods_description_template.yml @@ -0,0 +1,29 @@ +id: "nf-core-phaseimpute-methods-description" +description: "Suggested text and references to use when describing pipeline usage within the methods section of a publication." +section_name: "nf-core/phaseimpute Methods Description" +section_href: "https://github.com/nf-core/phaseimpute" +plot_type: "html" +## TODO nf-core: Update the HTML below to your preferred methods description, e.g. add publication citation for this pipeline +## You inject any metadata in the Nextflow '${workflow}' object +data: | +

Methods

+

Data was processed using nf-core/phaseimpute v${workflow.manifest.version} ${doi_text} of the nf-core collection of workflows (Ewels et al., 2020), utilising reproducible software environments from the Bioconda (Grüning et al., 2018) and Biocontainers (da Veiga Leprevost et al., 2017) projects.

+

The pipeline was executed with Nextflow v${workflow.nextflow.version} (Di Tommaso et al., 2017) with the following command:

+
${workflow.commandLine}
+

${tool_citations}

+

References

+
    +
  • Di Tommaso, P., Chatzou, M., Floden, E. W., Barja, P. P., Palumbo, E., & Notredame, C. (2017). Nextflow enables reproducible computational workflows. Nature Biotechnology, 35(4), 316-319. doi: 10.1038/nbt.3820
  • +
  • Ewels, P. A., Peltzer, A., Fillinger, S., Patel, H., Alneberg, J., Wilm, A., Garcia, M. U., Di Tommaso, P., & Nahnsen, S. (2020). The nf-core framework for community-curated bioinformatics pipelines. Nature Biotechnology, 38(3), 276-278. doi: 10.1038/s41587-020-0439-x
  • +
  • Grüning, B., Dale, R., Sjödin, A., Chapman, B. A., Rowe, J., Tomkins-Tinch, C. H., Valieris, R., Köster, J., & Bioconda Team. (2018). Bioconda: sustainable and comprehensive software distribution for the life sciences. Nature Methods, 15(7), 475–476. doi: 10.1038/s41592-018-0046-7
  • +
  • da Veiga Leprevost, F., Grüning, B. A., Alves Aflitos, S., Röst, H. L., Uszkoreit, J., Barsnes, H., Vaudel, M., Moreno, P., Gatto, L., Weber, J., Bai, M., Jimenez, R. C., Sachsenberg, T., Pfeuffer, J., Vera Alvarez, R., Griss, J., Nesvizhskii, A. I., & Perez-Riverol, Y. (2017). BioContainers: an open-source and community-driven framework for software standardization. Bioinformatics (Oxford, England), 33(16), 2580–2582. doi: 10.1093/bioinformatics/btx192
  • + ${tool_bibliography} +
+
+
Notes:
+
    + ${nodoi_text} +
  • The command above does not include parameters contained in any configs or profiles that may have been used. Ensure the config file is also uploaded with your publication!
  • +
  • You should also cite all software used within this run. Check the "Software Versions" of this report to get version information.
  • +
+
diff --git a/assets/multiqc_config.yaml b/assets/multiqc_config.yaml deleted file mode 100644 index 95e1da0a..00000000 --- a/assets/multiqc_config.yaml +++ /dev/null @@ -1,11 +0,0 @@ -report_comment: > - This report has been generated by the nf-core/phaseimpute - analysis pipeline. For information about how to interpret these results, please see the - documentation. -report_section_order: - software_versions: - order: -1000 - nf-core-phaseimpute-summary: - order: -1001 - -export_plots: true diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml new file mode 100644 index 00000000..465e50f4 --- /dev/null +++ b/assets/multiqc_config.yml @@ -0,0 +1,13 @@ +report_comment: > + This report has been generated by the nf-core/phaseimpute + analysis pipeline. For information about how to interpret these results, please see the + documentation. +report_section_order: + "nf-core-phaseimpute-methods-description": + order: -1000 + software_versions: + order: -1001 + "nf-core-phaseimpute-summary": + order: -1002 + +export_plots: true diff --git a/assets/nf-core-phaseimpute_logo.png b/assets/nf-core-phaseimpute_logo.png deleted file mode 100644 index 7637235747f9cebbf6e552baecec7cb1957ed3ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10842 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 diff --git a/assets/nf-core-phaseimpute_logo_light.png b/assets/nf-core-phaseimpute_logo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..0ee5e742e693f50b411e84f07cce723c525b2c5b GIT binary patch literal 107250 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

Mt4<|ynGIxj7@A&VAKpvn&6-;0%>%o!S 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 -Content-Disposition: inline; filename="nf-core-phaseimpute_logo.png" +Content-Disposition: inline; filename="nf-core-phaseimpute_logo_light.png" -<% out << new File("$baseDir/assets/nf-core-phaseimpute_logo.png"). - bytes. - encodeBase64(). - toString(). - tokenize( '\n' )*. - toList()*. - collate( 76 )*. - collect { it.join() }. - flatten(). - join( '\n' ) %> +<% out << new File("$projectDir/assets/nf-core-phaseimpute_logo_light.png"). + bytes. + encodeBase64(). + toString(). + tokenize( '\n' )*. + toList()*. + collate( 76 )*. + collect { it.join() }. + flatten(). + join( '\n' ) %> <% if (mqcFile){ @@ -37,15 +37,15 @@ Content-ID: Content-Disposition: attachment; filename=\"${mqcFileObj.getName()}\" ${mqcFileObj. - bytes. - encodeBase64(). - toString(). - tokenize( '\n' )*. - toList()*. - collate( 76 )*. - collect { it.join() }. - flatten(). - join( '\n' )} + bytes. + encodeBase64(). + toString(). + tokenize( '\n' )*. + toList()*. + collate( 76 )*. + collect { it.join() }. + flatten(). + join( '\n' )} """ }} %> diff --git a/assets/slackreport.json b/assets/slackreport.json new file mode 100644 index 00000000..264f3bed --- /dev/null +++ b/assets/slackreport.json @@ -0,0 +1,34 @@ +{ + "attachments": [ + { + "fallback": "Plain-text summary of the attachment.", + "color": "<% if (success) { %>good<% } else { %>danger<%} %>", + "author_name": "nf-core/phaseimpute ${version} - ${runName}", + "author_icon": "https://www.nextflow.io/docs/latest/_static/favicon.ico", + "text": "<% if (success) { %>Pipeline completed successfully!<% } else { %>Pipeline completed with errors<% } %>", + "fields": [ + { + "title": "Command used to launch the workflow", + "value": "```${commandLine}```", + "short": false + } + <% + if (!success) { %> + , + { + "title": "Full error message", + "value": "```${errorReport}```", + "short": false + }, + { + "title": "Pipeline configuration", + "value": "<% out << summary.collect{ k,v -> k == "hook_url" ? "_${k}_: (_hidden_)" : ( ( v.class.toString().contains('Path') || ( v.class.toString().contains('String') && v.contains('/') ) ) ? "_${k}_: `${v}`" : (v.class.toString().contains('DateTime') ? ("_${k}_: " + v.format(java.time.format.DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.MEDIUM))) : "_${k}_: ${v}") ) }.join(",\n") %>", + "short": false + } + <% } + %> + ], + "footer": "Completed at <% out << dateComplete.format(java.time.format.DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.MEDIUM)) %> (duration: ${duration})" + } + ] +} diff --git a/bin/check_samplesheet.py b/bin/check_samplesheet.py new file mode 100755 index 00000000..4a758fe0 --- /dev/null +++ b/bin/check_samplesheet.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python + + +"""Provide a command line tool to validate and transform tabular samplesheets.""" + + +import argparse +import csv +import logging +import sys +from collections import Counter +from pathlib import Path + +logger = logging.getLogger() + + +class RowChecker: + """ + Define a service that can validate and transform each given row. + + Attributes: + modified (list): A list of dicts, where each dict corresponds to a previously + validated and transformed row. The order of rows is maintained. + + """ + + VALID_FORMATS = ( + ".fq.gz", + ".fastq.gz", + ) + + def __init__( + self, + sample_col="sample", + first_col="fastq_1", + second_col="fastq_2", + single_col="single_end", + **kwargs, + ): + """ + Initialize the row checker with the expected column names. + + Args: + sample_col (str): The name of the column that contains the sample name + (default "sample"). + first_col (str): The name of the column that contains the first (or only) + FASTQ file path (default "fastq_1"). + second_col (str): The name of the column that contains the second (if any) + FASTQ file path (default "fastq_2"). + single_col (str): The name of the new column that will be inserted and + records whether the sample contains single- or paired-end sequencing + reads (default "single_end"). + + """ + super().__init__(**kwargs) + self._sample_col = sample_col + self._first_col = first_col + self._second_col = second_col + self._single_col = single_col + self._seen = set() + self.modified = [] + + def validate_and_transform(self, row): + """ + Perform all validations on the given row and insert the read pairing status. + + Args: + row (dict): A mapping from column headers (keys) to elements of that row + (values). + + """ + self._validate_sample(row) + self._validate_first(row) + self._validate_second(row) + self._validate_pair(row) + self._seen.add((row[self._sample_col], row[self._first_col])) + self.modified.append(row) + + def _validate_sample(self, row): + """Assert that the sample name exists and convert spaces to underscores.""" + if len(row[self._sample_col]) <= 0: + raise AssertionError("Sample input is required.") + # Sanitize samples slightly. + row[self._sample_col] = row[self._sample_col].replace(" ", "_") + + def _validate_first(self, row): + """Assert that the first FASTQ entry is non-empty and has the right format.""" + if len(row[self._first_col]) <= 0: + raise AssertionError("At least the first FASTQ file is required.") + self._validate_fastq_format(row[self._first_col]) + + def _validate_second(self, row): + """Assert that the second FASTQ entry has the right format if it exists.""" + if len(row[self._second_col]) > 0: + self._validate_fastq_format(row[self._second_col]) + + def _validate_pair(self, row): + """Assert that read pairs have the same file extension. Report pair status.""" + if row[self._first_col] and row[self._second_col]: + row[self._single_col] = False + first_col_suffix = Path(row[self._first_col]).suffixes[-2:] + second_col_suffix = Path(row[self._second_col]).suffixes[-2:] + if first_col_suffix != second_col_suffix: + raise AssertionError("FASTQ pairs must have the same file extensions.") + else: + row[self._single_col] = True + + def _validate_fastq_format(self, filename): + """Assert that a given filename has one of the expected FASTQ extensions.""" + if not any(filename.endswith(extension) for extension in self.VALID_FORMATS): + raise AssertionError( + f"The FASTQ file has an unrecognized extension: {filename}\n" + f"It should be one of: {', '.join(self.VALID_FORMATS)}" + ) + + def validate_unique_samples(self): + """ + Assert that the combination of sample name and FASTQ filename is unique. + + In addition to the validation, also rename all samples to have a suffix of _T{n}, where n is the + number of times the same sample exist, but with different FASTQ files, e.g., multiple runs per experiment. + + """ + if len(self._seen) != len(self.modified): + raise AssertionError("The pair of sample name and FASTQ must be unique.") + seen = Counter() + for row in self.modified: + sample = row[self._sample_col] + seen[sample] += 1 + row[self._sample_col] = f"{sample}_T{seen[sample]}" + + +def read_head(handle, num_lines=10): + """Read the specified number of lines from the current position in the file.""" + lines = [] + for idx, line in enumerate(handle): + if idx == num_lines: + break + lines.append(line) + return "".join(lines) + + +def sniff_format(handle): + """ + Detect the tabular format. + + Args: + handle (text file): A handle to a `text file`_ object. The read position is + expected to be at the beginning (index 0). + + Returns: + csv.Dialect: The detected tabular format. + + .. _text file: + https://docs.python.org/3/glossary.html#term-text-file + + """ + peek = read_head(handle) + handle.seek(0) + sniffer = csv.Sniffer() + dialect = sniffer.sniff(peek) + return dialect + + +def check_samplesheet(file_in, file_out): + """ + Check that the tabular samplesheet has the structure expected by nf-core pipelines. + + Validate the general shape of the table, expected columns, and each row. Also add + an additional column which records whether one or two FASTQ reads were found. + + Args: + file_in (pathlib.Path): The given tabular samplesheet. The format can be either + CSV, TSV, or any other format automatically recognized by ``csv.Sniffer``. + file_out (pathlib.Path): Where the validated and transformed samplesheet should + be created; always in CSV format. + + Example: + This function checks that the samplesheet follows the following structure, + see also the `viral recon samplesheet`_:: + + sample,fastq_1,fastq_2 + SAMPLE_PE,SAMPLE_PE_RUN1_1.fastq.gz,SAMPLE_PE_RUN1_2.fastq.gz + SAMPLE_PE,SAMPLE_PE_RUN2_1.fastq.gz,SAMPLE_PE_RUN2_2.fastq.gz + SAMPLE_SE,SAMPLE_SE_RUN1_1.fastq.gz, + + .. _viral recon samplesheet: + https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv + + """ + required_columns = {"sample", "fastq_1", "fastq_2"} + # See https://docs.python.org/3.9/library/csv.html#id3 to read up on `newline=""`. + with file_in.open(newline="") as in_handle: + reader = csv.DictReader(in_handle, dialect=sniff_format(in_handle)) + # Validate the existence of the expected header columns. + if not required_columns.issubset(reader.fieldnames): + req_cols = ", ".join(required_columns) + logger.critical(f"The sample sheet **must** contain these column headers: {req_cols}.") + sys.exit(1) + # Validate each row. + checker = RowChecker() + for i, row in enumerate(reader): + try: + checker.validate_and_transform(row) + except AssertionError as error: + logger.critical(f"{str(error)} On line {i + 2}.") + sys.exit(1) + checker.validate_unique_samples() + header = list(reader.fieldnames) + header.insert(1, "single_end") + # See https://docs.python.org/3.9/library/csv.html#id3 to read up on `newline=""`. + with file_out.open(mode="w", newline="") as out_handle: + writer = csv.DictWriter(out_handle, header, delimiter=",") + writer.writeheader() + for row in checker.modified: + writer.writerow(row) + + +def parse_args(argv=None): + """Define and immediately parse command line arguments.""" + parser = argparse.ArgumentParser( + description="Validate and transform a tabular samplesheet.", + epilog="Example: python check_samplesheet.py samplesheet.csv samplesheet.valid.csv", + ) + parser.add_argument( + "file_in", + metavar="FILE_IN", + type=Path, + help="Tabular input samplesheet in CSV or TSV format.", + ) + parser.add_argument( + "file_out", + metavar="FILE_OUT", + type=Path, + help="Transformed output samplesheet in CSV format.", + ) + parser.add_argument( + "-l", + "--log-level", + help="The desired log level (default WARNING).", + choices=("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"), + default="WARNING", + ) + return parser.parse_args(argv) + + +def main(argv=None): + """Coordinate argument parsing and program execution.""" + args = parse_args(argv) + logging.basicConfig(level=args.log_level, format="[%(levelname)s] %(message)s") + if not args.file_in.is_file(): + logger.error(f"The given input file {args.file_in} was not found!") + sys.exit(2) + args.file_out.parent.mkdir(parents=True, exist_ok=True) + check_samplesheet(args.file_in, args.file_out) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bin/markdown_to_html.py b/bin/markdown_to_html.py deleted file mode 100644 index 57cc4263..00000000 --- a/bin/markdown_to_html.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function -import argparse -import markdown -import os -import sys - -def convert_markdown(in_fn): - input_md = open(in_fn, mode="r", encoding="utf-8").read() - html = markdown.markdown( - "[TOC]\n" + input_md, - extensions = [ - 'pymdownx.extra', - 'pymdownx.b64', - 'pymdownx.highlight', - 'pymdownx.emoji', - 'pymdownx.tilde', - 'toc' - ], - extension_configs = { - 'pymdownx.b64': { - 'base_path': os.path.dirname(in_fn) - }, - 'pymdownx.highlight': { - 'noclasses': True - }, - 'toc': { - 'title': 'Table of Contents' - } - } - ) - return html - -def wrap_html(contents): - header = """ - - - - - -

- """ - footer = """ -
- - - """ - return header + contents + footer - - -def parse_args(args=None): - parser = argparse.ArgumentParser() - parser.add_argument('mdfile', type=argparse.FileType('r'), nargs='?', - help='File to convert. Defaults to stdin.') - parser.add_argument('-o', '--out', type=argparse.FileType('w'), - default=sys.stdout, - help='Output file name. Defaults to stdout.') - return parser.parse_args(args) - -def main(args=None): - args = parse_args(args) - converted_md = convert_markdown(args.mdfile.name) - html = wrap_html(converted_md) - args.out.write(html) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bin/scrape_software_versions.py b/bin/scrape_software_versions.py deleted file mode 100644 index 1692a218..00000000 --- a/bin/scrape_software_versions.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function -from collections import OrderedDict -import re - -# TODO nf-core: Add additional regexes for new tools in process get_software_versions -regexes = { - 'nf-core/phaseimpute': ['v_pipeline.txt', r"(\S+)"], - 'Nextflow': ['v_nextflow.txt', r"(\S+)"], - 'FastQC': ['v_fastqc.txt', r"FastQC v(\S+)"], - 'MultiQC': ['v_multiqc.txt', r"multiqc, version (\S+)"], -} -results = OrderedDict() -results['nf-core/phaseimpute'] = 'N/A' -results['Nextflow'] = 'N/A' -results['FastQC'] = 'N/A' -results['MultiQC'] = 'N/A' - -# Search each file using its regex -for k, v in regexes.items(): - try: - with open(v[0]) as x: - versions = x.read() - match = re.search(v[1], versions) - if match: - results[k] = "v{}".format(match.group(1)) - except IOError: - results[k] = False - -# Remove software set to false in results -for k in list(results): - if not results[k]: - del(results[k]) - -# Dump to YAML -print (''' -id: 'software_versions' -section_name: 'nf-core/phaseimpute Software Versions' -section_href: 'https://github.com/nf-core/phaseimpute' -plot_type: 'html' -description: 'are collected at run time from the software output.' -data: | -
-''') -for k,v in results.items(): - print("
{}
{}
".format(k,v)) -print ("
") - -# Write out regexes as csv file: -with open('software_versions.csv', 'w') as f: - for k,v in results.items(): - f.write("{}\t{}\n".format(k,v)) diff --git a/conf/base.config b/conf/base.config index c8bda3c9..6533ffab 100644 --- a/conf/base.config +++ b/conf/base.config @@ -1,51 +1,65 @@ /* - * ------------------------------------------------- - * nf-core/phaseimpute Nextflow base config file - * ------------------------------------------------- - * A 'blank slate' config file, appropriate for general - * use on most high performace compute environments. - * Assumes that all software is installed and available - * on the PATH. Runs in `local` mode - all jobs will be - * run on the logged in environment. - */ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + nf-core/phaseimpute Nextflow base config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A 'blank slate' config file, appropriate for general use on most high performance + compute environments. Assumes that all software is installed and available on + the PATH. Runs in `local` mode - all jobs will be run on the logged in environment. +---------------------------------------------------------------------------------------- +*/ process { - // TODO nf-core: Check the defaults for all processes - cpus = { check_max( 1 * task.attempt, 'cpus' ) } - memory = { check_max( 7.GB * task.attempt, 'memory' ) } - time = { check_max( 4.h * task.attempt, 'time' ) } + // TODO nf-core: Check the defaults for all processes + cpus = { check_max( 1 * task.attempt, 'cpus' ) } + memory = { check_max( 6.GB * task.attempt, 'memory' ) } + time = { check_max( 4.h * task.attempt, 'time' ) } - errorStrategy = { task.exitStatus in [143,137,104,134,139] ? 'retry' : 'finish' } - maxRetries = 1 - maxErrors = '-1' + errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } + maxRetries = 1 + maxErrors = '-1' - // Process-specific resource requirements - // NOTE - Only one of the labels below are used in the fastqc process in the main script. - // If possible, it would be nice to keep the same label naming convention when - // adding in your processes. - // TODO nf-core: Customise requirements for specific processes. - // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors - withLabel:process_low { - cpus = { check_max( 2 * task.attempt, 'cpus' ) } - memory = { check_max( 14.GB * task.attempt, 'memory' ) } - time = { check_max( 6.h * task.attempt, 'time' ) } - } - withLabel:process_medium { - cpus = { check_max( 6 * task.attempt, 'cpus' ) } - memory = { check_max( 42.GB * task.attempt, 'memory' ) } - time = { check_max( 8.h * task.attempt, 'time' ) } - } - withLabel:process_high { - cpus = { check_max( 12 * task.attempt, 'cpus' ) } - memory = { check_max( 84.GB * task.attempt, 'memory' ) } - time = { check_max( 10.h * task.attempt, 'time' ) } - } - withLabel:process_long { - time = { check_max( 20.h * task.attempt, 'time' ) } - } - withName:get_software_versions { - cache = false - } - + // Process-specific resource requirements + // NOTE - Please try and re-use the labels below as much as possible. + // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. + // If possible, it would be nice to keep the same label naming convention when + // adding in your local modules too. + // TODO nf-core: Customise requirements for specific processes. + // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors + withLabel:process_single { + cpus = { check_max( 1 , 'cpus' ) } + memory = { check_max( 6.GB * task.attempt, 'memory' ) } + time = { check_max( 4.h * task.attempt, 'time' ) } + } + withLabel:process_low { + cpus = { check_max( 2 * task.attempt, 'cpus' ) } + memory = { check_max( 12.GB * task.attempt, 'memory' ) } + time = { check_max( 4.h * task.attempt, 'time' ) } + } + withLabel:process_medium { + cpus = { check_max( 6 * task.attempt, 'cpus' ) } + memory = { check_max( 36.GB * task.attempt, 'memory' ) } + time = { check_max( 8.h * task.attempt, 'time' ) } + } + withLabel:process_high { + cpus = { check_max( 12 * task.attempt, 'cpus' ) } + memory = { check_max( 72.GB * task.attempt, 'memory' ) } + time = { check_max( 16.h * task.attempt, 'time' ) } + } + withLabel:process_long { + time = { check_max( 20.h * task.attempt, 'time' ) } + } + withLabel:process_high_memory { + memory = { check_max( 200.GB * task.attempt, 'memory' ) } + } + withLabel:error_ignore { + errorStrategy = 'ignore' + } + withLabel:error_retry { + errorStrategy = 'retry' + maxRetries = 2 + } + withName:CUSTOM_DUMPSOFTWAREVERSIONS { + cache = false + } } diff --git a/conf/igenomes.config b/conf/igenomes.config index 2de92422..3f114377 100644 --- a/conf/igenomes.config +++ b/conf/igenomes.config @@ -1,420 +1,440 @@ /* - * ------------------------------------------------- - * Nextflow config file for iGenomes paths - * ------------------------------------------------- - * Defines reference genomes, using iGenome paths - * Can be used by any config that customises the base - * path using $params.igenomes_base / --igenomes_base - */ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for iGenomes paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines reference genomes using iGenome paths. + Can be used by any config that customises the base path using: + $params.igenomes_base / --igenomes_base +---------------------------------------------------------------------------------------- +*/ params { - // illumina iGenomes reference file paths - genomes { - 'GRCh37' { - fasta = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Annotation/README.txt" - mito_name = "MT" - macs_gsize = "2.7e9" - blacklist = "${baseDir}/assets/blacklists/GRCh37-blacklist.bed" + // illumina iGenomes reference file paths + genomes { + 'GRCh37' { + fasta = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Homo_sapiens/Ensembl/GRCh37/Annotation/README.txt" + mito_name = "MT" + macs_gsize = "2.7e9" + blacklist = "${projectDir}/assets/blacklists/GRCh37-blacklist.bed" + } + 'GRCh38' { + fasta = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Annotation/Genes/genes.bed" + mito_name = "chrM" + macs_gsize = "2.7e9" + blacklist = "${projectDir}/assets/blacklists/hg38-blacklist.bed" + } + 'CHM13' { + fasta = "${params.igenomes_base}/Homo_sapiens/UCSC/CHM13/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/UCSC/CHM13/Sequence/BWAIndex/" + bwamem2 = "${params.igenomes_base}/Homo_sapiens/UCSC/CHM13/Sequence/BWAmem2Index/" + gtf = "${params.igenomes_base}/Homo_sapiens/NCBI/CHM13/Annotation/Genes/genes.gtf" + gff = "ftp://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/009/914/755/GCF_009914755.1_T2T-CHM13v2.0/GCF_009914755.1_T2T-CHM13v2.0_genomic.gff.gz" + mito_name = "chrM" + } + 'GRCm38' { + fasta = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Annotation/README.txt" + mito_name = "MT" + macs_gsize = "1.87e9" + blacklist = "${projectDir}/assets/blacklists/GRCm38-blacklist.bed" + } + 'TAIR10' { + fasta = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Annotation/README.txt" + mito_name = "Mt" + } + 'EB2' { + fasta = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Annotation/README.txt" + } + 'UMD3.1' { + fasta = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Annotation/README.txt" + mito_name = "MT" + } + 'WBcel235' { + fasta = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Annotation/Genes/genes.bed" + mito_name = "MtDNA" + macs_gsize = "9e7" + } + 'CanFam3.1' { + fasta = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Annotation/README.txt" + mito_name = "MT" + } + 'GRCz10' { + fasta = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Annotation/Genes/genes.bed" + mito_name = "MT" + } + 'BDGP6' { + fasta = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Annotation/Genes/genes.bed" + mito_name = "M" + macs_gsize = "1.2e8" + } + 'EquCab2' { + fasta = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Annotation/README.txt" + mito_name = "MT" + } + 'EB1' { + fasta = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Annotation/README.txt" + } + 'Galgal4' { + fasta = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Annotation/Genes/genes.bed" + mito_name = "MT" + } + 'Gm01' { + fasta = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Annotation/README.txt" + } + 'Mmul_1' { + fasta = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Annotation/README.txt" + mito_name = "MT" + } + 'IRGSP-1.0' { + fasta = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Annotation/Genes/genes.bed" + mito_name = "Mt" + } + 'CHIMP2.1.4' { + fasta = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Annotation/README.txt" + mito_name = "MT" + } + 'Rnor_5.0' { + fasta = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_5.0/Annotation/Genes/genes.bed" + mito_name = "MT" + } + 'Rnor_6.0' { + fasta = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Annotation/Genes/genes.bed" + mito_name = "MT" + } + 'R64-1-1' { + fasta = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Annotation/Genes/genes.bed" + mito_name = "MT" + macs_gsize = "1.2e7" + } + 'EF2' { + fasta = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Annotation/README.txt" + mito_name = "MT" + macs_gsize = "1.21e7" + } + 'Sbi1' { + fasta = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Annotation/README.txt" + } + 'Sscrofa10.2' { + fasta = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Annotation/README.txt" + mito_name = "MT" + } + 'AGPv3' { + fasta = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Annotation/Genes/genes.bed" + mito_name = "Mt" + } + 'hg38' { + fasta = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Annotation/Genes/genes.bed" + mito_name = "chrM" + macs_gsize = "2.7e9" + blacklist = "${projectDir}/assets/blacklists/hg38-blacklist.bed" + } + 'hg19' { + fasta = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Annotation/README.txt" + mito_name = "chrM" + macs_gsize = "2.7e9" + blacklist = "${projectDir}/assets/blacklists/hg19-blacklist.bed" + } + 'mm10' { + fasta = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Annotation/README.txt" + mito_name = "chrM" + macs_gsize = "1.87e9" + blacklist = "${projectDir}/assets/blacklists/mm10-blacklist.bed" + } + 'bosTau8' { + fasta = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Annotation/Genes/genes.bed" + mito_name = "chrM" + } + 'ce10' { + fasta = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Annotation/README.txt" + mito_name = "chrM" + macs_gsize = "9e7" + } + 'canFam3' { + fasta = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Annotation/README.txt" + mito_name = "chrM" + } + 'danRer10' { + fasta = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Annotation/Genes/genes.bed" + mito_name = "chrM" + macs_gsize = "1.37e9" + } + 'dm6' { + fasta = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Annotation/Genes/genes.bed" + mito_name = "chrM" + macs_gsize = "1.2e8" + } + 'equCab2' { + fasta = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Annotation/README.txt" + mito_name = "chrM" + } + 'galGal4' { + fasta = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Annotation/README.txt" + mito_name = "chrM" + } + 'panTro4' { + fasta = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Annotation/README.txt" + mito_name = "chrM" + } + 'rn6' { + fasta = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Annotation/Genes/genes.bed" + mito_name = "chrM" + } + 'sacCer3' { + fasta = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/BismarkIndex/" + readme = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Annotation/README.txt" + mito_name = "chrM" + macs_gsize = "1.2e7" + } + 'susScr3' { + fasta = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/WholeGenomeFasta/genome.fa" + bwa = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/BWAIndex/version0.6.0/" + bowtie2 = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/Bowtie2Index/" + star = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/STARIndex/" + bismark = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/BismarkIndex/" + gtf = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Annotation/Genes/genes.gtf" + bed12 = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Annotation/Genes/genes.bed" + readme = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Annotation/README.txt" + mito_name = "chrM" + } } - 'GRCh38' { - fasta = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Homo_sapiens/NCBI/GRCh38/Annotation/Genes/genes.bed" - mito_name = "chrM" - macs_gsize = "2.7e9" - blacklist = "${baseDir}/assets/blacklists/hg38-blacklist.bed" - } - 'GRCm38' { - fasta = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Mus_musculus/Ensembl/GRCm38/Annotation/README.txt" - mito_name = "MT" - macs_gsize = "1.87e9" - blacklist = "${baseDir}/assets/blacklists/GRCm38-blacklist.bed" - } - 'TAIR10' { - fasta = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Arabidopsis_thaliana/Ensembl/TAIR10/Annotation/README.txt" - mito_name = "Mt" - } - 'EB2' { - fasta = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Bacillus_subtilis_168/Ensembl/EB2/Annotation/README.txt" - } - 'UMD3.1' { - fasta = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Bos_taurus/Ensembl/UMD3.1/Annotation/README.txt" - mito_name = "MT" - } - 'WBcel235' { - fasta = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Caenorhabditis_elegans/Ensembl/WBcel235/Annotation/Genes/genes.bed" - mito_name = "MtDNA" - macs_gsize = "9e7" - } - 'CanFam3.1' { - fasta = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Canis_familiaris/Ensembl/CanFam3.1/Annotation/README.txt" - mito_name = "MT" - } - 'GRCz10' { - fasta = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Danio_rerio/Ensembl/GRCz10/Annotation/Genes/genes.bed" - mito_name = "MT" - } - 'BDGP6' { - fasta = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Drosophila_melanogaster/Ensembl/BDGP6/Annotation/Genes/genes.bed" - mito_name = "M" - macs_gsize = "1.2e8" - } - 'EquCab2' { - fasta = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Equus_caballus/Ensembl/EquCab2/Annotation/README.txt" - mito_name = "MT" - } - 'EB1' { - fasta = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Escherichia_coli_K_12_DH10B/Ensembl/EB1/Annotation/README.txt" - } - 'Galgal4' { - fasta = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Gallus_gallus/Ensembl/Galgal4/Annotation/Genes/genes.bed" - mito_name = "MT" - } - 'Gm01' { - fasta = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Glycine_max/Ensembl/Gm01/Annotation/README.txt" - } - 'Mmul_1' { - fasta = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Macaca_mulatta/Ensembl/Mmul_1/Annotation/README.txt" - mito_name = "MT" - } - 'IRGSP-1.0' { - fasta = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Oryza_sativa_japonica/Ensembl/IRGSP-1.0/Annotation/Genes/genes.bed" - mito_name = "Mt" - } - 'CHIMP2.1.4' { - fasta = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Pan_troglodytes/Ensembl/CHIMP2.1.4/Annotation/README.txt" - mito_name = "MT" - } - 'Rnor_6.0' { - fasta = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Rattus_norvegicus/Ensembl/Rnor_6.0/Annotation/Genes/genes.bed" - mito_name = "MT" - } - 'R64-1-1' { - fasta = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Saccharomyces_cerevisiae/Ensembl/R64-1-1/Annotation/Genes/genes.bed" - mito_name = "MT" - macs_gsize = "1.2e7" - } - 'EF2' { - fasta = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Schizosaccharomyces_pombe/Ensembl/EF2/Annotation/README.txt" - mito_name = "MT" - macs_gsize = "1.21e7" - } - 'Sbi1' { - fasta = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Sorghum_bicolor/Ensembl/Sbi1/Annotation/README.txt" - } - 'Sscrofa10.2' { - fasta = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Sus_scrofa/Ensembl/Sscrofa10.2/Annotation/README.txt" - mito_name = "MT" - } - 'AGPv3' { - fasta = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Zea_mays/Ensembl/AGPv3/Annotation/Genes/genes.bed" - mito_name = "Mt" - } - 'hg38' { - fasta = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg38/Annotation/Genes/genes.bed" - mito_name = "chrM" - macs_gsize = "2.7e9" - blacklist = "${baseDir}/assets/blacklists/hg38-blacklist.bed" - } - 'hg19' { - fasta = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Homo_sapiens/UCSC/hg19/Annotation/README.txt" - mito_name = "chrM" - macs_gsize = "2.7e9" - blacklist = "${baseDir}/assets/blacklists/hg19-blacklist.bed" - } - 'mm10' { - fasta = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Mus_musculus/UCSC/mm10/Annotation/README.txt" - mito_name = "chrM" - macs_gsize = "1.87e9" - blacklist = "${baseDir}/assets/blacklists/mm10-blacklist.bed" - } - 'bosTau8' { - fasta = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Bos_taurus/UCSC/bosTau8/Annotation/Genes/genes.bed" - mito_name = "chrM" - } - 'ce10' { - fasta = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Caenorhabditis_elegans/UCSC/ce10/Annotation/README.txt" - mito_name = "chrM" - macs_gsize = "9e7" - } - 'canFam3' { - fasta = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Canis_familiaris/UCSC/canFam3/Annotation/README.txt" - mito_name = "chrM" - } - 'danRer10' { - fasta = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Danio_rerio/UCSC/danRer10/Annotation/Genes/genes.bed" - mito_name = "chrM" - } - 'dm6' { - fasta = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Drosophila_melanogaster/UCSC/dm6/Annotation/Genes/genes.bed" - mito_name = "chrM" - macs_gsize = "1.2e8" - } - 'equCab2' { - fasta = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Equus_caballus/UCSC/equCab2/Annotation/README.txt" - mito_name = "chrM" - } - 'galGal4' { - fasta = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Gallus_gallus/UCSC/galGal4/Annotation/README.txt" - mito_name = "chrM" - } - 'panTro4' { - fasta = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Pan_troglodytes/UCSC/panTro4/Annotation/README.txt" - mito_name = "chrM" - } - 'rn6' { - fasta = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Rattus_norvegicus/UCSC/rn6/Annotation/Genes/genes.bed" - mito_name = "chrM" - } - 'sacCer3' { - fasta = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Sequence/BismarkIndex/" - readme = "${params.igenomes_base}/Saccharomyces_cerevisiae/UCSC/sacCer3/Annotation/README.txt" - mito_name = "chrM" - macs_gsize = "1.2e7" - } - 'susScr3' { - fasta = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/WholeGenomeFasta/genome.fa" - bwa = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/BWAIndex/genome.fa" - bowtie2 = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/Bowtie2Index/" - star = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/STARIndex/" - bismark = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Sequence/BismarkIndex/" - gtf = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Annotation/Genes/genes.gtf" - bed12 = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Annotation/Genes/genes.bed" - readme = "${params.igenomes_base}/Sus_scrofa/UCSC/susScr3/Annotation/README.txt" - mito_name = "chrM" - } - } } diff --git a/conf/modules.config b/conf/modules.config new file mode 100644 index 00000000..d91c6aba --- /dev/null +++ b/conf/modules.config @@ -0,0 +1,50 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Config file for defining DSL2 per module options and publishing paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Available keys to override module options: + ext.args = Additional arguments appended to command in module. + ext.args2 = Second set of arguments appended to command in module (multi-tool modules). + ext.args3 = Third set of arguments appended to command in module (multi-tool modules). + ext.prefix = File name prefix for output files. +---------------------------------------------------------------------------------------- +*/ + +process { + + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + + withName: SAMPLESHEET_CHECK { + publishDir = [ + path: { "${params.outdir}/pipeline_info" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName: FASTQC { + ext.args = '--quiet' + } + + withName: CUSTOM_DUMPSOFTWAREVERSIONS { + publishDir = [ + path: { "${params.outdir}/pipeline_info" }, + mode: params.publish_dir_mode, + pattern: '*_versions.yml' + ] + } + + withName: 'MULTIQC' { + ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } + publishDir = [ + path: { "${params.outdir}/multiqc" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + +} diff --git a/conf/test.config b/conf/test.config index bbfbd1c9..fbb2e5c5 100644 --- a/conf/test.config +++ b/conf/test.config @@ -1,26 +1,29 @@ /* - * ------------------------------------------------- - * Nextflow config file for running tests - * ------------------------------------------------- - * Defines bundled input files and everything required - * to run a fast and simple test. Use as follows: - * nextflow run nf-core/phaseimpute -profile test, - */ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running minimal tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a fast and simple pipeline test. + + Use as follows: + nextflow run nf-core/phaseimpute -profile test, --outdir + +---------------------------------------------------------------------------------------- +*/ params { - config_profile_name = 'Test profile' - config_profile_description = 'Minimal test dataset to check pipeline function' - // Limit resources so that this can run on GitHub Actions - max_cpus = 2 - max_memory = 6.GB - max_time = 48.h + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' + + // Limit resources so that this can run on GitHub Actions + max_cpus = 2 + max_memory = '6.GB' + max_time = '6.h' + + // Input data + // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv' - // Input data - // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets - // TODO nf-core: Give any required params for the test so that command line flags are not needed - single_end = false - readPaths = [ - ['Testdata', ['https://github.com/nf-core/test-datasets/raw/exoseq/testdata/Testdata_R1.tiny.fastq.gz', 'https://github.com/nf-core/test-datasets/raw/exoseq/testdata/Testdata_R2.tiny.fastq.gz']], - ['SRR389222', ['https://github.com/nf-core/test-datasets/raw/methylseq/testdata/SRR389222_sub1.fastq.gz', 'https://github.com/nf-core/test-datasets/raw/methylseq/testdata/SRR389222_sub2.fastq.gz']] - ] + // Genome references + genome = 'R64-1-1' } diff --git a/conf/test_full.config b/conf/test_full.config new file mode 100644 index 00000000..90d800db --- /dev/null +++ b/conf/test_full.config @@ -0,0 +1,24 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running full-size tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a full size pipeline test. + + Use as follows: + nextflow run nf-core/phaseimpute -profile test_full, --outdir + +---------------------------------------------------------------------------------------- +*/ + +params { + config_profile_name = 'Full test profile' + config_profile_description = 'Full test dataset to check pipeline function' + + // Input data for full size test + // TODO nf-core: Specify the paths to your full test data ( on nf-core/test-datasets or directly in repositories, e.g. SRA) + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_full_illumina_amplicon.csv' + + // Genome references + genome = 'R64-1-1' +} diff --git a/docs/README.md b/docs/README.md index c5e29a34..b04a3490 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,10 @@ # nf-core/phaseimpute: Documentation -The nf-core/phaseimpute documentation is split into the following files: +The nf-core/phaseimpute documentation is split into the following pages: -1. [Installation](https://nf-co.re/usage/installation) -2. Pipeline configuration - * [Local installation](https://nf-co.re/usage/local_installation) - * [Adding your own system config](https://nf-co.re/usage/adding_own_config) - * [Reference genomes](https://nf-co.re/usage/reference_genomes) -3. [Running the pipeline](usage.md) -4. [Output and how to interpret the results](output.md) -5. [Troubleshooting](https://nf-co.re/usage/troubleshooting) +- [Usage](usage.md) + - An overview of how the pipeline works, how to run it and a description of all of the different command-line flags. +- [Output](output.md) + - An overview of the different results produced by the pipeline and how to interpret them. + +You can find a lot more documentation about installing, configuring and running nf-core pipelines on the website: [https://nf-co.re](https://nf-co.re) diff --git a/docs/images/mqc_fastqc_adapter.png b/docs/images/mqc_fastqc_adapter.png new file mode 100755 index 0000000000000000000000000000000000000000..361d0e47acfb424dea1f326590d1eb2f6dfa26b5 GIT binary patch literal 23458 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 literal 0 HcmV?d00001 diff --git a/docs/images/mqc_fastqc_quality.png b/docs/images/mqc_fastqc_quality.png new file mode 100755 index 0000000000000000000000000000000000000000..a4b89bf56ab2ba88cab87841916eb680a816deae GIT binary patch literal 55769 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�)v~C(0C)c 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 M)78&qol`;+0Fu7JivR!s literal 0 HcmV?d00001 diff --git a/docs/images/nf-core-phaseimpute_logo.png b/docs/images/nf-core-phaseimpute_logo.png deleted file mode 100644 index e66b74f0823da074dbb9ea74e04fbe233d1b4871..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16403 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 qc%RgMiOA|XTmet{gS6E3)hbnOBK`*>uhj$q diff --git a/docs/images/nf-core-phaseimpute_logo_dark.png b/docs/images/nf-core-phaseimpute_logo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b3bd5aa7efe2cb0c1a26de6ca8931b7bcc63e406 GIT binary patch literal 27846 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 literal 0 HcmV?d00001 diff --git a/docs/images/nf-core-phaseimpute_logo_light.png b/docs/images/nf-core-phaseimpute_logo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..4f5070fea86b2b93716f854c5e6cf0faf87e4019 GIT binary patch literal 23674 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 literal 0 HcmV?d00001 diff --git a/docs/output.md b/docs/output.md index 1db00bdd..d7417cae 100644 --- a/docs/output.md +++ b/docs/output.md @@ -1,43 +1,71 @@ # nf-core/phaseimpute: Output +## Introduction + This document describes the output produced by the pipeline. Most of the plots are taken from the MultiQC report, which summarises results at the end of the pipeline. +The directories listed below will be created in the results directory after the pipeline has finished. All paths are relative to the top-level results directory. + ## Pipeline overview -The pipeline is built using [Nextflow](https://www.nextflow.io/) -and processes data using the following steps: +The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: + +- [FastQC](#fastqc) - Raw read QC +- [MultiQC](#multiqc) - Aggregate report describing results and QC from the whole pipeline +- [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution + +### FastQC + +
+Output files + +- `fastqc/` + - `*_fastqc.html`: FastQC report containing quality metrics. + - `*_fastqc.zip`: Zip archive containing the FastQC report, tab-delimited data file and plot images. + +
+ +[FastQC](http://www.bioinformatics.babraham.ac.uk/projects/fastqc/) gives general quality metrics about your sequenced reads. It provides information about the quality score distribution across your reads, per base sequence content (%A/T/G/C), adapter contamination and overrepresented sequences. For further reading and documentation see the [FastQC help pages](http://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/). + +![MultiQC - FastQC sequence counts plot](images/mqc_fastqc_counts.png) + +![MultiQC - FastQC mean quality scores plot](images/mqc_fastqc_quality.png) -* [FastQC](#fastqc) - read quality control -* [MultiQC](#multiqc) - aggregate report, describing results of the whole pipeline +![MultiQC - FastQC adapter content plot](images/mqc_fastqc_adapter.png) -## FastQC +:::note +The FastQC plots displayed in the MultiQC report shows _untrimmed_ reads. They may contain adapter sequence and potentially regions with low quality. +::: -[FastQC](http://www.bioinformatics.babraham.ac.uk/projects/fastqc/) gives general quality metrics about your reads. It provides information about the quality score distribution across your reads, the per base sequence content (%T/A/G/C). You get information about adapter contamination and other overrepresented sequences. +### MultiQC -For further reading and documentation see the [FastQC help](http://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/). +
+Output files -> **NB:** The FastQC plots displayed in the MultiQC report shows _untrimmed_ reads. They may contain adapter sequence and potentially regions with low quality. To see how your reads look after trimming, look at the FastQC reports in the `trim_galore` directory. +- `multiqc/` + - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. + - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. + - `multiqc_plots/`: directory containing static images from the report in various formats. -**Output directory: `results/fastqc`** +
-* `sample_fastqc.html` - * FastQC report, containing quality metrics for your untrimmed raw fastq files -* `zips/sample_fastqc.zip` - * zip file containing the FastQC report, tab-delimited data file and plot images +[MultiQC](http://multiqc.info) is a visualization tool that generates a single HTML report summarising all samples in your project. Most of the pipeline QC results are visualised in the report and further statistics are available in the report data directory. -## MultiQC +Results generated by MultiQC collate pipeline QC from supported tools e.g. FastQC. The pipeline has special steps which also allow the software versions to be reported in the MultiQC output for future traceability. For more information about how to use MultiQC reports, see . -[MultiQC](http://multiqc.info) is a visualisation tool that generates a single HTML report summarising all samples in your project. Most of the pipeline QC results are visualised in the report and further statistics are available in within the report data directory. +### Pipeline information -The pipeline has special steps which allow the software versions used to be reported in the MultiQC output for future traceability. +
+Output files -**Output directory: `results/multiqc`** +- `pipeline_info/` + - Reports generated by Nextflow: `execution_report.html`, `execution_timeline.html`, `execution_trace.txt` and `pipeline_dag.dot`/`pipeline_dag.svg`. + - Reports generated by the pipeline: `pipeline_report.html`, `pipeline_report.txt` and `software_versions.yml`. The `pipeline_report*` files will only be present if the `--email` / `--email_on_fail` parameter's are used when running the pipeline. + - Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. + - Parameters used by the pipeline run: `params.json`. -* `Project_multiqc_report.html` - * MultiQC report - a standalone HTML file that can be viewed in your web browser -* `Project_multiqc_data/` - * Directory containing parsed statistics from the different tools used in the pipeline +
-For more information about how to use MultiQC reports, see [http://multiqc.info](http://multiqc.info) +[Nextflow](https://www.nextflow.io/docs/latest/tracing.html) provides excellent functionality for generating various reports relevant to the running and execution of the pipeline. This will allow you to troubleshoot errors with the running of the pipeline, and also provide you with other information such as launch commands, run times and resource usage. diff --git a/docs/usage.md b/docs/usage.md index 1e0fb29d..f419339a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,331 +1,224 @@ # nf-core/phaseimpute: Usage -## Table of contents - -* [Table of contents](#table-of-contents) -* [Introduction](#introduction) -* [Running the pipeline](#running-the-pipeline) - * [Updating the pipeline](#updating-the-pipeline) - * [Reproducibility](#reproducibility) -* [Main arguments](#main-arguments) - * [`-profile`](#-profile) - * [`--reads`](#--reads) - * [`--single_end`](#--single_end) -* [Reference genomes](#reference-genomes) - * [`--genome` (using iGenomes)](#--genome-using-igenomes) - * [`--fasta`](#--fasta) - * [`--igenomes_ignore`](#--igenomes_ignore) -* [Job resources](#job-resources) - * [Automatic resubmission](#automatic-resubmission) - * [Custom resource requests](#custom-resource-requests) -* [AWS Batch specific parameters](#aws-batch-specific-parameters) - * [`--awsqueue`](#--awsqueue) - * [`--awsregion`](#--awsregion) - * [`--awscli`](#--awscli) -* [Other command line parameters](#other-command-line-parameters) - * [`--outdir`](#--outdir) - * [`--email`](#--email) - * [`--email_on_fail`](#--email_on_fail) - * [`--max_multiqc_email_size`](#--max_multiqc_email_size) - * [`-name`](#-name) - * [`-resume`](#-resume) - * [`-c`](#-c) - * [`--custom_config_version`](#--custom_config_version) - * [`--custom_config_base`](#--custom_config_base) - * [`--max_memory`](#--max_memory) - * [`--max_time`](#--max_time) - * [`--max_cpus`](#--max_cpus) - * [`--plaintext_email`](#--plaintext_email) - * [`--monochrome_logs`](#--monochrome_logs) - * [`--multiqc_config`](#--multiqc_config) +## :warning: Please read this documentation on the nf-core website: [https://nf-co.re/phaseimpute/usage](https://nf-co.re/phaseimpute/usage) -## Introduction - -Nextflow handles job submissions on SLURM or other environments, and supervises running the jobs. Thus the Nextflow process must run until the pipeline is finished. We recommend that you put the process running in the background through `screen` / `tmux` or similar tool. Alternatively you can run nextflow within a cluster job submitted your job scheduler. - -It is recommended to limit the Nextflow Java virtual machines memory. We recommend adding the following line to your environment (typically in `~/.bashrc` or `~./bash_profile`): +> _Documentation of pipeline parameters is generated automatically from the pipeline schema and can no longer be found in markdown files._ -```bash -NXF_OPTS='-Xms1g -Xmx4g' -``` - - - -## Running the pipeline - -The typical command for running the pipeline is as follows: +## Introduction -```bash -nextflow run nf-core/phaseimpute --reads '*_R{1,2}.fastq.gz' -profile docker -``` + -This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. +## Samplesheet input -Note that the pipeline will create the following files in your working directory: +You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. ```bash -work # Directory containing the nextflow working files -results # Finished results (configurable, see below) -.nextflow_log # Log file from Nextflow -# Other nextflow hidden files, eg. history of pipeline runs and old logs. +--input '[path to samplesheet file]' ``` -### Updating the pipeline +### Multiple runs of the same sample -When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline: +The `sample` identifiers have to be the same when you have re-sequenced the same sample more than once e.g. to increase sequencing depth. The pipeline will concatenate the raw reads before performing any downstream analysis. Below is an example for the same sample sequenced across 3 lanes: -```bash -nextflow pull nf-core/phaseimpute +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L003_R1_001.fastq.gz,AEG588A1_S1_L003_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L004_R1_001.fastq.gz,AEG588A1_S1_L004_R2_001.fastq.gz ``` -### Reproducibility - -It's a good idea to specify a pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. - -First, go to the [nf-core/phaseimpute releases page](https://github.com/nf-core/phaseimpute/releases) and find the latest version number - numeric only (eg. `1.3.1`). Then specify this when running the pipeline with `-r` (one hyphen) - eg. `-r 1.3.1`. +### Full samplesheet -This version number will be logged in reports when you run the pipeline, so that you'll know what you used when you look back in the future. - -## Main arguments - -### `-profile` +The pipeline will auto-detect whether a sample is single- or paired-end using the information provided in the samplesheet. The samplesheet can have as many columns as you desire, however, there is a strict requirement for the first 3 columns to match those defined in the table below. -Use this parameter to choose a configuration profile. Profiles can give configuration presets for different compute environments. - -Several generic profiles are bundled with the pipeline which instruct the pipeline to use software packaged using different methods (Docker, Singularity, Conda) - see below. - -> We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, however when this is not possible, Conda is also supported. - -The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to see if your system is available in these configs please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). +A final samplesheet file consisting of both single- and paired-end data may look something like the one below. This is for 6 samples, where `TREATMENT_REP3` has been sequenced twice. -Note that multiple profiles can be loaded, for example: `-profile test,docker` - the order of arguments is important! -They are loaded in sequence, so later profiles can overwrite earlier profiles. - -If `-profile` is not specified, the pipeline will run locally and expect all software to be installed and available on the `PATH`. This is _not_ recommended. +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP2,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz +CONTROL_REP3,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz +TREATMENT_REP1,AEG588A4_S4_L003_R1_001.fastq.gz, +TREATMENT_REP2,AEG588A5_S5_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L004_R1_001.fastq.gz, +``` -* `docker` - * A generic configuration profile to be used with [Docker](http://docker.com/) - * Pulls software from dockerhub: [`nfcore/phaseimpute`](http://hub.docker.com/r/nfcore/phaseimpute/) -* `singularity` - * A generic configuration profile to be used with [Singularity](http://singularity.lbl.gov/) - * Pulls software from DockerHub: [`nfcore/phaseimpute`](http://hub.docker.com/r/nfcore/phaseimpute/) -* `conda` - * Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker or Singularity. - * A generic configuration profile to be used with [Conda](https://conda.io/docs/) - * Pulls most software from [Bioconda](https://bioconda.github.io/) -* `test` - * A profile with a complete configuration for automated testing - * Includes links to test data so needs no other parameters +| Column | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | +| `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | +| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | - +An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. -### `--reads` +## Running the pipeline -Use this to specify the location of your input FastQ files. For example: +The typical command for running the pipeline is as follows: ```bash ---reads 'path/to/data/sample_*_{1,2}.fastq' +nextflow run nf-core/phaseimpute --input ./samplesheet.csv --outdir ./results --genome GRCh37 -profile docker ``` -Please note the following requirements: - -1. The path must be enclosed in quotes -2. The path must have at least one `*` wildcard character -3. When using the pipeline with paired end data, the path must use `{1,2}` notation to specify read pairs. - -If left unspecified, a default pattern is used: `data/*{1,2}.fastq.gz` - -### `--single_end` +This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. -By default, the pipeline expects paired-end data. If you have single-end data, you need to specify `--single_end` on the command line when you launch the pipeline. A normal glob pattern, enclosed in quotation marks, can then be used for `--reads`. For example: +Note that the pipeline will create the following files in your working directory: ```bash ---single_end --reads '*.fastq' +work # Directory containing the nextflow working files + # Finished results in specified location (defined with --outdir) +.nextflow_log # Log file from Nextflow +# Other nextflow hidden files, eg. history of pipeline runs and old logs. ``` -It is not possible to run a mixture of single-end and paired-end files in one run. - -## Reference genomes - -The pipeline config files come bundled with paths to the illumina iGenomes reference index files. If running with docker or AWS, the configuration is set up to use the [AWS-iGenomes](https://ewels.github.io/AWS-iGenomes/) resource. - -### `--genome` (using iGenomes) - -There are 31 different species supported in the iGenomes references. To run the pipeline, you must specify which to use with the `--genome` flag. +If you wish to repeatedly use the same parameters for multiple runs, rather than specifying each flag in the command, you can specify these in a params file. -You can find the keys to specify the genomes in the [iGenomes config file](../conf/igenomes.config). Common genomes that are supported are: +Pipeline settings can be provided in a `yaml` or `json` file via `-params-file `. -* Human - * `--genome GRCh37` -* Mouse - * `--genome GRCm38` -* _Drosophila_ - * `--genome BDGP6` -* _S. cerevisiae_ - * `--genome 'R64-1-1'` +:::warning +Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). +::: -> There are numerous others - check the config file for more. +The above pipeline run specified with a params file in yaml format: -Note that you can use the same configuration setup to save sets of reference files for your own use, even if they are not part of the iGenomes resource. See the [Nextflow documentation](https://www.nextflow.io/docs/latest/config.html) for instructions on where to save such a file. - -The syntax for this reference configuration is as follows: +```bash +nextflow run nf-core/phaseimpute -profile docker -params-file params.yaml +``` - +with `params.yaml` containing: -```nextflow -params { - genomes { - 'GRCh37' { - fasta = '' // Used if no star index given - } - // Any number of additional genomes, key is used with --genome - } -} +```yaml +input: './samplesheet.csv' +outdir: './results/' +genome: 'GRCh37' +<...> ``` - +You can also generate such `YAML`/`JSON` files via [nf-core/launch](https://nf-co.re/launch). -### `--fasta` +### Updating the pipeline -If you prefer, you can specify the full path to your reference genome when you run the pipeline: +When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline: ```bash ---fasta '[path to Fasta reference]' +nextflow pull nf-core/phaseimpute ``` -### `--igenomes_ignore` - -Do not load `igenomes.config` when running the pipeline. You may choose this option if you observe clashes between custom parameters and those supplied in `igenomes.config`. - -## Job resources +### Reproducibility -### Automatic resubmission +It is a good idea to specify a pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. -Each step in the pipeline has a default set of requirements for number of CPUs, memory and time. For most of the steps in the pipeline, if the job exits with an error code of `143` (exceeded requested resources) it will automatically resubmit with higher requests (2 x original, then 3 x original). If it still fails after three times then the pipeline is stopped. +First, go to the [nf-core/phaseimpute releases page](https://github.com/nf-core/phaseimpute/releases) and find the latest pipeline version - numeric only (eg. `1.3.1`). Then specify this when running the pipeline with `-r` (one hyphen) - eg. `-r 1.3.1`. Of course, you can switch to another version by changing the number after the `-r` flag. -### Custom resource requests +This version number will be logged in reports when you run the pipeline, so that you'll know what you used when you look back in the future. For example, at the bottom of the MultiQC reports. -Wherever process-specific requirements are set in the pipeline, the default value can be changed by creating a custom config file. See the files hosted at [`nf-core/configs`](https://github.com/nf-core/configs/tree/master/conf) for examples. +To further assist in reproducbility, you can use share and re-use [parameter files](#running-the-pipeline) to repeat pipeline runs with the same settings without having to write out a command with every single parameter. -If you are likely to be running `nf-core` pipelines regularly it may be a good idea to request that your custom config file is uploaded to the `nf-core/configs` git repository. Before you do this please can you test that the config file works with your pipeline of choice using the `-c` parameter (see definition below). You can then create a pull request to the `nf-core/configs` repository with the addition of your config file, associated documentation file (see examples in [`nf-core/configs/docs`](https://github.com/nf-core/configs/tree/master/docs)), and amending [`nfcore_custom.config`](https://github.com/nf-core/configs/blob/master/nfcore_custom.config) to include your custom profile. +:::tip +If you wish to share such profile (such as upload as supplementary material for academic publications), make sure to NOT include cluster specific paths to files, nor institutional specific profiles. +::: -If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack). +## Core Nextflow arguments -## AWS Batch specific parameters +:::note +These options are part of Nextflow and use a _single_ hyphen (pipeline parameters use a double-hyphen). +::: -Running the pipeline on AWS Batch requires a couple of specific parameters to be set according to your AWS Batch configuration. Please use [`-profile awsbatch`](https://github.com/nf-core/configs/blob/master/conf/awsbatch.config) and then specify all of the following parameters. +### `-profile` -### `--awsqueue` +Use this parameter to choose a configuration profile. Profiles can give configuration presets for different compute environments. -The JobQueue that you intend to use on AWS Batch. +Several generic profiles are bundled with the pipeline which instruct the pipeline to use software packaged using different methods (Docker, Singularity, Podman, Shifter, Charliecloud, Apptainer, Conda) - see below. -### `--awsregion` +:::info +We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, however when this is not possible, Conda is also supported. +::: -The AWS region in which to run your job. Default is set to `eu-west-1` but can be adjusted to your needs. +The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to see if your system is available in these configs please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). -### `--awscli` +Note that multiple profiles can be loaded, for example: `-profile test,docker` - the order of arguments is important! +They are loaded in sequence, so later profiles can overwrite earlier profiles. -The [AWS CLI](https://www.nextflow.io/docs/latest/awscloud.html#aws-cli-installation) path in your custom AMI. Default: `/home/ec2-user/miniconda/bin/aws`. +If `-profile` is not specified, the pipeline will run locally and expect all software to be installed and available on the `PATH`. This is _not_ recommended, since it can lead to different results on different machines dependent on the computer enviroment. + +- `test` + - A profile with a complete configuration for automated testing + - Includes links to test data so needs no other parameters +- `docker` + - A generic configuration profile to be used with [Docker](https://docker.com/) +- `singularity` + - A generic configuration profile to be used with [Singularity](https://sylabs.io/docs/) +- `podman` + - A generic configuration profile to be used with [Podman](https://podman.io/) +- `shifter` + - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) +- `charliecloud` + - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) +- `apptainer` + - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) +- `conda` + - A generic configuration profile to be used with [Conda](https://conda.io/docs/). Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker, Singularity, Podman, Shifter, Charliecloud, or Apptainer. -Please make sure to also set the `-w/--work-dir` and `--outdir` parameters to a S3 storage bucket of your choice - you'll get an error message notifying you if you didn't. +### `-resume` -## Other command line parameters +Specify this when restarting a pipeline. Nextflow will use cached results from any pipeline steps where the inputs are the same, continuing from where it got to previously. For input to be considered the same, not only the names must be identical but the files' contents as well. For more info about this parameter, see [this blog post](https://www.nextflow.io/blog/2019/demystifying-nextflow-resume.html). - +You can also supply a run name to resume a specific run: `-resume [run-name]`. Use the `nextflow log` command to show previous run names. -### `--outdir` +### `-c` -The output directory where the results will be saved. +Specify the path to a specific config file (this is a core Nextflow command). See the [nf-core website documentation](https://nf-co.re/usage/configuration) for more information. -### `--email` +## Custom configuration -Set this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file (`~/.nextflow/config`) then you don't need to specify this on the command line for every run. +### Resource requests -### `--email_on_fail` +Whilst the default requirements set within the pipeline will hopefully work for most people and with most input data, you may find that you want to customise the compute resources that the pipeline requests. Each step in the pipeline has a default set of requirements for number of CPUs, memory and time. For most of the steps in the pipeline, if the job exits with any of the error codes specified [here](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/base.config#L18) it will automatically be resubmitted with higher requests (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. -This works exactly as with `--email`, except emails are only sent if the workflow is not successful. +To change the resource requests, please see the [max resources](https://nf-co.re/docs/usage/configuration#max-resources) and [tuning workflow resources](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources) section of the nf-core website. -### `--max_multiqc_email_size` +### Custom Containers -Threshold size for MultiQC report to be attached in notification email. If file generated by pipeline exceeds the threshold, it will not be attached (Default: 25MB). +In some cases you may wish to change which container or conda environment a step of the pipeline uses for a particular tool. By default nf-core pipelines use containers and software from the [biocontainers](https://biocontainers.pro/) or [bioconda](https://bioconda.github.io/) projects. However in some cases the pipeline specified version maybe out of date. -### `-name` +To use a different container from the default container or conda environment specified in a pipeline, please see the [updating tool versions](https://nf-co.re/docs/usage/configuration#updating-tool-versions) section of the nf-core website. -Name for the pipeline run. If not specified, Nextflow will automatically generate a random mnemonic. +### Custom Tool Arguments -This is used in the MultiQC report (if not default) and in the summary HTML / e-mail (always). +A pipeline might not always support every possible argument or option of a particular tool used in pipeline. Fortunately, nf-core pipelines provide some freedom to users to insert additional parameters that the pipeline does not include by default. -**NB:** Single hyphen (core Nextflow option) +To learn how to provide additional arguments to a particular tool of the pipeline, please see the [customising tool arguments](https://nf-co.re/docs/usage/configuration#customising-tool-arguments) section of the nf-core website. -### `-resume` +### nf-core/configs -Specify this when restarting a pipeline. Nextflow will used cached results from any pipeline steps where the inputs are the same, continuing from where it got to previously. +In most cases, you will only need to create a custom config as a one-off but if you and others within your organisation are likely to be running nf-core pipelines regularly and need to use the same settings regularly it may be a good idea to request that your custom config file is uploaded to the `nf-core/configs` git repository. Before you do this please can you test that the config file works with your pipeline of choice using the `-c` parameter. You can then create a pull request to the `nf-core/configs` repository with the addition of your config file, associated documentation file (see examples in [`nf-core/configs/docs`](https://github.com/nf-core/configs/tree/master/docs)), and amending [`nfcore_custom.config`](https://github.com/nf-core/configs/blob/master/nfcore_custom.config) to include your custom profile. -You can also supply a run name to resume a specific run: `-resume [run-name]`. Use the `nextflow log` command to show previous run names. +See the main [Nextflow documentation](https://www.nextflow.io/docs/latest/config.html) for more information about creating your own configuration files. -**NB:** Single hyphen (core Nextflow option) +If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack) on the [`#configs` channel](https://nfcore.slack.com/channels/configs). -### `-c` +## Azure Resource Requests -Specify the path to a specific config file (this is a core NextFlow command). +To be used with the `azurebatch` profile by specifying the `-profile azurebatch`. +We recommend providing a compute `params.vm_type` of `Standard_D16_v3` VMs by default but these options can be changed if required. -**NB:** Single hyphen (core Nextflow option) +Note that the choice of VM size depends on your quota and the overall workload during the analysis. +For a thorough list, please refer the [Azure Sizes for virtual machines in Azure](https://docs.microsoft.com/en-us/azure/virtual-machines/sizes). -Note - you can use this to override pipeline defaults. +## Running in the background -### `--custom_config_version` +Nextflow handles job submissions and supervises the running jobs. The Nextflow process must run until the pipeline is finished. -Provide git commit id for custom Institutional configs hosted at `nf-core/configs`. This was implemented for reproducibility purposes. Default: `master`. +The Nextflow `-bg` flag launches Nextflow in the background, detached from your terminal so that the workflow does not stop if you log out of your session. The logs are saved to a file. -```bash -## Download and use config file with following git commid id ---custom_config_version d52db660777c4bf36546ddb188ec530c3ada1b96 -``` +Alternatively, you can use `screen` / `tmux` or similar tool to create a detached session which you can log back into at a later time. +Some HPC setups also allow you to run nextflow within a cluster job submitted your job scheduler (from where it submits more jobs). -### `--custom_config_base` +## Nextflow memory requirements -If you're running offline, nextflow will not be able to fetch the institutional config files -from the internet. If you don't need them, then this is not a problem. If you do need them, -you should download the files from the repo and tell nextflow where to find them with the -`custom_config_base` option. For example: +In some cases, the Nextflow Java virtual machines can start to request a large amount of memory. +We recommend adding the following line to your environment to limit this (typically in `~/.bashrc` or `~./bash_profile`): ```bash -## Download and unzip the config files -cd /path/to/my/configs -wget https://github.com/nf-core/configs/archive/master.zip -unzip master.zip - -## Run the pipeline -cd /path/to/my/data -nextflow run /path/to/pipeline/ --custom_config_base /path/to/my/configs/configs-master/ +NXF_OPTS='-Xms1g -Xmx4g' ``` - -> Note that the nf-core/tools helper package has a `download` command to download all required pipeline -> files + singularity containers + institutional configs in one go for you, to make this process easier. - -### `--max_memory` - -Use to set a top-limit for the default memory requirement for each process. -Should be a string in the format integer-unit. eg. `--max_memory '8.GB'` - -### `--max_time` - -Use to set a top-limit for the default time requirement for each process. -Should be a string in the format integer-unit. eg. `--max_time '2.h'` - -### `--max_cpus` - -Use to set a top-limit for the default CPU requirement for each process. -Should be a string in the format integer-unit. eg. `--max_cpus 1` - -### `--plaintext_email` - -Set to receive plain-text e-mails instead of HTML formatted. - -### `--monochrome_logs` - -Set to disable colourful command line output and live life in monochrome. - -### `--multiqc_config` - -Specify a path to a custom MultiQC configuration file. diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 370aabc4..00000000 --- a/environment.yml +++ /dev/null @@ -1,15 +0,0 @@ -# You can use this file to create a conda environment for this pipeline: -# conda env create -f environment.yml -name: nf-core-phaseimpute-1.0dev -channels: - - conda-forge - - bioconda - - defaults -dependencies: - - conda-forge::python=3.7.3 - - conda-forge::markdown=3.1.1 - - conda-forge::pymdown-extensions=6.0 - - conda-forge::pygments=2.5.2 - # TODO nf-core: Add required software dependencies here - - bioconda::fastqc=0.11.8 - - bioconda::multiqc=1.7 diff --git a/lib/NfcoreTemplate.groovy b/lib/NfcoreTemplate.groovy new file mode 100755 index 00000000..e248e4c3 --- /dev/null +++ b/lib/NfcoreTemplate.groovy @@ -0,0 +1,356 @@ +// +// This file holds several functions used within the nf-core pipeline template. +// + +import org.yaml.snakeyaml.Yaml +import groovy.json.JsonOutput +import nextflow.extension.FilesEx + +class NfcoreTemplate { + + // + // Check AWS Batch related parameters have been specified correctly + // + public static void awsBatch(workflow, params) { + if (workflow.profile.contains('awsbatch')) { + // Check params.awsqueue and params.awsregion have been set if running on AWSBatch + assert (params.awsqueue && params.awsregion) : "Specify correct --awsqueue and --awsregion parameters on AWSBatch!" + // Check outdir paths to be S3 buckets if running on AWSBatch + assert params.outdir.startsWith('s3:') : "Outdir not on S3 - specify S3 Bucket to run on AWSBatch!" + } + } + + // + // Warn if a -profile or Nextflow config has not been provided to run the pipeline + // + public static void checkConfigProvided(workflow, log) { + if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { + log.warn "[$workflow.manifest.name] You are attempting to run the pipeline without any custom configuration!\n\n" + + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + + "Please refer to the quick start section and usage docs for the pipeline.\n " + } + } + + // + // Generate version string + // + public static String version(workflow) { + String version_string = "" + + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string + } + + // + // Construct and send completion email + // + public static void email(workflow, params, summary_params, projectDir, log, multiqc_report=[]) { + + // Set up the e-mail variables + def subject = "[$workflow.manifest.name] Successful: $workflow.runName" + if (!workflow.success) { + subject = "[$workflow.manifest.name] FAILED: $workflow.runName" + } + + def summary = [:] + for (group in summary_params.keySet()) { + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['Date Started'] = workflow.start + misc_fields['Date Completed'] = workflow.complete + misc_fields['Pipeline script file path'] = workflow.scriptFile + misc_fields['Pipeline script hash ID'] = workflow.scriptId + if (workflow.repository) misc_fields['Pipeline repository Git URL'] = workflow.repository + if (workflow.commitId) misc_fields['Pipeline repository Git Commit'] = workflow.commitId + if (workflow.revision) misc_fields['Pipeline Git branch/tag'] = workflow.revision + misc_fields['Nextflow Version'] = workflow.nextflow.version + misc_fields['Nextflow Build'] = workflow.nextflow.build + misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp + + def email_fields = [:] + email_fields['version'] = NfcoreTemplate.version(workflow) + email_fields['runName'] = workflow.runName + email_fields['success'] = workflow.success + email_fields['dateComplete'] = workflow.complete + email_fields['duration'] = workflow.duration + email_fields['exitStatus'] = workflow.exitStatus + email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + email_fields['errorReport'] = (workflow.errorReport ?: 'None') + email_fields['commandLine'] = workflow.commandLine + email_fields['projectDir'] = workflow.projectDir + email_fields['summary'] = summary << misc_fields + + // On success try attach the multiqc report + def mqc_report = null + try { + if (workflow.success) { + mqc_report = multiqc_report.getVal() + if (mqc_report.getClass() == ArrayList && mqc_report.size() >= 1) { + if (mqc_report.size() > 1) { + log.warn "[$workflow.manifest.name] Found multiple reports from process 'MULTIQC', will use only one" + } + mqc_report = mqc_report[0] + } + } + } catch (all) { + if (multiqc_report) { + log.warn "[$workflow.manifest.name] Could not attach MultiQC report to summary email" + } + } + + // Check if we are only sending emails on failure + def email_address = params.email + if (!params.email && params.email_on_fail && !workflow.success) { + email_address = params.email_on_fail + } + + // Render the TXT template + def engine = new groovy.text.GStringTemplateEngine() + def tf = new File("$projectDir/assets/email_template.txt") + def txt_template = engine.createTemplate(tf).make(email_fields) + def email_txt = txt_template.toString() + + // Render the HTML template + def hf = new File("$projectDir/assets/email_template.html") + def html_template = engine.createTemplate(hf).make(email_fields) + def email_html = html_template.toString() + + // Render the sendmail template + def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as nextflow.util.MemoryUnit + def smail_fields = [ email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "$projectDir", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes() ] + def sf = new File("$projectDir/assets/sendmail_template.txt") + def sendmail_template = engine.createTemplate(sf).make(smail_fields) + def sendmail_html = sendmail_template.toString() + + // Send the HTML e-mail + Map colors = logColours(params.monochrome_logs) + if (email_address) { + try { + if (params.plaintext_email) { throw GroovyException('Send plaintext e-mail, not HTML') } + // Try to send HTML e-mail using sendmail + def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") + sendmail_tf.withWriter { w -> w << sendmail_html } + [ 'sendmail', '-t' ].execute() << sendmail_html + log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (sendmail)-" + } catch (all) { + // Catch failures and try with plaintext + def mail_cmd = [ 'mail', '-s', subject, '--content-type=text/html', email_address ] + if ( mqc_report != null && mqc_report.size() <= max_multiqc_email_size.toBytes() ) { + mail_cmd += [ '-A', mqc_report ] + } + mail_cmd.execute() << email_html + log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (mail)-" + } + } + + // Write summary e-mail HTML to a file + def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") + output_hf.withWriter { w -> w << email_html } + FilesEx.copyTo(output_hf.toPath(), "${params.outdir}/pipeline_info/pipeline_report.html"); + output_hf.delete() + + // Write summary e-mail TXT to a file + def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") + output_tf.withWriter { w -> w << email_txt } + FilesEx.copyTo(output_tf.toPath(), "${params.outdir}/pipeline_info/pipeline_report.txt"); + output_tf.delete() + } + + // + // Construct and send a notification to a web server as JSON + // e.g. Microsoft Teams and Slack + // + public static void IM_notification(workflow, params, summary_params, projectDir, log) { + def hook_url = params.hook_url + + def summary = [:] + for (group in summary_params.keySet()) { + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['start'] = workflow.start + misc_fields['complete'] = workflow.complete + misc_fields['scriptfile'] = workflow.scriptFile + misc_fields['scriptid'] = workflow.scriptId + if (workflow.repository) misc_fields['repository'] = workflow.repository + if (workflow.commitId) misc_fields['commitid'] = workflow.commitId + if (workflow.revision) misc_fields['revision'] = workflow.revision + misc_fields['nxf_version'] = workflow.nextflow.version + misc_fields['nxf_build'] = workflow.nextflow.build + misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp + + def msg_fields = [:] + msg_fields['version'] = NfcoreTemplate.version(workflow) + msg_fields['runName'] = workflow.runName + msg_fields['success'] = workflow.success + msg_fields['dateComplete'] = workflow.complete + msg_fields['duration'] = workflow.duration + msg_fields['exitStatus'] = workflow.exitStatus + msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + msg_fields['errorReport'] = (workflow.errorReport ?: 'None') + msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") + msg_fields['projectDir'] = workflow.projectDir + msg_fields['summary'] = summary << misc_fields + + // Render the JSON template + def engine = new groovy.text.GStringTemplateEngine() + // Different JSON depending on the service provider + // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format + def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" + def hf = new File("$projectDir/assets/${json_path}") + def json_template = engine.createTemplate(hf).make(msg_fields) + def json_message = json_template.toString() + + // POST + def post = new URL(hook_url).openConnection(); + post.setRequestMethod("POST") + post.setDoOutput(true) + post.setRequestProperty("Content-Type", "application/json") + post.getOutputStream().write(json_message.getBytes("UTF-8")); + def postRC = post.getResponseCode(); + if (! postRC.equals(200)) { + log.warn(post.getErrorStream().getText()); + } + } + + // + // Dump pipeline parameters in a json file + // + public static void dump_parameters(workflow, params) { + def timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') + def filename = "params_${timestamp}.json" + def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") + def jsonStr = JsonOutput.toJson(params) + temp_pf.text = JsonOutput.prettyPrint(jsonStr) + + FilesEx.copyTo(temp_pf.toPath(), "${params.outdir}/pipeline_info/params_${timestamp}.json") + temp_pf.delete() + } + + // + // Print pipeline summary on completion + // + public static void summary(workflow, params, log) { + Map colors = logColours(params.monochrome_logs) + if (workflow.success) { + if (workflow.stats.ignoredCount == 0) { + log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Pipeline completed successfully${colors.reset}-" + } else { + log.info "-${colors.purple}[$workflow.manifest.name]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-" + } + } else { + log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed with errors${colors.reset}-" + } + } + + // + // ANSII Colours used for terminal logging + // + public static Map logColours(Boolean monochrome_logs) { + Map colorcodes = [:] + + // Reset / Meta + colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" + colorcodes['bold'] = monochrome_logs ? '' : "\033[1m" + colorcodes['dim'] = monochrome_logs ? '' : "\033[2m" + colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m" + colorcodes['blink'] = monochrome_logs ? '' : "\033[5m" + colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m" + colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" + + // Regular Colors + colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" + colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" + colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" + colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" + colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" + colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" + colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" + colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" + + // Bold + colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" + colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" + colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" + colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" + colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" + colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" + colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" + colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" + + // Underline + colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" + colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" + colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" + colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" + colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" + colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" + colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" + colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" + + // High Intensity + colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" + colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" + colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" + colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" + colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" + colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" + colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" + colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" + + // Bold High Intensity + colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" + colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" + colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" + colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" + colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" + colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" + colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" + colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" + + return colorcodes + } + + // + // Does what is says on the tin + // + public static String dashedLine(monochrome_logs) { + Map colors = logColours(monochrome_logs) + return "-${colors.dim}----------------------------------------------------${colors.reset}-" + } + + // + // nf-core logo + // + public static String logo(workflow, monochrome_logs) { + Map colors = logColours(monochrome_logs) + String workflow_version = NfcoreTemplate.version(workflow) + String.format( + """\n + ${dashedLine(monochrome_logs)} + ${colors.green},--.${colors.black}/${colors.green},-.${colors.reset} + ${colors.blue} ___ __ __ __ ___ ${colors.green}/,-._.--~\'${colors.reset} + ${colors.blue} |\\ | |__ __ / ` / \\ |__) |__ ${colors.yellow}} {${colors.reset} + ${colors.blue} | \\| | \\__, \\__/ | \\ |___ ${colors.green}\\`-._,-`-,${colors.reset} + ${colors.green}`._,._,\'${colors.reset} + ${colors.purple} ${workflow.manifest.name} ${workflow_version}${colors.reset} + ${dashedLine(monochrome_logs)} + """.stripIndent() + ) + } +} diff --git a/lib/Utils.groovy b/lib/Utils.groovy new file mode 100644 index 00000000..8d030f4e --- /dev/null +++ b/lib/Utils.groovy @@ -0,0 +1,47 @@ +// +// This file holds several Groovy functions that could be useful for any Nextflow pipeline +// + +import org.yaml.snakeyaml.Yaml + +class Utils { + + // + // When running with -profile conda, warn if channels have not been set-up appropriately + // + public static void checkCondaChannels(log) { + Yaml parser = new Yaml() + def channels = [] + try { + def config = parser.load("conda config --show channels".execute().text) + channels = config.channels + } catch(NullPointerException | IOException e) { + log.warn "Could not verify conda channel configuration." + return + } + + // Check that all channels are present + // This channel list is ordered by required channel priority. + def required_channels_in_order = ['conda-forge', 'bioconda', 'defaults'] + def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean + + // Check that they are in the right order + def channel_priority_violation = false + def n = required_channels_in_order.size() + for (int i = 0; i < n - 1; i++) { + channel_priority_violation |= !(channels.indexOf(required_channels_in_order[i]) < channels.indexOf(required_channels_in_order[i+1])) + } + + if (channels_missing | channel_priority_violation) { + log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + " There is a problem with your Conda configuration!\n\n" + + " You will need to set-up the conda-forge and bioconda channels correctly.\n" + + " Please refer to https://bioconda.github.io/\n" + + " The observed channel order is \n" + + " ${channels}\n" + + " but the following channel order is required:\n" + + " ${required_channels_in_order}\n" + + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + } + } +} diff --git a/lib/WorkflowMain.groovy b/lib/WorkflowMain.groovy new file mode 100755 index 00000000..0d9976d2 --- /dev/null +++ b/lib/WorkflowMain.groovy @@ -0,0 +1,77 @@ +// +// This file holds several functions specific to the main.nf workflow in the nf-core/phaseimpute pipeline +// + +import nextflow.Nextflow + +class WorkflowMain { + + // + // Citation string for pipeline + // + public static String citation(workflow) { + return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" + + // TODO nf-core: Add Zenodo DOI for pipeline after first release + //"* The pipeline\n" + + //" https://doi.org/10.5281/zenodo.XXXXXXX\n\n" + + "* The nf-core framework\n" + + " https://doi.org/10.1038/s41587-020-0439-x\n\n" + + "* Software dependencies\n" + + " https://github.com/${workflow.manifest.name}/blob/master/CITATIONS.md" + } + + + // + // Validate parameters and print summary to screen + // + public static void initialise(workflow, params, log, args) { + + // Print workflow version and exit on --version + if (params.version) { + String workflow_version = NfcoreTemplate.version(workflow) + log.info "${workflow.manifest.name} ${workflow_version}" + System.exit(0) + } + + // Check that a -profile or Nextflow config has been provided to run the pipeline + NfcoreTemplate.checkConfigProvided(workflow, log) + // Check that the profile doesn't contain spaces and doesn't end with a trailing comma + checkProfile(workflow.profile, args, log) + + // Check that conda channels are set-up correctly + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { + Utils.checkCondaChannels(log) + } + + // Check AWS batch settings + NfcoreTemplate.awsBatch(workflow, params) + + // Check input has been provided + if (!params.input) { + Nextflow.error("Please provide an input samplesheet to the pipeline e.g. '--input samplesheet.csv'") + } + } + // + // Get attribute from genome config file e.g. fasta + // + public static Object getGenomeAttribute(params, attribute) { + if (params.genomes && params.genome && params.genomes.containsKey(params.genome)) { + if (params.genomes[ params.genome ].containsKey(attribute)) { + return params.genomes[ params.genome ][ attribute ] + } + } + return null + } + + // + // Exit pipeline if --profile contains spaces + // + private static void checkProfile(profile, args, log) { + if (profile.endsWith(',')) { + Nextflow.error "Profile cannot end with a trailing comma. Please remove the comma from the end of the profile string.\nHint: A common mistake is to provide multiple values to `-profile` separated by spaces. Please use commas to separate profiles instead,e.g., `-profile docker,test`." + } + if (args[0]) { + log.warn "nf-core pipelines do not accept positional arguments. The positional argument `${args[0]}` has been detected.\n Hint: A common mistake is to provide multiple values to `-profile` separated by spaces. Please use commas to separate profiles instead,e.g., `-profile docker,test`." + } + } +} diff --git a/lib/WorkflowPhaseimpute.groovy b/lib/WorkflowPhaseimpute.groovy new file mode 100755 index 00000000..49575cc1 --- /dev/null +++ b/lib/WorkflowPhaseimpute.groovy @@ -0,0 +1,122 @@ +// +// This file holds several functions specific to the workflow/phaseimpute.nf in the nf-core/phaseimpute pipeline +// + +import nextflow.Nextflow +import groovy.text.SimpleTemplateEngine + +class WorkflowPhaseimpute { + + // + // Check and validate parameters + // + public static void initialise(params, log) { + + genomeExistsError(params, log) + + + if (!params.fasta) { + Nextflow.error "Genome fasta file not specified with e.g. '--fasta genome.fa' or via a detectable config file." + } + } + + // + // Get workflow summary for MultiQC + // + public static String paramsSummaryMultiqc(workflow, summary) { + String summary_section = '' + for (group in summary.keySet()) { + def group_params = summary.get(group) // This gets the parameters of that particular group + if (group_params) { + summary_section += "

$group

\n" + summary_section += "
\n" + } + } + + String yaml_file_text = "id: '${workflow.manifest.name.replace('/','-')}-summary'\n" + yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" + yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" + yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" + yaml_file_text += "plot_type: 'html'\n" + yaml_file_text += "data: |\n" + yaml_file_text += "${summary_section}" + return yaml_file_text + } + + // + // Generate methods description for MultiQC + // + + public static String toolCitationText(params) { + + // TODO nf-core: Optionally add in-text citation tools to this list. + // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "Tool (Foo et al. 2023)" : "", + // Uncomment function in methodsDescriptionText to render in MultiQC report + def citation_text = [ + "Tools used in the workflow included:", + "FastQC (Andrews 2010),", + "MultiQC (Ewels et al. 2016)", + "." + ].join(' ').trim() + + return citation_text + } + + public static String toolBibliographyText(params) { + + // TODO Optionally add bibliographic entries to this list. + // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "
  • Author (2023) Pub name, Journal, DOI
  • " : "", + // Uncomment function in methodsDescriptionText to render in MultiQC report + def reference_text = [ + "
  • Andrews S, (2010) FastQC, URL: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/).
  • ", + "
  • Ewels, P., Magnusson, M., Lundin, S., & Käller, M. (2016). MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics , 32(19), 3047–3048. doi: /10.1093/bioinformatics/btw354
  • " + ].join(' ').trim() + + return reference_text + } + + public static String methodsDescriptionText(run_workflow, mqc_methods_yaml, params) { + // Convert to a named map so can be used as with familar NXF ${workflow} variable syntax in the MultiQC YML file + def meta = [:] + meta.workflow = run_workflow.toMap() + meta["manifest_map"] = run_workflow.manifest.toMap() + + // Pipeline DOI + meta["doi_text"] = meta.manifest_map.doi ? "(doi: ${meta.manifest_map.doi})" : "" + meta["nodoi_text"] = meta.manifest_map.doi ? "": "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " + + // Tool references + meta["tool_citations"] = "" + meta["tool_bibliography"] = "" + + // TODO Only uncomment below if logic in toolCitationText/toolBibliographyText has been filled! + //meta["tool_citations"] = toolCitationText(params).replaceAll(", \\.", ".").replaceAll("\\. \\.", ".").replaceAll(", \\.", ".") + //meta["tool_bibliography"] = toolBibliographyText(params) + + + def methods_text = mqc_methods_yaml.text + + def engine = new SimpleTemplateEngine() + def description_html = engine.createTemplate(methods_text).make(meta) + + return description_html + } + + // + // Exit pipeline if incorrect --genome key provided + // + private static void genomeExistsError(params, log) { + if (params.genomes && params.genome && !params.genomes.containsKey(params.genome)) { + def error_string = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + " Genome '${params.genome}' not found in any config files provided to the pipeline.\n" + + " Currently, the available genome keys are:\n" + + " ${params.genomes.keySet().join(", ")}\n" + + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + Nextflow.error(error_string) + } + } +} diff --git a/main.nf b/main.nf index d9570860..bbd5914a 100644 --- a/main.nf +++ b/main.nf @@ -1,426 +1,82 @@ #!/usr/bin/env nextflow /* -======================================================================================== - nf-core/phaseimpute -======================================================================================== - nf-core/phaseimpute Analysis Pipeline. - #### Homepage / Documentation - https://github.com/nf-core/phaseimpute +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + nf-core/phaseimpute +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Github : https://github.com/nf-core/phaseimpute + Website: https://nf-co.re/phaseimpute + Slack : https://nfcore.slack.com/channels/phaseimpute ---------------------------------------------------------------------------------------- */ -def helpMessage() { - // TODO nf-core: Add to this help message with new command line parameters - log.info nfcoreHeader() - log.info""" - - Usage: - - The typical command for running the pipeline is as follows: - - nextflow run nf-core/phaseimpute --reads '*_R{1,2}.fastq.gz' -profile docker - - Mandatory arguments: - --reads [file] Path to input data (must be surrounded with quotes) - -profile [str] Configuration profile to use. Can use multiple (comma separated) - Available: conda, docker, singularity, test, awsbatch, and more - - Options: - --genome [str] Name of iGenomes reference - --single_end [bool] Specifies that the input is single-end reads - - References If not specified in the configuration file or you wish to overwrite any of the references - --fasta [file] Path to fasta reference - - Other options: - --outdir [file] The output directory where the results will be saved - --email [email] Set this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits - --email_on_fail [email] Same as --email, except only send mail if the workflow is not successful - --max_multiqc_email_size [str] Theshold size for MultiQC report to be attached in notification email. If file generated by pipeline exceeds the threshold, it will not be attached (Default: 25MB) - -name [str] Name for the pipeline run. If not specified, Nextflow will automatically generate a random mnemonic - - AWSBatch options: - --awsqueue [str] The AWSBatch JobQueue that needs to be set when running on AWSBatch - --awsregion [str] The AWS Region for your AWS Batch job to run on - --awscli [str] Path to the AWS CLI tool - """.stripIndent() -} - -// Show help message -if (params.help) { - helpMessage() - exit 0 -} +nextflow.enable.dsl = 2 /* - * SET UP CONFIGURATION VARIABLES - */ - -// Check if genome exists in the config file -if (params.genomes && params.genome && !params.genomes.containsKey(params.genome)) { - exit 1, "The provided genome '${params.genome}' is not available in the iGenomes file. Currently the available genomes are ${params.genomes.keySet().join(", ")}" -} - -// TODO nf-core: Add any reference files that are needed -// Configurable reference genomes -// -// NOTE - THIS IS NOT USED IN THIS PIPELINE, EXAMPLE ONLY -// If you want to use the channel below in a process, define the following: -// input: -// file fasta from ch_fasta -// -params.fasta = params.genome ? params.genomes[ params.genome ].fasta ?: false : false -if (params.fasta) { ch_fasta = file(params.fasta, checkIfExists: true) } - -// Has the run name been specified by the user? -// this has the bonus effect of catching both -name and --name -custom_runName = params.name -if (!(workflow.runName ==~ /[a-z]+_[a-z]+/)) { - custom_runName = workflow.runName -} - -if (workflow.profile.contains('awsbatch')) { - // AWSBatch sanity checking - if (!params.awsqueue || !params.awsregion) exit 1, "Specify correct --awsqueue and --awsregion parameters on AWSBatch!" - // Check outdir paths to be S3 buckets if running on AWSBatch - // related: https://github.com/nextflow-io/nextflow/issues/813 - if (!params.outdir.startsWith('s3:')) exit 1, "Outdir not on S3 - specify S3 Bucket to run on AWSBatch!" - // Prevent trace files to be stored on S3 since S3 does not support rolling files. - if (params.tracedir.startsWith('s3:')) exit 1, "Specify a local tracedir or run without trace! S3 cannot be used for tracefiles." -} - -// Stage config files -ch_multiqc_config = file("$baseDir/assets/multiqc_config.yaml", checkIfExists: true) -ch_multiqc_custom_config = params.multiqc_config ? Channel.fromPath(params.multiqc_config, checkIfExists: true) : Channel.empty() -ch_output_docs = file("$baseDir/docs/output.md", checkIfExists: true) - -/* - * Create a channel for input read files - */ -if (params.readPaths) { - if (params.single_end) { - Channel - .from(params.readPaths) - .map { row -> [ row[0], [ file(row[1][0], checkIfExists: true) ] ] } - .ifEmpty { exit 1, "params.readPaths was empty - no input files supplied" } - .into { ch_read_files_fastqc; ch_read_files_trimming } - } else { - Channel - .from(params.readPaths) - .map { row -> [ row[0], [ file(row[1][0], checkIfExists: true), file(row[1][1], checkIfExists: true) ] ] } - .ifEmpty { exit 1, "params.readPaths was empty - no input files supplied" } - .into { ch_read_files_fastqc; ch_read_files_trimming } - } -} else { - Channel - .fromFilePairs(params.reads, size: params.single_end ? 1 : 2) - .ifEmpty { exit 1, "Cannot find any reads matching: ${params.reads}\nNB: Path needs to be enclosed in quotes!\nIf this is single-end data, please specify --single_end on the command line." } - .into { ch_read_files_fastqc; ch_read_files_trimming } -} - -// Header log info -log.info nfcoreHeader() -def summary = [:] -if (workflow.revision) summary['Pipeline Release'] = workflow.revision -summary['Run Name'] = custom_runName ?: workflow.runName -// TODO nf-core: Report custom parameters here -summary['Reads'] = params.reads -summary['Fasta Ref'] = params.fasta -summary['Data Type'] = params.single_end ? 'Single-End' : 'Paired-End' -summary['Max Resources'] = "$params.max_memory memory, $params.max_cpus cpus, $params.max_time time per job" -if (workflow.containerEngine) summary['Container'] = "$workflow.containerEngine - $workflow.container" -summary['Output dir'] = params.outdir -summary['Launch dir'] = workflow.launchDir -summary['Working dir'] = workflow.workDir -summary['Script dir'] = workflow.projectDir -summary['User'] = workflow.userName -if (workflow.profile.contains('awsbatch')) { - summary['AWS Region'] = params.awsregion - summary['AWS Queue'] = params.awsqueue - summary['AWS CLI'] = params.awscli -} -summary['Config Profile'] = workflow.profile -if (params.config_profile_description) summary['Config Description'] = params.config_profile_description -if (params.config_profile_contact) summary['Config Contact'] = params.config_profile_contact -if (params.config_profile_url) summary['Config URL'] = params.config_profile_url -if (params.email || params.email_on_fail) { - summary['E-mail Address'] = params.email - summary['E-mail on failure'] = params.email_on_fail - summary['MultiQC maxsize'] = params.max_multiqc_email_size -} -log.info summary.collect { k,v -> "${k.padRight(18)}: $v" }.join("\n") -log.info "-\033[2m--------------------------------------------------\033[0m-" - -// Check the hostnames against configured profiles -checkHostname() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + GENOME PARAMETER VALUES +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ -Channel.from(summary.collect{ [it.key, it.value] }) - .map { k,v -> "
    $k
    ${v ?: 'N/A'}
    " } - .reduce { a, b -> return [a, b].join("\n ") } - .map { x -> """ - id: 'nf-core-phaseimpute-summary' - description: " - this information is collected when the pipeline is started." - section_name: 'nf-core/phaseimpute Workflow Summary' - section_href: 'https://github.com/nf-core/phaseimpute' - plot_type: 'html' - data: | -
    - $x -
    - """.stripIndent() } - .set { ch_workflow_summary } +// TODO nf-core: Remove this line if you don't need a FASTA file +// This is an example of how to use getGenomeAttribute() to fetch parameters +// from igenomes.config using `--genome` +params.fasta = WorkflowMain.getGenomeAttribute(params, 'fasta') /* - * Parse software version numbers - */ -process get_software_versions { - publishDir "${params.outdir}/pipeline_info", mode: 'copy', - saveAs: { filename -> - if (filename.indexOf(".csv") > 0) filename - else null - } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + VALIDATE & PRINT PARAMETER SUMMARY +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ - output: - file 'software_versions_mqc.yaml' into ch_software_versions_yaml - file "software_versions.csv" +include { validateParameters; paramsHelp } from 'plugin/nf-validation' - script: - // TODO nf-core: Get all tools to print their version number here - """ - echo $workflow.manifest.version > v_pipeline.txt - echo $workflow.nextflow.version > v_nextflow.txt - fastqc --version > v_fastqc.txt - multiqc --version > v_multiqc.txt - scrape_software_versions.py &> software_versions_mqc.yaml - """ +// Print help message if needed +if (params.help) { + def logo = NfcoreTemplate.logo(workflow, params.monochrome_logs) + def citation = '\n' + WorkflowMain.citation(workflow) + '\n' + def String command = "nextflow run ${workflow.manifest.name} --input samplesheet.csv --genome GRCh37 -profile docker" + log.info logo + paramsHelp(command) + citation + NfcoreTemplate.dashedLine(params.monochrome_logs) + System.exit(0) } -/* - * STEP 1 - FastQC - */ -process fastqc { - tag "$name" - label 'process_medium' - publishDir "${params.outdir}/fastqc", mode: 'copy', - saveAs: { filename -> - filename.indexOf(".zip") > 0 ? "zips/$filename" : "$filename" - } - - input: - set val(name), file(reads) from ch_read_files_fastqc - - output: - file "*_fastqc.{zip,html}" into ch_fastqc_results - - script: - """ - fastqc --quiet --threads $task.cpus $reads - """ +// Validate input parameters +if (params.validate_params) { + validateParameters() } -/* - * STEP 2 - MultiQC - */ -process multiqc { - publishDir "${params.outdir}/MultiQC", mode: 'copy' - - input: - file (multiqc_config) from ch_multiqc_config - file (mqc_custom_config) from ch_multiqc_custom_config.collect().ifEmpty([]) - // TODO nf-core: Add in log files from your new processes for MultiQC to find! - file ('fastqc/*') from ch_fastqc_results.collect().ifEmpty([]) - file ('software_versions/*') from ch_software_versions_yaml.collect() - file workflow_summary from ch_workflow_summary.collectFile(name: "workflow_summary_mqc.yaml") - - output: - file "*multiqc_report.html" into ch_multiqc_report - file "*_data" - file "multiqc_plots" - - script: - rtitle = custom_runName ? "--title \"$custom_runName\"" : '' - rfilename = custom_runName ? "--filename " + custom_runName.replaceAll('\\W','_').replaceAll('_+','_') + "_multiqc_report" : '' - custom_config_file = params.multiqc_config ? "--config $mqc_custom_config" : '' - // TODO nf-core: Specify which MultiQC modules to use with -m for a faster run time - """ - multiqc -f $rtitle $rfilename $custom_config_file . - """ -} +WorkflowMain.initialise(workflow, params, log, args) /* - * STEP 3 - Output Description HTML - */ -process output_documentation { - publishDir "${params.outdir}/pipeline_info", mode: 'copy' - - input: - file output_docs from ch_output_docs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + NAMED WORKFLOW FOR PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ - output: - file "results_description.html" +include { PHASEIMPUTE } from './workflows/phaseimpute' - script: - """ - markdown_to_html.py $output_docs -o results_description.html - """ +// +// WORKFLOW: Run main nf-core/phaseimpute analysis pipeline +// +workflow NFCORE_PHASEIMPUTE { + PHASEIMPUTE () } /* - * Completion e-mail notification - */ -workflow.onComplete { - - // Set up the e-mail variables - def subject = "[nf-core/phaseimpute] Successful: $workflow.runName" - if (!workflow.success) { - subject = "[nf-core/phaseimpute] FAILED: $workflow.runName" - } - def email_fields = [:] - email_fields['version'] = workflow.manifest.version - email_fields['runName'] = custom_runName ?: workflow.runName - email_fields['success'] = workflow.success - email_fields['dateComplete'] = workflow.complete - email_fields['duration'] = workflow.duration - email_fields['exitStatus'] = workflow.exitStatus - email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') - email_fields['errorReport'] = (workflow.errorReport ?: 'None') - email_fields['commandLine'] = workflow.commandLine - email_fields['projectDir'] = workflow.projectDir - email_fields['summary'] = summary - email_fields['summary']['Date Started'] = workflow.start - email_fields['summary']['Date Completed'] = workflow.complete - email_fields['summary']['Pipeline script file path'] = workflow.scriptFile - email_fields['summary']['Pipeline script hash ID'] = workflow.scriptId - if (workflow.repository) email_fields['summary']['Pipeline repository Git URL'] = workflow.repository - if (workflow.commitId) email_fields['summary']['Pipeline repository Git Commit'] = workflow.commitId - if (workflow.revision) email_fields['summary']['Pipeline Git branch/tag'] = workflow.revision - email_fields['summary']['Nextflow Version'] = workflow.nextflow.version - email_fields['summary']['Nextflow Build'] = workflow.nextflow.build - email_fields['summary']['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp - - // TODO nf-core: If not using MultiQC, strip out this code (including params.max_multiqc_email_size) - // On success try attach the multiqc report - def mqc_report = null - try { - if (workflow.success) { - mqc_report = ch_multiqc_report.getVal() - if (mqc_report.getClass() == ArrayList) { - log.warn "[nf-core/phaseimpute] Found multiple reports from process 'multiqc', will use only one" - mqc_report = mqc_report[0] - } - } - } catch (all) { - log.warn "[nf-core/phaseimpute] Could not attach MultiQC report to summary email" - } - - // Check if we are only sending emails on failure - email_address = params.email - if (!params.email && params.email_on_fail && !workflow.success) { - email_address = params.email_on_fail - } - - // Render the TXT template - def engine = new groovy.text.GStringTemplateEngine() - def tf = new File("$baseDir/assets/email_template.txt") - def txt_template = engine.createTemplate(tf).make(email_fields) - def email_txt = txt_template.toString() - - // Render the HTML template - def hf = new File("$baseDir/assets/email_template.html") - def html_template = engine.createTemplate(hf).make(email_fields) - def email_html = html_template.toString() - - // Render the sendmail template - def smail_fields = [ email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, baseDir: "$baseDir", mqcFile: mqc_report, mqcMaxSize: params.max_multiqc_email_size.toBytes() ] - def sf = new File("$baseDir/assets/sendmail_template.txt") - def sendmail_template = engine.createTemplate(sf).make(smail_fields) - def sendmail_html = sendmail_template.toString() - - // Send the HTML e-mail - if (email_address) { - try { - if (params.plaintext_email) { throw GroovyException('Send plaintext e-mail, not HTML') } - // Try to send HTML e-mail using sendmail - [ 'sendmail', '-t' ].execute() << sendmail_html - log.info "[nf-core/phaseimpute] Sent summary e-mail to $email_address (sendmail)" - } catch (all) { - // Catch failures and try with plaintext - [ 'mail', '-s', subject, email_address ].execute() << email_txt - log.info "[nf-core/phaseimpute] Sent summary e-mail to $email_address (mail)" - } - } - - // Write summary e-mail HTML to a file - def output_d = new File("${params.outdir}/pipeline_info/") - if (!output_d.exists()) { - output_d.mkdirs() - } - def output_hf = new File(output_d, "pipeline_report.html") - output_hf.withWriter { w -> w << email_html } - def output_tf = new File(output_d, "pipeline_report.txt") - output_tf.withWriter { w -> w << email_txt } - - c_green = params.monochrome_logs ? '' : "\033[0;32m"; - c_purple = params.monochrome_logs ? '' : "\033[0;35m"; - c_red = params.monochrome_logs ? '' : "\033[0;31m"; - c_reset = params.monochrome_logs ? '' : "\033[0m"; - - if (workflow.stats.ignoredCount > 0 && workflow.success) { - log.info "-${c_purple}Warning, pipeline completed, but with errored process(es) ${c_reset}-" - log.info "-${c_red}Number of ignored errored process(es) : ${workflow.stats.ignoredCount} ${c_reset}-" - log.info "-${c_green}Number of successfully ran process(es) : ${workflow.stats.succeedCount} ${c_reset}-" - } - - if (workflow.success) { - log.info "-${c_purple}[nf-core/phaseimpute]${c_green} Pipeline completed successfully${c_reset}-" - } else { - checkHostname() - log.info "-${c_purple}[nf-core/phaseimpute]${c_red} Pipeline completed with errors${c_reset}-" - } - -} - - -def nfcoreHeader() { - // Log colors ANSI codes - c_black = params.monochrome_logs ? '' : "\033[0;30m"; - c_blue = params.monochrome_logs ? '' : "\033[0;34m"; - c_cyan = params.monochrome_logs ? '' : "\033[0;36m"; - c_dim = params.monochrome_logs ? '' : "\033[2m"; - c_green = params.monochrome_logs ? '' : "\033[0;32m"; - c_purple = params.monochrome_logs ? '' : "\033[0;35m"; - c_reset = params.monochrome_logs ? '' : "\033[0m"; - c_white = params.monochrome_logs ? '' : "\033[0;37m"; - c_yellow = params.monochrome_logs ? '' : "\033[0;33m"; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN ALL WORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ - return """ -${c_dim}--------------------------------------------------${c_reset}- - ${c_green},--.${c_black}/${c_green},-.${c_reset} - ${c_blue} ___ __ __ __ ___ ${c_green}/,-._.--~\'${c_reset} - ${c_blue} |\\ | |__ __ / ` / \\ |__) |__ ${c_yellow}} {${c_reset} - ${c_blue} | \\| | \\__, \\__/ | \\ |___ ${c_green}\\`-._,-`-,${c_reset} - ${c_green}`._,._,\'${c_reset} - ${c_purple} nf-core/phaseimpute v${workflow.manifest.version}${c_reset} - -${c_dim}--------------------------------------------------${c_reset}- - """.stripIndent() +// +// WORKFLOW: Execute a single named workflow for the pipeline +// See: https://github.com/nf-core/rnaseq/issues/619 +// +workflow { + NFCORE_PHASEIMPUTE () } -def checkHostname() { - def c_reset = params.monochrome_logs ? '' : "\033[0m" - def c_white = params.monochrome_logs ? '' : "\033[0;37m" - def c_red = params.monochrome_logs ? '' : "\033[1;91m" - def c_yellow_bold = params.monochrome_logs ? '' : "\033[1;93m" - if (params.hostnames) { - def hostname = "hostname".execute().text.trim() - params.hostnames.each { prof, hnames -> - hnames.each { hname -> - if (hostname.contains(hname) && !workflow.profile.contains(prof)) { - log.error "====================================================\n" + - " ${c_red}WARNING!${c_reset} You are running with `-profile $workflow.profile`\n" + - " but your machine hostname is ${c_white}'$hostname'${c_reset}\n" + - " ${c_yellow_bold}It's highly recommended that you use `-profile $prof${c_reset}`\n" + - "============================================================" - } - } - } - } -} +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ diff --git a/modules.json b/modules.json new file mode 100644 index 00000000..f04e53ba --- /dev/null +++ b/modules.json @@ -0,0 +1,27 @@ +{ + "name": "nf-core/phaseimpute", + "homePage": "https://github.com/nf-core/phaseimpute", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": { + "custom/dumpsoftwareversions": { + "branch": "master", + "git_sha": "8ec825f465b9c17f9d83000022995b4f7de6fe93", + "installed_by": ["modules"] + }, + "fastqc": { + "branch": "master", + "git_sha": "c9488585ce7bd35ccd2a30faa2371454c8112fb9", + "installed_by": ["modules"] + }, + "multiqc": { + "branch": "master", + "git_sha": "8ec825f465b9c17f9d83000022995b4f7de6fe93", + "installed_by": ["modules"] + } + } + } + } + } +} diff --git a/modules/local/samplesheet_check.nf b/modules/local/samplesheet_check.nf new file mode 100644 index 00000000..0ab4a9fd --- /dev/null +++ b/modules/local/samplesheet_check.nf @@ -0,0 +1,31 @@ +process SAMPLESHEET_CHECK { + tag "$samplesheet" + label 'process_single' + + conda "conda-forge::python=3.8.3" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/python:3.8.3' : + 'biocontainers/python:3.8.3' }" + + input: + path samplesheet + + output: + path '*.csv' , emit: csv + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: // This script is bundled with the pipeline, in nf-core/phaseimpute/bin/ + """ + check_samplesheet.py \\ + $samplesheet \\ + samplesheet.valid.csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + python: \$(python --version | sed 's/Python //g') + END_VERSIONS + """ +} diff --git a/modules/nf-core/custom/dumpsoftwareversions/environment.yml b/modules/nf-core/custom/dumpsoftwareversions/environment.yml new file mode 100644 index 00000000..9b3272bc --- /dev/null +++ b/modules/nf-core/custom/dumpsoftwareversions/environment.yml @@ -0,0 +1,7 @@ +name: custom_dumpsoftwareversions +channels: + - conda-forge + - bioconda + - defaults +dependencies: + - bioconda::multiqc=1.19 diff --git a/modules/nf-core/custom/dumpsoftwareversions/main.nf b/modules/nf-core/custom/dumpsoftwareversions/main.nf new file mode 100644 index 00000000..f2187611 --- /dev/null +++ b/modules/nf-core/custom/dumpsoftwareversions/main.nf @@ -0,0 +1,24 @@ +process CUSTOM_DUMPSOFTWAREVERSIONS { + label 'process_single' + + // Requires `pyyaml` which does not have a dedicated container but is in the MultiQC container + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/multiqc:1.19--pyhdfd78af_0' : + 'biocontainers/multiqc:1.19--pyhdfd78af_0' }" + + input: + path versions + + output: + path "software_versions.yml" , emit: yml + path "software_versions_mqc.yml", emit: mqc_yml + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + template 'dumpsoftwareversions.py' +} diff --git a/modules/nf-core/custom/dumpsoftwareversions/meta.yml b/modules/nf-core/custom/dumpsoftwareversions/meta.yml new file mode 100644 index 00000000..5f15a5fd --- /dev/null +++ b/modules/nf-core/custom/dumpsoftwareversions/meta.yml @@ -0,0 +1,37 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: custom_dumpsoftwareversions +description: Custom module used to dump software versions within the nf-core pipeline template +keywords: + - custom + - dump + - version +tools: + - custom: + description: Custom module used to dump software versions within the nf-core pipeline template + homepage: https://github.com/nf-core/tools + documentation: https://github.com/nf-core/tools + licence: ["MIT"] +input: + - versions: + type: file + description: YML file containing software versions + pattern: "*.yml" +output: + - yml: + type: file + description: Standard YML file containing software versions + pattern: "software_versions.yml" + - mqc_yml: + type: file + description: MultiQC custom content YML file containing software versions + pattern: "software_versions_mqc.yml" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@drpatelh" + - "@grst" +maintainers: + - "@drpatelh" + - "@grst" diff --git a/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py b/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py new file mode 100755 index 00000000..e55b8d43 --- /dev/null +++ b/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + + +"""Provide functions to merge multiple versions.yml files.""" + + +import platform +from textwrap import dedent + +import yaml + + +def _make_versions_html(versions): + """Generate a tabular HTML output of all versions for MultiQC.""" + html = [ + dedent( + """\\ + + + + + + + + + + """ + ) + ] + for process, tmp_versions in sorted(versions.items()): + html.append("") + for i, (tool, version) in enumerate(sorted(tmp_versions.items())): + html.append( + dedent( + f"""\\ + + + + + + """ + ) + ) + html.append("") + html.append("
    Process Name Software Version
    {process if (i == 0) else ''}{tool}{version}
    ") + return "\\n".join(html) + + +def main(): + """Load all version files and generate merged output.""" + versions_this_module = {} + versions_this_module["${task.process}"] = { + "python": platform.python_version(), + "yaml": yaml.__version__, + } + + with open("$versions") as f: + versions_by_process = yaml.load(f, Loader=yaml.BaseLoader) | versions_this_module + + # aggregate versions by the module name (derived from fully-qualified process name) + versions_by_module = {} + for process, process_versions in versions_by_process.items(): + module = process.split(":")[-1] + try: + if versions_by_module[module] != process_versions: + raise AssertionError( + "We assume that software versions are the same between all modules. " + "If you see this error-message it means you discovered an edge-case " + "and should open an issue in nf-core/tools. " + ) + except KeyError: + versions_by_module[module] = process_versions + + versions_by_module["Workflow"] = { + "Nextflow": "$workflow.nextflow.version", + "$workflow.manifest.name": "$workflow.manifest.version", + } + + versions_mqc = { + "id": "software_versions", + "section_name": "${workflow.manifest.name} Software Versions", + "section_href": "https://github.com/${workflow.manifest.name}", + "plot_type": "html", + "description": "are collected at run time from the software output.", + "data": _make_versions_html(versions_by_module), + } + + with open("software_versions.yml", "w") as f: + yaml.dump(versions_by_module, f, default_flow_style=False) + with open("software_versions_mqc.yml", "w") as f: + yaml.dump(versions_mqc, f, default_flow_style=False) + + with open("versions.yml", "w") as f: + yaml.dump(versions_this_module, f, default_flow_style=False) + + +if __name__ == "__main__": + main() diff --git a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test b/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test new file mode 100644 index 00000000..b1e1630b --- /dev/null +++ b/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test @@ -0,0 +1,43 @@ +nextflow_process { + + name "Test Process CUSTOM_DUMPSOFTWAREVERSIONS" + script "../main.nf" + process "CUSTOM_DUMPSOFTWAREVERSIONS" + tag "modules" + tag "modules_nfcore" + tag "custom" + tag "dumpsoftwareversions" + tag "custom/dumpsoftwareversions" + + test("Should run without failures") { + when { + process { + """ + def tool1_version = ''' + TOOL1: + tool1: 0.11.9 + '''.stripIndent() + + def tool2_version = ''' + TOOL2: + tool2: 1.9 + '''.stripIndent() + + input[0] = Channel.of(tool1_version, tool2_version).collectFile() + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.versions, + file(process.out.mqc_yml[0]).readLines()[0..10], + file(process.out.yml[0]).readLines()[0..7] + ).match() + } + ) + } + } +} diff --git a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap b/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap new file mode 100644 index 00000000..5f59a936 --- /dev/null +++ b/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap @@ -0,0 +1,33 @@ +{ + "Should run without failures": { + "content": [ + [ + "versions.yml:md5,76d454d92244589d32455833f7c1ba6d" + ], + [ + "data: \"\\n\\n \\n \\n \\n \\n \\n \\n \\n\\", + " \\n\\n\\n \\n \\n\\", + " \\ \\n\\n\\n\\n \\n \\", + " \\ \\n \\n\\n\\n\\n\\", + " \\n\\n \\n \\n\\", + " \\ \\n\\n\\n\\n\\n\\n \\n\\", + " \\ \\n \\n\\n\\n\\n\\", + " \\n\\n \\n \\n\\" + ], + [ + "CUSTOM_DUMPSOFTWAREVERSIONS:", + " python: 3.11.7", + " yaml: 5.4.1", + "TOOL1:", + " tool1: 0.11.9", + "TOOL2:", + " tool2: '1.9'", + "Workflow:" + ] + ], + "timestamp": "2024-01-09T23:01:18.710682" + } +} \ No newline at end of file diff --git a/modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml b/modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml new file mode 100644 index 00000000..405aa24a --- /dev/null +++ b/modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml @@ -0,0 +1,2 @@ +custom/dumpsoftwareversions: + - modules/nf-core/custom/dumpsoftwareversions/** diff --git a/modules/nf-core/fastqc/environment.yml b/modules/nf-core/fastqc/environment.yml new file mode 100644 index 00000000..1787b38a --- /dev/null +++ b/modules/nf-core/fastqc/environment.yml @@ -0,0 +1,7 @@ +name: fastqc +channels: + - conda-forge + - bioconda + - defaults +dependencies: + - bioconda::fastqc=0.12.1 diff --git a/modules/nf-core/fastqc/main.nf b/modules/nf-core/fastqc/main.nf new file mode 100644 index 00000000..9e19a74c --- /dev/null +++ b/modules/nf-core/fastqc/main.nf @@ -0,0 +1,55 @@ +process FASTQC { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/fastqc:0.12.1--hdfd78af_0' : + 'biocontainers/fastqc:0.12.1--hdfd78af_0' }" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("*.html"), emit: html + tuple val(meta), path("*.zip") , emit: zip + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + // Make list of old name and new name pairs to use for renaming in the bash while loop + def old_new_pairs = reads instanceof Path || reads.size() == 1 ? [[ reads, "${prefix}.${reads.extension}" ]] : reads.withIndex().collect { entry, index -> [ entry, "${prefix}_${index + 1}.${entry.extension}" ] } + def rename_to = old_new_pairs*.join(' ').join(' ') + def renamed_files = old_new_pairs.collect{ old_name, new_name -> new_name }.join(' ') + """ + printf "%s %s\\n" $rename_to | while read old_name new_name; do + [ -f "\${new_name}" ] || ln -s \$old_name \$new_name + done + + fastqc \\ + $args \\ + --threads $task.cpus \\ + $renamed_files + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.html + touch ${prefix}.zip + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) + END_VERSIONS + """ +} diff --git a/modules/nf-core/fastqc/meta.yml b/modules/nf-core/fastqc/meta.yml new file mode 100644 index 00000000..ee5507e0 --- /dev/null +++ b/modules/nf-core/fastqc/meta.yml @@ -0,0 +1,57 @@ +name: fastqc +description: Run FastQC on sequenced reads +keywords: + - quality control + - qc + - adapters + - fastq +tools: + - fastqc: + description: | + FastQC gives general quality metrics about your reads. + It provides information about the quality score distribution + across your reads, the per base sequence content (%A/C/G/T). + You get information about adapter contamination and other + overrepresented sequences. + homepage: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/ + documentation: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/ + licence: ["GPL-2.0-only"] +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - html: + type: file + description: FastQC report + pattern: "*_{fastqc.html}" + - zip: + type: file + description: FastQC report archive + pattern: "*_{fastqc.zip}" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@drpatelh" + - "@grst" + - "@ewels" + - "@FelixKrueger" +maintainers: + - "@drpatelh" + - "@grst" + - "@ewels" + - "@FelixKrueger" diff --git a/modules/nf-core/fastqc/tests/main.nf.test b/modules/nf-core/fastqc/tests/main.nf.test new file mode 100644 index 00000000..1f21c664 --- /dev/null +++ b/modules/nf-core/fastqc/tests/main.nf.test @@ -0,0 +1,212 @@ +nextflow_process { + + name "Test Process FASTQC" + script "../main.nf" + process "FASTQC" + + tag "modules" + tag "modules_nfcore" + tag "fastqc" + + test("sarscov2 single-end [fastq]") { + + when { + process { + """ + input[0] = Channel.of([ + [ id: 'test', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + + // NOTE The report contains the date inside it, which means that the md5sum is stable per day, but not longer than that. So you can't md5sum it. + // looks like this:
    Mon 2 Oct 2023
    test.gz
    + // https://github.com/nf-core/modules/pull/3903#issuecomment-1743620039 + + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("") }, + + { assert snapshot(process.out.versions).match("versions") } + ) + } + } + + test("sarscov2 paired-end [fastq]") { + + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + + { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, + { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, + { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, + { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, + { assert path(process.out.html[0][1][0]).text.contains("") }, + { assert path(process.out.html[0][1][1]).text.contains("") }, + + { assert snapshot(process.out.versions).match("versions") } + ) + } + } + + test("sarscov2 interleaved [fastq]") { + + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("") }, + + { assert snapshot(process.out.versions).match("versions") } + ) + } + } + + test("sarscov2 paired-end [bam]") { + + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("") }, + + { assert snapshot(process.out.versions).match("versions") } + ) + } + } + + test("sarscov2 multiple [fastq]") { + + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + + { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, + { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, + { assert process.out.html[0][1][2] ==~ ".*/test_3_fastqc.html" }, + { assert process.out.html[0][1][3] ==~ ".*/test_4_fastqc.html" }, + { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, + { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, + { assert process.out.zip[0][1][2] ==~ ".*/test_3_fastqc.zip" }, + { assert process.out.zip[0][1][3] ==~ ".*/test_4_fastqc.zip" }, + { assert path(process.out.html[0][1][0]).text.contains("") }, + { assert path(process.out.html[0][1][1]).text.contains("") }, + { assert path(process.out.html[0][1][2]).text.contains("") }, + { assert path(process.out.html[0][1][3]).text.contains("") }, + + { assert snapshot(process.out.versions).match("versions") } + ) + } + } + + test("sarscov2 custom_prefix") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'mysample', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + + { assert process.out.html[0][1] ==~ ".*/mysample_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("") }, + + { assert snapshot(process.out.versions).match("versions") } + ) + } + } + + test("sarscov2 single-end [fastq] - stub") { + + options "-stub" + + when { + process { + """ + input[0] = Channel.of([ + [ id: 'test', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out.html.collect { file(it[1]).getName() } + + process.out.zip.collect { file(it[1]).getName() } + + process.out.versions ).match() } + ) + } + } + +} diff --git a/modules/nf-core/fastqc/tests/main.nf.test.snap b/modules/nf-core/fastqc/tests/main.nf.test.snap new file mode 100644 index 00000000..5d624bb8 --- /dev/null +++ b/modules/nf-core/fastqc/tests/main.nf.test.snap @@ -0,0 +1,20 @@ +{ + "sarscov2 single-end [fastq] - stub": { + "content": [ + [ + "test.html", + "test.zip", + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ] + ], + "timestamp": "2024-01-17T18:40:57.254299" + }, + "versions": { + "content": [ + [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ] + ], + "timestamp": "2024-01-17T18:36:50.033627" + } +} \ No newline at end of file diff --git a/modules/nf-core/fastqc/tests/tags.yml b/modules/nf-core/fastqc/tests/tags.yml new file mode 100644 index 00000000..7834294b --- /dev/null +++ b/modules/nf-core/fastqc/tests/tags.yml @@ -0,0 +1,2 @@ +fastqc: + - modules/nf-core/fastqc/** diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml new file mode 100644 index 00000000..7625b752 --- /dev/null +++ b/modules/nf-core/multiqc/environment.yml @@ -0,0 +1,7 @@ +name: multiqc +channels: + - conda-forge + - bioconda + - defaults +dependencies: + - bioconda::multiqc=1.19 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf new file mode 100644 index 00000000..1b9f7c43 --- /dev/null +++ b/modules/nf-core/multiqc/main.nf @@ -0,0 +1,55 @@ +process MULTIQC { + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/multiqc:1.19--pyhdfd78af_0' : + 'biocontainers/multiqc:1.19--pyhdfd78af_0' }" + + input: + path multiqc_files, stageAs: "?/*" + path(multiqc_config) + path(extra_multiqc_config) + path(multiqc_logo) + + output: + path "*multiqc_report.html", emit: report + path "*_data" , emit: data + path "*_plots" , optional:true, emit: plots + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def config = multiqc_config ? "--config $multiqc_config" : '' + def extra_config = extra_multiqc_config ? "--config $extra_multiqc_config" : '' + def logo = multiqc_logo ? /--cl-config 'custom_logo: "${multiqc_logo}"'/ : '' + """ + multiqc \\ + --force \\ + $args \\ + $config \\ + $extra_config \\ + $logo \\ + . + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) + END_VERSIONS + """ + + stub: + """ + mkdir multiqc_data + touch multiqc_plots + touch multiqc_report.html + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) + END_VERSIONS + """ +} diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml new file mode 100644 index 00000000..45a9bc35 --- /dev/null +++ b/modules/nf-core/multiqc/meta.yml @@ -0,0 +1,58 @@ +name: multiqc +description: Aggregate results from bioinformatics analyses across many samples into a single report +keywords: + - QC + - bioinformatics tools + - Beautiful stand-alone HTML report +tools: + - multiqc: + description: | + MultiQC searches a given directory for analysis logs and compiles a HTML report. + It's a general use tool, perfect for summarising the output from numerous bioinformatics tools. + homepage: https://multiqc.info/ + documentation: https://multiqc.info/docs/ + licence: ["GPL-3.0-or-later"] +input: + - multiqc_files: + type: file + description: | + List of reports / files recognised by MultiQC, for example the html and zip output of FastQC + - multiqc_config: + type: file + description: Optional config yml for MultiQC + pattern: "*.{yml,yaml}" + - extra_multiqc_config: + type: file + description: Second optional config yml for MultiQC. Will override common sections in multiqc_config. + pattern: "*.{yml,yaml}" + - multiqc_logo: + type: file + description: Optional logo file for MultiQC + pattern: "*.{png}" +output: + - report: + type: file + description: MultiQC report file + pattern: "multiqc_report.html" + - data: + type: directory + description: MultiQC data dir + pattern: "multiqc_data" + - plots: + type: file + description: Plots created by MultiQC + pattern: "*_data" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@abhi18av" + - "@bunop" + - "@drpatelh" + - "@jfy133" +maintainers: + - "@abhi18av" + - "@bunop" + - "@drpatelh" + - "@jfy133" diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test new file mode 100644 index 00000000..d0438eda --- /dev/null +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -0,0 +1,83 @@ +nextflow_process { + + name "Test Process MULTIQC" + script "../main.nf" + process "MULTIQC" + tag "modules" + tag "modules_nfcore" + tag "multiqc" + + test("sarscov2 single-end [fastqc]") { + + when { + process { + """ + input[0] = Channel.of([file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz_fastqc_zip'], checkIfExists: true)]) + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, + { assert process.out.data[0] ==~ ".*/multiqc_data" }, + { assert snapshot(process.out.versions).match("versions") } + ) + } + + } + + test("sarscov2 single-end [fastqc] [config]") { + + when { + process { + """ + input[0] = Channel.of([file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz_fastqc_zip'], checkIfExists: true)]) + input[1] = Channel.of(file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true)) + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, + { assert process.out.data[0] ==~ ".*/multiqc_data" }, + { assert snapshot(process.out.versions).match("versions") } + ) + } + } + + test("sarscov2 single-end [fastqc] - stub") { + + options "-stub" + + when { + process { + """ + input[0] = Channel.of([file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz_fastqc_zip'], checkIfExists: true)]) + input[1] = [] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.report.collect { file(it).getName() } + + process.out.data.collect { file(it).getName() } + + process.out.plots.collect { file(it).getName() } + + process.out.versions ).match() } + ) + } + + } +} diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap new file mode 100644 index 00000000..d37e7304 --- /dev/null +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -0,0 +1,21 @@ +{ + "versions": { + "content": [ + [ + "versions.yml:md5,14e9a2661241abd828f4f06a7b5c222d" + ] + ], + "timestamp": "2024-01-09T23:02:49.911994" + }, + "sarscov2 single-end [fastqc] - stub": { + "content": [ + [ + "multiqc_report.html", + "multiqc_data", + "multiqc_plots", + "versions.yml:md5,14e9a2661241abd828f4f06a7b5c222d" + ] + ], + "timestamp": "2024-01-09T23:03:14.524346" + } +} \ No newline at end of file diff --git a/modules/nf-core/multiqc/tests/tags.yml b/modules/nf-core/multiqc/tests/tags.yml new file mode 100644 index 00000000..bea6c0d3 --- /dev/null +++ b/modules/nf-core/multiqc/tests/tags.yml @@ -0,0 +1,2 @@ +multiqc: + - modules/nf-core/multiqc/** diff --git a/nextflow.config b/nextflow.config index 4a3831e0..0b70c1aa 100644 --- a/nextflow.config +++ b/nextflow.config @@ -1,147 +1,275 @@ /* - * ------------------------------------------------- - * nf-core/phaseimpute Nextflow config file - * ------------------------------------------------- - * Default config options for all environments. - */ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + nf-core/phaseimpute Nextflow config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Default config options for all compute environments +---------------------------------------------------------------------------------------- +*/ // Global default params, used in configs params { - // Workflow flags - // TODO nf-core: Specify your pipeline's command line flags - genome = false - reads = "data/*{1,2}.fastq.gz" - single_end = false - outdir = './results' - - // Boilerplate options - name = false - multiqc_config = false - email = false - email_on_fail = false - max_multiqc_email_size = 25.MB - plaintext_email = false - monochrome_logs = false - help = false - igenomes_base = 's3://ngi-igenomes/igenomes/' - tracedir = "${params.outdir}/pipeline_info" - igenomes_ignore = false - custom_config_version = 'master' - custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" - hostnames = false - config_profile_description = false - config_profile_contact = false - config_profile_url = false - - // Defaults only, expecting to be overwritten - max_memory = 128.GB - max_cpus = 16 - max_time = 240.h + // TODO nf-core: Specify your pipeline's command line flags + // Input options + input = null + // References + genome = null + igenomes_base = 's3://ngi-igenomes/igenomes/' + igenomes_ignore = false + -} + // MultiQC options + multiqc_config = null + multiqc_title = null + multiqc_logo = null + max_multiqc_email_size = '25.MB' + multiqc_methods_description = null + + // Boilerplate options + outdir = null + publish_dir_mode = 'copy' + email = null + email_on_fail = null + plaintext_email = false + monochrome_logs = false + hook_url = null + help = false + version = false + + // Config options + config_profile_name = null + config_profile_description = null + custom_config_version = 'master' + custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" + config_profile_contact = null + config_profile_url = null + -// Container slug. Stable releases should specify release tag! -// Developmental code should specify :dev -process.container = 'nfcore/phaseimpute:dev' + // Max resource options + // Defaults only, expecting to be overwritten + max_memory = '128.GB' + max_cpus = 16 + max_time = '240.h' + + // Schema validation default options + validationFailUnrecognisedParams = false + validationLenientMode = false + validationSchemaIgnoreParams = 'genomes,igenomes_base' + validationShowHiddenParams = false + validate_params = true + +} // Load base.config by default for all pipelines includeConfig 'conf/base.config' // Load nf-core custom profiles from different Institutions try { - includeConfig "${params.custom_config_base}/nfcore_custom.config" + includeConfig "${params.custom_config_base}/nfcore_custom.config" } catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}/nfcore_custom.config") + System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}/nfcore_custom.config") } +// Load nf-core/phaseimpute custom profiles from different institutions. +// Warning: Uncomment only if a pipeline-specific institutional config already exists on nf-core/configs! +// try { +// includeConfig "${params.custom_config_base}/pipeline/phaseimpute.config" +// } catch (Exception e) { +// System.err.println("WARNING: Could not load nf-core/config/phaseimpute profiles: ${params.custom_config_base}/pipeline/phaseimpute.config") +// } profiles { - conda { process.conda = "$baseDir/environment.yml" } - debug { process.beforeScript = 'echo $HOSTNAME' } - docker { - docker.enabled = true - // Avoid this error: - // WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap. - // Testing this in nf-core after discussion here https://github.com/nf-core/tools/pull/351 - // once this is established and works well, nextflow might implement this behavior as new default. - docker.runOptions = '-u \$(id -u):\$(id -g)' - } - singularity { - singularity.enabled = true - singularity.autoMounts = true - } - test { includeConfig 'conf/test.config' } + debug { + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false + nextflow.enable.configProcessNamesValidation = true + } + conda { + conda.enabled = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + channels = ['conda-forge', 'bioconda', 'defaults'] + apptainer.enabled = false + } + mamba { + conda.enabled = true + conda.useMamba = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + docker { + docker.enabled = true + conda.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + docker.runOptions = '-u $(id -u):$(id -g)' + } + arm { + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + } + singularity { + singularity.enabled = true + singularity.autoMounts = true + conda.enabled = false + docker.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + podman { + podman.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + shifter { + shifter.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + charliecloud { + charliecloud.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + apptainer.enabled = false + } + apptainer { + apptainer.enabled = true + apptainer.autoMounts = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + } + gitpod { + executor.name = 'local' + executor.cpus = 4 + executor.memory = 8.GB + } + test { includeConfig 'conf/test.config' } + test_full { includeConfig 'conf/test_full.config' } +} + +// Set default registry for Apptainer, Docker, Podman and Singularity independent of -profile +// Will not be used unless Apptainer / Docker / Podman / Singularity are enabled +// Set to your registry if you have a mirror of containers +apptainer.registry = 'quay.io' +docker.registry = 'quay.io' +podman.registry = 'quay.io' +singularity.registry = 'quay.io' + +// Nextflow plugins +plugins { + id 'nf-validation@1.1.3' // Validation of pipeline parameters and creation of an input channel from a sample sheet } // Load igenomes.config if required if (!params.igenomes_ignore) { - includeConfig 'conf/igenomes.config' + includeConfig 'conf/igenomes.config' +} else { + params.genomes = [:] } +// Export these variables to prevent local Python/R libraries from conflicting with those in the container +// The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. +// See https://apeltzer.github.io/post/03-julia-lang-nextflow/ for details on that. Once we have a common agreement on where to keep Julia packages, this is adjustable. -// Export this variable to prevent local Python libraries from conflicting with those in the container env { - PYTHONNOUSERSITE = 1 + PYTHONNOUSERSITE = 1 + R_PROFILE_USER = "/.Rprofile" + R_ENVIRON_USER = "/.Renviron" + JULIA_DEPOT_PATH = "/usr/local/share/julia" } // Capture exit codes from upstream processes when piping process.shell = ['/bin/bash', '-euo', 'pipefail'] +// Disable process selector warnings by default. Use debug profile to enable warnings. +nextflow.enable.configProcessNamesValidation = false + +def trace_timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') timeline { - enabled = true - file = "${params.tracedir}/execution_timeline.html" + enabled = true + file = "${params.outdir}/pipeline_info/execution_timeline_${trace_timestamp}.html" } report { - enabled = true - file = "${params.tracedir}/execution_report.html" + enabled = true + file = "${params.outdir}/pipeline_info/execution_report_${trace_timestamp}.html" } trace { - enabled = true - file = "${params.tracedir}/execution_trace.txt" + enabled = true + file = "${params.outdir}/pipeline_info/execution_trace_${trace_timestamp}.txt" } dag { - enabled = true - file = "${params.tracedir}/pipeline_dag.svg" + enabled = true + file = "${params.outdir}/pipeline_info/pipeline_dag_${trace_timestamp}.html" } manifest { - name = 'nf-core/phaseimpute' - author = '@louislenezet' - homePage = 'https://github.com/nf-core/phaseimpute' - description = 'Nf-core pipeline for phasing and imputing genomic data.' - mainScript = 'main.nf' - nextflowVersion = '>=19.10.0' - version = '1.0dev' + name = 'nf-core/phaseimpute' + author = """LouisLeNezet""" + homePage = 'https://github.com/nf-core/phaseimpute' + description = """Phasing and imputation pipeline""" + mainScript = 'main.nf' + nextflowVersion = '!>=23.04.0' + version = '1.0dev' + doi = '' } +// Load modules.config for DSL2 module specific options +includeConfig 'conf/modules.config' + // Function to ensure that resource requirements don't go beyond // a maximum limit def check_max(obj, type) { - if (type == 'memory') { - try { - if (obj.compareTo(params.max_memory as nextflow.util.MemoryUnit) == 1) - return params.max_memory as nextflow.util.MemoryUnit - else - return obj - } catch (all) { - println " ### ERROR ### Max memory '${params.max_memory}' is not valid! Using default value: $obj" - return obj - } - } else if (type == 'time') { - try { - if (obj.compareTo(params.max_time as nextflow.util.Duration) == 1) - return params.max_time as nextflow.util.Duration - else - return obj - } catch (all) { - println " ### ERROR ### Max time '${params.max_time}' is not valid! Using default value: $obj" - return obj - } - } else if (type == 'cpus') { - try { - return Math.min( obj, params.max_cpus as int ) - } catch (all) { - println " ### ERROR ### Max cpus '${params.max_cpus}' is not valid! Using default value: $obj" - return obj + if (type == 'memory') { + try { + if (obj.compareTo(params.max_memory as nextflow.util.MemoryUnit) == 1) + return params.max_memory as nextflow.util.MemoryUnit + else + return obj + } catch (all) { + println " ### ERROR ### Max memory '${params.max_memory}' is not valid! Using default value: $obj" + return obj + } + } else if (type == 'time') { + try { + if (obj.compareTo(params.max_time as nextflow.util.Duration) == 1) + return params.max_time as nextflow.util.Duration + else + return obj + } catch (all) { + println " ### ERROR ### Max time '${params.max_time}' is not valid! Using default value: $obj" + return obj + } + } else if (type == 'cpus') { + try { + return Math.min( obj, params.max_cpus as int ) + } catch (all) { + println " ### ERROR ### Max cpus '${params.max_cpus}' is not valid! Using default value: $obj" + return obj + } } - } } diff --git a/nextflow_schema.json b/nextflow_schema.json new file mode 100644 index 00000000..330021f6 --- /dev/null +++ b/nextflow_schema.json @@ -0,0 +1,288 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://raw.githubusercontent.com/nf-core/phaseimpute/master/nextflow_schema.json", + "title": "nf-core/phaseimpute pipeline parameters", + "description": "Phasing and imputation pipeline", + "type": "object", + "definitions": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["input", "outdir"], + "properties": { + "input": { + "type": "string", + "format": "file-path", + "exists": true, + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "Path to comma-separated file containing information about the samples in the experiment.", + "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row. See [usage docs](https://nf-co.re/phaseimpute/usage#samplesheet-input).", + "fa_icon": "fas fa-file-csv" + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + }, + "email": { + "type": "string", + "description": "Email address for completion summary.", + "fa_icon": "fas fa-envelope", + "help_text": "Set this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.", + "pattern": "^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$" + }, + "multiqc_title": { + "type": "string", + "description": "MultiQC report title. Printed as page header, used for filename if not otherwise specified.", + "fa_icon": "fas fa-file-signature" + } + } + }, + "reference_genome_options": { + "title": "Reference genome options", + "type": "object", + "fa_icon": "fas fa-dna", + "description": "Reference genome related files and options required for the workflow.", + "properties": { + "genome": { + "type": "string", + "description": "Name of iGenomes reference.", + "fa_icon": "fas fa-book", + "help_text": "If using a reference genome configured in the pipeline using iGenomes, use this parameter to give the ID for the reference. This is then used to build the full paths for all required reference genome files e.g. `--genome GRCh38`. \n\nSee the [nf-core website docs](https://nf-co.re/usage/reference_genomes) for more details." + }, + "fasta": { + "type": "string", + "format": "file-path", + "exists": true, + "mimetype": "text/plain", + "pattern": "^\\S+\\.fn?a(sta)?(\\.gz)?$", + "description": "Path to FASTA genome file.", + "help_text": "This parameter is *mandatory* if `--genome` is not specified. If you don't have a BWA index available this will be generated for you automatically. Combine with `--save_reference` to save BWA index for future runs.", + "fa_icon": "far fa-file-code" + }, + "igenomes_ignore": { + "type": "boolean", + "description": "Do not load the iGenomes reference config.", + "fa_icon": "fas fa-ban", + "hidden": true, + "help_text": "Do not load `igenomes.config` when running the pipeline. You may choose this option if you observe clashes between custom parameters and those supplied in `igenomes.config`." + } + } + }, + "institutional_config_options": { + "title": "Institutional config options", + "type": "object", + "fa_icon": "fas fa-university", + "description": "Parameters used to describe centralised config profiles. These should not be edited.", + "help_text": "The centralised nf-core configuration profiles use a handful of pipeline parameters to describe themselves. This information is then printed to the Nextflow log when you run a pipeline. You should not need to change these values when you run a pipeline.", + "properties": { + "custom_config_version": { + "type": "string", + "description": "Git commit id for Institutional configs.", + "default": "master", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "custom_config_base": { + "type": "string", + "description": "Base directory for Institutional configs.", + "default": "https://raw.githubusercontent.com/nf-core/configs/master", + "hidden": true, + "help_text": "If you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.", + "fa_icon": "fas fa-users-cog" + }, + "config_profile_name": { + "type": "string", + "description": "Institutional config name.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_description": { + "type": "string", + "description": "Institutional config description.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_contact": { + "type": "string", + "description": "Institutional config contact information.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_url": { + "type": "string", + "description": "Institutional config URL link.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + } + } + }, + "max_job_request_options": { + "title": "Max job request options", + "type": "object", + "fa_icon": "fab fa-acquisitions-incorporated", + "description": "Set the top limit for requested resources for any single job.", + "help_text": "If you are running on a smaller system, a pipeline step requesting more resources than are available may cause the Nextflow to stop the run with an error. These options allow you to cap the maximum resources requested by any single job so that the pipeline will run on your system.\n\nNote that you can not _increase_ the resources requested by any job using these options. For that you will need your own configuration file. See [the nf-core website](https://nf-co.re/usage/configuration) for details.", + "properties": { + "max_cpus": { + "type": "integer", + "description": "Maximum number of CPUs that can be requested for any single job.", + "default": 16, + "fa_icon": "fas fa-microchip", + "hidden": true, + "help_text": "Use to set an upper-limit for the CPU requirement for each process. Should be an integer e.g. `--max_cpus 1`" + }, + "max_memory": { + "type": "string", + "description": "Maximum amount of memory that can be requested for any single job.", + "default": "128.GB", + "fa_icon": "fas fa-memory", + "pattern": "^\\d+(\\.\\d+)?\\.?\\s*(K|M|G|T)?B$", + "hidden": true, + "help_text": "Use to set an upper-limit for the memory requirement for each process. Should be a string in the format integer-unit e.g. `--max_memory '8.GB'`" + }, + "max_time": { + "type": "string", + "description": "Maximum amount of time that can be requested for any single job.", + "default": "240.h", + "fa_icon": "far fa-clock", + "pattern": "^(\\d+\\.?\\s*(s|m|h|d|day)\\s*)+$", + "hidden": true, + "help_text": "Use to set an upper-limit for the time requirement for each process. Should be a string in the format integer-unit e.g. `--max_time '2.h'`" + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "help": { + "type": "boolean", + "description": "Display help text.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": ["symlink", "rellink", "link", "copy", "copyNoFollow", "move"], + "hidden": true + }, + "email_on_fail": { + "type": "string", + "description": "Email address for completion summary, only when pipeline fails.", + "fa_icon": "fas fa-exclamation-triangle", + "pattern": "^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$", + "help_text": "An email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit successfully.", + "hidden": true + }, + "plaintext_email": { + "type": "boolean", + "description": "Send plain-text email instead of HTML.", + "fa_icon": "fas fa-remove-format", + "hidden": true + }, + "max_multiqc_email_size": { + "type": "string", + "description": "File size limit when attaching MultiQC reports to summary emails.", + "pattern": "^\\d+(\\.\\d+)?\\.?\\s*(K|M|G|T)?B$", + "default": "25.MB", + "fa_icon": "fas fa-file-upload", + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Do not use coloured log outputs.", + "fa_icon": "fas fa-palette", + "hidden": true + }, + "hook_url": { + "type": "string", + "description": "Incoming hook URL for messaging service", + "fa_icon": "fas fa-people-group", + "help_text": "Incoming hook URL for messaging service. Currently, MS Teams and Slack are supported.", + "hidden": true + }, + "multiqc_config": { + "type": "string", + "format": "file-path", + "description": "Custom config file to supply to MultiQC.", + "fa_icon": "fas fa-cog", + "hidden": true + }, + "multiqc_logo": { + "type": "string", + "description": "Custom logo file to supply to MultiQC. File name must also be set in the MultiQC config file", + "fa_icon": "fas fa-image", + "hidden": true + }, + "multiqc_methods_description": { + "type": "string", + "description": "Custom MultiQC yaml file containing HTML including a methods description.", + "fa_icon": "fas fa-cog" + }, + "validate_params": { + "type": "boolean", + "description": "Boolean whether to validate parameters against the schema at runtime", + "default": true, + "fa_icon": "fas fa-check-square", + "hidden": true + }, + "validationShowHiddenParams": { + "type": "boolean", + "fa_icon": "far fa-eye-slash", + "description": "Show all params when using `--help`", + "hidden": true, + "help_text": "By default, parameters set as _hidden_ in the schema are not shown on the command line when a user runs with `--help`. Specifying this option will tell the pipeline to show all parameters." + }, + "validationFailUnrecognisedParams": { + "type": "boolean", + "fa_icon": "far fa-check-circle", + "description": "Validation of parameters fails when an unrecognised parameter is found.", + "hidden": true, + "help_text": "By default, when an unrecognised parameter is found, it returns a warinig." + }, + "validationLenientMode": { + "type": "boolean", + "fa_icon": "far fa-check-circle", + "description": "Validation of parameters in lenient more.", + "hidden": true, + "help_text": "Allows string values that are parseable as numbers or booleans. For further information see [JSONSchema docs](https://github.com/everit-org/json-schema#lenient-mode)." + } + } + } + }, + "allOf": [ + { + "$ref": "#/definitions/input_output_options" + }, + { + "$ref": "#/definitions/reference_genome_options" + }, + { + "$ref": "#/definitions/institutional_config_options" + }, + { + "$ref": "#/definitions/max_job_request_options" + }, + { + "$ref": "#/definitions/generic_options" + } + ] +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..7d08e1c8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +# Config file for Python. Mostly used to configure linting of bin/*.py with Ruff. +# Should be kept the same as nf-core/tools to avoid fighting with template synchronisation. +[tool.ruff] +line-length = 120 +target-version = "py38" +select = ["I", "E1", "E4", "E7", "E9", "F", "UP", "N"] +cache-dir = "~/.cache/ruff" + +[tool.ruff.isort] +known-first-party = ["nf_core"] + +[tool.ruff.per-file-ignores] +"__init__.py" = ["E402", "F401"] diff --git a/subworkflows/local/input_check.nf b/subworkflows/local/input_check.nf new file mode 100644 index 00000000..0aecf87f --- /dev/null +++ b/subworkflows/local/input_check.nf @@ -0,0 +1,44 @@ +// +// Check input samplesheet and get read channels +// + +include { SAMPLESHEET_CHECK } from '../../modules/local/samplesheet_check' + +workflow INPUT_CHECK { + take: + samplesheet // file: /path/to/samplesheet.csv + + main: + SAMPLESHEET_CHECK ( samplesheet ) + .csv + .splitCsv ( header:true, sep:',' ) + .map { create_fastq_channel(it) } + .set { reads } + + emit: + reads // channel: [ val(meta), [ reads ] ] + versions = SAMPLESHEET_CHECK.out.versions // channel: [ versions.yml ] +} + +// Function to get list of [ meta, [ fastq_1, fastq_2 ] ] +def create_fastq_channel(LinkedHashMap row) { + // create meta map + def meta = [:] + meta.id = row.sample + meta.single_end = row.single_end.toBoolean() + + // add path(s) of the fastq file(s) to the meta map + def fastq_meta = [] + if (!file(row.fastq_1).exists()) { + exit 1, "ERROR: Please check input samplesheet -> Read 1 FastQ file does not exist!\n${row.fastq_1}" + } + if (meta.single_end) { + fastq_meta = [ meta, [ file(row.fastq_1) ] ] + } else { + if (!file(row.fastq_2).exists()) { + exit 1, "ERROR: Please check input samplesheet -> Read 2 FastQ file does not exist!\n${row.fastq_2}" + } + fastq_meta = [ meta, [ file(row.fastq_1), file(row.fastq_2) ] ] + } + return fastq_meta +} diff --git a/tower.yml b/tower.yml new file mode 100644 index 00000000..787aedfe --- /dev/null +++ b/tower.yml @@ -0,0 +1,5 @@ +reports: + multiqc_report.html: + display: "MultiQC HTML report" + samplesheet.csv: + display: "Auto-created samplesheet with collated metadata and FASTQ paths" diff --git a/workflows/phaseimpute.nf b/workflows/phaseimpute.nf new file mode 100644 index 00000000..7b195b2e --- /dev/null +++ b/workflows/phaseimpute.nf @@ -0,0 +1,141 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + PRINT PARAMS SUMMARY +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { paramsSummaryLog; paramsSummaryMap } from 'plugin/nf-validation' + +def logo = NfcoreTemplate.logo(workflow, params.monochrome_logs) +def citation = '\n' + WorkflowMain.citation(workflow) + '\n' +def summary_params = paramsSummaryMap(workflow) + +// Print parameter summary log to screen +log.info logo + paramsSummaryLog(workflow) + citation + +WorkflowPhaseimpute.initialise(params, log) + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CONFIG FILES +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +ch_multiqc_config = Channel.fromPath("$projectDir/assets/multiqc_config.yml", checkIfExists: true) +ch_multiqc_custom_config = params.multiqc_config ? Channel.fromPath( params.multiqc_config, checkIfExists: true ) : Channel.empty() +ch_multiqc_logo = params.multiqc_logo ? Channel.fromPath( params.multiqc_logo, checkIfExists: true ) : Channel.empty() +ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT LOCAL MODULES/SUBWORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// SUBWORKFLOW: Consisting of a mix of local and nf-core/modules +// +include { INPUT_CHECK } from '../subworkflows/local/input_check' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT NF-CORE MODULES/SUBWORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// MODULE: Installed directly from nf-core/modules +// +include { FASTQC } from '../modules/nf-core/fastqc/main' +include { MULTIQC } from '../modules/nf-core/multiqc/main' +include { CUSTOM_DUMPSOFTWAREVERSIONS } from '../modules/nf-core/custom/dumpsoftwareversions/main' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// Info required for completion email and summary +def multiqc_report = [] + +workflow PHASEIMPUTE { + + ch_versions = Channel.empty() + + // + // SUBWORKFLOW: Read in samplesheet, validate and stage input files + // + INPUT_CHECK ( + file(params.input) + ) + ch_versions = ch_versions.mix(INPUT_CHECK.out.versions) + // TODO: OPTIONAL, you can use nf-validation plugin to create an input channel from the samplesheet with Channel.fromSamplesheet("input") + // See the documentation https://nextflow-io.github.io/nf-validation/samplesheets/fromSamplesheet/ + // ! There is currently no tooling to help you write a sample sheet schema + + // + // MODULE: Run FastQC + // + FASTQC ( + INPUT_CHECK.out.reads + ) + ch_versions = ch_versions.mix(FASTQC.out.versions.first()) + + CUSTOM_DUMPSOFTWAREVERSIONS ( + ch_versions.unique().collectFile(name: 'collated_versions.yml') + ) + + // + // MODULE: MultiQC + // + workflow_summary = WorkflowPhaseimpute.paramsSummaryMultiqc(workflow, summary_params) + ch_workflow_summary = Channel.value(workflow_summary) + + methods_description = WorkflowPhaseimpute.methodsDescriptionText(workflow, ch_multiqc_custom_methods_description, params) + ch_methods_description = Channel.value(methods_description) + + ch_multiqc_files = Channel.empty() + ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml')) + ch_multiqc_files = ch_multiqc_files.mix(CUSTOM_DUMPSOFTWAREVERSIONS.out.mqc_yml.collect()) + ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.collect{it[1]}.ifEmpty([])) + + MULTIQC ( + ch_multiqc_files.collect(), + ch_multiqc_config.toList(), + ch_multiqc_custom_config.toList(), + ch_multiqc_logo.toList() + ) + multiqc_report = MULTIQC.out.report.toList() +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + COMPLETION EMAIL AND SUMMARY +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow.onComplete { + if (params.email || params.email_on_fail) { + NfcoreTemplate.email(workflow, params, summary_params, projectDir, log, multiqc_report) + } + NfcoreTemplate.dump_parameters(workflow, params) + NfcoreTemplate.summary(workflow, params, log) + if (params.hook_url) { + NfcoreTemplate.IM_notification(workflow, params, summary_params, projectDir, log) + } +} + +workflow.onError { + if (workflow.errorReport.contains("Process requirement exceeds available memory")) { + println("🛑 Default resources exceed availability 🛑 ") + println("💡 See here on how to configure pipeline: https://nf-co.re/docs/usage/configuration#tuning-workflow-resources 💡") + } +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ From ed4f00a5733483f007648c9991be977dbccc0c14 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Tue, 20 Feb 2024 15:29:58 +0000 Subject: [PATCH 2/3] Template update for nf-core/tools version 2.13 --- .editorconfig | 9 +- .github/workflows/awsfulltest.yml | 4 +- .github/workflows/awstest.yml | 4 +- .github/workflows/branch.yml | 2 +- .github/workflows/ci.yml | 7 +- .github/workflows/clean-up.yml | 2 +- .github/workflows/download_pipeline.yml | 17 +- .github/workflows/linting.yml | 12 +- .github/workflows/linting_comment.yml | 4 +- .github/workflows/release-announcements.yml | 11 +- README.md | 5 +- assets/multiqc_config.yml | 2 + assets/schema_input.json | 21 +- bin/check_samplesheet.py | 259 ----------- conf/modules.config | 8 - lib/NfcoreTemplate.groovy | 356 -------------- lib/Utils.groovy | 47 -- lib/WorkflowMain.groovy | 77 --- lib/WorkflowPhaseimpute.groovy | 122 ----- main.nf | 100 ++-- modules.json | 28 +- modules/local/samplesheet_check.nf | 31 -- .../dumpsoftwareversions/environment.yml | 7 - .../custom/dumpsoftwareversions/main.nf | 24 - .../custom/dumpsoftwareversions/meta.yml | 37 -- .../templates/dumpsoftwareversions.py | 102 ---- .../dumpsoftwareversions/tests/main.nf.test | 43 -- .../tests/main.nf.test.snap | 33 -- .../dumpsoftwareversions/tests/tags.yml | 2 - modules/nf-core/fastqc/tests/main.nf.test | 14 +- .../nf-core/fastqc/tests/main.nf.test.snap | 76 ++- modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 4 +- modules/nf-core/multiqc/tests/main.nf.test | 13 +- .../nf-core/multiqc/tests/main.nf.test.snap | 32 +- nextflow.config | 5 +- nextflow_schema.json | 1 + pyproject.toml | 8 +- subworkflows/local/input_check.nf | 44 -- .../utils_nfcore_phaseimpute_pipeline/main.nf | 247 ++++++++++ .../nf-core/utils_nextflow_pipeline/main.nf | 126 +++++ .../nf-core/utils_nextflow_pipeline/meta.yml | 38 ++ .../tests/main.function.nf.test | 54 +++ .../tests/main.function.nf.test.snap | 12 + .../tests/main.workflow.nf.test | 123 +++++ .../tests/nextflow.config | 9 + .../utils_nextflow_pipeline/tests/tags.yml | 2 + .../nf-core/utils_nfcore_pipeline/main.nf | 440 ++++++++++++++++++ .../nf-core/utils_nfcore_pipeline/meta.yml | 24 + .../tests/main.function.nf.test | 134 ++++++ .../tests/main.function.nf.test.snap | 138 ++++++ .../tests/main.workflow.nf.test | 29 ++ .../tests/main.workflow.nf.test.snap | 15 + .../tests/nextflow.config | 9 + .../utils_nfcore_pipeline/tests/tags.yml | 2 + .../nf-core/utils_nfvalidation_plugin/main.nf | 62 +++ .../utils_nfvalidation_plugin/meta.yml | 44 ++ .../tests/main.nf.test | 200 ++++++++ .../tests/nextflow_schema.json | 96 ++++ .../utils_nfvalidation_plugin/tests/tags.yml | 2 + workflows/phaseimpute.nf | 133 ++---- 61 files changed, 2108 insertions(+), 1406 deletions(-) delete mode 100755 bin/check_samplesheet.py delete mode 100755 lib/NfcoreTemplate.groovy delete mode 100644 lib/Utils.groovy delete mode 100755 lib/WorkflowMain.groovy delete mode 100755 lib/WorkflowPhaseimpute.groovy delete mode 100644 modules/local/samplesheet_check.nf delete mode 100644 modules/nf-core/custom/dumpsoftwareversions/environment.yml delete mode 100644 modules/nf-core/custom/dumpsoftwareversions/main.nf delete mode 100644 modules/nf-core/custom/dumpsoftwareversions/meta.yml delete mode 100755 modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py delete mode 100644 modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test delete mode 100644 modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap delete mode 100644 modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml delete mode 100644 subworkflows/local/input_check.nf create mode 100644 subworkflows/local/utils_nfcore_phaseimpute_pipeline/main.nf create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/main.nf create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/meta.yml create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/main.nf create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/meta.yml create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml create mode 100644 subworkflows/nf-core/utils_nfvalidation_plugin/main.nf create mode 100644 subworkflows/nf-core/utils_nfvalidation_plugin/meta.yml create mode 100644 subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test create mode 100644 subworkflows/nf-core/utils_nfvalidation_plugin/tests/nextflow_schema.json create mode 100644 subworkflows/nf-core/utils_nfvalidation_plugin/tests/tags.yml diff --git a/.editorconfig b/.editorconfig index 9b990088..dd9ffa53 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,7 +18,12 @@ end_of_line = unset insert_final_newline = unset trim_trailing_whitespace = unset indent_style = unset -indent_size = unset +[/subworkflows/nf-core/**] +charset = unset +end_of_line = unset +insert_final_newline = unset +trim_trailing_whitespace = unset +indent_style = unset [/assets/email*] indent_size = unset @@ -28,5 +33,5 @@ indent_size = unset indent_style = unset # ignore python -[*.{py}] +[*.{py,md}] indent_style = unset diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 02bfb85c..f1d7dec2 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Launch workflow via tower - uses: seqeralabs/action-tower-launch@v2 + uses: seqeralabs/action-tower-launch@922e5c8d5ac4e918107ec311d2ebbd65e5982b3d # v2 # TODO nf-core: You can customise AWS full pipeline tests as required # Add full size test data (but still relatively small datasets for few samples) # on the `test_full.config` test runs with only one set of parameters @@ -31,7 +31,7 @@ jobs: } profiles: test_full - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 with: name: Tower debug log file path: | diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index 9e6f2bbd..f6c2af9e 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -12,7 +12,7 @@ jobs: steps: # Launch workflow using Tower CLI tool action - name: Launch workflow via tower - uses: seqeralabs/action-tower-launch@v2 + uses: seqeralabs/action-tower-launch@922e5c8d5ac4e918107ec311d2ebbd65e5982b3d # v2 with: workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} @@ -25,7 +25,7 @@ jobs: } profiles: test - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 with: name: Tower debug log file path: | diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index af367f8d..1619a8f9 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -19,7 +19,7 @@ jobs: # NOTE - this doesn't currently work if the PR is coming from a fork, due to limitations in GitHub actions secrets - name: Post PR comment if: failure() - uses: mshick/add-pr-comment@v2 + uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 with: message: | ## This PR is against the `master` branch :x: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30231f6e..4cd73aa0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,13 +28,16 @@ jobs: - "latest-everything" steps: - name: Check out pipeline code - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Install Nextflow - uses: nf-core/setup-nextflow@v1 + uses: nf-core/setup-nextflow@b9f764e8ba5c76b712ace14ecbfcef0e40ae2dd8 # v1 with: version: "${{ matrix.NXF_VER }}" + - name: Disk space cleanup + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - name: Run pipeline with test data # TODO nf-core: You can customise CI pipeline run tests as required # For example: adding multiple test runs with different parameters diff --git a/.github/workflows/clean-up.yml b/.github/workflows/clean-up.yml index e37cfda5..0b6b1f27 100644 --- a/.github/workflows/clean-up.yml +++ b/.github/workflows/clean-up.yml @@ -10,7 +10,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9 with: stale-issue-message: "This issue has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment otherwise this issue will be closed in 20 days." stale-pr-message: "This PR has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment if it is still useful." diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml index 8611458a..f823210d 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -6,6 +6,11 @@ name: Test successful pipeline download with 'nf-core download' # - the head branch of the pull request is updated, i.e. if fixes for a release are pushed last minute to dev. on: workflow_dispatch: + inputs: + testbranch: + description: "The specific branch you wish to utilize for the test execution of nf-core download." + required: true + default: "dev" pull_request: types: - opened @@ -23,13 +28,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Nextflow - uses: nf-core/setup-nextflow@v1 + uses: nf-core/setup-nextflow@b9f764e8ba5c76b712ace14ecbfcef0e40ae2dd8 # v1 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 with: python-version: "3.11" architecture: "x64" - - uses: eWaterCycle/setup-singularity@v7 + - uses: eWaterCycle/setup-singularity@931d4e31109e875b13309ae1d07c70ca8fbc8537 # v7 with: singularity-version: 3.8.3 @@ -42,13 +47,13 @@ jobs: run: | echo "REPO_LOWERCASE=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} echo "REPOTITLE_LOWERCASE=$(basename ${GITHUB_REPOSITORY,,})" >> ${GITHUB_ENV} - echo "REPO_BRANCH=${GITHUB_REF#refs/heads/}" >> ${GITHUB_ENV} + echo "REPO_BRANCH=${{ github.event.inputs.testbranch || 'dev' }}" >> ${GITHUB_ENV} - name: Download the pipeline env: NXF_SINGULARITY_CACHEDIR: ./ run: | - nf-core download ${{ env.REPO_LOWERCASE }} \ + nf-core download ${{ env.REPO_LOWERCASE }} \ --revision ${{ env.REPO_BRANCH }} \ --outdir ./${{ env.REPOTITLE_LOWERCASE }} \ --compress "none" \ @@ -64,4 +69,4 @@ jobs: env: NXF_SINGULARITY_CACHEDIR: ./ NXF_SINGULARITY_HOME_MOUNT: true - run: nextflow run ./${{ env.REPOTITLE_LOWERCASE }}/$( sed 's/\W/_/g' <<< ${{ env.REPO_BRANCH }}) -stub -profile test,singularity --outdir ./results + run: nextflow run ./${{ env.REPOTITLE_LOWERCASE }}/$( sed 's/\W/_/g' <<< ${{ env.REPO_BRANCH }}) -stub -profile test,singularity --outdir ./results diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 81cd098e..748b4311 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -14,10 +14,10 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Set up Python 3.11 - uses: actions/setup-python@v5 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 with: python-version: 3.11 cache: "pip" @@ -32,12 +32,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Install Nextflow - uses: nf-core/setup-nextflow@v1 + uses: nf-core/setup-nextflow@b9f764e8ba5c76b712ace14ecbfcef0e40ae2dd8 # v1 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 with: python-version: "3.11" architecture: "x64" @@ -60,7 +60,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 147bcd10..b706875f 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@v3 + uses: dawidd6/action-download-artifact@f6b0bace624032e30a85a8fd9c1a7f8f611f5737 # v3 with: workflow: linting.yml workflow_conclusion: completed @@ -21,7 +21,7 @@ jobs: run: echo "pr_number=$(cat linting-logs/PR_number.txt)" >> $GITHUB_OUTPUT - name: Post PR comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.pr_number.outputs.pr_number }} diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 21ac3f06..c3674af2 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -9,6 +9,11 @@ jobs: toot: runs-on: ubuntu-latest steps: + - name: get topics and convert to hashtags + id: get_topics + run: | + curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ' > $GITHUB_OUTPUT + - uses: rzr/fediverse-action@master with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} @@ -20,11 +25,13 @@ jobs: Please see the changelog: ${{ github.event.release.html_url }} + ${{ steps.get_topics.outputs.GITHUB_OUTPUT }} #nfcore #openscience #nextflow #bioinformatics + send-tweet: runs-on: ubuntu-latest steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 with: python-version: "3.10" - name: Install dependencies @@ -56,7 +63,7 @@ jobs: bsky-post: runs-on: ubuntu-latest steps: - - uses: zentered/bluesky-post-action@v0.1.0 + - uses: zentered/bluesky-post-action@80dbe0a7697de18c15ad22f4619919ceb5ccf597 # v0.1.0 with: post: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! diff --git a/README.md b/README.md index 5d0b2689..bc332f78 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ nf-core/phaseimpute -[![GitHub Actions CI Status](https://github.com/nf-core/phaseimpute/workflows/nf-core%20CI/badge.svg)](https://github.com/nf-core/phaseimpute/actions?query=workflow%3A%22nf-core+CI%22) -[![GitHub Actions Linting Status](https://github.com/nf-core/phaseimpute/workflows/nf-core%20linting/badge.svg)](https://github.com/nf-core/phaseimpute/actions?query=workflow%3A%22nf-core+linting%22)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/phaseimpute/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) + +[![GitHub Actions CI Status](https://github.com/nf-core/phaseimpute/actions/workflows/ci.yml/badge.svg)](https://github.com/nf-core/phaseimpute/actions/workflows/ci.yml) +[![GitHub Actions Linting Status](https://github.com/nf-core/phaseimpute/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/phaseimpute/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/phaseimpute/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) [![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A523.04.0-23aa62.svg)](https://www.nextflow.io/) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml index 465e50f4..4770e881 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -11,3 +11,5 @@ report_section_order: order: -1002 export_plots: true + +disable_version_detection: true diff --git a/assets/schema_input.json b/assets/schema_input.json index e136b55c..6a7e788c 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -10,25 +10,22 @@ "sample": { "type": "string", "pattern": "^\\S+$", - "errorMessage": "Sample name must be provided and cannot contain spaces" + "errorMessage": "Sample name must be provided and cannot contain spaces", + "meta": ["id"] }, "fastq_1": { "type": "string", + "format": "file-path", + "exists": true, "pattern": "^\\S+\\.f(ast)?q\\.gz$", "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" }, "fastq_2": { - "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'", - "anyOf": [ - { - "type": "string", - "pattern": "^\\S+\\.f(ast)?q\\.gz$" - }, - { - "type": "string", - "maxLength": 0 - } - ] + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" } }, "required": ["sample", "fastq_1"] diff --git a/bin/check_samplesheet.py b/bin/check_samplesheet.py deleted file mode 100755 index 4a758fe0..00000000 --- a/bin/check_samplesheet.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python - - -"""Provide a command line tool to validate and transform tabular samplesheets.""" - - -import argparse -import csv -import logging -import sys -from collections import Counter -from pathlib import Path - -logger = logging.getLogger() - - -class RowChecker: - """ - Define a service that can validate and transform each given row. - - Attributes: - modified (list): A list of dicts, where each dict corresponds to a previously - validated and transformed row. The order of rows is maintained. - - """ - - VALID_FORMATS = ( - ".fq.gz", - ".fastq.gz", - ) - - def __init__( - self, - sample_col="sample", - first_col="fastq_1", - second_col="fastq_2", - single_col="single_end", - **kwargs, - ): - """ - Initialize the row checker with the expected column names. - - Args: - sample_col (str): The name of the column that contains the sample name - (default "sample"). - first_col (str): The name of the column that contains the first (or only) - FASTQ file path (default "fastq_1"). - second_col (str): The name of the column that contains the second (if any) - FASTQ file path (default "fastq_2"). - single_col (str): The name of the new column that will be inserted and - records whether the sample contains single- or paired-end sequencing - reads (default "single_end"). - - """ - super().__init__(**kwargs) - self._sample_col = sample_col - self._first_col = first_col - self._second_col = second_col - self._single_col = single_col - self._seen = set() - self.modified = [] - - def validate_and_transform(self, row): - """ - Perform all validations on the given row and insert the read pairing status. - - Args: - row (dict): A mapping from column headers (keys) to elements of that row - (values). - - """ - self._validate_sample(row) - self._validate_first(row) - self._validate_second(row) - self._validate_pair(row) - self._seen.add((row[self._sample_col], row[self._first_col])) - self.modified.append(row) - - def _validate_sample(self, row): - """Assert that the sample name exists and convert spaces to underscores.""" - if len(row[self._sample_col]) <= 0: - raise AssertionError("Sample input is required.") - # Sanitize samples slightly. - row[self._sample_col] = row[self._sample_col].replace(" ", "_") - - def _validate_first(self, row): - """Assert that the first FASTQ entry is non-empty and has the right format.""" - if len(row[self._first_col]) <= 0: - raise AssertionError("At least the first FASTQ file is required.") - self._validate_fastq_format(row[self._first_col]) - - def _validate_second(self, row): - """Assert that the second FASTQ entry has the right format if it exists.""" - if len(row[self._second_col]) > 0: - self._validate_fastq_format(row[self._second_col]) - - def _validate_pair(self, row): - """Assert that read pairs have the same file extension. Report pair status.""" - if row[self._first_col] and row[self._second_col]: - row[self._single_col] = False - first_col_suffix = Path(row[self._first_col]).suffixes[-2:] - second_col_suffix = Path(row[self._second_col]).suffixes[-2:] - if first_col_suffix != second_col_suffix: - raise AssertionError("FASTQ pairs must have the same file extensions.") - else: - row[self._single_col] = True - - def _validate_fastq_format(self, filename): - """Assert that a given filename has one of the expected FASTQ extensions.""" - if not any(filename.endswith(extension) for extension in self.VALID_FORMATS): - raise AssertionError( - f"The FASTQ file has an unrecognized extension: {filename}\n" - f"It should be one of: {', '.join(self.VALID_FORMATS)}" - ) - - def validate_unique_samples(self): - """ - Assert that the combination of sample name and FASTQ filename is unique. - - In addition to the validation, also rename all samples to have a suffix of _T{n}, where n is the - number of times the same sample exist, but with different FASTQ files, e.g., multiple runs per experiment. - - """ - if len(self._seen) != len(self.modified): - raise AssertionError("The pair of sample name and FASTQ must be unique.") - seen = Counter() - for row in self.modified: - sample = row[self._sample_col] - seen[sample] += 1 - row[self._sample_col] = f"{sample}_T{seen[sample]}" - - -def read_head(handle, num_lines=10): - """Read the specified number of lines from the current position in the file.""" - lines = [] - for idx, line in enumerate(handle): - if idx == num_lines: - break - lines.append(line) - return "".join(lines) - - -def sniff_format(handle): - """ - Detect the tabular format. - - Args: - handle (text file): A handle to a `text file`_ object. The read position is - expected to be at the beginning (index 0). - - Returns: - csv.Dialect: The detected tabular format. - - .. _text file: - https://docs.python.org/3/glossary.html#term-text-file - - """ - peek = read_head(handle) - handle.seek(0) - sniffer = csv.Sniffer() - dialect = sniffer.sniff(peek) - return dialect - - -def check_samplesheet(file_in, file_out): - """ - Check that the tabular samplesheet has the structure expected by nf-core pipelines. - - Validate the general shape of the table, expected columns, and each row. Also add - an additional column which records whether one or two FASTQ reads were found. - - Args: - file_in (pathlib.Path): The given tabular samplesheet. The format can be either - CSV, TSV, or any other format automatically recognized by ``csv.Sniffer``. - file_out (pathlib.Path): Where the validated and transformed samplesheet should - be created; always in CSV format. - - Example: - This function checks that the samplesheet follows the following structure, - see also the `viral recon samplesheet`_:: - - sample,fastq_1,fastq_2 - SAMPLE_PE,SAMPLE_PE_RUN1_1.fastq.gz,SAMPLE_PE_RUN1_2.fastq.gz - SAMPLE_PE,SAMPLE_PE_RUN2_1.fastq.gz,SAMPLE_PE_RUN2_2.fastq.gz - SAMPLE_SE,SAMPLE_SE_RUN1_1.fastq.gz, - - .. _viral recon samplesheet: - https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv - - """ - required_columns = {"sample", "fastq_1", "fastq_2"} - # See https://docs.python.org/3.9/library/csv.html#id3 to read up on `newline=""`. - with file_in.open(newline="") as in_handle: - reader = csv.DictReader(in_handle, dialect=sniff_format(in_handle)) - # Validate the existence of the expected header columns. - if not required_columns.issubset(reader.fieldnames): - req_cols = ", ".join(required_columns) - logger.critical(f"The sample sheet **must** contain these column headers: {req_cols}.") - sys.exit(1) - # Validate each row. - checker = RowChecker() - for i, row in enumerate(reader): - try: - checker.validate_and_transform(row) - except AssertionError as error: - logger.critical(f"{str(error)} On line {i + 2}.") - sys.exit(1) - checker.validate_unique_samples() - header = list(reader.fieldnames) - header.insert(1, "single_end") - # See https://docs.python.org/3.9/library/csv.html#id3 to read up on `newline=""`. - with file_out.open(mode="w", newline="") as out_handle: - writer = csv.DictWriter(out_handle, header, delimiter=",") - writer.writeheader() - for row in checker.modified: - writer.writerow(row) - - -def parse_args(argv=None): - """Define and immediately parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Validate and transform a tabular samplesheet.", - epilog="Example: python check_samplesheet.py samplesheet.csv samplesheet.valid.csv", - ) - parser.add_argument( - "file_in", - metavar="FILE_IN", - type=Path, - help="Tabular input samplesheet in CSV or TSV format.", - ) - parser.add_argument( - "file_out", - metavar="FILE_OUT", - type=Path, - help="Transformed output samplesheet in CSV format.", - ) - parser.add_argument( - "-l", - "--log-level", - help="The desired log level (default WARNING).", - choices=("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"), - default="WARNING", - ) - return parser.parse_args(argv) - - -def main(argv=None): - """Coordinate argument parsing and program execution.""" - args = parse_args(argv) - logging.basicConfig(level=args.log_level, format="[%(levelname)s] %(message)s") - if not args.file_in.is_file(): - logger.error(f"The given input file {args.file_in} was not found!") - sys.exit(2) - args.file_out.parent.mkdir(parents=True, exist_ok=True) - check_samplesheet(args.file_in, args.file_out) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/conf/modules.config b/conf/modules.config index d91c6aba..e3ea8fa6 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -18,14 +18,6 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] - withName: SAMPLESHEET_CHECK { - publishDir = [ - path: { "${params.outdir}/pipeline_info" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: FASTQC { ext.args = '--quiet' } diff --git a/lib/NfcoreTemplate.groovy b/lib/NfcoreTemplate.groovy deleted file mode 100755 index e248e4c3..00000000 --- a/lib/NfcoreTemplate.groovy +++ /dev/null @@ -1,356 +0,0 @@ -// -// This file holds several functions used within the nf-core pipeline template. -// - -import org.yaml.snakeyaml.Yaml -import groovy.json.JsonOutput -import nextflow.extension.FilesEx - -class NfcoreTemplate { - - // - // Check AWS Batch related parameters have been specified correctly - // - public static void awsBatch(workflow, params) { - if (workflow.profile.contains('awsbatch')) { - // Check params.awsqueue and params.awsregion have been set if running on AWSBatch - assert (params.awsqueue && params.awsregion) : "Specify correct --awsqueue and --awsregion parameters on AWSBatch!" - // Check outdir paths to be S3 buckets if running on AWSBatch - assert params.outdir.startsWith('s3:') : "Outdir not on S3 - specify S3 Bucket to run on AWSBatch!" - } - } - - // - // Warn if a -profile or Nextflow config has not been provided to run the pipeline - // - public static void checkConfigProvided(workflow, log) { - if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { - log.warn "[$workflow.manifest.name] You are attempting to run the pipeline without any custom configuration!\n\n" + - "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + - " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + - " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + - " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + - "Please refer to the quick start section and usage docs for the pipeline.\n " - } - } - - // - // Generate version string - // - public static String version(workflow) { - String version_string = "" - - if (workflow.manifest.version) { - def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' - version_string += "${prefix_v}${workflow.manifest.version}" - } - - if (workflow.commitId) { - def git_shortsha = workflow.commitId.substring(0, 7) - version_string += "-g${git_shortsha}" - } - - return version_string - } - - // - // Construct and send completion email - // - public static void email(workflow, params, summary_params, projectDir, log, multiqc_report=[]) { - - // Set up the e-mail variables - def subject = "[$workflow.manifest.name] Successful: $workflow.runName" - if (!workflow.success) { - subject = "[$workflow.manifest.name] FAILED: $workflow.runName" - } - - def summary = [:] - for (group in summary_params.keySet()) { - summary << summary_params[group] - } - - def misc_fields = [:] - misc_fields['Date Started'] = workflow.start - misc_fields['Date Completed'] = workflow.complete - misc_fields['Pipeline script file path'] = workflow.scriptFile - misc_fields['Pipeline script hash ID'] = workflow.scriptId - if (workflow.repository) misc_fields['Pipeline repository Git URL'] = workflow.repository - if (workflow.commitId) misc_fields['Pipeline repository Git Commit'] = workflow.commitId - if (workflow.revision) misc_fields['Pipeline Git branch/tag'] = workflow.revision - misc_fields['Nextflow Version'] = workflow.nextflow.version - misc_fields['Nextflow Build'] = workflow.nextflow.build - misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp - - def email_fields = [:] - email_fields['version'] = NfcoreTemplate.version(workflow) - email_fields['runName'] = workflow.runName - email_fields['success'] = workflow.success - email_fields['dateComplete'] = workflow.complete - email_fields['duration'] = workflow.duration - email_fields['exitStatus'] = workflow.exitStatus - email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') - email_fields['errorReport'] = (workflow.errorReport ?: 'None') - email_fields['commandLine'] = workflow.commandLine - email_fields['projectDir'] = workflow.projectDir - email_fields['summary'] = summary << misc_fields - - // On success try attach the multiqc report - def mqc_report = null - try { - if (workflow.success) { - mqc_report = multiqc_report.getVal() - if (mqc_report.getClass() == ArrayList && mqc_report.size() >= 1) { - if (mqc_report.size() > 1) { - log.warn "[$workflow.manifest.name] Found multiple reports from process 'MULTIQC', will use only one" - } - mqc_report = mqc_report[0] - } - } - } catch (all) { - if (multiqc_report) { - log.warn "[$workflow.manifest.name] Could not attach MultiQC report to summary email" - } - } - - // Check if we are only sending emails on failure - def email_address = params.email - if (!params.email && params.email_on_fail && !workflow.success) { - email_address = params.email_on_fail - } - - // Render the TXT template - def engine = new groovy.text.GStringTemplateEngine() - def tf = new File("$projectDir/assets/email_template.txt") - def txt_template = engine.createTemplate(tf).make(email_fields) - def email_txt = txt_template.toString() - - // Render the HTML template - def hf = new File("$projectDir/assets/email_template.html") - def html_template = engine.createTemplate(hf).make(email_fields) - def email_html = html_template.toString() - - // Render the sendmail template - def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as nextflow.util.MemoryUnit - def smail_fields = [ email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "$projectDir", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes() ] - def sf = new File("$projectDir/assets/sendmail_template.txt") - def sendmail_template = engine.createTemplate(sf).make(smail_fields) - def sendmail_html = sendmail_template.toString() - - // Send the HTML e-mail - Map colors = logColours(params.monochrome_logs) - if (email_address) { - try { - if (params.plaintext_email) { throw GroovyException('Send plaintext e-mail, not HTML') } - // Try to send HTML e-mail using sendmail - def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") - sendmail_tf.withWriter { w -> w << sendmail_html } - [ 'sendmail', '-t' ].execute() << sendmail_html - log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (sendmail)-" - } catch (all) { - // Catch failures and try with plaintext - def mail_cmd = [ 'mail', '-s', subject, '--content-type=text/html', email_address ] - if ( mqc_report != null && mqc_report.size() <= max_multiqc_email_size.toBytes() ) { - mail_cmd += [ '-A', mqc_report ] - } - mail_cmd.execute() << email_html - log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (mail)-" - } - } - - // Write summary e-mail HTML to a file - def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") - output_hf.withWriter { w -> w << email_html } - FilesEx.copyTo(output_hf.toPath(), "${params.outdir}/pipeline_info/pipeline_report.html"); - output_hf.delete() - - // Write summary e-mail TXT to a file - def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") - output_tf.withWriter { w -> w << email_txt } - FilesEx.copyTo(output_tf.toPath(), "${params.outdir}/pipeline_info/pipeline_report.txt"); - output_tf.delete() - } - - // - // Construct and send a notification to a web server as JSON - // e.g. Microsoft Teams and Slack - // - public static void IM_notification(workflow, params, summary_params, projectDir, log) { - def hook_url = params.hook_url - - def summary = [:] - for (group in summary_params.keySet()) { - summary << summary_params[group] - } - - def misc_fields = [:] - misc_fields['start'] = workflow.start - misc_fields['complete'] = workflow.complete - misc_fields['scriptfile'] = workflow.scriptFile - misc_fields['scriptid'] = workflow.scriptId - if (workflow.repository) misc_fields['repository'] = workflow.repository - if (workflow.commitId) misc_fields['commitid'] = workflow.commitId - if (workflow.revision) misc_fields['revision'] = workflow.revision - misc_fields['nxf_version'] = workflow.nextflow.version - misc_fields['nxf_build'] = workflow.nextflow.build - misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp - - def msg_fields = [:] - msg_fields['version'] = NfcoreTemplate.version(workflow) - msg_fields['runName'] = workflow.runName - msg_fields['success'] = workflow.success - msg_fields['dateComplete'] = workflow.complete - msg_fields['duration'] = workflow.duration - msg_fields['exitStatus'] = workflow.exitStatus - msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') - msg_fields['errorReport'] = (workflow.errorReport ?: 'None') - msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") - msg_fields['projectDir'] = workflow.projectDir - msg_fields['summary'] = summary << misc_fields - - // Render the JSON template - def engine = new groovy.text.GStringTemplateEngine() - // Different JSON depending on the service provider - // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format - def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" - def hf = new File("$projectDir/assets/${json_path}") - def json_template = engine.createTemplate(hf).make(msg_fields) - def json_message = json_template.toString() - - // POST - def post = new URL(hook_url).openConnection(); - post.setRequestMethod("POST") - post.setDoOutput(true) - post.setRequestProperty("Content-Type", "application/json") - post.getOutputStream().write(json_message.getBytes("UTF-8")); - def postRC = post.getResponseCode(); - if (! postRC.equals(200)) { - log.warn(post.getErrorStream().getText()); - } - } - - // - // Dump pipeline parameters in a json file - // - public static void dump_parameters(workflow, params) { - def timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') - def filename = "params_${timestamp}.json" - def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") - def jsonStr = JsonOutput.toJson(params) - temp_pf.text = JsonOutput.prettyPrint(jsonStr) - - FilesEx.copyTo(temp_pf.toPath(), "${params.outdir}/pipeline_info/params_${timestamp}.json") - temp_pf.delete() - } - - // - // Print pipeline summary on completion - // - public static void summary(workflow, params, log) { - Map colors = logColours(params.monochrome_logs) - if (workflow.success) { - if (workflow.stats.ignoredCount == 0) { - log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Pipeline completed successfully${colors.reset}-" - } else { - log.info "-${colors.purple}[$workflow.manifest.name]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-" - } - } else { - log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed with errors${colors.reset}-" - } - } - - // - // ANSII Colours used for terminal logging - // - public static Map logColours(Boolean monochrome_logs) { - Map colorcodes = [:] - - // Reset / Meta - colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" - colorcodes['bold'] = monochrome_logs ? '' : "\033[1m" - colorcodes['dim'] = monochrome_logs ? '' : "\033[2m" - colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m" - colorcodes['blink'] = monochrome_logs ? '' : "\033[5m" - colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m" - colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" - - // Regular Colors - colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" - colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" - colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" - colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" - colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" - colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" - colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" - colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" - - // Bold - colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" - colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" - colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" - colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" - colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" - colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" - colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" - colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" - - // Underline - colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" - colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" - colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" - colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" - colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" - colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" - colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" - colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" - - // High Intensity - colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" - colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" - colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" - colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" - colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" - colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" - colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" - colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" - - // Bold High Intensity - colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" - colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" - colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" - colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" - colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" - colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" - colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" - colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" - - return colorcodes - } - - // - // Does what is says on the tin - // - public static String dashedLine(monochrome_logs) { - Map colors = logColours(monochrome_logs) - return "-${colors.dim}----------------------------------------------------${colors.reset}-" - } - - // - // nf-core logo - // - public static String logo(workflow, monochrome_logs) { - Map colors = logColours(monochrome_logs) - String workflow_version = NfcoreTemplate.version(workflow) - String.format( - """\n - ${dashedLine(monochrome_logs)} - ${colors.green},--.${colors.black}/${colors.green},-.${colors.reset} - ${colors.blue} ___ __ __ __ ___ ${colors.green}/,-._.--~\'${colors.reset} - ${colors.blue} |\\ | |__ __ / ` / \\ |__) |__ ${colors.yellow}} {${colors.reset} - ${colors.blue} | \\| | \\__, \\__/ | \\ |___ ${colors.green}\\`-._,-`-,${colors.reset} - ${colors.green}`._,._,\'${colors.reset} - ${colors.purple} ${workflow.manifest.name} ${workflow_version}${colors.reset} - ${dashedLine(monochrome_logs)} - """.stripIndent() - ) - } -} diff --git a/lib/Utils.groovy b/lib/Utils.groovy deleted file mode 100644 index 8d030f4e..00000000 --- a/lib/Utils.groovy +++ /dev/null @@ -1,47 +0,0 @@ -// -// This file holds several Groovy functions that could be useful for any Nextflow pipeline -// - -import org.yaml.snakeyaml.Yaml - -class Utils { - - // - // When running with -profile conda, warn if channels have not been set-up appropriately - // - public static void checkCondaChannels(log) { - Yaml parser = new Yaml() - def channels = [] - try { - def config = parser.load("conda config --show channels".execute().text) - channels = config.channels - } catch(NullPointerException | IOException e) { - log.warn "Could not verify conda channel configuration." - return - } - - // Check that all channels are present - // This channel list is ordered by required channel priority. - def required_channels_in_order = ['conda-forge', 'bioconda', 'defaults'] - def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean - - // Check that they are in the right order - def channel_priority_violation = false - def n = required_channels_in_order.size() - for (int i = 0; i < n - 1; i++) { - channel_priority_violation |= !(channels.indexOf(required_channels_in_order[i]) < channels.indexOf(required_channels_in_order[i+1])) - } - - if (channels_missing | channel_priority_violation) { - log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " There is a problem with your Conda configuration!\n\n" + - " You will need to set-up the conda-forge and bioconda channels correctly.\n" + - " Please refer to https://bioconda.github.io/\n" + - " The observed channel order is \n" + - " ${channels}\n" + - " but the following channel order is required:\n" + - " ${required_channels_in_order}\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - } - } -} diff --git a/lib/WorkflowMain.groovy b/lib/WorkflowMain.groovy deleted file mode 100755 index 0d9976d2..00000000 --- a/lib/WorkflowMain.groovy +++ /dev/null @@ -1,77 +0,0 @@ -// -// This file holds several functions specific to the main.nf workflow in the nf-core/phaseimpute pipeline -// - -import nextflow.Nextflow - -class WorkflowMain { - - // - // Citation string for pipeline - // - public static String citation(workflow) { - return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" + - // TODO nf-core: Add Zenodo DOI for pipeline after first release - //"* The pipeline\n" + - //" https://doi.org/10.5281/zenodo.XXXXXXX\n\n" + - "* The nf-core framework\n" + - " https://doi.org/10.1038/s41587-020-0439-x\n\n" + - "* Software dependencies\n" + - " https://github.com/${workflow.manifest.name}/blob/master/CITATIONS.md" - } - - - // - // Validate parameters and print summary to screen - // - public static void initialise(workflow, params, log, args) { - - // Print workflow version and exit on --version - if (params.version) { - String workflow_version = NfcoreTemplate.version(workflow) - log.info "${workflow.manifest.name} ${workflow_version}" - System.exit(0) - } - - // Check that a -profile or Nextflow config has been provided to run the pipeline - NfcoreTemplate.checkConfigProvided(workflow, log) - // Check that the profile doesn't contain spaces and doesn't end with a trailing comma - checkProfile(workflow.profile, args, log) - - // Check that conda channels are set-up correctly - if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { - Utils.checkCondaChannels(log) - } - - // Check AWS batch settings - NfcoreTemplate.awsBatch(workflow, params) - - // Check input has been provided - if (!params.input) { - Nextflow.error("Please provide an input samplesheet to the pipeline e.g. '--input samplesheet.csv'") - } - } - // - // Get attribute from genome config file e.g. fasta - // - public static Object getGenomeAttribute(params, attribute) { - if (params.genomes && params.genome && params.genomes.containsKey(params.genome)) { - if (params.genomes[ params.genome ].containsKey(attribute)) { - return params.genomes[ params.genome ][ attribute ] - } - } - return null - } - - // - // Exit pipeline if --profile contains spaces - // - private static void checkProfile(profile, args, log) { - if (profile.endsWith(',')) { - Nextflow.error "Profile cannot end with a trailing comma. Please remove the comma from the end of the profile string.\nHint: A common mistake is to provide multiple values to `-profile` separated by spaces. Please use commas to separate profiles instead,e.g., `-profile docker,test`." - } - if (args[0]) { - log.warn "nf-core pipelines do not accept positional arguments. The positional argument `${args[0]}` has been detected.\n Hint: A common mistake is to provide multiple values to `-profile` separated by spaces. Please use commas to separate profiles instead,e.g., `-profile docker,test`." - } - } -} diff --git a/lib/WorkflowPhaseimpute.groovy b/lib/WorkflowPhaseimpute.groovy deleted file mode 100755 index 49575cc1..00000000 --- a/lib/WorkflowPhaseimpute.groovy +++ /dev/null @@ -1,122 +0,0 @@ -// -// This file holds several functions specific to the workflow/phaseimpute.nf in the nf-core/phaseimpute pipeline -// - -import nextflow.Nextflow -import groovy.text.SimpleTemplateEngine - -class WorkflowPhaseimpute { - - // - // Check and validate parameters - // - public static void initialise(params, log) { - - genomeExistsError(params, log) - - - if (!params.fasta) { - Nextflow.error "Genome fasta file not specified with e.g. '--fasta genome.fa' or via a detectable config file." - } - } - - // - // Get workflow summary for MultiQC - // - public static String paramsSummaryMultiqc(workflow, summary) { - String summary_section = '' - for (group in summary.keySet()) { - def group_params = summary.get(group) // This gets the parameters of that particular group - if (group_params) { - summary_section += "

    $group

    \n" - summary_section += "
    \n" - for (param in group_params.keySet()) { - summary_section += "
    $param
    ${group_params.get(param) ?: 'N/A'}
    \n" - } - summary_section += "
    \n" - } - } - - String yaml_file_text = "id: '${workflow.manifest.name.replace('/','-')}-summary'\n" - yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" - yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" - yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" - yaml_file_text += "plot_type: 'html'\n" - yaml_file_text += "data: |\n" - yaml_file_text += "${summary_section}" - return yaml_file_text - } - - // - // Generate methods description for MultiQC - // - - public static String toolCitationText(params) { - - // TODO nf-core: Optionally add in-text citation tools to this list. - // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "Tool (Foo et al. 2023)" : "", - // Uncomment function in methodsDescriptionText to render in MultiQC report - def citation_text = [ - "Tools used in the workflow included:", - "FastQC (Andrews 2010),", - "MultiQC (Ewels et al. 2016)", - "." - ].join(' ').trim() - - return citation_text - } - - public static String toolBibliographyText(params) { - - // TODO Optionally add bibliographic entries to this list. - // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "
  • Author (2023) Pub name, Journal, DOI
  • " : "", - // Uncomment function in methodsDescriptionText to render in MultiQC report - def reference_text = [ - "
  • Andrews S, (2010) FastQC, URL: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/).
  • ", - "
  • Ewels, P., Magnusson, M., Lundin, S., & Käller, M. (2016). MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics , 32(19), 3047–3048. doi: /10.1093/bioinformatics/btw354
  • " - ].join(' ').trim() - - return reference_text - } - - public static String methodsDescriptionText(run_workflow, mqc_methods_yaml, params) { - // Convert to a named map so can be used as with familar NXF ${workflow} variable syntax in the MultiQC YML file - def meta = [:] - meta.workflow = run_workflow.toMap() - meta["manifest_map"] = run_workflow.manifest.toMap() - - // Pipeline DOI - meta["doi_text"] = meta.manifest_map.doi ? "(doi: ${meta.manifest_map.doi})" : "" - meta["nodoi_text"] = meta.manifest_map.doi ? "": "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " - - // Tool references - meta["tool_citations"] = "" - meta["tool_bibliography"] = "" - - // TODO Only uncomment below if logic in toolCitationText/toolBibliographyText has been filled! - //meta["tool_citations"] = toolCitationText(params).replaceAll(", \\.", ".").replaceAll("\\. \\.", ".").replaceAll(", \\.", ".") - //meta["tool_bibliography"] = toolBibliographyText(params) - - - def methods_text = mqc_methods_yaml.text - - def engine = new SimpleTemplateEngine() - def description_html = engine.createTemplate(methods_text).make(meta) - - return description_html - } - - // - // Exit pipeline if incorrect --genome key provided - // - private static void genomeExistsError(params, log) { - if (params.genomes && params.genome && !params.genomes.containsKey(params.genome)) { - def error_string = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " Genome '${params.genome}' not found in any config files provided to the pipeline.\n" + - " Currently, the available genome keys are:\n" + - " ${params.genomes.keySet().join(", ")}\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - Nextflow.error(error_string) - } - } -} diff --git a/main.nf b/main.nf index bbd5914a..fdb2b251 100644 --- a/main.nf +++ b/main.nf @@ -13,66 +13,96 @@ nextflow.enable.dsl = 2 /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - GENOME PARAMETER VALUES + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -// TODO nf-core: Remove this line if you don't need a FASTA file -// This is an example of how to use getGenomeAttribute() to fetch parameters -// from igenomes.config using `--genome` -params.fasta = WorkflowMain.getGenomeAttribute(params, 'fasta') +include { PHASEIMPUTE } from './workflows/phaseimpute' +include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_phaseimpute_pipeline' +include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_phaseimpute_pipeline' + +include { getGenomeAttribute } from './subworkflows/local/utils_nfcore_phaseimpute_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - VALIDATE & PRINT PARAMETER SUMMARY + GENOME PARAMETER VALUES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { validateParameters; paramsHelp } from 'plugin/nf-validation' - -// Print help message if needed -if (params.help) { - def logo = NfcoreTemplate.logo(workflow, params.monochrome_logs) - def citation = '\n' + WorkflowMain.citation(workflow) + '\n' - def String command = "nextflow run ${workflow.manifest.name} --input samplesheet.csv --genome GRCh37 -profile docker" - log.info logo + paramsHelp(command) + citation + NfcoreTemplate.dashedLine(params.monochrome_logs) - System.exit(0) -} - -// Validate input parameters -if (params.validate_params) { - validateParameters() -} - -WorkflowMain.initialise(workflow, params, log, args) +// TODO nf-core: Remove this line if you don't need a FASTA file +// This is an example of how to use getGenomeAttribute() to fetch parameters +// from igenomes.config using `--genome` +params.fasta = getGenomeAttribute('fasta') /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - NAMED WORKFLOW FOR PIPELINE + NAMED WORKFLOWS FOR PIPELINE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { PHASEIMPUTE } from './workflows/phaseimpute' - // -// WORKFLOW: Run main nf-core/phaseimpute analysis pipeline +// WORKFLOW: Run main analysis pipeline depending on type of input // workflow NFCORE_PHASEIMPUTE { - PHASEIMPUTE () -} + take: + samplesheet // channel: samplesheet read in from --input + + main: + + // + // WORKFLOW: Run pipeline + // + PHASEIMPUTE ( + samplesheet + ) + + emit: + multiqc_report = PHASEIMPUTE.out.multiqc_report // channel: /path/to/multiqc_report.html + +} /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - RUN ALL WORKFLOWS + RUN MAIN WORKFLOW ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -// -// WORKFLOW: Execute a single named workflow for the pipeline -// See: https://github.com/nf-core/rnaseq/issues/619 -// workflow { - NFCORE_PHASEIMPUTE () + + main: + + // + // SUBWORKFLOW: Run initialisation tasks + // + PIPELINE_INITIALISATION ( + params.version, + params.help, + params.validate_params, + params.monochrome_logs, + args, + params.outdir, + params.input + ) + + // + // WORKFLOW: Run main workflow + // + NFCORE_PHASEIMPUTE ( + PIPELINE_INITIALISATION.out.samplesheet + ) + + // + // SUBWORKFLOW: Run completion tasks + // + PIPELINE_COMPLETION ( + params.email, + params.email_on_fail, + params.plaintext_email, + params.outdir, + params.monochrome_logs, + params.hook_url, + NFCORE_PHASEIMPUTE.out.multiqc_report + ) } /* diff --git a/modules.json b/modules.json index f04e53ba..06f9e9e2 100644 --- a/modules.json +++ b/modules.json @@ -5,22 +5,36 @@ "https://github.com/nf-core/modules.git": { "modules": { "nf-core": { - "custom/dumpsoftwareversions": { - "branch": "master", - "git_sha": "8ec825f465b9c17f9d83000022995b4f7de6fe93", - "installed_by": ["modules"] - }, "fastqc": { "branch": "master", - "git_sha": "c9488585ce7bd35ccd2a30faa2371454c8112fb9", + "git_sha": "f4ae1d942bd50c5c0b9bd2de1393ce38315ba57c", "installed_by": ["modules"] }, "multiqc": { "branch": "master", - "git_sha": "8ec825f465b9c17f9d83000022995b4f7de6fe93", + "git_sha": "ccacf6f5de6df3bc6d73b665c1fd2933d8bbc290", "installed_by": ["modules"] } } + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "cd08c91373cd00a73255081340e4914485846ba1", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "262b17ed2aad591039f914951659177e6c39a8d8", + "installed_by": ["subworkflows"] + }, + "utils_nfvalidation_plugin": { + "branch": "master", + "git_sha": "cd08c91373cd00a73255081340e4914485846ba1", + "installed_by": ["subworkflows"] + } + } } } } diff --git a/modules/local/samplesheet_check.nf b/modules/local/samplesheet_check.nf deleted file mode 100644 index 0ab4a9fd..00000000 --- a/modules/local/samplesheet_check.nf +++ /dev/null @@ -1,31 +0,0 @@ -process SAMPLESHEET_CHECK { - tag "$samplesheet" - label 'process_single' - - conda "conda-forge::python=3.8.3" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/python:3.8.3' : - 'biocontainers/python:3.8.3' }" - - input: - path samplesheet - - output: - path '*.csv' , emit: csv - path "versions.yml", emit: versions - - when: - task.ext.when == null || task.ext.when - - script: // This script is bundled with the pipeline, in nf-core/phaseimpute/bin/ - """ - check_samplesheet.py \\ - $samplesheet \\ - samplesheet.valid.csv - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - python: \$(python --version | sed 's/Python //g') - END_VERSIONS - """ -} diff --git a/modules/nf-core/custom/dumpsoftwareversions/environment.yml b/modules/nf-core/custom/dumpsoftwareversions/environment.yml deleted file mode 100644 index 9b3272bc..00000000 --- a/modules/nf-core/custom/dumpsoftwareversions/environment.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: custom_dumpsoftwareversions -channels: - - conda-forge - - bioconda - - defaults -dependencies: - - bioconda::multiqc=1.19 diff --git a/modules/nf-core/custom/dumpsoftwareversions/main.nf b/modules/nf-core/custom/dumpsoftwareversions/main.nf deleted file mode 100644 index f2187611..00000000 --- a/modules/nf-core/custom/dumpsoftwareversions/main.nf +++ /dev/null @@ -1,24 +0,0 @@ -process CUSTOM_DUMPSOFTWAREVERSIONS { - label 'process_single' - - // Requires `pyyaml` which does not have a dedicated container but is in the MultiQC container - conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.19--pyhdfd78af_0' : - 'biocontainers/multiqc:1.19--pyhdfd78af_0' }" - - input: - path versions - - output: - path "software_versions.yml" , emit: yml - path "software_versions_mqc.yml", emit: mqc_yml - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - def args = task.ext.args ?: '' - template 'dumpsoftwareversions.py' -} diff --git a/modules/nf-core/custom/dumpsoftwareversions/meta.yml b/modules/nf-core/custom/dumpsoftwareversions/meta.yml deleted file mode 100644 index 5f15a5fd..00000000 --- a/modules/nf-core/custom/dumpsoftwareversions/meta.yml +++ /dev/null @@ -1,37 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json -name: custom_dumpsoftwareversions -description: Custom module used to dump software versions within the nf-core pipeline template -keywords: - - custom - - dump - - version -tools: - - custom: - description: Custom module used to dump software versions within the nf-core pipeline template - homepage: https://github.com/nf-core/tools - documentation: https://github.com/nf-core/tools - licence: ["MIT"] -input: - - versions: - type: file - description: YML file containing software versions - pattern: "*.yml" -output: - - yml: - type: file - description: Standard YML file containing software versions - pattern: "software_versions.yml" - - mqc_yml: - type: file - description: MultiQC custom content YML file containing software versions - pattern: "software_versions_mqc.yml" - - versions: - type: file - description: File containing software versions - pattern: "versions.yml" -authors: - - "@drpatelh" - - "@grst" -maintainers: - - "@drpatelh" - - "@grst" diff --git a/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py b/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py deleted file mode 100755 index e55b8d43..00000000 --- a/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python - - -"""Provide functions to merge multiple versions.yml files.""" - - -import platform -from textwrap import dedent - -import yaml - - -def _make_versions_html(versions): - """Generate a tabular HTML output of all versions for MultiQC.""" - html = [ - dedent( - """\\ - -
    Process Name \\", + " \\ Software Version
    CUSTOM_DUMPSOFTWAREVERSIONSpython3.11.7
    yaml5.4.1
    TOOL1tool10.11.9
    TOOL2tool21.9
    WorkflowNextflow
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    - - - - - - - - """ - ) - ] - for process, tmp_versions in sorted(versions.items()): - html.append("") - for i, (tool, version) in enumerate(sorted(tmp_versions.items())): - html.append( - dedent( - f"""\\ - - - - - - """ - ) - ) - html.append("") - html.append("
    Process Name Software Version
    {process if (i == 0) else ''}{tool}{version}
    ") - return "\\n".join(html) - - -def main(): - """Load all version files and generate merged output.""" - versions_this_module = {} - versions_this_module["${task.process}"] = { - "python": platform.python_version(), - "yaml": yaml.__version__, - } - - with open("$versions") as f: - versions_by_process = yaml.load(f, Loader=yaml.BaseLoader) | versions_this_module - - # aggregate versions by the module name (derived from fully-qualified process name) - versions_by_module = {} - for process, process_versions in versions_by_process.items(): - module = process.split(":")[-1] - try: - if versions_by_module[module] != process_versions: - raise AssertionError( - "We assume that software versions are the same between all modules. " - "If you see this error-message it means you discovered an edge-case " - "and should open an issue in nf-core/tools. " - ) - except KeyError: - versions_by_module[module] = process_versions - - versions_by_module["Workflow"] = { - "Nextflow": "$workflow.nextflow.version", - "$workflow.manifest.name": "$workflow.manifest.version", - } - - versions_mqc = { - "id": "software_versions", - "section_name": "${workflow.manifest.name} Software Versions", - "section_href": "https://github.com/${workflow.manifest.name}", - "plot_type": "html", - "description": "are collected at run time from the software output.", - "data": _make_versions_html(versions_by_module), - } - - with open("software_versions.yml", "w") as f: - yaml.dump(versions_by_module, f, default_flow_style=False) - with open("software_versions_mqc.yml", "w") as f: - yaml.dump(versions_mqc, f, default_flow_style=False) - - with open("versions.yml", "w") as f: - yaml.dump(versions_this_module, f, default_flow_style=False) - - -if __name__ == "__main__": - main() diff --git a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test b/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test deleted file mode 100644 index b1e1630b..00000000 --- a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test +++ /dev/null @@ -1,43 +0,0 @@ -nextflow_process { - - name "Test Process CUSTOM_DUMPSOFTWAREVERSIONS" - script "../main.nf" - process "CUSTOM_DUMPSOFTWAREVERSIONS" - tag "modules" - tag "modules_nfcore" - tag "custom" - tag "dumpsoftwareversions" - tag "custom/dumpsoftwareversions" - - test("Should run without failures") { - when { - process { - """ - def tool1_version = ''' - TOOL1: - tool1: 0.11.9 - '''.stripIndent() - - def tool2_version = ''' - TOOL2: - tool2: 1.9 - '''.stripIndent() - - input[0] = Channel.of(tool1_version, tool2_version).collectFile() - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - process.out.versions, - file(process.out.mqc_yml[0]).readLines()[0..10], - file(process.out.yml[0]).readLines()[0..7] - ).match() - } - ) - } - } -} diff --git a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap b/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap deleted file mode 100644 index 5f59a936..00000000 --- a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap +++ /dev/null @@ -1,33 +0,0 @@ -{ - "Should run without failures": { - "content": [ - [ - "versions.yml:md5,76d454d92244589d32455833f7c1ba6d" - ], - [ - "data: \"\\n\\n \\n \\n \\n \\n \\n \\n \\n\\", - " \\n\\n\\n \\n \\n\\", - " \\ \\n\\n\\n\\n \\n \\", - " \\ \\n \\n\\n\\n\\n\\", - " \\n\\n \\n \\n\\", - " \\ \\n\\n\\n\\n\\n\\n \\n\\", - " \\ \\n \\n\\n\\n\\n\\", - " \\n\\n \\n \\n\\" - ], - [ - "CUSTOM_DUMPSOFTWAREVERSIONS:", - " python: 3.11.7", - " yaml: 5.4.1", - "TOOL1:", - " tool1: 0.11.9", - "TOOL2:", - " tool2: '1.9'", - "Workflow:" - ] - ], - "timestamp": "2024-01-09T23:01:18.710682" - } -} \ No newline at end of file diff --git a/modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml b/modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml deleted file mode 100644 index 405aa24a..00000000 --- a/modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -custom/dumpsoftwareversions: - - modules/nf-core/custom/dumpsoftwareversions/** diff --git a/modules/nf-core/fastqc/tests/main.nf.test b/modules/nf-core/fastqc/tests/main.nf.test index 1f21c664..70edae4d 100644 --- a/modules/nf-core/fastqc/tests/main.nf.test +++ b/modules/nf-core/fastqc/tests/main.nf.test @@ -33,7 +33,7 @@ nextflow_process { { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, { assert path(process.out.html[0][1]).text.contains("") }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("fastqc_versions_single") } ) } } @@ -63,7 +63,7 @@ nextflow_process { { assert path(process.out.html[0][1][0]).text.contains("") }, { assert path(process.out.html[0][1][1]).text.contains("") }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("fastqc_versions_paired") } ) } } @@ -89,7 +89,7 @@ nextflow_process { { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, { assert path(process.out.html[0][1]).text.contains("") }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("fastqc_versions_interleaved") } ) } } @@ -115,7 +115,7 @@ nextflow_process { { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, { assert path(process.out.html[0][1]).text.contains("") }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("fastqc_versions_bam") } ) } } @@ -153,7 +153,7 @@ nextflow_process { { assert path(process.out.html[0][1][2]).text.contains("") }, { assert path(process.out.html[0][1][3]).text.contains("") }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("fastqc_versions_multiple") } ) } } @@ -179,7 +179,7 @@ nextflow_process { { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, { assert path(process.out.html[0][1]).text.contains("") }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("fastqc_versions_custom_prefix") } ) } } @@ -204,7 +204,7 @@ nextflow_process { { assert process.success }, { assert snapshot(process.out.html.collect { file(it[1]).getName() } + process.out.zip.collect { file(it[1]).getName() } + - process.out.versions ).match() } + process.out.versions ).match("fastqc_stub") } ) } } diff --git a/modules/nf-core/fastqc/tests/main.nf.test.snap b/modules/nf-core/fastqc/tests/main.nf.test.snap index 5d624bb8..86f7c311 100644 --- a/modules/nf-core/fastqc/tests/main.nf.test.snap +++ b/modules/nf-core/fastqc/tests/main.nf.test.snap @@ -1,5 +1,17 @@ { - "sarscov2 single-end [fastq] - stub": { + "fastqc_versions_interleaved": { + "content": [ + [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-31T17:40:07.293713" + }, + "fastqc_stub": { "content": [ [ "test.html", @@ -7,14 +19,70 @@ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], - "timestamp": "2024-01-17T18:40:57.254299" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-31T17:31:01.425198" + }, + "fastqc_versions_multiple": { + "content": [ + [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-31T17:40:55.797907" + }, + "fastqc_versions_bam": { + "content": [ + [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-31T17:40:26.795862" + }, + "fastqc_versions_single": { + "content": [ + [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-31T17:39:27.043675" + }, + "fastqc_versions_paired": { + "content": [ + [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-31T17:39:47.584191" }, - "versions": { + "fastqc_versions_custom_prefix": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], - "timestamp": "2024-01-17T18:36:50.033627" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-31T17:41:14.576531" } } \ No newline at end of file diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index 7625b752..2212096a 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -4,4 +4,4 @@ channels: - bioconda - defaults dependencies: - - bioconda::multiqc=1.19 + - bioconda::multiqc=1.20 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 1b9f7c43..354f4430 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,8 +3,8 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.19--pyhdfd78af_0' : - 'biocontainers/multiqc:1.19--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.20--pyhdfd78af_0' : + 'biocontainers/multiqc:1.20--pyhdfd78af_0' }" input: path multiqc_files, stageAs: "?/*" diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test index d0438eda..f1c4242e 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -3,6 +3,7 @@ nextflow_process { name "Test Process MULTIQC" script "../main.nf" process "MULTIQC" + tag "modules" tag "modules_nfcore" tag "multiqc" @@ -12,7 +13,7 @@ nextflow_process { when { process { """ - input[0] = Channel.of([file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz_fastqc_zip'], checkIfExists: true)]) + input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) input[1] = [] input[2] = [] input[3] = [] @@ -25,7 +26,7 @@ nextflow_process { { assert process.success }, { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("multiqc_versions_single") } ) } @@ -36,7 +37,7 @@ nextflow_process { when { process { """ - input[0] = Channel.of([file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz_fastqc_zip'], checkIfExists: true)]) + input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) input[1] = Channel.of(file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true)) input[2] = [] input[3] = [] @@ -49,7 +50,7 @@ nextflow_process { { assert process.success }, { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("multiqc_versions_config") } ) } } @@ -61,7 +62,7 @@ nextflow_process { when { process { """ - input[0] = Channel.of([file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz_fastqc_zip'], checkIfExists: true)]) + input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) input[1] = [] input[2] = [] input[3] = [] @@ -75,7 +76,7 @@ nextflow_process { { assert snapshot(process.out.report.collect { file(it).getName() } + process.out.data.collect { file(it).getName() } + process.out.plots.collect { file(it).getName() } + - process.out.versions ).match() } + process.out.versions ).match("multiqc_stub") } ) } diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index d37e7304..c204b488 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -1,21 +1,41 @@ { - "versions": { + "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,14e9a2661241abd828f4f06a7b5c222d" + "versions.yml:md5,d320d4c37e349c5588e07e7a31cd4186" ] ], - "timestamp": "2024-01-09T23:02:49.911994" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-14T09:28:51.744211298" }, - "sarscov2 single-end [fastqc] - stub": { + "multiqc_stub": { "content": [ [ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,14e9a2661241abd828f4f06a7b5c222d" + "versions.yml:md5,d320d4c37e349c5588e07e7a31cd4186" ] ], - "timestamp": "2024-01-09T23:03:14.524346" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-14T09:29:28.847433492" + }, + "multiqc_versions_config": { + "content": [ + [ + "versions.yml:md5,d320d4c37e349c5588e07e7a31cd4186" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-14T09:29:13.223621555" } } \ No newline at end of file diff --git a/nextflow.config b/nextflow.config index 0b70c1aa..399795d8 100644 --- a/nextflow.config +++ b/nextflow.config @@ -16,9 +16,7 @@ params { genome = null igenomes_base = 's3://ngi-igenomes/igenomes/' igenomes_ignore = false - - - // MultiQC options + fasta = null// MultiQC options multiqc_config = null multiqc_title = null multiqc_logo = null @@ -43,7 +41,6 @@ params { custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" config_profile_contact = null config_profile_url = null - // Max resource options // Defaults only, expecting to be overwritten diff --git a/nextflow_schema.json b/nextflow_schema.json index 330021f6..bfb9e9ae 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -16,6 +16,7 @@ "type": "string", "format": "file-path", "exists": true, + "schema": "assets/schema_input.json", "mimetype": "text/csv", "pattern": "^\\S+\\.csv$", "description": "Path to comma-separated file containing information about the samples in the experiment.", diff --git a/pyproject.toml b/pyproject.toml index 7d08e1c8..56110621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,11 +3,13 @@ [tool.ruff] line-length = 120 target-version = "py38" -select = ["I", "E1", "E4", "E7", "E9", "F", "UP", "N"] cache-dir = "~/.cache/ruff" -[tool.ruff.isort] +[tool.ruff.lint] +select = ["I", "E1", "E4", "E7", "E9", "F", "UP", "N"] + +[tool.ruff.lint.isort] known-first-party = ["nf_core"] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "__init__.py" = ["E402", "F401"] diff --git a/subworkflows/local/input_check.nf b/subworkflows/local/input_check.nf deleted file mode 100644 index 0aecf87f..00000000 --- a/subworkflows/local/input_check.nf +++ /dev/null @@ -1,44 +0,0 @@ -// -// Check input samplesheet and get read channels -// - -include { SAMPLESHEET_CHECK } from '../../modules/local/samplesheet_check' - -workflow INPUT_CHECK { - take: - samplesheet // file: /path/to/samplesheet.csv - - main: - SAMPLESHEET_CHECK ( samplesheet ) - .csv - .splitCsv ( header:true, sep:',' ) - .map { create_fastq_channel(it) } - .set { reads } - - emit: - reads // channel: [ val(meta), [ reads ] ] - versions = SAMPLESHEET_CHECK.out.versions // channel: [ versions.yml ] -} - -// Function to get list of [ meta, [ fastq_1, fastq_2 ] ] -def create_fastq_channel(LinkedHashMap row) { - // create meta map - def meta = [:] - meta.id = row.sample - meta.single_end = row.single_end.toBoolean() - - // add path(s) of the fastq file(s) to the meta map - def fastq_meta = [] - if (!file(row.fastq_1).exists()) { - exit 1, "ERROR: Please check input samplesheet -> Read 1 FastQ file does not exist!\n${row.fastq_1}" - } - if (meta.single_end) { - fastq_meta = [ meta, [ file(row.fastq_1) ] ] - } else { - if (!file(row.fastq_2).exists()) { - exit 1, "ERROR: Please check input samplesheet -> Read 2 FastQ file does not exist!\n${row.fastq_2}" - } - fastq_meta = [ meta, [ file(row.fastq_1), file(row.fastq_2) ] ] - } - return fastq_meta -} diff --git a/subworkflows/local/utils_nfcore_phaseimpute_pipeline/main.nf b/subworkflows/local/utils_nfcore_phaseimpute_pipeline/main.nf new file mode 100644 index 00000000..dc90ad90 --- /dev/null +++ b/subworkflows/local/utils_nfcore_phaseimpute_pipeline/main.nf @@ -0,0 +1,247 @@ +// +// Subworkflow with functionality specific to the nf-core/pipeline pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { UTILS_NFVALIDATION_PLUGIN } from '../../nf-core/utils_nfvalidation_plugin' +include { paramsSummaryMap } from 'plugin/nf-validation' +include { fromSamplesheet } from 'plugin/nf-validation' +include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' +include { completionEmail } from '../../nf-core/utils_nfcore_pipeline' +include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' +include { dashedLine } from '../../nf-core/utils_nfcore_pipeline' +include { nfCoreLogo } from '../../nf-core/utils_nfcore_pipeline' +include { imNotification } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' +include { workflowCitation } from '../../nf-core/utils_nfcore_pipeline' + +/* +======================================================================================== + SUBWORKFLOW TO INITIALISE PIPELINE +======================================================================================== +*/ + +workflow PIPELINE_INITIALISATION { + + take: + version // boolean: Display version and exit + help // boolean: Display help text + validate_params // boolean: Boolean whether to validate parameters against the schema at runtime + monochrome_logs // boolean: Do not use coloured log outputs + nextflow_cli_args // array: List of positional nextflow CLI args + outdir // string: The output directory where the results will be saved + input // string: Path to input samplesheet + + main: + + ch_versions = Channel.empty() + + // + // Print version and exit if required and dump pipeline parameters to JSON file + // + UTILS_NEXTFLOW_PIPELINE ( + version, + true, + outdir, + workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 + ) + + // + // Validate parameters and generate parameter summary to stdout + // + pre_help_text = nfCoreLogo(monochrome_logs) + post_help_text = '\n' + workflowCitation() + '\n' + dashedLine(monochrome_logs) + def String workflow_command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --outdir " + UTILS_NFVALIDATION_PLUGIN ( + help, + workflow_command, + pre_help_text, + post_help_text, + validate_params, + "nextflow_schema.json" + ) + + // + // Check config provided to the pipeline + // + UTILS_NFCORE_PIPELINE ( + nextflow_cli_args + ) + // + // Custom validation for pipeline parameters + // + validateInputParameters() + + // + // Create channel from input file provided through params.input + // + Channel + .fromSamplesheet("input") + .map { + meta, fastq_1, fastq_2 -> + if (!fastq_2) { + return [ meta.id, meta + [ single_end:true ], [ fastq_1 ] ] + } else { + return [ meta.id, meta + [ single_end:false ], [ fastq_1, fastq_2 ] ] + } + } + .groupTuple() + .map { + validateInputSamplesheet(it) + } + .map { + meta, fastqs -> + return [ meta, fastqs.flatten() ] + } + .set { ch_samplesheet } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions +} + +/* +======================================================================================== + SUBWORKFLOW FOR PIPELINE COMPLETION +======================================================================================== +*/ + +workflow PIPELINE_COMPLETION { + + take: + email // string: email address + email_on_fail // string: email address sent on pipeline failure + plaintext_email // boolean: Send plain-text email instead of HTML + outdir // path: Path to output directory where results will be published + monochrome_logs // boolean: Disable ANSI colour codes in log output + hook_url // string: hook URL for notifications + multiqc_report // string: Path to MultiQC report + + main: + + summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + + // + // Completion email and summary + // + workflow.onComplete { + if (email || email_on_fail) { + completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs, multiqc_report.toList()) + } + + completionSummary(monochrome_logs) + + if (hook_url) { + imNotification(summary_params, hook_url) + } + } +} + +/* +======================================================================================== + FUNCTIONS +======================================================================================== +*/ +// +// Check and validate pipeline parameters +// +def validateInputParameters() { + genomeExistsError() +}// +// Validate channels from input samplesheet +// +def validateInputSamplesheet(input) { + def (metas, fastqs) = input[1..2] + + // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end + def endedness_ok = metas.collect{ it.single_end }.unique().size == 1 + if (!endedness_ok) { + error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") + } + + return [ metas[0], fastqs ] +} +// +// Get attribute from genome config file e.g. fasta +// +def getGenomeAttribute(attribute) { + if (params.genomes && params.genome && params.genomes.containsKey(params.genome)) { + if (params.genomes[ params.genome ].containsKey(attribute)) { + return params.genomes[ params.genome ][ attribute ] + } + } + return null +} + +// +// Exit pipeline if incorrect --genome key provided +// +def genomeExistsError() { + if (params.genomes && params.genome && !params.genomes.containsKey(params.genome)) { + def error_string = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + " Genome '${params.genome}' not found in any config files provided to the pipeline.\n" + + " Currently, the available genome keys are:\n" + + " ${params.genomes.keySet().join(", ")}\n" + + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + error(error_string) + } +}// +// Generate methods description for MultiQC +// +def toolCitationText() { + // TODO nf-core: Optionally add in-text citation tools to this list. + // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "Tool (Foo et al. 2023)" : "", + // Uncomment function in methodsDescriptionText to render in MultiQC report + def citation_text = [ + "Tools used in the workflow included:", + "FastQC (Andrews 2010),", + "MultiQC (Ewels et al. 2016)", + "." + ].join(' ').trim() + + return citation_text +} + +def toolBibliographyText() { + // TODO nf-core: Optionally add bibliographic entries to this list. + // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "
  • Author (2023) Pub name, Journal, DOI
  • " : "", + // Uncomment function in methodsDescriptionText to render in MultiQC report + def reference_text = [ + "
  • Andrews S, (2010) FastQC, URL: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/).
  • ", + "
  • Ewels, P., Magnusson, M., Lundin, S., & Käller, M. (2016). MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics , 32(19), 3047–3048. doi: /10.1093/bioinformatics/btw354
  • " + ].join(' ').trim() + + return reference_text +} + +def methodsDescriptionText(mqc_methods_yaml) { + // Convert to a named map so can be used as with familar NXF ${workflow} variable syntax in the MultiQC YML file + def meta = [:] + meta.workflow = workflow.toMap() + meta["manifest_map"] = workflow.manifest.toMap() + + // Pipeline DOI + meta["doi_text"] = meta.manifest_map.doi ? "(doi: ${meta.manifest_map.doi})" : "" + meta["nodoi_text"] = meta.manifest_map.doi ? "": "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " + + // Tool references + meta["tool_citations"] = "" + meta["tool_bibliography"] = "" + + // TODO nf-core: Only uncomment below if logic in toolCitationText/toolBibliographyText has been filled! + // meta["tool_citations"] = toolCitationText().replaceAll(", \\.", ".").replaceAll("\\. \\.", ".").replaceAll(", \\.", ".") + // meta["tool_bibliography"] = toolBibliographyText() + + + def methods_text = mqc_methods_yaml.text + + def engine = new groovy.text.SimpleTemplateEngine() + def description_html = engine.createTemplate(methods_text).make(meta) + + return description_html.toString() +} diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf new file mode 100644 index 00000000..ac31f28f --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf @@ -0,0 +1,126 @@ +// +// Subworkflow with functionality that may be useful for any Nextflow pipeline +// + +import org.yaml.snakeyaml.Yaml +import groovy.json.JsonOutput +import nextflow.extension.FilesEx + +/* +======================================================================================== + SUBWORKFLOW DEFINITION +======================================================================================== +*/ + +workflow UTILS_NEXTFLOW_PIPELINE { + + take: + print_version // boolean: print version + dump_parameters // boolean: dump parameters + outdir // path: base directory used to publish pipeline results + check_conda_channels // boolean: check conda channels + + main: + + // + // Print workflow version and exit on --version + // + if (print_version) { + log.info "${workflow.manifest.name} ${getWorkflowVersion()}" + System.exit(0) + } + + // + // Dump pipeline parameters to a JSON file + // + if (dump_parameters && outdir) { + dumpParametersToJSON(outdir) + } + + // + // When running with Conda, warn if channels have not been set-up appropriately + // + if (check_conda_channels) { + checkCondaChannels() + } + + emit: + dummy_emit = true +} + +/* +======================================================================================== + FUNCTIONS +======================================================================================== +*/ + +// +// Generate version string +// +def getWorkflowVersion() { + String version_string = "" + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Dump pipeline parameters to a JSON file +// +def dumpParametersToJSON(outdir) { + def timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') + def filename = "params_${timestamp}.json" + def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") + def jsonStr = JsonOutput.toJson(params) + temp_pf.text = JsonOutput.prettyPrint(jsonStr) + + FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") + temp_pf.delete() +} + +// +// When running with -profile conda, warn if channels have not been set-up appropriately +// +def checkCondaChannels() { + Yaml parser = new Yaml() + def channels = [] + try { + def config = parser.load("conda config --show channels".execute().text) + channels = config.channels + } catch(NullPointerException | IOException e) { + log.warn "Could not verify conda channel configuration." + return + } + + // Check that all channels are present + // This channel list is ordered by required channel priority. + def required_channels_in_order = ['conda-forge', 'bioconda', 'defaults'] + def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean + + // Check that they are in the right order + def channel_priority_violation = false + def n = required_channels_in_order.size() + for (int i = 0; i < n - 1; i++) { + channel_priority_violation |= !(channels.indexOf(required_channels_in_order[i]) < channels.indexOf(required_channels_in_order[i+1])) + } + + if (channels_missing | channel_priority_violation) { + log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + " There is a problem with your Conda configuration!\n\n" + + " You will need to set-up the conda-forge and bioconda channels correctly.\n" + + " Please refer to https://bioconda.github.io/\n" + + " The observed channel order is \n" + + " ${channels}\n" + + " but the following channel order is required:\n" + + " ${required_channels_in_order}\n" + + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + } +} diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml b/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml new file mode 100644 index 00000000..e5c3a0a8 --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NEXTFLOW_PIPELINE" +description: Subworkflow with functionality that may be useful for any Nextflow pipeline +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - print_version: + type: boolean + description: | + Print the version of the pipeline and exit + - dump_parameters: + type: boolean + description: | + Dump the parameters of the pipeline to a JSON file + - output_directory: + type: directory + description: Path to output dir to write JSON file to. + pattern: "results/" + - check_conda_channel: + type: boolean + description: | + Check if the conda channel priority is correct. +output: + - dummy_emit: + type: boolean + description: | + Dummy emit to make nf-core subworkflows lint happy +authors: + - "@adamrtalbot" + - "@drpatelh" +maintainers: + - "@adamrtalbot" + - "@drpatelh" + - "@maxulysse" diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test new file mode 100644 index 00000000..8ed4310c --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test @@ -0,0 +1,54 @@ + +nextflow_function { + + name "Test Functions" + script "subworkflows/nf-core/utils_nextflow_pipeline/main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Test Function getWorkflowVersion") { + + function "getWorkflowVersion" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function dumpParametersToJSON") { + + function "dumpParametersToJSON" + + when { + function { + """ + // define inputs of the function here. Example: + input[0] = "$outputDir" + """.stripIndent() + } + } + + then { + assertAll( + { assert function.success } + ) + } + } + + test("Test Function checkCondaChannels") { + + function "checkCondaChannels" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 00000000..db2030f8 --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,12 @@ +{ + "Test Function getWorkflowVersion": { + "content": [ + "v9.9.9" + ], + "timestamp": "2024-01-19T11:32:36.031083" + }, + "Test Function checkCondaChannels": { + "content": null, + "timestamp": "2024-01-19T11:32:50.456" + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test new file mode 100644 index 00000000..f7c54bc6 --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,123 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NEXTFLOW_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + workflow "UTILS_NEXTFLOW_PIPELINE" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Should run no inputs") { + + when { + params { + outdir = "tests/results" + } + workflow { + """ + print_version = false + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should print version") { + + when { + params { + outdir = "tests/results" + } + workflow { + """ + print_version = true + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert workflow.stdout.contains("nextflow_workflow v9.9.9") } + ) + } + } + + test("Should dump params") { + + when { + params { + outdir = "$outputDir" + } + workflow { + """ + print_version = false + dump_parameters = true + outdir = params.outdir + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = params.outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should not create params JSON if no output directory") { + + when { + params { + outdir = "$outputDir" + } + workflow { + """ + print_version = false + dump_parameters = true + outdir = params.outdir + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = null + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config b/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config new file mode 100644 index 00000000..53574ffe --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml b/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml new file mode 100644 index 00000000..f8476112 --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml @@ -0,0 +1,2 @@ +subworkflows/utils_nextflow_pipeline: + - subworkflows/nf-core/utils_nextflow_pipeline/** diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf new file mode 100644 index 00000000..a8b55d6f --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -0,0 +1,440 @@ +// +// Subworkflow with utility functions specific to the nf-core pipeline template +// + +import org.yaml.snakeyaml.Yaml +import nextflow.extension.FilesEx + +/* +======================================================================================== + SUBWORKFLOW DEFINITION +======================================================================================== +*/ + +workflow UTILS_NFCORE_PIPELINE { + + take: + nextflow_cli_args + + main: + valid_config = checkConfigProvided() + checkProfileProvided(nextflow_cli_args) + + emit: + valid_config +} + +/* +======================================================================================== + FUNCTIONS +======================================================================================== +*/ + +// +// Warn if a -profile or Nextflow config has not been provided to run the pipeline +// +def checkConfigProvided() { + valid_config = true + if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { + log.warn "[$workflow.manifest.name] You are attempting to run the pipeline without any custom configuration!\n\n" + + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + + "Please refer to the quick start section and usage docs for the pipeline.\n " + valid_config = false + } + return valid_config +} + +// +// Exit pipeline if --profile contains spaces +// +def checkProfileProvided(nextflow_cli_args) { + if (workflow.profile.endsWith(',')) { + error "The `-profile` option cannot end with a trailing comma, please remove it and re-run the pipeline!\n" + + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + } + if (nextflow_cli_args[0]) { + log.warn "nf-core pipelines do not accept positional arguments. The positional argument `${nextflow_cli_args[0]}` has been detected.\n" + + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + } +} + +// +// Citation string for pipeline +// +def workflowCitation() { + return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" + + "* The pipeline\n" + + " ${workflow.manifest.doi}\n\n" + + "* The nf-core framework\n" + + " https://doi.org/10.1038/s41587-020-0439-x\n\n" + + "* Software dependencies\n" + + " https://github.com/${workflow.manifest.name}/blob/master/CITATIONS.md" +} + +// +// Generate workflow version string +// +def getWorkflowVersion() { + String version_string = "" + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Get software versions for pipeline +// +def processVersionsFromYAML(yaml_file) { + Yaml yaml = new Yaml() + versions = yaml.load(yaml_file).collectEntries { k, v -> [ k.tokenize(':')[-1], v ] } + return yaml.dumpAsMap(versions).trim() +} + +// +// Get workflow version for pipeline +// +def workflowVersionToYAML() { + return """ + Workflow: + $workflow.manifest.name: ${getWorkflowVersion()} + Nextflow: $workflow.nextflow.version + """.stripIndent().trim() +} + +// +// Get channel of software versions used in pipeline in YAML format +// +def softwareVersionsToYAML(ch_versions) { + return ch_versions + .unique() + .map { processVersionsFromYAML(it) } + .unique() + .mix(Channel.of(workflowVersionToYAML())) +} + +// +// Get workflow summary for MultiQC +// +def paramsSummaryMultiqc(summary_params) { + def summary_section = '' + for (group in summary_params.keySet()) { + def group_params = summary_params.get(group) // This gets the parameters of that particular group + if (group_params) { + summary_section += "

    $group

    \n" + summary_section += "
    \n" + for (param in group_params.keySet()) { + summary_section += "
    $param
    ${group_params.get(param) ?: 'N/A'}
    \n" + } + summary_section += "
    \n" + } + } + + String yaml_file_text = "id: '${workflow.manifest.name.replace('/','-')}-summary'\n" + yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" + yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" + yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" + yaml_file_text += "plot_type: 'html'\n" + yaml_file_text += "data: |\n" + yaml_file_text += "${summary_section}" + + return yaml_file_text +} + +// +// nf-core logo +// +def nfCoreLogo(monochrome_logs=true) { + Map colors = logColours(monochrome_logs) + String.format( + """\n + ${dashedLine(monochrome_logs)} + ${colors.green},--.${colors.black}/${colors.green},-.${colors.reset} + ${colors.blue} ___ __ __ __ ___ ${colors.green}/,-._.--~\'${colors.reset} + ${colors.blue} |\\ | |__ __ / ` / \\ |__) |__ ${colors.yellow}} {${colors.reset} + ${colors.blue} | \\| | \\__, \\__/ | \\ |___ ${colors.green}\\`-._,-`-,${colors.reset} + ${colors.green}`._,._,\'${colors.reset} + ${colors.purple} ${workflow.manifest.name} ${getWorkflowVersion()}${colors.reset} + ${dashedLine(monochrome_logs)} + """.stripIndent() + ) +} + +// +// Return dashed line +// +def dashedLine(monochrome_logs=true) { + Map colors = logColours(monochrome_logs) + return "-${colors.dim}----------------------------------------------------${colors.reset}-" +} + +// +// ANSII colours used for terminal logging +// +def logColours(monochrome_logs=true) { + Map colorcodes = [:] + + // Reset / Meta + colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" + colorcodes['bold'] = monochrome_logs ? '' : "\033[1m" + colorcodes['dim'] = monochrome_logs ? '' : "\033[2m" + colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m" + colorcodes['blink'] = monochrome_logs ? '' : "\033[5m" + colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m" + colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" + + // Regular Colors + colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" + colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" + colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" + colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" + colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" + colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" + colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" + colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" + + // Bold + colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" + colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" + colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" + colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" + colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" + colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" + colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" + colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" + + // Underline + colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" + colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" + colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" + colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" + colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" + colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" + colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" + colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" + + // High Intensity + colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" + colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" + colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" + colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" + colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" + colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" + colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" + colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" + + // Bold High Intensity + colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" + colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" + colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" + colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" + colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" + colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" + colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" + colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" + + return colorcodes +} + +// +// Attach the multiqc report to email +// +def attachMultiqcReport(multiqc_report) { + def mqc_report = null + try { + if (workflow.success) { + mqc_report = multiqc_report.getVal() + if (mqc_report.getClass() == ArrayList && mqc_report.size() >= 1) { + if (mqc_report.size() > 1) { + log.warn "[$workflow.manifest.name] Found multiple reports from process 'MULTIQC', will use only one" + } + mqc_report = mqc_report[0] + } + } + } catch (all) { + if (multiqc_report) { + log.warn "[$workflow.manifest.name] Could not attach MultiQC report to summary email" + } + } + return mqc_report +} + +// +// Construct and send completion email +// +def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs=true, multiqc_report=null) { + + // Set up the e-mail variables + def subject = "[$workflow.manifest.name] Successful: $workflow.runName" + if (!workflow.success) { + subject = "[$workflow.manifest.name] FAILED: $workflow.runName" + } + + def summary = [:] + for (group in summary_params.keySet()) { + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['Date Started'] = workflow.start + misc_fields['Date Completed'] = workflow.complete + misc_fields['Pipeline script file path'] = workflow.scriptFile + misc_fields['Pipeline script hash ID'] = workflow.scriptId + if (workflow.repository) misc_fields['Pipeline repository Git URL'] = workflow.repository + if (workflow.commitId) misc_fields['Pipeline repository Git Commit'] = workflow.commitId + if (workflow.revision) misc_fields['Pipeline Git branch/tag'] = workflow.revision + misc_fields['Nextflow Version'] = workflow.nextflow.version + misc_fields['Nextflow Build'] = workflow.nextflow.build + misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp + + def email_fields = [:] + email_fields['version'] = getWorkflowVersion() + email_fields['runName'] = workflow.runName + email_fields['success'] = workflow.success + email_fields['dateComplete'] = workflow.complete + email_fields['duration'] = workflow.duration + email_fields['exitStatus'] = workflow.exitStatus + email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + email_fields['errorReport'] = (workflow.errorReport ?: 'None') + email_fields['commandLine'] = workflow.commandLine + email_fields['projectDir'] = workflow.projectDir + email_fields['summary'] = summary << misc_fields + + // On success try attach the multiqc report + def mqc_report = attachMultiqcReport(multiqc_report) + + // Check if we are only sending emails on failure + def email_address = email + if (!email && email_on_fail && !workflow.success) { + email_address = email_on_fail + } + + // Render the TXT template + def engine = new groovy.text.GStringTemplateEngine() + def tf = new File("${workflow.projectDir}/assets/email_template.txt") + def txt_template = engine.createTemplate(tf).make(email_fields) + def email_txt = txt_template.toString() + + // Render the HTML template + def hf = new File("${workflow.projectDir}/assets/email_template.html") + def html_template = engine.createTemplate(hf).make(email_fields) + def email_html = html_template.toString() + + // Render the sendmail template + def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as nextflow.util.MemoryUnit + def smail_fields = [ email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "${workflow.projectDir}", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes() ] + def sf = new File("${workflow.projectDir}/assets/sendmail_template.txt") + def sendmail_template = engine.createTemplate(sf).make(smail_fields) + def sendmail_html = sendmail_template.toString() + + // Send the HTML e-mail + Map colors = logColours(monochrome_logs) + if (email_address) { + try { + if (plaintext_email) { throw GroovyException('Send plaintext e-mail, not HTML') } + // Try to send HTML e-mail using sendmail + def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") + sendmail_tf.withWriter { w -> w << sendmail_html } + [ 'sendmail', '-t' ].execute() << sendmail_html + log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (sendmail)-" + } catch (all) { + // Catch failures and try with plaintext + def mail_cmd = [ 'mail', '-s', subject, '--content-type=text/html', email_address ] + mail_cmd.execute() << email_html + log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (mail)-" + } + } + + // Write summary e-mail HTML to a file + def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") + output_hf.withWriter { w -> w << email_html } + FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html"); + output_hf.delete() + + // Write summary e-mail TXT to a file + def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") + output_tf.withWriter { w -> w << email_txt } + FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt"); + output_tf.delete() +} + +// +// Print pipeline summary on completion +// +def completionSummary(monochrome_logs=true) { + Map colors = logColours(monochrome_logs) + if (workflow.success) { + if (workflow.stats.ignoredCount == 0) { + log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Pipeline completed successfully${colors.reset}-" + } else { + log.info "-${colors.purple}[$workflow.manifest.name]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-" + } + } else { + log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed with errors${colors.reset}-" + } +} + +// +// Construct and send a notification to a web server as JSON e.g. Microsoft Teams and Slack +// +def imNotification(summary_params, hook_url) { + def summary = [:] + for (group in summary_params.keySet()) { + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['start'] = workflow.start + misc_fields['complete'] = workflow.complete + misc_fields['scriptfile'] = workflow.scriptFile + misc_fields['scriptid'] = workflow.scriptId + if (workflow.repository) misc_fields['repository'] = workflow.repository + if (workflow.commitId) misc_fields['commitid'] = workflow.commitId + if (workflow.revision) misc_fields['revision'] = workflow.revision + misc_fields['nxf_version'] = workflow.nextflow.version + misc_fields['nxf_build'] = workflow.nextflow.build + misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp + + def msg_fields = [:] + msg_fields['version'] = getWorkflowVersion() + msg_fields['runName'] = workflow.runName + msg_fields['success'] = workflow.success + msg_fields['dateComplete'] = workflow.complete + msg_fields['duration'] = workflow.duration + msg_fields['exitStatus'] = workflow.exitStatus + msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + msg_fields['errorReport'] = (workflow.errorReport ?: 'None') + msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") + msg_fields['projectDir'] = workflow.projectDir + msg_fields['summary'] = summary << misc_fields + + // Render the JSON template + def engine = new groovy.text.GStringTemplateEngine() + // Different JSON depending on the service provider + // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format + def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" + def hf = new File("${workflow.projectDir}/assets/${json_path}") + def json_template = engine.createTemplate(hf).make(msg_fields) + def json_message = json_template.toString() + + // POST + def post = new URL(hook_url).openConnection(); + post.setRequestMethod("POST") + post.setDoOutput(true) + post.setRequestProperty("Content-Type", "application/json") + post.getOutputStream().write(json_message.getBytes("UTF-8")); + def postRC = post.getResponseCode(); + if (! postRC.equals(200)) { + log.warn(post.getErrorStream().getText()); + } +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml b/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml new file mode 100644 index 00000000..d08d2434 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NFCORE_PIPELINE" +description: Subworkflow with utility functions specific to the nf-core pipeline template +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - nextflow_cli_args: + type: list + description: | + Nextflow CLI positional arguments +output: + - success: + type: boolean + description: | + Dummy output to indicate success +authors: + - "@adamrtalbot" +maintainers: + - "@adamrtalbot" + - "@maxulysse" diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test new file mode 100644 index 00000000..1dc317f8 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test @@ -0,0 +1,134 @@ + +nextflow_function { + + name "Test Functions" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Test Function checkConfigProvided") { + + function "checkConfigProvided" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function checkProfileProvided") { + + function "checkProfileProvided" + + when { + function { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function workflowCitation") { + + function "workflowCitation" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function nfCoreLogo") { + + function "nfCoreLogo" + + when { + function { + """ + input[0] = false + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function dashedLine") { + + function "dashedLine" + + when { + function { + """ + input[0] = false + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function without logColours") { + + function "logColours" + + when { + function { + """ + input[0] = true + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function with logColours") { + function "logColours" + + when { + function { + """ + input[0] = false + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 00000000..10f948e6 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,138 @@ +{ + "Test Function checkProfileProvided": { + "content": null, + "timestamp": "2024-02-09T15:43:55.145717" + }, + "Test Function checkConfigProvided": { + "content": [ + true + ], + "timestamp": "2024-01-19T11:34:13.548431224" + }, + "Test Function nfCoreLogo": { + "content": [ + "\n\n-\u001b[2m----------------------------------------------------\u001b[0m-\n \u001b[0;32m,--.\u001b[0;30m/\u001b[0;32m,-.\u001b[0m\n\u001b[0;34m ___ __ __ __ ___ \u001b[0;32m/,-._.--~'\u001b[0m\n\u001b[0;34m |\\ | |__ __ / ` / \\ |__) |__ \u001b[0;33m} {\u001b[0m\n\u001b[0;34m | \\| | \\__, \\__/ | \\ |___ \u001b[0;32m\\`-._,-`-,\u001b[0m\n \u001b[0;32m`._,._,'\u001b[0m\n\u001b[0;35m nextflow_workflow v9.9.9\u001b[0m\n-\u001b[2m----------------------------------------------------\u001b[0m-\n" + ], + "timestamp": "2024-01-19T11:34:38.840454873" + }, + "Test Function workflowCitation": { + "content": [ + "If you use nextflow_workflow for your analysis please cite:\n\n* The pipeline\n https://doi.org/10.5281/zenodo.5070524\n\n* The nf-core framework\n https://doi.org/10.1038/s41587-020-0439-x\n\n* Software dependencies\n https://github.com/nextflow_workflow/blob/master/CITATIONS.md" + ], + "timestamp": "2024-01-19T11:34:22.24352016" + }, + "Test Function without logColours": { + "content": [ + { + "reset": "", + "bold": "", + "dim": "", + "underlined": "", + "blink": "", + "reverse": "", + "hidden": "", + "black": "", + "red": "", + "green": "", + "yellow": "", + "blue": "", + "purple": "", + "cyan": "", + "white": "", + "bblack": "", + "bred": "", + "bgreen": "", + "byellow": "", + "bblue": "", + "bpurple": "", + "bcyan": "", + "bwhite": "", + "ublack": "", + "ured": "", + "ugreen": "", + "uyellow": "", + "ublue": "", + "upurple": "", + "ucyan": "", + "uwhite": "", + "iblack": "", + "ired": "", + "igreen": "", + "iyellow": "", + "iblue": "", + "ipurple": "", + "icyan": "", + "iwhite": "", + "biblack": "", + "bired": "", + "bigreen": "", + "biyellow": "", + "biblue": "", + "bipurple": "", + "bicyan": "", + "biwhite": "" + } + ], + "timestamp": "2024-01-19T11:35:04.418416984" + }, + "Test Function dashedLine": { + "content": [ + "-\u001b[2m----------------------------------------------------\u001b[0m-" + ], + "timestamp": "2024-01-19T11:34:55.420000755" + }, + "Test Function with logColours": { + "content": [ + { + "reset": "\u001b[0m", + "bold": "\u001b[1m", + "dim": "\u001b[2m", + "underlined": "\u001b[4m", + "blink": "\u001b[5m", + "reverse": "\u001b[7m", + "hidden": "\u001b[8m", + "black": "\u001b[0;30m", + "red": "\u001b[0;31m", + "green": "\u001b[0;32m", + "yellow": "\u001b[0;33m", + "blue": "\u001b[0;34m", + "purple": "\u001b[0;35m", + "cyan": "\u001b[0;36m", + "white": "\u001b[0;37m", + "bblack": "\u001b[1;30m", + "bred": "\u001b[1;31m", + "bgreen": "\u001b[1;32m", + "byellow": "\u001b[1;33m", + "bblue": "\u001b[1;34m", + "bpurple": "\u001b[1;35m", + "bcyan": "\u001b[1;36m", + "bwhite": "\u001b[1;37m", + "ublack": "\u001b[4;30m", + "ured": "\u001b[4;31m", + "ugreen": "\u001b[4;32m", + "uyellow": "\u001b[4;33m", + "ublue": "\u001b[4;34m", + "upurple": "\u001b[4;35m", + "ucyan": "\u001b[4;36m", + "uwhite": "\u001b[4;37m", + "iblack": "\u001b[0;90m", + "ired": "\u001b[0;91m", + "igreen": "\u001b[0;92m", + "iyellow": "\u001b[0;93m", + "iblue": "\u001b[0;94m", + "ipurple": "\u001b[0;95m", + "icyan": "\u001b[0;96m", + "iwhite": "\u001b[0;97m", + "biblack": "\u001b[1;90m", + "bired": "\u001b[1;91m", + "bigreen": "\u001b[1;92m", + "biyellow": "\u001b[1;93m", + "biblue": "\u001b[1;94m", + "bipurple": "\u001b[1;95m", + "bicyan": "\u001b[1;96m", + "biwhite": "\u001b[1;97m" + } + ], + "timestamp": "2024-01-19T11:35:13.436366565" + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test new file mode 100644 index 00000000..8940d32d --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,29 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NFCORE_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + workflow "UTILS_NFCORE_PIPELINE" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Should run without failures") { + + when { + workflow { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap new file mode 100644 index 00000000..d07ce54c --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -0,0 +1,15 @@ +{ + "Should run without failures": { + "content": [ + { + "0": [ + true + ], + "valid_config": [ + true + ] + } + ], + "timestamp": "2024-01-19T11:35:22.538940073" + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config b/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config new file mode 100644 index 00000000..d0a926bf --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml b/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml new file mode 100644 index 00000000..ac8523c9 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml @@ -0,0 +1,2 @@ +subworkflows/utils_nfcore_pipeline: + - subworkflows/nf-core/utils_nfcore_pipeline/** diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/main.nf b/subworkflows/nf-core/utils_nfvalidation_plugin/main.nf new file mode 100644 index 00000000..2585b65d --- /dev/null +++ b/subworkflows/nf-core/utils_nfvalidation_plugin/main.nf @@ -0,0 +1,62 @@ +// +// Subworkflow that uses the nf-validation plugin to render help text and parameter summary +// + +/* +======================================================================================== + IMPORT NF-VALIDATION PLUGIN +======================================================================================== +*/ + +include { paramsHelp } from 'plugin/nf-validation' +include { paramsSummaryLog } from 'plugin/nf-validation' +include { validateParameters } from 'plugin/nf-validation' + +/* +======================================================================================== + SUBWORKFLOW DEFINITION +======================================================================================== +*/ + +workflow UTILS_NFVALIDATION_PLUGIN { + + take: + print_help // boolean: print help + workflow_command // string: default commmand used to run pipeline + pre_help_text // string: string to be printed before help text and summary log + post_help_text // string: string to be printed after help text and summary log + validate_params // boolean: validate parameters + schema_filename // path: JSON schema file, null to use default value + + main: + + log.debug "Using schema file: ${schema_filename}" + + // Default values for strings + pre_help_text = pre_help_text ?: '' + post_help_text = post_help_text ?: '' + workflow_command = workflow_command ?: '' + + // + // Print help message if needed + // + if (print_help) { + log.info pre_help_text + paramsHelp(workflow_command, parameters_schema: schema_filename) + post_help_text + System.exit(0) + } + + // + // Print parameter summary to stdout + // + log.info pre_help_text + paramsSummaryLog(workflow, parameters_schema: schema_filename) + post_help_text + + // + // Validate parameters relative to the parameter JSON schema + // + if (validate_params){ + validateParameters(parameters_schema: schema_filename) + } + + emit: + dummy_emit = true +} diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/meta.yml b/subworkflows/nf-core/utils_nfvalidation_plugin/meta.yml new file mode 100644 index 00000000..3d4a6b04 --- /dev/null +++ b/subworkflows/nf-core/utils_nfvalidation_plugin/meta.yml @@ -0,0 +1,44 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NFVALIDATION_PLUGIN" +description: Use nf-validation to initiate and validate a pipeline +keywords: + - utility + - pipeline + - initialise + - validation +components: [] +input: + - print_help: + type: boolean + description: | + Print help message and exit + - workflow_command: + type: string + description: | + The command to run the workflow e.g. "nextflow run main.nf" + - pre_help_text: + type: string + description: | + Text to print before the help message + - post_help_text: + type: string + description: | + Text to print after the help message + - validate_params: + type: boolean + description: | + Validate the parameters and error if invalid. + - schema_filename: + type: string + description: | + The filename of the schema to validate against. +output: + - dummy_emit: + type: boolean + description: | + Dummy emit to make nf-core subworkflows lint happy +authors: + - "@adamrtalbot" +maintainers: + - "@adamrtalbot" + - "@maxulysse" diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test new file mode 100644 index 00000000..517ee54e --- /dev/null +++ b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test @@ -0,0 +1,200 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NFVALIDATION_PLUGIN" + script "../main.nf" + workflow "UTILS_NFVALIDATION_PLUGIN" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "plugin/nf-validation" + tag "'plugin/nf-validation'" + tag "utils_nfvalidation_plugin" + tag "subworkflows/utils_nfvalidation_plugin" + + test("Should run nothing") { + + when { + + params { + monochrome_logs = true + test_data = '' + } + + workflow { + """ + help = false + workflow_command = null + pre_help_text = null + post_help_text = null + validate_params = false + schema_filename = "$moduleTestDir/nextflow_schema.json" + + input[0] = help + input[1] = workflow_command + input[2] = pre_help_text + input[3] = post_help_text + input[4] = validate_params + input[5] = schema_filename + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should run help") { + + + when { + + params { + monochrome_logs = true + test_data = '' + } + workflow { + """ + help = true + workflow_command = null + pre_help_text = null + post_help_text = null + validate_params = false + schema_filename = "$moduleTestDir/nextflow_schema.json" + + input[0] = help + input[1] = workflow_command + input[2] = pre_help_text + input[3] = post_help_text + input[4] = validate_params + input[5] = schema_filename + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert workflow.exitStatus == 0 }, + { assert workflow.stdout.any { it.contains('Input/output options') } }, + { assert workflow.stdout.any { it.contains('--outdir') } } + ) + } + } + + test("Should run help with command") { + + when { + + params { + monochrome_logs = true + test_data = '' + } + workflow { + """ + help = true + workflow_command = "nextflow run noorg/doesntexist" + pre_help_text = null + post_help_text = null + validate_params = false + schema_filename = "$moduleTestDir/nextflow_schema.json" + + input[0] = help + input[1] = workflow_command + input[2] = pre_help_text + input[3] = post_help_text + input[4] = validate_params + input[5] = schema_filename + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert workflow.exitStatus == 0 }, + { assert workflow.stdout.any { it.contains('nextflow run noorg/doesntexist') } }, + { assert workflow.stdout.any { it.contains('Input/output options') } }, + { assert workflow.stdout.any { it.contains('--outdir') } } + ) + } + } + + test("Should run help with extra text") { + + + when { + + params { + monochrome_logs = true + test_data = '' + } + workflow { + """ + help = true + workflow_command = "nextflow run noorg/doesntexist" + pre_help_text = "pre-help-text" + post_help_text = "post-help-text" + validate_params = false + schema_filename = "$moduleTestDir/nextflow_schema.json" + + input[0] = help + input[1] = workflow_command + input[2] = pre_help_text + input[3] = post_help_text + input[4] = validate_params + input[5] = schema_filename + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert workflow.exitStatus == 0 }, + { assert workflow.stdout.any { it.contains('pre-help-text') } }, + { assert workflow.stdout.any { it.contains('nextflow run noorg/doesntexist') } }, + { assert workflow.stdout.any { it.contains('Input/output options') } }, + { assert workflow.stdout.any { it.contains('--outdir') } }, + { assert workflow.stdout.any { it.contains('post-help-text') } } + ) + } + } + + test("Should validate params") { + + when { + + params { + monochrome_logs = true + test_data = '' + outdir = 1 + } + workflow { + """ + help = false + workflow_command = null + pre_help_text = null + post_help_text = null + validate_params = true + schema_filename = "$moduleTestDir/nextflow_schema.json" + + input[0] = help + input[1] = workflow_command + input[2] = pre_help_text + input[3] = post_help_text + input[4] = validate_params + input[5] = schema_filename + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ ERROR: Validation of pipeline parameters failed!') } } + ) + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/nextflow_schema.json b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/nextflow_schema.json new file mode 100644 index 00000000..7626c1c9 --- /dev/null +++ b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/nextflow_schema.json @@ -0,0 +1,96 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://raw.githubusercontent.com/./master/nextflow_schema.json", + "title": ". pipeline parameters", + "description": "", + "type": "object", + "definitions": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["outdir"], + "properties": { + "validate_params": { + "type": "boolean", + "description": "Validate parameters?", + "default": true, + "hidden": true + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + }, + "test_data_base": { + "type": "string", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/modules", + "description": "Base for test data directory", + "hidden": true + }, + "test_data": { + "type": "string", + "description": "Fake test data param", + "hidden": true + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "help": { + "type": "boolean", + "description": "Display help text.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "logo": { + "type": "boolean", + "default": true, + "description": "Display nf-core logo in console output.", + "fa_icon": "fas fa-image", + "hidden": true + }, + "singularity_pull_docker_container": { + "type": "boolean", + "description": "Pull Singularity container from Docker?", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": ["symlink", "rellink", "link", "copy", "copyNoFollow", "move"], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Use monochrome_logs", + "hidden": true + } + } + } + }, + "allOf": [ + { + "$ref": "#/definitions/input_output_options" + }, + { + "$ref": "#/definitions/generic_options" + } + ] +} diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/tags.yml b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/tags.yml new file mode 100644 index 00000000..60b1cfff --- /dev/null +++ b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/tags.yml @@ -0,0 +1,2 @@ +subworkflows/utils_nfvalidation_plugin: + - subworkflows/nf-core/utils_nfvalidation_plugin/** diff --git a/workflows/phaseimpute.nf b/workflows/phaseimpute.nf index 7b195b2e..195669c1 100644 --- a/workflows/phaseimpute.nf +++ b/workflows/phaseimpute.nf @@ -1,54 +1,15 @@ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - PRINT PARAMS SUMMARY + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { paramsSummaryLog; paramsSummaryMap } from 'plugin/nf-validation' - -def logo = NfcoreTemplate.logo(workflow, params.monochrome_logs) -def citation = '\n' + WorkflowMain.citation(workflow) + '\n' -def summary_params = paramsSummaryMap(workflow) - -// Print parameter summary log to screen -log.info logo + paramsSummaryLog(workflow) + citation - -WorkflowPhaseimpute.initialise(params, log) - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - CONFIG FILES -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -ch_multiqc_config = Channel.fromPath("$projectDir/assets/multiqc_config.yml", checkIfExists: true) -ch_multiqc_custom_config = params.multiqc_config ? Channel.fromPath( params.multiqc_config, checkIfExists: true ) : Channel.empty() -ch_multiqc_logo = params.multiqc_logo ? Channel.fromPath( params.multiqc_logo, checkIfExists: true ) : Channel.empty() -ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT LOCAL MODULES/SUBWORKFLOWS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -// -// SUBWORKFLOW: Consisting of a mix of local and nf-core/modules -// -include { INPUT_CHECK } from '../subworkflows/local/input_check' - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT NF-CORE MODULES/SUBWORKFLOWS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -// -// MODULE: Installed directly from nf-core/modules -// -include { FASTQC } from '../modules/nf-core/fastqc/main' -include { MULTIQC } from '../modules/nf-core/multiqc/main' -include { CUSTOM_DUMPSOFTWAREVERSIONS } from '../modules/nf-core/custom/dumpsoftwareversions/main' +include { FASTQC } from '../modules/nf-core/fastqc/main' +include { MULTIQC } from '../modules/nf-core/multiqc/main' +include { paramsSummaryMap } from 'plugin/nf-validation' +include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_phaseimpute_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -56,50 +17,45 @@ include { CUSTOM_DUMPSOFTWAREVERSIONS } from '../modules/nf-core/custom/dumpsoft ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -// Info required for completion email and summary -def multiqc_report = [] - workflow PHASEIMPUTE { - ch_versions = Channel.empty() + take: + ch_samplesheet // channel: samplesheet read in from --input - // - // SUBWORKFLOW: Read in samplesheet, validate and stage input files - // - INPUT_CHECK ( - file(params.input) - ) - ch_versions = ch_versions.mix(INPUT_CHECK.out.versions) - // TODO: OPTIONAL, you can use nf-validation plugin to create an input channel from the samplesheet with Channel.fromSamplesheet("input") - // See the documentation https://nextflow-io.github.io/nf-validation/samplesheets/fromSamplesheet/ - // ! There is currently no tooling to help you write a sample sheet schema + main: + + ch_versions = Channel.empty() + ch_multiqc_files = Channel.empty() // // MODULE: Run FastQC // FASTQC ( - INPUT_CHECK.out.reads + ch_samplesheet ) + ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.collect{it[1]}) ch_versions = ch_versions.mix(FASTQC.out.versions.first()) - CUSTOM_DUMPSOFTWAREVERSIONS ( - ch_versions.unique().collectFile(name: 'collated_versions.yml') - ) + // + // Collate and save software versions + // + softwareVersionsToYAML(ch_versions) + .collectFile(storeDir: "${params.outdir}/pipeline_info", name: 'nf_core_pipeline_software_mqc_versions.yml', sort: true, newLine: true) + .set { ch_collated_versions } // // MODULE: MultiQC // - workflow_summary = WorkflowPhaseimpute.paramsSummaryMultiqc(workflow, summary_params) - ch_workflow_summary = Channel.value(workflow_summary) - - methods_description = WorkflowPhaseimpute.methodsDescriptionText(workflow, ch_multiqc_custom_methods_description, params) - ch_methods_description = Channel.value(methods_description) - - ch_multiqc_files = Channel.empty() - ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml')) - ch_multiqc_files = ch_multiqc_files.mix(CUSTOM_DUMPSOFTWAREVERSIONS.out.mqc_yml.collect()) - ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.collect{it[1]}.ifEmpty([])) + ch_multiqc_config = Channel.fromPath("$projectDir/assets/multiqc_config.yml", checkIfExists: true) + ch_multiqc_custom_config = params.multiqc_config ? Channel.fromPath(params.multiqc_config, checkIfExists: true) : Channel.empty() + ch_multiqc_logo = params.multiqc_logo ? Channel.fromPath(params.multiqc_logo, checkIfExists: true) : Channel.empty() + summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) + ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) + ch_methods_description = Channel.value(methodsDescriptionText(ch_multiqc_custom_methods_description)) + ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) + ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml', sort: false)) MULTIQC ( ch_multiqc_files.collect(), @@ -107,31 +63,10 @@ workflow PHASEIMPUTE { ch_multiqc_custom_config.toList(), ch_multiqc_logo.toList() ) - multiqc_report = MULTIQC.out.report.toList() -} - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - COMPLETION EMAIL AND SUMMARY -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -workflow.onComplete { - if (params.email || params.email_on_fail) { - NfcoreTemplate.email(workflow, params, summary_params, projectDir, log, multiqc_report) - } - NfcoreTemplate.dump_parameters(workflow, params) - NfcoreTemplate.summary(workflow, params, log) - if (params.hook_url) { - NfcoreTemplate.IM_notification(workflow, params, summary_params, projectDir, log) - } -} -workflow.onError { - if (workflow.errorReport.contains("Process requirement exceeds available memory")) { - println("🛑 Default resources exceed availability 🛑 ") - println("💡 See here on how to configure pipeline: https://nf-co.re/docs/usage/configuration#tuning-workflow-resources 💡") - } + emit: + multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html + versions = ch_versions // channel: [ path(versions.yml) ] } /* From df206fd9ede11d893f597bab5c074e9b266173d9 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Thu, 29 Feb 2024 16:11:43 +0000 Subject: [PATCH 3/3] Template update for nf-core/tools version 2.13.1 --- .devcontainer/devcontainer.json | 10 +---- .github/CONTRIBUTING.md | 14 ++++--- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/awsfulltest.yml | 4 +- .github/workflows/awstest.yml | 4 +- .github/workflows/ci.yml | 2 +- .github/workflows/download_pipeline.yml | 2 +- .github/workflows/linting.yml | 2 +- .github/workflows/release-announcements.yml | 2 +- .gitpod.yml | 6 +-- README.md | 3 +- modules.json | 8 ++-- modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 4 +- .../nf-core/multiqc/tests/main.nf.test.snap | 12 +++--- .../utils_nfcore_phaseimpute_pipeline/main.nf | 10 +++-- .../tests/main.function.nf.test | 2 +- .../tests/main.function.nf.test.snap | 12 +++++- .../tests/main.workflow.nf.test | 20 ++------- .../tests/nextflow.config | 2 +- .../tests/main.function.nf.test.snap | 42 +++++++++++++++---- .../tests/main.workflow.nf.test.snap | 6 ++- .../tests/main.nf.test | 2 +- 23 files changed, 99 insertions(+), 74 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4ecfbfe3..b290e090 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,15 +10,7 @@ "vscode": { // Set *default* container specific settings.json values on container create. "settings": { - "python.defaultInterpreterPath": "/opt/conda/bin/python", - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.formatting.autopep8Path": "/opt/conda/bin/autopep8", - "python.formatting.yapfPath": "/opt/conda/bin/yapf", - "python.linting.flake8Path": "/opt/conda/bin/flake8", - "python.linting.pycodestylePath": "/opt/conda/bin/pycodestyle", - "python.linting.pydocstylePath": "/opt/conda/bin/pydocstyle", - "python.linting.pylintPath": "/opt/conda/bin/pylint" + "python.defaultInterpreterPath": "/opt/conda/bin/python" }, // Add the IDs of extensions you want installed when the container is created. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index bd157885..6866f649 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -9,9 +9,8 @@ Please use the pre-filled template to save time. However, don't be put off by this template - other more general issues and suggestions are welcome! Contributions to the code are even more welcome ;) -:::info -If you need help using or modifying nf-core/phaseimpute then the best place to ask is on the nf-core Slack [#phaseimpute](https://nfcore.slack.com/channels/phaseimpute) channel ([join our Slack here](https://nf-co.re/join/slack)). -::: +> [!NOTE] +> If you need help using or modifying nf-core/phaseimpute then the best place to ask is on the nf-core Slack [#phaseimpute](https://nfcore.slack.com/channels/phaseimpute) channel ([join our Slack here](https://nf-co.re/join/slack)). ## Contribution workflow @@ -27,8 +26,11 @@ If you're not used to this workflow with git, you can start with some [docs from ## Tests -You can optionally test your changes by running the pipeline locally. Then it is recommended to use the `debug` profile to -receive warnings about process selectors and other debug info. Example: `nextflow run . -profile debug,test,docker --outdir `. +You have the option to test your changes locally by running the pipeline. For receiving warnings about process selectors and other `debug` information, it is recommended to use the debug profile. Execute all the tests with the following command: + +```bash +nf-test test --profile debug,test,docker --verbose +``` When you create a pull request with changes, [GitHub Actions](https://github.com/features/actions) will run automatic tests. Typically, pull-requests are only fully reviewed when these tests are passing, though of course we can help out before then. @@ -90,7 +92,7 @@ Once there, use `nf-core schema build` to add to `nextflow_schema.json`. Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/master/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. -The process resources can be passed on to the tool dynamically within the process with the `${task.cpu}` and `${task.memory}` variables in the `script:` block. +The process resources can be passed on to the tool dynamically within the process with the `${task.cpus}` and `${task.memory}` variables in the `script:` block. ### Naming schemes diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0158fbb8..b1e6b755 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,7 +18,7 @@ Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/phas - [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/phaseimpute/tree/master/.github/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/phaseimpute _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. - [ ] Make sure your code lints (`nf-core lint`). -- [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). +- [ ] Ensure the test suite passes (`nf-test test main.nf.test -profile test,docker`). - [ ] Check for unexpected warnings in debug mode (`nextflow run . -profile debug,test,docker --outdir `). - [ ] Usage Documentation in `docs/usage.md` is updated. - [ ] Output Documentation in `docs/output.md` is updated. diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index f1d7dec2..02bfb85c 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Launch workflow via tower - uses: seqeralabs/action-tower-launch@922e5c8d5ac4e918107ec311d2ebbd65e5982b3d # v2 + uses: seqeralabs/action-tower-launch@v2 # TODO nf-core: You can customise AWS full pipeline tests as required # Add full size test data (but still relatively small datasets for few samples) # on the `test_full.config` test runs with only one set of parameters @@ -31,7 +31,7 @@ jobs: } profiles: test_full - - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 + - uses: actions/upload-artifact@v4 with: name: Tower debug log file path: | diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index f6c2af9e..9e6f2bbd 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -12,7 +12,7 @@ jobs: steps: # Launch workflow using Tower CLI tool action - name: Launch workflow via tower - uses: seqeralabs/action-tower-launch@922e5c8d5ac4e918107ec311d2ebbd65e5982b3d # v2 + uses: seqeralabs/action-tower-launch@v2 with: workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} @@ -25,7 +25,7 @@ jobs: } profiles: test - - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 + - uses: actions/upload-artifact@v4 with: name: Tower debug log file path: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cd73aa0..ecc6d0cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Install Nextflow - uses: nf-core/setup-nextflow@b9f764e8ba5c76b712ace14ecbfcef0e40ae2dd8 # v1 + uses: nf-core/setup-nextflow@v1 with: version: "${{ matrix.NXF_VER }}" diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml index f823210d..08622fd5 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Nextflow - uses: nf-core/setup-nextflow@b9f764e8ba5c76b712ace14ecbfcef0e40ae2dd8 # v1 + uses: nf-core/setup-nextflow@v1 - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 with: diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 748b4311..073e1876 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Install Nextflow - uses: nf-core/setup-nextflow@b9f764e8ba5c76b712ace14ecbfcef0e40ae2dd8 # v1 + uses: nf-core/setup-nextflow@v1 - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 with: diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index c3674af2..d468aeaa 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -12,7 +12,7 @@ jobs: - name: get topics and convert to hashtags id: get_topics run: | - curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ' > $GITHUB_OUTPUT + curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ' >> $GITHUB_OUTPUT - uses: rzr/fediverse-action@master with: diff --git a/.gitpod.yml b/.gitpod.yml index 363d5b1d..105a1821 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -10,13 +10,11 @@ tasks: vscode: extensions: # based on nf-core.nf-core-extensionpack - - codezombiech.gitignore # Language support for .gitignore files - # - cssho.vscode-svgviewer # SVG viewer - esbenp.prettier-vscode # Markdown/CommonMark linting and style checking for Visual Studio Code - - eamodio.gitlens # Quickly glimpse into whom, why, and when a line or code block was changed - EditorConfig.EditorConfig # override user/workspace settings with settings found in .editorconfig files - Gruntfuggly.todo-tree # Display TODO and FIXME in a tree view in the activity bar - mechatroner.rainbow-csv # Highlight columns in csv files in different colors - # - nextflow.nextflow # Nextflow syntax highlighting + # - nextflow.nextflow # Nextflow syntax highlighting - oderwat.indent-rainbow # Highlight indentation level - streetsidesoftware.code-spell-checker # Spelling checker for source code + - charliermarsh.ruff # Code linter Ruff diff --git a/README.md b/README.md index bc332f78..edf0acd8 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,13 @@ [![GitHub Actions CI Status](https://github.com/nf-core/phaseimpute/actions/workflows/ci.yml/badge.svg)](https://github.com/nf-core/phaseimpute/actions/workflows/ci.yml) [![GitHub Actions Linting Status](https://github.com/nf-core/phaseimpute/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/phaseimpute/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/phaseimpute/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) +[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) [![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A523.04.0-23aa62.svg)](https://www.nextflow.io/) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) -[![Launch on Nextflow Tower](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Nextflow%20Tower-%234256e7)](https://tower.nf/launch?pipeline=https://github.com/nf-core/phaseimpute) +[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://tower.nf/launch?pipeline=https://github.com/nf-core/phaseimpute) [![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23phaseimpute-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/phaseimpute)[![Follow on Twitter](http://img.shields.io/badge/twitter-%40nf__core-1DA1F2?labelColor=000000&logo=twitter)](https://twitter.com/nf_core)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core) diff --git a/modules.json b/modules.json index 06f9e9e2..da1b3812 100644 --- a/modules.json +++ b/modules.json @@ -12,7 +12,7 @@ }, "multiqc": { "branch": "master", - "git_sha": "ccacf6f5de6df3bc6d73b665c1fd2933d8bbc290", + "git_sha": "b7ebe95761cd389603f9cc0e0dc384c0f663815a", "installed_by": ["modules"] } } @@ -21,17 +21,17 @@ "nf-core": { "utils_nextflow_pipeline": { "branch": "master", - "git_sha": "cd08c91373cd00a73255081340e4914485846ba1", + "git_sha": "5caf7640a9ef1d18d765d55339be751bb0969dfa", "installed_by": ["subworkflows"] }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "262b17ed2aad591039f914951659177e6c39a8d8", + "git_sha": "5caf7640a9ef1d18d765d55339be751bb0969dfa", "installed_by": ["subworkflows"] }, "utils_nfvalidation_plugin": { "branch": "master", - "git_sha": "cd08c91373cd00a73255081340e4914485846ba1", + "git_sha": "5caf7640a9ef1d18d765d55339be751bb0969dfa", "installed_by": ["subworkflows"] } } diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index 2212096a..ca39fb67 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -4,4 +4,4 @@ channels: - bioconda - defaults dependencies: - - bioconda::multiqc=1.20 + - bioconda::multiqc=1.21 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 354f4430..47ac352f 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,8 +3,8 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.20--pyhdfd78af_0' : - 'biocontainers/multiqc:1.20--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.21--pyhdfd78af_0' : + 'biocontainers/multiqc:1.21--pyhdfd78af_0' }" input: path multiqc_files, stageAs: "?/*" diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index c204b488..bfebd802 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -2,14 +2,14 @@ "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,d320d4c37e349c5588e07e7a31cd4186" + "versions.yml:md5,21f35ee29416b9b3073c28733efe4b7d" ] ], "meta": { "nf-test": "0.8.4", "nextflow": "23.10.1" }, - "timestamp": "2024-02-14T09:28:51.744211298" + "timestamp": "2024-02-29T08:48:55.657331" }, "multiqc_stub": { "content": [ @@ -17,25 +17,25 @@ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,d320d4c37e349c5588e07e7a31cd4186" + "versions.yml:md5,21f35ee29416b9b3073c28733efe4b7d" ] ], "meta": { "nf-test": "0.8.4", "nextflow": "23.10.1" }, - "timestamp": "2024-02-14T09:29:28.847433492" + "timestamp": "2024-02-29T08:49:49.071937" }, "multiqc_versions_config": { "content": [ [ - "versions.yml:md5,d320d4c37e349c5588e07e7a31cd4186" + "versions.yml:md5,21f35ee29416b9b3073c28733efe4b7d" ] ], "meta": { "nf-test": "0.8.4", "nextflow": "23.10.1" }, - "timestamp": "2024-02-14T09:29:13.223621555" + "timestamp": "2024-02-29T08:49:25.457567" } } \ No newline at end of file diff --git a/subworkflows/local/utils_nfcore_phaseimpute_pipeline/main.nf b/subworkflows/local/utils_nfcore_phaseimpute_pipeline/main.nf index dc90ad90..41e1bb25 100644 --- a/subworkflows/local/utils_nfcore_phaseimpute_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_phaseimpute_pipeline/main.nf @@ -1,5 +1,5 @@ // -// Subworkflow with functionality specific to the nf-core/pipeline pipeline +// Subworkflow with functionality specific to the nf-core/phaseimpute pipeline // /* @@ -152,7 +152,9 @@ workflow PIPELINE_COMPLETION { // def validateInputParameters() { genomeExistsError() -}// +} + +// // Validate channels from input samplesheet // def validateInputSamplesheet(input) { @@ -190,7 +192,9 @@ def genomeExistsError() { "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" error(error_string) } -}// +} + +// // Generate methods description for MultiQC // def toolCitationText() { diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test index 8ed4310c..68718e4f 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test @@ -51,4 +51,4 @@ nextflow_function { ) } } -} \ No newline at end of file +} diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap index db2030f8..e3f0baf4 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -3,10 +3,18 @@ "content": [ "v9.9.9" ], - "timestamp": "2024-01-19T11:32:36.031083" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:05.308243" }, "Test Function checkCondaChannels": { "content": null, - "timestamp": "2024-01-19T11:32:50.456" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:12.425833" } } \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test index f7c54bc6..ca964ce8 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test @@ -11,9 +11,6 @@ nextflow_workflow { test("Should run no inputs") { when { - params { - outdir = "tests/results" - } workflow { """ print_version = false @@ -39,9 +36,6 @@ nextflow_workflow { test("Should print version") { when { - params { - outdir = "tests/results" - } workflow { """ print_version = true @@ -68,19 +62,16 @@ nextflow_workflow { test("Should dump params") { when { - params { - outdir = "$outputDir" - } workflow { """ print_version = false dump_parameters = true - outdir = params.outdir + outdir = 'results' check_conda_channels = false input[0] = false input[1] = true - input[2] = params.outdir + input[2] = outdir input[3] = false """ } @@ -96,19 +87,16 @@ nextflow_workflow { test("Should not create params JSON if no output directory") { when { - params { - outdir = "$outputDir" - } workflow { """ print_version = false dump_parameters = true - outdir = params.outdir + outdir = null check_conda_channels = false input[0] = false input[1] = true - input[2] = null + input[2] = outdir input[3] = false """ } diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config b/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config index 53574ffe..d0a926bf 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config @@ -6,4 +6,4 @@ manifest { nextflowVersion = '!>=23.04.0' version = '9.9.9' doi = 'https://doi.org/10.5281/zenodo.5070524' -} \ No newline at end of file +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap index 10f948e6..1037232c 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -1,25 +1,41 @@ { "Test Function checkProfileProvided": { "content": null, - "timestamp": "2024-02-09T15:43:55.145717" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:03.360873" }, "Test Function checkConfigProvided": { "content": [ true ], - "timestamp": "2024-01-19T11:34:13.548431224" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:59.729647" }, "Test Function nfCoreLogo": { "content": [ "\n\n-\u001b[2m----------------------------------------------------\u001b[0m-\n \u001b[0;32m,--.\u001b[0;30m/\u001b[0;32m,-.\u001b[0m\n\u001b[0;34m ___ __ __ __ ___ \u001b[0;32m/,-._.--~'\u001b[0m\n\u001b[0;34m |\\ | |__ __ / ` / \\ |__) |__ \u001b[0;33m} {\u001b[0m\n\u001b[0;34m | \\| | \\__, \\__/ | \\ |___ \u001b[0;32m\\`-._,-`-,\u001b[0m\n \u001b[0;32m`._,._,'\u001b[0m\n\u001b[0;35m nextflow_workflow v9.9.9\u001b[0m\n-\u001b[2m----------------------------------------------------\u001b[0m-\n" ], - "timestamp": "2024-01-19T11:34:38.840454873" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:10.562934" }, "Test Function workflowCitation": { "content": [ "If you use nextflow_workflow for your analysis please cite:\n\n* The pipeline\n https://doi.org/10.5281/zenodo.5070524\n\n* The nf-core framework\n https://doi.org/10.1038/s41587-020-0439-x\n\n* Software dependencies\n https://github.com/nextflow_workflow/blob/master/CITATIONS.md" ], - "timestamp": "2024-01-19T11:34:22.24352016" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:07.019761" }, "Test Function without logColours": { "content": [ @@ -73,13 +89,21 @@ "biwhite": "" } ], - "timestamp": "2024-01-19T11:35:04.418416984" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:17.969323" }, "Test Function dashedLine": { "content": [ "-\u001b[2m----------------------------------------------------\u001b[0m-" ], - "timestamp": "2024-01-19T11:34:55.420000755" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:14.366181" }, "Test Function with logColours": { "content": [ @@ -133,6 +157,10 @@ "biwhite": "\u001b[1;97m" } ], - "timestamp": "2024-01-19T11:35:13.436366565" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:21.714424" } } \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap index d07ce54c..859d1030 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -10,6 +10,10 @@ ] } ], - "timestamp": "2024-01-19T11:35:22.538940073" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:25.726491" } } \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test index 517ee54e..5784a33f 100644 --- a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test +++ b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test @@ -197,4 +197,4 @@ nextflow_workflow { ) } } -} \ No newline at end of file +}
    Process Name \\", - " \\ Software Version
    CUSTOM_DUMPSOFTWAREVERSIONSpython3.11.7
    yaml5.4.1
    TOOL1tool10.11.9
    TOOL2tool21.9
    WorkflowNextflow
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    File typeConventional base calls
    \n" + for (param in group_params.keySet()) { + summary_section += "
    $param
    ${group_params.get(param) ?: 'N/A'}
    \n" + } + summary_section += "