From 3f4c494b5385f1d13e5a063e50e307f877733117 Mon Sep 17 00:00:00 2001 From: Danilo Di Leo Date: Wed, 5 Jul 2023 11:30:52 +0200 Subject: [PATCH 1/6] Template update for nf-core/tools version 2.9 --- .devcontainer/devcontainer.json | 27 + .editorconfig | 2 +- .gitattributes | 1 + .github/CONTRIBUTING.md | 15 + .github/ISSUE_TEMPLATE/bug_report.yml | 4 +- .github/PULL_REQUEST_TEMPLATE.md | 3 +- .github/workflows/awsfulltest.yml | 11 +- .github/workflows/awstest.yml | 10 +- .github/workflows/branch.yml | 2 +- .github/workflows/ci.yml | 8 +- .github/workflows/clean-up.yml | 24 + .github/workflows/fix-linting.yml | 6 +- .github/workflows/linting.yml | 20 +- .github/workflows/linting_comment.yml | 2 +- .gitpod.yml | 5 + .pre-commit-config.yaml | 5 + .prettierignore | 2 + CITATION.cff | 56 -- CITATIONS.md | 6 + LICENSE | 2 +- README.md | 78 +-- assets/methods_description_template.yml | 12 +- assets/multiqc_config.yml | 4 +- assets/nf-core-metatdenovo_logo_light.png | Bin 10884 -> 83656 bytes assets/slackreport.json | 34 ++ bin/check_samplesheet.py | 3 - conf/base.config | 2 +- conf/igenomes.config | 8 + docs/usage.md | 138 ++--- lib/NfcoreSchema.groovy | 529 ------------------ lib/NfcoreTemplate.groovy | 43 +- lib/WorkflowMain.groovy | 48 +- lib/WorkflowMetatdenovo.groovy | 57 +- main.nf | 17 +- modules.json | 9 +- modules/local/samplesheet_check.nf | 8 +- .../custom/dumpsoftwareversions/main.nf | 6 +- .../custom/dumpsoftwareversions/meta.yml | 2 + .../templates/dumpsoftwareversions.py | 99 ++-- modules/nf-core/fastqc/main.nf | 40 +- modules/nf-core/multiqc/main.nf | 6 +- modules/nf-core/multiqc/meta.yml | 3 +- nextflow.config | 88 ++- nextflow_schema.json | 44 +- tower.yml | 5 + workflows/metatdenovo.nf | 34 +- 46 files changed, 589 insertions(+), 939 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/workflows/clean-up.yml create mode 100644 .pre-commit-config.yaml delete mode 100644 CITATION.cff create mode 100644 assets/slackreport.json delete mode 100755 lib/NfcoreSchema.groovy mode change 100644 => 100755 modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py create mode 100644 tower.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..ea27a584 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,27 @@ +{ + "name": "nfcore", + "image": "nfcore/gitpod:latest", + "remoteUser": "gitpod", + + // 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 index b78de6e6..b6b31907 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,7 @@ trim_trailing_whitespace = true indent_size = 4 indent_style = space -[*.{md,yml,yaml,html,css,scss,js,cff}] +[*.{md,yml,yaml,html,css,scss,js}] indent_size = 2 # These files are edited and tested upstream in nf-core/modules diff --git a/.gitattributes b/.gitattributes index 050bb120..7a2dabc2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +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/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a371c43a..bbe0d3e8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -101,3 +101,18 @@ If you are using a new feature from core Nextflow, you may bump the minimum requ ### 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/metatdenovo/codespaces) +- Tools installed + - nf-core + - Nextflow + +Devcontainer specs: + +- [DevContainer config](.devcontainer/devcontainer.json) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index cfce6da9..8c055936 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -42,9 +42,9 @@ body: attributes: label: System information description: | - * Nextflow version _(eg. 21.10.3)_ + * 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 or Charliecloud)_ + * Container engine: _(e.g. Docker, Singularity, Conda, Podman, Shifter, Charliecloud, or Apptainer)_ * OS _(eg. CentOS Linux, macOS, Linux Mint)_ * Version of nf-core/metatdenovo _(eg. 1.1, 1.5, 1.8.2)_ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6d7ad86f..2b117051 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,7 +15,8 @@ Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/meta - [ ] 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 you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/metatdenovo/tree/master/.github/CONTRIBUTING.md)- [ ] If necessary, also make a PR on the nf-core/metatdenovo _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. +- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/metatdenovo/tree/master/.github/CONTRIBUTING.md) +- [ ] If necessary, also make a PR on the nf-core/metatdenovo _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 `). - [ ] Usage Documentation in `docs/usage.md` is updated. diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 0a197b22..9b690aaa 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: nf-core/tower-action@v3 + 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 @@ -22,13 +22,18 @@ jobs: 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/metatdenovo/work-${{ github.sha }} parameters: | { + "hook_url": "${{ secrets.MEGATESTS_ALERTS_SLACK_HOOK_URL }}", "outdir": "s3://${{ secrets.AWS_S3_BUCKET }}/metatdenovo/results-${{ github.sha }}" } - profiles: test_full,aws_tower + profiles: test_full + - uses: actions/upload-artifact@v3 with: name: Tower debug log file - path: tower_action_*.log + path: | + tower_action_*.log + tower_action_*.json diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index a1bf42c4..5d1db42f 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -12,18 +12,22 @@ jobs: steps: # Launch workflow using Tower CLI tool action - name: Launch workflow via tower - uses: nf-core/tower-action@v3 + 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/metatdenovo/work-${{ github.sha }} parameters: | { "outdir": "s3://${{ secrets.AWS_S3_BUCKET }}/metatdenovo/results-test-${{ github.sha }}" } - profiles: test,aws_tower + profiles: test + - uses: actions/upload-artifact@v3 with: name: Tower debug log file - path: tower_action_*.log + path: | + tower_action_*.log + tower_action_*.json diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 4ffe4e9f..808a92b9 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -13,7 +13,7 @@ jobs: - name: Check PRs if: github.repository == 'nf-core/metatdenovo' run: | - { [[ ${{github.event.pull_request.head.repo.full_name }} == nf-core/metatdenovo ]] && [[ $GITHUB_HEAD_REF = "dev" ]]; } || [[ $GITHUB_HEAD_REF == "patch" ]] + { [[ ${{github.event.pull_request.head.repo.full_name }} == nf-core/metatdenovo ]] && [[ $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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 779b80df..ee20dd57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,10 @@ on: env: NXF_ANSI_LOG: false +concurrency: + group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" + cancel-in-progress: true + jobs: test: name: Run pipeline with test data @@ -20,11 +24,11 @@ jobs: strategy: matrix: NXF_VER: - - "21.10.3" + - "23.04.0" - "latest-everything" steps: - name: Check out pipeline code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Nextflow uses: nf-core/setup-nextflow@v1 diff --git a/.github/workflows/clean-up.yml b/.github/workflows/clean-up.yml new file mode 100644 index 00000000..694e90ec --- /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@v7 + 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/fix-linting.yml b/.github/workflows/fix-linting.yml index 7a0c8332..93b68ab0 100644 --- a/.github/workflows/fix-linting.yml +++ b/.github/workflows/fix-linting.yml @@ -24,7 +24,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 - name: Install Prettier run: npm install -g prettier @prettier/plugin-php @@ -34,9 +34,9 @@ jobs: id: prettier_status run: | if prettier --check ${GITHUB_WORKSPACE}; then - echo "::set-output name=result::pass" + echo "result=pass" >> $GITHUB_OUTPUT else - echo "::set-output name=result::fail" + echo "result=fail" >> $GITHUB_OUTPUT fi - name: Run 'prettier --write' diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 8a5ce69b..888cb4bc 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -4,6 +4,8 @@ name: nf-core linting # that the code meets the nf-core guidelines. on: push: + branches: + - dev pull_request: release: types: [published] @@ -12,9 +14,9 @@ jobs: EditorConfig: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 - name: Install editorconfig-checker run: npm install -g editorconfig-checker @@ -25,9 +27,9 @@ jobs: Prettier: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 - name: Install Prettier run: npm install -g prettier @@ -38,7 +40,7 @@ jobs: PythonBlack: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Check code lints with Black uses: psf/black@stable @@ -69,14 +71,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Nextflow uses: nf-core/setup-nextflow@v1 - - uses: actions/setup-python@v3 + - uses: actions/setup-python@v4 with: - python-version: "3.7" + python-version: "3.8" architecture: "x64" - name: Install dependencies @@ -97,7 +99,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 04758f61..0bbcd30f 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -18,7 +18,7 @@ jobs: - name: Get PR number id: pr_number - run: echo "::set-output name=pr_number::$(cat linting-logs/PR_number.txt)" + run: echo "pr_number=$(cat linting-logs/PR_number.txt)" >> $GITHUB_OUTPUT - name: Post PR comment uses: marocchino/sticky-pull-request-comment@v2 diff --git a/.gitpod.yml b/.gitpod.yml index 85d95ecc..25488dcc 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,4 +1,9 @@ image: nfcore/gitpod:latest +tasks: + - name: Update Nextflow and setup pre-commit + command: | + pre-commit install --install-hooks + nextflow self-update vscode: extensions: # based on nf-core.nf-core-extensionpack diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..0c31cdb9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v2.7.1" + hooks: + - id: prettier diff --git a/.prettierignore b/.prettierignore index eb74a574..437d763d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,6 @@ email_template.html adaptivecard.json +slackreport.json .nextflow* work/ data/ @@ -8,3 +9,4 @@ results/ testing/ testing* *.pyc +bin/ diff --git a/CITATION.cff b/CITATION.cff deleted file mode 100644 index 017666c0..00000000 --- a/CITATION.cff +++ /dev/null @@ -1,56 +0,0 @@ -cff-version: 1.2.0 -message: "If you use `nf-core tools` in your work, please cite the `nf-core` publication" -authors: - - family-names: Ewels - given-names: Philip - - family-names: Peltzer - given-names: Alexander - - family-names: Fillinger - given-names: Sven - - family-names: Patel - given-names: Harshil - - family-names: Alneberg - given-names: Johannes - - family-names: Wilm - given-names: Andreas - - family-names: Garcia - given-names: Maxime Ulysse - - family-names: Di Tommaso - given-names: Paolo - - family-names: Nahnsen - given-names: Sven -title: "The nf-core framework for community-curated bioinformatics pipelines." -version: 2.4.1 -doi: 10.1038/s41587-020-0439-x -date-released: 2022-05-16 -url: https://github.com/nf-core/tools -prefered-citation: - type: article - authors: - - family-names: Ewels - given-names: Philip - - family-names: Peltzer - given-names: Alexander - - family-names: Fillinger - given-names: Sven - - family-names: Patel - given-names: Harshil - - family-names: Alneberg - given-names: Johannes - - family-names: Wilm - given-names: Andreas - - family-names: Garcia - given-names: Maxime Ulysse - - family-names: Di Tommaso - given-names: Paolo - - family-names: Nahnsen - given-names: Sven - doi: 10.1038/s41587-020-0439-x - journal: nature biotechnology - start: 276 - end: 278 - title: "The nf-core framework for community-curated bioinformatics pipelines." - issue: 3 - volume: 38 - year: 2020 - url: https://dx.doi.org/10.1038/s41587-020-0439-x diff --git a/CITATIONS.md b/CITATIONS.md index 771862fa..71ed902f 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -12,7 +12,10 @@ - [FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/) + > Andrews, S. (2010). FastQC: A Quality Control Tool for High Throughput Sequence Data [Online]. Available online https://www.bioinformatics.babraham.ac.uk/projects/fastqc/. + - [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 @@ -31,5 +34,8 @@ - [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/LICENSE b/LICENSE index d55ce4bc..16b993a7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) Daniel Lundin +Copyright (c) Danilo Di Leo, Emelie Nilsson & Daniel Lundin 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 53bcc116..f1ddc15f 100644 --- a/README.md +++ b/README.md @@ -2,67 +2,81 @@ [![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/metatdenovo/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%A521.10.3-23aa62.svg)](https://www.nextflow.io/) +[![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/metatdenovo) -[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23metatdenovo-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/metatdenovo)[![Follow on Twitter](http://img.shields.io/badge/twitter-%40nf__core-1DA1F2?labelColor=000000&logo=twitter)](https://twitter.com/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) +[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23metatdenovo-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/metatdenovo)[![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) ## Introduction - +**nf-core/metatdenovo** is a bioinformatics pipeline that ... -**nf-core/metatdenovo** is a bioinformatics best-practice analysis pipeline for Assembly and annotation of metatranscriptomic data, both prokaryotic and eukaryotic. - -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 uses Docker/Singularity containers making installation trivial and results highly reproducible. The [Nextflow DSL2](https://www.nextflow.io/docs/latest/dsl2.html) implementation of this pipeline uses one container per process which makes it much easier to maintain and update software dependencies. Where possible, these processes have been submitted to and installed from [nf-core/modules](https://github.com/nf-core/modules) in order to make them available to all nf-core pipelines, and to everyone within the Nextflow community! - - - -On release, automated continuous integration tests run the pipeline on a full-sized dataset on the AWS cloud infrastructure. This ensures that the pipeline runs on AWS, has sensible resource allocation defaults set to run on real-world datasets, and permits the persistent storage of results to benchmark between pipeline releases and other analysis sources.The results obtained from the full-sized test can be viewed on the [nf-core website](https://nf-co.re/metatdenovo/results). - -## Pipeline summary + + 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 + +> **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. + + - Note that some form of configuration will be needed so that Nextflow knows how to fetch the required software. This is usually done in the form of a config profile (`YOURPROFILE` in the example command above). You can chain multiple config profiles in a comma-separated string. +Now, you can run the pipeline using: - > - The pipeline comes with config profiles called `docker`, `singularity`, `podman`, `shifter`, `charliecloud` and `conda` which instruct the pipeline to use the named tool for software management. For example, `-profile test,docker`. - > - Please check [nf-core/configs](https://github.com/nf-core/configs#documentation) to see if a custom config file to run nf-core pipelines already exists for your Institute. If so, you can simply use `-profile ` in your command. This will enable either `docker` or `singularity` and set the appropriate execution settings for your local compute environment. - > - If you are using `singularity`, please use the [`nf-core download`](https://nf-co.re/tools/#downloading-pipelines-for-offline-use) command to download images first, before running the pipeline. Setting the [`NXF_SINGULARITY_CACHEDIR` or `singularity.cacheDir`](https://www.nextflow.io/docs/latest/singularity.html?#singularity-docker-hub) Nextflow options enables you to store and re-use the images from a central location for future pipeline runs. - > - If you are using `conda`, it is highly recommended to use the [`NXF_CONDA_CACHEDIR` or `conda.cacheDir`](https://www.nextflow.io/docs/latest/conda.html) settings to store the environments in a central location for future pipeline runs. + -4. Start running your own analysis! +```bash +nextflow run nf-core/metatdenovo \ + -profile \ + --input samplesheet.csv \ + --outdir +``` - +> **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). - ```bash - nextflow run nf-core/metatdenovo --input samplesheet.csv --outdir --genome GRCh37 -profile - ``` +For more details and further functionality, please refer to the [usage documentation](https://nf-co.re/metatdenovo/usage) and the [parameter documentation](https://nf-co.re/metatdenovo/parameters). -## Documentation +## Pipeline output -The nf-core/metatdenovo pipeline comes with documentation about the pipeline [usage](https://nf-co.re/metatdenovo/usage), [parameters](https://nf-co.re/metatdenovo/parameters) and [output](https://nf-co.re/metatdenovo/output). +To see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/metatdenovo/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/metatdenovo/output). ## Credits -nf-core/metatdenovo was originally written by Daniel Lundin. +nf-core/metatdenovo was originally written by Danilo Di Leo, Emelie Nilsson & Daniel Lundin. We thank the following people for their extensive assistance in the development of this pipeline: diff --git a/assets/methods_description_template.yml b/assets/methods_description_template.yml index 3177466d..ddf47d99 100644 --- a/assets/methods_description_template.yml +++ b/assets/methods_description_template.yml @@ -3,17 +3,21 @@ description: "Suggested text and references to use when describing pipeline usag section_name: "nf-core/metatdenovo Methods Description" section_href: "https://github.com/nf-core/metatdenovo" plot_type: "html" -## TODO nf-core: Update the HTML below to your prefered methods description, e.g. add publication citation for this pipeline +## 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/metatdenovo v${workflow.manifest.version} ${doi_text} of the nf-core collection of workflows (Ewels et al., 2020).

+

Data was processed using nf-core/metatdenovo 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. https://doi.org/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. https://doi.org/10.1038/s41587-020-0439-x
  • +
  • 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:
diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml index 9cbe1aeb..568ad2a9 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -1,7 +1,7 @@ report_comment: > - This report has been generated by the nf-core/metatdenovo + This report has been generated by the nf-core/metatdenovo analysis pipeline. For information about how to interpret these results, please see the - documentation. + documentation. report_section_order: "nf-core-metatdenovo-methods-description": order: -1000 diff --git a/assets/nf-core-metatdenovo_logo_light.png b/assets/nf-core-metatdenovo_logo_light.png index 4c386b1db7965eb782fdebf431913a9cab9faa66..2b7ea51a6f7430f48395b06b7e32156aa5735bd2 100644 GIT binary patch literal 83656 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ⅆTFb6;HJ z@$JE4RMp00e*7_DFKoe|lp^=_a?3VWPQdM`U;-RDkYon}dn0P0szk3_)oErrx5F#l zQr{8{^PT}bBTbx@slws|no;dCnqL>}aNn0{3x-(+o?`elJ>rnrtVm2{)beqg2w1U5 z5sg+6T=CyUbwq*{rj8o~`44MpSEk+pTL$1lFFG7H*o&7(k>CTr>g*Z(7fQ7E)pr}8 z8oEN1>t|7YhwAMq&uP4}3LZZ4tR ze+#GH%gnUe%Tul$Gv3*}YNhLMWtdlEr!U1cRx;N7K6_(#1DtKRFMsRQ$_NDaUoa8p z=0CLHK;8}RMN4nDKu&7F&a39+;oZa-9WqWdnszHYd<<~zD3gUP{J8Tg*Ra`n7>tGd zSnpzZ-x0YwR7?u93jDsgG4px2^(bp5R`>muU-=Pyz1;A6>(B=5moSCT(l_}Yh!e-= zPlmpL@*J7YyF79d7S>GCuUqFFSi-xkM1z`94SJo8e9>R-g5`6L&d~SzINNLSl>OWz zHIN_nau34>SJV9Z+?mo2;!8ThRD{%4>jq?H8rj3T{pAy|ml7nuDNdOf>+Yn}bz?rV zn#Mr}>K^||Ungq&A#6$0@F{Zmr%{C3m`lh6vO(n3>rCYFw}Lq} z+_(=845(29r?^$^xIb-o(NRyLbjl6~uc1~3+{^i+zc9l++Mm~R9*A-Iq}zYEKm6WT z|D^!I_umajjQMIY17u~VdARcyNEp=Zb2Yy+eU=e9Q1y0~CpM1)hYe2WgpaUKx+vl# z>1VcW#^pp0ijT^bR{woog>5hMr+4(@sN99Rl^-k-Un4Y!?`A5ufa<{qU;CRe$1UYB zqZ$U0{HX>#Zu;<+F}~IHta=~`#d9)Ccq>QxBW`cwH^j8Su?%lW9fv7UxJ$XKyFbGX zaKgC@-yvM=y-@ZZ^ZH3I{-XoFd0rPY_N>>LB;?S6b0p(3cVq`V^W7cyNL$y3_gQ-! z13SI{y|Z=o!qH!zs;)e$EUFi5Eb8yKg@7oXYw#hCtkVM`+Yc555NcRdp8@ z$(?v@Mm_-HVyFUm=K6rAae0j&qZB}4hTPDnm(H$M4{IW)#O%Fy zRz*ccWM<)KE*LxAhU02*O-CbXCo83i97Lm}$;VHJbHwFyK4rTnlTC1i(P4eW$Mm!& zjx(O8r9V1aw!J4I{7H^DTi%DCwzK^3bU(+-K^T+4?w1&hTozzcfGHx@bmDE+GlC$2 z^}x->qqqA|ysW{q(qsVwb41plqV#0Qvy*qe{fY`xu;|DFL z;C~&{=78uqQZ}Td#pWU)GTy`xaOvP{_nq0x14nNwn|%mTg7Gsn&r2{}rB8@3Mp^Tv z42j{++C|%VCl0YH)&Ps#y5it@8~c(O>r9`|t+w4N{lvO4)y};lTNj&4L6-Dv!YRvj-03f#cE`X53sA~lMW8D`M_9$H*v_Mz!*STE zXPeBQ&XyNHvu|xN_cuO^6`oBDS5&T5JKC6`RlafD(V;2vWMHIkcr+Jd37sUIEI2lG zw76@cbC@t3F>ARkjnN`Hl%$$21IK7!gZqP0)Vmr_{FTul@-aLOb;exMRzqcMzJO-1 z5u7%vu^MV~xN#)D4MoK5s0eqg3~ngYEg>=Y>>EbA2&AN~<@y)~p<; z_Cs3X7lBH<)UoK_d2~6*$(QA9LIB6T1Wl`x(u{tJ*c9WXb{89wvR0+H-8JkFeokbI z^d>AG$0+yC+hs#lge^4(y3ES<&KvyrJ_0Vnnt^uf{7@VT2fOL`eNTF4XFMZjPm~vX z$Wo=rN66=HFRm&`M9kJ=Uh4-wiLyKSeF8c{8}v8@(1(4TvV|69i?NlZ zq(PP;>oC?eOy^7u3w-0#DA2}UM*&b z$T;yi`Iv$i0BS){{x(ct{_`-$&j_bl!blX_U0a$OO<-}zu<7jvIXs1L_DN3&bN<=r zZ60W$_ES3GzI&L{QsFoQB^hay!J1dHgmNzwKO6?_!rG<#!wsWx5*u9_Ol(Y89QVd7 zKs>%8;$b{bnm8vzKXxxCVJ3IR0nw zsJNGSp;XzJ?Ivq4B(*V+!aWI&=5rM*y$+d)}6pMuEMUL!zJHbaDbC-W7haI|UHmZ3)46 z$tR%@498twcZ^d===2Y62Z6DMtlGn%Q(&>cB)?nFx2_-6hq_VKs#hntyMixa8VxAEk$v|FrG&05cy-y8b5GDQraZ zc{-AfDD$)~Rv&&Ealbbw{n^t~U>H+pv zCghwVDBDwIU0N^?14$8DbLK60TB?l(9h=U=3O7EkmU{Ie6R0LiF+-3RhRS@`8>DD% z8NY+O0!%32qIO`8C-!LfHl|RUni4g&!{RbNb}b^z(((-$Gk;w4dZnBO&0wL&z|oqv$Yi zJ$P)`JoA?`8ztz?^F3TR#5(+SDI-d6v;zVMgOKEO_I5YwCq-+wAaJD|*jo?7 zW#~8c7Wol+tcti=_*Ro*5pu7Un3%Iv^_4mX!$w>c`yI=8x z=Z*>@X{Juu`^c+_+REjH-$xmz-4x0l@OyRpJTS)NiES=U;k+VHthM~Ush|XV$ zV1woy%}eNP-4u|6PuLqM?_;eel&|`Tcms;MErEHZ6B`yT@^5QH!XL<8$f9tKb4?hA zy5gxsq)P&wm(?AY->{+XK+Mnr*VvkqrrX2H7PUHX*ykm*c_fzn^nMVJWFR=YqeMk z!5wiE2!3-ME=xMaFfqfx!ldG#Z3(|FMNN@6T(R2yYS&GpjJ$tb12LEZqBI+a0D z`*r5->27y{oD%k^_Pxrlj^K6Xj_Bu|oKRQTkdPrWBp5!m`SF50d+U06Njn}%{bqI% z*M>YUkfe$)hX#2N5%JH19Co9*SLWkrVJSebkz^YA{C-3#)(Hm?RtJANVDFjM2ySGJ z;v!JV*^=RuxqrXd;K;!!uL?fnbs#qITgscZ;vNl~4(rtZbYr7VzYhBXc7t-=Nq_gN zZQt`W6IMJ7peKQ3299Vwu)!R%;$1XBZI61_6XWF7p^6~(FhrDBz6>Dj!YCyht5d!d zIIL@z5Xpeg3j*8C+rzB&%ZwXDvbZ)lFZLmL}mJASZSadMca{TXD(=ge4=` zgBr)VYDRmsv=6ZQSH%-qM9u)xJ75QzPK8SI7PVK{=tn2uVcKW^zORiF%hM2D`j-As zc`=gmGq+Z#+lRz26nTp)L~T%We@%8m|1`E^WQZlnia9(akh@AGM1x*pO&Y5cOc zUhT`63D}LS2+z|Hs`6ET!Zr&uqqsmOV>lmD`4YGua&+)SsVRSlLh9lRDfu?(K5Z=P z6dSd^?AK!ddC+<&nC^rsh^)>v3aFZzy2-D~C2Fuhh&N?J@Lx$nX_Xb$yQIbH#N(^AXzP>1(xLZi43N z`>h(aEbBFR z#JH0HG9UB4g>+4Lg4~g}xKp?YIEs?eh0GJHC*XTJZ*qt96Qq!Jw!FjPNu%{%M))SN z8G+%wB4a;lP4yeI*m~0ALwb7U`XU^9JAF9z6fY%3c)oBoc&eW_V!$`bO=_+4TqM4Y zDDIUVrM0f>8@-;`OW#=ic4KXb+&Mq18D)AimQTsrB|A&w*zYa5+(S-#yc{E^DS1Lj zUzfv#BZ#ak`lKS|lJd=p2H8hsw^W9&g?;?x*~%-Vk`deDLChOl(rhmd7Jlb z;%&}Ak>7hPeO+MF2w3C#!Vv_1B=0}7a}6;&RoT<)_8=Kdk7C0QpXYXeQ(h-_X~ldi6tI`6?*+1_sQw<-5&lIGh4H# zUYanS1yfT>_SJY~3uG9)*WdYNB0l=`_0w48fIi}GLta{M*EID^rW?A+e9MA6dn?HX z>Jbvs=XSiE1Gxm@-34Z4eZ!^97cF;SnZD!e*eIJ{_OJm9z%RkHgd^?`m+;Q>BN6z- zoYR&6ax!jLxbD{z9vFBrh2SZxrtC3d%Agh#DfLUq2GL-rGtHZ5=OpQ*B%-9Jm%-DU zWVkkiPad5amw1N_jQc#uuX;~g{vujMRmF~u^xe7O=^igFLUz0&nLD2Ql{q?MBT?2= z&DOh&QAztP6#3)Wt~}zV61G9oji4)}DK>>`w=vOlYXKaDfj>50<_7q9Ib(L>h6b^ zXKDFMHPKpUnWM{|mKB+ozR~2X`HI=Of)bLWDtTXrCaSl6(ut-fZYQMY47E<@SH@^O zczjA>nmhvTFKoq%6&Bw+g{(0Jo zJ#|7zx4|}AYtRz8lc4-=UE0e3yi~r9S73CD!3UX-mEw74WyB!X+T^(;A}smmqRS?l zf%`M%8V;Sjw<_&56cH`s%S2LZ_1mRAe4S&tT?EP0N!q~&{VPe*w6`Bs@;)2%4JbOK zGK}SwtOJ^jy>%rd>+63Xjm;Y{^xNb8!l!4-zyLMno^mVbT|R>&<}$ZDq+T^fFhvq( z_Qg9=In#@@c3**@IYH6a8!ghJ2cw1iLx9_3@2*eWQaC9|!LQ564~aQoY(ZthPPJUUGTyblG5q z)Deu`T`;#>)%vlK35GX}tY7|yoHbY>y*6KQvxXDe7^E)r{KbaMi z{0^Y?O(O!9kHLkavBvN!lemxRvo`_s?oZq zG{VzQZ0eL?w-4Qw#xh}6xb_BK!&_L0o%q$|ey&+yXU zrDJ+32C){eJ;-0XQ%Z;#ts83$E$kP6gIg`v?6uBIPYK$~8s6htN>x{<{;u|sl-}s& zWza>0=tD7r`h}bxJLS}HXXvR0=A74+Qc(8vu)PRtLZFl!6ip1PgC4uh3pGSOF74ok z+ZBbEFw9zzvcBV1JmV1-O&8c(k4|KYue`qc@qZgB6aGN==&3XY z^2hr)L(ffDvxmWgics}hiCGB-9htIIoW;?H4^dR)#%YP%J*O>mJq$XDHj?*^HH@l= z68O^XuWVGM2MqLjFP6#ncFPV|ja=SIz_mY%Rk8#5Om)t?80D#WqFdirPasS(Zx%b{ z^n=JvpWW4p&aM0}_^E{EMdk-PWI$JUGsHGp&K1}}e`XiUnV#C6Se}NY6 zac$O(Azp+PH*+wGa`^Ubz3$V9z9j$K7162yx=(gZgg++SpBc%GAMZ%+WNoAw=n7JE zHn1xUDEeORzlYFKUN59wK&XZyN=ikx2A$%w;7ojDgRi@Q?w zT0IOtdi1L%?9Uw1=qFLXV7GHKolbstIusD>HYNORe0_te<_`(sI4(0NY4%Bf{(0&g zL1WAxv?R*pm_Uz}_JQ)_Nd`MpZGu{`nyGa!v6^?W9OiE5>@Dfxdy!%cLDLCszn)V0 zpP$bGg-`;5x7blvt4Lm2Q`~lfReOPk05Kh7?s6ep{%wS;s}IBHQuSK}ABp0&Jes*sHo9(Llsw@4=1z7=^sw6Hhb`-ei@Az5?zj5vet561 zc6gq%bL`hyT~%q}UsjRhWk@j^K)M;I35P-%Psrw%E*n)3rKQT*U#@2ZaxdlhCsYZ1 zCw>iuCWl?8f9#Cq$M zb5J!iL-*(3R_VI8C4Oigo=ary-qU^&3WKvX+j9neBx|nKrtgflQ4P6FlXaOHs!Af+ zNLu(iX6yBxOHbDBGx}@8Mv*k4!#UVnZK~+7-m7o}sdBoKmC-F7ayjNDGAmAKQOT%- z`t!G&<^#Xom3D_XVLwEHyL!V4SMTQ}6bmx?Wxo~cH>$3JX$ke&swizT4zg=<-sP2P zNm9g=$isuy)ZMMCP3c`R(m#*3OS>Vp3#rJ^qTCFFbY4q#0CNyFi%SrFiA5cHTaq4SN}uFO0W_oaDRi6??n|(y?nlCZgc}+gr*^D$ zV(mIzT6I{PoO+7E;oO8IkE@}W*Xkt!aw8^|S+?BxP_)kPq6;tR%ZY#sZl0FGH&Z}) z;P8Z0rJw&&f7@CMK{E44)sGZKj~AI4&bsB1=_y{W_tVn*Pdw~WJWsV~@{k@-(QPnj z4@_4lUGOn(^B_lis2zN;GpgCf<$_yrSH=um4R->VxPu^pCPwocASU*Qp#BkYLu=+$ zu)LRvP?=8avRHw*x+xe|Civ?l{EX7+AABaCtX3|gvvh&WG!DIAZ`b#`BDg04Te6IJ}g~;AQ?sV^bNZ1ESQpVr%F%2 zHvOC%#Qzqh=|YBIRd}BpU@cNdn_0`Q?^DqDn#7Bis&ai*o5%GVB<+tZSC_tUOzh1d z)P(R|Htg22?8}rI&z+a#^@44U|Sl~-tlWT?6p zH`BRQdAUwqJKzG6d_9xU?taY|`)Q=j!*x;O)hEKDT&rT=ow5>*L)-zrd#0S=oaygr zapmQYXi7nKu@x^hh!I}pS+&f}jP)5w!8Usi9(u2Ozms>kxK`^2)uQti?)d9R?={9; z)Yk2O3Vps|wJ%)Nkl@Y1>D z6`n#0(~jVZZ^CS4S;haeYk;KRR+<~yD=B6y+N*l}+4g{9@}bW)5mPrRveNZBi4twa zO{d;7a3akw$$eyR4fPNexmgw6GCnmWZ<|P;Zk*&q?yElMeCkC-&vK>c7w43OBy9|_ z_1#3DJ+aOYulPoI={^pqWe<{b`d+`B?+(7~cb~PcUwV5)z`bwm*}lIsTA&a;ZBXR| zOAWWv^PLLe7XU8l(EPtHsi>Ur{sf$S8St{z1=z%|Elu+c7tvy8UWc6kuu?CPpfRju z{|LCQN7nZN!Qt<j@u?A9r%C;7 z!%GP#&bs11R_O$VSlH1%7shUrzK|p-J@hk60LE>6R^k*ByS3Xw@8s=6Mf+~NP}eQH zUvn+vF#py5p3M6L>VC(jyWQ<-Ctkb>NPROk(J>#)A140 zk4DvSxo)ghb`qhC@(~s{vDJ4TzV!C@Wf1S!s5CQ zM)b)T-6F14x7|$9LTT=r9TqH5YeI(2G$qZN<-kxS0R!XCkS9O6nJK;nT~cofO0#=u zMTLu-iW3FQ-Hjausk!8rmhRGYysKchzP!6=eMTU}6sg3$=c?x}GG`mz;Rgv_?Uw|? z*xa9${FLkEn~5eK(sK-azQ>!C?M3YQ9JRv+#g;%bGWMS_cQ<%cU3h$nPv8 zQDO3|W|F*1HZnTuAZFRD5Wa7|r$l&OFAow7C;=t493+lZ8#tbzmR@G7SXCKVen=)I ze@@%KMXKh_YfH~|H&5RktY!`TbdCC+C#ez1#lLfNn2R!fvwP5Ie0fN!o5?v$hN7o( zJr*a&ODneS#to>-{bjU2_obI;TmG7S7eta}ac(~cDw%RHiVB3abB(~Eb=0OYQIlEZ z=QMvUC?q$+a)m#vBE+-?_nv)bXHULxR_dZl5&nzD3gI z`mFQ@voibion~D>KHsvm^d?%iSGZbKNu24>oZ;ZY~0y^k7`xeuR(^EpMbjxEa znX}7o27c$wxIBj!_eM9&5v{w8l;7GBnWxWDN6Chy3=XQ%Alf^Jqua=%Nq-lCnd0v? zAZ?7J9$Ki@m`H^qUDMDeEjC5v}er*XWiZZOyUSVJvPfSX(_((rb+}lpY>(;ZHD+DZg z`WBZYQhUrhDJ36sMW$6ui5S&-(&-bbH4&$Coqedlh+C|yL1iQna{@WkH=Zjj`3q(z zja_AZW|7-fT56sZ;c1hAr@23u8YP6>i^-;h@1*E_)HK?ITdTQI+82iYuTn0ge4@;s54f>u9NtUyLYkdvSp|K zz&!!0{NT>eKNlf*9b{PQ6fRVS+%mZsi`+P%)|-2m&<#O)8rI`-De>q}LV zU)PF1`=)K4vYjszmrJnd_A&9eDxz+zEH}=`7`@o7&^StqZ7)`jTmMb_%j@=#+AJ$O z9y#OR@k9G03U#R4af{%pj#c64{*Wq|R`l{8+y+9GM3BTRqo)wO!_pa4l@>#ihb5yX z!jbQm*i5GUIHNt^*Tk>yv`A%xxs~p&i%i1RDq9kzGPLfSOpV-hp406rOS^ZJRG+=t zy`un^RwdbIjt)&}*6Y+Z&uO@}R#isIobh$5eC=DDSUk+2Z*FbQd-SdA8D01ygmTM7 z(m%fSCVgWvG;NvNmn- zJ|$0^BdW=!lBVJ;-qn7So!1ln}Ql!ndXH3c|H~tw5_VmFAwVnR*j)~ zrJJnnK2Y*2v)B@awJb?C&G;knYjvCu=BDjOlFXADIB?$Q%Ql_KWC(2nCydyr7Rr$w z`>^|<*=#K8*PPtvNuGNf@HCVz&4V}CkR*?^WqoxJFhh(aH70KJ(ziyEybKLF_T1Iu zZ>h`d6?+v>#KNDT?vtZ4=tFMxyjFJclt#nh5O#YRewTmpPA6tyjcM#fo19d3?zBRF zX*Qdo(Cz9vP$fV}@6LJcR-7@KR-C93!d_hos_EvRXeJ&@pNUi#x;JAqH&3peXY3mr=J}yR4wxKPdj(b z6m`kIz{+v=yDj^Ew65|f`guUQYs$7w|8TV@t*F-;@J!PXbiUCHrE+IbS46QJE}>h0 zprj!y-P+&yHN~ZXaXeM2giYq9#feKwf8|qi#nq^R1=-%^Rj9^ut|F(~R_1eeKpGJx z>gZf36?vV5N2Lp^$@kSmd*07MVAo_Qf7sBYwRhDlHHka})8ah&X{XFL=M&u?DGuo} zm2;{psYEt6lbPbvwaY#Bq*r<8H}X68R&h^y0!3kvuR7M3!p6S6P~7WFm3?b$*G}wu z#Y`J+A*RbT%>BJQytt>^>Rqx^F*eQ(qHNU=E9K=rOWhY-p2pn%Y<4}K5zaeQ1F~}Y zIuSKu`W}Bv+bVFfGCTQvl>ZV3pK~#+e1AmKe3ZZF2QnZqhIDrTj~*R>HnFX!POfEh zI+}_|IZAXT>Rb(c*OWP06)i0B<7^I$t9)p$#%oj;ca;gVmHKV34FtM&6G@z&Gy8Bx zsw|oc_?PrL3RLKA-Vyh1=}n37q}{giBXhXCqd)auS*U0#guhy*1t@Q#&0VxNtE?!p z@^JFp8P9&$-4xbU=bhuFz^H$foqVdwoJZ3Wk@y&rXMho%JBo5?zA=pJz5`nHjJ#=s zaPNZi?pN$0ka7MuklJ&Dv4N%DD?75h4@OT4yj)0RDvSerGO^OyZ0=;S$#j*(#h#J# zt$`lPb6j&@t(A3a3U;}5WfyD2@3c>r?_;ETS?%=xn#RqyAL zF7p|R!XL&5V+JwZy*@T5$g_RMGvqC`A-ixf!gHoZqMKD9SaC2HQi{?;hXcRddBkp_yU1slJtdx$J_KE-##3fA}nsw1=DdTCBL2_Ox)ceeUi# zS`}MIxm!QQ>!bA~B}dy{Iz+wxL0}E_o64@RfSNdK%*7GQ_Oa5X<`3dEzC2dPR)(aF ze|XCxjp;2V*_l!IG0Z7QJE%Ix-NWvDhxG`a#06aFFAApc9T#N*)Q#%g?*|a%vuYwN z6@~hJ1_winon9{_mS)Qh;rKQGoQZ>quTmd*LQWAm*PIwhtH zQjyeyqEAKszH$C%CN2N)zgij3ykt#j_ zp)|!OOgxMeiLrG5uMNBrc!Ii8Wxo&N>RgnZfst1>tXGWo z>exb^cIw-qr{%q*?_n~oqQ9-zTkojJlG#xxt_V)B#+2OKc4hagqEUj`dwi!hZ5g%w znFr^kY;usdq^{*oqs<%8y{n={nlM1P|ySj>8nToC5wM_Av zw-@_fpY*i+Oi_QU%p7q{Gb~OF)*PN>EmhA^^HTR$)@3wMDm7)!6fpcgy2V!7nNhfV zXTQz{`hVnoJ!f59SnycMkM^q~0%Uqp$V$tAX zQ$yM1&emq8S?=`3obQ^~-9%%_Fc&{6N4Lbjag(2BSfAnYtklsoJDBOXdZu42cue zGLR#lH7}*o!NceOm~?~&%=^W*h9N$Tp2zEY7Z(HXP*aqyd@W!|QD}0!5Ef<-ndEWH zYawralm;h^I*A#C`>Gx*RRT9{l-sGEkN5Y=wpQ&Vuw|O2gk@-zgnBbPZ*8%OAL+R# z#prwpr23{Rcx|q4Da`I6B))`bSmq55{5jAOS)l zi!}W}djO!PyPV$3?t?UlSS@A!b3TVX-ZQ@m(@N#*)as&eiFPmkI6+;OwhaGp@_obW z|0Bn>BH`2esk18oMSX#nA-9uHyNKj!D9r|=X81aqDm(7q2tj0nzOCtFH7mPE9m-4N2PQ?Nmd#+J zD?wPy|IbNFv8D#Dk`PLtx!~y^AxQAiyjVll=>0dv8J>mLzhtF*CRMn-Gvhk~(`3Re z-lb72-hKPLqt-Q{DwiB%sX#j*_d#W+=U#S&TSLDOeXN!37Mt32p|C5sF)gXMr@-ht zaYPCK>?35FwQ^G`(N*~N>bGJZzBcEZKguhY6sFlWLUikkHamZ2kM}o`;Y*zfLe2We zOGX^mTbTyByN-s>`a)bd!nfP3A=(te*#!umZW$f%M6atcArI`Yi6z+mcN0bxOZH9S zXX(sH=!M7af_!s3#DGz^|Bb(S_>pPd#TF~y-g(7WIFzW?SqA(%vf0~STlThIYQ;zF zI$zf}SWW)l4315rhJ{x$&ytalUb~Ke{RBUT8KQBHz7Gl1P8h#}0rb1qTo`KU+3#3D z6v8g-{NarF?;ZJ0<`tazH(B7vi*%?W_F`Zm^tGTS>XuIoQ?Yp_sEZQ#AOen43<6mA zOOQk&d~5M#soP zIIO*Ft%nF zo4pg_jKTK76Pl9F(vi1*kS7@&o6pi;IsLbjY)arR)-Nu-QindJ(;gE&4jNl@JW3g`< zi9E95QP&~vRGRav$p_~17JOhToR3^N*ydXZ+^P8gJK}gLOWX~Njo$x@4?{xsvr(F_ z1tvwM2Q{TwD)k0*YeXMpSip&a!PEn~G{4hlZc$d>qR?_^^=X>fP$*h)1*fKs+m&fj z!L|MZ@{A+!H^}Me>r%Mj1-60yJ5f>vK!zSK+qYvnzzhx+gRpGO0<(V@ zj1tu!eobk1vQ7}?lARr%)-<)Ov$Op_Ev_XdKNkl1=3)UI9O7Sem93ygrb$%HC6WfKfgWk3qi z1sDGO`S<4OYmd7F!R*fU4Q4lZj5@DsTWym0Om{DV?8mC&p>oqRfk~H5@W94vf91cj z-OoaY%5~xf)WIr1>F)y31vx3v#{V9)BX50jQ6_5gGa%T_QB|{_-OG!(Wyym`Qzwnd*tYIx8uPft1Xxu|SSp-tg7&}pm|4QgIC2FQfxzwv z&(0ui%{*U$IV*gd{Xi^~{{W<=YWKv1#g*ktFf2k9Dv? z1XZhU^qXe)RGY33CR>z?w)Gw;H)@s{rukhT-vV3Aj!g#D2oN%)QYf@1=u;=UD- z78wFf?trOLfWu&r(BNmZw#Hd|=rRz!4t0X{!9HMR&h?uTO0q8TqexNZ*;Z6o4f3Gi zwYwFMGN;?XkepxuopSWSxLrEiirtEx_6EoaqPr~z_d))B{!R824o(xMD0zjGflm4P zsTSP@$yiRWR`KM@)~*+Ur{LLl#N@75Vc#RW25>^y3hsDdU4!I~5^_69{;8Kc&}pu~ z^k-2mAUJg9razaG|27?mdYYFl$1+|$wS&@Yn(z`K9!lM@SB$FK(8lGHscJ^wyoSka zIcNk1+}|1`1eO8gD0sy)k&5(cF0vD#0)9NU4SVJ;8i8s+T8^z=d!Ry? z(T$Nn!V}h{II!oFniAWYr!fVpyIEir zLpIW-NkS|blPSSEix@P?{0pF~iX#~Ki3QTAMI^Ga_~UD*m&*gy{i{&^9Wi*{b}Rgf zXmJ-GvOUP9MISS8d&>D72FIJ`b760rk(3{*$c2dLX+>3&Wqfj14=z!2ZI=h!aPyzQ zQ^78+E!?3N1;%k1xIth9*)`B^1W0O_EMC`3+q#R0$+SmvJ_VT)!{q@&sU^;G1FIXq zla%mN+M`gKYl7ZimplZpZ8465AefnwW?eizYz4(&Iu8e(w6HsT> zd=2Fm8JriS^SfJnqoyiZ%XTQuA)r3q;zQg5*p_Q+kIg5vPM!b;ct3(suOHEUpOR0~B8%zH1i$fhh^$Vkx>BlLELdV1(P_cBwu62=|$pDj&@m zq5$(-{}=OgULZJKDUPx+$vlizw0=3@iqt&xIFPUz1?#lp@gVvS2Jup~L{+1%Sp9~w zb;#fxJn9rl7@V+E9~KAPSQ?ux{^r|7P)PTKTD$ z3%0!_O4&xwdYhL_NhR>7fP-j1;(|PG0Z1c`(;I=f!_}z|rGVwjyI-w(1=9P3FWjV^ zyPI1$Q&s`>ikquJ+*4{0*sNMn8yY9y=omaX13JjP&|+TnlAgjMzun+u?%f08{%#Fx zI<$3bszML76uo0#kcmRAkQ6n^*;dfk1qml&oi z$pGkST6wqw7iww_u%OtcDGWMcKz9QsdNIB|MzgWzEIl3yZ^1CuKNiu}>&W5m=R>0f zN4=ck>QEAE!1PmUEykk~_@z>~=_(@2Q#54KqiGe@kgH)uSBB7J z426Z^VEb^>Fn=}oD|kgwcZiQLESQ)gcu0x`z0AmyXRfcEMkE*tl4p5G0*44(H}n=d zfr+#-d6V4PT}n`c<~6h=%L!C4s^JY^1=|_9o?GMqxIOH+5QclUuG)+ab3;+_CwMV$ zEBO-kMT6GBr-gqtT4`!nhNi&H-!V2YlQZx8i*RTNZE8}&O~c9ThdFHa;~K~%T3|Yy zMR5-|1m7Lv@l~+7X$#~l{r2ZlRUIgl1tA-#abFttz*ECEu0@>_dtVj2yhBQ{h}6st z0hVGy0MJ#su%{T%ds!D1g#TcKgSs+WR_W6&27mI@{W zvI&&ahjzHkH$aR+Rz1SArhX#NrG%3SRAOArm4LyZF{?<;b2NO)jsP5Jjf3dfzbRh= z4=e{*j_hFe(x2}O`vrAV(fx5rvkvU7O#)vh8|XV61g4)tOlSeRJyB52V>iXW2J`%P zq1Oh0UAoUKH~QW|frWmU#aSE=iK#3RhUJn>p@U+k8#JkmFTugfwAAp6zPgsH=32FY1rq9gyf_!>{As&JLh~DI)iv2!* zgF(#saV%!}glS6v89&zG0muzgn=(W9u85U60TE*N~@3$mjQ(e^Yzv4&aKO zD619*q&lF<4|Bfu6(-4!**qUTR;ns6-{wB4qqM_eVpR^8azWGt zlV-t_K-Z2j0U9;o88E4%Tl}a5%aOEmPjg$gs*J_%H_;l<&eVhm5FGaR&A4W0IF_1P z75#gVVZdsd!b8r}?-6gu+|M2P&<;gdmMSoCAn3YD@}1&t3spBG3Bc@ivIf{3tO-4` zNXzL2075!6q$esE68v}b=Bw1NF8R! zbVXoLgZ)H(`1Fpzqbkd`-{o+JDdoHOCRen91K=_5Vp92Wmck3d*SaUmR>u-*K(DG7 zF3_a4JG8;4=!lv&U2HXoVF4K$74HC36k47hS*}A)#Qyg2Q6!Y)X+ALWH&ce?5-=oQdoWs{*QIf!$woz zryrEqqRWt}{swu_p$B4k9%e}Yjq8ug0W=DU-y6Cinud^3mo5l;e;Ek2$me&H$Swo` zxL#}1`uv|?9^vE_Xs&pXq|FDdEsvl_HnF&D8Bf337O-|J5*TW-Sh@sj6lUa_^RRMe zD;N*Y8efQ*y$X7Ge8b~~Z5W;~`+hj;4G zEa+4iMD;;U7pCBYngyI33`*4v)N5TXKNsWpmH7$*?|_a3OkR}ctxRY5N7lHTwfv?9 zNruwr+jxss-{O6h?!~_Wxk09HCE0_q&)2?~yn_^qXr^m;$y2}>_Ac}f-~x5%M@jwC zuDKos#RWRqc!?JkHP%VaeF>^nU!8zXn_6rzu z&2T6y%DESGlXPK6se1{e;a-gBA_h((p-&0TKEpP?`H>t;9lQh4V}ra z`nGco)Ec)6ZV7!d(v+`W)wSd%Gq7y4 zfklHt+DN+AlUIyq)D5WmE>mZaI5lBkI9UPa>`k*=v;1V116EW?p+!z|duy#6(5Ue< zkd9(Pp2KNninE+CJa`*q7WeX%ER({DdqKXshvt_^@>^f^*{YpuwOBVZbOHzbKyY(V zq17~)og;bX8y~Z#8&=F0%nTXlWUw23V6mwBq;{T{h-_%B7#e@Xp6UQ`bimE)1RjAQ z;9&b~VY+pU-y{r-*J5q@9t2lvL7x^sN+u@-6qBIKbkk#Vyg~SFX1x*p9q5qUT}(a# z>&+g;6#5lOPfl{+<{|u9{IrEs1wBgn_euTvBdBP*Tps+~tL2p0!GxP|6uTGt&5|mtQJTeD zELSn48PFE6h&B|bLp#o?o}>4$;4ZE5$P>T9z^T0xF)*7Al(0E+)Rb-~Wx=a{nqFb+ z+)}ewJfsDCYW>^PSq~h%I4ZyunTx@N*0X%D5>HaZ@+?jbY-&5EIS+lCFu2GU!}HsI zX6J$5dqP3HYQ;lN&|&5tYc7P+isyYTi3_~C7+mZj23F|?>0I{MD^nkT0hC%6#MW9E z^o7j*lDRS#lhf&3ZTRFe@p?O+#_5n4^IT;45^I=gXTvq)vM81n4@--eo@Z8B=LsXO zK9W>)K8v#s;nKTORdW=)QY(fvxf1~q#uU1h&tLg_Cg{(+5C*ez)15@{kucF?>a6jD zXZ;58h+Sc3kDMquZy|F9G~qTmTD;y$-~+#NI++c&jQhJFF7?imU8e8JMR6RUDD%2B zL%LztSm6sPVd^HZD_NwjGa}L~ZBm?$-2;#0jZ2jufarT{FPH=*wy*9s=V`gG{Vfp@ zEK$YtORxT{9 zaMEEOp@A718s5qC1S%b|r0dyLGU{p$wdB%PVJA|Gw~KCC_sneH?C+u3JB>Cj0GgDG z*Y~W|kN|{2`65Z8uk#~vZIPDVc~B4&x~e$(*fW0hToY;cWzr5CJp{{|<@wvW5x(_0 znv;w4I!gH>n41qsr>$e0$m+#VNQ5F{ypXv#ggm#pxQC{t)t47Sn6`)dhAY0f+*S;# ztAPRGJuauTBJy*Z6SHDE zGgJtjIu(eORKY3+Kauc`SMv9}M-0n9aK0@Mx0p>9^a%bWg!`GC@f(R*nmE?5ZV8xm z1P;GlJft7X8?WskAoIPRUuUtHm?GqZ$;>#0Yh^W?;b~TMuOxw!s(J6II-gbEb3@p9 zOI+@3*>}GufXQ&6|w{hdl@3x%hyTe`}ALLTg&`~1lH1ond0OMlU5Nj$Hb%xhP%tL==z9>C@X2! z)y{uigPFnv1CFB1WOv1pw8`AINQ_ML&UCkudAh1*bv<)M$ zln~&o@M|xj?G^FKzM`x>nB!fN?g?ZoY$Adfj{l1)<2F66-m}neCC+>toH@E<{7#B8 z(+lM1oEB*`$8fx!ALc{6z^*&11Sh^F*ejpZ^@H=NA5v8)&E^%*1gSVfr_Je_7L|37 z&H$8iMhA-wtU+V3ibwnyKjoQjXs2v^^2|PxC*#A)H4#Sms$m*%)J8P>9(4GL#1ry~ ze4;NJ9y{sp8kgjku{g(LKP-Wxf@zF`$q$anC5JJWNweo68VnpV#4Kl-NI!H%i|6%2 zfYNMG#dpvs2mf0DcihNjQ%^M3TM(YBIk1-9=`kfSOaxHPJ{W~zU1z9=@hxd0lkN9~ zW#g|^S&K_6#&HUKG=_37E#hIXro)?5C4Kr^c%GMTavAYKA5=RYC?M4j-w9~JI#h|q(&Y3WNVVD27FZ{N!hSRIL(LMMrg{1xiuIa>`qK`e33DAI^r6p`qHnb5L zVz5`-#yOKqDh(n*d|)Po*)h1qBCr%k4<8Si!Y|$+yTdufJ3KgY_2HGiD+dgO(SlVS zPwl|<)5s+{i}$cgD_2ePjURk`){fYa#eW(vBfbf}_4JR^IK2wgQoOwI-$Wu`mR3qG zj|=MQytL2O4V~`OBqivM-vaHvM*!N=eD`KxxN$&&#RJc+I+3!vLkuezq8(K`L7QZ` z^KH$z#Tr^byuQS7FL=DD?ixaa{^#@$GmMQF@R(qmjr&=JaGKIcksL`+MH6<8wv(hf zxQ+tAr-a*O`7Z@A@Ue+BKR&JYt|}z|l5nmq!e`nNbC4>~I9DDFiGbte(M_INNuGW9 zxwfev7frj;1HV<%ZVhQ7FkX=u5acR;yMXJ3lrC}hHn|Z>B=8bufdK{9Fsvcs%40H& zUi|ER2AW1eW;zf1RjxE29m6e78|U;$ z4U^v(Y}huiTzwJp{n-!CQci;5!RH${)R{;x)o(~5f-aB>+yOa1=X!fQV=O;hN{i4_K^1+k6qL7)qyugTRUo{S3tqB}k-@k(kJ zuQwlCXz5GrzO1(Rv2kIpGtGOVSl+HAnIBMQehX_KI1Af?q>_YL46ZBa%Ey0GiGR<( znFqs!@?#6MTVwKN5p~h~V2MkGEAh66E0AZE!xbD$q3&R|zmOW8kkBlB<31d+OK0%} zoJE<5G>=VNqAQ$5IB5s7_z8aR)F0-#fcn3W=H6R02F_M1ME>|v>(<&U%sf9gFw54L z48Fh|Q+JB4Me1j;*(n5_d}9@93FNk}uHg~mU_kYH2~Ac8iB-(fe$z$BtFwqo!eXs9$TKSSJS3sRTj_8O^W=+q_so zJ`bkn`T~$+{%COfiXGMYrjRUa&o!PA4Ru7;w}Vkyh?Vm!%W3&dYOpFuI&N2xA@y>n2P_Nfd9TB2d14*_wLWKy-vA*%ms*Zqqz) zboqO)_8oK&kxJ%=FuC&q}=fIp) zn@GQ#IF%)f=$i?hh^~H8`W`(TU{~YN9wK=JWH4IQQ~k>>jR1Yxlixj(53!4L{FJ?m zlZQ2yzKXU|af`j4*53KXwo;&(#q1afE-idvcX*dx!9IMe!}bY zc0JDJCeqcrPIC$7zMP^Lz6uxBl&sV5kw)piRPCDPKt<(kA1DMID2rUQ^d(e=4RXIk zYRGN9MtLoK&3UX~ukaGH;ILUq;e*BYEzQAGt>2$qi72e$`T2dJSkR>?WcJgwYZC#5 zLd`vl`?|_=cOYavtU+|h!K+K1ZxcxZMV2Id^VJ}ic*?+HHS^YrN`Ww)EM2_*1(qis zdiVxJS&u?T9tG#~R__4kypcNV7wq zz>$s*UQ7o+ltPxlpx=*1o^A|*HM2#KHHZFi&#e0O-Z9l9WaiVrM8RUF;r)efilOjQ zN+cupIGCM&Pn$FRAwC4Bh41;+;I4&b&$oegCX-iWT%YW3#k$jF+K@$WTMLl_AyIgT z4}1uVx-OqMtIysVM)tv7yI*{Ss|Cu*jYT6ao5Nbw_34km;(iY!Yk-A1*cJP6T9bbK z0Ic`m*hMBFb5p1K-}_`-`U%=(GyiFv>GEf&drHt<N0?ZB);V!v{arLZ@2u$=t>S9=#m!7QJ2WtJYzXmo4txLWJh^T#IGOn-4A(Gb1-d(x2A z$9fhrcHD*0S-~;y&9?SPlVe4~6TBYI)#-YD>efg)a8deq&(bkm-t0ju$u*(j3tMwu zj;sA$qKBDSy!uk4$l%9QP^3Mwyi+`QmZ$fbh52ccF~sU2m4Ox<$P2KJEB;Yah-IG$=Hj z-0pmInqD9dCw2noA|pK}?0V1ZfLf3OsDL+W)$^fX7=RhqKu|5I>rlgMEPp%z@Gw86 zM3#kR%Sy%6iZ8=d*z@fFkG=N{Ych-eMT4kV!5I}$nk9fp7o~)zqClu3M4A*uK~O|W z2t7EAVxh=O??{!2kC{gtxkLy$Me#zAS8O`+5srsM@=B4HtckeDHoS5{p1uC!+Gn6Ng~`$COLuxr>5*MO z#r)d6T`a@6R7}VFh>xm%85YSk{hC#dZZ@Hazg=5PHQLhyo?vM4@0dam>>wm4RE|g; z&TvZS;Xtg+^xeUC#g$pJLq}p>o{(i(&%o`qQQK9&iJy~xErc^A7a$`ja*_##7KYMJ{Txuy zT>7YEz@NO##nZH&+Wnn+SBT39S=wpPhtoP}!pEbJfS_L)4g7)*wjY%HAfj@_`oq;9 zJVzdZVS?!|+pzuj!g52Kpp0>Zb{T5vUt8r+=Y0+8myg*}q?Y|{t1YBv&z<)Y9%-NN zOXL=ReNl8|=w;%|sqgY@M=4_Uol*U3tCh1_Y(8QOW^!@0{g1aNxAu9aeXsC?i=0J* zy}nDEYT;jb6jeKwDB-zUEIAGcPpdp9NfcVa2`yqFnIK7%HV|#(+6DtRufuscIhx_u zMN6MPNpa3?LVY2j8(<6)WGqyqtjm^p##B%C$(S{p)HNN0aYZ1|Z~p#(X+_t)?GNzJ z*R(I$8O*x>tvv|-U>)w?7x*&5WrRJaousAZ^7%thZGrYkn!C_f7tzLrAMUG(83C+& z-+DiL@~kCY93xzZRgI$j9_@y|-`oTL9CE*}Av?)_M&hcHg*zc3uV)}$ATfE{(C0-S zqLm_gfGaDDf~@92=0U&Sptyz`4}CDd&k!9uTqbVkLk7}aGY9M0Nv1c4@cL_Adyk@iyB_->vS@2vTs=(=dk=vF*HbW@UjUqSxgLfoH`96QaPiD27;@?Y zCf@gNvz5BmMP+?f3SzF$eFqLanIUsk%vv)_KZl7s4C+<0ddC`^OGCaEIx%0FL$5%T zdGP9JYr-a-FxyP^<5j_o*`42#<^#iet#!h}GU&VvQg4+Z3H~HCxRIjz-S(IDky0}T z3d1v^!j{Ki<==!eZA)A$eiS79+l0^a8>4TMIKF2+q!h%Yt$kA|B%Tj^;Fcuwe`=9* zO~8v%J9)8Xd^yi9!@^- zXP0kOS6`1)pXc1oqJM3MYNQZs{ls)}j34R2z#)u^CHNq(G z+;HWT;Op!0gA%1DqW%i{^)wg8kTp^;TS7VI_t;!*7zBmqZ*4vMwm*3h?)^&Y)k2lP z=J~=v1XvsDzbAUa(Om-!j=~csqsw)NU)NJm_lE;EAk6Oo|1v+8CC%Z6Un*V@%$cs| z5t@wnh|aIo+ovJBtWUY~Z--%M$3Fy})=mta^Wc*SzkMk$fW!}l2W!@p`FI(f4R_#p zy)j~srro5=yon2pFP^j)g1JuoD78Jh6)>Of0-0T!8Zm}yx)lXh6+%N8tX9N@6sOa8?b|`z!I46 zrlKE^mmAsVA9{1kzh{voF8FZGyfcUD^IxAEL&)1YS2$@gVbnUv4{%(Rm|SghucH(1 z+dMzm@*TC_cJ`=UO0xw;`qj6`Pm~P$ut09b!tpSW|b4&k? zJ^F6}RZC3Jm5XNvVI68a!=m;l`h?|b!vH}rj4*|*0-tXLAc1Ki55=2KQdCOjvx-VB z&v!P#nTrdRDCQZ+M;WrrA(zUX`{>O#S~~YgsysBvL@D2}oSPRz{T%-TDq*Keph^s% zDQay-yk44m#28%(cm<8?I?{10~Mh_GF!_Vrvg?P*3gTro~8u%`F}7Y z7zB92cx33V$pL`9hQbU4Sz=~SheHZHg+G0IOdWmJe^knSviJ4dey5F@W39p{p7K5n z>fAZ)-(84T>@rqS95_2}00^05tw&|BwV(4(r7B|sGIJ+E@rZ7_XAa1-LspF_spuaI z9kta4&1D=yVJvh9z~ch>xfxN8#y_Sy)lAC$BF>J_)rLWy)iCZ;Gvez-Iof8ZIb%jZ zDoKbOWHAmj^vPQ*9(65(u-r_1k?STQm^H}r=yR6y^B@R!s#TcAQC&~^Q zU*WGQIQqr8=Xq9KQ;(c7WiY0Ht=^a-jcz&;@fGAJ|6>m%RHI>o%*1Q&5RORc9d2M) zc#kPvjvtkuzM!g${|2q}oHi>>Z`&-u&9;lcfMNwTQ2V;?RR9jwlQ2 zy?6zg#xewsWz>=lUtzxY!pU*!-o1iYh74SO#lrD z`oY)+BakyXgoQg}r)_cK53@$jd8G_T)_9&vOvte`u-W%(_&jzJPJVV&cHquk7q^q6 zvA~b94d)6q*^;9g$0S27Tb6bMI~(hh>wa|RQc-T1A&Y<|03uFw_DHYW=3~5W+zO|z z!vUsEPjQqc@SedH(fA!E@3FEGU4oleGh=+Ja+T2Fp$d9VL>AtQE50A3LxI^|9w$1( z!eD^z!(7N+FUlL-W%LS8B%i!J*c?KBaC#`3|F2J|F(iW;4^$z|o|!mKz*Kb)ml8@#osl&xI2A8F9stYTQN1dwR+FZU?2 zC?4S>x`?1&KN=pXx1)T6#tS4Wp6P~lF|fHISdv+i;FFlUX7M!c-M*dBtIPG7xsM@_ zy7uy=L8tS}Q|%`ggMXihcqCv_a-p)5gS!JSIeoon4c^ul7-CMn#ylB2*@q<^k(wwq z+MY3`${d4?>)a8VRM{e+^}LBu{Uw<_?jBLc>4&yY;Dg}lM| zwD*n#+HJpR>|9Bu(T8WblXuNJZvR9{6kABGS*$c~X1?$SwNSxv!@=(J{EkYh;L31U zgSUqZj#}@tE55u3TO%zMGAIi%#XBJ>VSaO{M8*}5JTE$2oP8{KwLBLO z%4*=O4qUkB5tVXhNI+U`d^%{n)~Xsy;dbNqnvJizf5H_tRl-CS-Z9wLAg`Y(R!Yq( z)L4xG5#jk#d2jh%?#?})>M#vR9BJn8j-%ShKfx zMy$)V;o!-|Zy}xRjQO<7g|4<_JcJ)Hjki^RM7qv-gmkH-t8Bf1DK5p{aWCxB;5r* zL5dmH=CLjtj(3e77%v*zqZICuU8{K>R4>*Tzmy2X(@{b`CA(K{J2BzpvSaS>)B47z z!yKXNNPQpB$SGREuH1)R(LP9C=fwhg7P@_sJc`~)FHUaFxVS;MS{+&8&^G5);BWWKG|dK2QclM zn;^DuJg(IPKl#QOtHtSFM0H57dtNA%VCJN_8q9m$`D^eqYnW4Jjc6;uGT>C9uA>8D zSteIj)d_xqx8b^5fI{hBac%#J_BW;r3;7t+QLTQ(a%D57bHjaVjvMV;>Knam*O%P& z-;ytO8hYKN?-IHdh{$su-noGN3BHi`j5jtU56WSvB*J=e9LSWL_kF-<=<8dA?#+mf z>AAO>SgmdhPe);3?8+~yg&V}Vg1=oQx7Bme#l#oxgSD%8`@3>mY9Z7~IS0<*U`GJm4$ zfVd=X6>)zC5YpUt3iih6Kb~}g#AKk$Zl*hhmBPs)!4|S>y~nk5Zs)ua7liG;X+ShM^1b9Hb}kGcLtp;|!T&aYB@nho$mc!m zXy3lj*K_sgW!v!3ee>05?ehA@xpR=uLMdGMkMHdq7;2L9O1y8hHet0s!yBwPZ!n&N z>PqX5IyLelz(C^@=j=&D@_HZw0IIdD+`1B*@-5=9P&@!xW*@lOUzo%5QAv2j8Z95sN zTaT7^u(S7f;Hm>sfxkIgY9hYrE;nSsQ=kf20%`B5B>2SDS-e z-Gj@{3x61dEeJp+CH%AS{GT?qAk<}GN7@z8L_0OcWAsE_(?KHO^C>G&thbTjpkSYFh@GpK9Q=v7h2D=no7XqTmK0COzGoNp85=`8MYGTC)^T8&6M=_q#sZS z?LzyH!-~^67(g`@Xw1Di{QFQF3Z-`qq;)KI#SU>VsT6)(jUK6~X zO3?Cd>6@Y`nppdI>TZBiNhLfb{MLWGNld&FvaKFF7qZFDF`)!xeWcoD(G*>XPOR-z zniMth@ zh?4T*(Je#qR1xfO?Ycq*Tz!A_pIDi2bb>z;T?INJ6L7g42KHIF5FT%dETWgleby7N zmSY?J@&4nTk*i^_O(uwo#L|7VC3-;dN_h+`742a<_Z&n$xa1{=2?hT`&-*fPQ5)+O z0!r^Y8;p_14Ur9whb6|lGYuW?Qh#X1QSSifGWUPw`2yTIk4VQhDw4@MD@ljF4Ut-j zrH`;Zt9_S1%^d!pXF9?&y<=D44lAMWD%jQ699dOEMZJY>A)?7V$c^jiSKC&M5ixtv zXHHXMe!z(@$i;N(PHdDIt36Z4L4U3W?Eu=&H61b#s@wh(XC4semV@Mn98e|#DA@~O z8KPdpbGwY^D$oTdqK-*}YzM=N`Ap^|2VljWXzf(03nF7YKbdlhAh~o45duIq4`YD8 zw{HB$-!@QNA?J}6mxU%lfHwcmU!zb;H-QxZMF-A`iM0UGov?8` zT!G+s*b7J+G)r;Af8TyDGbQQ<|5$+bH%E$&X{!_qcLpG)=%sUrJjV)MPlI?qX~Vxt zKj{(&Z-&GNjK;VZKY>hmE(3snw+4KT-8}+$d0_DFA!K#qP`C{bhYPM)`^;5~F@Ftf zf&|6r6T_s9sr;4P1r%a*&;oCs)X(ZFw3yr_BA zlkS7QZ2fn)P^c&hYpR!RnRf=JZR89Tb_X@9Jw`|D@JRl zQ7LH2oh#;pvD7{?y(o6EA>Ip4V@v+G!6=0+_}R?V=HkG!h1y68WoB;pfeZ@BND z>TXWEvIxuhwyr@V{<90HR*7}t0RD;TdwH_V?QH#ykm*Px0q=j6n8UGYIr;yp2-JsG z>fb|3h=w}Zo03Q%7%devDUqw!_0-&G91)!G&^!P0!wTHK-v;ckokm{1$ZT3;W;*qm z)5>Epqq*1UmUwEcNhb*OC&*rM^d@|i+`K+B3Tf}^UBs7a(2`mc)rZO4w0F<=gy&fA z&|r9tA^t=;&i`xv*MFzAwPt;`)A|?q!|8Ya^WU%B|KfT5Yv}F&^B4aILGZthj{ma? z_@!Bi2BD$^_AH`o^8fXAMd1dGNtQgSv4ko|t+xp)8Uw!Z!qW@Rh z{pQ2}*Sfr>8dB_`))qKJtS`Ed*>4oyEQwX||E5l*SC`U7hgYg#U9G!{c~GqiBsW8( zy|Y)x!4_=3wmDxg9xH01?Xc(`;)N3An-_HEH0G7>B9V}^3~C%&($s#eLAPvai0N#$kT+;?v6y#=j5~&Ln zv#7z^zJJy-8b^q4mhF@^9sT;aQI3K*{AHwv()Dq-WZBx!M`h8b#K^|Dko=oMqmx0Z zWE&QJs@kZ}TB3}Y)Hsq#&0s41Y<6O+aXBU=w<~1E`8>W&!f27Vs;Q#+g4iR}(X2uL zH~ohGK8m9s8fX@-ho#OJn$E9;V+y6X$c!__#=@ zw4s+;C|!eJ>Ps73Fn@44T1_ZMbeW6#Q)Bw}Ia-jW{}$Fr{^uilB;-v$kHLn@i;sv0 z*jXA1GHXtVN3Q(N!Cr;_i)z%rqqv1ac$7Eu-uUI-*47$>xpmveh4sE=Esh$+Ox;NE zv8!#dKBgK$`W!=DL3ioT+0j(SOg#2af6`2&_MsiHb1vAMjCuy6CMGGL6o5tO&;|CG zUSCkv*&0+tGN;I&PmiHyj*p)1b)~7?oP6JuyvMgk-exF+dV!EGuk5`cYAAQq+ZXXh zf*{pfm|W5|5#EzG((&vB*}-IZ?J`_vU-ZA(<1z^JDK$95q+40}7w3N(!`l|GAD`b(;&h-jC-~G48d(rKg32&9*v^3?w%lVsuYaga?jXQRXP2+q1eTOYUAT4=-B2o zDjzcFQ=DfmZs(l5fTDAC6l@wc^gj?A`@-v%-B97L<0tAo;gnk?vymyD#V`5eeaZq; zg*=E*wb4t6rUEMewMNM43B??U;7zkhCI&4r^Hu1z%X6ztX7sDP{=Al#;}d8i5@J5J zS`(9BKr%6G+4|iMVlx*PEmK!@{^g#`s^xJsF>ZAOtuz0=U^RKR!7z5^7K=}|_xQQM zWh6+7+>Wd;l3FI^wv9{sRgnS$cr)(#_EAqvy#JEpIT}};8XKg{UPyXB9CJ)^^t*&< zWt{>U(&p%SV#-7}=q~{z2u*fT4tQIN$W^ulwAPl3wst&C^g6>klP7Ftx%PCV-N+mV zS^r}6NaCu^QgOuCtz_3Pi6QUD^U=hlkoQ;13oKFPRvlwG&Z0^fgZf9gq?%mq`}7a( zq8TFF_4S;HtULLsND#)`UhoO2Ab(`zuMDPual!GO#~TsQmNTVV$lod5L1bzbF3~}xLv9l01JXPR$U^NsB(k~buVy*q22Cb`SF#|G&VUs|N~U+lPu%82vV*gK)^uL8&SSxZizxA2lkl|5qUihIqYv^u>mjbRI50v4Ot&Cw?g z`s{bim*3M=?egNok)T85op$|0A2CLVMQ;={T0r%?en;#ViT&x^0{UY5bN`gm`-CPh zZN~N9frQ~|)Qb}Lo`|*)be$Y|*_l0Y&$`m_^+shzo?Sm1N;vaZ&ga)3AbUet%)Tp^ zN~dFQ>d&d0eA4ut*cg7v#E4A2^4?OS?ikrKa;&2=7z>33%YM8=$c)3q@>x$9fYln% zYGK7U`eeW`S!$Ug;H!;<*bT+f{o=?0@2=83Zq=z`tBtE3iJGiYAB-5x0oQ)2bSfp1 zrd47u_G0vOIC~Ii6F!J=w`7%4<23mMc$7;wzsEuRn=5!jgd z@%SE7Y7}}FJGc72`EVnMu$L=fbd&Xr`f(M25}%|st)7w4cj;OYDS_fq-f~RTLCttk zyiS5#R{!w(8yPFb+mYtYm<_SlOWsigyyYXVmsnS5LxyI1L*}jdkKKxrsyn+58KQ z4C@I+C1Y%2owC}dh1NrqO|;p$pHK3Zp!l~a>A!qjEb|=B)O79I?Qyh=34QXPd%@yl z^&WvUgYTX`>1q_qmJ=bhMKpX=!pj)%oCH5_JLKRqr@57U!dqyb$VK%=74# z{o8wk8B{wU*VE?bhC0BkY)Z}TUXWH4HClQqbZEX8&U3&!w z&?ZaMyJGdCTNKFKp;VyCt2f(*o}H;@c$b8AaC`stQb9866zEmr1~MJe+p3+1SVQQp z>mrwTOVMurN#{~}8PNJyMdGmlKJ-#GGjm*d0ek00DL9_w)Tf^|wfQfMqdmapxAmQJ zw!_&auPTl|oQC;96Z=b=NUSktzv;t?iBkINBv6^`uh9=wLRnYI-7Da&Kekz$eVXE5 z0cRqYkwz7ERwE&2?G`RQi8{x;6EnZLvyB;~#MXU2t7=WSC?T|=%owUchLK~pK8Vmv z-?7fn4^-+dJ$PyLYp$2Nu}V62#R~BjYQJPvTR=q5d8o(G?kDdSZhO&nKL>}i6!*W5 z#0u$Mf^I$&*u3PcnvS_Mc{0V-pnY3(-~}W%wcvekdLB%Syr(T{w0yw4DJGE&<_Oj^ zdTnb@=#`K(h(i})YpOaatuRA$*^$1h@{NY*-ua}7IGXVvhl|=N`AeS4yaNupq+pb@; z0Dtf71sw1+s!t8BQJ5c7h}L_rkq_q;Qc?#+2$v8sBQLDR(L}xF0E9S%A9B$6DR0qB zroB5PA9Z%EV(A*|VNRe8&!sWabR)Vyj~F3Z=^;xN0?udyF`GEVoQ^M(kW{pbS!GsM z?r*;|Ka@%Rh8_NPWhks;E3ebziB}a#Y3ID{SVA`+ZxSDp0xAf_JRSQ2rG#&*_hcrp zyZcUkN+Mx2*)HFqTKeK+umXFwr@@vdSsaJALDu44YrH;=h5Y#V+CyK`w0vcf%C5C9 zrS#!Xm{-LL(E<FKM$(-*6REX9ol_@+0-7UmS1(lK%dzJNg9^ zQT<_9dms|#bG?X3)_6PTjxFu{Dwo?)X^UvI(Ca;Fcqz!--tdZD<3;4f`9yJlW5j%a z`{SlSio?T}{`=d&C{*wA{%VEmm8N+3S#8f27a!uX=1U6f<91GJ+7m?)KxTYxe$J%E z%@P&aTeEvrc~d9G4{8M~s)=oaD zeS3$7v$+)QsViwZ7p;v@^6p|^8o=5fB?m55EklSVy;63rqEN^xl5(TgCE)g4#;w3<;mo$3K9t-4P9j3 z*5&pQ8ZUTa*JM@#T4gE{U!3ei8o`|>zzfETNb@}oVwnS&?e7OOR$a4IYI%=sm*|hr zbWhKD+yKn&#k@L1BB@9&sg!shFE60iPuB2ur}!InI4R$6_?%24VIGt0&2Ypx>MTW@ zF_Ud&@pUR~_oktmYdci~Bqf(}NUY?(Q>sr$od$D>So=ZD_ON9#nrM$JsOH^Z zWQj}Hs=K-7H@M?j8nZuIZC zl*T`wBxUCHHOa{2OkF)`wTl@x^O`0Wxk*751Tzpp|qN8~mM#XTw<1{e=Kbx^5ttX3lWQ6Hl+?9zhc6>spDWv-0 z$=dfgoFQ%@5L+6#?`>=mdKPv~&dscnUe6O7^b8+$_b3@xq9ca$28+H5CJhnOpco@hr;yEy9NUc8DPEyg=0tozBslgqHRF@D{qYuYW=;KDS2G+ zEDrCat_xIJu|Hxm=okotMb2IE7keW^Z(l-f4EueTWX^w6gFuW9X zF|PRueOH9k6OuHta_P@#{Ru0XJnbb`bxGLq@^j)Z+W$E|BvnL`#b&^aLv9s<-6E8P zl^^XWS>Um#**;-x|a+Qf<>=7 zR12?&8G8}wyLM`N%vq<>{C)W=pVcXB@|#b(Ejz8@d{WEDQ*{l)9~U~X*n?Ym+IO(c zd2ZxmxPWMvw8r$Gp^_CR*t)ZO0nI%)Tzi4G)b|xQVfzoqQ?}ysy9^s*`3_s^A#z!U zH0^>EzY^7WxtvV0Ng64#>&Ih2{3sELhN!QE#q3r8?6>IM4lNOm&hA){54B6Xb)BC+UZ9~TYu2?0 zO2`qiT$Aaz@#9mF(JIsbn(L|jaxX2@PayzQSUgn;Fy~YA@_zmV1^uulvr(S?J_#cE z%KQ8f&I?2Bu8qoM68gO2NYihBdAy1Ff>2fB%TRuQenD33ap~QdEeepBN}-Nn<*xnS zT;(?bsEDpU(5Y#kFmD#wx9Rh#I;#|IuhJLr5Qb@nRm1gqx|xJ9QCb&l_~DOPeo;StH^ z?d4L}2-aPr`J}u2mqnF!jAWq%bLjCWm7Trj$UAAKMxl`yDYn%8Ug%v?qIZ0nxl?D! zao1v@)%9hCXshB=QPf!FMV!2w$YoK`TUf0>mEwFu9+OIMg7YpjCv`_X_Tn(+)teQ4 zmLyS@lb0ko(RcO0ySVwUih4&ttyD0_Jhl-!=z!7!$)zTl;qdc!@i&op=(8N;>?P{(WCjCW&Nj8=i#6;V&Yl1;iaEgo~uptGHS(U8L{dIbn*^ zlqH8>miUgmXiFb%H?O6+6b!2@h^j9Btu&V^kX&cDpVe$er zYv`4dYQ&eUkUWseOICc22C#?EE?VQjUr~HWntqR!gH|nG&md%^jHF0(Zh#pnZJ@Fh z{!A>8r^q6F5W<}UwmOO`g>lH-!C`BqG-;X!?<}u%{=pEu$^!SL^-7NbbS}NdE&K%y ze}{i&piSYxuVCB)D*rj)^j*S{*TD}-7;UmVG%ZGtx$gU6+zlxZS+XSuG;c{ZRbI5? z8ZG$H&862x9QQ*U_36o^Vtd==tth7&h{J*XpntJQ=JoWj?f_>b(}N@azQ(w3*F-k> zdBy4_I`m?A6u@8_K?+eYah~*cD}HyZdH2mXU(;W_S@C;LRT{L4e0sz0Zxj8e9N0sK z1xXvhv`Z{QMBxAm>Kde89B1Z}a&mLGw|=r)_j42tCB)8n+@quN>Biu)a)4tkDdF!9 z01I_al=nPF`< zH&mjNd7LuZh4an|okq!Ilwd<@@b|10{i&nvi0}wzdyXac0!P#2HXV!N0DMuVtvfAwvj+US-Uqq)cA>X9ctcKzbem?ykbvxsEkE^;}lsT?0_ z?CXJyz!wf_SE_)tN#bS{|MPg!f|ckHIkJC=PPu~I6H?K-W|I~72LWoWC6S6^FMa@K z9-(vHkf3uZ$ZZeaqKk8X=VyBON?NDk9E`y=A_pQfAPX_Fo+%8oWR3BEW;pnS0>qbo z%&SKoP7@!JCKdHyG(_mxawWEt5x)&iQ&ZHmZ6Z()r&hQ%RC4Ys2~&#SGMYus`83@3 zd+Uy01|>RXjl;wB%{!Z<$gU+it(%+tBMM5Pl5L5Y!omCxIFqXyM;}5vYBRbYWMR{U z;{3HZpv)nxS_& zV$ev!hw@8|Q!`!Dxuag3ZwvfNwOVV6dL;!yA##~l_VX!|OmJ90bKboos?^@55j}7K zR5kzWItYhhiR>W6+a}!cQqz?=)q^NMjk1(I_d?qs(k55hPNFtn)eKF5*Z4I)A@TpX z8%=l}Dc4bESTqf6-%&!Zmq#zbDCKoqlwr}UhUNbj;1hA@~CoHt75)*O&7U z$+|-xa~t^tn%(_6XsUImLyZhKD3wo13 z82*_NxG_p6@j~x?kk=Ri$bOPuclqZL6y4-&zt0@2h9c@#SharZ1`r;i^UFe z;0ChWAqa5e6|n5>1TW|%HRctVS1xXUC!7ceRZtsOAoD<2aE(Am&1aG1^t}w|!l~C} z$q?5nqUV!h_6=z`JAsC>IxYZSr?o`1(X$#nuj;EM93JMlf=63|wTIC$GUX0NPoX82 zBT`AHK%|6^lEq7OZXmVLn8OW~J0Z~Fe<@}N2IO|$a>t(m8`>(doDOMnFh-6OicZJ+ z{5$^m{298EZ#&vQmH>|A93=zxi@%Tk{S30?v97|4@G-d43d7G}A@?1Y&_w0i`e@I2@OC372 z>C~BPqy@ba?IXDB`>x9_5aMB$rhZH|?!lgl^JfgWy$&Cu47wV{NVXl%k8!3LF*K4p zQjAzD6@*cAT3Tk{Z1p0c$w8@K_|>gW$^N?1>))Dwejbtkpe~s*&=~OUG-{~MU`Ha? z>nt&aOHA&1J#EyHm;0QRUk)wm@wj%!cXNgEcOXoY_(fK;u79{sS6;Mo{$7OtO!(9X zZbq{l=FCfj<;D%UUUWr?(1UiUlgi%c^aHf#Fyt~Y(JMX3q|FIwK16%hQ@4fiB;fmU z1tYyCGN=4Ncb*H|!Vh!VKBz0<+|vULeRdt#uB2{55${$*9e)tg`cF6C zJy?`r^AhQ%fuAZyU#Med&yPAyJ9V^XX@Oh-XS+_0I5YRWPKyMpiX2`lfb!M6#Uv z&+uIPbK%(^AIDaYR){)n5QJ`#D?Z3JQOb$RC1hA6J;&vzVZAv;Z?LIyBsjQxHWtxx zb$%z_q(j+6N|8)XhcAP{IzGElCudhu;J532?o9F3xQFM;A~jLJShL8_zG%(xm3bqa zz-iu#!q!J}9O_UD-k) z8c~Ga&#;lw3;6K)h&M*hhvjG`-3L**6%LeAR)s0jA014p@zegEgd?U)cP8ef@x!$t z*`gCpdxs`wVyZ6m8l;RAwUidgdmSgO4r!WFys+{({Nd^Bd?N4W`ys8*r+ApV*(k?f z_7b$Q?t>}L&W7mpKhm8Jz`@DvT=}}?e7TUUhS@SA!v>ofpUcus+Af}6=7!gfm=$sU z`9rzWd_zeBGw|)eJN3?vD8iy%b{Mn6`YqnxC%CCW?~f<#v8OdfOaG)Rw*44crMIj~ zW4Hr4IxZLnc>6@%{wz+r&#Ew{nBrtbh>lBes$cQ}ee`i?jC7tx(Q5rUiXw?|l04Wd z@|Py5W~b`+z_mpd-B**XK2qd%;7Z@UaBND!G$Hs38y`dZwqb(6Qh9SJwLxqj3?p+^ z)}xrl(W&#dL=&03gcfPcn`<32caTzCwdO_L25}J@qai0O;)=7du#=33*CIVz=Xl|x z{*{}!%%$ieo5$Zrrps(CM8du$y{$RrtZdrrYohnjLBKE|DmO2H^-05NzGZtCgH)($ zeW%goL&mAztxC%}Sh0vsMaN}}l5=x-EV0b(0D)*B2m;g_$=f1F^T9QV3lBdWWOq;P zGR_vewTs2Y?t2%~>fd*^DE`;ZTiu}%XDx2En@Iv(kkl( z?;5Z5jL+hlB<4&{$BY}cMJzq1-M4Um+fop7r$UZmP}9*(ZXs^GHi3gj^7E;kel{Y0}u)d>C_AEj?CNo z8-6V%nVpdveP(j%HXYqGG^B}nW7TmZpET0nkX^aazo>zzjCrNM z!{L%}*#72}d$*e=77O=3^Ru83#7knU4wAU6iFaGhQMQtz-kZkx_?$`D&@?>c5yh*R za=XdpL(}t*Ta}b>DkpB;R8uQ;+fFVss1H1$;Yea8fs^q z)^-#O=;uq4E6p={YmfW#L+dDQc|lwIABOGcUzePfA^pR)!r9Jc9KG<%?S;2wL!?(` zudT}3nH~6O=kQ!pLza<4G*RzZady9+cmT=Fx*WD zMf;$g8l3+B?U<+I--;)p2gi7r+@p5zW! z#|`bk6WI&h%^qxXm5j~==22Yx6o2Zq@_>d7TkzT5AMYn54TvMFM=|Z8H!P~B$9L*1 ziHJEmSBj?}rtKuLwhT_$NxA7KhJ>P7Tsl1s>!`hIWQ$AhVVkq@!OVmXXWux|-YKFE zWn0DDev6A!42K82-FuYO^?LDQ8t$37+VRYogQX!iogbout$J_m%AjpxtXlcplSm$D zA$?{a&3QM(zB{!m_)+=D4=+NCJm!q~llGi!(ZrHx0TC~q^aY%0tJw zVoL)xTZ)i)`X9mt=w*y{Ip>-)luby8ZEwxHq$KGm$FHjbd@VG`G+Wy)MNwAWhT4I>h=UHh4rtD%AuWjGMP`e<$Q=K720t z<4{=yhhxk2xk@?S%r{P?8b{G9?jnWumlnJ)Z~CTVH_MRD;lM7$l2VZEz0v>8ki(xM z*?b){VirT8v8V21xV3crFNrx4+%m`pLKO^Pz8N8LPqsA~AD2D02Lwe8aM| zI6FJn)?#PUWj9|K$3p3NgU4kR?IowsFBHdgY+|BTw z1+|&oW1fpV;!Q@qZ)1`hwJ@mAbE#-{a;~L5t7V7{Cd)-f>_a@7Ffz_i`S>zlKEWr9 z9g*R@LCznNzk(!_kI*i&n+Rzz0njExl%v9yJX~CsgO*kic(D&kJkW9 zFR~cgvUr0)ENB?{#(2$JUcY>p5mHBTnwKwhd-~6J=hT7j?t`I!RbXEJqgm+Yw;(Xc zj+#<%_gvIutO_6*Id1wlb#9@JxH(|r*45715nV`1@A6t4xwWW17+lVM9w={Edyt#> z;I%^AeB%^qC&rZ0F||vxT#s^nkbRUOPZI5IZvk&>vnTfg7dVEyMY3VP4SP3Su=XsK zPcPHRoNN#u5){e4^l@z4f@sCpi)a+YVyQ@!5FpZQYq+ip!?HJBT2%5V|}7B z>Osq}wi8S}MK!-Vk4%#42LxW8(aze%Ronj4fZO?e zG?{;I)pWnd8J?-zB1p*VA&+QYiUgqr+xuWZJ-d>5=KdrKJj^XViYh1fE~RLtr4BV! zIqjKNTu?1C%Csv~%-tR_%4nHk5OgQ6g*A-ilL~zZJ_#A0kI;ZlU4r~q$JQZ2!?L=C zyI7MZ#*|O?NZ$Qx&fem7yV4K0hF}E}RH@?(qD0};?sA>RgCHpvX3X&1A3?_v>zCsh zod#z}zD?(}#K0%Y(N?_a)sWZ&Udkqd!(mz?+C8e1 z!Yw`hZTz9x;^5O^!IqQvEuD^o9W!~*+5H&T&6RRSGvSP8XEEs4ZXg)_m7zY!-ixeS z6_*A)37>8ldpO2ed}34bEe6Et_%Dge-!5a%?hrPmWX3gaDj}6@An1lqy@3El9h6X^=YsF&?jgJ}JuTJldeS3l zK&GPPmp)8b{5T{Q1(iW=cIbv~J`}5cXz|%HOw|yB=NnjU9P5Y2AN9S;y8nddaforR zqlL5h@SCNpSZe}St+ylLO!yYWwc*#FJl%!uADyNZAGAM`dlc8Oc1Q4|Na55$yFxd# z0ZjZkWVs_F`%X7kev#IImLf*4{ilBYo1w+CJg%t?BTqDn99u8|BdLLdG*fZSFGKQP zi!zzkkzR{1Q&X;xy7wQ6hX?Jl{9O!EK(Hl6Evx@xfZ(-Sbn_h%VxzJ$2ZQr+g|fk{ z#K0?Fb{J6#yVW+>S^s*=93y#r3XIJC8ZFy)>uK`)NZ#&TmX#vic&`$=1Psy%C3RiO z9Kv4;5wy1e@VLP&|xZO|UuFvgPgX~JA6cDxhH6P7@XXkWjbSGtZm>30D9c-v9 zW>PcMzMRADLyZW)tDs(%8&ev@*`@?yS%XI;?{ObSvnB5E^f2J-`6Iaxjs3bjQ}^+t z3m@`)FHKGH#KA>-A9O%Z=6(T#_$qq})#F3T9q2yz*WfrqESk5QhY^zKz6CqvbWM2v9 z-nIX{ij_l&Tc}J!owKqMX5+aBtPMl^y43<4i*hZW1@@Jgn(?!^?&JW%KMSEX{}9RM zx!b&@O&VY_AVrJpKFE4Cw05S>BW1OE3T5oQ2-7?wI{&y5LrV8KM)u+!)57-ahF{}a z5JaA|A1xOhbKLR&koD$)P`BUT_(-KixKmLKDpFa(WE(`cEMrTHJxgKigo*6hNXas? zjD1T8WnU&sH(AFr))-4g){L^=h<@j-&*%I5KF{;d{l{I+`?}6`me+Zmb6saTMNW8l5J=0rW|J?(}wa#6joQvL2cRw)%|h^jmI3oi*;U`4}bwpTNHJ5zt^hO4P5 zs1`yj%;VdO4BesmbE_F|H<;V$~svf^k*G^3HL=f&nLC|mH+MvCIl#8m6{5|SlIF^q86?R=NdfT)x`=C<^j33 ze#EG+)RON1Jd*~yX}j;sQX;Ua!R%Qj$Dycuc!Qy`3;e>IJuV?#dG`sofUTF)sLP`Z z^i6gZW9anLQ)*1p&E7je_h5Dt=b;r&l{@Mjb?Pn# zrN+kk%FbuCL&z@hRL?Z`gT0$_(m;CXgo57|99Gy&?S9c(Slu*3WkA`IGo`CGHAnnR z32_ttCbE|{S%bk3FyPD7yO&_8kYZ~Cu*m9SRi9D)CUYi}H}FG_XF}yEtXk?IUaHrj z{lW_tw$FcgcGi%;#tr+WWC~>fvIKM$Gn~!cbSs-5Kw%Ta{OBl z$zz=8-9)lt&M?ia{TU1D$k*O!)CUlgNrp1&P&#h}GdQZT;Vu1dBRZxh=zntxsxj@0 zsL9f~vXyMc3vm@kWf;=sRU6?>I^9ggdl(a*#+Y)VQ%?hKIf70X=(P-e<{f(2+J2HD zF77Hs_lt5^rQM!m1@8s3m3g|`OG>{vfAf36Gb==8z~0RzwTH4W@Rw+Ia}96ltyrAh z7(bnBe_Is@l(P+O>5RL*Z&kW@D3PX_Ca!?)m0yL$T&&=6)A~0}6gpOI(C4})k zayTP7eS^yIkh$__irv9FlM#hEk1N3&KGC(3J>0k^wO|y{ORM|faCI;FCsTe++Uy2o znnAvKdzV|z0V1y+09G&_O24$`hKW)O( z>i}bVy`a-%C2y- zUsg~R2N(5&dF8_ga7VlTEK0i}ZM(z#7%b{|a8r}J3#=&d2;5xso<0cYW#v%A;B?=W z5XfgD;CBuNRqX<=fuMoEsE!4A4;kXtqE;=i&@lS+kV11XxyoF7YaWxsb7Wbx9&MjE9xU`g~)3!dg=^r&05cATaglpf{L zJ+i@&y(5Y*@NWm)zExe?en$qxE^bjSWbTsw$-Q|a=(Qa9CD}2`B=9PKS65At;5go0 z?X&R8$!>~c22>R>qs&@2(WTWI^5y$>Tgl;m(>(ushQDE{j2A*!P$<9a0}V_W?kG+c zjGRXI!)a~?`kMcDS0;S+@EkjVPFLx*)ZzqBXaQb3d)P05U)26v{augW)iPM6;2KbyCW$=nYI&%o zEO(WI%7=d8w(yoXwESZEBWR-LG?Nm7fzewl+devkz_1ZWOHs&?6+*fUI29gh}?AXd9<8bjkWpZN<*~y~#*r*k~P*));~m%en~$Uj*`a zpMqcpN}44|%jLhmQt4_!*q17>%%}vuOx82tdUwGzH)oGHY`DavJA-=iz+E_`Fzfr_ zdCyqbCU#@E2q|@xW9nEDtQu24c{6^RO{DFIkvnRo;&`tmGmml=sw z^i|cVx@pRHPMmuN_}LHBD8{;Dv8afvWhYJJW9ycMpR=&>(fU2rAHpXQ>>yq6X89$l z9q2?rb$$>SE^C-m7j;!qG^;j6apmBe+zLrXgr{~DaOg}PZ%*)EIJUysqomN73b~Pm zuwocXAlvZFm^rCzldOoXL-{Jg-;=1GWU#ok(x1bV#8_uxXO?YrF_kvk_SO>Mxl*sd( zg`woJKS~wRE9AqiCH5%|y1G$30EY{wprDdKejBx{1kneus1|iktN2(6KsqL{p!2ih zdqtH0ViddQlg`Mu8$P-51f1~K^x=Pb4A$#4NupO6>#n%w?X@@+177o8B~>%X z!4D%rd%JcTC6ku{V9VB_nu|65Cq%_C5x{$q%=Hl&vL7iyGW=ek#Vo^9^tlXWLZEkO z4s5hbLgXZi#UdxFIb_Tyz`9bYupZ%$r{z4yEb%6Kt1XU=Uk;<#t?`f zS$$Jw0k2o~Z_G*I6p#O0NWQzK-Sg{BNyxz()S0i45$}5E$ETsHhzdpyivtep{JQ&+ z?p!+K5!-yu1LvQS7cj+h0$Y~}F{s%1!A+(TF6|&aLb*nj2=8}i%Y-2Ca zoJPZ>Hp*)53134PQ?fE}h{&_>!T!CnU9$Aap0T60#zEr^%Ho;NPf=OxZd?7E_?kB` zExmTs{P-m~)_*S(0(a#{5j+S_!zhx)mGl}RtU8mv=7!(5gaoLg3FADkFp*LZg7`lV zF&xsq$^nLSS)tLwTJ94(?~ok>tW1}9Yq17fqi1L)1V2<97L#ytMg}LS>7A1q)%%mf! z{?bOg-h*vPFtTGp9}Yy&Yx*ek2!b;R(WM&|L}>s1H@@Ru zoZXSc3VTJNk)RtcZV3JyCB{4`TExgC457;6&TM{MXVkJWPOu?srnh^;H3Z7EdvTt) zjjBvZdWZh&8=#(I_nw1*LrCXVLx?q9p3f1VY#F-HO-?bvmFW zU*SD!ktaFh+zw5L;Ypce(aYMn-*$L&OmTUfQP$C2sNE99vbp-(9TQAQftt#yq`$1F z5oqw=UI0eaMLSR!7AJR4XK$xCvP*1FHH8}%5+B=Xx3 z7HXPSib(JHF_4VaAIaQ`Q__FG`hZ8G*plK{3Ihc<-e*QVI=#~8h_lJDU;f=dsjAc{R%ZRrIx#H5CUuHX(b#WPo)r22N*#1pk3z8-1FM0m>dT z)RT>O5#MCR#rB#fV?Ttq!w}w3%#0joBKmwW2?$~Nk)k72{PvA;;g5FWcCDJlt^>Mp`k1Y?9Zo}_=GkXQ1LHkRLis;37NK)IC z)>mcXe1QZaGeTI*{Pmgv^a`go=wZYkJ^XZvi{yIk#6CP8ngXN+MlQ@~xr)k~X4yX( zxuCJ(i6J*qV)od~p4NxZex_#+YtBYqb8n*BrDev5<69_{b_*f&mlYxKp%Wm9G_3^^ zV^O}XcbzQ0ziDjkK=g0y`y3v&&dlYp8TH=rRS>cIEyF#i)G3N%jA0I2rRz18eRuGh zl5? zcUp`FgB?QO=yUw}Asv6!@tYn--#Nkotd3IShr6@l@t|jXu3uPDf!pJs4-TS_Lb1B> zJTGyX5vfb!CI~q5=;hNGA6D9C5m8i31u^zY{Z1uhcW7jHBrbs0zyCs~S||dLNA?#f z!J@6v{f)|?n(Lwu_N&%LcV9b1J-N$j!!eDg98#OhkXFM7P z8;rQ(hvQbIhTTp4Tc0TY;kQjuQ7@hP+Vc@Lb`^d6{XQ$oRwHZ4UZ3u8H7k0vD9M25 zZi3tBKJPqI(h>BX$Hh0d9e%_^;*;HEyK9B>Q9}egxwF-@XTQ~29w@?Iyi^b6(y195 zF-B$TL8kr6`|xaIqczi&^M^_AV0V(g5xzk}ax#*23uOg;6-qZG(5Dxkff$n@PQ7$H z3-lkZO7ss1R7D&|TbV``&^&Dakt6^Ul+y3QYJsBhe=c@OU`&K({BgB{9qgOa`*0}{ z%cpT_Og0Q%0fX`7a^Rmtz*T-ne3V9(rB%?wp{SY4_T^;R-csVKO7OnBHKZrMN823> zGBN4yC61+J8`DI-Uy}W4T9O$Kq+}Y%`m5{Dqaeuy4m4_Cql#>*3f?0(b3qd;E_0x< zT(|qzG#k2gC~t3;kjc`jBA}H2{lSSprrc5vo1(6*d_>?aCG-^-V>M;J&vFbBeB?LV4BMgCcIe8O$E*`)^{arRC<*p!l0I~4p zv4C(E)Nadf+^Ku;tCw*B=UUR?DY_GSi_b6_E&Xw7qW{%9u&VdcSfvj|3gx7r8J4f~ zHJmZRNc_Zm-0EeOF5W}bdA_lZZo#7bQDDx^tHW9OO|Ne6DBd{ztW{I1tWrJY`wil@ z7<53fAjXTw14Nn!cVF@p{5N35)N`b;$l^l%fkL0_r zF+rLrw7%wZcGlGg5F$SyM37J=sH+7YpSQgKVgp4Vw=pu!jSB3yau~)tP)>7fFf@nx zqXM(6u&Vg8*$RWmIVMntGZW>?-*-^b_2sJ1PEUCV%za){CtWfq>I)UM_Ho8kyF6?B zqs*?vopI7? zQ~SiOc6*ur)tX2&a9 z)co-2o=yBsdW~6kJYEAfzbOahw!KVEAU_k7@GH;_rzwLM`(t~qpZDCo#u6ui zk<)<-WlQi&q>AYW|09?9^MQpRZ%)W_=&)5tcr?AL&t5Xw?1r42<{FN|=qIz@@gUyN5dBk0qs9fNoq|NGPm;K&Oxeosp1S~&rerl<(XdXHY5F(kTDA-j>*9l*;BaN#4uOoz_KN2a@xg!=HAB;PO zk^8&*HDvIpzd`-n5}zVs{x^}SD^#ng+1I&DkmjB+Wp@j%+UN}1L=Pb&jIW7Ga?SIT4%B`sQ z$Qe%3`1q>T8ysjJdm*GufyarW4(m3FV~cCygaoo8WU4m*k8WIJ`D}G8P7EWb-3_2D zaIaYAK`rgFMyIkyc{gAkkidy7J+EOb*-`Qwh~*aDKP7Vo2&G%2{nAp!H~34due)T1 zJ{lQsSw6Zw3f`)_X?h;23P8Mri;as{fN0dHQFRvZ zMIiVRJou8ye=8J<5gN~v5Xe)7lTC@0mfSkj4)D`GVY^0w=$h(t>i>v}<=!`TTj>nJ#p*F&!-BDpC4?-$#b`w7{D@Eg={JTJfz<~^4Yr{TAAc)ijR#YV&qyV=bZ-NwuG z5MVI>2URZf(oA-7m+!KaD8>kgyy#%K7z$Z@&blHZ_T8b~?&qZ(Qj2)|#Nc02I^?eEhRkB+jF}7ocYY78~jGWKR&` zngj?Kb@6nM_~P%UEV^R)^m>MJ+|UX1sk+vnnogBNekqr#AX;`>|6hn{GaOJ1>)qi4 z+(9G}qz=ZCx+iZ(hbNt~8s!)NtHTIdX=wGEZz0eN0n(&YZu&z73p_|NsXeJfI{Wt~ z=0t0cv<$(%(ZEfP^9=SuyBQys1rsj*E>vY)X zjkp2zaR-MjU~zyt365pCX3K;`Av=9WqYc}83Ux2)GyWrC4tNA|2Q)w$=c5egByh3x z0hsgqq;oUVpwPg&!HI@yNlNy7)))XLV_LUtsTBw57}5E8$6prTF8*^x-A51q}aza^61asSXk*xFQsel;f4Ml&TW8YzP@(9X@c;u7?n)K*=HW&OYX zUuT-G3{>apH{Qi<4*Qu`+mCDE^aGN7nk{c{$rNpS6Sm?8sAKuN!G+$4`uXcNDYw%- z_2Tid&Sl2CXAzGRVORHn>yxns-aXo3ZZQQ?hNpVO4j}Fezu{N^_6#5t{Z1o^L=cP* zk~Tg|d3N4($U5KSenr6e*OWZ<1|Yei#AEOL!dpV^;?Eb8w{s4e6AA z&K@>YAkj+xU+lHR9N$nDzHcG#ghx&Lh;P-a+D3c||4An=5SMMY?mr`;MP~SM7a|GI z7A4oz>H%yVso3Pf(BQNQF)Rv*8-aQo=yTo3TKpgnt~jHZFky>x$WT6VhNi3An4tLD zK031xvg=hW#^*J80sT9iXChs{yTPRGMG(8Pf+{1NXx?$eK1#`J>S7V1mGG2JW(^Ra@R51g2$|7uCvQ)dHiVel2liQ_NO%fa472P`c?a;GcQCBtagw3*}9RB+B~^)Dv+VDxS3?BhR^@-E<2=61mp2J7`)S zwf7|L{6TkqGzU;G`~T>K-P!4ND#HQ7-;h4f6V@Ds>U9hnqi<9(AL80lqWp_0(d4WCcM6Jb)P5ogQHw!(Qs1t?)_@1H=~SRFU$kZq^nRgl7p@fkZH#FPvwH>0U1yRgVs zM0;}inIP5EfCS}F+?0wY+gm>#hsY`1~lOSgQCV6xxmfoPe+~=jqM07SaVZ~c6{)bzBfrRP3sh6lGTySn)J3T{C7XiPt`;*AbsrZzf zALPAR%_Au{S1L*EZyXxh;G3-tG zI}}Pi*FZo9YWPjgcRj!m}Z;F_y2@m^dpO->l-iJnz~LX%jtwzyD-}mvXxuf)tsb*j;O6 z_$~z*J=BekJlIhKXrer~(7)Fve()#UWk?m&aC8o{P9j5xH|*r4(_9@RUftUP>Q%U9 zM#R6JC^oBf_BE=@UG8y%&IA;z8q?VaUYSs5FQ+vBq=PqM8dz_D&!q@%~jtRQ%6sK&AOyN}c4714jYdJ#&Xq1@b81#tPk}}(X^3zV^ zMXaSOE>yax@Z;)4q9CxlPf_3TeeY=kPQcz4LW?KhygkR63TTjo-UnOUAzPR`A&+?I z_n$hn5(a#rty;CIoLlRAW4^AJp&at68xf>-L{dPAfD+d&=SPL$EKl?1kdEpBk`=6q{pyCuB_B0^e0 z)CBdUn8=&Cp$B{6+>b#qa);}yFIG+LN^!gP31N2!+MRQvy(#z(K-ghtYL%DylX4) zH4+T83>v+$nurhgrr#rmg|PfJ0cv`n{5%$w^<5wFZOB(bgS5OI9xHM0A4g-S1IC1Hd6h0wy#%CodGL|xLl3>s!f#@yDcX$ zau|II$ffM2qqDAGBx)-F6k*FM>C%`W*bu*P7&0qP(kv{WFx{FwhhVK%K%-%jhzu8AvUG z^CJz0-*i%QkCCZqm`DWFl3H|VX50S1vN-XT&Wam4mXx8^L0n#kruHSkb#XEq3>oRX zNH-7a0MTouW-V?|mU{9OX>dWkN*JpqR@P6CAiJ^1B4r+*QMS zyvPiQU6v+xur(32TjYJM5eUe6!qfBHiQc3OW`ahAMCT{i2U)*N!7RbjJ?BpLQS_f% zZk|l%6~k^Y970HCtCcYJ1aRx|r)M1SQi@phSc)Sl^KCZPx%vBLq0hCj&?{VnhuSD21NXl$j7TH=2T!%*tcEPF$i z_C;vo^FNKPQ?hoLs5y;A{VkShlBSb-ZcTj%T9Jcea!&LroW`o}#%%iGUL&;dzg&FN zOrg*H>n?&OM^IwHluXj?1vZYPEaA&;b#D>kb$2)9QBpP^baKlhQlr z&$~@{8X|^y-XZpP*~yWkcn?RyKE8eT9%8BcJjaA)pczI%@@ls<<@-UB{^-Txa`h@K z)Lj5AoPY~tj?0W^9su*zPy@9Drm$GTZ{3dStX9CI%i#cVY-SiZ7+oU^*rl!4`ewZ& zPK;zgk2EHoM-Y0mzz zG}6C~h@2!aW11`40S4Rgl%;nM#>5`KAYSb9#`xrvJF(*kqdCZIy0TMAva&W!_z_z>te>^`Z2qXSMNVsWk~}!9-ELfH5H>Ch=pr z-bu?VYu&k3$IGDxg;h(+hA3N~%O6#IrCX4H3u;FLO#_b%or=s}P2_o(QUW3g4}#X? zore0SQ6g|7-3tl7S6zWAqd)DSVq7AZ6)`0kWIEfmJAD_M!s+uGox?_OyMoUsGT!Eps7ZFno6Y!qu6c zXHMhd#YXog6~&4JB9>E#HRom&PIMh`8AR|Y^o9#|L@#^d``0znR$8yW;omas8+l)C zCi2!<-x@tEr`^|Teq6+$!0D+G&Y(9$Sy%9ct~mkvAevC^(aU(frh(tL@#bR1?(L2q zcMBf%PVE+=d^gs6V-5E<>`BWHQdY0Py)r6moD`Dh;=uPPv7+DDljq;Y5Aw~1kdiWH z_m(Z^iN-?{s^NNBf>xxX87RMS4M*pl#1b>l^(!A*u zur7*o#)qqWD|MF(C`{A8JaSzTt1HaiTV^Iab061vqZEBHXbJ~C)(}WB0$atT>iQ8E zmnEh?WwR+w??*vi91Hci$!|kNF~x8pY;~Fs?Q;*JW15cq3oJF%p~3!8kNDN@k1Lib zr2N?BrIV=UY(Os=??jtXd(ac&`kd{(?!b=$g7kz=>E_cg|x)Q z@L<)t2k{=onQunm(tK*xMVM zj+s_uP9h%p>{D_;3#EWqB8=6%tkoCNa6^zCUk23C#?QAa190meH$aMo~ddEegg%$+KyJnxY=QzcFha?@T!sjdHBO&HYC6Bk)l=d-J+n2u>t#R)=%FpYh#sTA*#lIeo;*6Q~59Fyt~ zFnsG@aix!~?cmLP(R&Yb*YwYA8vqIbi5|xIl#&;ovh);<-H=Rtl9Ozq=cA9H(wAB$wRmd*K2u zhnUO5Mt-2xehFs)OTV2Dr*@6*;kn_gpsbL5arWn3-Ej6cf0%I;c)i-RdkYd`EyG9? zdKuhepEvKxHMs0+c)Dy%+c7c|?unVJd%H~Z>u5udw!l_WZgjr{H03EMC&j&AzIR8S zUPDEi(4dZ+syxS@S9291n`YMCaR#|nA6TQ@AFDF&N9(d4bke)W{w?`0^R?^~!toii z>&Z71kM)fmY_Dt?kw(@ya?-9i^CR{0VPj~U306@^#?HD1TnlI4AqjU?ZZhIm1#lc! zY#78&<~u7qN*_5LL|~0OIU_;@-hQoWbA|dKD`Qk*R)lyd&6lz2dLLgyPd2-48x$6f zy_q=`4xIfERG(mNu50KO?Yf0$9oHjHH$+^`tj_J&>bgO%8KV?LFMpX9ZgF&5lgm)H zAoPyw>xNri3y;cRU_~ia!dZ4mqqfTR`})asR(_|!d5T0V%VIVSFk}j4?>%?8b8)eF ziQmt>Te4L)RY*J&dKq>bf1x`Fj6ehJbeAH;V#q5|sH~}a;C(e;2+I_2CJ|iOnIXu$}KjO}{ z@VB4%d9N(9k~rCGK7}*pn=x7$P$7op^7eliE8d-u!MMx@_uQ+lcN^ti>IuBQb&n7z z3guLkg*VhrNPfD^?YAp7jS%F5U2xAxQH3Y>Ey! zUW(>M(zGzDaoGZd6@`?JUJ%2=IwZv4I`U0LRqIB1Xea{}I0*s)j=%kTRIWY`lSYYl z_Xzc5)sAl3Z63r4eXQdSvPsE|rq?K9_nmQy&g_JIe>j7_eHT`704iX>nck>)#Dfat zE_exD^t%Jafl7j#dV8NYif9xgLH(LwR~hRY<7?b!sju11*`Sn(syoux#9AwIZq9Xg z&>V)oZ3{ovB9i{Qr_3Rp(ZUVLGO=0l{fJ|5 z=7p`_^CJPxuWXWde!MB-zG5}_thCCZTy-tDpsJvX-rr~&d$XwD*|a6=uoyGP?Xm!Q z8;UKSNLuq7UAv@siC5-5W}jQgLgIq#n)}F_0(SRQr#$`;I?Rh=2QN)oXqHq9wlt|t zp^~YvfjVz{AO04o>IRa2rRK9McTf^7u9j9ywv;3P%dcR*ka|)TUN%ILJ)DRc8On9C zGNc*f`SFZL?}Sobuq&^%YPY=z%@bUTyLX#s=^_l_dS|+AL?lDYMaOKIp|9oC_I&~> zE~Mv9XGfxi;;rKb4n?wI0_zjgY(oEdqfp;oSL+wo zzQd6Pl5UmzJ}qBChWgM;wv34hYEywWclla^Rf8?0Qh3l7jDBzC)2Jsup_k$$EqL5j zkyTsAu#Q~N?uMVoY|Kuh@=6uC(I}6Z`&m7AWaZo+Dh#Yn@|QzdpG+_(UVXt*2+tCt z>IzJgCcaQler666th`ex{=EIi3w~Dk!`nwvq=UUExf0>BLh%0Dr@x&RG!`E~uL!0} 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 b`@l>ZV^FIDCIwU@2&Ad1tJ0uk6ZwAtC#&|a diff --git a/assets/slackreport.json b/assets/slackreport.json new file mode 100644 index 00000000..ce37eacb --- /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/metatdenovo v${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 index 11b15572..4a758fe0 100755 --- a/bin/check_samplesheet.py +++ b/bin/check_samplesheet.py @@ -158,9 +158,6 @@ def sniff_format(handle): peek = read_head(handle) handle.seek(0) sniffer = csv.Sniffer() - if not sniffer.has_header(peek): - logger.critical("The given sample sheet does not appear to contain a header.") - sys.exit(1) dialect = sniffer.sniff(peek) return dialect diff --git a/conf/base.config b/conf/base.config index e3eadee8..d5bf715f 100644 --- a/conf/base.config +++ b/conf/base.config @@ -15,7 +15,7 @@ process { 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' } + errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } maxRetries = 1 maxErrors = '-1' diff --git a/conf/igenomes.config b/conf/igenomes.config index 7a1b3ac6..3f114377 100644 --- a/conf/igenomes.config +++ b/conf/igenomes.config @@ -36,6 +36,14 @@ params { 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/" diff --git a/docs/usage.md b/docs/usage.md index b93322ae..59eb55f6 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -57,7 +57,7 @@ An [example samplesheet](../assets/samplesheet.csv) has been provided with the p The typical command for running the pipeline is as follows: ```bash -nextflow run nf-core/metatdenovo --input samplesheet.csv --outdir --genome GRCh37 -profile docker +nextflow run nf-core/metatdenovo --input ./samplesheet.csv --outdir ./results --genome GRCh37 -profile docker ``` This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. @@ -71,6 +71,29 @@ work # Directory containing the nextflow working files # Other nextflow hidden files, eg. history of pipeline runs and old logs. ``` +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. + +Pipeline settings can be provided in a `yaml` or `json` file via `-params-file `. + +> ⚠️ 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). + +The above pipeline run specified with a params file in yaml format: + +```bash +nextflow run nf-core/metatdenovo -profile docker -params-file params.yaml +``` + +with `params.yaml` containing: + +```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). + ### Updating 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: @@ -83,9 +106,13 @@ nextflow pull nf-core/metatdenovo 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. -First, go to the [nf-core/metatdenovo releases page](https://github.com/nf-core/metatdenovo/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`. +First, go to the [nf-core/metatdenovo releases page](https://github.com/nf-core/metatdenovo/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. + +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. -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. +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 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. ## Core Nextflow arguments @@ -95,7 +122,7 @@ This version number will be logged in reports when you run the pipeline, so that 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, Podman, Shifter, Charliecloud, Conda) - see below. When using Biocontainers, most of these software packaging methods pull Docker containers from quay.io e.g [FastQC](https://quay.io/repository/biocontainers/fastqc) except for Singularity which directly downloads Singularity images via https hosted by the [Galaxy project](https://depot.galaxyproject.org/singularity/) and Conda which downloads and installs software locally from [Bioconda](https://bioconda.github.io/). +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. > We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, however when this is not possible, Conda is also supported. @@ -104,8 +131,11 @@ The pipeline also dynamically loads configurations from [https://github.com/nf-c 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. +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` @@ -116,11 +146,10 @@ If `-profile` is not specified, the pipeline will run locally and expect all sof - 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 or Charliecloud. -- `test` - - A profile with a complete configuration for automated testing - - Includes links to test data so needs no other parameters + - 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. ### `-resume` @@ -138,96 +167,19 @@ Specify the path to a specific config file (this is a core Nextflow command). Se 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. -For example, if the nf-core/rnaseq pipeline is failing after multiple re-submissions of the `STAR_ALIGN` process due to an exit code of `137` this would indicate that there is an out of memory issue: - -```console -[62/149eb0] NOTE: Process `NFCORE_RNASEQ:RNASEQ:ALIGN_STAR:STAR_ALIGN (WT_REP1)` terminated with an error exit status (137) -- Execution is retried (1) -Error executing process > 'NFCORE_RNASEQ:RNASEQ:ALIGN_STAR:STAR_ALIGN (WT_REP1)' - -Caused by: - Process `NFCORE_RNASEQ:RNASEQ:ALIGN_STAR:STAR_ALIGN (WT_REP1)` terminated with an error exit status (137) - -Command executed: - STAR \ - --genomeDir star \ - --readFilesIn WT_REP1_trimmed.fq.gz \ - --runThreadN 2 \ - --outFileNamePrefix WT_REP1. \ - - -Command exit status: - 137 - -Command output: - (empty) - -Command error: - .command.sh: line 9: 30 Killed STAR --genomeDir star --readFilesIn WT_REP1_trimmed.fq.gz --runThreadN 2 --outFileNamePrefix WT_REP1. -Work dir: - /home/pipelinetest/work/9d/172ca5881234073e8d76f2a19c88fb - -Tip: you can replicate the issue by changing to the process work dir and entering the command `bash .command.run` -``` - -To bypass this error you would need to find exactly which resources are set by the `STAR_ALIGN` process. The quickest way is to search for `process STAR_ALIGN` in the [nf-core/rnaseq Github repo](https://github.com/nf-core/rnaseq/search?q=process+STAR_ALIGN). -We have standardised the structure of Nextflow DSL2 pipelines such that all module files will be present in the `modules/` directory and so, based on the search results, the file we want is `modules/nf-core/software/star/align/main.nf`. -If you click on the link to that file you will notice that there is a `label` directive at the top of the module that is set to [`label process_high`](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/modules/nf-core/software/star/align/main.nf#L9). -The [Nextflow `label`](https://www.nextflow.io/docs/latest/process.html#label) directive allows us to organise workflow processes in separate groups which can be referenced in a configuration file to select and configure subset of processes having similar computing requirements. -The default values for the `process_high` label are set in the pipeline's [`base.config`](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/base.config#L33-L37) which in this case is defined as 72GB. -Providing you haven't set any other standard nf-core parameters to **cap** the [maximum resources](https://nf-co.re/usage/configuration#max-resources) used by the pipeline then we can try and bypass the `STAR_ALIGN` process failure by creating a custom config file that sets at least 72GB of memory, in this case increased to 100GB. -The custom config below can then be provided to the pipeline via the [`-c`](#-c) parameter as highlighted in previous sections. - -```nextflow -process { - withName: 'NFCORE_RNASEQ:RNASEQ:ALIGN_STAR:STAR_ALIGN' { - memory = 100.GB - } -} -``` - -> **NB:** We specify the full process name i.e. `NFCORE_RNASEQ:RNASEQ:ALIGN_STAR:STAR_ALIGN` in the config file because this takes priority over the short name (`STAR_ALIGN`) and allows existing configuration using the full process name to be correctly overridden. -> -> If you get a warning suggesting that the process selector isn't recognised check that the process name has been specified correctly. - -### Updating containers - -The [Nextflow DSL2](https://www.nextflow.io/docs/latest/dsl2.html) implementation of this pipeline uses one container per process which makes it much easier to maintain and update software dependencies. If for some reason you need to use a different version of a particular tool with the pipeline then you just need to identify the `process` name and override the Nextflow `container` definition for that process using the `withName` declaration. For example, in the [nf-core/viralrecon](https://nf-co.re/viralrecon) pipeline a tool called [Pangolin](https://github.com/cov-lineages/pangolin) has been used during the COVID-19 pandemic to assign lineages to SARS-CoV-2 genome sequenced samples. Given that the lineage assignments change quite frequently it doesn't make sense to re-release the nf-core/viralrecon everytime a new version of Pangolin has been released. However, you can override the default container used by the pipeline by creating a custom config file and passing it as a command-line argument via `-c custom.config`. - -1. Check the default version used by the pipeline in the module file for [Pangolin](https://github.com/nf-core/viralrecon/blob/a85d5969f9025409e3618d6c280ef15ce417df65/modules/nf-core/software/pangolin/main.nf#L14-L19) -2. Find the latest version of the Biocontainer available on [Quay.io](https://quay.io/repository/biocontainers/pangolin?tag=latest&tab=tags) -3. Create the custom config accordingly: - - - For Docker: +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. - ```nextflow - process { - withName: PANGOLIN { - container = 'quay.io/biocontainers/pangolin:3.0.5--pyhdfd78af_0' - } - } - ``` +### Custom Containers - - For Singularity: +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. - ```nextflow - process { - withName: PANGOLIN { - container = 'https://depot.galaxyproject.org/singularity/pangolin:3.0.5--pyhdfd78af_0' - } - } - ``` +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. - - For Conda: +### Custom Tool Arguments - ```nextflow - process { - withName: PANGOLIN { - conda = 'bioconda::pangolin=3.0.5' - } - } - ``` +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:** If you wish to periodically update individual tool-specific results (e.g. Pangolin) generated by the pipeline then you must ensure to keep the `work/` directory otherwise the `-resume` ability of the pipeline will be compromised and it will restart from scratch. +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. ### nf-core/configs diff --git a/lib/NfcoreSchema.groovy b/lib/NfcoreSchema.groovy deleted file mode 100755 index b3d092f8..00000000 --- a/lib/NfcoreSchema.groovy +++ /dev/null @@ -1,529 +0,0 @@ -// -// This file holds several functions used to perform JSON parameter validation, help and summary rendering for the nf-core pipeline template. -// - -import org.everit.json.schema.Schema -import org.everit.json.schema.loader.SchemaLoader -import org.everit.json.schema.ValidationException -import org.json.JSONObject -import org.json.JSONTokener -import org.json.JSONArray -import groovy.json.JsonSlurper -import groovy.json.JsonBuilder - -class NfcoreSchema { - - // - // Resolve Schema path relative to main workflow directory - // - public static String getSchemaPath(workflow, schema_filename='nextflow_schema.json') { - return "${workflow.projectDir}/${schema_filename}" - } - - // - // Function to loop over all parameters defined in schema and check - // whether the given parameters adhere to the specifications - // - /* groovylint-disable-next-line UnusedPrivateMethodParameter */ - public static void validateParameters(workflow, params, log, schema_filename='nextflow_schema.json') { - def has_error = false - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// - // Check for nextflow core params and unexpected params - def json = new File(getSchemaPath(workflow, schema_filename=schema_filename)).text - def Map schemaParams = (Map) new JsonSlurper().parseText(json).get('definitions') - def nf_params = [ - // Options for base `nextflow` command - 'bg', - 'c', - 'C', - 'config', - 'd', - 'D', - 'dockerize', - 'h', - 'log', - 'q', - 'quiet', - 'syslog', - 'v', - 'version', - - // Options for `nextflow run` command - 'ansi', - 'ansi-log', - 'bg', - 'bucket-dir', - 'c', - 'cache', - 'config', - 'dsl2', - 'dump-channels', - 'dump-hashes', - 'E', - 'entry', - 'latest', - 'lib', - 'main-script', - 'N', - 'name', - 'offline', - 'params-file', - 'pi', - 'plugins', - 'poll-interval', - 'pool-size', - 'profile', - 'ps', - 'qs', - 'queue-size', - 'r', - 'resume', - 'revision', - 'stdin', - 'stub', - 'stub-run', - 'test', - 'w', - 'with-charliecloud', - 'with-conda', - 'with-dag', - 'with-docker', - 'with-mpi', - 'with-notification', - 'with-podman', - 'with-report', - 'with-singularity', - 'with-timeline', - 'with-tower', - 'with-trace', - 'with-weblog', - 'without-docker', - 'without-podman', - 'work-dir' - ] - def unexpectedParams = [] - - // Collect expected parameters from the schema - def expectedParams = [] - def enums = [:] - for (group in schemaParams) { - for (p in group.value['properties']) { - expectedParams.push(p.key) - if (group.value['properties'][p.key].containsKey('enum')) { - enums[p.key] = group.value['properties'][p.key]['enum'] - } - } - } - - for (specifiedParam in params.keySet()) { - // nextflow params - if (nf_params.contains(specifiedParam)) { - log.error "ERROR: You used a core Nextflow option with two hyphens: '--${specifiedParam}'. Please resubmit with '-${specifiedParam}'" - has_error = true - } - // unexpected params - def params_ignore = params.schema_ignore_params.split(',') + 'schema_ignore_params' - def expectedParamsLowerCase = expectedParams.collect{ it.replace("-", "").toLowerCase() } - def specifiedParamLowerCase = specifiedParam.replace("-", "").toLowerCase() - def isCamelCaseBug = (specifiedParam.contains("-") && !expectedParams.contains(specifiedParam) && expectedParamsLowerCase.contains(specifiedParamLowerCase)) - if (!expectedParams.contains(specifiedParam) && !params_ignore.contains(specifiedParam) && !isCamelCaseBug) { - // Temporarily remove camelCase/camel-case params #1035 - def unexpectedParamsLowerCase = unexpectedParams.collect{ it.replace("-", "").toLowerCase()} - if (!unexpectedParamsLowerCase.contains(specifiedParamLowerCase)){ - unexpectedParams.push(specifiedParam) - } - } - } - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// - // Validate parameters against the schema - InputStream input_stream = new File(getSchemaPath(workflow, schema_filename=schema_filename)).newInputStream() - JSONObject raw_schema = new JSONObject(new JSONTokener(input_stream)) - - // Remove anything that's in params.schema_ignore_params - raw_schema = removeIgnoredParams(raw_schema, params) - - Schema schema = SchemaLoader.load(raw_schema) - - // Clean the parameters - def cleanedParams = cleanParameters(params) - - // Convert to JSONObject - def jsonParams = new JsonBuilder(cleanedParams) - JSONObject params_json = new JSONObject(jsonParams.toString()) - - // Validate - try { - schema.validate(params_json) - } catch (ValidationException e) { - println '' - log.error 'ERROR: Validation of pipeline parameters failed!' - JSONObject exceptionJSON = e.toJSON() - printExceptions(exceptionJSON, params_json, log, enums) - println '' - has_error = true - } - - // Check for unexpected parameters - if (unexpectedParams.size() > 0) { - Map colors = NfcoreTemplate.logColours(params.monochrome_logs) - println '' - def warn_msg = 'Found unexpected parameters:' - for (unexpectedParam in unexpectedParams) { - warn_msg = warn_msg + "\n* --${unexpectedParam}: ${params[unexpectedParam].toString()}" - } - log.warn warn_msg - log.info "- ${colors.dim}Ignore this warning: params.schema_ignore_params = \"${unexpectedParams.join(',')}\" ${colors.reset}" - println '' - } - - if (has_error) { - System.exit(1) - } - } - - // - // Beautify parameters for --help - // - public static String paramsHelp(workflow, params, command, schema_filename='nextflow_schema.json') { - Map colors = NfcoreTemplate.logColours(params.monochrome_logs) - Integer num_hidden = 0 - String output = '' - output += 'Typical pipeline command:\n\n' - output += " ${colors.cyan}${command}${colors.reset}\n\n" - Map params_map = paramsLoad(getSchemaPath(workflow, schema_filename=schema_filename)) - Integer max_chars = paramsMaxChars(params_map) + 1 - Integer desc_indent = max_chars + 14 - Integer dec_linewidth = 160 - desc_indent - for (group in params_map.keySet()) { - Integer num_params = 0 - String group_output = colors.underlined + colors.bold + group + colors.reset + '\n' - def group_params = params_map.get(group) // This gets the parameters of that particular group - for (param in group_params.keySet()) { - if (group_params.get(param).hidden && !params.show_hidden_params) { - num_hidden += 1 - continue; - } - def type = '[' + group_params.get(param).type + ']' - def description = group_params.get(param).description - def defaultValue = group_params.get(param).default != null ? " [default: " + group_params.get(param).default.toString() + "]" : '' - def description_default = description + colors.dim + defaultValue + colors.reset - // Wrap long description texts - // Loosely based on https://dzone.com/articles/groovy-plain-text-word-wrap - if (description_default.length() > dec_linewidth){ - List olines = [] - String oline = "" // " " * indent - description_default.split(" ").each() { wrd -> - if ((oline.size() + wrd.size()) <= dec_linewidth) { - oline += wrd + " " - } else { - olines += oline - oline = wrd + " " - } - } - olines += oline - description_default = olines.join("\n" + " " * desc_indent) - } - group_output += " --" + param.padRight(max_chars) + colors.dim + type.padRight(10) + colors.reset + description_default + '\n' - num_params += 1 - } - group_output += '\n' - if (num_params > 0){ - output += group_output - } - } - if (num_hidden > 0){ - output += colors.dim + "!! Hiding $num_hidden params, use --show_hidden_params to show them !!\n" + colors.reset - } - output += NfcoreTemplate.dashedLine(params.monochrome_logs) - return output - } - - // - // Groovy Map summarising parameters/workflow options used by the pipeline - // - public static LinkedHashMap paramsSummaryMap(workflow, params, schema_filename='nextflow_schema.json') { - // Get a selection of core Nextflow workflow options - def Map workflow_summary = [:] - if (workflow.revision) { - workflow_summary['revision'] = workflow.revision - } - workflow_summary['runName'] = workflow.runName - if (workflow.containerEngine) { - workflow_summary['containerEngine'] = workflow.containerEngine - } - if (workflow.container) { - workflow_summary['container'] = workflow.container - } - workflow_summary['launchDir'] = workflow.launchDir - workflow_summary['workDir'] = workflow.workDir - workflow_summary['projectDir'] = workflow.projectDir - workflow_summary['userName'] = workflow.userName - workflow_summary['profile'] = workflow.profile - workflow_summary['configFiles'] = workflow.configFiles.join(', ') - - // Get pipeline parameters defined in JSON Schema - def Map params_summary = [:] - def params_map = paramsLoad(getSchemaPath(workflow, schema_filename=schema_filename)) - for (group in params_map.keySet()) { - def sub_params = new LinkedHashMap() - def group_params = params_map.get(group) // This gets the parameters of that particular group - for (param in group_params.keySet()) { - if (params.containsKey(param)) { - def params_value = params.get(param) - def schema_value = group_params.get(param).default - def param_type = group_params.get(param).type - if (schema_value != null) { - if (param_type == 'string') { - if (schema_value.contains('$projectDir') || schema_value.contains('${projectDir}')) { - def sub_string = schema_value.replace('\$projectDir', '') - sub_string = sub_string.replace('\${projectDir}', '') - if (params_value.contains(sub_string)) { - schema_value = params_value - } - } - if (schema_value.contains('$params.outdir') || schema_value.contains('${params.outdir}')) { - def sub_string = schema_value.replace('\$params.outdir', '') - sub_string = sub_string.replace('\${params.outdir}', '') - if ("${params.outdir}${sub_string}" == params_value) { - schema_value = params_value - } - } - } - } - - // We have a default in the schema, and this isn't it - if (schema_value != null && params_value != schema_value) { - sub_params.put(param, params_value) - } - // No default in the schema, and this isn't empty - else if (schema_value == null && params_value != "" && params_value != null && params_value != false) { - sub_params.put(param, params_value) - } - } - } - params_summary.put(group, sub_params) - } - return [ 'Core Nextflow options' : workflow_summary ] << params_summary - } - - // - // Beautify parameters for summary and return as string - // - public static String paramsSummaryLog(workflow, params) { - Map colors = NfcoreTemplate.logColours(params.monochrome_logs) - String output = '' - def params_map = paramsSummaryMap(workflow, params) - def max_chars = paramsMaxChars(params_map) - for (group in params_map.keySet()) { - def group_params = params_map.get(group) // This gets the parameters of that particular group - if (group_params) { - output += colors.bold + group + colors.reset + '\n' - for (param in group_params.keySet()) { - output += " " + colors.blue + param.padRight(max_chars) + ": " + colors.green + group_params.get(param) + colors.reset + '\n' - } - output += '\n' - } - } - output += "!! Only displaying parameters that differ from the pipeline defaults !!\n" - output += NfcoreTemplate.dashedLine(params.monochrome_logs) - return output - } - - // - // Loop over nested exceptions and print the causingException - // - private static void printExceptions(ex_json, params_json, log, enums, limit=5) { - def causingExceptions = ex_json['causingExceptions'] - if (causingExceptions.length() == 0) { - def m = ex_json['message'] =~ /required key \[([^\]]+)\] not found/ - // Missing required param - if (m.matches()) { - log.error "* Missing required parameter: --${m[0][1]}" - } - // Other base-level error - else if (ex_json['pointerToViolation'] == '#') { - log.error "* ${ex_json['message']}" - } - // Error with specific param - else { - def param = ex_json['pointerToViolation'] - ~/^#\// - def param_val = params_json[param].toString() - if (enums.containsKey(param)) { - def error_msg = "* --${param}: '${param_val}' is not a valid choice (Available choices" - if (enums[param].size() > limit) { - log.error "${error_msg} (${limit} of ${enums[param].size()}): ${enums[param][0..limit-1].join(', ')}, ... )" - } else { - log.error "${error_msg}: ${enums[param].join(', ')})" - } - } else { - log.error "* --${param}: ${ex_json['message']} (${param_val})" - } - } - } - for (ex in causingExceptions) { - printExceptions(ex, params_json, log, enums) - } - } - - // - // Remove an element from a JSONArray - // - private static JSONArray removeElement(json_array, element) { - def list = [] - int len = json_array.length() - for (int i=0;i - if(raw_schema.keySet().contains('definitions')){ - raw_schema.definitions.each { definition -> - for (key in definition.keySet()){ - if (definition[key].get("properties").keySet().contains(ignore_param)){ - // Remove the param to ignore - definition[key].get("properties").remove(ignore_param) - // If the param was required, change this - if (definition[key].has("required")) { - def cleaned_required = removeElement(definition[key].required, ignore_param) - definition[key].put("required", cleaned_required) - } - } - } - } - } - if(raw_schema.keySet().contains('properties') && raw_schema.get('properties').keySet().contains(ignore_param)) { - raw_schema.get("properties").remove(ignore_param) - } - if(raw_schema.keySet().contains('required') && raw_schema.required.contains(ignore_param)) { - def cleaned_required = removeElement(raw_schema.required, ignore_param) - raw_schema.put("required", cleaned_required) - } - } - return raw_schema - } - - // - // Clean and check parameters relative to Nextflow native classes - // - private static Map cleanParameters(params) { - def new_params = params.getClass().newInstance(params) - for (p in params) { - // remove anything evaluating to false - if (!p['value']) { - new_params.remove(p.key) - } - // Cast MemoryUnit to String - if (p['value'].getClass() == nextflow.util.MemoryUnit) { - new_params.replace(p.key, p['value'].toString()) - } - // Cast Duration to String - if (p['value'].getClass() == nextflow.util.Duration) { - new_params.replace(p.key, p['value'].toString().replaceFirst(/d(?!\S)/, "day")) - } - // Cast LinkedHashMap to String - if (p['value'].getClass() == LinkedHashMap) { - new_params.replace(p.key, p['value'].toString()) - } - } - return new_params - } - - // - // This function tries to read a JSON params file - // - private static LinkedHashMap paramsLoad(String json_schema) { - def params_map = new LinkedHashMap() - try { - params_map = paramsRead(json_schema) - } catch (Exception e) { - println "Could not read parameters settings from JSON. $e" - params_map = new LinkedHashMap() - } - return params_map - } - - // - // Method to actually read in JSON file using Groovy. - // Group (as Key), values are all parameters - // - Parameter1 as Key, Description as Value - // - Parameter2 as Key, Description as Value - // .... - // Group - // - - private static LinkedHashMap paramsRead(String json_schema) throws Exception { - def json = new File(json_schema).text - def Map schema_definitions = (Map) new JsonSlurper().parseText(json).get('definitions') - def Map schema_properties = (Map) new JsonSlurper().parseText(json).get('properties') - /* Tree looks like this in nf-core schema - * definitions <- this is what the first get('definitions') gets us - group 1 - title - description - properties - parameter 1 - type - description - parameter 2 - type - description - group 2 - title - description - properties - parameter 1 - type - description - * properties <- parameters can also be ungrouped, outside of definitions - parameter 1 - type - description - */ - - // Grouped params - def params_map = new LinkedHashMap() - schema_definitions.each { key, val -> - def Map group = schema_definitions."$key".properties // Gets the property object of the group - def title = schema_definitions."$key".title - def sub_params = new LinkedHashMap() - group.each { innerkey, value -> - sub_params.put(innerkey, value) - } - params_map.put(title, sub_params) - } - - // Ungrouped params - def ungrouped_params = new LinkedHashMap() - schema_properties.each { innerkey, value -> - ungrouped_params.put(innerkey, value) - } - params_map.put("Other parameters", ungrouped_params) - - return params_map - } - - // - // Get maximum number of characters across all parameter names - // - private static Integer paramsMaxChars(params_map) { - Integer max_chars = 0 - for (group in params_map.keySet()) { - def group_params = params_map.get(group) // This gets the parameters of that particular group - for (param in group_params.keySet()) { - if (param.size() > max_chars) { - max_chars = param.size() - } - } - } - return max_chars - } -} diff --git a/lib/NfcoreTemplate.groovy b/lib/NfcoreTemplate.groovy index 27feb009..408951ae 100755 --- a/lib/NfcoreTemplate.groovy +++ b/lib/NfcoreTemplate.groovy @@ -32,6 +32,25 @@ class NfcoreTemplate { } } + // + // 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 // @@ -61,7 +80,7 @@ class NfcoreTemplate { misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp def email_fields = [:] - email_fields['version'] = workflow.manifest.version + email_fields['version'] = NfcoreTemplate.version(workflow) email_fields['runName'] = workflow.runName email_fields['success'] = workflow.success email_fields['dateComplete'] = workflow.complete @@ -109,7 +128,7 @@ class NfcoreTemplate { def email_html = html_template.toString() // Render the sendmail template - def max_multiqc_email_size = params.max_multiqc_email_size as nextflow.util.MemoryUnit + 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) @@ -146,10 +165,10 @@ class NfcoreTemplate { } // - // Construct and send adaptive card - // https://adaptivecards.io + // Construct and send a notification to a web server as JSON + // e.g. Microsoft Teams and Slack // - public static void adaptivecard(workflow, params, summary_params, projectDir, log) { + public static void IM_notification(workflow, params, summary_params, projectDir, log) { def hook_url = params.hook_url def summary = [:] @@ -170,7 +189,7 @@ class NfcoreTemplate { misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp def msg_fields = [:] - msg_fields['version'] = workflow.manifest.version + msg_fields['version'] = NfcoreTemplate.version(workflow) msg_fields['runName'] = workflow.runName msg_fields['success'] = workflow.success msg_fields['dateComplete'] = workflow.complete @@ -178,13 +197,16 @@ class NfcoreTemplate { msg_fields['exitStatus'] = workflow.exitStatus msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') msg_fields['errorReport'] = (workflow.errorReport ?: 'None') - msg_fields['commandLine'] = workflow.commandLine + 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() - def hf = new File("$projectDir/assets/adaptivecard.json") + // 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() @@ -209,7 +231,7 @@ class NfcoreTemplate { 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.red} Pipeline completed successfully, but with errored process(es) ${colors.reset}-" + 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}-" @@ -297,6 +319,7 @@ class NfcoreTemplate { // public static String logo(workflow, monochrome_logs) { Map colors = logColours(monochrome_logs) + String workflow_version = NfcoreTemplate.version(workflow) String.format( """\n ${dashedLine(monochrome_logs)} @@ -305,7 +328,7 @@ class NfcoreTemplate { ${colors.blue} |\\ | |__ __ / ` / \\ |__) |__ ${colors.yellow}} {${colors.reset} ${colors.blue} | \\| | \\__, \\__/ | \\ |___ ${colors.green}\\`-._,-`-,${colors.reset} ${colors.green}`._,._,\'${colors.reset} - ${colors.purple} ${workflow.manifest.name} v${workflow.manifest.version}${colors.reset} + ${colors.purple} ${workflow.manifest.name} ${workflow_version}${colors.reset} ${dashedLine(monochrome_logs)} """.stripIndent() ) diff --git a/lib/WorkflowMain.groovy b/lib/WorkflowMain.groovy index 2c4070ea..a4a4b3ff 100755 --- a/lib/WorkflowMain.groovy +++ b/lib/WorkflowMain.groovy @@ -2,6 +2,8 @@ // This file holds several functions specific to the main.nf workflow in the nf-core/metatdenovo pipeline // +import nextflow.Nextflow + class WorkflowMain { // @@ -18,55 +20,24 @@ class WorkflowMain { " https://github.com/${workflow.manifest.name}/blob/master/CITATIONS.md" } - // - // Print help to screen if required - // - public static String help(workflow, params, log) { - def command = "nextflow run ${workflow.manifest.name} --input samplesheet.csv --genome GRCh37 -profile docker" - def help_string = '' - help_string += NfcoreTemplate.logo(workflow, params.monochrome_logs) - help_string += NfcoreSchema.paramsHelp(workflow, params, command) - help_string += '\n' + citation(workflow) + '\n' - help_string += NfcoreTemplate.dashedLine(params.monochrome_logs) - return help_string - } - - // - // Print parameter summary log to screen - // - public static String paramsSummaryLog(workflow, params, log) { - def summary_log = '' - summary_log += NfcoreTemplate.logo(workflow, params.monochrome_logs) - summary_log += NfcoreSchema.paramsSummaryLog(workflow, params) - summary_log += '\n' + citation(workflow) + '\n' - summary_log += NfcoreTemplate.dashedLine(params.monochrome_logs) - return summary_log - } // // Validate parameters and print summary to screen // public static void initialise(workflow, params, log) { - // Print help to screen if required - if (params.help) { - log.info help(workflow, params, log) - System.exit(0) - } - // Validate workflow parameters via the JSON schema - if (params.validate_params) { - NfcoreSchema.validateParameters(workflow, params, log) + // 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) } - // Print parameter summary log to screen - - log.info paramsSummaryLog(workflow, params, log) - // Check that a -profile or Nextflow config has been provided to run the pipeline NfcoreTemplate.checkConfigProvided(workflow, log) // Check that conda channels are set-up correctly - if (params.enable_conda) { + if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { Utils.checkCondaChannels(log) } @@ -75,8 +46,7 @@ class WorkflowMain { // Check input has been provided if (!params.input) { - log.error "Please provide an input samplesheet to the pipeline e.g. '--input samplesheet.csv'" - System.exit(1) + Nextflow.error("Please provide an input samplesheet to the pipeline e.g. '--input samplesheet.csv'") } } // diff --git a/lib/WorkflowMetatdenovo.groovy b/lib/WorkflowMetatdenovo.groovy index 342c44b5..a4349631 100755 --- a/lib/WorkflowMetatdenovo.groovy +++ b/lib/WorkflowMetatdenovo.groovy @@ -2,6 +2,7 @@ // This file holds several functions specific to the workflow/metatdenovo.nf in the nf-core/metatdenovo pipeline // +import nextflow.Nextflow import groovy.text.SimpleTemplateEngine class WorkflowMetatdenovo { @@ -10,12 +11,12 @@ class WorkflowMetatdenovo { // Check and validate parameters // public static void initialise(params, log) { + genomeExistsError(params, log) if (!params.fasta) { - log.error "Genome fasta file not specified with e.g. '--fasta genome.fa' or via a detectable config file." - System.exit(1) + Nextflow.error "Genome fasta file not specified with e.g. '--fasta genome.fa' or via a detectable config file." } } @@ -46,32 +47,76 @@ class WorkflowMetatdenovo { return yaml_file_text } - public static String methodsDescriptionText(run_workflow, mqc_methods_yaml) { + // + // Generate methods description for MultiQC + // + + public static String toolCitationText(params) { + + // TODO 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)) { - log.error "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + 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" + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - System.exit(1) + Nextflow.error(error_string) } } } diff --git a/main.nf b/main.nf index 739a1aab..97ef8fa3 100644 --- a/main.nf +++ b/main.nf @@ -4,7 +4,6 @@ nf-core/metatdenovo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Github : https://github.com/nf-core/metatdenovo - Website: https://nf-co.re/metatdenovo Slack : https://nfcore.slack.com/channels/metatdenovo ---------------------------------------------------------------------------------------- @@ -26,6 +25,22 @@ params.fasta = WorkflowMain.getGenomeAttribute(params, 'fasta') ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +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) /* diff --git a/modules.json b/modules.json index 1077d4ce..91e0bb53 100644 --- a/modules.json +++ b/modules.json @@ -7,15 +7,18 @@ "nf-core": { "custom/dumpsoftwareversions": { "branch": "master", - "git_sha": "5e34754d42cd2d5d248ca8673c0a53cdf5624905" + "git_sha": "76cc4938c1f6ea5c7d83fed1eeffc146787f9543", + "installed_by": ["modules"] }, "fastqc": { "branch": "master", - "git_sha": "5e34754d42cd2d5d248ca8673c0a53cdf5624905" + "git_sha": "c8e35eb2055c099720a75538d1b8adb3fb5a464c", + "installed_by": ["modules"] }, "multiqc": { "branch": "master", - "git_sha": "5e34754d42cd2d5d248ca8673c0a53cdf5624905" + "git_sha": "f2d63bd5b68925f98f572eed70993d205cc694b7", + "installed_by": ["modules"] } } } diff --git a/modules/local/samplesheet_check.nf b/modules/local/samplesheet_check.nf index 8f0b1793..f24f9d22 100644 --- a/modules/local/samplesheet_check.nf +++ b/modules/local/samplesheet_check.nf @@ -1,10 +1,11 @@ process SAMPLESHEET_CHECK { tag "$samplesheet" + label 'process_single' - conda (params.enable_conda ? "conda-forge::python=3.8.3" : null) + 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' : - 'quay.io/biocontainers/python:3.8.3' }" + 'biocontainers/python:3.8.3' }" input: path samplesheet @@ -13,6 +14,9 @@ process SAMPLESHEET_CHECK { 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/metatdenovo/bin/ """ check_samplesheet.py \\ diff --git a/modules/nf-core/custom/dumpsoftwareversions/main.nf b/modules/nf-core/custom/dumpsoftwareversions/main.nf index cebb6e05..800a6099 100644 --- a/modules/nf-core/custom/dumpsoftwareversions/main.nf +++ b/modules/nf-core/custom/dumpsoftwareversions/main.nf @@ -2,10 +2,10 @@ process CUSTOM_DUMPSOFTWAREVERSIONS { label 'process_single' // Requires `pyyaml` which does not have a dedicated container but is in the MultiQC container - conda (params.enable_conda ? 'bioconda::multiqc=1.13' : null) + conda "bioconda::multiqc=1.14" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.13--pyhdfd78af_0' : - 'quay.io/biocontainers/multiqc:1.13--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.14--pyhdfd78af_0' : + 'quay.io/biocontainers/multiqc:1.14--pyhdfd78af_0' }" input: path versions diff --git a/modules/nf-core/custom/dumpsoftwareversions/meta.yml b/modules/nf-core/custom/dumpsoftwareversions/meta.yml index 60b546a0..c32657de 100644 --- a/modules/nf-core/custom/dumpsoftwareversions/meta.yml +++ b/modules/nf-core/custom/dumpsoftwareversions/meta.yml @@ -1,7 +1,9 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-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: diff --git a/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py b/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py old mode 100644 new mode 100755 index 787bdb7b..e55b8d43 --- a/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py +++ b/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py @@ -1,5 +1,9 @@ #!/usr/bin/env python + +"""Provide functions to merge multiple versions.yml files.""" + + import platform from textwrap import dedent @@ -7,6 +11,7 @@ def _make_versions_html(versions): + """Generate a tabular HTML output of all versions for MultiQC.""" html = [ dedent( """\\ @@ -45,47 +50,53 @@ def _make_versions_html(versions): return "\\n".join(html) -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) +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/fastqc/main.nf b/modules/nf-core/fastqc/main.nf index 05730368..9ae58381 100644 --- a/modules/nf-core/fastqc/main.nf +++ b/modules/nf-core/fastqc/main.nf @@ -2,7 +2,7 @@ process FASTQC { tag "$meta.id" label 'process_medium' - conda (params.enable_conda ? "bioconda::fastqc=0.11.9" : null) + conda "bioconda::fastqc=0.11.9" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/fastqc:0.11.9--0' : 'quay.io/biocontainers/fastqc:0.11.9--0' }" @@ -20,30 +20,22 @@ process FASTQC { script: def args = task.ext.args ?: '' - // Add soft-links to original FastQs for consistent naming in pipeline def prefix = task.ext.prefix ?: "${meta.id}" - if (meta.single_end) { - """ - [ ! -f ${prefix}.fastq.gz ] && ln -s $reads ${prefix}.fastq.gz - fastqc $args --threads $task.cpus ${prefix}.fastq.gz - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) - END_VERSIONS - """ - } else { - """ - [ ! -f ${prefix}_1.fastq.gz ] && ln -s ${reads[0]} ${prefix}_1.fastq.gz - [ ! -f ${prefix}_2.fastq.gz ] && ln -s ${reads[1]} ${prefix}_2.fastq.gz - fastqc $args --threads $task.cpus ${prefix}_1.fastq.gz ${prefix}_2.fastq.gz - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: \$( fastqc --version | sed -e "s/FastQC v//g" ) - END_VERSIONS - """ - } + // 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 -e "s/FastQC v//g" ) + END_VERSIONS + """ stub: def prefix = task.ext.prefix ?: "${meta.id}" diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index a8159a57..4b604749 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -1,10 +1,10 @@ process MULTIQC { label 'process_single' - conda (params.enable_conda ? 'bioconda::multiqc=1.13' : null) + conda "bioconda::multiqc=1.14" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.13--pyhdfd78af_0' : - 'quay.io/biocontainers/multiqc:1.13--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.14--pyhdfd78af_0' : + 'quay.io/biocontainers/multiqc:1.14--pyhdfd78af_0' }" input: path multiqc_files, stageAs: "?/*" diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index ebc29b27..f93b5ee5 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/yaml-schema.json name: MultiQC description: Aggregate results from bioinformatics analyses across many samples into a single report keywords: @@ -37,7 +38,7 @@ output: description: MultiQC report file pattern: "multiqc_report.html" - data: - type: dir + type: directory description: MultiQC data dir pattern: "multiqc_data" - plots: diff --git a/nextflow.config b/nextflow.config index 82b2ce61..e84450ea 100644 --- a/nextflow.config +++ b/nextflow.config @@ -12,12 +12,12 @@ params { // 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 @@ -27,7 +27,6 @@ params { // Boilerplate options outdir = null - tracedir = "${params.outdir}/pipeline_info" publish_dir_mode = 'copy' email = null email_on_fail = null @@ -35,20 +34,16 @@ params { monochrome_logs = false hook_url = null help = false - validate_params = true - show_hidden_params = false - schema_ignore_params = 'genomes' - enable_conda = 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_description = null config_profile_contact = null config_profile_url = null - config_profile_name = null - + // Max resource options // Defaults only, expecting to be overwritten @@ -56,6 +51,13 @@ params { max_cpus = 16 max_time = '240.h' + // Schema validation default options + validationFailUnrecognisedParams = false + validationLenientMode = false + validationSchemaIgnoreParams = 'genomes' + validationShowHiddenParams = false + validate_params = true + } // Load base.config by default for all pipelines @@ -75,63 +77,89 @@ try { // } catch (Exception e) { // System.err.println("WARNING: Could not load nf-core/config/metatdenovo profiles: ${params.custom_config_base}/pipeline/metatdenovo.config") // } - - profiles { - debug { process.beforeScript = 'echo $HOSTNAME' } + debug { + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false + } conda { - params.enable_conda = true + conda.enabled = true docker.enabled = false singularity.enabled = false podman.enabled = false shifter.enabled = false charliecloud.enabled = false + apptainer.enabled = false } mamba { - params.enable_conda = true + 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 docker.userEmulation = true + conda.enabled = false singularity.enabled = false podman.enabled = false shifter.enabled = false charliecloud.enabled = false + apptainer.enabled = false + } + 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 + conda.enabled = false docker.enabled = false singularity.enabled = false podman.enabled = false shifter.enabled = false + charliecloud.enabled = false } gitpod { executor.name = 'local' @@ -142,6 +170,18 @@ profiles { 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' // Validation of pipeline parameters and creation of an input channel from a sample sheet +} // Load igenomes.config if required if (!params.igenomes_ignore) { @@ -149,8 +189,6 @@ if (!params.igenomes_ignore) { } 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. @@ -168,28 +206,28 @@ process.shell = ['/bin/bash', '-euo', 'pipefail'] def trace_timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') timeline { enabled = true - file = "${params.tracedir}/execution_timeline_${trace_timestamp}.html" + file = "${params.outdir}/pipeline_info/execution_timeline_${trace_timestamp}.html" } report { enabled = true - file = "${params.tracedir}/execution_report_${trace_timestamp}.html" + file = "${params.outdir}/pipeline_info/execution_report_${trace_timestamp}.html" } trace { enabled = true - file = "${params.tracedir}/execution_trace_${trace_timestamp}.txt" + file = "${params.outdir}/pipeline_info/execution_trace_${trace_timestamp}.txt" } dag { enabled = true - file = "${params.tracedir}/pipeline_dag_${trace_timestamp}.html" + file = "${params.outdir}/pipeline_info/pipeline_dag_${trace_timestamp}.html" } manifest { name = 'nf-core/metatdenovo' - author = 'Daniel Lundin' + author = """Danilo Di Leo, Emelie Nilsson & Daniel Lundin""" homePage = 'https://github.com/nf-core/metatdenovo' - description = 'Assembly and annotation of metatranscriptomic data, both prokaryotic and eukaryotic' + description = """Assembly and annotation of metatranscriptomic data, both prokaryotic and eukaryotic""" mainScript = 'main.nf' - nextflowVersion = '!>=21.10.3' + nextflowVersion = '!>=23.04.0' version = '2.5.1' doi = '' } diff --git a/nextflow_schema.json b/nextflow_schema.json index 8e906617..d34f66b6 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -15,9 +15,9 @@ "input": { "type": "string", "format": "file-path", + "exists": true, "mimetype": "text/csv", "pattern": "^\\S+\\.csv$", - "schema": "assets/schema_input.json", "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/metatdenovo/usage#samplesheet-input).", "fa_icon": "fas fa-file-csv" @@ -57,6 +57,7 @@ "fasta": { "type": "string", "format": "file-path", + "exists": true, "mimetype": "text/plain", "pattern": "^\\S+\\.fn?a(sta)?(\\.gz)?$", "description": "Path to FASTA genome file.", @@ -157,7 +158,7 @@ "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|day)\\s*)+$", + "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'`" } @@ -174,6 +175,14 @@ "type": "boolean", "description": "Display help text.", "fa_icon": "fas fa-question-circle", + "default": false, + "hidden": true + }, + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "default": false, "hidden": true }, "publish_dir_mode": { @@ -197,6 +206,7 @@ "type": "boolean", "description": "Send plain-text email instead of HTML.", "fa_icon": "fas fa-remove-format", + "default": false, "hidden": true }, "max_multiqc_email_size": { @@ -211,17 +221,19 @@ "type": "boolean", "description": "Do not use coloured log outputs.", "fa_icon": "fas fa-palette", + "default": false, "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, only MS Teams is supported.", + "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 @@ -237,13 +249,6 @@ "description": "Custom MultiQC yaml file containing HTML including a methods description.", "fa_icon": "fas fa-cog" }, - "tracedir": { - "type": "string", - "description": "Directory to keep pipeline Nextflow logs and reports.", - "default": "${params.outdir}/pipeline_info", - "fa_icon": "fas fa-cogs", - "hidden": true - }, "validate_params": { "type": "boolean", "description": "Boolean whether to validate parameters against the schema at runtime", @@ -251,18 +256,29 @@ "fa_icon": "fas fa-check-square", "hidden": true }, - "show_hidden_params": { + "validationShowHiddenParams": { "type": "boolean", "fa_icon": "far fa-eye-slash", "description": "Show all params when using `--help`", + "default": false, "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." }, - "enable_conda": { + "validationFailUnrecognisedParams": { + "type": "boolean", + "fa_icon": "far fa-check-circle", + "description": "Validation of parameters fails when an unrecognised parameter is found.", + "default": false, + "hidden": true, + "help_text": "By default, when an unrecognised parameter is found, it returns a warinig." + }, + "validationLenientMode": { "type": "boolean", - "description": "Run this workflow with Conda. You can also use '-profile conda' instead of providing this parameter.", + "fa_icon": "far fa-check-circle", + "description": "Validation of parameters in lenient more.", + "default": false, "hidden": true, - "fa_icon": "fas fa-bacon" + "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)." } } } 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/metatdenovo.nf b/workflows/metatdenovo.nf index d15a9197..d9fc1b9d 100644 --- a/workflows/metatdenovo.nf +++ b/workflows/metatdenovo.nf @@ -1,21 +1,19 @@ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - VALIDATE INPUTS + PRINT PARAMS SUMMARY ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -def summary_params = NfcoreSchema.paramsSummaryMap(workflow, params) +include { paramsSummaryLog; paramsSummaryMap } from 'plugin/nf-validation' -// Validate input parameters -WorkflowMetatdenovo.initialise(params, log) +def logo = NfcoreTemplate.logo(workflow, params.monochrome_logs) +def citation = '\n' + WorkflowMain.citation(workflow) + '\n' +def summary_params = paramsSummaryMap(workflow) -// TODO nf-core: Add all file path parameters for the pipeline to the list below -// Check input path parameters to see if they exist -def checkPathParamList = [ params.input, params.multiqc_config, params.fasta ] -for (param in checkPathParamList) { if (param) { file(param, checkIfExists: true) } } +// Print parameter summary log to screen +log.info logo + paramsSummaryLog(workflow) + citation -// Check mandatory parameters -if (params.input) { ch_input = file(params.input) } else { exit 1, 'Input samplesheet not specified!' } +WorkflowMetatdenovo.initialise(params, log) /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -69,9 +67,12 @@ workflow METATDENOVO { // SUBWORKFLOW: Read in samplesheet, validate and stage input files // INPUT_CHECK ( - ch_input + 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 @@ -91,7 +92,7 @@ workflow METATDENOVO { workflow_summary = WorkflowMetatdenovo.paramsSummaryMultiqc(workflow, summary_params) ch_workflow_summary = Channel.value(workflow_summary) - methods_description = WorkflowMetatdenovo.methodsDescriptionText(workflow, ch_multiqc_custom_methods_description) + methods_description = WorkflowMetatdenovo.methodsDescriptionText(workflow, ch_multiqc_custom_methods_description, params) ch_methods_description = Channel.value(methods_description) ch_multiqc_files = Channel.empty() @@ -102,12 +103,11 @@ workflow METATDENOVO { MULTIQC ( ch_multiqc_files.collect(), - ch_multiqc_config.collect().ifEmpty([]), - ch_multiqc_custom_config.collect().ifEmpty([]), - ch_multiqc_logo.collect().ifEmpty([]) + ch_multiqc_config.toList(), + ch_multiqc_custom_config.toList(), + ch_multiqc_logo.toList() ) multiqc_report = MULTIQC.out.report.toList() - ch_versions = ch_versions.mix(MULTIQC.out.versions) } /* @@ -122,7 +122,7 @@ workflow.onComplete { } NfcoreTemplate.summary(workflow, params, log) if (params.hook_url) { - NfcoreTemplate.adaptivecard(workflow, params, summary_params, projectDir, log) + NfcoreTemplate.IM_notification(workflow, params, summary_params, projectDir, log) } } From 7c71c4a5b95cd0e983de15fe0f9babb61f5aa3e4 Mon Sep 17 00:00:00 2001 From: Danilo Di Leo Date: Thu, 28 Sep 2023 05:39:07 +0200 Subject: [PATCH 2/6] linting --- modules.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules.json b/modules.json index 8bd3e99f..72de1e5f 100644 --- a/modules.json +++ b/modules.json @@ -8,20 +8,26 @@ "custom/dumpsoftwareversions": { "branch": "master", "git_sha": "911696ea0b62df80e900ef244d7867d177971f73", - "installed_by": ["modules"] + "installed_by": [ + "modules" + ] }, "fastqc": { "branch": "master", "git_sha": "bd8092b67b5103bdd52e300f75889442275c3117", - "installed_by": ["modules"] + "installed_by": [ + "modules" + ] }, "multiqc": { "branch": "master", "git_sha": "911696ea0b62df80e900ef244d7867d177971f73", - "installed_by": ["modules"] + "installed_by": [ + "modules" + ] } } } } } -} +} \ No newline at end of file From e1587bd75a654af7c1decfd56a60590a9b2dedaa Mon Sep 17 00:00:00 2001 From: Danilo Di Leo Date: Thu, 28 Sep 2023 05:42:36 +0200 Subject: [PATCH 3/6] prettier --- assets/multiqc_config.yml | 4 ++-- modules.json | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml index b93885b1..3a6bb901 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -1,9 +1,9 @@ report_comment: > - + This report has been generated by the nf-core/metatdenovo analysis pipeline. For information about how to interpret these results, please see the documentation. - + report_section_order: "nf-core-metatdenovo-methods-description": order: -1000 diff --git a/modules.json b/modules.json index 72de1e5f..8bd3e99f 100644 --- a/modules.json +++ b/modules.json @@ -8,26 +8,20 @@ "custom/dumpsoftwareversions": { "branch": "master", "git_sha": "911696ea0b62df80e900ef244d7867d177971f73", - "installed_by": [ - "modules" - ] + "installed_by": ["modules"] }, "fastqc": { "branch": "master", "git_sha": "bd8092b67b5103bdd52e300f75889442275c3117", - "installed_by": [ - "modules" - ] + "installed_by": ["modules"] }, "multiqc": { "branch": "master", "git_sha": "911696ea0b62df80e900ef244d7867d177971f73", - "installed_by": [ - "modules" - ] + "installed_by": ["modules"] } } } } } -} \ No newline at end of file +} From 7213ca2eb9b8e2b1f2acf3c57d86c241cba971d0 Mon Sep 17 00:00:00 2001 From: Danilo Di Leo Date: Thu, 28 Sep 2023 06:34:00 +0200 Subject: [PATCH 4/6] removed duplicated version --- nextflow_schema.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nextflow_schema.json b/nextflow_schema.json index e05505c0..28709a24 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -177,12 +177,6 @@ "default": false, "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", From 6dd09ab98742ef6a2726ccaef2979276cb43fbbd Mon Sep 17 00:00:00 2001 From: Danilo Di Leo Date: Thu, 28 Sep 2023 06:36:31 +0200 Subject: [PATCH 5/6] linting --- modules.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules.json b/modules.json index 8bd3e99f..72de1e5f 100644 --- a/modules.json +++ b/modules.json @@ -8,20 +8,26 @@ "custom/dumpsoftwareversions": { "branch": "master", "git_sha": "911696ea0b62df80e900ef244d7867d177971f73", - "installed_by": ["modules"] + "installed_by": [ + "modules" + ] }, "fastqc": { "branch": "master", "git_sha": "bd8092b67b5103bdd52e300f75889442275c3117", - "installed_by": ["modules"] + "installed_by": [ + "modules" + ] }, "multiqc": { "branch": "master", "git_sha": "911696ea0b62df80e900ef244d7867d177971f73", - "installed_by": ["modules"] + "installed_by": [ + "modules" + ] } } } } } -} +} \ No newline at end of file From b66ed9a0833e32a68ca39560c53db44491e58831 Mon Sep 17 00:00:00 2001 From: Danilo Di Leo Date: Thu, 28 Sep 2023 06:36:51 +0200 Subject: [PATCH 6/6] prettier --- modules.json | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/modules.json b/modules.json index 72de1e5f..8bd3e99f 100644 --- a/modules.json +++ b/modules.json @@ -8,26 +8,20 @@ "custom/dumpsoftwareversions": { "branch": "master", "git_sha": "911696ea0b62df80e900ef244d7867d177971f73", - "installed_by": [ - "modules" - ] + "installed_by": ["modules"] }, "fastqc": { "branch": "master", "git_sha": "bd8092b67b5103bdd52e300f75889442275c3117", - "installed_by": [ - "modules" - ] + "installed_by": ["modules"] }, "multiqc": { "branch": "master", "git_sha": "911696ea0b62df80e900ef244d7867d177971f73", - "installed_by": [ - "modules" - ] + "installed_by": ["modules"] } } } } } -} \ No newline at end of file +}