From 1135a2b9b233016be72a901172b148cd23cdade3 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Fri, 9 May 2025 03:48:33 -0400 Subject: [PATCH 01/43] Course structure --- docs/hello_nf-core/00_orientation.md | 64 ++++++++++++++++ docs/hello_nf-core/01_run_demo.md | 45 +++++++++++ docs/hello_nf-core/02_rewrite_hello.md | 101 +++++++++++++++++++++++++ docs/hello_nf-core/03_add_module.md | 59 +++++++++++++++ docs/hello_nf-core/index.md | 38 ++++++++++ docs/hello_nf-core/next_steps.md | 39 ++++++++++ docs/hello_nf-core/survey.md | 7 ++ 7 files changed, 353 insertions(+) create mode 100644 docs/hello_nf-core/00_orientation.md create mode 100644 docs/hello_nf-core/01_run_demo.md create mode 100644 docs/hello_nf-core/02_rewrite_hello.md create mode 100644 docs/hello_nf-core/03_add_module.md create mode 100644 docs/hello_nf-core/index.md create mode 100644 docs/hello_nf-core/next_steps.md create mode 100644 docs/hello_nf-core/survey.md diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md new file mode 100644 index 000000000..6c07a0055 --- /dev/null +++ b/docs/hello_nf-core/00_orientation.md @@ -0,0 +1,64 @@ +# Orientation + +## GitHub Codespaces + +The GitHub Codespaces environment contains all the software, code and data necessary to work through this training course, so you don't need to install anything yourself. +However, you do need a (free) GitHub account to log in, and you should take a few minutes to familiarize yourself with the interface. + +If you have not yet done so, please go through the [Environment Setup](../../envsetup/) mini-course before going any further. + +## Working directory + +Throughout this training course, we'll be working in the `hello-nf-core/` directory. + +Change directory now by running this command in the terminal: + +```bash +cd hello-nf-core/ +``` + +!!!tip + + If for whatever reason you move out of this directory, you can always use the full path to return to it, assuming you're running this within the Github Codespaces training environment: + + ```bash + cd /workspaces/training/hello-nf-core + ``` + +Now let's have a look at the contents of this directory. + +## Materials provided + +You can explore the contents of this directory by using the file explorer on the left-hand side of the training workspace. +Alternatively, you can use the `tree` command. + +Throughout the course, we use the output of `tree` to represent directory structure and contents in a readable form, sometimes with minor modifications for clarity. + +Here we generate a table of contents to the second level down: + +```bash +tree . -L 2 +``` + +If you run this inside `hello-nf-core`, you should see the following output: + +```console title="Directory contents" +. +├── ... +├── solutions +│ ├── 1-... +└── ... + +X directories, Y files +``` + +**Here's a summary of what you should know to get started:** + +- [...] + +- **The `solutions` directory** contains the completed workflow scripts that result from each step of the course. + They are intended to be used as a reference to check your work and troubleshoot any issues. + The name and number in the filename correspond to the step of the relevant part of the course. + For example, [...] + +**Now, to begin the course, click on the arrow in the bottom right corner of this page.** diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md new file mode 100644 index 000000000..98805b71d --- /dev/null +++ b/docs/hello_nf-core/01_run_demo.md @@ -0,0 +1,45 @@ +# Part 1: Run nf-core/demo + +In this first part of the Hello nf-core training course, we show you how to find and try out an nf-core pipeline, understand how the code is organized, and recognize how it differs from plain Nextflow code as shown in [Hello Nextflow](../hello_nextflow/index.md). + +--- + +## 1. Find and pull the nf-core/demo pipeline + +TODO: instructions + +### Takeaway + +You now know how to [...]. + +### What's next? + +Find out [...]. + +--- + +## 2. Examine the nf-core/demo code organization + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +Learn how to [...]. + +--- + +## 3. Run the nf-core/demo pipeline + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md new file mode 100644 index 000000000..a848bb40c --- /dev/null +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -0,0 +1,101 @@ +# Part 2: Rewrite Hello + +In this second part of the Hello nf-core training course, we show you how to create an nf-core-compliant pipeline version of the final pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. + +--- + +## 1. Create a new pipeline project + +TODO: instructions + +### Takeaway + +You now know how to [...]. + +### What's next? + +Find out [...]. + +--- + +## 2. Create modules + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +Learn how to [...]. + +--- + +## 3. Plug the modules into the workflow + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. + +--- + +## 4. Set up inputs + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. + +--- + +## 5. Set up parameters + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. + +--- + +## 6. Output tool versions + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. + +--- + +## 7. Run the pipeline + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. diff --git a/docs/hello_nf-core/03_add_module.md b/docs/hello_nf-core/03_add_module.md new file mode 100644 index 000000000..7d8a9ec59 --- /dev/null +++ b/docs/hello_nf-core/03_add_module.md @@ -0,0 +1,59 @@ +# Part 3: Add existing module + +In this third part of the Hello nf-core training course, we show you how to add an existing nf-core module to your pipeline. + +--- + +## 1. Find cat/cat to replace collectGreetings + +TODO: instructions + +### Takeaway + +You now know how to [...]. + +### What's next? + +Find out [...]. + +--- + +## 2. Install and import the module + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +Learn how to [...]. + +--- + +## 3. Wire up the module to the workflow + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. + +--- + +## 4. [anything else?] + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. diff --git a/docs/hello_nf-core/index.md b/docs/hello_nf-core/index.md new file mode 100644 index 000000000..d47e459a2 --- /dev/null +++ b/docs/hello_nf-core/index.md @@ -0,0 +1,38 @@ +--- +title: Hello nf-core +hide: + - toc +--- + +# Hello nf-core + + + +During this training, you will be introduced to nf-core in a series of hands-on exercises. + +Let's get started! Click on the "Open in GitHub Codespaces" button below to launch the training environment (preferably in a separate tab), then read on while it loads. + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/nextflow-io/training?quickstart=1&ref=master) + +## Learning objectives + +You will learn to use and develop nf-core compliant modules and pipelines, and utilize nf-core tooling effectively. + +By the end of this training, you will be able to: + +- Find and run nf-core pipelines +- Describe the code structure and project organization of nf-core pipelines +- Create a basic nf-core compliant pipeline from a template +- Convert basic Nextflow modules to nf-core compliant modules +- Manage inputs and parameters using nf-core tooling +- Add nf-core modules to an nf-core compliant pipeline + +## Audience & prerequisites + +This is a general-purpose training for learners who have at least basic Nextflow skills and wish to level up to using nf-core. + +**Prerequisites** + +- A GitHub account OR a local installation as described [here](../envsetup/02_local). +- Experience with command line and basic scripting. +- Completed [Hello Nextflow](../hello_nextflow/index.md) or equivalent. diff --git a/docs/hello_nf-core/next_steps.md b/docs/hello_nf-core/next_steps.md new file mode 100644 index 000000000..b8108e996 --- /dev/null +++ b/docs/hello_nf-core/next_steps.md @@ -0,0 +1,39 @@ +# Next Steps + +Congrats again on completing the Hello nf-core training course and thank you for completing our survey! + +**Here are our top 3 recommendations for what you can do next to take your Nextflow skills to the next level.** + +TODO: UPDATE + +### 1. See how what you just learned applies to a scientific analysis use case + +**Check out the [Nextflow for Science](../nf4_science/index.md) page** for a list of short standalone courses that demonstrate how to apply the basic concepts and mechanisms presented in Hello Nextflow to common scientific analysis use cases. + +If you don't see your domain represented by a relatable use case, let us know in the [Community forum](https://community.seqera.io/) so we can add it to our development list. + +### 2. Delve into the details + +In the Hello Nextflow course, we keep the level of technical complexity low on purpose to avoid overloading you with information you don't need in order to get started with Nextflow. +As you move forward with your work, you're going to want to learn how to use the full feature set and power of Nextflow. + +To that end, we are currently working on a collection of Side Quests, which are meant to be short standalone courses that go deep into specific topics like testing, metadata handling, using conditional statements and the differences between working on HPC _vs._ cloud. + +In the meantime, feel free to **browse the [Fundamentals Training](../basic_training/index.md) and [Advanced Training](../advanced/index.md)** to find training exercises about the topics that interest you. + +### 3. Learn how to use nf-core resources and the Seqera Platform + +**The [nf-core project](https://nf-co.re/) is a worldwide collaborative effort to develop standardized open-source pipelines for a wide range of scientific research applications.** +It includes [over 100 pipelines](https://nf-co.re/pipelines/) that are available for use out of the box and [well over 1400 process modules](https://nf-co.re/modules/) that can be integrated into your own projects, as well as a rich set of developer tools. + +**[Seqera Platform](https://seqera.io/) is the best way to run Nextflow in practice.** +It is a cloud-based platform that you can connect to your own compute infrastructure to make it much easier to launch and manage your workflows. +The Free Tier is available for free use by everyone (with usage quotas). +Qualifying academics can get free Pro-level access (no usage limitations) through the [Academic Program](https://seqera.typeform.com/to/SRB8Ci3n). + +We are currently developing a short training course demonstrating how to use both of these resources (either independently or in combination). +In the meantime, check out the [nf-core docs](https://nf-co.re/docs/) and the [Seqera Platform tutorials](https://docs.seqera.io/platform/latest/getting-started/quickstart-demo/comm-showcase) + +### That's it for now! + +**Good luck in your Nextflow journey and don't hesitate to let us know in the [Community forum](https://community.seqera.io/) what else we could do to help.** diff --git a/docs/hello_nf-core/survey.md b/docs/hello_nf-core/survey.md new file mode 100644 index 000000000..f88bfe501 --- /dev/null +++ b/docs/hello_nf-core/survey.md @@ -0,0 +1,7 @@ +# Feedback survey + +Before you move on, please complete this short 4-question survey to rate the training, share any feedback you may have about your experience, and let us know what else we could do to help you in your Nextflow journey. + +This should take you less than a minute to complete. Thank you for helping us improve our training materials for everyone! + +
From 0f6b03e532d4b66966ef636d8700738e5300b1e3 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Sat, 10 May 2025 02:17:35 -0400 Subject: [PATCH 02/43] Add contents --- docs/hello_nf-core/01_run_demo.md | 137 +++++++++++++++++- .../img/nf-core-demo-subway-cropped.png | Bin 0 -> 16075 bytes docs/hello_nf-core/img/search-results.png | Bin 0 -> 103853 bytes docs/hello_nf-core/index.md | 10 +- 4 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 docs/hello_nf-core/img/nf-core-demo-subway-cropped.png create mode 100644 docs/hello_nf-core/img/search-results.png diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 98805b71d..d937098eb 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -2,25 +2,144 @@ In this first part of the Hello nf-core training course, we show you how to find and try out an nf-core pipeline, understand how the code is organized, and recognize how it differs from plain Nextflow code as shown in [Hello Nextflow](../hello_nextflow/index.md). +We are going to use a pipeline called nf-core/demo that is maintained by the nf-core project as part of its inventory of pipelines for demonstrating code structure and tool operations. + --- -## 1. Find and pull the nf-core/demo pipeline +## 0. Warmup -TODO: instructions +Before we go looking for the pipeline, let's create a project directory where we're going to do the work. + +Make sure you are in the `hello-nf-core/` directory as instructed in the [Orientation](./00_orientation.md), then create the directory as follows: + +```bash +mkdir demo_run/ +cd demo_run +``` + + + +--- + +## 1. Find and retrieve the nf-core/demo pipeline + +Let's start by locating the nf-core/demo pipeline on the project website at [nf-co.re](https://nf-co.re), which centralizes all information such as: general documentation and help articles, documentation for each of the pipelines, blog posts, event announcements and so forth. + +### 1.1. Find the pipeline on the website + +In your web browser, go to https://nf-co.re/pipelines/ and type `demo` in the search bar. + +![search results](./img/search-results.png) + +Click on the pipeline name, `demo`, to access the pipeline details page. + +Each released pipeline has a dedicated page that includes the following documentation sections: + +- **Introduction:** An introduction and overview of the pipeline +- **Usage:** Descriptions of how to execute the pipeline +- **Parameters:** Grouped pipeline parameters with descriptions +- **Output:** Descriptions and examples of the expected output files +- **Results:** Example output files generated from the full test dataset +- **Releases & Statistics:** Pipeline version history and statistics + +Whenever you are considering adopting a new pipeline, you should read the pipeline documentation carefully first to understand what it does and how it should be configured before attempting to run it. + +Have a look now and see if you can find out: + +- which tools the pipeline will run (Check the tab: `Introduction`) +- which inputs and parameters the pipeline accepts or requires (Check the tab: `Parameters`) +- what are the outputs produced by the pipeline (Check the tab: `Output`) + + The `Introduction` tab provides an overview of the pipeline, including a visual representation (called a subway map) and a list of tools that are run as part of the pipeline. + + ![pipeline subway map](./img/nf-core-demo-subway-cropped.png) + + 1. Read QC (FASTQC) + 2. Adapter and quality trimming (SEQTK_TRIM) + 3. Present QC for raw reads (MULTIQC) + + The documentation also provides an example input file (see below) and an example command line. + + ```bash + nextflow run nf-core/demo \ + -profile \ + --input samplesheet.csv \ + --outdir + ``` + +You'll notice that the example command does NOT specify a workflow file, just the reference to the pipeline repository, `nf-core/demo`. + +When invoked this way, Nextflow will assume that the code is organized in a certain way. +Let's retrieve the code so we can examine this structure. + +### 1.2. Retrieve the pipeline code + +Once we've determined the pipeline appears to be suitable for our purposes, we're going to want to try it out. +Fortunately Nextflow makes it easy to retrieve pipeline from correctly-formatted repositories without having to download anything manually. + +Return to your terminal and run the following: + +```bash +nextflow pull nf-core/demo +``` + +Nextflow will `pull` the pipeline code, meaning it will download the full repository to your local drive. + +```console title="Output" +Checking nf-core/demo ... + downloaded from https://github.com/nf-core/demo.git - revision: 04060b4644 [master] +``` + +By default, the files will be saved to `$HOME/.nextflow/assets`. +This can be customized using the `NXF_ASSETS` environment variable; see the configuration documentation at https://www.nextflow.io/docs/latest/config.html. + +!!! note +To be clear, you can do this with any Nextflow pipeline that is appropriately set up in GitHub, not just nf-core pipelines. +However nf-core is the largest open-source collection of Nextflow pipelines. ### Takeaway -You now know how to [...]. +You now know how to find a pipeline via the nf-core website and retrieve a local copy of the source code. ### What's next? -Find out [...]. +Explore how the code is organized and why. --- -## 2. Examine the nf-core/demo code organization +## 2. Examine the pipeline code structure -TODO: instructions +Now that we've retrieved the pipeline's source code, let's have a look at how it's organized. + +```bash +tree -L 1 $HOME/.nextflow/assets/nf-core/demo +``` + +```console title="Output" +/root/.nextflow/assets/nf-core/demo +├── assets +├── CHANGELOG.md +├── CITATIONS.md +├── CODE_OF_CONDUCT.md +├── conf +├── docs +├── LICENSE +├── main.nf +├── modules +├── modules.json +├── nextflow_schema.json +├── nextflow.config +├── README.md +├── subworkflows +├── tower.yml +└── workflows +``` + +### 2.1. Examine the pipeline code structure + +Now that we've retrieved the pipeline's source code, let's have a look at how it's organized. + +You can view the files ### Takeaway @@ -43,3 +162,9 @@ You know how to [...]. ### What's next? [...]. + +--- + +!!! note +nf-core tools are pre-installed for you in our training environment. +If you are using a different environment, you need to install the nf-core tools package as described here: https://nf-co.re/docs/nf-core-tools/installation. diff --git a/docs/hello_nf-core/img/nf-core-demo-subway-cropped.png b/docs/hello_nf-core/img/nf-core-demo-subway-cropped.png new file mode 100644 index 0000000000000000000000000000000000000000..ac07a8bc49bca61e9230745f9d4173f04b807d5b GIT binary patch literal 16075 zcmd73WmsHG7B1YlL*oz}f`#DjuE8CGYjB6g-7Po-4esvl5Ht|n9fG^d*Euui%-nhI z_y0Cev#YCWmDj4;@4HsGqP!#u;yXkD0DvMbC8i7jKiPpQ(_L}-?<_6a-jYW11$fD*|cH!fFB6}%N@@Nl{Xf-48D984U1jQ`h^|CJQA zv$k_ou{Sg}6#!TCm&kub{crBS>e2d7JzK?5Qp;^E%@{`}wZ-@b>bsi_OPI-JYQpc>p}!?_OTt7T&u;< zo%K0g*!MZ+=?s23s5-iN?yTc(ZB(dR&4Mv?8o=yj-FtD@pxNZ)3t z{+>b;bjVZ{TK3Puqhk&D8;qTghh?8tcbMLPusjZw=;{jz^iXvTk4P~3XP7d?0M6sDX-gNYZEl>+=eCej^OJxqk z!Jgj3plp9vSP@KHdeHfF`t+RTdHUlEB#Zb@)h{&}Ca|c4cwgqqweB0R4cE87h0*uK zM8t&>I>NhEb_QGNYAek=YjHTs-^co8Lhv9bu(j7@`~~!kKQFIS zR;p1YfSz#_a~{ey6PZgm6uCX=9X*_;I15GiJpTWr~h6w2owMW|twK_A*6W6KwDP_UU~`VAdys{|LLs#)Zv`%eaqdiey^P3e9H#`l!^V# zmqBXS8w9N)_3nukzFIPaYFa5zVs_A^=e1?8V>5&Rb=E~~hsXmnGq1kx`kPfHvyQ2( z*Gq1G!{g(8*Xzv}Pv6&@Dql4?oOQ8LxUIzgtj!F=<7A0XRom}4tiSk%N_p5ManN_e(%V>rY5zFy4j z_R3R)^Ddd!4vub4aDUQ?M?aV?n;)W??&bUC-0s|VL*$*7S{bZn7_bdYO8;t*W-+O* zt*!lX=KJ=Z2@?}4Kg9fqCle={?HRLv#(!E` zR(Z(fu+=Z@DKg`OH~$LqnZ+@4Z*w`~sBUh)7ZWgGf88xe(eq%YD%&FQeXgN!?Y#}& z>@Y^Zq1mK})I^MCEggstmzmm&^xkvb(MW%2Da z*iAXS7$VW=dVA%qnt~&=@1|b&eS7hgbsM2Ax_&)rS}(s?YIP~|*pBAJv*teTR%W-F z_NjYpl!$%@l?~P%m+%E1(ELLOfT+a>yt1Nu1Fxro%J%R)6uE~2z{_b;FKU0-r`bm9T`+{ zvFA%n$S_+dM{qNMt50LEj@>U}2|l>;(D<)^Dws`X*+g?*IYKxsSrm;2&Oe&@^%Zny zJ}WFY+7#vTv!4sNX0w`4I2U54I71&&cJw2DIPXIfBoqLhZ-^JK*~BRc(=V%>P=V+_ za3*l;wW7&=V|yU<^^yrdC(9gVqlF z)gY(DjK*YG-7j}G&xf2d@Pu?wghl8`#1gINp^%%*Q6t#O*)~dHex3rT@SMiKvD5iU zuSb|`Z6M-bEa|^q;>z==ujqN6G~gu!r^%kUsFJ_&G~x!S8i1Aj%IeM`X{iTFocemJ z>&d(On|XcDAY=2%s9#h(zpsJcVZa9(bs*VAe=|OE9D`AY7Dp5n+LY<}>p15+vTMne zWM<5T0{=~73BlR4SwuBZUnvGUq@&6oiJ{p5KoHot7YNTx2$zX^wg(s0)M9k617|&cG_O07N0XHK2#l@@I zZMpB*2dE*ZQKe@SzCOW!r{i)xC3$r4Z6ii3Y_~dn7dLaGqG|10v*WG|2A>j9GZ6$( zS>+_Wo4qY<^Onh;l;P)Osw`GB^W#*j+rdxaxT{>Eurh(AH}JrI#{7_7)p5II+?|Hh zBV1G_cK5-0Gz5iU7~%Z{SweY7=7!l!gJ57^0|z0mCm+|l5Ks5lhkd$(rNun@m_>iM zZx0Q7a3o&c;mcgfC|m3%?bT8ld&=gv#2kVWOPYUX;2=6d-CuJUx4nj;V}f^pSxlpZ}DIlyA930AE38BJZ`FR*el?*@v%EEZ@rI^! zjnvA}QUv2)ygpW6PYRPHG=0gAvzsKpr5MqveLP?dWIyaY_c<&n%1Q`uNF?7D0<2U} zbK{`mic@9lIZui6J2VlBhi_zQ=3*U_*Y{&a;?Qp)uIzwMpvj|y>&F;A>R!8P=$C?x z+y!c;Or3RZ3oZ8?Ix&YYL82`Z)`8*7Vmi6vF(yAK@>~oZ)9vVj9vOHhECT8&D?n$a zDY_Dk&@IBP<8wqAQQV}+A_Ce#Lpd}Bb6;4uQj)Bbi4AyfW=XTs3DR79XBT$(`8Q8Zj_@6%5`tI}hqUGvyb!pAw?%TxnKMir%=Ghha>HzA+zaY! zP0`#3J{*0AAKhf`9LIr*4$(iRzMT+OF~Jvyrl2{5k;x=8>UXYJ>b8}~R^V)IqUD_n zGiiNKpPlD}<{!Sg#W&BGg2Y_<_*v(*-Tmqnjqc``jiGB*n%B?4vt#rnw18vt6G@_E zTyp78zwr_RZlVSv3DJM7C||7q!9M?nFs@Epd=9 zTw}DN@A}H`0B6f0a|rEkQ2Y{soc-i*{;3EW72d1c!fr!=F_)W@&Y^<9VN_Fc!@n9OS%d>@Ma4|YZ@+Ar2pS&F^TaHmNUwD?Ly zNUacb+kJ0xtOem(bEVIW3*MTG4sF8K85VAxy65cP(MR_eph5uEC1%3ZntEjpS%Bov zObO?Qs6;9Ft+%*(pv|+TScf`i-)3Xm$LrrbnsVtJHtp`!?xeXo(XsAm$6tAoTQwz+ zZ-z*G+lN@RMLH0q5zi zsu#%Rf%x>XgYBh{%0G^jO%Te^^Kdwhf?k9~EWV?7_7?7beRdM~n|=OugCFgz4FM8v zoIwwnti3m!EkPPLF~z*Oo3xbhjYP^aOeoQO5E?oW-|o~%Y#z%^;__KAHAhj?Ao$Jh zBOg$Kp=P&wQGAo1#mJb5;VzN<>nt1eT2ai;i)V#$H^Bnj;Ii&j2KSEbyKO^%CYzW|NpEy1{QDg+Ay5r_O2sh70&j~#DX!l+R z(jsk*b?8jnneuN~>+WL3BIIFjuQEgkF@H}5rAFeFj|#77@vMU{P+lphllf1d7(1MU zPDRgcm7ss_KZc_2Pp~!vWysoN7lO|HRCQDT6+jcAM%_wB5yzG&&pEM(y# zWgmfBHWyuX@7@cQGlenJ5|`1KH=txJ@*bnulfs4<&Fa}J(MmM6MV=cB2=5SF>Itt` z!iI6tp<3MAraSaN?y+RX<_>B~micY-$rObaJG_5JM-{_r`JwKOR|n+`Nz*S$WEaCe zq_ZC^H6yULeqe8JIQh$Tg#QL!sMcWc{jylJ(Bw7B3t@sR8xeKAiTc+PQ(>KHcwURb zz$-Gm0(W4~g|=Z?yld9j$O2BKflYJ8OB(OHlNA0_@G*Z@n9W?$1Srax2Oo&@4!;WJ zZo0ioLezZO7vQI*=K|rkgZ6i>N1myRr0m_3DV{NjB;%<2c@aM+R**&zG5i1ivIOZV zC)oW~cpHesi4ie1FU(lZ5tb;TqqZR5pB}DMRMG88%+=RGids5wyZIp?{)v2*7>LyG zPr~@Yl7KHRCL_Gd|I;8}%!@xtk?)ZssO$Cd2-`JEyRrR#EHhF2SgOF%o;QVQrN_cG zX0x6vpV5FSS2hxJ5ekN|Gix_7Pfm#`)OP#<7osO;d z)N|B@KmC&aVArRw$K30nEj|p2#b+ISXBYIjg?of(56TT`G&QF z08t)sH+R1+>XG%CYQe2F=Jz=Sn!(U!@cr=IPJeggD|HsAxBS}hXdhn}^|HcHKu<@y zkE>|)Rs&#E>B>{l=Z$zJj8+~knt|=S6DN5os$hBmXdHk$4@FsVnb+MD?6cR~cwR`# zLC?opa;MzB4o3Lsm#OV~IuR?YA-w|kMbgiMW8sj*k6HVdZOC%$HN?;nTS$w}j?6gn z(rG}`ia!zyu>c*K4R73000L(&@&>Q;zC-gQa`Hu-Xa=eWd#YI*>ITvF)w}`M(Z}G6 zrEkazb#+T2ZnYJm9=H^8fHOsY&!vqm2kdRoMP@JRDc~gUH`AISbQAjNpg+Mf8XB2J zz$77KjQ6lFfH#8o@Vc74kp#T_k>YUR>qmkL9)S)#uaRc$WIU(_hS);O6o+u=e6+a+ z1^1a87C*H1c*EaHP?0wAcy>~9pn=^WOE0L!u?xh`&jlcoqxS%n=~}$%y3BL(jZO1x zN)&P0VRQ(4A4Fh}^j8U-ypDr8vA|VxQ9>jU(qjVMVMlxXU}GSB2r1Iw?>l#>z4AHu z$URNmU$_#VoL3x%P69!W$j*!CZVPznKt0%S3EBHR-AfzsX7tIR{R+SRI^Cro;T=^S z{G{!}%n{w)IE|Q?cVxGG*H+H?Ix*bN{af`*QxhD5NJ2KTeO_J+=f@?u?gtk_JK(-~4*bw7*-EOrA^%o< zpim6Do=$yx67mlDYYs8e?x+nnP!EwD=0(%u)FgWwFV&=BA5Jzbvqf1}-CtSW8Twqw z7H}a!>}umc?%?p1)tg|~cEaJD6mFr?hRoSGi!enKl+GWkedza(yyZ!-cwD>*gO&s zC0zrjL-*%5-)`RPrt^DMfjS$|`mOM?s93bTi=Nx6%hN`eBCbx)S;p2y7Pj#59B#y+ zDELLYpWGN?j72u&=83a!DDr_Uq&(2=93FHB88Ys-xz_!~j_;~Z2CCUDvb2=0Vc>VKH^r|I-k|i0LC@J(BZ-bW?8dA6E{oXhgl}seM-}MX<5&DsO~fFL*3Hae zC|ny<9Edu5HbPwbt@tPa0%O=_xpL1%ZKs8FB{}VYu!~SnyMppaZ*K>;>W5}L**4a) zh>?+tUbtQiG6?)%>ViIAE231<^%M-)W_*w!%E;(+dy=nnXrB z#x zYXdt>#)8-u9`dSZS9x^U<8F-#%5uU=>#`6g1#bdZVEjSofY2Oh_s+hEP693t4WlXL z5bS4BP)t*Ch8!BQTh7(n5K=RC0K668rO0x_vOM<(g2Q64UzD3`w7K|A2-Oz@&CmW$ zS&Enjz2b({6%(F_d}xQE&a1RpY^uhldmzJxozYCE3GTw|>S>~$IeFX-<=DTe8O255 zFjhbyW1~dT^$4=J^!=@HlADLP+mRDNgg| zWa1j)*46vvK-`3_hfOGH@m=JN@#cse)AIkY9=O1;bX!C?%2UW{D_V_`75n2It-oo2 zVKwL=94Y&qZK`&IG3HY~ghsjAe{lQ&p_OL6BxIkBESCABx3i7^b) zUoiw#W5*@++W+QO2!K*a3^*~Yqo&&3xSQBb+z5EQ`7k>f&U6};JCTDY$I=eZqRyUA zhqZE)H8xN5e7gwxP??Fa`-)_Q1bB4x0T4e#QhneFT^9@om^2hiqEhyL+NV}dH&day zGWcOX04A>J1`1PFR!)q8C5j5fAE)-WHOsv+W)xw4)1o9jhM-_`k9wt?+`Oua&sXs@ zRNl1)_bkuhMRI^_W1QsI2RhIy^Lg;FZ!1i)W?&1NnDpy~bb@dNJ-~R}6O-rLmlopw zRDK$(=Ot|xUa_}En`QX{Bmqh-65QlpHx*^IuvM~W@M~w5)ys2EYwhl$%pCQzMl0aA z+fQ#xuqf~N9J(RkWP3Oc^}nu7X?Om3PrMGF;WDRn(*-7JjKSE8ATZ%aN%u;5?WP78 zxqg61zRVs_V)hYfH z!iOPa1*G)cU~ao%18_Dha7wi9H}Oz2@@v;4VPIhPzN)F(Hd(|u`4EcMoJX-vG&>AN z;dj!?1NHn}-#Tda3gC23e@JfpQV^_I14Fx6>3EZE?f65l86yv{AN%_^ohH-NlfZ2d|N%oL^kSbI~{q}({}3gKFKHYV9d z2Mq11R}Lr4h$w8Sk7)4dWF9Vf--(Ne^e~pXZuQf$Oq3DA!`&ZSF?WD(%t{& z_&v1MsDaXIyXIhdl5v9PjSqt7@zZLZ`K0B$aBzVWyOMC2?t2Gd;n6dyg zyXDM%Tlw)zv~-j0st2VNUp0oeS{S46_EejjUFDV=m9cD&TRj3CHs{Se`46HP0DKiM zUra1xr7;*ufb}S+rd|IfTaHAiH~lFqXY;wS>bP zmoLQu=?P{Vn@N1`4ZUhdf-AQJ@3&X!`ZbKgL}olUGq{~Vu*S+d4h|Qzr0s?97fE;- z3$oLvFL&$Nu`3cq3^nm1PQrp!Yr1Z$4PO0W==3j%6z-zv-JIrUKY{zy&uCugdSG7b zF~#WzMk#uCQ`gmQioSd+ZJ`royM1^4v#?RGIpx6yecGo|zlL1r@Wc&OQv2IQbF}Wz z{tVE!ex4A~XD@S%(b4eS%GSfuPDOQZa+pkh@7^E;V--bPbzJu$BDa~B$s}tH z=&4;syUqu*7Zco!vR%X4^&#VR-Tg<+2jTizp?BZmj39#V89VQLF)0RJm|Tw+;`mIN z(NHEf-oM%-7N6j++(lVEvQF~-GP>OPQI%i1c{E?u)#&^73Z<;()0p(s14qKvZ}RL~ z`UoSxBZw;ae7>#nC*cz+8N)Xc8X58RYRp<5XTwgI9z{y*-F<^J8~)oxm&({Al0D~d zLoyT>yYL2gdDI@=E==b`3mVSpz`U5886PD+M1W#`S@XDCCS{hzA0$c-1; zMk=FLgm0>I2^6NDYX0GDS&>0KJ*C8wL4x!qq0G%u75GXOiS>AP^K#x0 zKS4*gyjwti>UAA2Kiy0UxbDQeZ2y$+$&}cMjYDR+*&A#*&!8Y`irwh2J;yY-?kY8h0i&v||Y&^|b zrBJ~WG1EmiiWdSx1-;aAwiIz_NON7jh)v#b$5G&A@6+~$F2NZVqhSp?NUuWms7vo1 zuc3QH^Wdx=O{cHT@#L19B)~baXdJaN2sdKy0;XE+W~y+C&x|Kj?W0^NaKP(D7t zM}6x?0MZu+ZF>Dq9bM37@FBRnsM2{P27(f+_I)UacD_&PmdlM5H$A%~ILYO+iL@%^ zwGP()0fwBj^%lpOBdN&PF^xY5=#m9}osbaPt8pRn;`eTALga2M@^;E<16@Q_Pts4F3 zMpeaaCh2Z=wMl`a=ZU4?m{~kf{0ej=vhXGQS)LP#J@!us>{R3qvD6Y#bek~b?+=-w zs*k=K)UaD#t$|hDSJ1+o(n3%EACffq%4dRmp9@3JAo@dwFg7#Oj)3#;>48>z)qq|Z zb@Vas+i-e(O3qJh6Pdha^ZLHJ4YsS=aA~{~#46s8*Gfagp5*~tVt){4s)?R$i>G2y z0XiB*oO5}UW^D3JX7q)I;U~%0K>|C~p9m2&m_r3Lc>AheZteDB+a;{IwI9)F^jaZs zX4;t@TDUy7!iVKzsWYA9d#Bh-y`OI9+(>auScp&IlggLNM;fu)sH+EnZpX6q!HJ%p z*9V3UAuUpC)ezeReLq0`0GjZr4H>F(g-~wJ51RuNP$%^O?wn23ArGx6)R6f~-Q|cv zHh(9#k3|R+(`dIIIg(b)Quip;ZFw;~GMoKGpOky-iLHu>!Ua9?zb)o&C)ii*7a(i& zB4I|F2LwQ)I75*iK0WMbi}uP3JeY_&|Ar^*$TQGXh;uS^1JiV@Vz?UhIwzHyH6ltg zeti+5>YZFwGI5866rqqD!yg5G@g%pRVvJ;X0$#eI(C~62p+0@UlW8smuhzK_-)JfpmNxe8 z-!=pA;LO@6nKvoBY-^f-!nk-X=5XwW4-rAwM`KX63n3jFvbJfUyN!WS&GKXhUCUjH z8nYA$X4}zZdUZnUOYUSjazx%4dfQ}qQ8Uza1(q)#hG%b-tLQd^$97FhLY0R(-g`19 z^MaxLiuc-~5&g*=a)9p?l&)w4!gX`$3{YD%$k>ki}girNyvNYiCyk^%-)X@ zJDAoxQZiupuO4-TFXA%lWiV|9YpaNWdKQX*11%uh#15xFF{)Lcm~XA}fz4aO8qRxd zT_V2+uwr848e{Kv&s<#B#7f{uTq1d2hwx~Emv@6qj`bawsb?wZ$tk$7Ler36QbM(Q zyT8o0$I$X+?j}}s3EPdZ-D0$Z*`)Gw^{V;HTFT7%i)vrEl}CA9xvB}P4!zOQWM@j^ zHxj+UKrvhwPzM)28Y4|h5yG+jDCBC9>QKjvj}eF6nzbo4eK&VzJbVV@Ns-jLdk@jI z7^gVJJ3fNu?HHl9c*@TT!Ca@#Q!z3>xS|$3-fyZYE9X`=Zy_F~ja;cV|Ekr2Rrc3Y zsf2cVeSU}-8E@}=x&2J1pDTz(ZE3zA1BQ&WvR$p=P~lN5vSa!>hWBEZkyJQxx3M`b zFU)By)g|4vh&+^Ls0>eMYxErA;IY`t%G$%4jvw*k3KB#6>i}ibs)Kz9k};vmbQ!rK zz3owOHzXY~;m?918_`eShqs@ih@RH4K4g9drl^LN3V}_zkZ8jnwSG9~Gd4p+AQP6N z_5kZ>iKzn1<=l6C)chhPRMPguwn0fM(o`jFp~vQj`iqQh!~p-PEumUW+O7hDBpe7w zY!|SZMu@5qjJacDecmPCVmKDWmMmx8C*h$XC$P~{gY;=(Tf>(r@`R9+*xuc+kngm8 zvkAENI0kKIU&F+J#dDQ9a7^lYj)L3{Voo!L7hA<`$_vB1oP(dz7UK)G+loEiWI0=` zXm#FxiNW&U>dI_7+?pS{s##=Wl!SMDi@Mgh;UZp69DacvEX(Ny}K z8#F0%#H*6iqWivVyFddIbZBo4B z^@B2nOI$n%o1>ieF&4czp9nP z!!R|SL=c8F*uca@3bBm{ZT!S2fJ`(*^OozN$OiSKc{?tH(MPHwP$VbUk?-vQ3KqFq zmiH@OUfu+5(w6(c(eUoHEC29I>*WYtMJ;WBPJ}LwSK|@O`Sv6NCpPL;ZBfA{tz2tN zm7eAI zl;Yfg+W6YlUEkghFs9+-|yK zmInL}+pJ(q=a;b;s`X0V-B`iRRCSe9pjg6LCe29Vm#JlLJ^Euaool$^W`ECYxzqE9 zLBc5wsrZ&W{}QM?e!GfwkKr(m_8X{3!aVz?T9lOuiFn;i_2Z50VCeEXsxwV6f`Cp| zlUp}%-7yw{QK4o3E<%G!)?3k=>x)8Io8A5SX2c^@Jz3Gfwk*HZ+{b}vd=I#2PlR1& zy%wIDkspKir8ezOJhoso(CQOcQ`oY`#MR!o3iy?qqbQ< zf5bJp|9DBIT6A=Pj+1QhWJO7)`z`cknWUoHo7aVgx9k+1eTdkpv-qI+RP_x%8iMo`)c*bXLwv@xy_p0{Y0*J?GCB=R9uc<>-G_yNS155SQYu1u zqe|SUPa@
{@zZ>@z;3lMnkXQlJjgmcmy1Im@I#u|kzSOMq-v``YuB3o&&ZSUmx z_J{O9%JM5fB}U3^e;VCyf8CC-dL|UZX(4u6a^k;wBL8Ohw1SGD zDuw?n!D%twA7n_8O^fEyMnyQyf+>%7{Yy@#Uce(-w2hyhJhx^bUWDxKY<->QP_m1O zbUN{6db53bAR7Flm}|-+Lh#v`&mK|BPzgd1edtju*%ix^*lCP8xxgdDK|_8ri{GW* z=(Fn3ooiofJhE39*D5a(5pAn4YG+sUIIJ&|qIi6Ug}!W^K5Q>z?lk*ar+i^32?wn#2jX`U{wWIR)ucGw! z3y@UxqMX!MU1_Bv%*c!(p^aMgC)?fjr}}{M_ACy zb{*BU6tt1JMVEDC$cW9X)dY``&et$QzBfHv5riBL4Dj8+6Xb)f&J&Ex83RHBPT;oE zZ|duOS047@fwhPTkHD2zvCm%{oY2%{ig}E>ihDQuY%bzC-)-(bDG0M7&-CQvSsI}!N3zf?37HMy2oD?HoxwT0Z1fJbvvTx;@u{Bhg# z$}vkEFyEWWas7*1P=BU?@t7@7fcg!-B)QFEt*N}rqc%$7t~)0Csain!ZM^?rp6YY2J zZ1QQ5juaRcJUOiwmxH{uP|G5m)?bUov(N#Zo9^{VXZ?J@#*M&Gx|;an$jXyvFP zG=cjKqMxC1a}&8l3w*N|HQ+-4i(m)3nD*d3M@UbOg6t4?NWX#zs>@ddiU}yl`%@4; z8n)cfD9PxUCX5{_7Gm&eJ(#W^_+S2i%B2V>BuD?x?VBqrl9;*^ypd!mW$s( z>jT)3Dbmwim+J#5-kKK}WLoN*m?5Tc-VER2{s2jc2GlkFPB0JGn&s0^j<{0yWS=Ot zA4=k*8CF?bEEx+ubu)c1mA5R6W@dApIA!c0f7VvbvIbq020w*OR4Qh~2^zjm4`r*A zKKI4y9mUT42|xUU??pogfe~xNs#+vHVOHVD>!wo3ovhu}-sn;-*2Gl|(Dr$~J72L1 zY}u~BLluS*#;gV4c>ccNvW#|HU~(mjRo*V~=auiw$O@!;@L6DkJ(hm;BXHWLJF^qf zBfXcb5h9@&cc|+lWKaDa1lSxFb|g##@e*lblOF^&$Mj|<7J;VbfmgaT%!*MBiI6?B zih&%mk~WFM*a`xKsVZo;MZ8lwkSPkF{0QmKj!xzPow1TL>c)E+@dSMd8C`vr&&-j{ zHnhRchy;BLQ8J5zvzVUOT;Ntpy+xxIKNtw0f^jTm=naRVFo-S2MK$cgTaJ1n{b*zu zF#Cr4bz30-Z?D2U%iSy(*ndHKLT_UqPYg?Wmfrb%If_}$yzi0f!rf2Hd9Lq&(M%N7 zQ?`)QK4(icNZS!K5G(XG%p1XIImLW3t3!~>ut)^rfhDouPNn$?S?r4S-nTvELX*>G zsU9#$!xzm*jo?qr?=co~2_i9AffqM=tsV^8o_?p1zH!Bq#qqVc&#Rths+~J^5_4$u zh6JKo(to;x70y4hX>^{nuWM@@OTnYu^1KTpMuRW)LGIJo_gH243k|Z^F0Rw&ZC6od zP*YT(z6s`N>;+9KpSz=6hK>>xCW=Ba&FNal6HX0G5?0I`+8rJ)0$hVD zwVee0X^hMcEK8K8LF5~5uKebHE-gqbtUg68}(E*3kB?qhVvG!XDbOdDYKa z+y<;H%uY_6@>kXqh&gG2 zM;zeu$O<8YxpqXx*}}^saIjXY*ZnSy4@E2XcV)$2pijXBQ7PE?7(dYUr%Aw1$Xz_^ z;EQA0z7P~ElF9q#Q%mO$L2VTFVTbk^6`-n^p*hdlW@fhwKC3?i5S8Xdymj2NS2S-W5`-; zezqL42q=A10ceHMIH|mDwz{ge#j7XFO~%Nzzl4=0t})GxU<{VS82StYkfkB{F&|11NeG^svDKcToBD`T>I+|(-F=$ygu$nwfoEx*@51a^8|N9?rV zKb#>BB$%WMzk463DQ+g~6$p#sH6`qTsR#BW7+cKa0{b_KQTYFHxcTEq1nsXQNosBZ zf!xk0+5^t?)ne~*RKhin>z~r*(T5iwelikaac%~4(x>!HEOzcc;z~R+{%e-1;r#1q zlg~Sihpf54WdwGf~QblHMz76q^4S5HNpCm41TxR~M8+g%~Cx zJD)OWHXB%7wl%pwgi24A{qS7R{KHvt90V|V~0pcGbG)>++sAgW``>M zKZ}h6+R;JpdUYS{p>&gEbN%aQmd>%$JAuGX~;jV04uuW4nF2KD5Y&iW=v_wzsG`964>2-uM*=tyC%1Ot?<30p6eOO zc_|!|x)kgs@O!Ad6@0Z_Udy&SM%(&lR2=dBYin6p8ULuR4}yY9dh)vxU`GVsdd{Ce z1#i2Z?>%!dveXh;=fQSZU3NTvNj(2dtgO&ReuDf~dn&6l;7PkeyCKVGh>Gm89t_zF zK2>n_T~@&?d}1vhpVs-Og{JV%Crd80h;Qts*s8q&ZEk0J%-3kv+Pm1;*xsnJj{eiS zUVCZgr)mg8`rqFFGjcSLp~N0E`$GfGwOPc)`dzR4`M%GPr$v74aTJnsnvK?4eev<} zgJ9A!n)J*jNMbfLqe*3Hne_VppKTQ&^d8b3Y!u>plF94F!AMVU<9}e5110s9y`OIxaL3a;Kfw2y z$Y&b*8|;SeFTf12;6&xZTQqw%lp_y5qZ cXVUXG&)+z+OHn59e_XVs#pT5+MGOM|Kf{U9kZD~66jgaQKtgDxR1st5z~6axkZP89hG za7IUi!372eP0CzEL|#Hf1SD^7YhrF?3HPd3tw&R6EDidSDIeIUqv!kJ8$f6y9!a`T81;E@iRq<8>Hgna&}P7H%ffO>ln`!yjSIY@0b^EDg~!4}#p z3NotUpv{Xj*0 z?C(;L0%}8Ii+~kvOHUmiF)k)V+y%`D?3Tg$1C<=KSGUj zSSbS#tMBu-Xq0nnPwFs_0wEoR}+T1B)>Yb%3X2%){)~XqbnI;`i{f*E-qg-95?#E(rR%y^FeC6b+xrUjy%@h zv4gp+?@;@`dYpt#?5#Ng!|kJ<7O`2#t?fq9#)U+BB5ogM-<_D@mD;6WVdDDXl#vv{Zt6dqK`CgnDI=ccc2?eEmwdKLs>XC zw&@Dv0&K|&7C#xmiqg;7UpP%9rQi2|j{oAkqgV>{p&CK{gy7*%<0tl9tOq1bjf*Y$ zbp9y>*>pvB57b136C|C%IZAre`N9C-?up$~J9sV~ zp(vrep@l>&ySNS1$s(!ZENSs$m?JFXjU05}RKID6oc*Bh5_Hi1V9i_R~*stiuQ@(dUc+z%uU$aQ@AWcMlP6BC&#-@#tlm3zm< z-MOfHr@P0w&5qZY=`PZq>3-=*>2#~DFYyeaD6ur>j2XBq&9ZI?T)OJN+Szf1`8bHh zS;hW^HR(g$WLalfch3y%jJQSB&`yj(JnQ((Fn7Oe^$F{`?6Xfn;XyM-)xCDnLh22S z7z_}VF5bbt*PI;atSVn^SgkqltxT-(ZB@-`x(`}-CX=jFY+B};W}D`}|1c=(lnx;Z zS!Se*v+9@bua4VhT-MUiqSj=pDyVh0DK;IfWG*WyZ!s0AJgH7FP%?K|j5F)68LU~< z_vpl^cBt;JDlBWX3ZHA74LRhRsSO2v`T3lJP=RMHE}#Lvn_@Y=Yc-u}oOPjmqTHmrCsmKi6qubfHq%`0L!p%qp?KFnW?n?!D^DtLTc|=HVhMs?iQ2d z8s;CqPw{c%aW(chJa-rDa{ppBP{SJDJlJ%-q}4R|QRLycK4n2;;Tf#DFj&}5*fPyo-&5ahB>+^J_9g9G+L8YB z3Sm3VTV#q46bx@|PQJLp>2j9xO$a!u?(Dw0D4QueEfZUHUak1qRlYMbQeN1#6jJsj zLUcmRUtD*ipyyU$NYS)+G1VRVqIBY&TXVZg5?#DV~JC}9qHN2C- zr{^DIKJI1(sopG zw9@hTJa%`Oy@_4b*wN(1B+kS+sD91CdE+Dv^^=TzTKg4 zOJUo1__lf^A8foje;ss2Oz@oPYvam^T)WUDt~qXRunp6>W}P{9i+EF>)5%WfKE4kF zKE58K9X%b>b%kiz#@cYEH}h@1i}}sil(9|!Yi1hec*fTH?=J5qC5vMb8OTeC8VhVV zEuBvGYAs(D*URrIHkdOx6&qN4Xt-*0@P;_`PkYe~Gqx(M>Xn-|uikX-FWX|-)HRIS z8XmsPl%Y-(1njG&nc7uKCtJmMS9NXbu2`;?+KTTA^)`O*@X5SXjn5**?5HlDl2gN6 zbAx;C>X0#~S!hO%gIZq+LnFL1*97l`&1}blQU$?*%E7&%-d;=MZO0`SxjuQGfSDWk zo}w##jW^52UB|`C#LGiOQDlB~q+>8tHxv@aO*T7fG^cE*e6>?yIAMqyc0tC%z2xxL zJ!9XftG9bF`rB~|k;~rA?#_6z>Gz=|A!5hN37(<)_3h?iE}frE;9r!t2KUspQcg@I zOb~CU`wRI(Ez3pYtA2Df{t}LwP;)-#$*a9yOW5fl-EEy#N2;~=^Mk(g zQu3P3d$|nP?Z<*9ZeCh$R!>ZjK!*umm|Sl zV?AQqk~NI_8!vYh7zGZPHGUXg_j7$x?*5BpzSuzI;U|eytvQ=Hem+XdreoxHWOH!Y z1*p&G9#FY%{qHfGzgPg-MX<5Dgo%s{3_Y-q41)km1cM0d!2%yaSmJ;0zlWuTf&YCT z4h9Bd4ukOb8d=~Q`WFFwp!fXwhL8FLg9IGm03X*hxPM*!6eA7(U;A*PKpV^(B@qb; z;9JSi-q_g2!OYfi%@1cBIPnxLuHgU!gHH|pz)C2-_z66J+FV)PQC&uw*U;9Q`Gb+I zfibhIH5mFF7=Bk?VAtB%@dL=!+RDa(*HwV>_Z7UrKJ+vTCFu7hj+O$H>N4^m5nFp> z&}-&b%&#Z~Q9vLNzrB$OucGMtzqbQN0+eQsj$mFE78e&6W*2s5TYFO$RvsQ6mRD>n zY-~)x6-*9pHjW=$nQR=W{ygMg&k;3tFtj%ZJDS_tfS}L)U|{RyC_qUGebK-F{`Aw> z)%?HSWaIF6Sik^Tpe-z{%&%Df{cPY?e&|_Vd2?4|D-BU|Yd~heI|MnoU-AFG{{PkT zUvK=6JJtW|PBwN{?*F{?Kbrpgttt-2_9C{{z&jlU|4UzgZ~o85zc=!;K!^SxqWB}{ z-)8|q3!?C|{99>)C|1ZcrGOqu%thstfo}k1&_7s8U_<-o8@iu9t0G0;4+A3%BO&@m z*%kI+5y=zx-DCHO9?A>xW6)TZ3&NWSN)Q~%Yx(jFL0?j{#bE$cBd0E-=s?np-f&F-P;mKuU!pDV$1^y3jU|*)(Dk_r`{arJ+C2LcHk0;CS>A3h%et1KO+or2hzSLXdc>JZQk-xaKB7&v5* z??2kS#szU3b;#!S{?i+fu}Id4{+V=u&a{03hHEvm%R%^$W$G`Y4gZfjGb8W25`oT_ z{F3UQ4Hp?C4E}8SPelm>ON;{O_e1Wu(LdHNGKL-cKa@Q(NM8ijU*z?Hx<`l2HuW6YL~o3s66yaC{U@_)hqKceaX zg8%<$`u~4{e?>AYWs{)_urO8$q+m|h-v|s)4PAU zInSp%Fbb3Q7B4bz+`mxb|Bv941DsI`r4`hbzJ4|`9)8IGl>sT7wIO1#A!Up5w^z_7 z_6s(~$$H-@+FfN*W6pv8@0a|y{z(IIg3a#-1<)_yCKM9+59x=u4%9SUx?19m|GLy~ z98MyLl9Pc(k{XZF;C~Fe+)RMaW`G5}URi$IlvjPlBW)(yY}Kv#>pxzn1}{(h^&1N{ z1-k#k*eJMvyn~KlMjE7M9QgWZC^nM@8GW{B-<uQBqUC`_RZLKFPK>G!|) z$gc?i1)ib$Zc+LAHX}HincWX*OGwdh7?q~vR{zEB3Y~m|s6R*E8`qxkKsvqG+Tpi=wDK)cCx& z1imG_gp7yx}r$_nMmLU0*yc)uv*ax7v$vp%qz_K$zAE05yL{QnDO=I{)u-x1(ssiUwid?g|g!Z z9;-avDC=uD;7wvkoQx^*cu0v-LHfL>EFD9i4S)L~I|GGuQiTe0);OWXIOWc^)8)N> z-kU()u^deYbH)6W30fT)$=sNj9|*UuGug}Lxwd`_P1+Lu{1klSmpyy%#KtEncoh#L z_}x!$R`)LkS>nl@cyE~7{7ZlG=(o#e_5p+OC4l8y>S%i9y2xpLMsn>rX~Yv{H*n%E zz*(TbZ!wsq65FNty1I&JG`k1|Dc4mXK6wkVeT*;&`Od)t4y*a?WwX{j^YK-QliW)7P7kjzE$iR|1T7(|S`EkUGE8b0TjVR#zae_^Dp=ml$+LOyb z!2T!!S~~h*qM?24R9n*^$y~BLc#uy*@DTttXvCkNnu|uwzQUfw)Fpphmu!cFyC;ep z{f|F#0fNsFnH5wbc2p7RIS0Ds7w3AE0~~!kWAed73N_0-ozIPX(1^PKTBWcw%FZa{ z<$Wx5f(E5u82?0*b5s_7c&|yIHr+NdRd#SH1r;xvaZGx|JGwSmIQNZDPtR!jPLyam z`=~Hac=U4%<7%LuH5YMhlgy+>D47a9S)u47nx#u+Rq@gSwA)$GZm;_D53=4wpkVIq z4m`<=gxDBuq=Tq=es{Y~1xP;%pX@_rPxADD_bW{!MnYnam7Zpb)r`x{4D8`k^v+RZ zVWmI5x2)I~1JkQoo*FWt*eaQ!OlNI_Qo+vfqAvk&kkpcTVOMLbmoG%nS=r5M+T$s@ zM*dlz7JpN$fVLZ3RbGo!9 zM*kM)nD`DIfte^h;DiBD8Tr!#Y)zFzujP6Avf(A}5RMyBKuqKc(s=)z! zSh;WvwOf)m`QLNtlrj_hGOT$ra>Vg-XhOzA{7n8_t*->TU1i)WL1}GCEfE!@(Ibf~ z7hzH@Lt;iTetv*P_}aLLq@SNzTFxXrEVF=>ZYAk7TUwINt$A3TE;5NI`r^C@Hx>Rb z%eZ`2wkI%PC1<>8)W4`i7EeXk{c*Q#mi=a?Ap~C9nFrJ^t6XnC$mPqakve(dhu*VisgJt>`rC|DbpJ6pyA*41$ zp9-p;Fnenf=bBh@5FH!6k;ti(B~xv}RxzGzg3SE{7U2$=pKse*qscT|d$ulId%7q! zmCA6NS`f(~mnH8hR7x_i3`-pY?p`f7V%PSQ;bR$+RD|QkUuVa;nb82x{N4At5w`mT zg}}{7TU>l!HB-@vgL#C^CI>Vq`yr!Zkp#bb)d?k8qgdQvM!A{;PA-|*?@YO3#0J3? zX6LxaK~S{-?UDFkS&bYqQVTt?`{5sd!>iDEEP4XR80#FCQcS{*C&rt$kG>8K1Oj^_ zxA!TWcJI$IR~5tqBZ#(Ms9qO|vDB8AwtJ8;QWcEXPd-V3ARI=}EI4 zV@%iExf$(OD&c<1bXfx~aN8u#YFDa~VrpV_g0c(xDx4}EMTs`@UO3BKvc zQS9l#LT$G0wdSdGIAk!T%kBdXh#U814=BVgjF%(l_LaGV$r(s$^|zd18v!|2+~Nt} zVhe+q5vPuNVyn)-2Z$tMZ19q0I9Wx!INVFPMZc><$W-_{1gvQ|AFF{#w|xha54)8N zcx1@*I+SmSl2v}$sS4lf_inQ}tuqqNRHdSsjdi1_dQc|ASGvYCs{J~`Y-P?+j*`%NHp{rd}HFs*dN8~o_# zWUBhv`mtIC7ef1^>Nj!U&42gD8Ok%S7<@0Oe1)}97?-0M5;OvlAaVJ=qbCFWAHG%8 zHKCc|{=o_I9n(ruNs+Roe-#HdvLsy`h-vSK7`W$}cvD)bs}>$^6%Q4=wo_O(rWw3WwqtTX_GEmSOSPiLvr&o(W{w z5EPO@@#yquF^Pm2#Z&KAQ^N*4k%{d#R;YBt|429Hw{)Ed1)Y)(cv|#y5AW-gSyVdz za{ynPn)rK%2I2~`>3jL&h475%evbj94-GcXt#D>SfcwmDqvXUBTU|#F8c>0yYDU}F z(Z=aP@7$~w{+*>kS+VFnUqO@LMNGh&=QBUhy`h2@XRIYZBYZ1a&Ri-@>ZOki&a*W` z+-%n!f;oLJC<&^+++W>0R(N`<@i=YR@CIPCj#u(Lz_ZeN5Q`i!*+bZO8j+}}_>>+T zu0s?ldH%E8>0gV!FiNOQKhVJIp{C=?{^lZOl|a70cO_^o2IE(U_e%*8GQc>rGV)Wf zbC6_+^)G~deT@-y6Ac8pvxwbnk`N#z*L!4Qsa}FB=>34D3n3*I zpfi95*AADkY!A!lIK{xUHv%~UBJS5)pIkEa2rYgqPhSF%RNTR9`cOcXzW_{bEJM`( zl882Mz}o>S$Z}9>-;Ae`uY|FZP+9g7hEu*;h%j~$P_iRQ?Pemp#0X^PkJodt52?$;nT7q ztH}tEjAb6^25ADCn3pvG3N5?>U5EikO+dDM>i?Y?8X}-=)j*rSNX^5Ov=eOXzmla) z7ES*xBi+ABJDD?QZHiuzKj(u0n_FKZsJ*E|{l*1Z)&sQX1b`@YLzX;>;66~nidx-qqaifrbXhlVgX$~rcW+7B*EU>K&0{}mxqTo?g7#) zS_+sT2QK|d35XdLDgb*$KbaycO~&uwdkWyy;s1j6|1)U&)Jsj> zRX;J#)Qeig2`KHwmg33l8E+*;A@arbq(mdZ@ea?o$13(qbbqtX616X=jh}@8TKEww zT(nFK*BKUgxv{a?ciL=ebQXCS6&t@^`4n&RJ_pE-6tj)qrFQQSeAh`?=0ZJr=PyD5 z_i5G9U*v;u7BW=nTL2`jykSQ?5wJc(t6C?srBx_@0D#~RScoW?_E#Z<9s4IU7AhuP z^Ynn1E#tJqcDVqXeb-r3f>)oy?vWF3U$V6=*w#0^xPwq^^C2}gCR$@xFbvt#Mbh5R zt>(5vF;lv_O>PSf`o_0#<({_4pIGM(XMIj1nqD0X%-S14^7m+{U-ml5u*CKgU7Wjn z*7cv=y!@~L0QSq*H~8dB2Z-hFMJlD2HrPOaeu?7uq$8~*d!>!v7&Uy8>phU z6vo~%ccq>70z#S(q0*XHg`>V3KaAY2qG+-mCFu-POJi-Pq+@|}a%F!r?droc<9rw5GZ~>CEA+dt?>x4odvNDpdlxx)4r}7?>tkK+{T&OxZWAcYV0wV(B z0qiWXJ-uUB%y=oRvjTj@#*^l)bPMic?HwG9jM>1YOVxaD5tq-rojR#Hr)taG85Lab zOt+6LssozCq#K0$VK2}<p2Vd9S=@Q>Rp^8EXi!}f0 zRF%1%!j}Hvx5AJ`fy^AFNfH7eR$a6Wh(gBwYT}QDr;i3+@{`F?K zfJaZCLBSveXbonZ=gTt}W>zP<7x_qq(_F3EH?Mb&Tzf3`USPZheTN(M6ekoPH>giZ z2YfES%Ipr|Go$--ruSUB<5j8JlUd>AgOZ-8Xg5c1dV7XAhc9G=KSw@zS;SlRrt9G{SmDs7ny&}3dYHXNK&>l(q{zWsw?3VJ$*Im^{DgC1vmTEu zJ=6>dVq@-BeO@$9_qLc_&&o!(WZ|}-`=TF9Y%mo!hw3Rt;L|7rsQ)B}`c}K`aYw+i z^q`hCUt5LtFUtao+qit~>3FwOeM+sZbUb_HQ}8+#F(veJ&g2x-A+bN*_Uu(!9@e#o zgbG6^=v?9)DtlUG=lR7hlex{BjyFS@Pqlp3g;fo7LEkXx;M;>X-9O=wj6F%<$u(*Z zqpsNA@DmnqBGaO)PI4@oeJ}YU*WF7X32@ZOQxmD7H|%5e6AXG%LxrPr^@O#0Pcb^J zr!@;cKn?Q=+qc6}s_os|1c&+t3BCG;-OiywIK5=2NtPY#%X4v$5~;d=2PQtn{ZR}I zo!LB~?6xt_uXuRL$R)rh^6CEWqZaqX*;jGvXC%QfjGNHVb_wtnqtuq1Y{00f07<;w zv-ks#(r}I;Yi0^6kmg{P@=MNNX?URTRAW1l$p;f{hrPB$)KKAqo( z42;buhaPp~S*T$L^&eP2AH4j?xAW}i)c*wSJP80TT(K=bnH-pw%p>+Sl#;z;5C-R& zHM?;E{whrzPy*glc3XdFXa^ecq50zd%E&fDcuAj5Vc=>)SS$P} zM$A+?q9}=ReIe_k==IOB?dB99jnX%u{tu%>fXH6G06K~Bg&ViW<#jhD);D3sWnzDG z4UYL|M`?0({it2+BLHteq{n|)2MlL91_#PV8=nG(qZK}`3(&hjfRkuBvWG$?3{BHv zfS$e+;yzzka4{M^!h2sDE62oeeCgFUb6LA~4fBq?Ft>6PGeLmh_xy~QC=La~WUx=`@lcFKHa+oti>jlS8u?7x(BhVv>H8tNe1(F72_~1g3DpZm zjXN~G+&S;ov-ag}_>9N<%~E!#`+Wjoo4jby@5Y&RIQ13shbvW)WTGLptck?gI|C8* zn{fuu-H2#?sSQ&Gi_6{C-Ot#}Cf5v~nCjh7NXW=yFFt(RbI5hu{1moJ5gLq8mLXL5 zFjuK~nN1#-X>pInX|KIl!I>R)bHH7xjF-B3Bt6dn)Xlk<%#SYLvAJ{`)Vh$4+U3nV zCt3`?;9;G$PMr<4?sl$Uba8w|N1b1{e$I)+)!XN_Z#*326_uRGv?&!6{bsLa&307D zx;&?c@@VY@CvAYRGwWZ}fUl-{z3>_NQtgR>NGCjC0APp*@c`BGCohvl4TRP^8@{B9Jrl<{VPN`=9ug+_~K z{ecizxG#p8s#vo+rYn@FuikoIe1ED~yReP-j~A)|!UpcWe!zCX7!n^^zkb zOT+o;zUji;p4`Hhge+IQjUi|}G>_ZH>xl8Obi_WJz(dp|x}98$;A>K!@`r0N3}AkxXF4W?|nE@2J=oK^>E zns;|)$0y#0innORa`bRty|Nh1Y-0ZWN;+>kxU^vh>jZP(JGPhD*#xSQqmD>-HdZ|x_}Zs@}xZ-x&4VeBXpXN`)}$E#Dh}C186uXV$z>n8ELt{ z-8`Y`>aWtvy*=;+AlCPFG&Ac%#}imY$k;f$(OtA$KY^b2W~J5r+sJH5so2UcGfh?wuaX z6ytI`t5(ZWFE?nxusfJu&9!^2;-H}9p!Fl@U4Rh96#GoToZN}6t*jS|N%u+21{8tv3S856m^ zoc{id&Qh8BTYEa+M-SS8y==k;-ag_ou0)Z`f=X^U4&H&x&#$alM9*OuC)DT=jjlNKZ1S9!M=H4Y`Q zL)CL`3`6HF;vXS(kc2HRCw-1%C*GzKAAEF^T@^XRX01k~<_;a*R~IPfK;^?PIPlTc zUu2alTND~%Z2&)bFtuJq0E=Knq2IYOGF3dkQNd6e^^E2;H}&VVhJd?c^zOJR5#$%K zo!NSqjQ!QmTceIop2&j^Sdpo^IE`D;Nj5S>BZytFZv0p3=aZRk|5v2Oa^128v*N2q;rbx&?nt?oJ z2MMyIbr&Q(&)%J6 zTX=Mg-Ce!+P7UMSLSs}I#`$z^WOti%wYwf)xYQWjm4lL+w5N5tZgz%TLQ?iTL1+!1&1+_-K4YoHGMMgMf%$>Px!57SJMHR@)lkRW~0 zSGdysQUCM2H$%X&P?Uxi0WTNoJ<$+0b?R)w+=WJD4dtVyshhJ`1$0+ z(i<@CK-wEzpq`4%A^Up<3Jl~$8(r;B0OP?Thf_txAQke~W^Oj{-7){s#N_^s&4=o2 z$H5a3qFSoK5U;zmtW~bq>MbClIv!Q5(Kxif4}YUaF)Vm*3NL`i0<$C*6wi!D=XjqddS;W%oR4bqy6M$=G<~4xPC+ zfdG9EIumnzPV64vTX=We;N=?Th{qCeYFCkDZ}6#mYB^$#sRL7C*#-C`#9-C0(8jci zToK>o&{N=d4Emhkmn4w%=(KUZy(sv2&cOR)OizYzDo;_T+ccc4*Px@FDU6O#AeVJq z$@;=_gsQczb=tAVsO%Z?L%2}_K0-ZFs>p80OvOgYP54GM7ZZX{*oj&h*tHCo{vD@?dT2jV#m#~el z_{bc}{w=Zkt-!QVPQR2xr!K0As{0*AsdlULm$c8l-mnm3SVsc$#X&8QMrK~of{v^d zDeh9d>558~or|N?6k05tDL*dKmB|YkN$IG??yKI7i*aKhxhToyo6=K+t*P`+x*XC_ z=l(9T(2?|Q^`7X^Bl$t&Jkag9H;!uQ(R#eH`o_JvWH;MhBl9j*K_~pFi(84Ym_hQ2 z{0n7x2B7Y&uS)`*6#&TtYjEY?JiCPESaAepn&6!P%txkS{wx1PkHK%Iz2kCW@l(Z` z5tANbmXAIQ7>VxE(uWYJ2U>^$6KG#T4p|7_(?J46r)Q zvwCaHt`A$aH5ivi*w%JYIgCn4jp< z=2)G+Vu;gS7jBRijv z%!=>V@g+U80 zTC$s#3lG`va2t+@+?I&Mz}OxAbl%~$ow0b)Blq;_(;=O%X}sI%-d`G}g!As3EoToD zH|=h;Qnah->TW9q^W=w+7?WGjAJ$bX#J(NFI8!cqnZ&Oj*I}ofJz+mqZbA&5Z69%4 z<>kgviY=D^kbF|9=Tc}uil<1D|DK~zJ|hX*Ig#k_p(GG*(wg>pj4{g<(fR^Mi#w|K zL*+dDn0TNKKLKeQp@5ga6?fC5Ykr3NV=ar@*o(+SDy@I|%o zl*m)S6QhdqmKhqZ>gI}dXtub7u2)hquymWk(-(NihtP+LB30Zo0XgonN8pVnBLaj- zttj{HPxQCek|W!>$-K;G&dhP^dk;jRfh+PH6K-;VUj(&4OEzjgV?@xnOrf8>BJb}0 z$#G|5l8JV_e*F4$kAa2vJ4cF6gXOeM{q+U~0u8b=Gxt0ZGNye&DA~7k!yjn<@3N89 z47>xrQAlXM#~f8E+`*=T=H+ru9tnNR8PxdGpYN@CEMYudYTD5j0 zrnRP*r%#Ww^?2VWnyJ?NRh7&s1C=(-zU;UYr@GzycEBO;0uI@Xkw1$Ao~-`@jg#9b z=f2!k`h~y9&6!jC5H*{}JbBozy(8AfU`Tfe*>%@+h1D|A==f?Kb1Eiyd8$-KS4iZc z@_=Ez?7JWhW;S``t@lIjPFnD^COdwe)17bRyr(4c9KzHHfO?F zE%`f^Sihi>1*8)Zc1}S%)g<ZsfN^hRukO zPs!&h{Rst(d_*6B=29QJxzD!gI)mT&Of((iX3V1j~M%FZ$_?ZFb!%fzK z0QQXd=hZy!_H=-cLTRDCEg;d$BOgs*LfnoXa6S=aoMPcA>)JMCj+tKw1^ViD&-PW| zOXI1f;)qH;!nmufu2FV1_C|M4kay&2*^^Et@ilATxwiRaPV+piw2;h=ED;Cy{k#wG zbGNa){99Y`mXkS#vD~Y7DV?}8mn4IE2AwDs`bP(F-R1Cq>Xxp53jP$^)zyz16bTJ> zaX`)>2GqIvTyF2&-BvxR**>M_-YC_co^H`#R}r*(D!2>zO0K>4P&VV{ zS%e7T&utlX^2yk5&Jc48?lO4Ej~jxiT^$R|r+59%3m!^PUBEpDSEn!W90_-^9Y>Xd zcYQ8Thnw~ z9}Zv0k=WO4Y+i6U0Hb z%i*9?giCT`HEDEhaUoYS=~Bd1x*$JPI;$!pHkH-3^BfTa;|#+GU^iYX1y6ys8EkR; z65yS4E+qv7NSF~xw7{N&Rq9Qp*1blxZE_J`U9a^GjSjIbAT;#p2#L&vw^TRk_p`b1 zobD3|Pw@67S)XNo-#tr`Qb2+n@m{^(Rc#D7J8brz%v?ji>+|Y!5Ma~73s(qeN-1&{ ziIayCS?Tv>BBsl*-XtWPm} zCDj5XxFj~xVy;fc;)Z)WLkx7FQ5)?remotn=1CKxox@<6S29l1SRY=oSqt-wWM;erAthrt{fIEY};kNs04x*J+NWhj}7yT5=W*>w@x+%{R?@p^q)@ZJ{a zl1gb$pI-LV()D6`giRnr-}zRE^t}64E8+f@WVY->rd^NkgIbTnFu$>t#}f$i&fGh} zufM`h8n*j}bQ(AOkFGOecN=!FgYTFg{6(Oo{Y@>}Ygx<}xY=^2YBdV941ak?0>C@& zQbP=yEw9LgKn^a9QG{CCjWR)Nnzle*2TN%y1ypoMIF0*LEr=@I`0zu>f;yZe@okSc z7fb<`4a6kUcCexgIe)XdXu6nH z-qkqy6$#a8)R52XfIvn~_1(d>D-nW5H*s#KLmA-$Vb04FRPoh$W8%EiRx}rV)Al~2 z{i*s&_K&sb6_7}3~M@*gj(kAT`pG))M+Kc)_9=!Q^ z+m@FE7fmDECrto>`CSSG7A{rDf_pC9=aXyXUOrsp@8rwz$8URtMe!P2x*o;p`-`|c zLLQ5M>3T1`s%-mAgt*k!kHLTlalF7x;b$Bk^b)HfS=`O$M0D9575$XNE)Nh&x?cv* zuj)QQ4DJC!>CA{^uiZ%fC)<&uc6uC^`-Z}k^@1SYv{8tWvrpL}J7~`RMSzGy4NH^{ z%YDVj=)5QAk{Vc?{4x6G%UwME{D7h$5Eti*^!)ORw1-1Gf$*we^Mm8er`6dyyw*v8 zGCE~%z=@8LTN&4B!p}n<;5Jc>CP8;Z8rkf-c+Gpw-l0 zVDZ?kWHr5TQd~*R;|s^;)dp?Tb7(P`I|G0{m2%6!mw@b>la*S!=W9>(cwh-GY$g;a zBO?KGA+RjcxxmcZvlpGv#Q8O_Hp^&zD0dgI^;W1d?>USM&>F8`GpPm;ERv9H7aL@l zfmw`&T2pY|qYAw7>?z(*hse#`*>|$jA+1tkW7j7T8)7zfGH=>e(k?%r(vN)H9IM%C z8_6zb+x;!;H56=X?Y9EUQ`^p|E%EiCc^oH;wOJmusGelQsDDj40LXMLpFonPm#PLG zNc=$CH4k%{vSRg_hv+4ZB>R3(Am_FYtxYTN8g&lleii&KFQ{Kg4zf+Y3s-;TvyHQE zTnOm3^Mb>QeFB+M$o65;8s&MW0vWJ$JGeLR>^Nd;k86Xfe&?sW?%NdY@r@|)78&)v znm&=~|E27!-@tOq*(Cp@x^C?J1o*dGitXvb--+C_Q19>gz0S02b8b*EcHy1U&LHUH zq?v@k;_>?Aed|o?i!HW{b?XcvA%S>xG>qL>gCgVK2^dGINOmK`wBx@6G+Y5vZSk$4 zytUcD;srfv&8X67@l5TMbpxAEJH_G>SchTSOJG4+Kqu6zSd5K3Ygq{b!8ye-5ah_( zC+fd?qSGoI4@^3zB;9{9KYZHJECZ%p$nc8?Vm+9B z=yw1GLzhh+wc|tGIKgk#0<{S=GIz)avYyW(A&;7r)7puTX+7-RS^blnd<3MDdt>#L z4(w^A+cnly(-;Hfi?l$LASD`GrP#LbxR~y(qaky!!M~|iNxZaQ`|)(WF7>4s4LZ>T zSB=KspE!|0*DL}P{iPDaNQdr{Nx)(PCU8Aa34tLLQ5vNi6s5a+6hyi^1_VV^N?MSRhM`+pR6x2J zx;v$FnC}^MpZA=7_Sx_CUElZT{!8{|_{Dlw-fOLUbO0LX0?EvDuJn{W!+Bps1Ua_Y zQ$MoO@P3nm7&d^UVv?GDh@nO=UxnA^K1_PB19Rw9NP`v8~Np&)CFh z&{Q=&6v+e_tQzmxo#9FWhnH~{g%ZJoA*&5g&@7GF9Y2Pe3`QUIDh7KvYeWR1cLZhy z!C;;;zh?Bh9{urylEsuv8Y?iW0;YSQ7VxeP3kH_AXI^qYUH3Ep-~y1Awu5-I9S~Lq zkEmv6V){DyTb7f?-^eL-SJ7C+tQC{9(wzs*UR7c^m2 zkTDDOn`Cipto(CUj#-G-^$4lz@tAz;kuDW6(<5K6frHg!(iKbx*)bX_yo1+vGm>2V zPQS;F)Ezwr)WizPXqH@Tz_baA@Ix^Ea0`=4ya?=beVO@VvJuR!78hOznO9-?3k^RB zy>gqV#M&{R2)D-G$Ki2tx@OpJA)0d(5RaAE#W^VI><8oG;79D1B`MnbJ zB&XL3zi~Zin_b#CUbYwblu=;EF1VH^p;Q;mM5QhivrwUi%}TR$?>l0;=}e2vixFf9 zyycg!5K!Dj)~3*Ecc_`bidLj#graEq5ge;bJT_L|{)DC76c5YmLud_KcHEPVI)7fTP7=hS3-m##} z5OZbYa`}i4-s`mX+EUr}(k}V4Z_{~__X)F!b4L<}d%=hrzpoh>70D^vEJ>k&=sG`n zX^hEXx?VM~K?fSNU6ndhtLT>DiBsYK%a&o(l(ME~G`G3;F`q)hjF{I!8MnFq;V!mq zEi%7mx7D(iU&ZoQIdM00Zp-@jG#J}jWx3(2O6Z@$gIKv5r@eehU_ztmx#xw6LR z3`cqZt`RlN<>Q=_TC$M{m7th!DPb)?9~2=f3`cj0E(av45i*izV@GwSo6le7EMKkd zR8WJO+9Yt;B9DBUib&-muOnZKI$pWW9~qXwbbK*?m9uJRycce99bteqxJLrDsnA}(8HXYFh#Lo#<(O~Wd8)bc)isDibTaI6P8gqlCcQ<9BwJ6PMWME7T#T6* zWU%k;a*wY*IrTFN=d*U=8+HK*(XU#-E!C9QE!p5K`PkxCC}r>)YAHe z#&6dI+`p`ABZ6hK=hKWkcvWvz{)8&qi*SUmCemjaq)IAR!ex@)Pi&thp_srp2Bpb| z*tqqnmTwM=O2$EsyRwB1HxUo(G?B>cW^M;1S6Y=PY3Jd_#Vy2+OJ0Fz@UembS$;am z4AWMBCYeA1?#>mo@S(Qr>KQ03@0o+YpA8;(7i;-P?}{?hId9x>L%{VtnCr8{=mFE2 zT9%Whk9qjhHjD*vR`v5WQ*#a0O(J;~R~0>L$K{dor>~m3z8}}!=-iIqq-rSuP9)nh z-^x#7A)fzy%JoYH2;`HzHR#DFZB2A_i9c3rYKt!Vp29#+*reSI_3h-jsVV3edA1~q z9HAIjC?IiMu8Ct_IVZNyQI&T2kt}+H=!2*@HO@K}HO=4J7P75n7vWfYY9%Ga@3cE~ z-+VMbRfV<|K<8eJ)Ix`6)^`>g=xJyeCcO`89o0H5Cly7{HtU1`12yWwCqUXZ9&E3V zBn7w0DFtkwrFY55@c4%e&%d2C3>#SCt4av0nCu=ba0kf-4ueP9HQP2Y+cRH8!BJJa zYmP$PRvGA;qtP`ByJC z4|O`EIKp|06w>>YPNcTG_0np(T!)LdlvwY)dx#M_GprdsU_w+BuUGw=|2aWwXk$OB zPGlLlq>Shxjo(v{VP{Y8FMS|bGgH;isHWgTGdf$yn~MdDP9 zPkiMArCPKG<#{7Xh$9let9!+c^umhOtqZ$7#RVq4$IDH62o9)T;>OndqIFx7S;{c|guG0ia zJcCk~dz+grOw6l(lS?&4d-~}Y^th%WPp#R&=%ijS;z3P_yf| zySj>^#Ti$8@(Y#%+e4r-vMGGJl?Zmm;w#4U6CDS*=?N40hBiqAk1<8Sq;5{ekHwt3 z)d`c(;7!=2acyz?>;Yw`p`b1SV*;`@KDe;VuV) zA8bl3B{C`f5Z&wUtsH}GD0pWUbQ9`ts?i60WeHM(w(k3fUIInfH|W_WE?oVp(IZGWqR221R6TY6OSkV!%~J@J>!-zKUMB4e_Y z^$PY@c|2K?!GYR+ zEy~b%uDw@dywh>KyH9Omcl;8_+z>W)qonx8h` z*!!H<;oMtdbGKe&2f~mRg8O!&V8byHk!K2ZSJcRuydCcR^J@JHQ`By43nDycgNq}} zyj5R{puZWEkhh%kmJaG=f(e#9@Ll*5d2Q(ZK<1iu{ z1rP3(z#iKzm69~vEYXrbr3OjzA87bvu%DBa)#xq;0H;qlm;f&tDRRtoTJ335NO#QK zneRYA!@wL&)jER8axhY*Sl{d^5sA?$UAmFXUzS;4q?&qtGlv-OdfiB{q9`GN7T)>=z@El z*?O5F@<&^qJ!(Zo=-LeA?VA98Nuh%DPA64+L)4}&mU+K%^$BR`EqrKD5Hl4PWE>I@ zwMuBs^z#@SnHbf}l#aWG=qp$<*N$(auuxC+f$sY`_N`kg+JxvI1fN8jkFPpKjt*Qh zvP~t(Q>CXtIu2UeB;PdnR1Zp1h6kb1Q~H8g*wKVecC-|IM{zqji>1aM^)%*>LqE?{ zErAuzXUi(@gvkNMU`t7IKJRD`?>6urnW zXi`LnR5?|ECX0s7Zojc$_k872S9<0^mU-KNG)I15h4R9_arJxybWG}=r<#J4mX3lP zc?i&~_ujT$D(<^=KBZN<$NjD-EQVSr$H&M}0{a!>srbM;l)U;=^)BMscFX`LiZJ*$ zfDdTaL4`VW6r_-es>tk@iiv?6U5kr#EI!#63KwU);M4?n-MT_>ls1h8wC=*i2yn>q5AAP&1AoGB5nvRhtFK}V!(1mi zP;3&l1tx(DoU&L?<9N;2aEPddqVG5^bVfZrs6q{o|K)PPOXqzS=%my5VBfE3#ehaT!RqS^U+MKe8`s;E8W#+w{;2| z>ppep8V|7+VFPLOMuRQ2@(Ns1y_>MRuY_}cC4-!5QB>%FTBk-xOg|5rl`Pr&x%8=$ zfSf{XWK;y3C814tl*K!mF6`;fho=C(mpbZM>}S(b6CAM0Y3&SyGq8|+5L}GV0Q=Pg zYVnO!P-mh~?qF}{mcZj(31;fAKNnJ!ev(0YPo(5Mb<3&8)3t&hE1e8h8){{z&fL(GdiI_abpn_GKxM0sL(jH8$2r&q;0As$*j0xSW~&s9sN34? zlgL39fugACYH*ManaP@{+c}`8;rGu?U@HF6b12lqK@Ga;U<04)*)RX zkC`!N1U&**1;7C&sxhw2IuFAJxZmdiE{xS@npL>wR;qofXSY1Yodq&=xhMvf6%hs@ z&d4L@al}SFQK|&5fkh&Vb8A9+#s@(h)mk3-AC_l9ix2H@RRT6p^D-I*u;aYo$IyV|vjKU3ET<1{^_0`Lvtibp zGQp9H?mqqWlFNmxZRWn(m3*&q#BSIxm8Ka;L8{W{2YO3FP%6`3o%4=ZqvC#Y`TcRVt$|Zxy%E?6})^acbng}|BSvLN(t^;1q&=P%s8anRY zT3qcC<}u8#vv}dycSwl`AZ-c}4`o!&Wy(DVgPv5D^o)!{?tz9}4$%|ZRQ$@6qL6d1 zia>i+T1YSX2k-X>VR6UIk?F8tP0o~;C;i3w^UK)&-!g)nUqST`s`F8D!QyjXM78Tf z>=ZmmtR%u7gD%JX)n%uU=NSTg$PoUtOr~nrmCqQ2_Y&@|7IsouyuwE?u<+@<&f#@a zygBC)wCIuKLH;8$xSpaK24JciOBA8zvq2HKI?ISp0aq=C>))rY?G&St3ssRl9+Bq@ za^Ce~1_ht0J8M-yg*H{yW-TJZBC)Wp&5F&bs8pem{m6aUNmEZohJC|DKi3-IZ+_WH z1RAhj6wjib7#JM{|S%}v6=A|&-U7QEL?1DpEDU|b#2*!t?{Ly}H z6}wn6L`)qZd%~r6INTS*)?SoC)KclGWK{(dSLqZ z=$l7QVi(K#TeJAXhnAEsdIh;JsgM5g^Mr=+9y~ne4n=hwk0moL^5hbeUPe$+=pQp`U^7(N z3Nv8Wnb&dEEA|j!dJ)Sx9D_2QUA8=c zOm57 zEj-$N%$C?g{k}-mhXRw|n}aJOV)HI<2rk$3S_VZbv8|SiMe~9NMDKbZA@d%ig~y}f zuVhF8c&5TBpadBA(+63(VA|fs45~$+w4bwj>HYjr{(&q$kH^P?#|WJO5g6=jrRUk@ zin=DmviCs%C8XECK&VtFQC;ml);7^$>mAq}C68)&fu{y2gQN!@#GEX*($1~&sO?!K zqlt%Fiqx7-swjgyD-UrA9 zR+n5#m99#soW2L_S&nk-F;gR?dq*oS$69@eYVVw3V*O@stD8Q2n1UK9bSf$t%D0|$zfu|!AP zfET1MC8}m(V`Dj8PSv&>W1vTCEysJSEo~fibof;LF_H>9wEI`jn%Ov4!UDV!D{N8T z6njfIj;sf*(955@B+n}*IHgAtMh6$|BkrIW4@BOh3=hVf1IAo8?fmbnwN0Pu^g$hlqKl#>Lme>ZlJeWcIKP=)k|Rtp!s3$ zYyM;RNbm)>vclwS)slInp}`@5pAsrV>ZUz{ykEeKcUW#~AzgP|>4RdN#HYvWlqg0g zGvcpga{fn3 zmdKv%0_r~e8qA}(##1e4_@aG_c*!rR?4rP1jWJOrD!b?b76}(&^YXaW~unx z*%z%2q(6*`{Gk5nY8N$F-kS^55NieVhj?CRgXd*z80x^b%Mob}@|a7p^|Kta@3A$M zSA?C#t~m7^1SIiwMuYKt={Vhk@}>9h6J2&f%K1nH6K2^a#pd>7&!z!pz(gv$SMjQv zpz|QE%sSbt=2R#NnSHW7v18*b|8#%4w@ZV_C3;7lV&aT!IX5F}rwl#Yvl$fu$dPSP zYIRV)4%-*fp-Y)kEqE^9nW5|NV!)`9ElwkE96iDE)NHp*svq3Dz{B8~wBfh_7wJlOE_C5yhM5b7URKtIOSGcd_1NX2*`x5O zcxC-@=jYp32GZqXI(w@$AQ`Ga-Wk$+`v_>i{?=r~VW3A9Q ziSa=1(c`?j1G2$#KZ*R>%7ep;!wyaXv&km93oQ1wp1-fl5oQh54a_g2W7A?ptHM`GNs~y zOFAOK9|=gf^W9vuM<5nRqdl)2VW;2$5BAXt$;5=6kgA2^@v6^`c`hN!au07xT-Fxg zpBtRQv4LnNQF(laP+w(^{=n+oZ>qCi%#-$Pi|e7cmPmRE!9yFNDm4JCEHf(k*sN~n z#N=OdIy&Bw_L*2#>6gvvf>HW0qqDOE7!S3p_pJdIo;@FNBaL>fL0}WOc_POV1&q+E8*1BgW(leL(4z#Gh^dgDn z5DGtD5Hr(hZsIra^2LFi$5ezfwyprHc>J$7B)Edwh%^! zsKD6#QLge0L*RoieJlArZhMVjdz~(@D3Z_$@X7=7bui-F>aAeTq?KCN` z`t9R`#INFkhs^=h5#J0H20+;H{d^rwNC9wwnT=alK0QEpUfy+sbn6zTToE{d7r~$@ zGMrUn&J4J2qyRky>^Ls^Hv!`d0WhV78PIOP1o4024#*>%%49`4+8l4At1$5_G$jRi zz!g&Bkt582hdX%9!(iViS(*ZU*;??F!__Er5dfBD1LT~4T|4d%UXBN#lYfh!WVi9# zTbx58K=+}Q)8B{9RecV$-=YH~BA5Lnh(AkXFosVlU(hBNjGR(_^@NEG-69GDNH3K$ zuL4-5J3;s2m7sm9zXfp*OdA5qlS&r-1r6z81k0z78zt%pKM zQ_5!E)BGQ&l=(@R&>&hN8bJ0u5X%E>GyLd9{!D7Ql3&Ar;&f)`Qt+#D<0rld)*0NF zETTM)pFHO~mjWgxV3Yy=2ZLPZ!ejJeDa*qEKqf-O+K3h+FGv8MrFLdyg9@7#J@U3} zsr$#y^a2n(F@)1K-Hrrf0T9)64>0d>b99BBn?*93_XRAuW@$Y#Tf7dz?cjizf`0;T znV{?6eRT7D^DJ`@wXelUnlM~*(H6?+LhIy(=wC!t>=-H+> zfSh!k3M}7F0bP*xleqZg@scu-wO=}#(EE!dxRzD_FgGbMS$bfyqA&%yU)>M!FY!P^ z0CCSJ33>RX1~9)=0p^#5d4X%_tbtA)%_jra9{1kitb2iy>d1cnccQ1kcG5y;Y zNdSq&gIB@b^z z%SAwQb;lids_(2o#^|BZ)&4ik=ryKF_ezI##J7RT)N6AkX9dkpsfYi?+P0WsOZy&> z&TS{PK_joVFg;hA{d^A`JMQ&oIE_nF$`(Z=M{{wAhm8KF0c5)%la@M-v(=^Tn&D;*aC7;DXivaDXQogjPxo@BCvdU=ceA;)$CB$UN z_UaJ0gCVI|cthAcEe&k!yoDu&6=$dCDVwakdBK1g@;t4jyCpRZ%}Q85S#sZC@#c!- z)QzM|_s6Bw3BW}eWzVV6s;(vpD(*ujISTf@_2DsaqkZlWya?dB?>)Gz6Lj`CY^~iY zTeGUT1~we=_q1)H0;N_``H-&mh>PoifMXOmsBNQ9D!*-rPKa>6MGLPa4b9&sobyw~ zRrMW%bfBO=ttr+>57(U^V2;Dbc>S2eRZogaO|O?;Gdl;efuy%3QxPQMhk)`hU^p!NrwzJ*L5WHnxLFSC8;= zR>q7OIcO8m#!$=t!H!0E+gGgLENG`+J%6C}&X3_Ja0pTCVRXUrm_ZNW>ck1B4dGoT zD@{6_h2~-u-Dyh1wJf$T#ZU3P$~@kAm0N`G<|8yfje949s(n#h-jy&4og%%{{~`<8 z1^g)8C#++Y=RQ!>=VcDVdfTNFJ{vq}qkS}TR_)NczAMT|d8M?0siE9ZQL%LO7V^RWW9a<$uK zbFnl*pN$6D^qYZWk9lS{zXaBOvLr&CtEEN;&4~4H#-AqwPdtIQDzC=`-*e&XM)Hdp zvV&)!)L+lx5mFVSFDkZohVBo&k9*-5R}46q_nrGu@~qDAJF@AV{@6kCB&62^uEuh! zhM3TpRuz9(!e=HX#OP!)Y)y*{KR6Mwe0v8lpT4z82?xq2{v9jVjgaeQ_+59-rBQ4K zutyZM%qg#{ZvJe6JLJl5RaXfDcZ!^w*>3rQxcvax{EJOP{2uU4ek!vN@IZ7;Se%M) zbBEx);&-C00~}16r-ifC1XS29KU&RP{XcFdm;OI!IGI0CJ6uCtA} zc=RXJbcCn7D>xGNe>HrH`S#c(qwnGJ`szo{!TT%S-C#dBuyX^_QjuzfIioujqfp=Wy9E6H@we*+}`5cBHxuKph3 ziGYs4%3?3dbW?F^J+4#qLVfJ@@b?cMT~jlHr@)(1-egC@GhW8QX_*-ZC@tRLgel8P zD60jQN;6q^$K4w6g}XDVl)R@4{~^8LWlAm5EK=E5NulVn0o&hPb=^1gT(3oV584-; zZPahB5Fu#Yo|aln`;gSU0>lR5FE04TgN&fy;zbNh%Iq(%S^|w8=Eo@ujDDvGoGE*o zw7*_qowGEoBh%+FwtEwN9DJ^rMOizAHQ4T@O-HI8X!;{Fr1F_nLfPl{IyOeNXoo*k zoRMmYIXj+x|5R|w&ud`(Z6%?Uh)y|E{Hc3Pe>N(H;BDRNy0R%MH(}S2&XUVW7!!vm zEB;ICnNY5Nc8{&-HH0Y4vhJj-ZcMDMbnyASA$(F52Zw+SYvP)rF1WRU_nJ#UlOfew z%_~<23h+cYM2G4i*zoy+#|u{fbh9mr!H@3_ThKu!+dJO8Tl*&|Kb7f`x^8UfkJ5GD z##A+1N;F+)jCUs<*Rmw5t&WC1$SPt}({%pogIu$=N3HsV?&oQ}RVx?Mb<1K7fnn_7 zq=|h#n<-i~S{Ms8-V}wtrLQv@mRJ_9QlaRxycomc0tqkWbHS0-Psu&ivUm~w7`af?XFK)NQ zh35&L)XWK^puXasXq*np!Ni~3Xa1!>SeHby>1ir&$($brPOdzL|9ngDhw5@$GO9cs zZ?i15e4nJU*3Ej(N|GjzqglQtu}ODMAG6_NzjsO)&UhrzE;6+)x}K~2_M`MGb#$|) zNXU;vObQwx#^_{m;}dF4c7=gS=V&$-E$bFQO2k%aAW^`|suZ0+>RxL-n<+#y+^x5y zbbfma5+VpZ>@huBc>=z)E0Hy}ZAMq<>r4mZMFsS3d029Xa^y{Z?KQ!IB%viQ{zCjY zmTFXT=7c_xE$nc){)D@&7d*`+cUda_}Usx>=ObY@W>M>07!QP;ssfxnKu z{zKzgsL}#ktA=LKho3!P-5h5!s+0G_~>KfV%7fJMCvN30NxzQo#(;T$EufFoNVVX@W|PM zC>)rjW;m11A#A4>UOP)YUM;{1L1sn?`^lQ$}VDs>HflnT7OV4#ZXDj2<`TVbVs zd#|qj?v815(wUGTjmJ(>;{}yk@@4T)SFWUe>E2k~jLcCkl-dY=XH7N#@kF4TF>^qU zm`5})^hu#@a4T5hAjJEl zH;-}tr1p4Ig$%r5p_hRvYdKPY9k#F_)nP}CZ(e1Tv&3$~Ir($imeVDjp z5$_sXcIxW63SaOwx%=Mlp|u`w;C^NOd_kg~VeSqbKj)sh^X=7nW_;%k|B$%t{lmk> z8Gk|)m8};yyXXfVrswSmq(`VIOEt-@4F%axI{9f90fRYBaXBu*6O|U29}9}?twwk7 z6k5M@`QG6fH;lgED|vg$RogwHl2NN}KK8)y-Qt~)4wTSlsQ}k@)SKsBNoo`LyKs#s zCb$IYK8DewgM}%mGJ;g+HqD;waf`}#+SI8^i_S(lG#?wuJ-tL7EHCtX1YbWtiYMk+ zm;uq6-Fx7_v}G_SI@zYTrb|tBCk-{jU3ult-6Swp7>4Cs^fLp#irYECKvcg@@!x+? zPyu);Q)iXy9q~^sxHW=LpUSss5Eg@zIrk4Zk8-1PR7;-{Cf}UI`inoujLQcVb6O#u>m=GA+OF0LdOvOd!6x(^9h=a%gHrGEri+BV% zIL2-0MZmtr&Erc#({l7%bpk6@U=zo6qz4X{pk_TFVuf-KS9sY z502$4(?{MYM9Ut8h4=C6sE5?AC;7yLSNg-mf6y@1-D%y-x;W!h=>6mnyJMRf)3PiiO6ixyToQeZ+Fuj76qzU2J& zZU6CX$^qcSsQDGa5r1OBZBL}SZ_<=`F-2(smzbw<^94`@^sQM+#H$B8ZehI^bTuQh z)+=bYHCp?R%lsjC?K9w@+a`yuqs^}uH;t&LeK^oab3@ErjLnkQry)iA^Sy^@X6Xr< zm+t<$a;-R=w!g;xQ5#vuc3Wuri*C5JW0b!GikM7yITL~R-(m!r$AgOv$+$+IB>$y0 ze;oe5iz^qT8YU)pf?oc`FaPTT;#k0tnBT0c{N>yIn|ht!p%`dKWK`@ufI4hmHlDkxuND z*+0L|U*75;VgGR?e~9ZJVgFeH|IzF}xr=|?$e+E=KW^k7H}Yo*{NqOcEP;RA$e$$e zj~n^31paJ9|70V7HllyBkv|#HKiSBijp(0j zWgb0H{|6`ePucqB;;3`;@1C7(HZR@x>8L-Ufw=~5j92pwO&9ry(I|-`ovdmHG>ecf zqr8V6K5;~Q=hhD2M?9g%i$8!W@^_lVxOZnsL>?s zAlMsu{CKq7n#zeV;R-9E?>nr339r?>8x9TrTP=4>b~bzR4MzD_gZhk-dxI<|wT7_0 zvK!oor$(p;ri5?s`NlrESq1#r|Nf`#>5xh%CpSp2zj2*_Rq%hO_py7lJ8q4bv4;sc zE_MdbDhV@*o*mA5AvV2oppc(?$kpn0chKH?IMqlF4U(d9Pjd@+-VxWhQN2l1>v_Ol zK2YJ<{Q$esx?)^&Z9iet>#k%7L`S$>0!T{zHvjH~=Vr6J#OtQr2XEcPZ?}bmN zU-){ZnU;Lix>`ZT(;B-=^uSBst0ld$4+ey`a~YqNX7r}`lVRMNv*XpRZV5t-YV)0i z4&x*k=o5)}Y6?}jeM^&xNC9)kVSYaTdCseYTQ=;Ez)}a|VT2L}%cCci*Z~>xvEc>R zxZSrSDWQd6bUBO}+j(QGvS~+yO6qiD;WNq?J5a%Mv&zqDSm;l-{HFu?5%=X3 z3vVV|E}(ITqegkQySG;=GvTyv$f~3Z;Z|oc8=)jZT3=)3R%D*wJQ#x4NvjXU`Ql6Q zb?Ju0lZ|Q)GIo9KSp~f^3(}L#dY{_qqySoP{^R|1y>%Mnrl7Y5uH$98o@7*aloBBo zlir^4AMPdd*Oc~;_+r!C_-IuulSY(gJx`*Y-;f;3W!mJvNEp1OQOD17e{Zda{xShS z*GPpev~Sr63_*r{CHS)e{0HdC;`=4#aiGzmhE{Vu3b`pL_3`WiU<=NC45w2^+~HS` z330@XPWbZu?e*D2&yBZ7JBtYSK9}{8#*5exBJ;-dk@6-Yh{tB_!OUEQ61$Ee8`}V{ z(f2n)PXoZ0E?Im$jrs(u#0q%cl}IhYQNw8HGaFodqnG2$Zs0xS!(8wUG;jq;p}&1V z9g@$}O~m&FwKUS=*+_MzvB*`Y{YSvm*~qif>)I|DYMF|`)f-hTsvF7iZ2qrR-5CAf zSd4&stD1XR_hl#qnkK<`(e^OYmY%e#XH#k7wA7tE6kV1ue+{Dho~6uH#m1lmb-LJi zxz?7sHb(Ymx~dnCwVwn}w%K1bO8zB1xalHkRkEv+hhO>yFwUMrkzyv)0+p&zZmG(wCuBP+?tdUGesM zh**9Tr>36-gBQWy8r_eqFR%kKqWapS=lxN_WGi zl&@r8!e~>c)3_lU^;)$tnPJ#t4(`8Ry;;{&!@=K{|d@mxi^{sGXNfD_6I2ZmoA#l z7z1^|~Hu zg#qh|N#fLvy6R`WBMaVP3C>MjDJOB3O+Cm^76I4s0j~=u`0pHBG2=bEZuhNYs`P=E$FQ9*hYNfJKL{%_0F^)VSXDljli@PH zs2Ffcs@uAm3uhBm^W^1>tiiNKxLSA!|Io|Bx=0*_yGVCYE}iWTsV#07_y%a;WAc$OBW zz3!b;Q0L)=T!n`Aq)IKG9kJ%&45mtjo`yg3Iqpl6J&R=kTBS3A_vzrWjT;3dEW7^ z$hf&FHB^+cOL#~A6x{m-l9GitOh>sO&X3t#8F6-4d^k~%o z_AM0G-Ja}9Y*l}>Z3%^M=2w3|a7<2CcSf5MGKm z1G&-kHW5(dp__&BSgIGyj5d<0zm~7SbDGd0Wqq_F)N`XUvuUc^`(&N`5AO~Fh{0#> zL2ct;{-0ki1vUc$w0FfW4g%3`djndt#ck|4WZ$#bFJ~rsdz}6Z5NpTwf}geL<@vN0 zj=4_4>?hn88+W?Ipn4TXCzXqdR3Hg@wz23CYNQAF2=c3#6$R!ajr0VBnABai8p);L z&!haUV=o8v;-$ArSS}dm1A1A8O)an~W$+UuamFL1**ze03SaF5WBfeV@c%|Ym^jbm z>DYv#%Rzw9qSApiBs)DgPCcsL3<)hmY-n5)lwHIO2=@#AQcNz58q2WYqJ{0MSk-Jb z9m#U}zkA>ez9U8Yd1riVfo25rv{hllQmj6GE9jS0#{n<+i_fIe+xXV zkoRsXZIrqX^X7_@XS$sde4!6S8A=X0(v;VPv^0zHj*7vHtCqsM-4ezi6!+w4aWo_! z7m|r_@R$A?Y5L3Pa2|N$;y|BQ?W6tX=nYIT-VRdJS})R1Vf0dDncV)4l0i3!h1Los z%2v!86-_qMWGzEQ_O(wZQAlV42xuC~>;_fe*($uMZa-6~P-+WJaO#ygGFgBpLJOZf z@OCeLNhr$yy#0g9p1M71bn?u_X{Dd(?5NA9Nsgmd-KwA}!cWRPK6~YTdMmuOzl4Kd zo$!6%-=00U2WY)ZiZvP*5`$?5Hq=x%$W)EGKRr02>zsimU%Yhr`vYuhlGTEGZ<6Rx zIk~uCDn51_aQa?YpWukK&$A)+!0sFGe6&(66X*pL0EwP9bu-EX*U{EK`~A97kOJR0 zH>gq~#G?GLuK>q0*~KbPM}1I8 zdJ*GVP!4fNd1Vy#V3F$+#ppZ8$ebj&PEeezS3p6`C>{dwJuS-}_@w9Ld4Kzy+|)qm z8C-z)aSJ^*IXR^~{W1KZAlkJCQSvb7f1Y`_G+FOchpSRyGmdKjhjo7Bg?Y zYD49@UZ!PL)JVoK5Q>Wc5%IRg5eWp2CCT---cvym%ptVMmyugP0y73t;P2`R?@X_NxVx8*gwqw*0E650=rI4GU6D*0TU&f`gV*OByplJ z53j@NfUO2>+V8KfTsIo~Y=6Yiq534NJP+g~?Xg_OLxoT8+f99;febPjNp4F5(ba!eWZaP4C=LOGK>teO|LceDIkW`EB0Z-i>3&H- zmuzi73mOaxHL?8qvtzgA^cc!hHaHW1WHgKCLagZwQ0m~jiEb&mo-|%R8+r<$^+PVB z)~g1*3U*mEfbi4fMf|U^p#Pz@pL+1X=+!aPzF#nUF2$iCd26=0nFpaC0Tr||uao5g zF#riF#v5HC2l`iU+@sq7{{6DZOs^bAht}iln*e54rQji0g6j$pEWj=03XpHwOR)iE+sp@A5~Uy*L{jwQcrT&8O-Bcn!tQ zzlv(>e-+j2Yx0T00;q%D5NaW^IInATg|9HcBjwlbkDT`E=IK?0aCP$2*X@@T%qJcG z{6;7>SqIAzfwh0QU$mNEy^u3C1`;g$jmpI)P~Xe|YYL$c6)x6Akq-X^=JXi1BM2f; z%pWvS>)HHXIgR9qngAL%?+KD7mdB)Xr?7J=T8sC(BHF5y#@)rQJTow-c)c z(3*+AFY+-p)5JmMp>8&v>-%yBB#@y%ySPHc_CG8|oXmPX&EepWd?>{id}tlyerS6n zr+V|Xh)V&DHse^=-HRA|c#+p3RhxAuXnc#Ua`=nyC1Vi6Q~-AvIs&oee@>Yj*==v! z)ap(CirvP=&+T(|D!qBqVe4HoqL=<53HW3%_oi*eVy8cv= zR1oG}bnrzN7Zb39akrfgzS-r>q|SgZ7I4YZ9!aW3TkoBZ7QRfNE_%+EG>;kZ+CWq| z(QP|>hMIS&PpDHEY1)&a@X<@ppdlHJD|O@f&=l03Np2le*VjluK*cyyME$qE2PaqB z_t;Ou94mU)wdd<#vP1yC=@*@%LEZ5kg*~hQn|A-#G!Ufn^!CzNrQ=mv7xS$lAZ~oL zlI{lEdeLuGt>#pt~ zZpYLIk(9!#sO`TKA2G{=BEe-~Ask6^2r$I6YuFGwaT!mEml6!*sLd%*6uc8VohGzz zzdzu3_gesAmzKW;$f37@W4ZOXLkI}OhWhB_^CHJV^w^U?eT%iZq(l-nz0xe_?Bb(J zj;{qZ*IUL5fLZ-d@l)MHMaIT$ zY!yKuJ7PO}dFE;o!)`F+uRk5RF)o(JUEa^9f0<7ka4WV%++CF5M;N#o*r(}P6+I5R zasO7Ysln*s)-1ih8ydOowceUihFk0c45Sf_>3L0p=yVI3&ZzMUo|B@Yi)#)1A398Z zxNj`EMS+gs-V{}e34)}f!1bqF$THzX4dm%;Fyu%fqQ9B7T_H$suG{GpvY$ATn4MJ; zWuj;_^xPl@PB{l~DYaB%(?AS*`SSK+uT@YBxiFf${@V^Fz2VmW(P z>`10%S^X(9R&=jJxc~aN>trx3>acmMS=E0#q|VDsL17o@bd9N?bPlL;9CDjMsS{$aNY#LfDxY?X|OxC z(-}&5N0F7{V8(}D;pWRybtSmE`cNQ#PP^^za_OyO#$d-=JEu+GX9yg`G9_tH9j^ zC)F0V^5N$nKTrAL8D%E8yn{C+l7xE!m%W3C2cM&fA*uU zT>|sBl?AYM%dma52Oqyi!%4=0+Yf`|Y2@)ncB1wx>Vr4E530i^QVY}PyHwvSBkIqb zlbL+FCXpMiMWZ&=^Ouy(*FgWGqh4vil$1mJqEPHr0)~LhB=+CbCHQvqOw6y2Rdn-B!l2WIto`t=d6La6YzG{oV@ z%`o^rYhC&iRbA#>r6ke&YzI347Dx($x5cqdh*5LUKxD1aIXMKj+Y=Velw&Tm^y%eH z4issTHs3q}Qien1rVlN;G%d@Ao$PmL5s#Rq2FrG)uRLPn-!Dl*=B9~Yg#!@BwlR`P zZCfK-QS@kze>JznXixuXJgZeKJ^@2~JrYT4zrL5mP_%+!3Al{`fXGzT(nN(A==l2@ z9w+yn?`zC}Ub;R9lSRT>I?+z#gR1y-Piz?9{-|q`L)D7J3-MSCd?3G38lTgxE%4y* z_PdYtoq8VIw=!i$j@S26$C%nN*2=**BH#q+${oc+7QjQdN>+?H$U&U@Rpj>G=-Jlt z5D5SL63#5W{5m`CJR~+l9OXsR)JG+X%e{0mUP@fq+VDJs(A!qm@^*Q7f}RdAG{yA& zI&D+2yI1}<1^^;h;VmpmN{Fm6^~rYI)=nVDTi`VZ1a=G7cLw+mJ+TLdn`OXO4tpV3 zeJ`!*1@!}@V%$ZqmQem_Y#>M7=Lv6?;&0mo1nI6POR1qV+ie`Sc4Zj^)Pm}Ivbh<+ zhMJmbeITwA?xvzcWry3w%U%Y09-UEIhOq_SkY=5iVVXsn@`#?wk6bH^HBK_MS) zs+e5H?3;B@)VCikfR~$f<(s5DnDFt*V;2)%`GJPp5FQg({$6jNJ$NDmQjsww(LHs2 z-qY68a8aJx=1!sY8|H6|OUJ^3!TJpg`n9t&)5#6aXVPn>eTre4-{F>W7qif4LMN{3GBu&t>CY+~wIWAS5Fx^qq^KRO>bTWB zs&~`t&*GryhUY45SiN(3#j+sKM|9h)tPUHtI9i1AhUN_BqxTa3#?$Fszrnv$orhNf^_pbvCK9pfmS(vqUb=`O$MF-?T^f2G z7J+Q#KLTV{s!z}b;la3T7q$l3=E*>TH;-g6rG_n$$=0go_pP~>h*^-Lhl0GFHKMto z?s#QueAAl`je@Lug97^pTi>CJ@afNQ_8_59FA+LkN;L+6MPOdVgrKV1QhJQxJ9L)` z`=vih9kcR`9&{+_8e#}>=tgQ_hB|Bh``PEZ_CEJ{p1t2b*M1|u;5W?7TI(C1 z_^$od|If1ao!(odk!QDqf5T(x4sEC&OL>crE=Z+KTg*DGQbsi+!E_xo(&A+=j-Q>H zijhBzrjn<8r)izPf!TKQ7Z-Ei(5fw(I0v_kcXBsxkpteB3xfhZp&2OS@h!`PnJq0U zc_nz>a;N4Mj-bcl9$VCaS1S7D!e*fPltt1UwBFd&+>Z*RB9&9 z$$a$xw!?Ra0FM)pe^!P{E+%ai!7NXCZ1QvpHD11JHPi_(R=4_p+cno|Yh{W*^Q}~n zbvJnaijee}=fWNC%>6!A>3qQW@o;2{SEqJ?Lwr|jf}MxN?=vvCJ-a85&H}hu8@3LF z`i8Axb@Wp_z6=Wfu0EdCebO1nRFkgauaqf7lc!IFoVMGp9&*TV^J_caJxW0(Rv$ODOnR2p9((#?^v&@pA_Yx$ z?e1S!u0OnT^|s8F|H6N%wXb(Qq^H$hR4=;3Fa@2l?o7r%sm`%{6kQ~!-G4m|Uk`D&nS z&bQ&7=l}KZ8$5QG)Rf@Aw?;wWnFz#7*1rGNV_<0&7Z9ZuhdQ&^D|+8zUc>;s^vc{DDI^jW1&{bC&hzLKtj^zFY zh$c(W1@cBo;jAUKt-iVhqkuMG071-SC@p+P{4tkw`}eN`FaIgWzRA?i$UX}VvKez=_^IL(t!!YdeFYk&*>`E#f4i4buu~I;mSOPGGj)eNB zIo9Q1Y0hJ20N;qCl-i6-Pv|(W0Y)zzJ5dj}z;oTff$PjN3t41- ze*#EWZ=Ju*UA7wj7%TNp#nbQmYr$$7Oyp|G&pYh_5`uz-`kkClT>{;OHD=!5-)&HI z6`3>tHh`qxrXbA{YW#5-C+hL~!PN*Zpv-emmgyft9k*TjRs#-U|3@jAg**mq%#Us( za#YCyq+&l?0N|Ua*$Qf8OY;!CTukERh?N?&4!gqMDIOJ7uC zH+0%;ALzdQ6nsE0^Zj0-mPoFi@}ShIeKlwjl0UAdP3C2-3$f70B)#Ba!nuBuzAf`m zEt8H1>sNT6RkdF%o)W5S`WF6Re@s~a7!Y3$fC#(g24txM52FKKn;6I+j`RGO98;5a zVDvjdHx=sqIGRF+Av#hW4Gl%mEUIciCa5~a810e%Ii#U`89UX^J%xRu^)EXKA)7!d*$MR z(R+<*a1QCPGuQxdYI)Z{9Yo?qNaBcNrz9l|NZ=3$)NplVBWFUK^Jf}ZFZoi_h+<|c z4>l6*{p*0yZB&8P!i*xn+D!}BoF?vBjsl_%H%OIjeik0_^)6cPt{F~wj+roU5}a{r zYyG$o!nzTC=z0t&h_SUG4vcUiHY6|J?VqZyx4HM$>cDRJlIFEhWI3iHtl}p>VHbQG z=B4t1xBZQXK+^K4ZSfP@7+raO3+s?7)r*=}b9>!PP6EUl{Z>*|gY?w^21v3iZ_HZj1s&r zuR~TEHxkv)Dz!&Afq1WuYZwn31Nymspt!VQ1R#=^q94#3rSSOeg;}D5sLC@+TnmiQ z!Nz#j!3TaEk0>5_h9!Q7xKwn>+ynT(t{ioEinVvv-1K6MHa(@Q3VXKL%ut1s5hPX zh%K&-dVp59D7?k~2({uqX=K$E#WWwyJ-~=R%qc*6@C_|-E8vaH%ZCHH7(Ba^C_;Ge zc*aS(8Xtn{Tb8+wT!^rFg8U=eIz|?e68k@`y$=Co*KZF51frLsh%9PNeXhdqh~of6x_op2KAZvCtFr}sT1SzgtJfd?X}hk;gDrwB zM5pddhK&Q*${v4U|5#OT*Uh9njub((oNqe~op%x5e*cL6Z^nOYR%HBdzu$oPfN!mu zj9f62=_1Yl0=q(Na`JK~pbKC)Opoa|ghtS}$_%2>H^Szj_|W(9^ta?MmkZx1wzo)?#U_N{D-!d`qT9$ zG{gtHoH!7|nw~^5b^00L^%ea?x<}pLZ^O6n>()k#AZxZt1--qaq$pAYvZ&Q&bi&7;=)0?6(FF7-4?WvX(}2*-yC0lc;%bly zQa~Xy1RGaa#RX{R=S)tM{bHkKuhsupySb(sEd^}Ph)(||b zoCe4%f!-ANCD_!Cb??3=q>d8AAl~FsjMDvDbZl8_o>h{_tq9a|I%`uALUrIJa{{|w zlg(#iJ-#R$F;7eMcsCIBJedKhFJ3S0q=Qw;R0^<=sbs!uviPd#IWgxqnde`#nc{YV z1H#IVrf&jY8Kp{}udBUwSKp4fC@Qb`h9Gh%D=HU~8N8{J0qO>+1Hf;NmR5Gr>iLp~ z9m9`XE#tMJJ+A`fY$9q|^fRzS6Rh@*FenzLU{#%enSi6 z_D*<>6-I44uXlxE_rsf*)UPjzF7ZkH!I_i)>CDztXI>d%i(Wv6^4ui( zEPOv1C>6Qr=%RTv%&&*aUNi&8heSj3EZXD}H`y;-LbJ7;*u2+8$M65`EN%as{p`8l zSoV~_&74X`ESk}4jTx9)CaF6)ObXA!X%9YLp3Y}r6ZMKqKQFz#%y*r*ejV^XR-^XHD$!!L<%c)H#GH1a0XI$Xu;#!)4?gldMAq=AY; z-_s2>cZm7hBEAzxOxoOC(jtf9a0kb5m21j2OagTU9&)~AHI%*8)7c&uh9>2!H*ACj z%1bT`9h#>tkHD9yVW`AatJSwxsG{?64k!#)K*H*&qdhPLRd$t-szYiQaXi@(;r3Dv zk?Qt0hf~JWOx?{D>6v>IrK9zh_mO{gfk3O+0o1-EtM3P-PPZV{WbHvV-w3`78uYjx z>+`7$cq{}RPow;7J%|t~zR`noO!*q!6H|83E9%~0)5oHxKb=hj9`6hB?%%}Rg?cV> z1Ac;EP6wjw5P^g!_sC|!>6GmTJCDW!#A)eOEBY4e=Ztl9dGNrlS$;D9~MO7&d(FW}1sF-aETTC9Gl@b*w(FTMfRocBV zC@VWW3pgNUy^3%aTB{S^ZE+h2&kY}Z<7LZ+&<&P zGauz<8gwPhEN%n>bCp8Z7hAOsB>ZqzjM znVB+OQ!MxqimnpU#B3uzJ3zvDKu}NFnuaS+luniL&f6{mvD7Sh_`$8!n-wH^%FpiG zU!nVc^V`+`@Z-~nHNo6I+w_Z_uN4Xx&pDt9xD4;*p#(K$DXw`|i(6fGP@hqx+L&EN zzP2J!6?<2armoBw&hWd~h0x}8SgOAfcre|yMVorlz1Jt35k>}|h zY*>`&N|GVgroW>9Lm&rP&}08oV85}$6Q$Mu9@wf<()D_vIO3*y(_23Kv6j`A&XE41 zj%qQ6$i#2=o1aa1JL%3O-a0f8&?T|`-Jd&c!RpqiUB>C`YW8uXPg7afqKSBQ@FN17^nH?2S`C8dZY=Z8@#j&5ah9iPu9<-*lT=lch zQ^+QtT61i7g63^btK?qTVt%S)F^cRUwJYmGhn7u!tme{m6|cRg{f!*Kqa%y196^r) zpO(W>rM&ivzhnE9;tdA2)M;NP+d+6Jy^@-NY$oSME@`uTM`kE8(pjDWJ@85b{^|6T zTsZg66K(XCSFxaY^DDN;I!%Y_SyZvmn<$Il9$kr=WO8Q3u-&BL_94553L9w7xdsAd zwNC^n-=LzfFEbLP%*NgfqhSGAzP%MJpJX`T(o~6Qs66BwXOG~ZX}}83$3!W+$orUD zk%W@w2V;kCt?g4b*TkdP&)6XOOFJ(_1n9RLdl0#bRRm%hzlcQBd6pT-lxw30EPE&0kP&rqH$s7d4WM&vLzO|U?Z#liB4 zW@6_}PY7kX-Usw9xj~%9R?xA6a%Qq_C>X7|9H+~XXMd+XoWXzBd%ln2tjHC8_SFu{S)@vG6aV3?gpQLFVys8Tiy+veO+nIp^S z2tjLxqrb_9H=z>3r=ENW6t|Eu9jIT!v{Sw0dcJdEyieeyV&S6nokJy&CkB(UYDw^_ zZd;D{MBUVR1<}rh#DIR)t46)d{ zAUQY*r4Xjq)xHt@6u!>F zR)b#?q#{Wd$Nn;}-XrxD@B(&D!6{8@RgP{@{pO@4nEc`%i0AOEee6NlRDN5xiH&|^ zxgmGMQ&|Mrzaxu|p4JZuPMJ%@899VliWATL(LHT%L>F4cLF|?(?vv(i08W>J^r&iA zIYRsaA*@DBlY^fL_i+r~HB}D44uprEc_WTmx}`qeT#}e#KqA*gH=?_aJm0^pql>5Q z%=Iu?(LIky^}IATo)9L`{@5#Uy9^DG5twGL!3Rze8 zuhyYC&pC(^1TBfx~UA_JF>X-29cJchJ}Z0H@^uAetPk&85TY`ZwB5T?9l`#SgAuLK7* zL$Nui!>jrPb~n>t1D7#nTREk+qO|)=I+*jjq&+V8ygwZtfuHUeoH*a_8gjXu)|hH%{67=ZRr|L!DEz+AXJkEW>l@1b;gn%R zd9eir=M!VYen~q!YQF|21(Hv_FG5O6gk5AY8#__N-f4TK%xIYE~ zp7R%&`dGBxLk*L2yYZmxJyN6C-TnIoq)pgtcgB0&& zH-ds%>!=Cr605vG*$t~9vCUG%1!RcO%lH}jqmr`7b@q2^qo1vp@3*yvY}>I-0G%_H zalzjABo8!@-`T0eW_KXQMI~Wt$%;etMIk!xjZ@;LL`*5a;*5^j?~|_oPz`9--;gIb zG|eh{2wEUouFudwS$2802=g1<0prh2^dI$8Hr#C1et{?W3oGrKI3-0+Zp0TmbE`GI z84F{ZYf_usWKkgK*-3FOx%GZ|o{F-N+{Pw1Y3=30qcVeyg1&7IM;bqBm@)UD#6kaT z01~){GPqP8csoyq%eS4*?Pl*aoJ{45NBdQ;Pbk4NqO_9uz9iGG1DU3nRaljZ3M)IE z$I&a7LSOX&dhciPYi|&;tm-Z{G3Q+gPvUsh$Ifv4Kc8_Aa8jhE$@3(--fnQ7gOXh> zYARtlf5{g+E$S;s>3sraxKm)}IFVjxTs(kuwY6nBRCCwtqi?EXqkN6Z>piP`NiQ&0 z*o)x1>sFKz1#An}dbYG?t<;U1bI@b9wk&h=7|KwbLt67M2ZNrd1^L#kLL+TsWJ1%o z+IS<|)+60i)=yvZ`3@^4{n%9^)282bF0U$K@@qP$o%6Vku26Oj?ES8*j3coK_{R)I z?(lH=a_ld)k$!%uFr+f`zA%j6Uk5$pD2ESN{DP{FOk&}2mjHZ=^&Q{w$=X+04^ZV| zCijymGRJE9N|*G$?8eD+({qee-n=dNXBRPfq!@cNrs3#}t z5#F2$E1ztfnhM$(?_Rw)8cfG~i7BtI=SU7bWW=!?ndY3;@y%x_Jw{}qZJM>2Snj%7 zP&qDpPb7UkTP|iDbex`394=UHJNtli%nNh~eY1!0GSu7?xKGOP;rA!h8#wKOmT$Va zr(>OFpykiub6Sp(q)!>b+sziih)U9+91m%GS*?2D3i@K_Hf=HJ+YVu3D-pH@`FNw< z382wiViD+~3hmQeD~6*+?ZdR9z0xF5cxcX$-?vr_FIgp)q-v^~JhEklD8c^a4+t?6 z=%TrQTG<3;t1GqVDK#=Lv%d|*{H4&B*d)-n4TR|A5whk8+T(-M+wlQ` zMKd#%OT1@JK8tjXWS1oiOZ%f04i*Sz$->5KI_M6hnPy362d~%nc+=n;tlA~72&5$3 z?eXxi5r;YQN;%O)+pq0DT>%x{lVJvaxKDD;I{6sC!DOtr>rExjL-RNiwZ+sUX=37Gucl zfjO&@?CSC*(O90jZ!J@B#bJ;cqckZAMx~1BLGZE7gI#s4r&$4KgXr4*^Pb%#@K3qG zulon*Y@C6z?c)hbHL>Jhv2|H4ydmNFY8k)&xU@O>4`62Qk!tgC7rieClqDZA0&V*d z-;@?3OYvCm9Vvd{i6q(pb#ZdK`@=-{Y@;+?T>Kg~sF$_jEun$3w6Vn9ys~xwd#eul zR4NeUMhypEGsVqSb`1-3E!VS7Pd>Q|e5tn7JjxNZ#ZeO-+_#dkn#ik4ThF!w z{mZ6#YXh+30Ju3 z4&-smVsw=FuTX~Fhr}-fK<>%bFKla(_OGQrNZt+n`zZtLTA)_Y*h7E&d?Knh8!wEm zJ*!`eZl0LmkYmR@EUUfNe#>0)y`3myTei^sr;QDoWN5GGXNnok84lRUJa%Xre5rf1 z=%!&cF;*!5{Evka#&5reb+tTfe2)?&LdUkp(np(1O2AXnSJZ8DdG^uP~TfHrn(s&#u}eg#ui^F`712X_ae-zLfZF z?g)6IC44``(s8o3vgUO45M5{~b?h5nMMi?d?pb%mP2I{TfBv(6 z6-Kkur}Gg($STl*ULf{Gp>&I^r?vM#xS zvUnfF7%uaaMfVP?aP$;=9eKcz;vx+>rpKu~`Go+n{1=how#4%vB}#D$RuaacM=;0z&=QQFx(6 z*DN~&U>SAZPd0Dk4Vz>9kwY_tjOQMbEUGckCoBn2`X;p$N=YU*Mir4vKN*K6rGPqZ zm;J@9Za2>u(IwHfp>VEdvy3u6wj>^TGt?B!>#4@iO_V-LhU!d!*w zfz${2!px$wGdl<+_nAJT?ae7$&iG%hBeLap^wTO$*lL*QsG}rB*3j~O)j{ha3`%+h zw{A#tlk^IEn}!5+HV%qqIR3FaW@&roi1N25n#S9XvOZ1Lc%!LUH|z!Ot7)9<2#-4u z{^etUgfkw--!v*veK9t(!7|aNO7_hQ``)y(&h{`&>`73WUegoq{dNyUNSkbEP=%Wp zvxayc%+_5(5i*6!oxUI9hE&Z*V1^11)^Z{>K;s$jHKk+79-v-ZvQDMF;@8wsn22Dm zElP=;;$hnEhX1ucw!B;7AKiLlx_0#iL;-iM9$INH(O=I5$1uRuZ|mff5w|ZiTyC>l z$k6;l|N0k6_-c^{LE)_407Su|pOoGw24o3!;Y(I-?|V!tpAve6_h_|RTMX}1`+8gD?;K0G36+(7rkcwmdfGt`Lm}fDdp&El9bG>?Y;QZJ z8x_*#pd+#b8wqn!2x7A15&X?G*n%$ToS#%?&AMCBf2)5#wnS1R?Xcb4Uu5&%@`PSv zCkU84W3E9LNt)qql)eQEDiKye2xg-KN;5-)^*TQj`aYO=!I|yLYY|0_-nH@ES%t0I zE#yxUXb8snfADqFkKKiTP-@vanBZ5kA0_4ze;~!wc-UN)9unFicfPPRs+ogfd<`1s@o-6GQQlIu;|fKzU6dwpX6luP{cBDpk#Ys^NdLi^r9rf z4Lly)C$(W0clX-CP05zV6%0_+@A#owH(pyGlaF9Y8W@+_lM(NuHmWB6a2LmNmrp_6 zH}UXBXN78hX^OE!ViJXKpGdP!>Y&z>96B$MTTZ~t$W@sko8TkGP2$y*68u1YM+m(Qzm~_lRg& zbEvDLn=wr}Zr$}gnlIadLely|4+-Tx# zk$uS@_d%;gleZinx)e-L{h=c7wrc*yt3Ucw!&cS&aS-!F_5&XNREN4`J%`%ye$ZTv zIZJtDHf(9-bwN9VGs+Z)&L;iObtKD!nMA2|uNC2}gJdl?%j#994K;$_b?X?q1SRvD zzHGOEezM)7owfXNgQ|a{bX&Lofq}iLeYQPU*gE71DxcAHmtvHwAx!*S45!^)- z-Tx?zGbXK`q^^?3t5K+X?j7Ypmj_XoUy%8)qjJw=m&LARz>~}Z#=Xmkd`{mbrt%Ss zdC*Iewiz4g>N+;SSjnHsV~wh7pmCSS&?8$VwzKpl*(!>^>+7#fX7?u zNPee}eFhuS9HoE^Vx_B=kMPH9rMg0!s`jEh;Rk82rQk>rImx%p_Yd5Ac#ea3e3;h{ z4t6KuT~-2)OP45=diYs?A9r)<7yk)TS4+QFm$c&5-YcFSi;pgx&~Sz){XRh+n1JAn zCAD9Gq31X8XeXn;Lvj^W-*x&fOnBbM4Vvi7^q&1OEl;$XXHJAr=rr!Ds9Qf!)0=Kp zeLQ-orS^`W*1-Cx!eB8bROd+7{T!72<*eovt(fP~+tyu?3xhv^ww+AhlGO3pG?;(xrTY0CrJY~3dduc1)X=4qGYSx~&Re6wZUNtm zm|wI?L^~IzUTT^=w(g>5n{96ps8(-~NE&bwy8p%UNmW-GTi0}&i+pvewWOHclBi(H zf(@o3o(6`XVMny&Q|v=UC@dCtsOq2dlb$d0_!rqU)>wYDUG+5hK|HoL+FRFrR0P4p z<~`xEDcf&M&I3K($=SKneJQiMnffQik$zL@+Dr&?hP?KkEr*s)UvPy*ZRuwo1GO=o zTwKf1crO@a5l0Mh(AZ$Syr?>5TXw3(bda^h^P1UOpQ?|=;B+8mT@$y+64kU&hpt`B zWj$LHFWoIF^n80&2iSuZb0*%ACgwsi&}y+$MCNVwv+#sZcP2yPd-ayl<)-4g+pNnk zKK^sP0mauFX_tBNW#_}+p0<7V;bk6TKSxzooXKYB#!IPW1WidCAtANZLsHH8$;tZY z&2kc7J#HVYh#;h;xf})Udtl??=!>r4*)kHjfUc(jlddCSC8(&ezCaLEW`nubS%A{0 z5lHvXuza2T3!!{_f6*3tP5RC@|KjjZyLrLt7RugnRf)Zh6w7Y1zFFxBO@Dr?!EhYb z!oN!P(u;$>ccajREEMAnTDB82eo{TO`nonFMeuaW)E0xE-?=ZgX1g0%i=WLWbpeU< z^rQRe9`@XUbIhZAs(3?O+Zdn<&Rxs{64tEGY^-`xNeesk*6?%lVUMeHDqUQ_9sHVyKyk0}gqu(Cj6Ui|>-|iO`EGbq4 z*=ZBgt`3XsF1 zRz-J@onO$pBGMK`baei`iG6d6v9kT~7#C(G@i2{cE&m0i5?bB9M?h4khS3MrvL>by z%YdQu=xR=xsPRO<*H^s_!_6}`RlQTv`^SComY`E8dY>&Ao@v>=zuSH080p`CIld=(Nm-qf0Vt5wtL%#Zx zDxh`_(-zWbhlqk?EBWfGwqK06t1i)D#y4WeBT=Xu29ExS6TrwU8*R5ejjOEAucd&M zC$7n7=MP(!tWb2BEW6AH$@Z4UZF=B(tgjYFYyGUGfoUR`gQ)0`q!Gpad%U133AQV! z=4pQ+p-rky%j)8OA@jwnXZID58X^#{jmYLTVNI$sb*>vl1+O>X(|B_$Yv$4nFHOYlQdxN5blt%k}`Y(#X3FA;qcMsRU1)x21wC|SH||K-Z*M==!HIm#kPRvRa<9EG3v zmtt*!y0U)NQ4REFX0-K5m(?lqX9QsiFg9NAFvT7KcV|qb=yoK8Q6x-b!N3fO;};G; z*(4aj2+{2<7-MnNjiYLx5bwi@ZNCKHX0Y3Es+9`(ULk9W+CX=8Ms$MO;pD7f^)tTT zW?QirOpkMx$Lh>n0tey*smSlY3}@y!83~8lX{bt;kZLo~SpsHy&6h|TyQa2p-)rtf z#2NBwe^3^A(hjkjw72CrhXZ}ZB#ZL(jdbst` zjB`laEWwKnmNw8%DBfQ1C(N!I+4oA(7q(PNOXnab6>T0)6x$FuylWr?2eWyEymdWH zJ2qmcNm`$k1b9xmH>tS))+T}p2yOztT3t*{a~2?P8lrVnCO$3!_n9qtY&``j-9wG^ zo~r48QrGQD5z`(l)|#q3NI?jFg+rCB6+f?(^Q1oY^lbp(aQOgHqstxP|3Jb2Z$duP zi_W^cpzvzS)N_M<^r_T?HVN~K`YL#f%WnVl*8kU7T^n*?x=ZBPUc z-JHV|?kOsjc!s0q!A|@WIk1L}XgAB{rpprC*k#8}9tfl4*a)_IG8e zKXg%UUoY6^ZCBRD45d}T`IKUAJ*Y~Swq*FpZ4w?zt8Cwf6;mf_(KfTuyMFWovW)7y zTjNmJ(G&NUF;#IvpZA*xGx4U4aciz!usyVo(%2J`AE3|ZHNB}KEAuvdIaUF)w(kcl ziHYRwwysyWO!F~%{I?Ve=?xr;3cwuQ_ zbJgQ*pzQMnAS``ateNOi7>&?-1%d$0cOZ_S;_@MZ+R-X$+qQi-x@tYGCHeNWOIjJh zOtJzaJA3{I@7p{Ks4@TH@$UVf?Up5~_|}OIsO0|UXUy@u|E0aYk*8BJBvkTqv$t2J zw%AhA@N>(Q)~(C{(%#y~+&nn1u1kFumPo?H#SMWuHYnHuvy+q?ulknWn4DT~zw1^t zs1;118T7Z&`Ox~+MV`lp{4dQ}+lBN2+P?Yi*j#&9^OJ+OJY~mn-^v(4f_OFUN>8@}=I5b^AnqnNbYS z`6NkreJLOe+aNFl>*AV~oKB!56~#<%gn9Yal)VroEECXO1-OCTT{R}Vrc?84HwQNM z!5*2caG9)Y*2W?BNb?k!N)?y1pBjcd!Y6D)IB;Z@3mGncqGkHypnposGn@o-Qu_C` zH$PGZV>~GR7^hJ>29Nz4rm0ANGP(8hjR^Jf*&i<+dW?2S=9Iumy*}1+&}+iY+#6;< zov0u=6bR0JwT3v;sxU{jmVY_s71N$lH!mP+zeYpQinBBZwZ-AiKh+Y`A+^dm?;qK= znVD%$!fj$`j&AH+6pJ*T;2IgI(a{g(U-`YU2e{eXrPC7ardF%#%-8@o%RdnV#;_MU!XJaCAej z;Z+i{33&bA1n|pZck`U(eS^O~DChT_fp!?N?@rO~0=cNqe4q5AmE|@LU(Q}{C!Rw4f5kkN=qWqa6cxI<^Yq6hy zs2_U)2Vz+(a|H`Td^B5~G9@DGhf|oKF6T)40k)~^?8iQFBAdTw#Mwcmwh9mlw%vU3 zwHzo7v^pDdM<3j0c&{X~q>DAc4%X@`iL4Y?4WE=gHUCp#<^N}s(gOLB#{Cg}arUkj zO+i=nOHj6itK27<1%?h1bKkV-0id-v8mrE<>z;%5%?jBKL1HCp*y)K5GOeT}>${+? zG36Z@bKlriO=(YEB-9biS3iI`S9bcCpEN2XLKLJ$2b0fVqWkaZ->MH^fD$yL-=2UB zKKrnhy>>{Y7_TcW*Sf_WT>Q-TB(#I{oEN%a-zp2xMCo^2Gn#YZDth%m=!yh7(bQ_5 zGKLnss%mNh=ywv?Cfff%x>LC18H$X_A(IUU8@Kbe5)1QCN1F9r6vY=|f0w9boha~p z4{h%>sBupVG0tY$xZm!)F{X9PxYqgES5ww_o;;l~g5d2^dLlK+BQ1hNigpDf{bJ5# z3H4MH%;r;+vWVT6=^-G(e(v4iO8>9pUsi7tDQPfe`hTij@Ziq)v`n*<9HAL-8CTu( z&pd!+0M)B#M%n>`HhPHrF?~!LfV!0lA01YjuZiDrFp)Y1E)_oE5-)yqbg$M$(Q^VY z(a7Il#m`X3W4iZEG1+x(OT}1ArWl^_w$~Rg%h;Kv^^eoSq5(?H8 z6-RYBjf9_7l1pOOky3~(+nG2m&`0Qay8>)ssn@ZjtgaWE6^0mbD%JsWF!Z@glQNpL z(dycZF}-g9Cthqo%ddT|KBL~jBgmLp$>je!GMo3r;m;Rz)V~$K z3Z_qdC7#c)&tg}#_;8F%`2;9%QyII%;McU`kwAu_rTSqT%(zWgY1X?A*)s<785+Iu z(cnyyMgzC`>Abk4Jd}Ir!E=qAWu$vbs1nb6T#QUSXG_B3K7&G=ZtW?MN9aUs;4}LS zkgd8M$=0#l3)zSQxBs%Fj7&0_`bvPg$*B`>=6F)Y4=&-)G$fIw{{kx814{P`mOGzZ z8x;&y@+cX6s2t^AD$$uxKs1g`q2)PqeeJTwl7mueVT9LOT8S)w>(JSFW#pyF@L?Rm zJh0^f2HhY{E{t#p2U|&Nwg8r&`xTx%$5|&EzXQn1d-#5vZ0;snWdAYeH)zb8Op(0% z(^1d>YMp~?+Qf_E{VyfzJ;N&SjYR68Ai@ezMs88uCss zDxKdn*3WJIONP#cu{75-&*LicRb>AGFn+W#Br+)13*93kwg{uWWe?T)N@7#;V;$(Q zpIE)Y>!;n!P1rz0nGgsn6Vm|EXki0X>0%|QUV)s{5(w@6X#cqiH!0!z%b$;3z*xcNnoJ{GJVVCNY29H?PHPj2H z<&xIJ8S4-V!JU=kWwS!E)360~oWOl^`-y6Gkz6qM-8fyu#jJJyS@qxH>6B(AF(;+P z3tAo7#q&2+MRLQHj^JP*m=JZeld_{U2w1-be4!@~vtTRPFm3m(=Mt~O7rHbSgr{Q4j{8jhf^4LTlR6qx*SIB64g9*><1(43q z<%Vk2J@qEqjrT2jp>r#_H)EqJkgm#rDzQ$ra7m1Qd=n^h;AmPSJW#XDcyiv58z+el zyzhFL503ZZjFLAM`c`} zY(qS+{AL>Lqb7}AUHvy-4bed`*C?Or?q#}VHC-*@)p<}74sB<`|B%ubH0O^cO#Xcq zK#k4DvU7sy9LF2EXZE%%D$K$Rz*=D88ow2LrE|$JFEcWIxc94j z=2xmKdNa7B%|N+BDW&4e%l6~t+K?FVL%A`nU`yZ64}nq6v&z4TSix>gq&$7AT3suU z%JPcoOE5oaPn$3px_a}|3tJ&3lJ7xqL20Jx$?g!`Xa0B2fNT})cYP>b-Viuh*0fPv zyAIXL_6auK)LlLLVRNEUKzs|W-gMFZ0kb(9`-N;-JD42)^I~a{OEJ|i@hzWF@WWI% zXr5e6t-p((eb&!@OcL3>ph>R{Fz_AFvKA}7Ve)-n&b|J4K0EoPR-!<9P9=?we+^}h zC6jaa2Ny??4aVg-?gH?u3;J^NMPA;WlsW=Py>SDRJBz)#=E;>Tl7c(=EM2qim1SLb z)WC=vLkaQi8py;@vp3Q&ctOR6?1|<3$&NZ(bF=OGeR3ig1+?&BShqY-13i_;q+?qZ z2*f#UFRYDjuu2=9PczA*qs8gL-Q z>sR%!iM6WRLNk*x5%nak*23Pr%7P6s+b&XEi zXgbmSW$#D#-=-&Kt`mAz%Z%^cO}4`5YS}8+?T^f|B87bjp*R+=P!)#)k$+hj2cM3A zI+hj5J+$$X5%okq6=~KiV22^VEd2!YGH;|yLEI0sdv(iIT$1!ybP4C23y1Rivgz_9 zenobx*0Z8CPgA>+7KJqmF_}23+E0}gocao}Oo=mZM37#m%Ttv>4F4o^xFV$fZ=JSj z{+hXrj;cQNB~xV2T9v{lAt$ z`MB8}(*Qv)FSVD%WVFCM(7Uw1+AD9)FjgkR^Yptou*%R$Av})1F+?`WiPoB^rV+u4 z*Tg~M=0iu9M}!cJ-?Wu`5aQgvdIJ@O`!z)#-K563YS!oo>9Uc#ZF}*1!*iCf)nWt^w`zd!&0%NvQt`r~Em&2I-gwu7h zxlF{yQq*Nq)L(xJn!NTYvv4j^rAII8IcST$MP1iNW9+i1l|R}Ds&HRAhB+4=Vfre~ ziN*gfn1DVW+U+BPE%CQ3Cm3Y=fhZw6uY?yu{!0(d1m;uCP96_Rvt+dc)pLgL(zCYw zS3+!i0nMyW=|`2X;73jk@~rNB8B$($0X8u!IBF`KU`L-ssQ4?Q=~;%3NZ4P^Zw#^+64&ay)AdVo;nn0uo)vN zJE7_>4gt82N2&T3TKsoxwua9iI0_d1O^x-4O|co7D^a*2V33tgNfC8jX{R0u+6aY0 zid37CHj|H9i~>EqVDfa#5S093NJs{ZT_kAlXz|0_Albi}+lk^^{jPQg%6hSBtkR?^ zAlmA;E_jJx9jc1!*`E%pu#?CF3;3gT*dDo+xfSIifDL&JE-E(#HlvWM1FvMcCB3pZ z$$xb$d`=Sw^OJIjJc4-m-fpEs2aTM6+-OX;F=TAg$Uu$^q)UoVj^v8{d2yFC9?iP? zcUw!U`AVIw^b;bx2MO2T@xV{ax(XPclKVEgYo&I^0RxV zYWYtFj{gTe`gDPk=`GNOisV+sw#k=V3G}1(&3s_MSV(sv+!?=bRS$q`ocHz^^2#Le zSm~X7GWc1(CIW1WQcJPy{8w}+>H^8t^!Hwus`gstlf8j31w07aynLE*09*o;5wd%> z6KC0<6s1d_12-XV65g>@xc0S+84q=rM__xTmMS6#GPB-4ld9E_W4g_8#~)k8-o+AM zry3+%TrswN=VjW2PVM$!qKTHrnUKU$conz~yN7be%+fQUY z_x0FH0i~G4nx3Y4MCE2*`q&-24FZH-v?0T~AS;zxTO9!xy=Im*Q7O`%3P1HwYcG!| z>omoEE9;B(O8B4IZ$z+n>M5uv7}p0Yd>*Ad)Q2&Yr=gi9U5YKs;@sWrM=?ws2hUdp z%+-`F))$l5Ho#oxulE5R1efV2x#6mbS0jMHvrNuHPYLrREA({ z@U^)?S=FCSlglQ@dOQl;2G*4*XAD=YCng{=ukg`z*LhsJx1_QK2&6Y&8*y&nbB7#) z`KX$_lY)!vfgSOj=)f84kB9|TRJf&qz11Ba%0O5p9|9M)CxvS4I;^&xRQ#RYa&W**J10FkvC3s0ED;&^ zw-iBrMdDS-zbLftbSFEQ!}zQvfQ`7kw9j5}dkHi$v_`Q_@n^-Vwh{JRYT8eYjdF0F zzzq9z{L=5t>-RXtPtZ=gtpBOCPlkVH6E8_HUMVn;gTLao*w0M7H4vd~4;$(RNbF|M>X~k||8S@*G=E$mim=^%2W;-1Zv{zR8 z-E(hgly=4==4lYj+4|c|uWqti`mPxdUA0rCQAC^K+)R1u)3mc3RRz^@}^%Mn24y$(DH?@~fLA`#8d#0yE*T|dV;9e#r5kc~& zb0LP|r{{EUKMVdo4wb(8_`P!mFr!-!*Qo@5BTWJnAJx7Yc97hW$Z?%g*YQ=F3kR35 zP_)lO5X_x9ds53;@$i?|>{PC3)J3fcvUZnjY*Wiwn|f{!Nlm4K@g%h3R~&3>FG;*W z&m-uut$z@r=7Z=5bU0VHMM1@L4-B1o+si_jsAWHO^+G5|3Rm3}f)jqS(8If}6xj$o z$`MABgHx~{!JDP7{dlu;&uAUEDTaDRkM=a$5s>GVUJ@AQNxYBU5@gDXdAnp9pa}t! zX45Kf#-(LgrGM-P0nbkAs7NgtyPDDFSJsyE2S{X~{9x=GC-?bNp@m0MY(k;!?6cvN zR|IG4ftQ)az^01#?1R}U=0*qJle_}VV!fkf0w?}Z8wb3}n~vk*&;2Z)ga5_edq*{$ zu6w+eQEY%6hiVxGK|tx!qN1W8MXIz!L8NyQB$yx;7)3yoB2oe(y+nEm5EKRJNJ(e` zDWN0~ln??5N$#7Ov(MRQpE>KE`S0Gf_CK>`t(lPIm*;)nr+&Wb8^1U*N^FYnwgb?d zD%jp(cl@#D2>{(m{H^>UudL8S9XrdslH>cFh#yt0CNMZ+aahv8$)Tx{vDM1ar7O}4 zTTxc9!lPer{WPMP4b`HKc75VC3h3S*E`e9btsW!Y+80}5zRShyg;cEN^xJOqYFTqb zV=^Djl3Q=UsTfIHo2b|yZaE^VWr-<)DGxl}I|~Z^orPe<ZT-Y*dxYb z)ZUDSmaZpv`Enldu`FS>**sbeaahZVSliX+0(k#Q&da^KrjVD%qFLvLYT-}%yW0*g zTASIR3NUvC{&IsDphy7mV3p6OEd%Ok(wxDPyCX5JO5+WO_6>w4U~i!H*ImpSYP{KH zFy#D6FeXeqvfez3ndG;vPV<};K}*GXNLaY6FHSu$+C=*xl%Tt0A7*6il-7dhVbZrF6GOf7N zdS#E(;rNv0<5KCooQ-WH}rjirnxM#J)#1dGGA9~Y%X}A2{ z1p(9$ywrnpT9<6n@U{PzvED=RJ>r|SWISnj9@+__sm6!wgM+u~k~pu-pF2_tt=#A; z8ZK*ISvbS6N#)Pem-=m5FHu#K9*O4x@^NUW!YCBvtD%Tyw39$nl!wVcLIc%dNeMCe z@UETc)MVGtA^jJrt*wKx}B*8ZdlvZ;=|IyK=1T>+#6D z!`sPM)_XC%*aqmLnfr!ix0O4dQFxSOu;!WNtD@_+O>fA`D?~21hr^JRtaXjnOQu2n*pPdE zT~q{meO*)J!@G0UIE=Z>Y< z%Gf0=S=&i?Jh&(o>8_nVj=nvBy#xAloPBy(mTZz_!LK@puNxohIUgp=>asGQZ@#F? z&1?!aO9KT=Y1DXEEoBZpG zZ$IQyHNJm3=Ei?%tr4FL^KAcL6Gi?7vlTXR5`4z{?a^`kU)wcl)2EDtSih}*2_yN} zm+mtJRFvQ4ZI!lz4|&uCyldJ{mw)|ifBE5GSZ&!6an+;swGVi}7W?l5nWjzv7jOhk z4!dq1fTwo?g^-T_Lht;)gC@gb!Ta}oRdn*-@qc+@|M*m4;^3K||1AYPw*S)y|Kqo4 z{3;Ee*>vK$^H2WH%PvHNhbz9H&y)E-nbIF0==SshKr2~pe}Soc#NE{qwQ?_wo9tr}6)bbV_vEbo8!p?`6~ zU2x_jpz~j-?z{MxUhSWLfq(wj+qe1g%A0pbkNqvjn#;eOD7OwA_*-skdpLMi*y&yS zxBM+MIE1x(z06IYL_=U={lrxosj`u)4Npf?|@!bHx=qmjK zU>%dd>cLk~GYmb)=d%$2&~E%ero`^`h)us3>`xawz4LiHNTJ(y_UKwhr|kHPjO6#d z7c>rreLB74Li+j5f@54}D6gsp$c=S?X|-P8CZGc4YUKbQ2oDgq(EGm9XQB=!(hLs| zz4a(|qt(&7Do^mIfIInkL=qmTBfWJ_u*qKhP<_tiI}jgJ1wvYng25t>`zhlTS2ZB% zvvDQG&EPg`W&YmsbSpsgrJVyZp@~Ca&id_`a-qlu0~@NkUx2b?9`cpEOHH1MY+mAyx+5sxVpRDCYplBp94 zP<@@C*6$7|rDw_}!o}W#UO@-Ha_K;}IC#YC+P+jSN9`?(=4~puv+iw$3s8`La(?=y zUI*AV^QioxvGCU&{2v1V{ z_%FBck$HY*wzB_J=-#=_VL*iM4ZpTJ14h%jo)+79|E6~5)bHYN4UGAvhZJpEv zrQlBR-^UPjpt+GU1SDX~*!(Y^%x9r_MSO{h`b8z64G@v6AgA{|SyTAXxCKC~RiuBg zsMut%qcx_@91UPZO5OjynGO^^@M!H<2#Xv5ot-;l_y;b$(5CFF*{XkV4WJ!%f)&Bo z138pWo$BMG{2FIXh{onH#14BDW8fZNvTt10Z{=u5+Tfz*cud1A(7#dU_tR1r`6Y#? zBG6k98d=Een&Nz=)T%2LHzLiz2Sp!*b`F<$RSsomc&!1zt|tNr^&0;aT;P9j$(OKS4Dc(24+K!0ftHhONFe2Uz(B2IbwLlZ- zE$EXMPlyYA`9+0otU>L%BI0nja@f6KQY>PH$VXkyym5+dHG_yg(R&+zS$XegE*LY^ zre`UlPOl!A4S&-JhGZzpr0bh*4JTW$X(CL22fZ;(R8Kt9N(Ed_UnbbxIP zvz@lXFAdHTPkamD|8G#p7$2Dpwn7~s2(8SbCB(XSke1nfRgdlmA0OXZ1aqr1?_V3c zi;Ogm>$6pUT-4O3+oV(7h}EZrYOVjCE6{r4+P|jUM|?s0xxMb;HOXJLM_nkg zQU+7{o~ZDccq`k}cHVANQR-c#5{7KnW))2Jj1KK*vDIw&BUIwc@FSmo6Rw}TlBJ^m1#PCaKtWkv6E_J}|d} z*EIm3-YEcC`^frvZ8!rZ`fJuuM>adwkavF9gek<}p~k;>v_0yeaM0v{elNKt{=j`c z%NcY{#*lTdrA^fME2Wf{g_W4boB9&YDn0oS!BUTVVaD9OLhwu2^?K1pa2D;9V_xSOhS<7sl zzS4SIe*}1EWkZFUy8`9g*;d0j9~CgAd2&vD&P@~ooEvcPQ-(LF#QuvBWJZ9r;hQ;O z`rsCaW4?gx_y))$7f1q$p|U;iA~d5LXnZpKJ)p2;Bl;U_y;;5C+f5|+u1{i;kM+Q} z@V^V=1{d~_e;Paa9cp1NaD3`OZ4~xjU#DLT)C0>?9u&LkE#E+*vcaq`woh;E@s-;u zI!!7kOxXP|dVDu@e^*om{@M-Sg8Qb5lT>?xu7W9|jHf`Y?`{PCh5PgE{Sp;a+oCU4! z)A27&HB>@Y7YszL6E)=HSAq9V=ELS^ynI6JTH4=nz;Afg1i}eaJ6d$Lwr|KFu7OWx zwvko=F!y^Brr%1h(^%Hf;4f!@q$z*RI?%?;t$@urIk{rF44N33n~RZNQoTXl7{0zuA6`?1wvd^V>Y?tWtE6bHX{kMitx+xAkCs?(3}mo{B6W;+`D|P&&?MfCN$c)W;`8^hHg0E2MmHmuFO%gw^J(aCa0)f z39W^qQhVRkW^wa~p^N>_p^YB5p6JV>S!g zGJBr`mgPHGzf?%&L!5b13Z(nOJ<8Esi-@*{ z-8KGhK_kYzac_HCgLm_gcW7^+PJo+N&0J`u$U41=GDA$%pf;Mr3&BWe*Bfj9{9d7g{g1k~|CYAGk!Hn-u>uL%(gh zlYdK0S?)U6tQfl9ezEiTJg81=f6@rtpmG@q7%rO!fMt_6AP&-C+2u$Bd+73v7YArS z4j2UW9+Fj92JI|<%dWlG5AfmiA2;xC*BsChp7TNJxCz*JY&_nqc2>!&QLRCBr136~ zF9}{)3^tB972q!vqoH^^fU?b(^8jKOSIc!iDDP6WU;6aI%luBDqmxy+X<2^9Fkdpw zFrv4CzrsMPo{DxIcKSK|@%u2iJl!jmzdmKAG3#D@RO!ImGj&AGGq zT&_5G$T^h4tPWW$ykavP*bSH+<~)bOPre&d-qWovvLP00lT|ZsJqUdhy}_UN>GylK z*qOXOw0l68S<~%YxlsJgsxkx!4iK>PKJk&IaIepwqEZidBw7tQEr_PR=J2MaA*~cw z*D}X4u(&~pqxEx&(yVXsWq!L7;;n|5AP$0-eM&3-lC#3!Ezciv&U2UA_%1-baS%g! z?D=um#H*j{h)m5xGeq5^geD78&r&|DRHS9xYd}qx9|oN*Jc_;NQlB@pISG8f{z!tzbXJjy~ynrTFZ|$pW~vY?l<{ayx^9kGsa!(6EbTf_Y9wvUs&kKtPCkm ztvyqm=fB!rFm6`6prj^96sf?Vbr;&*n^5jzqf3N|fgi~W!<@q-V_=!nCONB?nsXE3 zh_99NEOVU5>iR;d^EB<2n4pJG>Vi3G4@7$5*~JDURVQl;>3)Ubi+yid;}$HquIC8= z%u2(JnY{M2Vmg3euV(fjIXKY)k;kWGi(FM7eu*a7;T5SZ$}bPG2KXwQu54+oaUebL z3Q@HB87okNdV1%I5W}BN7o*}($?jGPMRfwVZeCpM(GxE6s-4dkT+HpwnRV2eTI)_k zYqrtY-4Y}UFZn0sN(vf{`-b^EtPBx?2+PL&_*Kr+a2PfAV`_yNGE^Lti88Z|p9Mm) z^&b%qs1Xo2x%0{UozEw!jE-*12yQNV7b3r-3h=aFyQcCPv1qQ&aWaWGE&@nVN5f5)q*zQOvve zw4ef1pj#Zta=mL|(wTkLP3)1SYGE3RY*InwT8@gC9v+C>mNKE8t9BGt5Yp@(q>Pgj3I*kP{Gn#ok~*OR5@eo=26rg4yFh_l>4d$Zk4z%4)1- z__`jB`3B)Ltt%?!dHaV~W6(W7{jgFUzj?qTH_*HH{q>sdxOf4BXWq4LV*Y7gwy!zO zC-#*lmBFIR2J0uz3;x~-pzlc{n4;}F7fWw{^97rfnnr?FPkugap%g9)&593`@(w= zgHhD!P?z&b++NYit)D>4aS}NB86U=%M|SQ!MFg6vGC5}j_gg*vv_BCYTHvwbxp!=w z;wKHanbe0E<#9ml1~JT;?7jUOPz{b|wMRm3<%e);#R%2|$AU2Uf%G`m0g<3@2n-4^ zL@%v2MK4Cc8*JEFE4k?Fze(;knSGFKz|g5Jj@QiiIBW6Xj@Yq`fcr(5Q_rjcPI@%% zCtt&0uW^poH9WNlMzDlzz3yq+ugF3u_&MJ~JC7K{?$6Jv6M}FWwx<*CEp2ueX@74y z?{;b$J|F4TwEF%z7cb&hTL&+IxLM8Ik^F?v_~S_UuKSP34l|5Lq=AE>m!_$rV$<4l zMp%npa@~h$+`(0T(%@Rg*du{6?k1u`-l2$9EyYY~L0af14_0SZl962fe!=UbbF+30 zo1UpQZ3JdUEw@{v9wc!smKyKaewy;GmyTf6i7jUK+A_2PzD7)aWJX1YD(fF@U0qm8uy9G_-t)ZZV#$c_0V42Zz;$a zC|hR8dIrtud9CW`yl07oNxa9q~~<7lq00KWb1`Co4fJd|mS3>FlR4?eSuc z_ER=8?1zh0pLNHC>XjD6vl`K59wO%R!C+%D(~qroh#C^tg`hkc?z|=0r1mZeEY-r!_;72I zeHv;cU8KFf9__co7q+iLNcXqD+R^7iS;|=$?&Ol&MnbnUmV}ucwefDB!X>N0#W+KX z&IK>~Hmt*KoET;~KFJ3>y300i5;MNXdROIMqh3{yISDthpdHy5##F;9- zT5)(nRn@IEO5{S4MBz z!#S`EKW_has61^^MK2vA{Orv@<@m1@p>?9e6$QQUf(IDzh69%G4D z)>80nXP>+?5Mm|F{aIC^2x~AoCHl(u8n2SIE?F@PbYhF^GqjN~$+O`FX`8Zb0Y>XFw~CTd8bma z9n#yinvPxNkL0K584|f5(|t~e>d70BKXmU!YW5GrJi#g&CMd4yG6L3aR(OLr;kDDx z*}SR8M|O)kiR^YnJHAjQxGo%RF23nm66q+iO-1eC$47_6WOuC|zH;+S$+5%zzaLo; zm3#hs{mH{Bjm*#`j*D6uJ++>MTT-niGJiglSJ%SPM~D1z7%=ya9p#9)@#S_4ym(-( zaDVV=NWF%|I{aqeV;voFX7WDku?+9_OO;5CoWvx}iGm^ckj$-?gigg6$%Q;Sk>so7 zpP?mri~S9y!B4`u*js(Q1z!#m%|{Au?+ZB>SKz&YGLe6MSi_=K>FJFD%~z|Dl>F6L z=&LN9Uo3mxa-J4R9%bP>1UY7wY4OEk8K{GAXLSb!>+38Nm1*4G_Ok=>aEZ)~l}m=C z4>fKpk%XSw(wX^_CCM?(3?-Q2mg!=r%Pr5P#M5C+7jo)qjqQp*F;65&vjM+087f@W z|NIpWZz;@y&DAnatt@{Fy-)eLGhbsY|kbs+`5)&>Pe*U)+K z05AYrp;?t1nQxl*b~bG{9Bk7O3G2f-=(9;sO0L+s5O2bHwf?pnbF5i5n<$@}dvTV{w_|Mk%3nEXBh3@wsoA2WHPmcn@w!?LZRg*QwG>sJFUJ<3gx}Xv1!R0mz8sc++VFjf znpu1WdB=wt8LlZEK}o=4IaV194L{opI281~ma}jxvar(L62glC_&wa#2s+(j&&@UnQLaPUX z_=U8Q_^ab$3nkM+oInG+g@#WKm4nqG3O6qV%6QE|4Q3CJgxT|O>|JI_IJ)uviRyy| zeX_5m;6CkrL(|r9pJ^-))(~nBg5^x&^_#x9^t;YoCd{$upKxD#I^6H6Yaj| z2hThfgQst3e%I~jZM`Nub4gLtc>#aa7Lr4$1vmZ)^lXUSF6nmYEq$diTaAK`yQ2TB*0dVe(UZ|wURqixzI-BbVO4p za7IoMZVq-2b6-j6(e zmK;&}g{C*4S9M4+D}ZX0CUR5r>cZ}zARSx0)ZREm+K*%^@!Zr*)=yKNflhZ<2&Han zd{oxAy<54UFkbf*hY^vjr?|XQsvfrr5`ZXX)~efFonxXLIqR)Mc1i|x&j>3k1DY~- z(q0D3XRO6qZH9Skj>Fco9I6ghPZnEGQHnoynoQAxFS)HO&jP5MdwiF|EDX{=X*7I} zf?=;rewi_E`v49WY|JrC&!AwFfJd&AdLX)r?2E6SZ-1NW15__5_zza5@gbNDMmF4q z!uxck{ppsMi?xx0;4~#zZWhjP4m?A7Q#!9=ar8BYP-LFrVRE|VGZ@Bxc?+|=N}R=< zSpG#QFKs0Ko;qYvAPAQKLK9oZ!_1BCG9@hn}fyeWs&X=aR>zAp;s|>s- zwQz;;xo9ewM%zQFCFS@5F?|)&7uP0*`)H}@uZ^NP0N^@j@C9f$BTO98WuJl6os@hLx~;m zDW}jUTDZ7af8RPY0g5>|FS0Dj_ep=vhg+En}=@m<@lP}qKRHaI&-|D$@~ZuIAp z-s9|D?U%wibt?6+i3nz!V!6VWh+V?hF1_h!da7w!|HpB;?mqoBHm@b)(XG(~BM&!k zedOkGNaH;NHlgZ7(p!eKf`QjF(DrSJ`8#8lckhXnvuTUza(Kn%fqd z)jplf+I$=_qO!t_VJB(P@Qn;Rz1*uLUn0zN6!k$28B9e>B0oCP36Fzyo*3=ETsB`C z?%t}uK6gttfgtHosXGMI7O^|Ek|foiB_f#-_%=tR)BUH+N{yY*C1dVZimA1L8VsT| zHa0liP0FwnU@VVvZcK5J@n|!HC%9hfRRU@v(+~3f#Gi*o>1j4cBzRZbwUD2MWRg;@ zP*&>5a)ZTgFZ0v%6(u6USuk?jLs=Mm%N51b8-zvsP;xw+B&y+i+mV#v)U6{d)9u*u ztg>4^CPA>ix=feVJ6X9mJ<)Kr%q%=!Goiw`s?H`%4^8}4bLP^>;&|1bUzOflYVz(g zU^JcOuP2_N;#VgL#V~kNku@2;ZzV<~b?SM=9&*suOsmNSwV@0T@!HjDxD548PkpTI z3IBFVQ0&Z~2V~$XuAbX7Zau7y7B!mHiBS3ESFp%n!sTgUk3a4fuln&^Vz2|_)Y5wE_*Ka5A?#yN88zyMlgifIMm-)zM;5Yo zg|3&o@s8#4wjQdo+aqycS4UY(miFiacxu2>8As};%5y{8)YYX(i}A>9DC=c$^3u3j zaSr!9PIUZrfnTwIySQf6V6A1|QimEOd>|zHnV0SeI2Z1IbFXHL4O8s=I%l6BFfI#` zFr^NxN_GfwTw=;_o5@oeIYJgWLe=9Ff#~^>Dxb-gn$;Ai2yjHqc4w=>wEC5Mwb`t~ zvM%=u&$%H#D`-aJu?h>HL^@vCL#rJH^`sXPUKY3Q686pqIolusCsZw57I=JDMd^fh zCVR_GBM%&A$d9+p-gfuZ+WG#+$0~YW&SH2Ep&xZte}gS-eo0c$xqTz#M=&6ZHV2>X zE<7qlo>z}4RtQ?CRax=kM2vXbM&T+mBC63y`~apraR_;FX}j3h;cmme=bja5qS^I@ zRqd3-6mOP~#!i!eumDWIoAM9LVGo^4@K4^bxi7i$5nDccY6^0`*SPl!;%J2#?3cdEJqb&=QKxSWSXfzvU&i?_MLyLUr-p?u3B zPt-R*=h=vG%y;5I75=+mj(gXdRon;sSNr<9LnogX16rg?{FKgh{!X*mWdKy z7oup1A0fhQMnMsXxiqmJyjdMco}Cx7`t`+JZZ*9W{a`Z9r;H{y`k<^CUgQ^}%+r_k z?ps0TQn&MDkBoGV^CTA_KftA7n!1W}GMBp-yqfGmObCd>g&NkUvE+@dr$S0YQ>-U9 z26M-gNSd7*-QjW$0;P|_yHGQ23A`I7+_jS4kqhX44@zU9jld{dgyeFMr1$~j(#h#S zJK+a1g$9>YM8n5!){c9rLi2#pOD@hzAiLu3O+q|Op@5rfGpXf1TuOVO?t4u>g9R!4 z%{TYQ+0WaY#DysrP?U!u%5NC^?b%|yw)l9MJbb&WtDv9VB-TNzBBl2%%o82HSzlWQr`>!eba2hj!ol_H415tXyMjV=B zc_`zyh45G;2dUywpY@Z zmB*+y33;IQ^oo?J2vun=&fNG})oH56H^KKLTF?157?yW-O#ZQib=5gF6G4uZ(ewBU z(U$k5Vn;pR%&^bX7O+*TRpWePw{|<*tW$8+pDq|Bm;Q+)d4++S-=IHER`VET3J*Lp zmDEA5{9bh|Fcr~#V3?Poy`YWPrZkrh!mnc1i`wJ+F2l2;o#UJ2eXPE}>}8V*SuIu2 zyR{!HkT!1hqZxlBBm<(ai{xr0@6}KOlA(I=&E#UmrRtClISgpxDk@_D07|FC8{#^u zx{B37OSA>tn6qDmifkM{w-Im|{8fCNPZp!5GGo(W`PZ}Jy*pZp_7B6+(|JPy{|mbqS8p;1N4jW^!r%x(!Od9eIo z24j+s;tyGRnfCGFgi05hQ9>wAwg{Zw0$vp3MQ_Lbah(?!jxg$24xh`;7-U}`LG6N zQVczDc0xgQX0tLuyac2UV`~?Qla8`{<{}fXYu)-`UxPA^{pu%6o9%fj=uvpCQ>VrF zX%8|L$J5K?%g7Pycd_3sv$vSvaH-NvU-Cv)ylvXd0{Abu5O9PL48-#J|gy5z>w(89rPl`~cZck$P>+P-EG>tv*F3bMg>Pk`@JgGVG* zZMi+_CGT7}N=@LIHA520x(?9PSmd8EdD?xuY~l0{E$Fj9J+!p%HHR}b89INe=~k7;0xV=hGmiPD zhZ7+WvQ+Ka-yKJ$k%N9X1;LU18vT4jeT~EFT|A_g8cwUfX!j_`D{WVE@76urv}@U) z;Hxcq7sa>@3hKVOqa4Tf1ETq=@+9ju@`pYpqRvCK#$MZoxM1%3`(a|Jmt7Wd9<#g9 z{4^lszOroLD6KI)&>VLv=W2Zybr?otR+;vEDDt7HQChYl&?OmLxTBouB?0CUCU*0e zw@c2ICf+d}lpma#vr;l>KeEms7mD=-2yPU<<+9or}C)N~kO9~fW zMHCj#Z)V=*X<$FEmq?B$c{w=WV!NbtdK>rbg%=OSY;GH0b!x2d?nOO>CQJ<@toNLI zFVyj3?DTqfg@Xta7tw6E-Y`8g6(cOEt-H=?w5oLOy035 z$L^0i#AUUA`EYAbNxD!!mLH^)LNB>OER}rcfGVf!H%-P5*=`8adIj0)Y|N(Ygn`I->lx<{I_}aC}CdPHFTfBq6-E zcSCRsz5J{baaKsq?~ELzsxEj-MEMK$jwLF}r;aZ_a_}Ma z=*-Y(&Vdn#%c-J3JeQ_PCT7&NJQ;cQ#P*hNd;F~U1dXY!Sn4nfGDy`=ejY`ms8o12 zG%w_6k)7h|z^62iw=Q*W!}Q2B5sxIb6d*CzKN@JYK0T5UBF1}Qwn)Xd+0DaQvyaY{ z^#DDo-wLAH&Oerj%PEGVD9oW;@!=V>8M2NvZscwYeQc_FWNN%=ux?`lx73iVk)|Id z8m}Rph-_fc71fGRGu~yK_?(fftcJ3=w2SL=>Y3_?ES_1*zG14M3-16)kuq{awEKr41JS+~W?g_Y+oOTD!Jk+q(@`5rGEP}o)NApl zkAgn^)W*i9H|5%gB&g7i?R$>A)WF6&793U`ty3v}h%w`?3on5qUwj?r5v3U5+9qXunz>_DpHOM1(@w|>ML(pCV91zOQUGN--XT35gyEt%I>eu&RQQQ#`{$T(K#slkYCwc~{~{+XV}L z)p+kMNrD3{aoz^~$>Pgndi&cC%6X_pxAv&9=o=#1Hk&jM zs?ZhKx(x;M0ohl$BHDdI@TdClwrUF^n0r%#lW2&wDcU(KV>q!0tr^4ue0k&hGB3Y! zz(~=bLQqlC$@EZ!;%hEpVKc4X4(J3i|fNYZX&7b;1wU6+BqcA#??_PjK_i zNinC3Y`2%YsqTR{kz^FSQxR1|62!`cVL?y3uA5mjSdblOR`5S#r+L49Gsm6`Of-V6bes*31mX1q`G}nZ(Hzo!myw;`j)zTsUXK zr75Amo2s^1Ja@~2zO~Hso-VO`YbLcY{NT{p?8GhZ=i7u!zpVh4ajXY$XDSxHFN>r^ zDfa0RgbZ)Lm4`$lyJwBZg6zt>h|rKC=eeGYn@_573Q<&|qBrY5T_*n`zZQ0rR(oj* zjj7`*eQC3ea#AXf;?^19zm~1fqVn$bB`RCcGrT#yy9@o7zbTQSRU3!>HW-QiX+O4Z z;?S;d)D`GBP)Z6H{o0e&eE|?!*I+?kXT=P<3*pPecFDOpcWIhb6ZPWax-5X>6UKeGd?kTp@P7eaK0A(FOuJosCJh6m6!}#be1FN_2i?LfQsB`#j zXk#OxU==`a-Z9MaN`BO6=^<^WUO8nGO>PvgPb zkIOckhQP8m+j*znitA$7l}K$|TAkv6C(Az^i{~Ogu~P!NPTMjoq5PA#n4I?}pLsod%c{XUn7&=e?L1T4b!*7V?gJsT9@Wf2 zgiEUQXkuDhnN=qo*HTAFp515wIZg|ipoHH0yT^inP<+6RHTkUdk7IwhyVK?43+MrU zpwWf{%@lrGqc^)H*|-U~yc*#vftVb6GEOpR129`n`U~8_8`fvh5*^34j`|-Iv;LKH z_zf3K^yR#LPbOnb-2FJ%70m)ao!0y*^sgK23hmkbL72PSga)W}#aiKXry6U2=Ztj;UMEmO6q7dT)nysGcfRVSLiC zf18t@E#lYU`|aG!7>}9>@=bYO(pYUWfaoTtXUr6tXAutdnp-dTZ^my6X6hbYs-{uX zP^&{~DKw_^5LII?~;37e$kmf0GAz`r8y)`QzkR?5EjHr#lP&OLbuN80t? zSE|;{1|pMZVFzd2t-7eF2Zo$pzw>6nGbCm4KR)gLXay@T8qQJc<_og8qPbX;GU!XT z@GiYDy?BNHN`I&~2|Yzl+;%(p0xK2%E`*HlRrQN*>+A9+f?{fCRJ~+`dE}R@5}~Xz z`UZ@gD5mjVpqka2-*{A2&WFy7K)Sh5Yr@gxL00uYrA*n1??xGXw+Snb<|*1MarHz zuYcF;HEeqj%1cl@pXuk(KJ$-@y{6@w)qxH!>TX)&F~}Tfr8E^TC)C28qlA0TWiH9d zHi60GampLtDvzG@i_chvA-^%{+MwI<9#nJxsbGbNwFfBpF-@$#oAUFa<~pWEt#nW~ zNC{IO08rTec|G~nIAjCdh#rljng&Hw?#ZyKJ1Bk^V!ij71ksedv#9K*{qq603jeu; zD^qUcC9=WM{)>9V`{RA2s}SE@P{`#>)$N~cu|>J8F1{%y1$5ecWIdntdHyK;pvGNZ zDvvnF$^|&} zLP>Ot*C$=AaqE|ePsZ5p^o1~ig8kamWu$yT`frfbY(1mI z@GTMl@W(gEMS-LpN#Qd!a6;$@D#Sv5b$Jma@dgWaMiGQWH6KcU-ZOF}JS&HwFpA9i z>AxkS)Hc_*YG7O!e&;(n&C=(AAqY&9t$|yjB}3KZhS3c6B{>hN`O&!sS7J?*gU@MVQR{1I>E+(qfh*a`nC8e->5cNYghU0h zGjHC|#5Vnzt%7gp5LQ4B{lV5%f>viB+1^IfsY+aK7xJpY`HT|6@5Ul{x7%OA(|+K8 zcXvQD71b042M$G&1Vpp9RaLh?X(rK=J55@y$`n0HBXN={#|kwq+s!i@@{hmR8dWW>Nmt6XV9Jg@ZC$*QV(6ack|QJ0xx0Ik_8f;`lhns zs$Xk#x znQ2D^_6#*16I6V#N=cY^{zZ77%H{gEmK5EQ^Bw~Oa>doh{jj;z4{2U=A$D_Guh80j zU5zT+tL=FrzpU~KD|kb&1uT-dIaO;kl<%+UaCMWY7*W;yLi+1*q|Y46)o#yDaoGnS zJ$D~%pVXJgy&>@x`G%~eZub3UWhegpfZ`$;f?oW*er2;Ni}5ZbY==W&|BLM8t5aaG z!r@)}uUFa&Dvm=O=7`Z*!;~*Y$5%;pjHAXPdze>>Bpum`@+(_fmkwVMxsaR)?O$gr zsVvSGh+MVBCtH2I%#GA9k`Bx$3`{aO2Zt$$c(6D?Zu5Dng;0G5yuIKjXmdk&<@1ih z2uta^-prDQuOz_IdzOHm#zth(+WoVLSiL^NuWzvrEFwnXQIa~Eu@TViU~HvwzI~^7 zFJg@}KQ~CXn*=LUiM9j!p;GriC=yl*LJ`$G#P^pS{m-96?+-Ha#g0{Mx~H>v_TZj3 zmIMQ-*9fsl!9Oft?msO5(G3zykq63i3j17-TT6tZYXLl1Wh@543C7xaNBY^i*m_8P z={kT&rJ1su#*MeVqa-fLMi>=%ia9p97`+psJ3j* zEUz)|)oiXA6U!i+J*Z*v=kNy+Mq4idWd=QXVsL!0_jxUia9i=x&6g8j;1#&KuX752 z5xj|A?my6LYSLD;l?Ba~(w}R%QB-*k-dQ+Hp|OvLED}DX5Nz5>-U*pq<^uOPn%~nP zDw4Frjd0I7}yDi={;5a>ZZ}r60T{A1D)*LHssC>&g9sR za9f<|TU)qgHhkS7YB2JlSoY}r@a0U4@2j)V-@V4A=>#aq} z4p@z3K8)_sESKE2Z(Z@#qFOZAW3UV!Exw3kPzES;)Ad7f2a#vx^n+QO^=PZEHKu9t zXZXPjXe-vp+NfVL&Iq;q4i*- zD>!{HSbDMfd_m!GxPjzrbke21f%~yJjJRbp`ErM>jxvYQLFBy%gw%SN2FqT4_z58v zPU=-{oAHUr7jt9u>IiKlv|geTweg3J;3NmMw(WjedC|FF^|*YuBV7)D#zSJ)T*Zh- z#ITxIh?R)muHP!#tMz!>JaW%)e~J!EWe~&{kmZ;_;~frD-Jm>DYG08GOn#g`$WAN| z2$cC?6g-n>Zqssk4*F^7YOm((dHw03Q^}ZcfG~M{2%O7! zN{KJ#pwC8o6#py-q0Ie@ZeA$Atv~5;>K^#DmM4VxRh`DA0x(ulm!EYqUOjL@_I7_& zH{ZzlaeK2S@C8tl($yMJrqE8|8@UwZ{+I0~HEt9?D3N7Y)ACI%JX(K1t}_Ua&4Z4F zx?R-FO7n5R)=jww8_({?$&0!V`^0w55OG0Emu<7Gd){%hRXl%5EYvJoBp_hTy)WQedCMfu>LW!^u`J~p@_q+#eQVJTE6%NdJ{-dU;n ze$%`5Q=@}QPBT38=UA)5AiyzkgFjyHk3teQbB?z1CX#47+fdsbx=TNJN1voz;7}fN z6z)X0EkO*1^xaXU8J7rtD9LhAk`Ki*kSOAHL%Xhx9_8wCmn!l!tkwC(N#zq*mPFv7 zlT`lJV9+XRSG5Iqf{}2rp5pd}r;QSni#8<++YVY{2!XZ9F%pW1*wX_9&4T!dI`TKe z7d=>Jd?IRRrkDK5=LoY5+%s*eHB*fu+;97Sn~$?#~FmKGSvl-GC|+VJw@uB(>xN@d;wJ$zgB2mjfL{s+8`7`^Oc7bhy>ftNG*; zK{!CH^w|*T=SIq?Lzo}$gB)~aOS_IYXL-mC^>fwk-iMJlmR8BdW-aeKv4bB(6_?8! z%%J&)MjGlcS@q%->@N~PjYavJ*;9P9S+SjFVqV1&h_F^FJnh5$#sq(}_j{yol{ms4 zu2V!c0RCd$K)k)pZuU5DTQbSG;%ZzaT5Fuq>yJD}#;26Ik?xGqpV;oEe#_LS(Gwbe zp=M=(Vox*43F;6$3bdbV2q+K&XBYRW${v1U)ppKrE+e9HF7fw_QZg`W(y3&8(E(Ab z<3M1Gz^;Zf;YC{_EWE3>84?GitRI$$?Ra@u<2~I*W{3WI4X0yP-sd#?3PWZw$DEf{v!p&b&AgH6gR82#SkmH%$EGhqiD=P z-dbP|JS9K=2=SAIBI5y7!LF^EeL}On!i|ea zyFS>hTG-DIW~hYJ^2Q7Uw8?4=-5^EEV?A|<g}G5q!XIjy7HOs*K0sn`kD>}38L*XFH%kN=CJbbl$3Vnlrt_0-Ni8S$ zOYyX-vfEpwy?zCux0ak>jwHPWqMkwSZ7=?v7u7I37Czj7eDk5JIX`n?SPGCV%^pm4 z95}|O6E@HHL3`HacUR*ji>=NF1|UIgxdQq9p5iX#x!lBr42AQTM&1(yHfX}0P*K@h zl|}2fYIrlRMSszJOM!|DFArZ%kH|uvOI=;oySSOQdN^c$p_mv={fK)TDqKJKt?rm! z_~qHJV81~X8uMatIpdC9`+IG}bMVmMg6vMck@vyKJ5<~GEDu}TPV8lpOah*r#NIvO zh|-IZ(5kfbFs-~X3`6cy10$>N$o@n-Px~U&`vJrH4(pda{KEa&fxr^>pxA|EIn0 z3~OrZ)`cjFD4alcystFKG8Z^}K*m0!?bO$=jSr*Bw7X*D{ajd3;k>+zQjt6XRWpc1l{^}z9T zx7K@PP-ApxRsJA9-f#BJA+6bM-zy>}eF`t6ULJxoBwXesH=C=NMdek|8$DVn=`*CJQ_KAT6eYC9$oq(G2Q)5QK03%JlpLf&?=h|<@_YrB&Xkk@pE~S`}xz6Cf??@{l!M>9Cq(+?+K5|bvoUx zH@b(Nv&eInzN0UC1`$EngG)&lcAr+{&#LxW)=ZhDl=A}3&LZM01Al7eX6Ii2s^stc zCV$%a{vxAc-zT`%;+FSFj4wUn^@I5J&@qFfp&fA(9F^5J^vrD355cFLt z*^WVyR!tV8y7+!++F9uEBTjdC4r=FdzWQcrblS9NqQvA*n~EjbM}_+*2EF2&zT8-Z-8EIEb-3)9-KTJD1f#zF!R6>k(=%TE3^yyc> z>HJ`f5eEje=EeG6f6d3Vg&W3LmW0&Ud>yfhUE)F^8F5me~zTiN?_t!d~NwU3_vC|J^V z76bEdB|;-^*$>tPd4bL?6Js|K1qx>xkwyZ5h4DB$l|ZxAr4qiKybZmW z$m;~&YA!M8v`*sNc~G^ns>Mof<8OJ!st`<6)ruTA!M6hJ%Yb*5y=*aCBEFTI{jo~L}YOps8 z2?@1}UA$?`W;}i>9>}f{E?53A;wHhai0B+k>Hyv2o0T2M#k*uH_CI1xj)gJMA97z3 zEeDy`=N`5k=kJ8RUWXX-z_p?YI}QeNT-%cJ%3k2Rc%*!=t&n`+*4MqGVE;!u8cNsp zfb^Nyp1Lpmrf$qiOjV@quXiRv<$J6zXCw9od2_2H2DW+OSfEPdk6r(SCtduU^a|+J4NSnqM zSM=E8lC=!wLhck5TnMH>;TU*J*z9ObS56q_a;i>boyJmg1u4fU<4|FAvfuU_*oDfy z_TBoCAI0g(|Kp=lEE9TD3M{MlvB6TU{h*`vhF?%KLglhDx8~0Mxe-BT_a_duuP@bZ za|Etl>wM$4Qt7l;0508wlH-0(?~gsGuKm0oI6Int{{|RUBHOVz)_yz%vy&3u9I6Zt zb?J(&kf8ByIL>CMq&!UF)&=`}d#$OcoJ*!EG4F`67fpDM+2xjtq#g@Wo~ClCo>MD^ zB%a$azq>N8E5z4QRf{XAPEbUzpe{YprSj7hIGH`EXUp0!Ah4{37m(H;>mFbOJSzNz zYwea7vB=dD!^%U==CH#|ikTarw-?{SDNsxMS3%8$K}5~9W)0Jst`g?!L^FAgBKR0e z;jl=^dv)qd%F6vOpXVL*Y`-xWuo90{9_e0OmPuKEa;2$IlmFEPYgk$8b3(~ko|n-= z95b>nxHW62x%4hjwp@-To+vav!zFN&w_x8++N@8aoRmAi9&>}<+(ft}=@!eU+R|e3 zQ?FDMlBF_S{E+c8=fph^By?r@TT=4uS5C91dI3y7;k+)-vW*Us(F66@<|B*wdcmc)zoY+D2^F(1;Hi3qjiBNF`9ksD9UEf?SxOIEbT{9 zqb=^jMqtiuT&m@m%*P2#s!#+Qn+Lgf<=4{lof$`E?E-A(Ql*$Cyj?N)A-jA60{QCX z$Al0w)T+Fuhkfji1K9MmJ;I}RM@R=Ue+v=Tv5!FRZU1@dO9F1^1&qCVYzWXQesFSw zl<0~_?G=0!cPQboj7w4v=vOdsP6Km^1k0|AcbwcAtm4d+{{vX;mk&ox!I^-MQBKRH zpl^tn|8W%M%zO&wSrCBnfd1|dnmY*$x(ctm{4vNa;`aY}8=>5jaF%*B;WnmlN_^UZ z!PRe7cG7;A;pRqBM+qa8q$67t&0BuZD1qO_>NM2iAf@O|(sFM*vzx_}es+7jA|j>s zM;NTyw*$N~Zgz-VP>bxSVC6kP5I!RWqHsyjJ<6@voNs1W*bL_kTs)fO@2A|Sq(>rxXjWQ`4_OLkwCD-x%>ru3wLm`Ul$YiCf!yv04=R##gtG3^+ zst%c#?ozw8%zJ!m-Wd_)fO8}b^;MrZ-6HODWgq3vgeyVrmlkF^nMJqw5M=Slq6 zaTMCwfjqPqp^C6wDRKGfGoSiqn(~{Ft_YBJsCu{YhI?8NG~j3BpdqvoQAV0mkk2?Y z=XW%YcG6WGVd5Y|4RS9*lU72e&tJ1W zN#1k9`VM_n{{E%PYCcF*&nS4`uWa5|b>rMoi?R0^lapVr?~xcQG0-l1naHG_L(h5h z$2E6id%&;<1URo)$oNwWJ)^0Kid);34Ut&kZ9Hd>cj_Dbr;{9ElJMWe1LTcshEFpfb{9Qu8nZ%w6saQiZ8phq3}-GIpl2qeKG(3 zW5eb`^9sJ8aKLI{b(md(e03q-rFQ$)+Co!r;<9@b*~m9>)&Ig%tq(koi`HS=vow&&l^ zzEy5IKPsGDMii(A6Nmm@zykA)?z4znpZ_62*ykCZy|yt`99aPyTqUYI|!`q?EhvBux<QQRY_sM)Mf9$zj+~!s<^*N!qRaR+Hzuo5$t!%4P*|fB3C)m~haWM%ee)O!ECxnu= zqSrpKxl0?o<`Y0sO9s^;8dmGe(^&N!yg(Cm@Kgy5INh}) zNchd-MRySBcyo@cS?b?KLB+Fg9mORv}Zv9qQmk&{VIW`06!YmS|3?_VZ5JKQn%sZgrwT1-1* ziz%HTIvllam}^{;ctc0C!ajDwiV^WS|8|txyi}uVfI-Ff+ton!<_zY2 zt(0Gz(v#KL5*j!yr|P&qbn6qTyLNy>%Tb0~z|F3%?hJ`!;!^x+%rVA^+YkNh6ekokG_*z1AQJ_QrV_eUS5U@z#t!{r#Vg|_ti6&1CxyVEFK|T zc~@URGnT&U#pR$!9Th?%_5uBsE)LyjcWnt!qKT6kSk0-h9%FjjeU~`VMkC;H-6-*} zZm%NMHTLUYj)szGu#U`Tc1IV+ZByfo%mN5kIg{g5&CY9JRrf8JXHk|$@zDCMdCnUL4K|shECZ?f3dh#U#%8rXGGm(Z-1MXuap-1 zCg9mPw#mNW$-b(oixBme87@ev2<=-^8Pb)cd|FaU;JcTe*MWV-GwPW5| z0_;$gNIQ1z?3e)JyGU{@bKJrtWUDfR9?8c3uRk1&z{iQb->0yp<=$LSa4SCQRbz{M zd~wTRyRT)s%VGK7AI`Yv`}{}`V_0LqzM7uh+Fnp*6kfAFA<&82Ff2fOUHoi?Ru7|N zKj>qBR)*zdlE*zCk14D0)=MdV>Vx)(>S4V<#FPK;#{T>7(gIOWd9=k1vbDqva&#V; z1-8o1R%4**B;Cw3Fe`+p|MiM?-={!n3$%4~SPuA33N3)xMItD^{hV=Q16bbpo3oLI z>oNiB9uhzfWc9520%~}8i_{XPiK}ZL4w)a8D!r)DrjHJMAYJ>%j<8Fz(gRKl=O*RO zR?pu{$AGo}f0Ssq1R5rpGj{A# z3{d*nd)9kUR@}knBjNo+^6H3WF<}`@Lr5D~4X2k!yo~eGj#l-R4Q@9(b>RGt?LY4_ zzjw>@qTT7SCz>Lf{?DBKer)???BJgZmJma-OGbu#iK16!~ubKFz)>Z@wZ%!!c>}6YD9nsC; z;+C1K*_g7?nZ`bm7dZ8@J7D?r5SK*Yrh9lRd7H?9fv#t5{9Y#5Ss=t{qAXxww3L#jg;!}zSrIikRX>SVvy zNcYGRF+^KQ63%QuVsch84;Fe(TO2aJT&kR!L`eV_eJ_idDesDlK>y zn;%VU+|u03Ryjv9!PlL!t-L^J7SW>iYK=a>I2hhmDO~fzzZhKj$tPi`uzQ}Nm_vp} zHaRKMA9d8;H#R8P$$3sh!IiJx`735niuVDrQNlxrz2dNXtvm308aWf=jGWGzHa&Ng zU73x~;c)HRO4yJc`Q7Wr9kN@kOW)zg?vQd*oaNhm>t{1@2&`;{3!pahZu45EHiLa9 z3uWhu-qXo`S)BCqur%X!<0XpgTc*FO zXZIek(K+=TYq>1%S`!mTr+wA0pO%DwJ6g|~MMc-|)=9v$joW@0m(1u6Av)*{)cVa< z4K9wa#GacM5U#3sY7bbbSNP?L{Oi0<2p|ujcls4D853O_G)yZ6T{)`BDL3U>KhAcq zzgYvdf-R~R(k&6v%Yfbg)8%V;=^l*zofjNM1dz5@Y1hg7T26nL~LKTo-$0+=rZsgxh+ULz&8R!h!RAa@w4q$816@Y?IxxV}1v%R|M9yZ^hT zYYSEZ6<1M#m({*!$HoTK6EYv35)>10%pmjS#Mcfeit8}buqDX4XRi@7@;=z}OBJ=> zVU3%7d7*T;GdEb}G`(ECg6z!m@-fFkTujAuH|xlF^>J3`uLS@3PdV4;_3||5!tVd( zQKBZQrls!)JJDhD;qpPz-5jrKW#?{>99$^k@#XCNyg3~lHf#-Xo~K0VJ6EFzotGS} z4QbdWJU{+Hdfk|MH=+hfSK2eT54ka6ABhc@^_9$;`6Ym>lC|PfRQso=^0z0Io2C4I zH{Dvn=i8|3MRlcLZkR}nZ>@|V-y1Mi=!lrtt#u9t5u=um0PI;BZW3G%Mo2qEsBG!7 z+im;yPKP@OEG)$$*%Go%VA!S%CV5F(>%_RbmaBXHwXpw<$uvhy=2y>^)1c^E!zcja zu>Pd1@YWBV#y?>OW1A)1!A~8iFfa0gBBXaG8|?GCF6w0NqB9j=yy5;SEB3?p&mS}C zl`Q1E&7{Q&zueyZHnvTxxZqJdDJ)-AeE4ta**`8%ZZWbb?EN{Z6=YX>p12fFa)~j> z2{9~m@C*cQuoo|R$@3t#d%|yOj+Ejz8`0@CLHu+-;2UM8%`f1+Y}A`jod%58E~|g4C z2BOm4;$?jv$p}<$8E7|Lv8Ofh^vXc1-NttL8VPfYRz{R{^V(?yjypH620dJA+kaY+ z$6p30b5fj^dmLNqtJ~@&u-NXYDspgmq}O^U9jV%V*UA5_yFS=%w!vY6h`r2`#o*uK|DfTrQU{6`cY=10$Hd>ALo(>T|Y z_o(Q!lh)Jl+w;=!R=d*@!!2=Fj)Kzt+q}R>E%RIf;I zKdGu59FPA+VEONZXSWZziSw6ilLG)w`Z{^bp})#oLeq1~j+Rsd^ zABQrW5&8gqN<)8n+GF(c>2>j!jZ8m4ZKcH@(lHVk3u$uPsrJC6!!P7ORL^ zV6yj~#HYiuIPwL|V8Aj>w!Bii{?@rmtRU^M}gO)gR4v*9#0LN%X&AK7x%+En@bt9V|iZ(wR`I zyI(mub9E-srRa6VkB|lYo0WdoUHcSHoAC&^edO8f-9aBe(71?uT5$&nFj^ER?`NEm zC_X#%h-RZfX;5-wMS-^+ZNMDTq=eWIaZQor=&05Myv7y}Wl48qD?soZKj1gZu50A} z4fJkhK;6_l)z&8&m3m;t|R{GNJ>#LnwDO6m8)|l zZU6~e3Mx6EOg)&G=vrRdC7#X#rKii+)<){t3@9j0Jh^c0%q4wN!sJVlBg#M1EI`J&|2wRDc?L`#_ zD?CLhCN8(!?7n<}r2iIK{I_<%Li!Mwo1OIFTSofeo(r%pJ@&2_nSc2biK#5g9J_^En<{R_hwK%$V>b2B@2m;fxKR`yt8e6$ z>OUX829;m!(X*ceroZAMlmtA#^}5zdaUQDNG1Zo?lD@avh9Lr)ynZ764(Y5l2%?WJ z>~qS=#+9Yv6q&V#OdzsXWiZyBtM)_LTaShtqbY*&HbjO zGN(0g#-d~(uh3waB)faYpcctgv#;)3#rD9BrP7t{MUD6frd_i zK@u#jwn!QBzum6?c2>%1>u@?Q55`Gt8o{nzzeGi~!gg~gSierKo0+#CJQ0(V*w}3b z9Z{sGF)9@GfKk%&mPyAvyQoD_d#xpwUg1Yjk5{jA1h*KBwz$E#`mI#i16bB^fNE8Z z-T0t*DTt8bH)B5oKU;pnLmy>Ecffqfsb?{vNKrUboN2>A^e4;(0CWQNouX#mIC}t9 z%bvE^JbjD~r`dvWwP@0KPO9{5&Bn_f*mwlEWhM<~Lhp@cAp#WEjU{iMFJR>Rn0rOh ziwcNxcSG(@l_*`}iVlg^IlXj=oN3qzGeFbF!41FK#%bX9{c=$#f~EIDEf{lF{G$Nu zOS9)qSuiYQHlzD0nEp40@%%ScgN3(KQPwSr8&25L>pQ}T8d@o72W>LCeE~i0HZ#li!N6Wa2;NQaF)s{* zh-W)_5}hnbN$xtC^A3q!84ovP>^n|$R=C@G8xjK&dWRn=5^D@r+z>7`0h-^OQ?T1U zNKup<73#!;ZhWlH!7{Bvb)g@opOYS`&|TQM!+y|vtnIDvLE30TO7uia%l7kT>FHYt zWXj(Na^FayHe<`F({2?$E*_>oG$u0<4BDc%Is-4qdegi@Loae>4+ME)Kh;09YhGXh zs*N3gFB zhsU7$`7lRncsCuN-T5=>&}3kFESZO~jGTL zW_NOm@5FQ7p=NEQnUJ^cV~Yut{4#Iw>U|;`u#bN(G*>VQzDR?)K_(JgogG~w9;>!+X?Jn!|0Jo!Boq1)O&C^E4#gRU^Wq`XQ3x4HmC z);NUgq7GXx)9o@KY z3G)!>)r9o;&foi;h5AYj3&m>N9iptVH#dTxs{TtI8&zqpiQr1B>-F;m{i{Ml<>UQz zX)t_SvK{p}B%Y`pzh$s;*~RT)@+=+GM-PbJ8;hGjWOXQL2Qu1+b97|)bUo*$_c>`8 z{%xcn&v@!vj3=gR*MF+1#wXiReVPxXs z3^>RKB&4@W&9tA515tbfPdk-*(`B_%N((=!i_iDAy>lek1si)L0 z+jqMR=@|yy>wt&1e0{|-kgFq3%nHv*URCFg*j9_=V9zS+Oi$<3GH@As#Hx^Kc;C!_ z?nB$e!3AKfCYLwMD7lS6i;PAIGcnJo(6GOnTU+`JBp zS2kfr;4$QC0XN5?jfu`NDRV)pI*}dFmmGO|Ij#rp-nlgUO2kF!#}ge83?44K^!;-p zSat$reGbw-l8CDznE6fH0RQR;RV26?;W^pgK{;>?5afDw=kE!w+>;xc3cj_A*Frs5 zyi~KOgm!+1a#RZ~8ycpz5LV|#I+-I$vry7hqdc>|up`W+a-63ays}#Acr8R+@AE-j zY|g{3-Zd+?4;)@tcK4ITL;SXO{VoEk&^Ty^jh*%#~ zDn~G!Iu8B%>yzeW2O|2(mci%i$i%K>E%wU(L_X0Ap7C@GU_K+s`i`>I9<4-a0`NXMd3m2j)#@p0bMFYdL&xsmyE4P> z+Ci_hX@L}zZpujxmrcRbZ37_bfGrkiH3`spCzPVYs&C{Voy>V^5e5(ezvHTebNkhW zt#0hZfm0qmHnA+uVOz~G0xH{JuO^}jELkz%B#;QNx&j$#yZ9eh3d(~+V^~mOpb?$; zT$qXgL}ON@qrc7@FI|Bcjpo)9cGQDoKcFh5E>xial7R)%7brO#w6nnz>uW0gi~(Ul zJ!HNEtXT!nJrxqT43_6+6+nE>#X#q2>`Z?QBZjy;z>*OWswA8|)x0+8t8j4BySPae zZ^hto4IX^L+z0lTq1jeD7ngjCPL5Z{FtY9_HnkeMvJoO`L>Zs+Ihz3JlgfzfqV0j< zCm|PVH)iDB5e^xY)jAd6KA#tueeXZ{Hd>V7H-gab5mv>g44)DorCU%YzgGkBi#_R7~Ia&CI07yE*;aAdr%tOS-WmtlPx( z^v}E@6@tfR1*pb*OE8WqNf&vbb|m0EfYr20yq zK39?1?^xFF)!bFtg^!mY09nEw2uxxU)*tt0a&fQBwySeWL2cR7R_x(F?6*I+q#hOA z(u*LC7;4FoI@!@AD;rStWI2iL_3u0HPi+Hv}GU+=lPJB=6o7t zGKa_8K7x+_VNWZN&jZ31GO7*If|8!YIK1R&l6ynv_T7-HeXU#2&}B6A2z7Tcr~4L! zY%lGq`^c~v7hxDsXD&DYMyzeBKLi9wv8_1bu&1pzn5Yk(K58(u!lgtQchiO}spvMO z{4e6vuM!3gBw^SS<8&amtPUc3ma^^n#KFx5UeOcNt+Ot9UxQU>8jy%!Sol3N^oC^C zWy3w3Di5C^Y4ktuspH}12jL00NQu*a%lAJ&W#W>stfzMW@pgCPIJAO;W>Gndw9}O+ zH6i)ulTy)q)3ZoUizIZ!V?-F!hC!x^o$K)3NoT)<@yaqhY1Q70X)H1>4}koHFVfx_ z-m?Q3kkFXR2Lb#hBy62433(2eD!Txwbixk0Q(Rw1=fYY(LX7JA4MRrq5O+wazFfHT z8+PRKL9inludE}K-%veNT(@%vXtE(OeJGKRU9P^<%ld1Hy;{MK+$zxxqv^V%1vx;J zRV$$GskQ9C3LRw42;WKCUQ!U&05-T3GM(yJA*3fq+fh%p^!n`!N;)*W#E(Gq)$owr z5zbPl*C|wev8y2)f!Ca$yA?w`_7r^#jon%r>8b{MiUDUUfh4mEy2pV}03I5d z7Ic_u^GhvE=@FbL)jV?!2`?l!kTG>cs#sQ09PIc6$R8R?Tn7h)k9;qNuG+>ZvS^ec#p7v`vrdxB6Q}^S zzxrl->#oa2571df5?$*=NKtK1vK;%hX^%Pzi6)DkU`Sm)R~qvjAmNRla|;X6?Kbms zmVnVathl0)U3bfEUZ@T|>h?yw4ArZ0>{qqk^@kxBi2c#qjC~6bF*J5M15`l~iPF#3 ziXVO{hi(MM{58hhkzGZ+<+jj=q5oBH=#k+};3Ga;7hcom?9^0$* zNW&h9d(O#GX(j`<5$*%L(+Y`ZY#t+2~Xj8JDZW?pRC2)KA>dz@~5Z zEkYt0K402zSCRT0mjW=C^h9?);_6o%);GcYNukwEwrhZi$2+EK8nz}x#rMi~s4f^W zlVUU%*I-H*Tfqc*dzK8qY4y`eG!7$)7~FQ%Lm$rQxLAJA3yMYNehOBOWz4A0l@Crt zq9Z5Z?l&vs#qHX-+`roAs1_4%JVuLZY#5{wA(1>7&{yVa?hf3q9PS5Axs~b4DQ&6C z5l%!^lkP!i;>RN?HPwmGju#Q-DX0C^mLwM|NS~U$jB|$aoXdibLxb$2%XOxqRUyY6C+%!vzPu7(No<1*NstU}g*()vf@@5VC=RtS@6^ zPbm@IHwJL$+iJ*}NeCJVRwLFJCU<2hXO-r|lCr2#N;Damrt(>wUlRt%*!fc_shK}b zBlseJ*aYENgy6xRT-NG~um1AS&rvpjJeC+$m3;PhstL$J`_EVW+T8qH-2W`pe-`SWj_ZGp z-hXV=Kc0~P7?6Klp8xWMl2yu87oO&nO3I_Y|1LvG*}{-%tXiwFNIJPLz39so>R*Kj zf7Q2w8}{?cyV;TO@g=8-hc_M-{5rb$^Q~zZ)HFcu@lpb3e;JVc^_FV@(mTYNzPK+1g0i;6Ym|GL_L_&()nqzaNO<@ldR@;lGv zKab@9gHy6u>opy>i-O|LwZqtMn*&RtAvy!BsGZdNxs-z_DB*<%>Ro!v*MrE*fB)`( zcoED>`9~W;fbHzR-uXZL)t{e~#sK5TyJLR&p~#>2QB#MxnEkQ;H?Dyal^o=jTU>hX ecdlVb%|f_SXs6kWuR|2@?}Dn%xtC`xLjDge5C*6K literal 0 HcmV?d00001 diff --git a/docs/hello_nf-core/index.md b/docs/hello_nf-core/index.md index d47e459a2..68ed46193 100644 --- a/docs/hello_nf-core/index.md +++ b/docs/hello_nf-core/index.md @@ -6,7 +6,15 @@ hide: # Hello nf-core - +nf-core is a community effort to develop and maintain a curated set of scientific pipelines built using Nextflow, as well as relevant tooling and guidelines that promote open development, testing, and peer review. + +These pipelines are designed to be modular, scalable, and portable, allowing researchers to easily adapt and execute them using their own data and compute resources. +The best practices guidelines enforced by the project further ensure that the pipelines are robust, well-documented, and validated against real-world datasets. This helps to increase the reliability and reproducibility of scientific analyses and ultimately enables researchers to accelerate their scientific discoveries. + +nf-core is published in Nature Biotechnology: [Nat Biotechnol 38, 276–278 (2020). Nature Biotechnology](https://www.nature.com/articles/s41587-020-0439-x). +An updated preprint is available at [bioRxiv](https://www.biorxiv.org/content/10.1101/2024.05.10.592912v1). + +You can learn more about the project's origins and governance at https://nf-co.re/about. During this training, you will be introduced to nf-core in a series of hands-on exercises. From 2d8f1a629932cc02b8830a9fcb790fe8ddbea7e4 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Sat, 10 May 2025 16:23:54 -0400 Subject: [PATCH 03/43] First section of hello nf-core --- docs/hello_nf-core/01_run_demo.md | 345 +++++++++++++++++++++++-- docs/hello_nf-core/02_rewrite_hello.md | 26 +- docs/hello_nf-core/03_add_module.md | 2 +- 3 files changed, 346 insertions(+), 27 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index d937098eb..08a7b5b24 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -17,7 +17,7 @@ mkdir demo_run/ cd demo_run ``` - +Once that's done, you can move on to find and run your first nf-core pipeline! --- @@ -103,19 +103,188 @@ You now know how to find a pipeline via the nf-core website and retrieve a local ### What's next? -Explore how the code is organized and why. +Learn how to try out an nf-core pipeline with minimal effort. --- -## 2. Examine the pipeline code structure +## 2. Run the pipeline with the provided test profile -Now that we've retrieved the pipeline's source code, let's have a look at how it's organized. +Conveniently, every nf-core pipeline comes with a `test` profile. +This is a minimal set of configuration settings for the pipeline to run using a small test dataset hosted in the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. It's a great way to quickly try out a pipeline at small scale. + +The `test` profile for `nf-core/demo` is shown below: + +```groovy title="conf/test.config" linenums="1" hl_lines="26" +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running minimal tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a fast and simple pipeline test. + + Use as follows: + nextflow run nf-core/demo -profile test, --outdir + +---------------------------------------------------------------------------------------- +*/ + +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} + +params { + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' + + // Input data + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv' + +} +``` + +This tells us that the `nf-core/demo` `test` profile already specifies the input parameter, so you don't have to provide any input yourself. +However, the `outdir` parameter is not included in the `test` profile, so you have to add it to the execution command using the `--outdir` flag. + +Here, we're also going to specify `-profile docker`, which by nf-core convention enables the use of Docker containers. + +Lets' try it! ```bash -tree -L 1 $HOME/.nextflow/assets/nf-core/demo +nextflow run nf-core/demo -profile docker,test --outdir results ``` +Here's the console output from the pipeline: + ```console title="Output" + N E X T F L O W ~ version 24.10.0 + +Launching `https://github.com/nf-core/demo` [maniac_jones] DSL2 - revision: 04060b4644 [master] + + +------------------------------------------------------ + ,--./,-. + ___ __ __ __ ___ /,-._.--~' + |\ | |__ __ / ` / \ |__) |__ } { + | \| | \__, \__/ | \ |___ \`-._,-`-, + `._,._,' + nf-core/demo 1.0.1 +------------------------------------------------------ +Input/output options + input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv + outdir : results + +Institutional config options + config_profile_name : Test profile + config_profile_description: Minimal test dataset to check pipeline function + +Core Nextflow options + revision : master + runName : maniac_jones + containerEngine : docker + launchDir : /workspaces/training/side-quests/nf-core/nf-core-demo + workDir : /workspaces/training/side-quests/nf-core/nf-core-demo/work + projectDir : /workspaces/.nextflow/assets/nf-core/demo + userName : gitpod + profile : docker,test + configFiles : + +!! Only displaying parameters that differ from the pipeline defaults !! +------------------------------------------------------* The pipeline + https://doi.org/10.5281/zenodo.12192442 + +* The nf-core framework + https://doi.org/10.1038/s41587-020-0439-x + +* Software dependencies + https://github.com/nf-core/demo/blob/master/CITATIONS.md + +executor > local (7) +[3c/a00024] NFC…_DEMO:DEMO:FASTQC (SAMPLE2_PE) | 3 of 3 ✔ +[94/d1d602] NFC…O:DEMO:SEQTK_TRIM (SAMPLE2_PE) | 3 of 3 ✔ +[ab/460670] NFCORE_DEMO:DEMO:MULTIQC | 1 of 1 ✔ +-[nf-core/demo] Pipeline completed successfully- +Completed at: 05-Mar-2025 09:46:21 +Duration : 1m 54s +CPU hours : (a few seconds) +Succeeded : 7 +``` + +You see that there is more console output than when you run a basic Netxflow pipeline. +There's a header that includes a summary of the pipeline's version, inputs and outputs, and a few elements of configuration. + +Moving on to the execution output, let's have a look at the lines that tell us what processes were run: + +```console title="Output (subset)" +[3c/a00024] NFC…_DEMO:DEMO:FASTQC (SAMPLE2_PE) | 3 of 3 ✔ +[94/d1d602] NFC…O:DEMO:SEQTK_TRIM (SAMPLE2_PE) | 3 of 3 ✔ +[ab/460670] NFCORE_DEMO:DEMO:MULTIQC | 1 of 1 ✔ +``` + +This tells us that three processes were run, corresponding to the three tools shown in the pipeline documentation page on the nf-core website: FASTQC, SEQTK_TRIM and MULTIQC. + +!!! note +The full process names as shown here, such as `NFCORE_DEMO:DEMO:MULTIQC`, are longer than what you may have seen in the introductory Hello Nextflow material. +These includes the names of their parent workflows and reflect the modularity of the pipeline code. +We will go into more detail about that shortly. + +Finally, let's have a look at the `results` directory produced by the pipeline. + +```bash +tree results +``` + +```console title="Output" +results/ +├── fastqc +│ ├── SAMPLE1_PE +│ ├── SAMPLE2_PE +│ └── SAMPLE3_SE +├── fq +│ ├── SAMPLE1_PE +│ ├── SAMPLE2_PE +│ └── SAMPLE3_SE +├── multiqc +│ ├── multiqc_data +│ ├── multiqc_plots +│ └── multiqc_report.html +└── pipeline_info + ├── execution_report_2025-03-05_09-44-26.html + ├── execution_timeline_2025-03-05_09-44-26.html + ├── execution_trace_2025-03-05_09-44-26.txt + ├── nf_core_pipeline_software_mqc_versions.yml + ├── params_2025-03-05_09-44-29.json + └── pipeline_dag_2025-03-05_09-44-26.html +``` + +If you're curious about what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.1/). + +Congratulations! You have just run your first nf-core pipeline. + +#### Takeaway + +You know how to run an nf-core pipeline using its built-in test profile. + +### What's next? + +Learn how the pipeline code is organized. + +--- + +## 3. Examine the pipeline code structure + +The nf-core project enforces strong guidelines for how pipelines are structured, and how the code is organized, configured and documented. + +Let's have a look at how the pipeline code is organized in the `nf-core/demo` repository. +You can either use `tree` or use the file explorer in your IDE. + +```bash +tree -L 1 $HOME/.nextflow/assets/nf-core/demo +``` + +```console title="Output (top-level only)" /root/.nextflow/assets/nf-core/demo ├── assets ├── CHANGELOG.md @@ -135,36 +304,168 @@ tree -L 1 $HOME/.nextflow/assets/nf-core/demo └── workflows ``` -### 2.1. Examine the pipeline code structure +There's a lot going on in there, so we'll tackle this in stages. +We're going to look at the following categories: -Now that we've retrieved the pipeline's source code, let's have a look at how it's organized. +1. Pipeline code components -You can view the files +- main script in `main.nf` +- modular components in `workflows`, `subworkflows` and `modules` -### Takeaway +2. Configuration, parameters and inputs +3. Documentation and other stuff -You know how to [...]. +Let's start with the code proper, though note that for now, we're going to focus on how everything is organized, without looking at the actual code just yet. -### What's next? +### 3.1. Pipeline code components -Learn how to [...]. +The pipeline code organization follows a modular structure that is designed to maximize code reuse. ---- +!!!note +We won't go over the actual code for how these modular components are connected, because there is some additional complexity associated with the use of subworkflows that can be confusing, and understanding that is not necessary at this stage of the training. +For now, we're going to focus on the logic of this modular organization. -## 3. Run the nf-core/demo pipeline +### 3.1.1. Overall organization and `main.nf` script -TODO: instructions +At the top level, there is the `main.nf` script, which is the entrypoint Nextflow starts from when we execute `nextflow run nf-core/demo`. That means when you run `nextflow run nf-core/demo` to run the pipeline, Nextflow automatically finds and executes the `main.nf` script, and everything else will flow from there. -### Takeaway +In practice, the `main.nf` script calls the actual workflow of interest, stored inside the `workflows` folder, called `demo.nf`. It also calls a few 'housekeeping' subworkflows that we're going to ignore for now. -You know how to [...]. +```bash +tree $HOME/.nextflow/assets/nf-core/demo/workflows +``` -### What's next? +```console title="Output" +/root/.nextflow/assets/nf-core/demo/workflows +└── demo.nf +``` -[...]. +The `demo.nf` workflow itself calls out to various script components, namely, modules and subworkflows, stored in the corresponding `modules` and `subworkflows` folders. ---- +- **Module:** A wrapper around a single process. +- **Subworkflow:** A mini workflow that calls two or more modules and is designed to be called by another workflow. + +Here's an overview of the nested structure of a workflow composed of subworkflows and modules: + +
+ --8<-- "docs/side_quests/img/nf-core/nested.excalidraw.svg" +
+ +Not all workflows use subworkflows to organize their modules, but this is a very common pattern that makes it possible to reuse chunks of code across different pipelines in a way that is flexible while minimizing maintenance burden. + +Within this structure, `modules` and `subworkflows` are further organized into `local` and `nf-core` folders. +The `nf-core` folder is for components that have come from the nf-core GitHub repository, while the `local` folder is for components that have been developed independently. +Usually these are operations that very specific to that pipeline. + +Let's take a peek into those directories. + +### 3.1.2. Modules + +The modules are where the process code lives, as described in [Part 4 of the Hello Nextflow training course](../hello_nextflow/04_hello_modules.md). + +In the nf-core project, modules are organized using a nested structure that refers to toolkit and tool names. +The module code file describing the process is always called `main.nf`, and is accompanied by tests and `.yml` files. + +```bash +tree -L 4 $HOME/.nextflow/assets/nf-core/demo/modules +``` + +```console title="Output" +/root/.nextflow/assets/nf-core/demo/modules +└── nf-core + ├── fastqc + │   ├── environment.yml + │   ├── main.nf + │   ├── meta.yml + │   └── tests + ├── multiqc + │   ├── environment.yml + │   ├── main.nf + │   ├── meta.yml + │   └── tests + └── seqtk + └── trim + ├── environment.yml + ├── main.nf + ├── meta.yml + └── tests +``` + +Here you see that the `fastqc` and `multiqc` modules sit at the top level within the `nf-core` modules, whereas the `trim` module sits under the toolkit that it belongs to, `seqtk`. +In this case there are no `local` modules. + +### 3.1.3. Subworkflows + +As noted above, subworkflows function as wrappers that call two or more modules. + +In an nf-core pipeline, the subworkflows are divided into `local` and `nf-core` directories, and each subworkflow has its own nested directory structure with its own `main.nf` script. + +```bash +tree -L 4 $HOME/.nextflow/assets/nf-core/demo/subworkflows +``` + +```console title="Output" +/root/.nextflow/assets/nf-core/demo/subworkflows +├── local +│   └── utils_nfcore_demo_pipeline +│   └── main.nf +└── nf-core + ├── utils_nextflow_pipeline + │   ├── main.nf + │   ├── meta.yml + │   └── tests + ├── utils_nfcore_pipeline + │   ├── main.nf + │   ├── meta.yml + │   └── tests + └── utils_nfschema_plugin + ├── main.nf + ├── meta.yml + └── tests +``` + +In the case of the `nf-core/demo` pipeline, the subworkflows involved are all 'utility' or housekeeping subworkflows, as denoted by the `utils_` prefix in their names. +These subworkflows are what produces the fancy nf-core header in the console output, among other accessory functions. + +Other pipelines may also use subworkflows as part of the main workflow of interest. !!! note -nf-core tools are pre-installed for you in our training environment. -If you are using a different environment, you need to install the nf-core tools package as described here: https://nf-co.re/docs/nf-core-tools/installation. +If you would like to learn how to compose workflows with subworkflows, see the [Workflows of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows/) Side Quest (also known as 'the WoW side quest'). + +### 3.2. Configuration, parameters and inputs + +The nf-core project applies guidelines for pipeline configuration that aim to build on Nextflow's flexible customization options in a way that provides greater consistency and maintainability across pipelines. + +The central configuration file `nextflow.config` is used to set default values for parameters and other configuration options. The majority of these configuration options are applied by default while others (e.g., software dependency profiles) are included as optional profiles. + +There are several additional configuration files that are stored in the `conf` folder and which can be added to the configuration by default or optionally as profiles: + +- `base.config`: A 'blank slate' config file, appropriate for general use on most high-performance computing. environments. This defines broad bins of resource usage, for example, which are convenient to apply to modules. +- `modules.config`: Additional module directives and arguments. +- `test.config`: A profile to run the pipeline with minimal test data, which we used when we ran the demo pipeline in the previous section. +- `test_full.config`: A profile to run the pipeline with a full-sized test dataset. + +### 3.3. Documentation and other assets + +At the top level, you can find a README file with summary information, as well as accessory files that summarize project information such as licensing, contribution guidelines, citation and code of conduct. + +Detailed pipeline documentation is located in the `docs` directory. +This content is used to generate the web pages on the nf-core website. + +In addition to these human-readable documents, there are two JSON files that provide useful machine-readable information describing parameters and input requirements, `nextflow_schema.json` and `assets/schema_input.json`. + +##### `nextflow_schema.json` + +The `nextflow_schema.json` is a file used to store parameter related information including type, description and help text in a machine readable format. The schema is used for various purposes, including automated parameter validation, help text generation, and interactive parameter form rendering in UI interfaces. + +##### `assets/schema_input.json` + +The `schema_input.json` is a file used to define the input samplesheet structure. Each column can have a type, pattern, description and help text in a machine readable format. The schema is used for various purposes, including automated validation, and providing helpful error messages. + +### Takeaway + +You know what are the main components of an nf-core pipeline and how the code is organized, what are the main elements of configuration, and what are some additional sources of information that can be useful. + +### What's next? + +Take a break! That was a lot. When you're feeling refreshed and ready, move on to the next section to apply what you've learned to write an nf-core compliant pipeline. diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index a848bb40c..9960bcbbc 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1,7 +1,25 @@ -# Part 2: Rewrite Hello +# Part 2: Rewrite Hello for nf-core In this second part of the Hello nf-core training course, we show you how to create an nf-core-compliant pipeline version of the final pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. +!!! note +nf-core tools are pre-installed for you in our training environment. +If you are using a different environment, you need to install the nf-core tools package as described here: https://nf-co.re/docs/nf-core-tools/installation. + +--- + +## 0. Side Quest: Workflows or Workflows + +TODO: instructions -> pop out to run through the WoW side quest + +### Takeaway + +You now know how to [...]. + +### What's next? + +Find out [...]. + --- ## 1. Create a new pipeline project @@ -18,7 +36,7 @@ Find out [...]. --- -## 2. Create modules +## 2. Create the modules TODO: instructions @@ -46,7 +64,7 @@ You know how to [...]. --- -## 4. Set up inputs +## 4. Set up the inputs TODO: instructions @@ -60,7 +78,7 @@ You know how to [...]. --- -## 5. Set up parameters +## 5. Set up the parameters TODO: instructions diff --git a/docs/hello_nf-core/03_add_module.md b/docs/hello_nf-core/03_add_module.md index 7d8a9ec59..223226297 100644 --- a/docs/hello_nf-core/03_add_module.md +++ b/docs/hello_nf-core/03_add_module.md @@ -1,4 +1,4 @@ -# Part 3: Add existing module +# Part 3: Add an existing nf-core module In this third part of the Hello nf-core training course, we show you how to add an existing nf-core module to your pipeline. From f5ec9c5aa5e78f3cf259606a8e68f7cd341db28d Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 08:44:11 -0400 Subject: [PATCH 04/43] Add section 2 materials + update index --- docs/hello_nf-core/02_rewrite_hello.md | 2 +- docs/index.md | 10 ++++++++++ hello_nf-core/main.nf | 16 +++++++++++++++ hello_nf-core/modules/reverse_text.nf | 19 ++++++++++++++++++ hello_nf-core/modules/say_hello.nf | 21 ++++++++++++++++++++ hello_nf-core/modules/say_hello_upper.nf | 21 ++++++++++++++++++++ hello_nf-core/modules/timestamp_greeting.nf | 15 ++++++++++++++ hello_nf-core/modules/validate_name.nf | 22 +++++++++++++++++++++ hello_nf-core/workflows/greeting.nf | 18 +++++++++++++++++ hello_nf-core/workflows/transform.nf | 16 +++++++++++++++ mkdocs.yml | 10 ++++++++++ 11 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 hello_nf-core/main.nf create mode 100644 hello_nf-core/modules/reverse_text.nf create mode 100644 hello_nf-core/modules/say_hello.nf create mode 100644 hello_nf-core/modules/say_hello_upper.nf create mode 100644 hello_nf-core/modules/timestamp_greeting.nf create mode 100644 hello_nf-core/modules/validate_name.nf create mode 100644 hello_nf-core/workflows/greeting.nf create mode 100644 hello_nf-core/workflows/transform.nf diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 9960bcbbc..499687824 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -8,7 +8,7 @@ If you are using a different environment, you need to install the nf-core tools --- -## 0. Side Quest: Workflows or Workflows +## 0. Side Quest: Workflows of Workflows TODO: instructions -> pop out to run through the WoW side quest diff --git a/docs/index.md b/docs/index.md index f978d5afa..92b23d463 100644 --- a/docs/index.md +++ b/docs/index.md @@ -50,6 +50,16 @@ These are foundational, domain-agnostic courses intended for those who are compl [Start the Hello Nextflow training :material-arrow-right:](hello_nextflow/index.md){ .md-button .md-button--primary } +!!! exercise "Hello nf-core" + + !!! tip inline end "" + + :material-run-fast:{.nextflow-primary} Learn to run and develop nf-core pipelines. + + This is a course for newcomers who wish to learn run and develop [nf-core](https://nf-co.re/) compliant pipelines. The course covers the structure of nf-core pipelines in enough detail to enable developing simple but fully functional pipelines that follow the nf-core template and development best practices. + + [Start the Hello Nextflow nf-core :material-arrow-right:](hello_nf-core/index.md){ .md-button .md-button--primary } + !!! info "" **Coming soon:** "Nextflow Run" — Learn to run Nextflow pipelines (run only, no code development) diff --git a/hello_nf-core/main.nf b/hello_nf-core/main.nf new file mode 100644 index 000000000..37ebce7af --- /dev/null +++ b/hello_nf-core/main.nf @@ -0,0 +1,16 @@ +include { GREETING_WORKFLOW } from './workflows/greeting' +include { TRANSFORM_WORKFLOW } from './workflows/transform' + +workflow { + names = Channel.from('Alice', 'Bob', 'Charlie') + + // Run the greeting workflow + GREETING_WORKFLOW(names) + + // Run the transform workflow + TRANSFORM_WORKFLOW(GREETING_WORKFLOW.out.timestamped) + + // View results + TRANSFORM_WORKFLOW.out.upper.view { "Uppercase: $it" } + TRANSFORM_WORKFLOW.out.reversed.view { "Reversed: $it" } +} diff --git a/hello_nf-core/modules/reverse_text.nf b/hello_nf-core/modules/reverse_text.nf new file mode 100644 index 000000000..cb3a188b0 --- /dev/null +++ b/hello_nf-core/modules/reverse_text.nf @@ -0,0 +1,19 @@ +/* + * Use a text manipulation tool to reverse the text in a file + */ +process REVERSE_TEXT { + publishDir 'results', mode: 'copy' + + tag "reversing ${input_file}" + + input: + path input_file + + output: + path "REVERSED-${input_file}" + + script: + """ + cat '${input_file}' | rev > 'REVERSED-${input_file}' + """ +} diff --git a/hello_nf-core/modules/say_hello.nf b/hello_nf-core/modules/say_hello.nf new file mode 100644 index 000000000..b929ac457 --- /dev/null +++ b/hello_nf-core/modules/say_hello.nf @@ -0,0 +1,21 @@ +#!/usr/bin/env nextflow + +/* + * Use echo to print 'Hello World!' to a file + */ +process SAY_HELLO { + publishDir 'results', mode: 'copy' + + tag "greeting ${name}" + + input: + val name + + output: + path "${name}-output.txt" + + script: + """ + echo 'Hello, ${name}!' > "${name}-output.txt" + """ +} diff --git a/hello_nf-core/modules/say_hello_upper.nf b/hello_nf-core/modules/say_hello_upper.nf new file mode 100644 index 000000000..5ca93350e --- /dev/null +++ b/hello_nf-core/modules/say_hello_upper.nf @@ -0,0 +1,21 @@ +#!/usr/bin/env nextflow + +/* + * Use a text replacement tool to convert the greeting to uppercase + */ +process SAY_HELLO_UPPER { + publishDir 'results', mode: 'copy' + + tag "converting ${input_file}" + + input: + path input_file + + output: + path "UPPER-${input_file}" + + script: + """ + cat '${input_file}' | tr '[a-z]' '[A-Z]' > 'UPPER-${input_file}' + """ +} diff --git a/hello_nf-core/modules/timestamp_greeting.nf b/hello_nf-core/modules/timestamp_greeting.nf new file mode 100644 index 000000000..4a796c6af --- /dev/null +++ b/hello_nf-core/modules/timestamp_greeting.nf @@ -0,0 +1,15 @@ +process TIMESTAMP_GREETING { + tag "adding timestamp to greeting" + + input: + path greeting_file + + output: + path 'timestamped_*.txt' + + script: + def base_name = greeting_file.baseName + """ + echo "[\$(date '+%Y-%m-%d %H:%M:%S')] \$(cat $greeting_file)" > timestamped_${base_name}.txt + """ +} diff --git a/hello_nf-core/modules/validate_name.nf b/hello_nf-core/modules/validate_name.nf new file mode 100644 index 000000000..75de0c1c7 --- /dev/null +++ b/hello_nf-core/modules/validate_name.nf @@ -0,0 +1,22 @@ +process VALIDATE_NAME { + tag "validating ${name}" + + input: + val name + + output: + val name + + script: + """ + if [[ "${name}" =~ [^a-zA-Z] ]]; then + echo "Error: Name '${name}' contains invalid characters" >&2 + exit 1 + fi + variable=$name + if [[ "\${#variable}" -lt 2 ]]; then + echo "Error: Name '${name}' is too short" >&2 + exit 1 + fi + """ +} diff --git a/hello_nf-core/workflows/greeting.nf b/hello_nf-core/workflows/greeting.nf new file mode 100644 index 000000000..5eaffc62c --- /dev/null +++ b/hello_nf-core/workflows/greeting.nf @@ -0,0 +1,18 @@ +include { VALIDATE_NAME } from '../modules/validate_name' +include { SAY_HELLO } from '../modules/say_hello' +include { TIMESTAMP_GREETING } from '../modules/timestamp_greeting' + +workflow GREETING_WORKFLOW { + take: + names_ch // Input channel with names + + main: + // Chain processes: validate -> create greeting -> add timestamp + validated_ch = VALIDATE_NAME(names_ch) + greetings_ch = SAY_HELLO(validated_ch) + timestamped_ch = TIMESTAMP_GREETING(greetings_ch) + + emit: + greetings = greetings_ch // Original greetings + timestamped = timestamped_ch // Timestamped greetings +} diff --git a/hello_nf-core/workflows/transform.nf b/hello_nf-core/workflows/transform.nf new file mode 100644 index 000000000..819c87603 --- /dev/null +++ b/hello_nf-core/workflows/transform.nf @@ -0,0 +1,16 @@ +include { SAY_HELLO_UPPER } from '../modules/say_hello_upper' +include { REVERSE_TEXT } from '../modules/reverse_text' + +workflow TRANSFORM_WORKFLOW { + take: + input_ch // Input channel with messages + + main: + // Apply transformations in sequence + upper_ch = SAY_HELLO_UPPER(input_ch) + reversed_ch = REVERSE_TEXT(upper_ch) + + emit: + upper = upper_ch // Uppercase greetings + reversed = reversed_ch // Reversed uppercase greetings +} diff --git a/mkdocs.yml b/mkdocs.yml index cecb40a28..dbf57312e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,6 +21,14 @@ nav: - hello_nextflow/06_hello_config.md - hello_nextflow/survey.md - hello_nextflow/next_steps.md + - Hello nf-core: + - hello_nf-core/index.md + - hello_nf-core/00_orientation.md + - hello_nf-core/01_run_demo.md + - hello_nf-core/02_rewrite_hello.md + - hello_nf-core/03_add_module.md + - hello_nf-core/survey.md + - hello_nf-core/next_steps.md - Nextflow for Genomics: - nf4_science/genomics/index.md - nf4_science/genomics/00_orientation.md @@ -174,6 +182,7 @@ plugins: restart_increment_after: - envsetup/01_setup.md - hello_nextflow/00_orientation.md + - hello_nf-core/00_orientation.md - nf4_science/genomics/00_orientation.md - nf4_science/rnaseq/00_orientation.md - side_quests/orientation.md @@ -185,6 +194,7 @@ plugins: - help.md - envsetup/*.md - hello_nextflow/*.md + - hello_nf-core/*.md - nf4_science/genomics/*.md - nf4_science/rnaseq/*.md - side_quests/*.md From 4ec4bf8c4d6ac012bfea6fb76b42ca389bc9e117 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 08:48:13 -0400 Subject: [PATCH 05/43] small fix --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 92b23d463..46c8e2f97 100644 --- a/docs/index.md +++ b/docs/index.md @@ -58,7 +58,7 @@ These are foundational, domain-agnostic courses intended for those who are compl This is a course for newcomers who wish to learn run and develop [nf-core](https://nf-co.re/) compliant pipelines. The course covers the structure of nf-core pipelines in enough detail to enable developing simple but fully functional pipelines that follow the nf-core template and development best practices. - [Start the Hello Nextflow nf-core :material-arrow-right:](hello_nf-core/index.md){ .md-button .md-button--primary } + [Start the Hello nf-core training :material-arrow-right:](hello_nf-core/index.md){ .md-button .md-button--primary } !!! info "" From 551316837a5d747df3ef3492c525883a9c313998 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 08:57:47 -0400 Subject: [PATCH 06/43] tiny fix --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 46c8e2f97..4e2688684 100644 --- a/docs/index.md +++ b/docs/index.md @@ -54,7 +54,7 @@ These are foundational, domain-agnostic courses intended for those who are compl !!! tip inline end "" - :material-run-fast:{.nextflow-primary} Learn to run and develop nf-core pipelines. + :material-run-fast:{.nextflow-primary} Learn to develop nf-core compliant pipelines. This is a course for newcomers who wish to learn run and develop [nf-core](https://nf-co.re/) compliant pipelines. The course covers the structure of nf-core pipelines in enough detail to enable developing simple but fully functional pipelines that follow the nf-core template and development best practices. From 11e2302e12b3146b448aecd22c33adc55f5a23d2 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 09:25:20 -0400 Subject: [PATCH 07/43] Add WoW Side Quest as a prelude --- docs/hello_nf-core/02_rewrite_hello.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 499687824..f4e7c8e9f 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1,6 +1,6 @@ # Part 2: Rewrite Hello for nf-core -In this second part of the Hello nf-core training course, we show you how to create an nf-core-compliant pipeline version of the final pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. +In this second part of the Hello nf-core training course, we show you how to create an nf-core-compliant pipeline version of the pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. !!! note nf-core tools are pre-installed for you in our training environment. @@ -8,17 +8,15 @@ If you are using a different environment, you need to install the nf-core tools --- -## 0. Side Quest: Workflows of Workflows +## 0. Side Quest: Workflow of Workflows -TODO: instructions -> pop out to run through the WoW side quest +Before you get started with nf-core proper, you need to learn how to use an optional feature of Nextflow workflows that is not covered in the [Hello Nextflow](../hello_nextflow/index.md) beginners' course: the encapsulation of a workflow within another workflow. -### Takeaway +This feature is covered by the [Workflow of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows) (a.k.a. WoW) side quest, a standalone mini-course that you can run through in the same training environment. -You now know how to [...]. +Go ahead and do that now. -### What's next? - -Find out [...]. +Once you have completed the side quest (or if you are confident you already know how to use this feature), you can move on to the next section. --- From cb0d5b3c064c7072cb1a66d0336966002b5b4279 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 11:55:30 -0400 Subject: [PATCH 08/43] Replace WoW case by Hello Nextflow original --- docs/hello_nf-core/02_rewrite_hello.md | 282 ++++++++++++++++-- .../modules/collectGreetings.nf | 21 ++ .../6-hello-config/modules/convertToUpper.nf | 7 +- .../6-hello-config/modules/sayHello.nf | 11 +- hello-nf-core/hello-original/hello.nf | 37 +++ hello-nf-core/hello-original/modules/cowpy.nf | 22 ++ hello-nf-core/hello-original/nextflow.config | 1 + hello_nf-core/main.nf | 16 - hello_nf-core/modules/reverse_text.nf | 19 -- hello_nf-core/modules/timestamp_greeting.nf | 15 - hello_nf-core/modules/validate_name.nf | 22 -- hello_nf-core/workflows/greeting.nf | 18 -- hello_nf-core/workflows/transform.nf | 16 - 13 files changed, 345 insertions(+), 142 deletions(-) create mode 100644 hello-nextflow/solutions/6-hello-config/modules/collectGreetings.nf rename hello_nf-core/modules/say_hello_upper.nf => hello-nextflow/solutions/6-hello-config/modules/convertToUpper.nf (66%) rename hello_nf-core/modules/say_hello.nf => hello-nextflow/solutions/6-hello-config/modules/sayHello.nf (55%) create mode 100644 hello-nf-core/hello-original/hello.nf create mode 100644 hello-nf-core/hello-original/modules/cowpy.nf create mode 100644 hello-nf-core/hello-original/nextflow.config delete mode 100644 hello_nf-core/main.nf delete mode 100644 hello_nf-core/modules/reverse_text.nf delete mode 100644 hello_nf-core/modules/timestamp_greeting.nf delete mode 100644 hello_nf-core/modules/validate_name.nf delete mode 100644 hello_nf-core/workflows/greeting.nf delete mode 100644 hello_nf-core/workflows/transform.nf diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index f4e7c8e9f..9a186d8f5 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -2,53 +2,268 @@ In this second part of the Hello nf-core training course, we show you how to create an nf-core-compliant pipeline version of the pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. +You'll have noticed in the first section of the training that nf-core pipelines follow a fairly elaborate structure with a lot of accessory files. +Creating all that from scratch would be very tedious, so the nf-core community has developed tooling to do it from a template instead, to bootstrap the process. + +We are going to show you how to use this tooling to create a pipeline scaffold, then adapt existing 'regular' pipeline code onto the nf-core scaffold. + !!! note -nf-core tools are pre-installed for you in our training environment. -If you are using a different environment, you need to install the nf-core tools package as described here: https://nf-co.re/docs/nf-core-tools/installation. +The nf-core-tools package is pre-installed for you in our training environment. +If you are using a different environment, you need to check whether the package is installed (run `nf-core --help` in your terminal) and if not, install it as described here: https://nf-co.re/docs/nf-core-tools/installation. --- -## 0. Side Quest: Workflow of Workflows +## 1. Create a new pipeline project -Before you get started with nf-core proper, you need to learn how to use an optional feature of Nextflow workflows that is not covered in the [Hello Nextflow](../hello_nextflow/index.md) beginners' course: the encapsulation of a workflow within another workflow. +First, we create the scaffold for the new pipeline. -This feature is covered by the [Workflow of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows) (a.k.a. WoW) side quest, a standalone mini-course that you can run through in the same training environment. +!!! note +Make sure you are in the `hello_nf-core` directory in your terminal. + +### 1.1. Run the template-based pipeline creation tool + +Let's start by creating a new pipeline with the `nf-core pipelines create` command. +This will create a new pipeline scaffold using the nf-core base template, customized with a pipeline name, description, and author. + +```bash +nf-core pipelines create +``` + +Running this command will open a Text User Interface (TUI) for pipeline creation: + +
+ +
+ +This TUI will ask you to provide basic information about your pipeline and will provide you with a choice of features to include or exclude in your pipeline scaffold. + +1. Select **Let's go!** on the welcome screen +2. Select **Custom** on the `Choose pipeline type` screen +3. Enter your pipeline details, replacing < YOUR NAME > with your own name, then select **Next** + +- **GitHub organisation:** myorg +- **Workflow name:** hello +- **A short description of your pipeline:** nf-core compliant version of Hello Nextflow +- **Name of the main author / authors:** < YOUR NAME > + +4. On the Template features screen, set "Toggle all features" to **off**, then selectively **enable** the following: + +- `Add configuration files` +- `Use nf-core components` +- `Use nf-schema` +- `Add documentation` +- `Add testing profiles` + +5. Select **Finish** on the Final details screen +6. Wait for the pipeline to be created, then select **Continue** +7. Select **Finish without creating a repo** on the Create GitHub repository screen +8. Select **Close** on the HowTo create a GitHub repository page + +Once the TUI closes, you should see the following console output. + +```console title="Output" + ,--./,-. + ___ __ __ __ ___ /,-._.--~\ + |\ | |__ __ / ` / \ |__) |__ } { + | \| | \__, \__/ | \ |___ \`-._,-`-, + `._,._,' + + nf-core/tools version 3.2.1 - https://nf-co.re + + +INFO Launching interactive nf-core pipeline creation tool. +``` + +There is no explicit confirmation in the console output that the pipeline creation worked, but you should see a new directory called `myorg-hello`. + +View the contents of the new directory to see how much work you saved yourself by using the template. + +```bash +tree myorg-hello +``` + +```console title="Output" +myorg-hello +├── assets +│ ├── samplesheet.csv +│ └── schema_input.json +├── conf +│ ├── base.config +│ ├── modules.config +│ ├── test.config +│ └── test_full.config +├── docs +│ ├── output.md +│ ├── README.md +│ └── usage.md +├── main.nf +├── modules.json +├── nextflow.config +├── nextflow_schema.json +├── README.md +├── subworkflows +│ ├── local +│ │ └── utils_nfcore_hello_pipeline +│ │ └── main.nf +│ └── nf-core +│ ├── utils_nextflow_pipeline +│ │ ├── main.nf +│ │ ├── meta.yml +│ │ └── tests +│ │ ├── main.function.nf.test +│ │ ├── main.function.nf.test.snap +│ │ ├── main.workflow.nf.test +│ │ ├── nextflow.config +│ │ └── tags.yml +│ ├── utils_nfcore_pipeline +│ │ ├── main.nf +│ │ ├── meta.yml +│ │ └── tests +│ │ ├── main.function.nf.test +│ │ ├── main.function.nf.test.snap +│ │ ├── main.workflow.nf.test +│ │ ├── main.workflow.nf.test.snap +│ │ ├── nextflow.config +│ │ └── tags.yml +│ └── utils_nfschema_plugin +│ ├── main.nf +│ ├── meta.yml +│ └── tests +│ ├── main.nf.test +│ ├── nextflow.config +│ └── nextflow_schema.json +└── workflows + └── hello.nf + +14 directories, 36 files +``` + +That's a lot of files! + + -Go ahead and do that now. +!!! note +One important difference compared to the `nf-core/demo` pipeline we examined in the first part of this training is that there is no `modules` directory. +This is because we didn't include any of the default nf-core modules. -Once you have completed the side quest (or if you are confident you already know how to use this feature), you can move on to the next section. +### 1.2. Test that the scaffold is functional ---- +Believe it or not, even though you haven't yet added any modules to make it do real work, the pipeline scaffold can actually be run using the test profile, the same way we ran the `nf-core/demo` pipeline. -## 1. Create a new pipeline project +```bash +nextflow run myorg-hello -profile docker,test --outdir results +``` -TODO: instructions +```console title="Output" +Nextflow 25.04.0 is available - Please consider updating your version to it -### Takeaway + N E X T F L O W ~ version 24.10.4 -You now know how to [...]. +Launching `myorg-hello/main.nf` [naughty_babbage] DSL2 - revision: c0376c97f3 -### What's next? +Input/output options + input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv + outdir : test_hello -Find out [...]. +Institutional config options + config_profile_name : Test profile + config_profile_description: Minimal test dataset to check pipeline function ---- +Generic options + trace_report_suffix : 2025-05-12_14-46-59 -## 2. Create the modules +Core Nextflow options + runName : naughty_babbage + launchDir : /workspaces/training/hello-nf-core + workDir : /workspaces/training/hello-nf-core/work + projectDir : /workspaces/training/hello-nf-core/myorg-hello + userName : root + profile : docker,test + configFiles : -TODO: instructions +!! Only displaying parameters that differ from the pipeline defaults !! +------------------------------------------------------ +-[myorg/hello] Pipeline completed successfully- +``` + +This shows you that all the basic wiring is in place. + +### 1.3. Examine the placeholder workflow + +If you look inside the `main.nf` file, you'll see it imports a workflow called `HELLO` from `workflows/hello`. +This is a placeholder workflow for our workflow of interest, with some nf-core functionality already in place. + +```groovy title="conf/test.config" linenums="1" hl_lines="17,19,35" +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +include { paramsSummaryMap } from 'plugin/nf-schema' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow HELLO { + + take: + ch_samplesheet // channel: samplesheet read in from --input + main: + + ch_versions = Channel.empty() + + // + // Collate and save software versions + // + softwareVersionsToYAML(ch_versions) + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'hello_software_' + 'versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } + + + emit: + versions = ch_versions // channel: [ path(versions.yml) ] + +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +``` + +Compared to the original Hello Nextflow workflow, you'll notice there's something new here: the keywords `take`, `main` and `emit`. +These are optional features of Nextflow that make the workflow **composable**, meaning that it can be called from within another workflow. + +We are going to need to plug the relevant logic from our workflow of interest into that structure. ### Takeaway -You know how to [...]. +You now know how to create a pipeline scaffold using nf-core tools. ### What's next? -Learn how to [...]. +Learn how to make a simple workflow composable. --- -## 3. Plug the modules into the workflow +## 2. Make the original Hello Nextflow workflow composable + +TODO: overview + +### 2.1. Add `take`, `main` and `emit` statements + +TODO: instructions + +### 2.2. Test the workflow TODO: instructions @@ -56,13 +271,28 @@ TODO: instructions You know how to [...]. +!!!note +If you're interested in digging deeper into options for composing workflows of workflows, check out the [Workflow of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows) (a.k.a. WoW) side quest. + ### What's next? -[...]. +Learn how to [...]. --- -## 4. Set up the inputs +## 3. Fit the updated workflow logic into the placeholder workflow + +TODO: overview + +### 3.1. Plug in the main workflow logic + +TODO: instructions + +### 3.2. Copy over the modules + +TODO: instructions + +### 3.3. Set up the module imports TODO: instructions @@ -72,11 +302,11 @@ You know how to [...]. ### What's next? -[...]. +Learn how to [...]. --- -## 5. Set up the parameters +## 4. Set up inputs, parameters and validation TODO: instructions @@ -90,7 +320,7 @@ You know how to [...]. --- -## 6. Output tool versions +## 5. Wire up tool version output TODO: instructions @@ -104,7 +334,7 @@ You know how to [...]. --- -## 7. Run the pipeline +## 6. Test the pipeline TODO: instructions diff --git a/hello-nextflow/solutions/6-hello-config/modules/collectGreetings.nf b/hello-nextflow/solutions/6-hello-config/modules/collectGreetings.nf new file mode 100644 index 000000000..849bba4b6 --- /dev/null +++ b/hello-nextflow/solutions/6-hello-config/modules/collectGreetings.nf @@ -0,0 +1,21 @@ +/* + * Collect uppercase greetings into a single output file + */ +process collectGreetings { + + publishDir 'results', mode: 'copy' + + input: + path input_files + val batch_name + + output: + path "COLLECTED-${batch_name}-output.txt" , emit: outfile + val count_greetings , emit: count + + script: + count_greetings = input_files.size() + """ + cat ${input_files} > 'COLLECTED-${batch_name}-output.txt' + """ +} diff --git a/hello_nf-core/modules/say_hello_upper.nf b/hello-nextflow/solutions/6-hello-config/modules/convertToUpper.nf similarity index 66% rename from hello_nf-core/modules/say_hello_upper.nf rename to hello-nextflow/solutions/6-hello-config/modules/convertToUpper.nf index 5ca93350e..b2689e8e9 100644 --- a/hello_nf-core/modules/say_hello_upper.nf +++ b/hello-nextflow/solutions/6-hello-config/modules/convertToUpper.nf @@ -3,10 +3,9 @@ /* * Use a text replacement tool to convert the greeting to uppercase */ -process SAY_HELLO_UPPER { - publishDir 'results', mode: 'copy' +process convertToUpper { - tag "converting ${input_file}" + publishDir 'results', mode: 'copy' input: path input_file @@ -16,6 +15,6 @@ process SAY_HELLO_UPPER { script: """ - cat '${input_file}' | tr '[a-z]' '[A-Z]' > 'UPPER-${input_file}' + cat '$input_file' | tr '[a-z]' '[A-Z]' > 'UPPER-${input_file}' """ } diff --git a/hello_nf-core/modules/say_hello.nf b/hello-nextflow/solutions/6-hello-config/modules/sayHello.nf similarity index 55% rename from hello_nf-core/modules/say_hello.nf rename to hello-nextflow/solutions/6-hello-config/modules/sayHello.nf index b929ac457..6005ad54c 100644 --- a/hello_nf-core/modules/say_hello.nf +++ b/hello-nextflow/solutions/6-hello-config/modules/sayHello.nf @@ -3,19 +3,18 @@ /* * Use echo to print 'Hello World!' to a file */ -process SAY_HELLO { - publishDir 'results', mode: 'copy' +process sayHello { - tag "greeting ${name}" + publishDir 'results', mode: 'copy' input: - val name + val greeting output: - path "${name}-output.txt" + path "${greeting}-output.txt" script: """ - echo 'Hello, ${name}!' > "${name}-output.txt" + echo '$greeting' > '$greeting-output.txt' """ } diff --git a/hello-nf-core/hello-original/hello.nf b/hello-nf-core/hello-original/hello.nf new file mode 100644 index 000000000..f187831d1 --- /dev/null +++ b/hello-nf-core/hello-original/hello.nf @@ -0,0 +1,37 @@ +#!/usr/bin/env nextflow + +/* + * Pipeline parameters + */ +params.greeting = 'greetings.csv' +params.batch = 'test-batch' +params.character = 'turkey' + +// Include modules +include { sayHello } from './modules/sayHello.nf' +include { convertToUpper } from './modules/convertToUpper.nf' +include { collectGreetings } from './modules/collectGreetings.nf' +include { cowpy } from './modules/cowpy.nf' + +workflow { + + // create a channel for inputs from a CSV file + greeting_ch = Channel.fromPath(params.greeting) + .splitCsv() + .map { line -> line[0] } + + // emit a greeting + sayHello(greeting_ch) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // collect all the greetings into one file + collectGreetings(convertToUpper.out.collect(), params.batch) + + // emit a message about the size of the batch + collectGreetings.out.count.view { "There were $it greetings in this batch" } + + // generate ASCII art of the greetings with cowpy + cowpy(collectGreetings.out.outfile, params.character) +} diff --git a/hello-nf-core/hello-original/modules/cowpy.nf b/hello-nf-core/hello-original/modules/cowpy.nf new file mode 100644 index 000000000..2bc7ed612 --- /dev/null +++ b/hello-nf-core/hello-original/modules/cowpy.nf @@ -0,0 +1,22 @@ +#!/usr/bin/env nextflow + +// Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) +process cowpy { + + publishDir 'results', mode: 'copy' + + container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' + conda 'conda-forge::cowpy==1.1.5' + + input: + path input_file + val character + + output: + path "cowpy-${input_file}" + + script: + """ + cat $input_file | cowpy -c "$character" > cowpy-${input_file} + """ +} diff --git a/hello-nf-core/hello-original/nextflow.config b/hello-nf-core/hello-original/nextflow.config new file mode 100644 index 000000000..0a5fd46b9 --- /dev/null +++ b/hello-nf-core/hello-original/nextflow.config @@ -0,0 +1 @@ +docker.enabled = false diff --git a/hello_nf-core/main.nf b/hello_nf-core/main.nf deleted file mode 100644 index 37ebce7af..000000000 --- a/hello_nf-core/main.nf +++ /dev/null @@ -1,16 +0,0 @@ -include { GREETING_WORKFLOW } from './workflows/greeting' -include { TRANSFORM_WORKFLOW } from './workflows/transform' - -workflow { - names = Channel.from('Alice', 'Bob', 'Charlie') - - // Run the greeting workflow - GREETING_WORKFLOW(names) - - // Run the transform workflow - TRANSFORM_WORKFLOW(GREETING_WORKFLOW.out.timestamped) - - // View results - TRANSFORM_WORKFLOW.out.upper.view { "Uppercase: $it" } - TRANSFORM_WORKFLOW.out.reversed.view { "Reversed: $it" } -} diff --git a/hello_nf-core/modules/reverse_text.nf b/hello_nf-core/modules/reverse_text.nf deleted file mode 100644 index cb3a188b0..000000000 --- a/hello_nf-core/modules/reverse_text.nf +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Use a text manipulation tool to reverse the text in a file - */ -process REVERSE_TEXT { - publishDir 'results', mode: 'copy' - - tag "reversing ${input_file}" - - input: - path input_file - - output: - path "REVERSED-${input_file}" - - script: - """ - cat '${input_file}' | rev > 'REVERSED-${input_file}' - """ -} diff --git a/hello_nf-core/modules/timestamp_greeting.nf b/hello_nf-core/modules/timestamp_greeting.nf deleted file mode 100644 index 4a796c6af..000000000 --- a/hello_nf-core/modules/timestamp_greeting.nf +++ /dev/null @@ -1,15 +0,0 @@ -process TIMESTAMP_GREETING { - tag "adding timestamp to greeting" - - input: - path greeting_file - - output: - path 'timestamped_*.txt' - - script: - def base_name = greeting_file.baseName - """ - echo "[\$(date '+%Y-%m-%d %H:%M:%S')] \$(cat $greeting_file)" > timestamped_${base_name}.txt - """ -} diff --git a/hello_nf-core/modules/validate_name.nf b/hello_nf-core/modules/validate_name.nf deleted file mode 100644 index 75de0c1c7..000000000 --- a/hello_nf-core/modules/validate_name.nf +++ /dev/null @@ -1,22 +0,0 @@ -process VALIDATE_NAME { - tag "validating ${name}" - - input: - val name - - output: - val name - - script: - """ - if [[ "${name}" =~ [^a-zA-Z] ]]; then - echo "Error: Name '${name}' contains invalid characters" >&2 - exit 1 - fi - variable=$name - if [[ "\${#variable}" -lt 2 ]]; then - echo "Error: Name '${name}' is too short" >&2 - exit 1 - fi - """ -} diff --git a/hello_nf-core/workflows/greeting.nf b/hello_nf-core/workflows/greeting.nf deleted file mode 100644 index 5eaffc62c..000000000 --- a/hello_nf-core/workflows/greeting.nf +++ /dev/null @@ -1,18 +0,0 @@ -include { VALIDATE_NAME } from '../modules/validate_name' -include { SAY_HELLO } from '../modules/say_hello' -include { TIMESTAMP_GREETING } from '../modules/timestamp_greeting' - -workflow GREETING_WORKFLOW { - take: - names_ch // Input channel with names - - main: - // Chain processes: validate -> create greeting -> add timestamp - validated_ch = VALIDATE_NAME(names_ch) - greetings_ch = SAY_HELLO(validated_ch) - timestamped_ch = TIMESTAMP_GREETING(greetings_ch) - - emit: - greetings = greetings_ch // Original greetings - timestamped = timestamped_ch // Timestamped greetings -} diff --git a/hello_nf-core/workflows/transform.nf b/hello_nf-core/workflows/transform.nf deleted file mode 100644 index 819c87603..000000000 --- a/hello_nf-core/workflows/transform.nf +++ /dev/null @@ -1,16 +0,0 @@ -include { SAY_HELLO_UPPER } from '../modules/say_hello_upper' -include { REVERSE_TEXT } from '../modules/reverse_text' - -workflow TRANSFORM_WORKFLOW { - take: - input_ch // Input channel with messages - - main: - // Apply transformations in sequence - upper_ch = SAY_HELLO_UPPER(input_ch) - reversed_ch = REVERSE_TEXT(upper_ch) - - emit: - upper = upper_ch // Uppercase greetings - reversed = reversed_ch // Reversed uppercase greetings -} From c40f12090334ede642a1e3d4f4e7ef450ada78ed Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 16:22:32 -0400 Subject: [PATCH 09/43] fix modules location --- .../hello-original}/modules/collectGreetings.nf | 0 .../hello-original}/modules/convertToUpper.nf | 0 .../hello-original}/modules/sayHello.nf | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {hello-nextflow/solutions/6-hello-config => hello-nf-core/hello-original}/modules/collectGreetings.nf (100%) rename {hello-nextflow/solutions/6-hello-config => hello-nf-core/hello-original}/modules/convertToUpper.nf (100%) rename {hello-nextflow/solutions/6-hello-config => hello-nf-core/hello-original}/modules/sayHello.nf (100%) diff --git a/hello-nextflow/solutions/6-hello-config/modules/collectGreetings.nf b/hello-nf-core/hello-original/modules/collectGreetings.nf similarity index 100% rename from hello-nextflow/solutions/6-hello-config/modules/collectGreetings.nf rename to hello-nf-core/hello-original/modules/collectGreetings.nf diff --git a/hello-nextflow/solutions/6-hello-config/modules/convertToUpper.nf b/hello-nf-core/hello-original/modules/convertToUpper.nf similarity index 100% rename from hello-nextflow/solutions/6-hello-config/modules/convertToUpper.nf rename to hello-nf-core/hello-original/modules/convertToUpper.nf diff --git a/hello-nextflow/solutions/6-hello-config/modules/sayHello.nf b/hello-nf-core/hello-original/modules/sayHello.nf similarity index 100% rename from hello-nextflow/solutions/6-hello-config/modules/sayHello.nf rename to hello-nf-core/hello-original/modules/sayHello.nf From ef8c6e67a28dea1078d1f4e2f2a8d30a277b89e0 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 16:31:24 -0400 Subject: [PATCH 10/43] add greetings.csv input for nf-core --- hello-nf-core/hello-original/greetings.csv | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 hello-nf-core/hello-original/greetings.csv diff --git a/hello-nf-core/hello-original/greetings.csv b/hello-nf-core/hello-original/greetings.csv new file mode 100644 index 000000000..c5889e19a --- /dev/null +++ b/hello-nf-core/hello-original/greetings.csv @@ -0,0 +1,3 @@ +Hello +Bonjour +Holà From 844dc0472a582f4d7f687e743cfcf4dfd5897035 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 16:41:10 -0400 Subject: [PATCH 11/43] change dir name for starting case --- hello-nf-core/{hello-original => original-hello}/greetings.csv | 0 hello-nf-core/{hello-original => original-hello}/hello.nf | 0 .../modules/collectGreetings.nf | 0 .../{hello-original => original-hello}/modules/convertToUpper.nf | 0 hello-nf-core/{hello-original => original-hello}/modules/cowpy.nf | 0 .../{hello-original => original-hello}/modules/sayHello.nf | 0 hello-nf-core/{hello-original => original-hello}/nextflow.config | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename hello-nf-core/{hello-original => original-hello}/greetings.csv (100%) rename hello-nf-core/{hello-original => original-hello}/hello.nf (100%) rename hello-nf-core/{hello-original => original-hello}/modules/collectGreetings.nf (100%) rename hello-nf-core/{hello-original => original-hello}/modules/convertToUpper.nf (100%) rename hello-nf-core/{hello-original => original-hello}/modules/cowpy.nf (100%) rename hello-nf-core/{hello-original => original-hello}/modules/sayHello.nf (100%) rename hello-nf-core/{hello-original => original-hello}/nextflow.config (100%) diff --git a/hello-nf-core/hello-original/greetings.csv b/hello-nf-core/original-hello/greetings.csv similarity index 100% rename from hello-nf-core/hello-original/greetings.csv rename to hello-nf-core/original-hello/greetings.csv diff --git a/hello-nf-core/hello-original/hello.nf b/hello-nf-core/original-hello/hello.nf similarity index 100% rename from hello-nf-core/hello-original/hello.nf rename to hello-nf-core/original-hello/hello.nf diff --git a/hello-nf-core/hello-original/modules/collectGreetings.nf b/hello-nf-core/original-hello/modules/collectGreetings.nf similarity index 100% rename from hello-nf-core/hello-original/modules/collectGreetings.nf rename to hello-nf-core/original-hello/modules/collectGreetings.nf diff --git a/hello-nf-core/hello-original/modules/convertToUpper.nf b/hello-nf-core/original-hello/modules/convertToUpper.nf similarity index 100% rename from hello-nf-core/hello-original/modules/convertToUpper.nf rename to hello-nf-core/original-hello/modules/convertToUpper.nf diff --git a/hello-nf-core/hello-original/modules/cowpy.nf b/hello-nf-core/original-hello/modules/cowpy.nf similarity index 100% rename from hello-nf-core/hello-original/modules/cowpy.nf rename to hello-nf-core/original-hello/modules/cowpy.nf diff --git a/hello-nf-core/hello-original/modules/sayHello.nf b/hello-nf-core/original-hello/modules/sayHello.nf similarity index 100% rename from hello-nf-core/hello-original/modules/sayHello.nf rename to hello-nf-core/original-hello/modules/sayHello.nf diff --git a/hello-nf-core/hello-original/nextflow.config b/hello-nf-core/original-hello/nextflow.config similarity index 100% rename from hello-nf-core/hello-original/nextflow.config rename to hello-nf-core/original-hello/nextflow.config From 4dd4a30930dc66f30bc741b74f5f08f1f5a9868a Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 17:16:03 -0400 Subject: [PATCH 12/43] original hello complete --- docs/hello_nf-core/02_rewrite_hello.md | 48 +++++++++++++++++-- .../{original-hello => }/greetings.csv | 0 hello-nf-core/original-hello/nextflow.config | 2 +- 3 files changed, 44 insertions(+), 6 deletions(-) rename hello-nf-core/{original-hello => }/greetings.csv (100%) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 9a186d8f5..c35d74550 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -155,8 +155,6 @@ nextflow run myorg-hello -profile docker,test --outdir results ``` ```console title="Output" -Nextflow 25.04.0 is available - Please consider updating your version to it - N E X T F L O W ~ version 24.10.4 Launching `myorg-hello/main.nf` [naughty_babbage] DSL2 - revision: c0376c97f3 @@ -193,7 +191,7 @@ This shows you that all the basic wiring is in place. If you look inside the `main.nf` file, you'll see it imports a workflow called `HELLO` from `workflows/hello`. This is a placeholder workflow for our workflow of interest, with some nf-core functionality already in place. -```groovy title="conf/test.config" linenums="1" hl_lines="17,19,35" +```groovy title="conf/test.config" linenums="1" hl_lines="15,17,19,35" /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS @@ -240,7 +238,13 @@ workflow HELLO { */ ``` -Compared to the original Hello Nextflow workflow, you'll notice there's something new here: the keywords `take`, `main` and `emit`. +Compared to a basic Nextflow workflow like the one developed in Hello Nextflow, you'll notice a few things that are new here: + +- The workflow block has a name +- Workflow inputs are declared using the `take:` keyword +- Workflow content is placed inside a `main:` block +- Outputs are declared using the `emit:` keyword + These are optional features of Nextflow that make the workflow **composable**, meaning that it can be called from within another workflow. We are going to need to plug the relevant logic from our workflow of interest into that structure. @@ -257,7 +261,41 @@ Learn how to make a simple workflow composable. ## 2. Make the original Hello Nextflow workflow composable -TODO: overview +We provide you with a clean, fully functional copy of the completed Hello Nextflow workflow in the directory `original-hello` along with its modules and the default CSV file it expects to use as input. + +```bash +tree original-hello/ +``` + +```console title="Output" +original-hello/ +├── hello.nf +├── modules +│ ├── collectGreetings.nf +│ ├── convertToUpper.nf +│ ├── cowpy.nf +│ └── sayHello.nf +└── nextflow.config +``` + +Feel free to run it to satisfy yourself that it works: + +```bash +nextflow run original-hello/hello.nf +``` + +```console title="Output" + N E X T F L O W ~ version 24.10.4 + +Launching `hello-original/hello.nf` [goofy_babbage] DSL2 - revision: e9e72441e9 + +executor > local (8) +[a4/081cec] sayHello (1) | 3 of 3 ✔ +[e7/7e9058] convertToUpper (3) | 3 of 3 ✔ +[0c/17263b] collectGreetings | 1 of 1 ✔ +[94/542280] cowpy | 1 of 1 ✔ +There were 3 greetings in this batch +``` ### 2.1. Add `take`, `main` and `emit` statements diff --git a/hello-nf-core/original-hello/greetings.csv b/hello-nf-core/greetings.csv similarity index 100% rename from hello-nf-core/original-hello/greetings.csv rename to hello-nf-core/greetings.csv diff --git a/hello-nf-core/original-hello/nextflow.config b/hello-nf-core/original-hello/nextflow.config index 0a5fd46b9..d3af3eaae 100644 --- a/hello-nf-core/original-hello/nextflow.config +++ b/hello-nf-core/original-hello/nextflow.config @@ -1 +1 @@ -docker.enabled = false +docker.enabled = true From 6f7d94a2c2b0e15dc2c018f1cd9135a08fd02ca3 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Mon, 12 May 2025 23:41:27 -0400 Subject: [PATCH 13/43] composable workflow is composable --- docs/hello_nf-core/02_rewrite_hello.md | 275 +++++++++++++++++- .../solutions/composable-hello/hello.nf | 40 +++ .../solutions/composable-hello/main.nf | 20 ++ 3 files changed, 327 insertions(+), 8 deletions(-) create mode 100644 hello-nf-core/solutions/composable-hello/hello.nf create mode 100644 hello-nf-core/solutions/composable-hello/main.nf diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index c35d74550..396d0e7e1 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -191,7 +191,7 @@ This shows you that all the basic wiring is in place. If you look inside the `main.nf` file, you'll see it imports a workflow called `HELLO` from `workflows/hello`. This is a placeholder workflow for our workflow of interest, with some nf-core functionality already in place. -```groovy title="conf/test.config" linenums="1" hl_lines="15,17,19,35" +```groovy title="conf/test.config" linenums="1" hl_lines="15 17 19 35" /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS @@ -248,6 +248,7 @@ Compared to a basic Nextflow workflow like the one developed in Hello Nextflow, These are optional features of Nextflow that make the workflow **composable**, meaning that it can be called from within another workflow. We are going to need to plug the relevant logic from our workflow of interest into that structure. +The first step for that is to make our original workflow composable. ### Takeaway @@ -287,7 +288,7 @@ nextflow run original-hello/hello.nf ```console title="Output" N E X T F L O W ~ version 24.10.4 -Launching `hello-original/hello.nf` [goofy_babbage] DSL2 - revision: e9e72441e9 +Launching `original-hello/hello.nf` [goofy_babbage] DSL2 - revision: e9e72441e9 executor > local (8) [a4/081cec] sayHello (1) | 3 of 3 ✔ @@ -297,24 +298,282 @@ executor > local (8) There were 3 greetings in this batch ``` -### 2.1. Add `take`, `main` and `emit` statements +For reference, here is the complete workflow code (not counting the processes, which are in modules): -TODO: instructions +```groovy title="original-hello/hello.nf" linenums="1" +#!/usr/bin/env nextflow -### 2.2. Test the workflow +/* +* Pipeline parameters +*/ +params.greeting = 'greetings.csv' +params.batch = 'test-batch' +params.character = 'turkey' -TODO: instructions +// Include modules +include { sayHello } from './modules/sayHello.nf' +include { convertToUpper } from './modules/convertToUpper.nf' +include { collectGreetings } from './modules/collectGreetings.nf' +include { cowpy } from './modules/cowpy.nf' + +workflow { + + // create a channel for inputs from a CSV file + greeting_ch = Channel.fromPath(params.greeting) + .splitCsv() + .map { line -> line[0] } + + // emit a greeting + sayHello(greeting_ch) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // collect all the greetings into one file + collectGreetings(convertToUpper.out.collect(), params.batch) + + // emit a message about the size of the batch + collectGreetings.out.count.view { "There were $it greetings in this batch" } + + // generate ASCII art of the greetings with cowpy + cowpy(collectGreetings.out.outfile, params.character) +} +``` + +Let's walk through the necessary changes one by one. + +### 2.1. Name the workflow + +First, let's give the workflow a name so we can refer to it from a parent workflow. + +=== "After" + + ```groovy title="original-hello/hello.nf" linenums="16" + workflow HELLO { + ``` + +=== "Before" + + ```groovy title="original-hello/hello.nf" linenums="16" + workflow { + ``` + +The same conventions apply to workflow names as to module names. + +### 2.2. Replace channel construction with `take` statement + +Now, replace the channel construction with a simple `take` statement declaring expected inputs. + +=== "After" + + ```groovy title="original-hello/hello.nf" linenums="18" + take: + // channel of greetings + greeting_ch + ``` + +=== "Before" + + ```groovy title="original-hello/hello.nf" linenums="18" + // create a channel for inputs from a CSV file + greeting_ch = Channel.fromPath(params.greeting) + .splitCsv() + .map { line -> line[0] } + ``` + +This leaves the details of how the inputs are provided up to the parent workflow. + +As part of this change, you should also delete the line `params.greeting = 'greetings.csv'` from the block of parameter definitions (line 6). +That will also be left to the parent workflow to declare. + +### 2.3. Preface workflow operations with `main` statement + +Next, add a `main` statement before the rest of the operations called in the body of the workflow. + +=== "After" + + ```groovy title="original-hello/hello.nf" linenums="21" hl_lines="22" + main: + // emit a greeting + sayHello(greeting_ch) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // collect all the greetings into one file + collectGreetings(convertToUpper.out.collect(), params.batch) + + // emit a message about the size of the batch + collectGreetings.out.count.view { "There were $it greetings in this batch" } + + // generate ASCII art of the greetings with cowpy + cowpy(collectGreetings.out.outfile, params.character) + ``` + +=== "Before" + + ```groovy title="original-hello/hello.nf" linenums="21" + // emit a greeting + sayHello(greeting_ch) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // collect all the greetings into one file + collectGreetings(convertToUpper.out.collect(), params.batch) + + // emit a message about the size of the batch + collectGreetings.out.count.view { "There were $it greetings in this batch" } + + // generate ASCII art of the greetings with cowpy + cowpy(collectGreetings.out.outfile, params.character) + ``` + +This basically says 'this is what this workflow _does_'. + +### 2.4. Add `emit` statement + +Finally, add an `emit` statement declaring what are the final outputs of the workflow. + +```groovy title="original-hello/hello.nf" linenums="37" + emit: + final_result = cowpy.out +``` + +This is a net new addition to the code compared to the original workflow. + +### 2.5. Recap of the completed changes + +If you've done all the changes as described, your workflow should now look like this: + +```groovy title="original-hello/hello.nf" linenums="1" hl_lines="15 18-20 22 38-39" +#!/usr/bin/env nextflow + +/* +* Pipeline parameters +*/ +params.batch = 'test-batch' +params.character = 'turkey' + +// Include modules +include { sayHello } from './modules/sayHello.nf' +include { convertToUpper } from './modules/convertToUpper.nf' +include { collectGreetings } from './modules/collectGreetings.nf' +include { cowpy } from './modules/cowpy.nf' + +workflow HELLO { + + take: + // channel of greetings + greeting_ch + + main: + // emit a greeting + sayHello(greeting_ch) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // collect all the greetings into one file + collectGreetings(convertToUpper.out.collect(), params.batch) + + // emit a message about the size of the batch + collectGreetings.out.count.view { "There were $it greetings in this batch" } + + // generate ASCII art of the greetings with cowpy + cowpy(collectGreetings.out.outfile, params.character) + + emit: + final_result = cowpy.out +} +``` + +This describes everything Nextflow needs EXCEPT what to feed into the input channel. +That is going to be defined in the parent workflow, also called the **entrypoint** workflow. + +### 2.6. Make a dummy entrypoint workflow + +We can make a dummy entrypoint workflow to test the composable workflow without yet having to deal with the rest of the complexity of the nf-core pipeline scaffold. + +Create a blank file named `main.nf` in the same`original-hello` directory. + +```bash +touch original-hello/main.nf +``` + +Copy the following code into the `main.nf` file. + +```groovy title="original-hello/main.nf" linenums="1" +#!/usr/bin/env nextflow + +// import the workflow code from the hello.nf file +include { HELLO } from './hello.nf' + +// declare input parameter +params.greeting = 'greetings.csv' + +workflow { + // create a channel for inputs from a CSV file + greeting_ch = Channel.fromPath(params.greeting) + .splitCsv() + .map { line -> line[0] } + + // call the imported workflow on the channel of greetings + HELLO(greeting_ch) + + // view the outputs emitted by the workflow + HELLO.out.view { "Outputs: $it" } +} +``` + +You can see that the syntax for calling the imported workflow is essentially the same as the syntax for calling modules. + +You should also note that everything that has to do with pulling the inputs into the workflow (input parameter and channel construction) is now declared in this parent workflow. + +!!!note +You can name the entrypoint workflow file whatever you want, it does not have to be named `main.nf`. +The advantage of naming it `main.nf` is that if you don't specify a workflow file, Nextflow will automatically look for a file named `main.nf` in the specified directory. + +### 2.7. Test that the workflow runs + +We finally have all the pieces we need to verify that the composable workflow works. + +```bash +nextflow run original-hello +``` + +!!! note +Here you see the advantage of using the `main.nf` naming convention, which allows us to omit including the name of the workflow file in the command. +If we had named it `something_else.nf`, we would have had to do `nextflow run original-hello/something_else.nf`. + +If you made all the changes correctly, this should run to completion. + +```console title="Output" + N E X T F L O W ~ version 24.10.4 + +Launching `original-hello/main.nf` [friendly_wright] DSL2 - revision: 1ecd2d9c0a + +executor > local (8) +[24/c6c0d8] HELLO:sayHello (3) | 3 of 3 ✔ +[dc/721042] HELLO:convertToUpper (3) | 3 of 3 ✔ +[48/5ab2df] HELLO:collectGreetings | 1 of 1 ✔ +[e3/693b7e] HELLO:cowpy | 1 of 1 ✔ +There were 3 greetings in this batch +Outputs: /workspaces/training/hello-nf-core/work/e3/693b7e48dc119d0c54543e0634c2e7/cowpy-COLLECTED-test-batch-output.txt +``` + +This means we've successfully upgraded our HELLO workflow to be composable. ### Takeaway -You know how to [...]. +You know how to make a workflow composable by giving it a name and adding `take`, `main` and `emit` statements, and how to call it from an entrypoint workflow. !!!note If you're interested in digging deeper into options for composing workflows of workflows, check out the [Workflow of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows) (a.k.a. WoW) side quest. ### What's next? -Learn how to [...]. +Learn how to graft a basic composable workflow onto the nf-core scaffold. --- diff --git a/hello-nf-core/solutions/composable-hello/hello.nf b/hello-nf-core/solutions/composable-hello/hello.nf new file mode 100644 index 000000000..738a0e826 --- /dev/null +++ b/hello-nf-core/solutions/composable-hello/hello.nf @@ -0,0 +1,40 @@ +#!/usr/bin/env nextflow + +/* + * Pipeline parameters + */ +params.greeting = 'greetings.csv' +params.batch = 'test-batch' +params.character = 'turkey' + +// Include modules +include { sayHello } from './modules/sayHello.nf' +include { convertToUpper } from './modules/convertToUpper.nf' +include { collectGreetings } from './modules/collectGreetings.nf' +include { cowpy } from './modules/cowpy.nf' + +workflow HELLO { + + take: + // channel of greetings + greeting_ch + + main: + // emit a greeting + sayHello(greeting_ch) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // collect all the greetings into one file + collectGreetings(convertToUpper.out.collect(), params.batch) + + // emit a message about the size of the batch + collectGreetings.out.count.view { "There were $it greetings in this batch" } + + // generate ASCII art of the greetings with cowpy + cowpy(collectGreetings.out.outfile, params.character) + + emit: + final_result = cowpy.out +} diff --git a/hello-nf-core/solutions/composable-hello/main.nf b/hello-nf-core/solutions/composable-hello/main.nf new file mode 100644 index 000000000..7b1ecc595 --- /dev/null +++ b/hello-nf-core/solutions/composable-hello/main.nf @@ -0,0 +1,20 @@ +#!/usr/bin/env nextflow + +// import the workflow code from the hello.nf file +include { HELLO } from './hello.nf' + +// declare input parameter +params.greeting = 'greetings.csv' + +workflow { + // create a channel for inputs from a CSV file + greeting_ch = Channel.fromPath(params.greeting) + .splitCsv() + .map { line -> line[0] } + + // call the imported workflow on the channel of greetings + HELLO(greeting_ch) + + // view the outputs emitted by the workflow + HELLO.out.view { "Outputs: $it" } +} From b8a283eeaa586a9d7fce1e691cada26d05a65f4c Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 13 May 2025 03:35:59 -0400 Subject: [PATCH 14/43] nf-core conversion of hello nextflow complete --- docs/hello_nf-core/02_rewrite_hello.md | 490 +++++++++++++++++++--- docs/hello_nf-core/03_add_module.md | 4 + docs/hello_nf-core/04_input_validation.md | 63 +++ mkdocs.yml | 1 - 4 files changed, 509 insertions(+), 49 deletions(-) create mode 100644 docs/hello_nf-core/04_input_validation.md diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 396d0e7e1..ecf3bdb37 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -41,16 +41,15 @@ This TUI will ask you to provide basic information about your pipeline and will 2. Select **Custom** on the `Choose pipeline type` screen 3. Enter your pipeline details, replacing < YOUR NAME > with your own name, then select **Next** -- **GitHub organisation:** myorg +- **GitHub organisation:** core - **Workflow name:** hello -- **A short description of your pipeline:** nf-core compliant version of Hello Nextflow +- **A short description of your pipeline:** basic nf-core style version of Hello Nextflow - **Name of the main author / authors:** < YOUR NAME > 4. On the Template features screen, set "Toggle all features" to **off**, then selectively **enable** the following: - `Add configuration files` - `Use nf-core components` -- `Use nf-schema` - `Add documentation` - `Add testing profiles` @@ -74,16 +73,16 @@ Once the TUI closes, you should see the following console output. INFO Launching interactive nf-core pipeline creation tool. ``` -There is no explicit confirmation in the console output that the pipeline creation worked, but you should see a new directory called `myorg-hello`. +There is no explicit confirmation in the console output that the pipeline creation worked, but you should see a new directory called `core-hello`. View the contents of the new directory to see how much work you saved yourself by using the template. ```bash -tree myorg-hello +tree core-hello ``` ```console title="Output" -myorg-hello +core-hello/ ├── assets │ ├── samplesheet.csv │ └── schema_input.json @@ -151,37 +150,38 @@ This is because we didn't include any of the default nf-core modules. Believe it or not, even though you haven't yet added any modules to make it do real work, the pipeline scaffold can actually be run using the test profile, the same way we ran the `nf-core/demo` pipeline. ```bash -nextflow run myorg-hello -profile docker,test --outdir results +nextflow run core-hello -profile docker,test --outdir results ``` ```console title="Output" N E X T F L O W ~ version 24.10.4 -Launching `myorg-hello/main.nf` [naughty_babbage] DSL2 - revision: c0376c97f3 +Launching `core-hello/main.nf` [nauseous_meucci] DSL2 - revision: c31b966b36 Input/output options input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv - outdir : test_hello + outdir : whatever Institutional config options config_profile_name : Test profile config_profile_description: Minimal test dataset to check pipeline function Generic options - trace_report_suffix : 2025-05-12_14-46-59 + trace_report_suffix : 2025-05-13_04-48-09 Core Nextflow options - runName : naughty_babbage + runName : nauseous_meucci + containerEngine : docker launchDir : /workspaces/training/hello-nf-core workDir : /workspaces/training/hello-nf-core/work - projectDir : /workspaces/training/hello-nf-core/myorg-hello + projectDir : /workspaces/training/hello-nf-core/core-hello userName : root - profile : docker,test + profile : test,docker configFiles : !! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------ --[myorg/hello] Pipeline completed successfully- +-[core/hello] Pipeline completed successfully- ``` This shows you that all the basic wiring is in place. @@ -191,7 +191,7 @@ This shows you that all the basic wiring is in place. If you look inside the `main.nf` file, you'll see it imports a workflow called `HELLO` from `workflows/hello`. This is a placeholder workflow for our workflow of interest, with some nf-core functionality already in place. -```groovy title="conf/test.config" linenums="1" hl_lines="15 17 19 35" +```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="15 17 19 35" /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS @@ -238,10 +238,10 @@ workflow HELLO { */ ``` -Compared to a basic Nextflow workflow like the one developed in Hello Nextflow, you'll notice a few things that are new here: +Compared to a basic Nextflow workflow like the one developed in Hello Nextflow, you'll notice a few things that are new here (highlighted lines above): - The workflow block has a name -- Workflow inputs are declared using the `take:` keyword +- Workflow inputs are declared using the `take:` keyword and the channel construction is moved up to the parent workflow - Workflow content is placed inside a `main:` block - Outputs are declared using the `emit:` keyword @@ -360,7 +360,7 @@ First, let's give the workflow a name so we can refer to it from a parent workfl The same conventions apply to workflow names as to module names. -### 2.2. Replace channel construction with `take` statement +### 2.2. Replace channel construction with `take` Now, replace the channel construction with a simple `take` statement declaring expected inputs. @@ -579,66 +579,460 @@ Learn how to graft a basic composable workflow onto the nf-core scaffold. ## 3. Fit the updated workflow logic into the placeholder workflow -TODO: overview +This is the current content of the `HELLO` workflow in `core-hello/workflows/hello.nf`. +We need to add the relevant code from the version of the original workflow that we made composable. -### 3.1. Plug in the main workflow logic +```groovy title="core-hello/workflows/hello.nf" linenums="1" +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +include { paramsSummaryMap } from 'plugin/nf-schema' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' -TODO: instructions +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ -### 3.2. Copy over the modules +workflow HELLO { -TODO: instructions + take: + ch_samplesheet // channel: samplesheet read in from --input + main: -### 3.3. Set up the module imports + ch_versions = Channel.empty() -TODO: instructions + // + // Collate and save software versions + // + softwareVersionsToYAML(ch_versions) + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'hello_software_' + 'versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } -### Takeaway -You know how to [...]. + emit: + versions = ch_versions // channel: [ path(versions.yml) ] -### What's next? +} -Learn how to [...]. +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +``` ---- +### 3.1. Copy over the modules and set up module imports -## 4. Set up inputs, parameters and validation +This is the easiest part so let's do it first. +In the original workflow, the four processes are stored in modules, so we need to copy those over to this new project (into a new `local` directory) and add import statements to the workflow file. -TODO: instructions +```bash +mkdir core-hello/modules/local/ +cp original-hello/modules/* core-hello/modules/local/. +``` -### Takeaway +You should now see the directory of modules listed under `core-hello/`. -You know how to [...]. +```console title="Output" +tree core-hello/modules +``` -### What's next? +```console title="Output" +core-hello/modules +└── local + ├── collectGreetings.nf + ├── convertToUpper.nf + ├── cowpy.nf + └── sayHello.nf + +1 directory, 4 files +``` -[...]. +Finally, copy the import statements from the `original-hello/hello.nf` workflow to the `core-hello/workflows/hello.nf` version. ---- +```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="8-11" +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +include { paramsSummaryMap } from 'plugin/nf-schema' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { sayHello } from '../modules/local/sayHello.nf' +include { convertToUpper } from '../modules/local/convertToUpper.nf' +include { collectGreetings } from '../modules/local/collectGreetings.nf' +include { cowpy } from '../modules/local/cowpy.nf' +``` -## 5. Wire up tool version output +Notice that here we've adapted the spacing of the import statements to follow the nf-core style convention, and we've updated the relative paths to the modules to reflect that they're now stored at a different level of nesting. -TODO: instructions +### 3.2. Leave the `take` declaration as is -### Takeaway +The nf-core project has a lot of prebuilt functionality around the concept of the samplesheet, which is typically a CSV file containing columnar data. +Since that is essentially what our `greetings.csv` file is, we'll keep the current `take` declaration as is, and simply update the name of the input channel in the next step. -You know how to [...]. +```groovy title="core-hello/workflows/hello.nf" linenums="16" + take: + ch_samplesheet // channel: samplesheet read in from --input +``` -### What's next? +### 3.3. Update the `main` block -[...]. +Now that our modules are available to the workflow, we can plug the workflow logic into the `main` block (skipping over `take` for now, we'll come back to it in a moment). ---- +There is already some code in there that has to do with capturing the versions of the tools that get run by the workflow; we're going to leave that alone for now and simply insert our code right after the `main:` line. + +Importantly, we have to update the name of the channel we're passing to the `sayHello()` process from `greeting_ch` to `ch_samplesheet` (see highlighted lines). + +=== "After" + + ```groovy title="core-hello/workflows/hello.nf" linenums="16" hl_lines="18 19" + main: + + // emit a greeting (updated to use the default ch_samplesheet name) + sayHello(ch_samplesheet) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // collect all the greetings into one file + collectGreetings(convertToUpper.out.collect(), params.batch) + + // emit a message about the size of the batch + collectGreetings.out.count.view { "There were $it greetings in this batch" } + + // generate ASCII art of the greetings with cowpy + cowpy(collectGreetings.out.outfile, params.character) + + ch_versions = Channel.empty() + + // + // Collate and save software versions + // + softwareVersionsToYAML(ch_versions) + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'hello_software_' + 'versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } + + ``` + +=== "Before" + + ```groovy title="core-hello/workflows/hello.nf" linenums="16" + main: + + ch_versions = Channel.empty() + + // + // Collate and save software versions + // + softwareVersionsToYAML(ch_versions) + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'hello_software_' + 'versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } + + ``` + +We'll address the tool versions in a later section of this training. + +### 3.4. Update the `emit` block + +Finally, we need to update the `emit` block to include the declaration of the workflow's final outputs. + +=== "After" + + ```groovy title="core-hello/workflows/hello.nf" linenums="55" hl_lines="56" + emit: + final_result = cowpy.out + versions = ch_versions // channel: [ path(versions.yml) ] + ``` + +=== "Before" + + ```groovy title="core-hello/workflows/hello.nf" linenums="55" hl_lines="56" + emit: + versions = ch_versions // channel: [ path(versions.yml) ] + ``` + +This concludes the modifications we need to make to the HELLO workflow itself. + +### 3.5. Adapt the input handling + +Now that the HELLO workflow is ready to go, we need to adapt how the inputs are handled (to make sure our `greetings.csv` will be handled appropriately). +The first step is to figure out where the input handling is done. + +You may recall that when we rewrote the Hello Nextflow workflow to be composable, we moved the input parameter declaration up one level, in the `main.nf` entrypoint workflow. +So let's have a look at the top level `main.nf` entrypoint workflow that was created as part of the pipeline scaffold: + +```groovy title="core-hello/main.nf" linenums="1" +#!/usr/bin/env nextflow +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Github : https://github.com/core/hello +---------------------------------------------------------------------------------------- +*/ + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { HELLO } from './workflows/hello' +include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_hello_pipeline' +include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_hello_pipeline' +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + NAMED WORKFLOWS FOR PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// WORKFLOW: Run main analysis pipeline depending on type of input +// +workflow CORE_HELLO { + + take: + samplesheet // channel: samplesheet read in from --input + + main: + + // + // WORKFLOW: Run pipeline + // + HELLO ( + samplesheet + ) +} +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow { + + main: + // + // SUBWORKFLOW: Run initialisation tasks + // + PIPELINE_INITIALISATION ( + params.version, + params.validate_params, + params.monochrome_logs, + args, + params.outdir, + params.input + ) + + // + // WORKFLOW: Run main workflow + // + CORE_HELLO ( + PIPELINE_INITIALISATION.out.samplesheet + ) + // + // SUBWORKFLOW: Run completion tasks + // + PIPELINE_COMPLETION ( + params.outdir, + params.monochrome_logs, + ) +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +``` + +The nf-core project makes heavy use of nested subworkflows, so this bit can be a little confusing on first approach. + +What matters here is that there are two workflows defined: + +- `CORE_HELLO` is a thin wrapper for running the HELLO workflow we just finished adapting in `core-hello/workflows/hello.nf`. +- An unnamed workflow that calls `CORE_HELLO` as well as two other subworkflows, `PIPELINE_INITIALISATION` and `PIPELINE_COMPLETION`. + +Importantly, we cannot find any code constructing an input channel at this level, only references to a samplesheet provided via the `--input` parameter. + +A bit of poking around reveals that the input handling is done by the `PIPELINE_INITIALISATION` subworkflow, appropriately enough. + +If we open up `core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf` and scroll down, we come to this chunk of code: + +```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" + // + // Create channel from input file provided through params.input + // + + Channel + .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + .map { + meta, fastq_1, fastq_2 -> + if (!fastq_2) { + return [ meta.id, meta + [ single_end:true ], [ fastq_1 ] ] + } else { + return [ meta.id, meta + [ single_end:false ], [ fastq_1, fastq_2 ] ] + } + } + .groupTuple() + .map { samplesheet -> + validateInputSamplesheet(samplesheet) + } + .map { + meta, fastqs -> + return [ meta, fastqs.flatten() ] + } + .set { ch_samplesheet } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions +``` + +This is the channel factory that parses the samplesheet and passes it on in a form that is ready to be consumed by the HELLO workflow. +It is quite complex because it does a lot of parsing and validation work. + +The good news is that our pipeline's needs are much simpler, so we can replace all of that by the relevant channel factory we defined in the original Hello Nextflow workflow. -## 6. Test the pipeline +```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" + // + // Create channel from input file provided through params.input + // + ch_samplesheet = Channel.fromPath(params.input) + .splitCsv() + .map { line -> line[0] } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions +``` + +In its current form, this won't let us take advantage of nf-core's built-in capabilities for schema validation, but we can add that in later. +For now, let's focus on keeping it as simple as possible to get to something we can run successfully on test data. + +### 3.6. Update the test profiles + +Speaking of test data and parameters, let's update the test profile for this pipeline to use the `greetings.csv` mini-samplesheet. -TODO: instructions +Under `core-hello/config`, we find two test profiles: `test.config` and `test_full.config`, which are meant to test a small data sample and a full-size one. +Given the purpose of our pipeline, there's not really a point to setting up a full-size test profile, so feel free to ignore or delete `test_full.config`. +We're going to focus on setting up `test.config` to run on our `greetings.csv` file with a few default parameters. + +First we need to copy the greetings.csv file to an appropriate place in our pipeline project. +Typically small test files are stored under a directory called `assets`. +There isn't one yet so let's create it now, then copy the file over from our working directory. + +```bash +mkdir assets +cp greetings.csv assets/. +``` + +Now we can update the `test.config` file as follows: + +=== "After" + + ```groovy title="core-hello/config/test.config" linenums="21" + params { + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' + + // Input data + input = 'assets/greetings.csv' + + // Other parameters + batch = 'test' + character = 'tux' + } + ``` + +=== "Before" + + ```groovy title="core-hello/config/test.config" linenums="21" + params { + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' + + // Input data + // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv' + } + ``` + +### 3.6. Run the pipeline with the test profile + +That was a lot, but we can finally try running the pipeline! +Note that we have to add `--validate_params false` to the command line because we didn't set up the validation yet (that will come later). + +```bash +nextflow run core-hello --outdir . -profile test,docker --validate_params false +``` + +If you've done all of this correctly, it should produce the typical nf-core summary at the start (thanks to the initialisation subworkflow) and run to completion. + +```console title="Output" + N E X T F L O W ~ version 24.10.4 + +Launching `core-hello/main.nf` [ridiculous_pesquet] DSL2 - revision: c31b966b36 + +Input/output options + input : assets/greetings.csv + outdir : whatever + +Institutional config options + config_profile_name : Test profile + config_profile_description: Minimal test dataset to check pipeline function + +Generic options + validate_params : false + trace_report_suffix : 2025-05-13_07-16-11 + +Core Nextflow options + runName : ridiculous_pesquet + containerEngine : docker + launchDir : /workspaces/training/hello-nf-core + workDir : /workspaces/training/hello-nf-core/work + projectDir : /workspaces/training/hello-nf-core/core-hello + userName : root + profile : test,docker + configFiles : + +!! Only displaying parameters that differ from the pipeline defaults !! +------------------------------------------------------ +executor > local (8) +[cd/49cc7d] CORE_HELLO:HELLO:sayHello (3) [100%] 3 of 3 ✔ +[dc/729e82] CORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔ +[f8/72db8c] CORE_HELLO:HELLO:collectGreetings [100%] 1 of 1 ✔ +[fe/4922dd] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ +There were 3 greetings in this batch +-[core/hello] Pipeline completed successfully- +``` + +And there it is! It may seem like a low of work to accomplish the same result as the original pipeline, but this gives you a solid foundation to adopting key additional benefits of nf-core, including input validation and some neat metadata handling capabilities that we'll cover in a later section. + +--- ### Takeaway -You know how to [...]. +You know how to convert a regular Nextflow pipeline into an nf-core style pipeline using the nf-core project's template. As part of that, you learned how to make a workflow composable, and identify the most common elements of the nf-core template that need to be adapted when developing a custom nf-core style pipeline. ### What's next? -[...]. +Take a big break, that was hard work! Your brain deserves to chill out and you could probably use some hydration and a bit of stretching. When you're ready, move on to the next section to learn how to add an nf-core module to an existing nf-core style pipeline. diff --git a/docs/hello_nf-core/03_add_module.md b/docs/hello_nf-core/03_add_module.md index 223226297..677a58fda 100644 --- a/docs/hello_nf-core/03_add_module.md +++ b/docs/hello_nf-core/03_add_module.md @@ -2,6 +2,10 @@ In this third part of the Hello nf-core training course, we show you how to add an existing nf-core module to your pipeline. +TODO: THIS IS A WIP AND SHOULD NOT BE ADDED TO THE NAVIGATION + +(NOT SUBJECT TO REVIEW) + --- ## 1. Find cat/cat to replace collectGreetings diff --git a/docs/hello_nf-core/04_input_validation.md b/docs/hello_nf-core/04_input_validation.md new file mode 100644 index 000000000..ea9242f08 --- /dev/null +++ b/docs/hello_nf-core/04_input_validation.md @@ -0,0 +1,63 @@ +# Part 4: Input validation + +In this fourth part of the Hello nf-core training course, we show you how to use the nf-schema plugin to validate inputs. + +TODO: THIS IS A WIP AND SHOULD NOT BE ADDED TO THE NAVIGATION + +(NOT SUBJECT TO REVIEW) + +--- + +## 1. Add a schema + +TODO: instructions + +### Takeaway + +You now know how to [...]. + +### What's next? + +Find out [...]. + +--- + +## 2. Set up the validation + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +Learn how to [...]. + +--- + +## 3. Something with metamap? + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. + +--- + +## 4. [anything else?] + +TODO: instructions + +### Takeaway + +You know how to [...]. + +### What's next? + +[...]. diff --git a/mkdocs.yml b/mkdocs.yml index dbf57312e..8182c87d7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,7 +26,6 @@ nav: - hello_nf-core/00_orientation.md - hello_nf-core/01_run_demo.md - hello_nf-core/02_rewrite_hello.md - - hello_nf-core/03_add_module.md - hello_nf-core/survey.md - hello_nf-core/next_steps.md - Nextflow for Genomics: From a35a86c86350ed2f2e7bc030ae2dba408e2114ba Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 13 May 2025 03:43:07 -0400 Subject: [PATCH 15/43] fix note formatting --- docs/hello_nf-core/01_run_demo.md | 24 +++++++++++-------- docs/hello_nf-core/02_rewrite_hello.md | 32 +++++++++++++++----------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 08a7b5b24..0db030ee9 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -94,8 +94,9 @@ By default, the files will be saved to `$HOME/.nextflow/assets`. This can be customized using the `NXF_ASSETS` environment variable; see the configuration documentation at https://www.nextflow.io/docs/latest/config.html. !!! note -To be clear, you can do this with any Nextflow pipeline that is appropriately set up in GitHub, not just nf-core pipelines. -However nf-core is the largest open-source collection of Nextflow pipelines. + + To be clear, you can do this with any Nextflow pipeline that is appropriately set up in GitHub, not just nf-core pipelines. + However nf-core is the largest open-source collection of Nextflow pipelines. ### Takeaway @@ -226,9 +227,10 @@ Moving on to the execution output, let's have a look at the lines that tell us w This tells us that three processes were run, corresponding to the three tools shown in the pipeline documentation page on the nf-core website: FASTQC, SEQTK_TRIM and MULTIQC. !!! note -The full process names as shown here, such as `NFCORE_DEMO:DEMO:MULTIQC`, are longer than what you may have seen in the introductory Hello Nextflow material. -These includes the names of their parent workflows and reflect the modularity of the pipeline code. -We will go into more detail about that shortly. + + The full process names as shown here, such as `NFCORE_DEMO:DEMO:MULTIQC`, are longer than what you may have seen in the introductory Hello Nextflow material. + These includes the names of their parent workflows and reflect the modularity of the pipeline code. + We will go into more detail about that shortly. Finally, let's have a look at the `results` directory produced by the pipeline. @@ -321,9 +323,10 @@ Let's start with the code proper, though note that for now, we're going to focus The pipeline code organization follows a modular structure that is designed to maximize code reuse. -!!!note -We won't go over the actual code for how these modular components are connected, because there is some additional complexity associated with the use of subworkflows that can be confusing, and understanding that is not necessary at this stage of the training. -For now, we're going to focus on the logic of this modular organization. +!!! note + + We won't go over the actual code for how these modular components are connected, because there is some additional complexity associated with the use of subworkflows that can be confusing, and understanding that is not necessary at this stage of the training. + For now, we're going to focus on the logic of this modular organization. ### 3.1.1. Overall organization and `main.nf` script @@ -430,7 +433,8 @@ These subworkflows are what produces the fancy nf-core header in the console out Other pipelines may also use subworkflows as part of the main workflow of interest. !!! note -If you would like to learn how to compose workflows with subworkflows, see the [Workflows of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows/) Side Quest (also known as 'the WoW side quest'). + + If you would like to learn how to compose workflows with subworkflows, see the [Workflows of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows/) Side Quest (also known as 'the WoW side quest'). ### 3.2. Configuration, parameters and inputs @@ -468,4 +472,4 @@ You know what are the main components of an nf-core pipeline and how the code is ### What's next? -Take a break! That was a lot. When you're feeling refreshed and ready, move on to the next section to apply what you've learned to write an nf-core compliant pipeline. +Take a break! That was a lot. When you're feeling refreshed and ready, move on to the next section to apply what you've learned to write an nf-core compatible pipeline. diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index ecf3bdb37..8e3f87f1e 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1,6 +1,6 @@ # Part 2: Rewrite Hello for nf-core -In this second part of the Hello nf-core training course, we show you how to create an nf-core-compliant pipeline version of the pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. +In this second part of the Hello nf-core training course, we show you how to create an nf-core compatible pipeline version of the pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. You'll have noticed in the first section of the training that nf-core pipelines follow a fairly elaborate structure with a lot of accessory files. Creating all that from scratch would be very tedious, so the nf-core community has developed tooling to do it from a template instead, to bootstrap the process. @@ -8,8 +8,9 @@ Creating all that from scratch would be very tedious, so the nf-core community h We are going to show you how to use this tooling to create a pipeline scaffold, then adapt existing 'regular' pipeline code onto the nf-core scaffold. !!! note -The nf-core-tools package is pre-installed for you in our training environment. -If you are using a different environment, you need to check whether the package is installed (run `nf-core --help` in your terminal) and if not, install it as described here: https://nf-co.re/docs/nf-core-tools/installation. + + The nf-core-tools package is pre-installed for you in our training environment. + If you are using a different environment, you need to check whether the package is installed (run `nf-core --help` in your terminal) and if not, install it as described here: https://nf-co.re/docs/nf-core-tools/installation. --- @@ -18,7 +19,8 @@ If you are using a different environment, you need to check whether the package First, we create the scaffold for the new pipeline. !!! note -Make sure you are in the `hello_nf-core` directory in your terminal. + + Make sure you are in the `hello_nf-core` directory in your terminal. ### 1.1. Run the template-based pipeline creation tool @@ -142,8 +144,9 @@ That's a lot of files! !!! note -One important difference compared to the `nf-core/demo` pipeline we examined in the first part of this training is that there is no `modules` directory. -This is because we didn't include any of the default nf-core modules. + + One important difference compared to the `nf-core/demo` pipeline we examined in the first part of this training is that there is no `modules` directory. + This is because we didn't include any of the default nf-core modules. ### 1.2. Test that the scaffold is functional @@ -530,9 +533,10 @@ You can see that the syntax for calling the imported workflow is essentially the You should also note that everything that has to do with pulling the inputs into the workflow (input parameter and channel construction) is now declared in this parent workflow. -!!!note -You can name the entrypoint workflow file whatever you want, it does not have to be named `main.nf`. -The advantage of naming it `main.nf` is that if you don't specify a workflow file, Nextflow will automatically look for a file named `main.nf` in the specified directory. +!!! note + + You can name the entrypoint workflow file whatever you want, it does not have to be named `main.nf`. + The advantage of naming it `main.nf` is that if you don't specify a workflow file, Nextflow will automatically look for a file named `main.nf` in the specified directory. ### 2.7. Test that the workflow runs @@ -543,8 +547,9 @@ nextflow run original-hello ``` !!! note -Here you see the advantage of using the `main.nf` naming convention, which allows us to omit including the name of the workflow file in the command. -If we had named it `something_else.nf`, we would have had to do `nextflow run original-hello/something_else.nf`. + + Here you see the advantage of using the `main.nf` naming convention, which allows us to omit including the name of the workflow file in the command. + If we had named it `something_else.nf`, we would have had to do `nextflow run original-hello/something_else.nf`. If you made all the changes correctly, this should run to completion. @@ -568,8 +573,9 @@ This means we've successfully upgraded our HELLO workflow to be composable. You know how to make a workflow composable by giving it a name and adding `take`, `main` and `emit` statements, and how to call it from an entrypoint workflow. -!!!note -If you're interested in digging deeper into options for composing workflows of workflows, check out the [Workflow of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows) (a.k.a. WoW) side quest. +!!! note + + If you're interested in digging deeper into options for composing workflows of workflows, check out the [Workflow of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows) (a.k.a. WoW) side quest. ### What's next? From 0c43c0281d1e6358a400540a868a8d895944f407 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 13 May 2025 03:46:55 -0400 Subject: [PATCH 16/43] change compliant to compatible to avoid overselling --- docs/hello_nf-core/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/index.md b/docs/hello_nf-core/index.md index 68ed46193..74520a144 100644 --- a/docs/hello_nf-core/index.md +++ b/docs/hello_nf-core/index.md @@ -24,16 +24,16 @@ Let's get started! Click on the "Open in GitHub Codespaces" button below to laun ## Learning objectives -You will learn to use and develop nf-core compliant modules and pipelines, and utilize nf-core tooling effectively. +You will learn to use and develop nf-core compatible modules and pipelines, and utilize nf-core tooling effectively. By the end of this training, you will be able to: - Find and run nf-core pipelines - Describe the code structure and project organization of nf-core pipelines -- Create a basic nf-core compliant pipeline from a template -- Convert basic Nextflow modules to nf-core compliant modules +- Create a basic nf-core compatible pipeline from a template +- Convert basic Nextflow modules to nf-core compatible modules - Manage inputs and parameters using nf-core tooling -- Add nf-core modules to an nf-core compliant pipeline +- Add nf-core modules to an nf-core compatible pipeline ## Audience & prerequisites From 1e429db75b3ccf2c4eafc34689a3c569d3bab3a7 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 13 May 2025 03:50:18 -0400 Subject: [PATCH 17/43] fix numbers formatting --- docs/hello_nf-core/02_rewrite_hello.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 8e3f87f1e..bee8dab7b 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -43,10 +43,10 @@ This TUI will ask you to provide basic information about your pipeline and will 2. Select **Custom** on the `Choose pipeline type` screen 3. Enter your pipeline details, replacing < YOUR NAME > with your own name, then select **Next** -- **GitHub organisation:** core -- **Workflow name:** hello -- **A short description of your pipeline:** basic nf-core style version of Hello Nextflow -- **Name of the main author / authors:** < YOUR NAME > +**GitHub organisation:** core +**Workflow name:** hello +**A short description of your pipeline:** basic nf-core style version of Hello Nextflow +**Name of the main author / authors:** < YOUR NAME > 4. On the Template features screen, set "Toggle all features" to **off**, then selectively **enable** the following: From 06fa7eba23ef7efef374c25722901560e6f75e75 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 13 May 2025 03:57:58 -0400 Subject: [PATCH 18/43] a few more minor fixes --- docs/hello_nf-core/02_rewrite_hello.md | 8 ++++---- mkdocs.yml | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index bee8dab7b..796df4af5 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -449,7 +449,7 @@ This is a net new addition to the code compared to the original workflow. If you've done all the changes as described, your workflow should now look like this: -```groovy title="original-hello/hello.nf" linenums="1" hl_lines="15 18-20 22 38-39" +```groovy title="original-hello/hello.nf" linenums="1" hl_lines="15 17-19 21 37-38" #!/usr/bin/env nextflow /* @@ -692,7 +692,7 @@ Since that is essentially what our `greetings.csv` file is, we'll keep the curre ### 3.3. Update the `main` block -Now that our modules are available to the workflow, we can plug the workflow logic into the `main` block (skipping over `take` for now, we'll come back to it in a moment). +Now that our modules are available to the workflow, we can plug the workflow logic into the `main` block. There is already some code in there that has to do with capturing the versions of the tools that get run by the workflow; we're going to leave that alone for now and simply insert our code right after the `main:` line. @@ -700,7 +700,7 @@ Importantly, we have to update the name of the channel we're passing to the `say === "After" - ```groovy title="core-hello/workflows/hello.nf" linenums="16" hl_lines="18 19" + ```groovy title="core-hello/workflows/hello.nf" linenums="16" hl_lines="3 4" main: // emit a greeting (updated to use the default ch_samplesheet name) @@ -1041,4 +1041,4 @@ You know how to convert a regular Nextflow pipeline into an nf-core style pipeli ### What's next? -Take a big break, that was hard work! Your brain deserves to chill out and you could probably use some hydration and a bit of stretching. When you're ready, move on to the next section to learn how to add an nf-core module to an existing nf-core style pipeline. +Take a big break, that was hard work! Your brain deserves to chill out and you could probably use some hydration and a bit of stretching. When you're ready, move on to the next section to learn how to add an nf-core module to an existing nf-core style pipeline. (COMING SOON) diff --git a/mkdocs.yml b/mkdocs.yml index 8182c87d7..2725c49e5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,7 +26,6 @@ nav: - hello_nf-core/00_orientation.md - hello_nf-core/01_run_demo.md - hello_nf-core/02_rewrite_hello.md - - hello_nf-core/survey.md - hello_nf-core/next_steps.md - Nextflow for Genomics: - nf4_science/genomics/index.md From f7b512ce832bb1308c5f4b61bac58674597a7af9 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 13 May 2025 04:06:51 -0400 Subject: [PATCH 19/43] minor fixes --- docs/hello_nf-core/01_run_demo.md | 4 ---- docs/hello_nf-core/02_rewrite_hello.md | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 0db030ee9..1e4038986 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -458,12 +458,8 @@ This content is used to generate the web pages on the nf-core website. In addition to these human-readable documents, there are two JSON files that provide useful machine-readable information describing parameters and input requirements, `nextflow_schema.json` and `assets/schema_input.json`. -##### `nextflow_schema.json` - The `nextflow_schema.json` is a file used to store parameter related information including type, description and help text in a machine readable format. The schema is used for various purposes, including automated parameter validation, help text generation, and interactive parameter form rendering in UI interfaces. -##### `assets/schema_input.json` - The `schema_input.json` is a file used to define the input samplesheet structure. Each column can have a type, pattern, description and help text in a machine readable format. The schema is used for various purposes, including automated validation, and providing helpful error messages. ### Takeaway diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 796df4af5..560f3023c 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -946,8 +946,8 @@ Typically small test files are stored under a directory called `assets`. There isn't one yet so let's create it now, then copy the file over from our working directory. ```bash -mkdir assets -cp greetings.csv assets/. +mkdir core-hello/assets +cp greetings.csv core-hello/assets/. ``` Now we can update the `test.config` file as follows: @@ -988,7 +988,7 @@ That was a lot, but we can finally try running the pipeline! Note that we have to add `--validate_params false` to the command line because we didn't set up the validation yet (that will come later). ```bash -nextflow run core-hello --outdir . -profile test,docker --validate_params false +nextflow run core-hello --outdir results -profile test,docker --validate_params false ``` If you've done all of this correctly, it should produce the typical nf-core summary at the start (thanks to the initialisation subworkflow) and run to completion. @@ -1000,7 +1000,7 @@ Launching `core-hello/main.nf` [ridiculous_pesquet] DSL2 - revision: c31b966b36 Input/output options input : assets/greetings.csv - outdir : whatever + outdir : results Institutional config options config_profile_name : Test profile From 7e933cedcfde50d1fedec20756dce89e4973507f Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 14 May 2025 05:31:43 -0400 Subject: [PATCH 20/43] Minor improvements to orientation and demo run section --- docs/hello_nf-core/00_orientation.md | 23 +++--- docs/hello_nf-core/01_run_demo.md | 109 ++++++++++++++++++--------- 2 files changed, 87 insertions(+), 45 deletions(-) diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md index 6c07a0055..63b81cb49 100644 --- a/docs/hello_nf-core/00_orientation.md +++ b/docs/hello_nf-core/00_orientation.md @@ -17,7 +17,7 @@ Change directory now by running this command in the terminal: cd hello-nf-core/ ``` -!!!tip +!!! tip If for whatever reason you move out of this directory, you can always use the full path to return to it, assuming you're running this within the Github Codespaces training environment: @@ -44,21 +44,24 @@ If you run this inside `hello-nf-core`, you should see the following output: ```console title="Directory contents" . -├── ... -├── solutions -│ ├── 1-... -└── ... - -X directories, Y files +├── greetings.csv +├── original-hello +│ ├── hello.nf +│ ├── modules +│ └── nextflow.config +└── solutions + └── composable-hello + +4 directories, 3 files ``` **Here's a summary of what you should know to get started:** -- [...] +- **The `greetings.csv` file** is a CSV containing some minimal columnar data we use for testing purposes. + +- **The `original-hello` directory** contains a copy of the source code produced by working through the complete Hello Nextflow training series (with Docker enabled). - **The `solutions` directory** contains the completed workflow scripts that result from each step of the course. They are intended to be used as a reference to check your work and troubleshoot any issues. - The name and number in the filename correspond to the step of the relevant part of the course. - For example, [...] **Now, to begin the course, click on the arrow in the bottom right corner of this page.** diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 1e4038986..49e47f0f1 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -4,20 +4,7 @@ In this first part of the Hello nf-core training course, we show you how to find We are going to use a pipeline called nf-core/demo that is maintained by the nf-core project as part of its inventory of pipelines for demonstrating code structure and tool operations. ---- - -## 0. Warmup - -Before we go looking for the pipeline, let's create a project directory where we're going to do the work. - -Make sure you are in the `hello-nf-core/` directory as instructed in the [Orientation](./00_orientation.md), then create the directory as follows: - -```bash -mkdir demo_run/ -cd demo_run -``` - -Once that's done, you can move on to find and run your first nf-core pipeline! +Make sure you are in the `hello-nf-core/` directory as instructed in the [Orientation](./00_orientation.md). --- @@ -90,13 +77,59 @@ Checking nf-core/demo ... downloaded from https://github.com/nf-core/demo.git - revision: 04060b4644 [master] ``` -By default, the files will be saved to `$HOME/.nextflow/assets`. -This can be customized using the `NXF_ASSETS` environment variable; see the configuration documentation at https://www.nextflow.io/docs/latest/config.html. +To be clear, you can do this with any Nextflow pipeline that is appropriately set up in GitHub, not just nf-core pipelines. +However nf-core is the largest open-source collection of Nextflow pipelines. + +You can get Nextflow to give you a list of what pipelines you have retrieved in this way: + +```bash +nextflow list +``` + +```console title="Output" +nf-core/demo +``` + +You'll notice that the files are not in your current work directory. +By default, they are saved to `$NXF_HOME/assets`. + +```bash +tree -L 2 $NXF_HOME/assets/ +``` + +```console title="Output" +/workspaces/.nextflow/assets/ +└── nf-core + └── demo +``` !!! note +The full path may differ on your system if you're not using our training environment. + +The location of the downloaded source code is intentionally 'out of the way' on the principle that these pipelines should be used more like libraries than code that you would directly interact with. - To be clear, you can do this with any Nextflow pipeline that is appropriately set up in GitHub, not just nf-core pipelines. - However nf-core is the largest open-source collection of Nextflow pipelines. +However, for the purposes of this training, we'd like to be able to poke around and see what's in there. +So to make that easier, let's create a symbolic link to that location from our current working directory. + +```bash +ln -s $NXF_HOME/assets pipelines +``` + +This creates a shortcut that makes it easier to explore the code we just downloaded. + +```bash +tree -L2 pipelines +``` + +```bash +pipelines +└── nf-core + └── demo +``` + +Now we can more easily peek into the source code as needed. + +But first, let's try running your first nf-core pipeline! ### Takeaway @@ -151,10 +184,10 @@ However, the `outdir` parameter is not included in the `test` profile, so you ha Here, we're also going to specify `-profile docker`, which by nf-core convention enables the use of Docker containers. -Lets' try it! +Let's try it! ```bash -nextflow run nf-core/demo -profile docker,test --outdir results +nextflow run nf-core/demo -profile docker,test --outdir demo-results ``` Here's the console output from the pipeline: @@ -232,14 +265,14 @@ This tells us that three processes were run, corresponding to the three tools sh These includes the names of their parent workflows and reflect the modularity of the pipeline code. We will go into more detail about that shortly. -Finally, let's have a look at the `results` directory produced by the pipeline. +Finally, let's have a look at the `demo-results` directory produced by the pipeline. ```bash -tree results +tree demo-results ``` ```console title="Output" -results/ +demo-results/ ├── fastqc │ ├── SAMPLE1_PE │ ├── SAMPLE2_PE @@ -261,7 +294,10 @@ results/ └── pipeline_dag_2025-03-05_09-44-26.html ``` -If you're curious about what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.1/). +If you're curious about the specifics what that all means, check out [the nf-core/demo pipeline documentation page](https://nf-co.re/demo/1.0.1/). + +At this stage, what's important to observe is that the results are organized by module, and there is additionally a directory called `pipeline_info` containing various timestamped reports about the pipeline execution. +This is standard for nf-core pipelines. Congratulations! You have just run your first nf-core pipeline. @@ -279,15 +315,15 @@ Learn how the pipeline code is organized. The nf-core project enforces strong guidelines for how pipelines are structured, and how the code is organized, configured and documented. -Let's have a look at how the pipeline code is organized in the `nf-core/demo` repository. +Let's have a look at how the pipeline code is organized in the `nf-core/demo` repository (using the `pipelines` symlink we created earlier). You can either use `tree` or use the file explorer in your IDE. ```bash -tree -L 1 $HOME/.nextflow/assets/nf-core/demo +tree -L 1 pipelines/nf-core/demo ``` ```console title="Output (top-level only)" -/root/.nextflow/assets/nf-core/demo +pipelines/nf-core/demo ├── assets ├── CHANGELOG.md ├── CITATIONS.md @@ -335,11 +371,11 @@ At the top level, there is the `main.nf` script, which is the entrypoint Nextflo In practice, the `main.nf` script calls the actual workflow of interest, stored inside the `workflows` folder, called `demo.nf`. It also calls a few 'housekeeping' subworkflows that we're going to ignore for now. ```bash -tree $HOME/.nextflow/assets/nf-core/demo/workflows +tree pipelines/nf-core/demo/workflows ``` ```console title="Output" -/root/.nextflow/assets/nf-core/demo/workflows +pipelines/nf-core/demo/workflows └── demo.nf ``` @@ -370,11 +406,11 @@ In the nf-core project, modules are organized using a nested structure that refe The module code file describing the process is always called `main.nf`, and is accompanied by tests and `.yml` files. ```bash -tree -L 4 $HOME/.nextflow/assets/nf-core/demo/modules +tree -L 4 pipelines/nf-core/demo/modules ``` ```console title="Output" -/root/.nextflow/assets/nf-core/demo/modules +pipelines/nf-core/demo/modules └── nf-core ├── fastqc │   ├── environment.yml @@ -404,11 +440,11 @@ As noted above, subworkflows function as wrappers that call two or more modules. In an nf-core pipeline, the subworkflows are divided into `local` and `nf-core` directories, and each subworkflow has its own nested directory structure with its own `main.nf` script. ```bash -tree -L 4 $HOME/.nextflow/assets/nf-core/demo/subworkflows +tree -L 4 pipelines/nf-core/demo/subworkflows ``` ```console title="Output" -/root/.nextflow/assets/nf-core/demo/subworkflows +pipelines/nf-core/demo/subworkflows ├── local │   └── utils_nfcore_demo_pipeline │   └── main.nf @@ -458,9 +494,12 @@ This content is used to generate the web pages on the nf-core website. In addition to these human-readable documents, there are two JSON files that provide useful machine-readable information describing parameters and input requirements, `nextflow_schema.json` and `assets/schema_input.json`. -The `nextflow_schema.json` is a file used to store parameter related information including type, description and help text in a machine readable format. The schema is used for various purposes, including automated parameter validation, help text generation, and interactive parameter form rendering in UI interfaces. +The `nextflow_schema.json` is a file used to store parameter related information including type, description and help text in a machine readable format. +The schema is used for various purposes, including automated parameter validation, help text generation, and interactive parameter form rendering in UI interfaces. -The `schema_input.json` is a file used to define the input samplesheet structure. Each column can have a type, pattern, description and help text in a machine readable format. The schema is used for various purposes, including automated validation, and providing helpful error messages. +The `schema_input.json` is a file used to define the input samplesheet structure. +Each column can have a type, pattern, description and help text in a machine readable format. +The schema is used for various purposes, including automated validation, and providing helpful error messages. ### Takeaway From e6c3fc8a72b51842cf7dbd514d20ef620dfe7de3 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 14 May 2025 05:55:55 -0400 Subject: [PATCH 21/43] added schemas and example samplesheet details --- docs/hello_nf-core/01_run_demo.md | 109 ++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 49e47f0f1..51c28d187 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -472,7 +472,7 @@ Other pipelines may also use subworkflows as part of the main workflow of intere If you would like to learn how to compose workflows with subworkflows, see the [Workflows of Workflows](https://training.nextflow.io/latest/side_quests/workflows_of_workflows/) Side Quest (also known as 'the WoW side quest'). -### 3.2. Configuration, parameters and inputs +### 3.2. Configuration The nf-core project applies guidelines for pipeline configuration that aim to build on Nextflow's flexible customization options in a way that provides greater consistency and maintainability across pipelines. @@ -482,10 +482,10 @@ There are several additional configuration files that are stored in the `conf` f - `base.config`: A 'blank slate' config file, appropriate for general use on most high-performance computing. environments. This defines broad bins of resource usage, for example, which are convenient to apply to modules. - `modules.config`: Additional module directives and arguments. -- `test.config`: A profile to run the pipeline with minimal test data, which we used when we ran the demo pipeline in the previous section. +- `test.config`: A profile to run the pipeline with minimal test data, which we used when we ran the demo pipeline in the previous section (code shown there). - `test_full.config`: A profile to run the pipeline with a full-sized test dataset. -### 3.3. Documentation and other assets +### 3.3. Documentation and related assets At the top level, you can find a README file with summary information, as well as accessory files that summarize project information such as licensing, contribution guidelines, citation and code of conduct. @@ -494,13 +494,114 @@ This content is used to generate the web pages on the nf-core website. In addition to these human-readable documents, there are two JSON files that provide useful machine-readable information describing parameters and input requirements, `nextflow_schema.json` and `assets/schema_input.json`. -The `nextflow_schema.json` is a file used to store parameter related information including type, description and help text in a machine readable format. +The `nextflow_schema.json` is a file used to store information about the pipeline parameters including type, description and help text in a machine readable format. The schema is used for various purposes, including automated parameter validation, help text generation, and interactive parameter form rendering in UI interfaces. +```json title="assets/nextflow_schema.json (not showing full file)" linenums="1" +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/nf-core/demo/master/nextflow_schema.json", + "title": "nf-core/demo pipeline parameters", + "description": "An nf-core demo pipeline", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["input", "outdir"], + "properties": { + "input": { + "type": "string", + "format": "file-path", + "exists": true, + "schema": "assets/schema_input.json", + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "Path to comma-separated file containing information about the samples in the experiment.", + "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/demo/usage#samplesheet-input).", + "fa_icon": "fas fa-file-csv" + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + }, + "email": { + "type": "string", + "description": "Email address for completion summary.", + "fa_icon": "fas fa-envelope", + "help_text": "Set this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.", + "pattern": "^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$" + }, + "multiqc_title": { + "type": "string", + "description": "MultiQC report title. Printed as page header, used for filename if not otherwise specified.", + "fa_icon": "fas fa-file-signature" + } + } + }, +(truncated) +``` + The `schema_input.json` is a file used to define the input samplesheet structure. Each column can have a type, pattern, description and help text in a machine readable format. + +```json title="assets/schema_input.json" linenums="1" +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/nf-core/demo/master/assets/schema_input.json", + "title": "nf-core/demo pipeline - params.input schema", + "description": "Schema for the file provided with params.input", + "type": "array", + "items": { + "type": "object", + "properties": { + "sample": { + "type": "string", + "pattern": "^\\S+$", + "errorMessage": "Sample name must be provided and cannot contain spaces", + "meta": ["id"] + }, + "fastq_1": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + }, + "fastq_2": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + } + }, + "required": ["sample", "fastq_1"] + } +} +``` + The schema is used for various purposes, including automated validation, and providing helpful error messages. +An example samplesheet is provided under the `assets` directory: + +```csv title="assets/samplesheet.csv" linenums="1" +sample,fastq_1,fastq_2 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, + +``` + +!!! Note +The paths in this example samplesheet are not real. +For paths to real data files, you should look in the test profiles, which link to data in the `nf-core/test-datasets` repository. + + In general, it's considered good practice to link out to example data rather than include it in the pipeline code repository, unless the example data is of trivial size (as is the case for the `greetings.csv` in the Hello Nextflow training series). + ### Takeaway You know what are the main components of an nf-core pipeline and how the code is organized, what are the main elements of configuration, and what are some additional sources of information that can be useful. From c403c48242888d4f48b4b70972f407804b85481f Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 14 May 2025 07:31:41 -0400 Subject: [PATCH 22/43] Improved Hello rewrite --- docs/hello_nf-core/02_rewrite_hello.md | 206 ++++++++++++++++------- hello-nf-core/solutions/core-hello-end | 1 + hello-nf-core/solutions/core-hello-start | 1 + 3 files changed, 150 insertions(+), 58 deletions(-) create mode 160000 hello-nf-core/solutions/core-hello-end create mode 160000 hello-nf-core/solutions/core-hello-start diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 560f3023c..a285f6e95 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -1,6 +1,6 @@ # Part 2: Rewrite Hello for nf-core -In this second part of the Hello nf-core training course, we show you how to create an nf-core compatible pipeline version of the pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. +In this second part of the Hello nf-core training course, we show you how to create an nf-core compatible version of the pipeline produced by the [Hello Nextflow](../hello_nextflow/index.md) course. You'll have noticed in the first section of the training that nf-core pipelines follow a fairly elaborate structure with a lot of accessory files. Creating all that from scratch would be very tedious, so the nf-core community has developed tooling to do it from a template instead, to bootstrap the process. @@ -52,6 +52,7 @@ This TUI will ask you to provide basic information about your pipeline and will - `Add configuration files` - `Use nf-core components` +- `Use nf-schema` - `Add documentation` - `Add testing profiles` @@ -153,41 +154,47 @@ That's a lot of files! Believe it or not, even though you haven't yet added any modules to make it do real work, the pipeline scaffold can actually be run using the test profile, the same way we ran the `nf-core/demo` pipeline. ```bash -nextflow run core-hello -profile docker,test --outdir results +nextflow run core-hello -profile docker,test --outdir core-hello-results ``` ```console title="Output" N E X T F L O W ~ version 24.10.4 -Launching `core-hello/main.nf` [nauseous_meucci] DSL2 - revision: c31b966b36 +Launching `core-hello/main.nf` [special_ride] DSL2 - revision: c31b966b36 +Downloading plugin nf-schema@2.2.0 Input/output options input : https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv - outdir : whatever + outdir : core-hello-results Institutional config options config_profile_name : Test profile config_profile_description: Minimal test dataset to check pipeline function Generic options - trace_report_suffix : 2025-05-13_04-48-09 + trace_report_suffix : 2025-05-14_10-01-18 Core Nextflow options - runName : nauseous_meucci + runName : special_ride containerEngine : docker launchDir : /workspaces/training/hello-nf-core workDir : /workspaces/training/hello-nf-core/work projectDir : /workspaces/training/hello-nf-core/core-hello userName : root - profile : test,docker + profile : docker,test configFiles : !! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------ --[core/hello] Pipeline completed successfully- +-[core/hello] Pipeline completed successfully ``` This shows you that all the basic wiring is in place. +You can take a look at the reports in the `pipeline_info` directory to see what was run; not much at all! + +!!! note +The nf-core pipeline template includes an example samplesheet, but at time of writing it is very domain-specific. +Future work will aim to produce something more generic. ### 1.3. Examine the placeholder workflow @@ -259,7 +266,7 @@ You now know how to create a pipeline scaffold using nf-core tools. ### What's next? -Learn how to make a simple workflow composable. +Learn how to make a simple workflow composable as a prelude to making it nf-core compatible. --- @@ -395,7 +402,7 @@ Next, add a `main` statement before the rest of the operations called in the bod === "After" - ```groovy title="original-hello/hello.nf" linenums="21" hl_lines="22" + ```groovy title="original-hello/hello.nf" linenums="21" hl_lines="1" main: // emit a greeting sayHello(greeting_ch) @@ -525,7 +532,7 @@ workflow { HELLO(greeting_ch) // view the outputs emitted by the workflow - HELLO.out.view { "Outputs: $it" } + HELLO.out.view { "Output: $it" } } ``` @@ -564,7 +571,7 @@ executor > local (8) [48/5ab2df] HELLO:collectGreetings | 1 of 1 ✔ [e3/693b7e] HELLO:cowpy | 1 of 1 ✔ There were 3 greetings in this batch -Outputs: /workspaces/training/hello-nf-core/work/e3/693b7e48dc119d0c54543e0634c2e7/cowpy-COLLECTED-test-batch-output.txt +Output: /workspaces/training/hello-nf-core/work/e3/693b7e48dc119d0c54543e0634c2e7/cowpy-COLLECTED-test-batch-output.txt ``` This means we've successfully upgraded our HELLO workflow to be composable. @@ -586,6 +593,8 @@ Learn how to graft a basic composable workflow onto the nf-core scaffold. ## 3. Fit the updated workflow logic into the placeholder workflow This is the current content of the `HELLO` workflow in `core-hello/workflows/hello.nf`. +Overall this code does very little aside from some housekeeping that has to do with capturing the version of any software tools that get run in the pipeline. + We need to add the relevant code from the version of the original workflow that we made composable. ```groovy title="core-hello/workflows/hello.nf" linenums="1" @@ -635,19 +644,30 @@ workflow HELLO { */ ``` +We're going to tackle this in the following stages: + +1. Copy over the modules and set up module imports +2. Leave the `take` declaration as is +3. Update the `main` block +4. Update the `emit` block + +!!! note +We're going to ignore the version capture for this first pass and will look at how to wire that up in a later section. + ### 3.1. Copy over the modules and set up module imports -This is the easiest part so let's do it first. In the original workflow, the four processes are stored in modules, so we need to copy those over to this new project (into a new `local` directory) and add import statements to the workflow file. +First let's copy the module files over: + ```bash -mkdir core-hello/modules/local/ +mkdir -p core-hello/modules/local/ cp original-hello/modules/* core-hello/modules/local/. ``` You should now see the directory of modules listed under `core-hello/`. -```console title="Output" +```bash tree core-hello/modules ``` @@ -658,25 +678,37 @@ core-hello/modules ├── convertToUpper.nf ├── cowpy.nf └── sayHello.nf - -1 directory, 4 files ``` Finally, copy the import statements from the `original-hello/hello.nf` workflow to the `core-hello/workflows/hello.nf` version. -```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="8-11" -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ -include { paramsSummaryMap } from 'plugin/nf-schema' -include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' -include { sayHello } from '../modules/local/sayHello.nf' -include { convertToUpper } from '../modules/local/convertToUpper.nf' -include { collectGreetings } from '../modules/local/collectGreetings.nf' -include { cowpy } from '../modules/local/cowpy.nf' -``` +=== "After" + + ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="8-11" + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + include { paramsSummaryMap } from 'plugin/nf-schema' + include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' + include { sayHello } from '../modules/local/sayHello.nf' + include { convertToUpper } from '../modules/local/convertToUpper.nf' + include { collectGreetings } from '../modules/local/collectGreetings.nf' + include { cowpy } from '../modules/local/cowpy.nf' + ``` + +=== "Before" + + ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="8-11" + /* + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + include { paramsSummaryMap } from 'plugin/nf-schema' + include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' + ``` Notice that here we've adapted the spacing of the import statements to follow the nf-core style convention, and we've updated the relative paths to the modules to reflect that they're now stored at a different level of nesting. @@ -690,6 +722,8 @@ Since that is essentially what our `greetings.csv` file is, we'll keep the curre ch_samplesheet // channel: samplesheet read in from --input ``` +The input handling will be done upstream of this workflow (not in this code file). + ### 3.3. Update the `main` block Now that our modules are available to the workflow, we can plug the workflow logic into the `main` block. @@ -701,7 +735,7 @@ Importantly, we have to update the name of the channel we're passing to the `say === "After" ```groovy title="core-hello/workflows/hello.nf" linenums="16" hl_lines="3 4" - main: + // emit a greeting (updated to use the default ch_samplesheet name) sayHello(ch_samplesheet) @@ -761,7 +795,7 @@ Finally, we need to update the `emit` block to include the declaration of the wo === "After" - ```groovy title="core-hello/workflows/hello.nf" linenums="55" hl_lines="56" + ```groovy title="core-hello/workflows/hello.nf" linenums="55" hl_lines="2" emit: final_result = cowpy.out versions = ch_versions // channel: [ path(versions.yml) ] @@ -769,16 +803,29 @@ Finally, we need to update the `emit` block to include the declaration of the wo === "Before" - ```groovy title="core-hello/workflows/hello.nf" linenums="55" hl_lines="56" + ```groovy title="core-hello/workflows/hello.nf" linenums="55" emit: versions = ch_versions // channel: [ path(versions.yml) ] ``` This concludes the modifications we need to make to the HELLO workflow itself. -### 3.5. Adapt the input handling +### Takeaway + +You know how to fit the core pieces of a composable workflow into an nf-core placeholder workflow. + +### What's next? + +Learn how to adapt how the inputs are handle in the nf-core pipeline scaffold. + +--- + +## 4. Adapt the input handling Now that the HELLO workflow is ready to go, we need to adapt how the inputs are handled (to make sure our `greetings.csv` will be handled appropriately). + +### 4.1. Identify where inputs are handled + The first step is to figure out where the input handling is done. You may recall that when we rewrote the Hello Nextflow workflow to be composable, we moved the input parameter declaration up one level, in the `main.nf` entrypoint workflow. @@ -915,9 +962,24 @@ If we open up `core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf This is the channel factory that parses the samplesheet and passes it on in a form that is ready to be consumed by the HELLO workflow. It is quite complex because it does a lot of parsing and validation work. -The good news is that our pipeline's needs are much simpler, so we can replace all of that by the relevant channel factory we defined in the original Hello Nextflow workflow. +!!! note +The syntax above is a little different from what we've used previously, but basically this: -```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" + ```groovy + Channel.<...>.set { ch_samplesheet } + ``` + + is equivalent to this: + + ```groovy + ch_samplesheet = Channel.<...> + ``` + +### 4.2. Replace the templated input channel code + +The good news is that our pipeline's needs are much simpler, so we can replace all of that by the channel construction code we developed in the original Hello Nextflow workflow. + +```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" hl_lines="4" // // Create channel from input file provided through params.input // @@ -930,23 +992,23 @@ The good news is that our pipeline's needs are much simpler, so we can replace a versions = ch_versions ``` +Importantly, in that highlighted line, we've updated the channel name from `greeting_ch` to `ch_samplesheet`, and the parameter name from `params.greeting` to `params.input`. + In its current form, this won't let us take advantage of nf-core's built-in capabilities for schema validation, but we can add that in later. For now, let's focus on keeping it as simple as possible to get to something we can run successfully on test data. -### 3.6. Update the test profiles +### 4.3. Update the test profile -Speaking of test data and parameters, let's update the test profile for this pipeline to use the `greetings.csv` mini-samplesheet. +Speaking of test data and parameters, let's update the test profile for this pipeline to use the `greetings.csv` mini-samplesheet instead of the example samplesheet provided in the template. -Under `core-hello/config`, we find two test profiles: `test.config` and `test_full.config`, which are meant to test a small data sample and a full-size one. +Under `core-hello/config`, we find two templated test profiles: `test.config` and `test_full.config`, which are meant to test a small data sample and a full-size one. Given the purpose of our pipeline, there's not really a point to setting up a full-size test profile, so feel free to ignore or delete `test_full.config`. We're going to focus on setting up `test.config` to run on our `greetings.csv` file with a few default parameters. -First we need to copy the greetings.csv file to an appropriate place in our pipeline project. -Typically small test files are stored under a directory called `assets`. -There isn't one yet so let's create it now, then copy the file over from our working directory. +First we need to copy the `greetings.csv` file to an appropriate place in our pipeline project. +Typically small test files are stored in the `assets` directory, so let's copy the file over from our working directory. ```bash -mkdir core-hello/assets cp greetings.csv core-hello/assets/. ``` @@ -960,7 +1022,7 @@ Now we can update the `test.config` file as follows: config_profile_description = 'Minimal test dataset to check pipeline function' // Input data - input = 'assets/greetings.csv' + input = 'core-hello/assets/greetings.csv' // Other parameters batch = 'test' @@ -982,13 +1044,40 @@ Now we can update the `test.config` file as follows: } ``` -### 3.6. Run the pipeline with the test profile +And while we're at it, let's lower the default resource allocations: + +=== "After" + + ```groovy title="core-hello/config/test.config" linenums="13" + process { + resourceLimits = [ + cpus: 1, + memory: '1.GB' + ] + } + ``` + +=== "Before" + + ```groovy title="core-hello/config/test.config" linenums="13" + process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] + } + ``` + +This completes the code modifications we need to do. + +### 4.4. Run the pipeline with the test profile That was a lot, but we can finally try running the pipeline! Note that we have to add `--validate_params false` to the command line because we didn't set up the validation yet (that will come later). ```bash -nextflow run core-hello --outdir results -profile test,docker --validate_params false +nextflow run core-hello --outdir core-hello-results -profile test,docker --validate_params false ``` If you've done all of this correctly, it should produce the typical nf-core summary at the start (thanks to the initialisation subworkflow) and run to completion. @@ -996,11 +1085,11 @@ If you've done all of this correctly, it should produce the typical nf-core summ ```console title="Output" N E X T F L O W ~ version 24.10.4 -Launching `core-hello/main.nf` [ridiculous_pesquet] DSL2 - revision: c31b966b36 +Launching `core-hello/main.nf` [agitated_noyce] DSL2 - revision: c31b966b36 Input/output options - input : assets/greetings.csv - outdir : results + input : core-hello/assets/greetings.csv + outdir : core-hello-results Institutional config options config_profile_name : Test profile @@ -1008,10 +1097,10 @@ Institutional config options Generic options validate_params : false - trace_report_suffix : 2025-05-13_07-16-11 + trace_report_suffix : 2025-05-14_11-10-22 Core Nextflow options - runName : ridiculous_pesquet + runName : agitated_noyce containerEngine : docker launchDir : /workspaces/training/hello-nf-core workDir : /workspaces/training/hello-nf-core/work @@ -1023,21 +1112,22 @@ Core Nextflow options !! Only displaying parameters that differ from the pipeline defaults !! ------------------------------------------------------ executor > local (8) -[cd/49cc7d] CORE_HELLO:HELLO:sayHello (3) [100%] 3 of 3 ✔ -[dc/729e82] CORE_HELLO:HELLO:convertToUpper (3) [100%] 3 of 3 ✔ -[f8/72db8c] CORE_HELLO:HELLO:collectGreetings [100%] 1 of 1 ✔ -[fe/4922dd] CORE_HELLO:HELLO:cowpy [100%] 1 of 1 ✔ -There were 3 greetings in this batch +[d6/b59dca] CORE_HELLO:HELLO:sayHello (1) | 3 of 3 ✔ +[0b/42f9a1] CORE_HELLO:HELLO:convertToUpper (2) | 3 of 3 ✔ +[73/bec621] CORE_HELLO:HELLO:collectGreetings | 1 of 1 ✔ +[3f/e0a67a] CORE_HELLO:HELLO:cowpy | 1 of 1 ✔ -[core/hello] Pipeline completed successfully- ``` -And there it is! It may seem like a low of work to accomplish the same result as the original pipeline, but this gives you a solid foundation to adopting key additional benefits of nf-core, including input validation and some neat metadata handling capabilities that we'll cover in a later section. +And there it is! It may seem like a low of work to accomplish the same result as the original pipeline, but if you check out the results directory, you'll see that in addition to the results produced by the Hello pipeline, you still get the `pipeline_info` directory containing the various reports produced by the nf-core utility subworkflows. + +On top of that, this gives you a solid foundation to adopting key additional benefits of nf-core, including input validation and some neat metadata handling capabilities that we'll cover in a later section. --- ### Takeaway -You know how to convert a regular Nextflow pipeline into an nf-core style pipeline using the nf-core project's template. As part of that, you learned how to make a workflow composable, and identify the most common elements of the nf-core template that need to be adapted when developing a custom nf-core style pipeline. +You know how to convert a regular Nextflow pipeline into an nf-core style pipeline using the nf-core template. As part of that, you learned how to make a workflow composable, and identify the most common elements of the nf-core template that need to be adapted when developing a custom nf-core style pipeline. ### What's next? diff --git a/hello-nf-core/solutions/core-hello-end b/hello-nf-core/solutions/core-hello-end new file mode 160000 index 000000000..34066d31a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end @@ -0,0 +1 @@ +Subproject commit 34066d31a6b9da1867d32b9c873dd5aa59685fe6 diff --git a/hello-nf-core/solutions/core-hello-start b/hello-nf-core/solutions/core-hello-start new file mode 160000 index 000000000..34066d31a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start @@ -0,0 +1 @@ +Subproject commit 34066d31a6b9da1867d32b9c873dd5aa59685fe6 From 03a61040350aa888d1e77f0ad1b33dbe1f6c057a Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 14 May 2025 07:39:52 -0400 Subject: [PATCH 23/43] deleting due to weirdness --- hello-nf-core/solutions/core-hello-end | 1 - hello-nf-core/solutions/core-hello-start | 1 - 2 files changed, 2 deletions(-) delete mode 160000 hello-nf-core/solutions/core-hello-end delete mode 160000 hello-nf-core/solutions/core-hello-start diff --git a/hello-nf-core/solutions/core-hello-end b/hello-nf-core/solutions/core-hello-end deleted file mode 160000 index 34066d31a..000000000 --- a/hello-nf-core/solutions/core-hello-end +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 34066d31a6b9da1867d32b9c873dd5aa59685fe6 diff --git a/hello-nf-core/solutions/core-hello-start b/hello-nf-core/solutions/core-hello-start deleted file mode 160000 index 34066d31a..000000000 --- a/hello-nf-core/solutions/core-hello-start +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 34066d31a6b9da1867d32b9c873dd5aa59685fe6 From 2f72b53326c56a4d998bf13900039c6edfcf1c44 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 14 May 2025 07:42:33 -0400 Subject: [PATCH 24/43] Start and end states of core-hello (minus the git file) --- .../solutions/core-hello-end/README.md | 75 ++++ .../core-hello-end/assets/greetings.csv | 3 + .../core-hello-end/assets/samplesheet.csv | 3 + .../core-hello-end/assets/schema_input.json | 33 ++ .../solutions/core-hello-end/conf/base.config | 62 +++ .../core-hello-end/conf/modules.config | 21 + .../solutions/core-hello-end/conf/test.config | 30 ++ .../solutions/core-hello-end/docs/README.md | 8 + .../solutions/core-hello-end/docs/output.md | 29 ++ .../solutions/core-hello-end/docs/usage.md | 211 +++++++++ .../solutions/core-hello-end/main.nf | 82 ++++ .../solutions/core-hello-end/modules.json | 30 ++ .../modules/local/collectGreetings.nf | 21 + .../modules/local/convertToUpper.nf | 20 + .../core-hello-end/modules/local/cowpy.nf | 22 + .../core-hello-end/modules/local/sayHello.nf | 20 + .../solutions/core-hello-end/nextflow.config | 238 ++++++++++ .../core-hello-end/nextflow_schema.json | 151 +++++++ .../local/utils_nfcore_hello_pipeline/main.nf | 123 +++++ .../nf-core/utils_nextflow_pipeline/main.nf | 126 ++++++ .../nf-core/utils_nextflow_pipeline/meta.yml | 38 ++ .../tests/main.function.nf.test | 54 +++ .../tests/main.function.nf.test.snap | 20 + .../tests/main.workflow.nf.test | 113 +++++ .../tests/nextflow.config | 9 + .../utils_nextflow_pipeline/tests/tags.yml | 2 + .../nf-core/utils_nfcore_pipeline/main.nf | 419 ++++++++++++++++++ .../nf-core/utils_nfcore_pipeline/meta.yml | 24 + .../tests/main.function.nf.test | 126 ++++++ .../tests/main.function.nf.test.snap | 136 ++++++ .../tests/main.workflow.nf.test | 29 ++ .../tests/main.workflow.nf.test.snap | 19 + .../tests/nextflow.config | 9 + .../utils_nfcore_pipeline/tests/tags.yml | 2 + .../nf-core/utils_nfschema_plugin/main.nf | 45 ++ .../nf-core/utils_nfschema_plugin/meta.yml | 35 ++ .../utils_nfschema_plugin/tests/main.nf.test | 117 +++++ .../tests/nextflow.config | 8 + .../tests/nextflow_schema.json | 103 +++++ .../core-hello-end/workflows/hello.nf | 61 +++ .../solutions/core-hello-start/README.md | 75 ++++ .../core-hello-start/assets/samplesheet.csv | 3 + .../core-hello-start/assets/schema_input.json | 33 ++ .../core-hello-start/conf/base.config | 62 +++ .../core-hello-start/conf/modules.config | 21 + .../core-hello-start/conf/test.config | 29 ++ .../core-hello-start/conf/test_full.config | 24 + .../solutions/core-hello-start/docs/README.md | 8 + .../solutions/core-hello-start/docs/output.md | 29 ++ .../solutions/core-hello-start/docs/usage.md | 211 +++++++++ .../solutions/core-hello-start/main.nf | 82 ++++ .../solutions/core-hello-start/modules.json | 30 ++ .../core-hello-start/nextflow.config | 238 ++++++++++ .../core-hello-start/nextflow_schema.json | 151 +++++++ .../local/utils_nfcore_hello_pipeline/main.nf | 140 ++++++ .../nf-core/utils_nextflow_pipeline/main.nf | 126 ++++++ .../nf-core/utils_nextflow_pipeline/meta.yml | 38 ++ .../tests/main.function.nf.test | 54 +++ .../tests/main.function.nf.test.snap | 20 + .../tests/main.workflow.nf.test | 113 +++++ .../tests/nextflow.config | 9 + .../utils_nextflow_pipeline/tests/tags.yml | 2 + .../nf-core/utils_nfcore_pipeline/main.nf | 419 ++++++++++++++++++ .../nf-core/utils_nfcore_pipeline/meta.yml | 24 + .../tests/main.function.nf.test | 126 ++++++ .../tests/main.function.nf.test.snap | 136 ++++++ .../tests/main.workflow.nf.test | 29 ++ .../tests/main.workflow.nf.test.snap | 19 + .../tests/nextflow.config | 9 + .../utils_nfcore_pipeline/tests/tags.yml | 2 + .../nf-core/utils_nfschema_plugin/main.nf | 45 ++ .../nf-core/utils_nfschema_plugin/meta.yml | 35 ++ .../utils_nfschema_plugin/tests/main.nf.test | 117 +++++ .../tests/nextflow.config | 8 + .../tests/nextflow_schema.json | 103 +++++ .../core-hello-start/workflows/hello.nf | 44 ++ 76 files changed, 5291 insertions(+) create mode 100644 hello-nf-core/solutions/core-hello-end/README.md create mode 100644 hello-nf-core/solutions/core-hello-end/assets/greetings.csv create mode 100644 hello-nf-core/solutions/core-hello-end/assets/samplesheet.csv create mode 100644 hello-nf-core/solutions/core-hello-end/assets/schema_input.json create mode 100644 hello-nf-core/solutions/core-hello-end/conf/base.config create mode 100644 hello-nf-core/solutions/core-hello-end/conf/modules.config create mode 100644 hello-nf-core/solutions/core-hello-end/conf/test.config create mode 100644 hello-nf-core/solutions/core-hello-end/docs/README.md create mode 100644 hello-nf-core/solutions/core-hello-end/docs/output.md create mode 100644 hello-nf-core/solutions/core-hello-end/docs/usage.md create mode 100644 hello-nf-core/solutions/core-hello-end/main.nf create mode 100644 hello-nf-core/solutions/core-hello-end/modules.json create mode 100644 hello-nf-core/solutions/core-hello-end/modules/local/collectGreetings.nf create mode 100644 hello-nf-core/solutions/core-hello-end/modules/local/convertToUpper.nf create mode 100644 hello-nf-core/solutions/core-hello-end/modules/local/cowpy.nf create mode 100644 hello-nf-core/solutions/core-hello-end/modules/local/sayHello.nf create mode 100644 hello-nf-core/solutions/core-hello-end/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-end/nextflow_schema.json create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/local/utils_nfcore_hello_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/main.nf create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json create mode 100644 hello-nf-core/solutions/core-hello-end/workflows/hello.nf create mode 100644 hello-nf-core/solutions/core-hello-start/README.md create mode 100644 hello-nf-core/solutions/core-hello-start/assets/samplesheet.csv create mode 100644 hello-nf-core/solutions/core-hello-start/assets/schema_input.json create mode 100644 hello-nf-core/solutions/core-hello-start/conf/base.config create mode 100644 hello-nf-core/solutions/core-hello-start/conf/modules.config create mode 100644 hello-nf-core/solutions/core-hello-start/conf/test.config create mode 100644 hello-nf-core/solutions/core-hello-start/conf/test_full.config create mode 100644 hello-nf-core/solutions/core-hello-start/docs/README.md create mode 100644 hello-nf-core/solutions/core-hello-start/docs/output.md create mode 100644 hello-nf-core/solutions/core-hello-start/docs/usage.md create mode 100644 hello-nf-core/solutions/core-hello-start/main.nf create mode 100644 hello-nf-core/solutions/core-hello-start/modules.json create mode 100644 hello-nf-core/solutions/core-hello-start/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-start/nextflow_schema.json create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/local/utils_nfcore_hello_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/main.nf create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/main.nf create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/meta.yml create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config create mode 100644 hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json create mode 100644 hello-nf-core/solutions/core-hello-start/workflows/hello.nf diff --git a/hello-nf-core/solutions/core-hello-end/README.md b/hello-nf-core/solutions/core-hello-end/README.md new file mode 100644 index 000000000..0a533c4c4 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/README.md @@ -0,0 +1,75 @@ +# core/hello + +## Introduction + +**core/hello** is a bioinformatics pipeline that ... + + + + + + +## 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. + + + +Now, you can run the pipeline using: + + + +```bash +nextflow run core/hello \ + -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/docs/usage/getting_started/configuration#custom-configuration-files). + +## Credits + +core/hello was originally written by GG. + +We thank the following people for their extensive assistance in the development of this pipeline: + + + +## Contributions and Support + +If you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md). + +## Citations + + + + +This pipeline uses code and infrastructure developed and maintained by the [nf-core](https://nf-co.re) community, reused here under the [MIT license](https://github.com/nf-core/tools/blob/main/LICENSE). + +> **The nf-core framework for community-curated bioinformatics pipelines.** +> +> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen. +> +> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x). diff --git a/hello-nf-core/solutions/core-hello-end/assets/greetings.csv b/hello-nf-core/solutions/core-hello-end/assets/greetings.csv new file mode 100644 index 000000000..c5889e19a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/assets/greetings.csv @@ -0,0 +1,3 @@ +Hello +Bonjour +Holà diff --git a/hello-nf-core/solutions/core-hello-end/assets/samplesheet.csv b/hello-nf-core/solutions/core-hello-end/assets/samplesheet.csv new file mode 100644 index 000000000..5f653ab7b --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/assets/samplesheet.csv @@ -0,0 +1,3 @@ +sample,fastq_1,fastq_2 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, diff --git a/hello-nf-core/solutions/core-hello-end/assets/schema_input.json b/hello-nf-core/solutions/core-hello-end/assets/schema_input.json new file mode 100644 index 000000000..bc0261f32 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/assets/schema_input.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/assets/schema_input.json", + "title": "core/hello pipeline - params.input schema", + "description": "Schema for the file provided with params.input", + "type": "array", + "items": { + "type": "object", + "properties": { + "sample": { + "type": "string", + "pattern": "^\\S+$", + "errorMessage": "Sample name must be provided and cannot contain spaces", + "meta": ["id"] + }, + "fastq_1": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + }, + "fastq_2": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + } + }, + "required": ["sample", "fastq_1"] + } +} diff --git a/hello-nf-core/solutions/core-hello-end/conf/base.config b/hello-nf-core/solutions/core-hello-end/conf/base.config new file mode 100644 index 000000000..1abcd9876 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/conf/base.config @@ -0,0 +1,62 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello Nextflow base config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A 'blank slate' config file, appropriate for general use on most high performance + compute environments. Assumes that all software is installed and available on + the PATH. Runs in `local` mode - all jobs will be run on the logged in environment. +---------------------------------------------------------------------------------------- +*/ + +process { + + // TODO nf-core: Check the defaults for all processes + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + + errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } + maxRetries = 1 + maxErrors = '-1' + + // Process-specific resource requirements + // NOTE - Please try and reuse the labels below as much as possible. + // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. + // If possible, it would be nice to keep the same label naming convention when + // adding in your local modules too. + // TODO nf-core: Customise requirements for specific processes. + // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors + withLabel:process_single { + cpus = { 1 } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_low { + cpus = { 2 * task.attempt } + memory = { 12.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_medium { + cpus = { 6 * task.attempt } + memory = { 36.GB * task.attempt } + time = { 8.h * task.attempt } + } + withLabel:process_high { + cpus = { 12 * task.attempt } + memory = { 72.GB * task.attempt } + time = { 16.h * task.attempt } + } + withLabel:process_long { + time = { 20.h * task.attempt } + } + withLabel:process_high_memory { + memory = { 200.GB * task.attempt } + } + withLabel:error_ignore { + errorStrategy = 'ignore' + } + withLabel:error_retry { + errorStrategy = 'retry' + maxRetries = 2 + } +} diff --git a/hello-nf-core/solutions/core-hello-end/conf/modules.config b/hello-nf-core/solutions/core-hello-end/conf/modules.config new file mode 100644 index 000000000..e27fd2826 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/conf/modules.config @@ -0,0 +1,21 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Config file for defining DSL2 per module options and publishing paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Available keys to override module options: + ext.args = Additional arguments appended to command in module. + ext.args2 = Second set of arguments appended to command in module (multi-tool modules). + ext.args3 = Third set of arguments appended to command in module (multi-tool modules). + ext.prefix = File name prefix for output files. +---------------------------------------------------------------------------------------- +*/ + +process { + + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + +} diff --git a/hello-nf-core/solutions/core-hello-end/conf/test.config b/hello-nf-core/solutions/core-hello-end/conf/test.config new file mode 100644 index 000000000..d02b0fe46 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/conf/test.config @@ -0,0 +1,30 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running minimal tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a fast and simple pipeline test. + + Use as follows: + nextflow run core/hello -profile test, --outdir + +---------------------------------------------------------------------------------------- +*/ + +process { + resourceLimits = [ + cpus: 1, + memory: '1.GB' + ] +} + +params { + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' + + // Input data + input = 'core-hello/assets/greetings.csv' + + // Other parameters + batch = 'test' + character = 'tux' +} diff --git a/hello-nf-core/solutions/core-hello-end/docs/README.md b/hello-nf-core/solutions/core-hello-end/docs/README.md new file mode 100644 index 000000000..593e4a39e --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/docs/README.md @@ -0,0 +1,8 @@ +# core/hello: Documentation + +The core/hello documentation is split into the following pages: + +- [Usage](usage.md) + - An overview of how the pipeline works, how to run it and a description of all of the different command-line flags. +- [Output](output.md) + - An overview of the different results produced by the pipeline and how to interpret them. diff --git a/hello-nf-core/solutions/core-hello-end/docs/output.md b/hello-nf-core/solutions/core-hello-end/docs/output.md new file mode 100644 index 000000000..7a49820c8 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/docs/output.md @@ -0,0 +1,29 @@ +# core/hello: Output + +## Introduction + +This document describes the output produced by the pipeline. + +The directories listed below will be created in the results directory after the pipeline has finished. All paths are relative to the top-level results directory. + + + +## Pipeline overview + +The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: + +- [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution + +### Pipeline information + +
+Output files + +- `pipeline_info/` + - Reports generated by Nextflow: `execution_report.html`, `execution_timeline.html`, `execution_trace.txt` and `pipeline_dag.dot`/`pipeline_dag.svg`. + - Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. + - Parameters used by the pipeline run: `params.json`. + +
+ +[Nextflow](https://www.nextflow.io/docs/latest/tracing.html) provides excellent functionality for generating various reports relevant to the running and execution of the pipeline. This will allow you to troubleshoot errors with the running of the pipeline, and also provide you with other information such as launch commands, run times and resource usage. diff --git a/hello-nf-core/solutions/core-hello-end/docs/usage.md b/hello-nf-core/solutions/core-hello-end/docs/usage.md new file mode 100644 index 000000000..78b55f9af --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/docs/usage.md @@ -0,0 +1,211 @@ +# core/hello: Usage + +> _Documentation of pipeline parameters is generated automatically from the pipeline schema and can no longer be found in markdown files._ + +## Introduction + + + +## Samplesheet input + +You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. + +```bash +--input '[path to samplesheet file]' +``` + +### Multiple runs of the same sample + +The `sample` identifiers have to be the same when you have re-sequenced the same sample more than once e.g. to increase sequencing depth. The pipeline will concatenate the raw reads before performing any downstream analysis. Below is an example for the same sample sequenced across 3 lanes: + +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L003_R1_001.fastq.gz,AEG588A1_S1_L003_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L004_R1_001.fastq.gz,AEG588A1_S1_L004_R2_001.fastq.gz +``` + +### Full samplesheet + +The pipeline will auto-detect whether a sample is single- or paired-end using the information provided in the samplesheet. The samplesheet can have as many columns as you desire, however, there is a strict requirement for the first 3 columns to match those defined in the table below. + +A final samplesheet file consisting of both single- and paired-end data may look something like the one below. This is for 6 samples, where `TREATMENT_REP3` has been sequenced twice. + +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP2,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz +CONTROL_REP3,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz +TREATMENT_REP1,AEG588A4_S4_L003_R1_001.fastq.gz, +TREATMENT_REP2,AEG588A5_S5_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L004_R1_001.fastq.gz, +``` + +| Column | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | +| `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | +| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | + +An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. + +## Running the pipeline + +The typical command for running the pipeline is as follows: + +```bash +nextflow run core/hello --input ./samplesheet.csv --outdir ./results -profile docker +``` + +This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. + +Note that the pipeline will create the following files in your working directory: + +```bash +work # Directory containing the nextflow working files + # Finished results in specified location (defined with --outdir) +.nextflow_log # Log file from Nextflow +# Other nextflow hidden files, eg. history of pipeline runs and old logs. +``` + +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 `. + +> [!WARNING] +> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). + +The above pipeline run specified with a params file in yaml format: + +```bash +nextflow run core/hello -profile docker -params-file params.yaml +``` + +with: + +```yaml title="params.yaml" +input: './samplesheet.csv' +outdir: './results/' +<...> +``` + +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: + +```bash +nextflow pull core/hello +``` + +### Reproducibility + +It is a good idea to specify the 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 [core/hello releases page](https://github.com/core/hello/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. + +To further assist in reproducibility, you can use share and reuse [parameter files](#running-the-pipeline) to repeat pipeline runs with the same settings without having to write out a command with every single parameter. + +> [!TIP] +> If you wish to share such profile (such as upload as supplementary material for academic publications), make sure to NOT include cluster specific paths to files, nor institutional specific profiles. + +## Core Nextflow arguments + +> [!NOTE] +> These options are part of Nextflow and use a _single_ hyphen (pipeline parameters use a double-hyphen) + +### `-profile` + +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, Apptainer, Conda) - see below. + +> [!IMPORTANT] +> We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, however when this is not possible, Conda is also supported. + +The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to check if your system is supported, please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). + +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, since it can lead to different results on different machines dependent on the computer environment. + +- `test` + - A profile with a complete configuration for automated testing + - Includes links to test data so needs no other parameters +- `docker` + - A generic configuration profile to be used with [Docker](https://docker.com/) +- `singularity` + - A generic configuration profile to be used with [Singularity](https://sylabs.io/docs/) +- `podman` + - A generic configuration profile to be used with [Podman](https://podman.io/) +- `shifter` + - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) +- `charliecloud` + - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) +- `apptainer` + - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) +- `wave` + - A generic configuration profile to enable [Wave](https://seqera.io/wave/) containers. Use together with one of the above (requires Nextflow ` 24.03.0-edge` or later). +- `conda` + - A generic configuration profile to be used with [Conda](https://conda.io/docs/). Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker, Singularity, Podman, Shifter, Charliecloud, or Apptainer. + +### `-resume` + +Specify this when restarting a pipeline. Nextflow will use cached results from any pipeline steps where the inputs are the same, continuing from where it got to previously. For input to be considered the same, not only the names must be identical but the files' contents as well. For more info about this parameter, see [this blog post](https://www.nextflow.io/blog/2019/demystifying-nextflow-resume.html). + +You can also supply a run name to resume a specific run: `-resume [run-name]`. Use the `nextflow log` command to show previous run names. + +### `-c` + +Specify the path to a specific config file (this is a core Nextflow command). See the [nf-core website documentation](https://nf-co.re/usage/configuration) for more information. + +## Custom configuration + +### Resource requests + +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 pipeline steps, 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 resources request (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. + +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. + +### Custom Containers + +In some cases, you may wish to change the container or conda environment used by a pipeline steps 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. + +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. + +### Custom Tool Arguments + +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. + +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 + +In most cases, you will only need to create a custom config as a one-off but if you and others within your organisation are likely to be running nf-core pipelines regularly and need to use the same settings regularly it may be a good idea to request that your custom config file is uploaded to the `nf-core/configs` git repository. Before you do this please can you test that the config file works with your pipeline of choice using the `-c` parameter. You can then create a pull request to the `nf-core/configs` repository with the addition of your config file, associated documentation file (see examples in [`nf-core/configs/docs`](https://github.com/nf-core/configs/tree/master/docs)), and amending [`nfcore_custom.config`](https://github.com/nf-core/configs/blob/master/nfcore_custom.config) to include your custom profile. + +See the main [Nextflow documentation](https://www.nextflow.io/docs/latest/config.html) for more information about creating your own configuration files. + +If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack) on the [`#configs` channel](https://nfcore.slack.com/channels/configs). + +## Running in the background + +Nextflow handles job submissions and supervises the running jobs. The Nextflow process must run until the pipeline is finished. + +The Nextflow `-bg` flag launches Nextflow in the background, detached from your terminal so that the workflow does not stop if you log out of your session. The logs are saved to a file. + +Alternatively, you can use `screen` / `tmux` or similar tool to create a detached session which you can log back into at a later time. +Some HPC setups also allow you to run nextflow within a cluster job submitted your job scheduler (from where it submits more jobs). + +## Nextflow memory requirements + +In some cases, the Nextflow Java virtual machines can start to request a large amount of memory. +We recommend adding the following line to your environment to limit this (typically in `~/.bashrc` or `~./bash_profile`): + +```bash +NXF_OPTS='-Xms1g -Xmx4g' +``` diff --git a/hello-nf-core/solutions/core-hello-end/main.nf b/hello-nf-core/solutions/core-hello-end/main.nf new file mode 100644 index 000000000..f72a23666 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/main.nf @@ -0,0 +1,82 @@ +#!/usr/bin/env nextflow +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Github : https://github.com/core/hello +---------------------------------------------------------------------------------------- +*/ + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { HELLO } from './workflows/hello' +include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_hello_pipeline' +include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_hello_pipeline' +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + NAMED WORKFLOWS FOR PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// WORKFLOW: Run main analysis pipeline depending on type of input +// +workflow CORE_HELLO { + + take: + samplesheet // channel: samplesheet read in from --input + + main: + + // + // WORKFLOW: Run pipeline + // + HELLO ( + samplesheet + ) +} +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow { + + main: + // + // SUBWORKFLOW: Run initialisation tasks + // + PIPELINE_INITIALISATION ( + params.version, + params.validate_params, + params.monochrome_logs, + args, + params.outdir, + params.input + ) + + // + // WORKFLOW: Run main workflow + // + CORE_HELLO ( + PIPELINE_INITIALISATION.out.samplesheet + ) + // + // SUBWORKFLOW: Run completion tasks + // + PIPELINE_COMPLETION ( + params.outdir, + params.monochrome_logs, + ) +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ diff --git a/hello-nf-core/solutions/core-hello-end/modules.json b/hello-nf-core/solutions/core-hello-end/modules.json new file mode 100644 index 000000000..e36947ce0 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/modules.json @@ -0,0 +1,30 @@ +{ + "name": "core/hello", + "homePage": "https://github.com/core/hello", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": {} + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "c2b22d85f30a706a3073387f30380704fcae013b", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "51ae5406a030d4da1e49e4dab49756844fdd6c7a", + "installed_by": ["subworkflows"] + }, + "utils_nfschema_plugin": { + "branch": "master", + "git_sha": "2fd2cd6d0e7b273747f32e465fdc6bcc3ae0814e", + "installed_by": ["subworkflows"] + } + } + } + } + } +} diff --git a/hello-nf-core/solutions/core-hello-end/modules/local/collectGreetings.nf b/hello-nf-core/solutions/core-hello-end/modules/local/collectGreetings.nf new file mode 100644 index 000000000..849bba4b6 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/modules/local/collectGreetings.nf @@ -0,0 +1,21 @@ +/* + * Collect uppercase greetings into a single output file + */ +process collectGreetings { + + publishDir 'results', mode: 'copy' + + input: + path input_files + val batch_name + + output: + path "COLLECTED-${batch_name}-output.txt" , emit: outfile + val count_greetings , emit: count + + script: + count_greetings = input_files.size() + """ + cat ${input_files} > 'COLLECTED-${batch_name}-output.txt' + """ +} diff --git a/hello-nf-core/solutions/core-hello-end/modules/local/convertToUpper.nf b/hello-nf-core/solutions/core-hello-end/modules/local/convertToUpper.nf new file mode 100644 index 000000000..b2689e8e9 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/modules/local/convertToUpper.nf @@ -0,0 +1,20 @@ +#!/usr/bin/env nextflow + +/* + * Use a text replacement tool to convert the greeting to uppercase + */ +process convertToUpper { + + publishDir 'results', mode: 'copy' + + input: + path input_file + + output: + path "UPPER-${input_file}" + + script: + """ + cat '$input_file' | tr '[a-z]' '[A-Z]' > 'UPPER-${input_file}' + """ +} diff --git a/hello-nf-core/solutions/core-hello-end/modules/local/cowpy.nf b/hello-nf-core/solutions/core-hello-end/modules/local/cowpy.nf new file mode 100644 index 000000000..2bc7ed612 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/modules/local/cowpy.nf @@ -0,0 +1,22 @@ +#!/usr/bin/env nextflow + +// Generate ASCII art with cowpy (https://github.com/jeffbuttars/cowpy) +process cowpy { + + publishDir 'results', mode: 'copy' + + container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' + conda 'conda-forge::cowpy==1.1.5' + + input: + path input_file + val character + + output: + path "cowpy-${input_file}" + + script: + """ + cat $input_file | cowpy -c "$character" > cowpy-${input_file} + """ +} diff --git a/hello-nf-core/solutions/core-hello-end/modules/local/sayHello.nf b/hello-nf-core/solutions/core-hello-end/modules/local/sayHello.nf new file mode 100644 index 000000000..6005ad54c --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/modules/local/sayHello.nf @@ -0,0 +1,20 @@ +#!/usr/bin/env nextflow + +/* + * Use echo to print 'Hello World!' to a file + */ +process sayHello { + + publishDir 'results', mode: 'copy' + + input: + val greeting + + output: + path "${greeting}-output.txt" + + script: + """ + echo '$greeting' > '$greeting-output.txt' + """ +} diff --git a/hello-nf-core/solutions/core-hello-end/nextflow.config b/hello-nf-core/solutions/core-hello-end/nextflow.config new file mode 100644 index 000000000..d633adb98 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/nextflow.config @@ -0,0 +1,238 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello Nextflow config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Default config options for all compute environments +---------------------------------------------------------------------------------------- +*/ + +// Global default params, used in configs +params { + + // TODO nf-core: Specify your pipeline's command line flags + // Input options + input = null + + // Boilerplate options + outdir = null + publish_dir_mode = 'copy' + monochrome_logs = false + help = false + help_full = false + show_hidden = false + version = false + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss')// Config options + config_profile_name = null + config_profile_description = null + + custom_config_version = 'master' + custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" + config_profile_contact = null + config_profile_url = null + + // Schema validation default options + validate_params = true +} + +// Load base.config by default for all pipelines +includeConfig 'conf/base.config' + +profiles { + debug { + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false + nextflow.enable.configProcessNamesValidation = true + } + conda { + conda.enabled = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + conda.channels = ['conda-forge', 'bioconda'] + apptainer.enabled = false + } + mamba { + conda.enabled = true + conda.useMamba = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + docker { + docker.enabled = true + conda.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + docker.runOptions = '-u $(id -u):$(id -g)' + } + arm { + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + } + singularity { + singularity.enabled = true + singularity.autoMounts = true + conda.enabled = false + docker.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + podman { + podman.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + shifter { + shifter.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + charliecloud { + charliecloud.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + apptainer.enabled = false + } + apptainer { + apptainer.enabled = true + apptainer.autoMounts = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + } + wave { + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + test { includeConfig 'conf/test.config' } + test_full { includeConfig 'conf/test_full.config' } +} + +// Load nf-core custom profiles from different Institutions +includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" + +// Load core/hello custom profiles from different institutions. +// TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs +// includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/hello.config" : "/dev/null" + +// Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile +// Will not be used unless Apptainer / Docker / Podman / Charliecloud / 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' +charliecloud.registry = 'quay.io' + + + +// 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. + +env { + PYTHONNOUSERSITE = 1 + R_PROFILE_USER = "/.Rprofile" + R_ENVIRON_USER = "/.Renviron" + JULIA_DEPOT_PATH = "/usr/local/share/julia" +} + +// Set bash options +process.shell = [ + "bash", + "-C", // No clobber - prevent output redirection from overwriting files. + "-e", // Exit if a tool returns a non-zero status/exit code + "-u", // Treat unset variables and parameters as an error + "-o", // Returns the status of the last command to exit.. + "pipefail" // ..with a non-zero status or zero if all successfully execute +] + +// Disable process selector warnings by default. Use debug profile to enable warnings. +nextflow.enable.configProcessNamesValidation = false + +timeline { + enabled = true + file = "${params.outdir}/pipeline_info/execution_timeline_${params.trace_report_suffix}.html" +} +report { + enabled = true + file = "${params.outdir}/pipeline_info/execution_report_${params.trace_report_suffix}.html" +} +trace { + enabled = true + file = "${params.outdir}/pipeline_info/execution_trace_${params.trace_report_suffix}.txt" +} +dag { + enabled = true + file = "${params.outdir}/pipeline_info/pipeline_dag_${params.trace_report_suffix}.html" +} + +manifest { + name = 'core/hello' + author = """GG""" // The author field is deprecated from Nextflow version 24.10.0, use contributors instead + contributors = [ + // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 + [ + name: 'GG', + affiliation: '', + email: '', + github: '', + contribution: [], // List of contribution types ('author', 'maintainer' or 'contributor') + orcid: '' + ], + ] + homePage = 'https://github.com/core/hello' + description = """basic nf-core style version of Hello Nextflow""" + mainScript = 'main.nf' + defaultBranch = 'main' + nextflowVersion = '!>=24.04.2' + version = '1.0.0dev' + doi = '' +} + +// Nextflow plugins +plugins { + id 'nf-schema@2.2.0' // Validation of pipeline parameters and creation of an input channel from a sample sheet +} + +validation { + defaultIgnoreParams = ["genomes"] + monochromeLogs = params.monochrome_logs + help { + enabled = true + command = "nextflow run core/hello -profile --input samplesheet.csv --outdir " + fullParameter = "help_full" + showHiddenParameter = "show_hidden" + } +} + +// Load modules.config for DSL2 module specific options +includeConfig 'conf/modules.config' diff --git a/hello-nf-core/solutions/core-hello-end/nextflow_schema.json b/hello-nf-core/solutions/core-hello-end/nextflow_schema.json new file mode 100644 index 000000000..5ee5ec357 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/nextflow_schema.json @@ -0,0 +1,151 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/nextflow_schema.json", + "title": "core/hello pipeline parameters", + "description": "basic nf-core style version of Hello Nextflow", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["input", "outdir"], + "properties": { + "input": { + "type": "string", + "format": "file-path", + "exists": true, + "schema": "assets/schema_input.json", + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "Path to comma-separated file containing information about the samples in the experiment.", + "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.", + "fa_icon": "fas fa-file-csv" + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + } + } + }, + "institutional_config_options": { + "title": "Institutional config options", + "type": "object", + "fa_icon": "fas fa-university", + "description": "Parameters used to describe centralised config profiles. These should not be edited.", + "help_text": "The centralised nf-core configuration profiles use a handful of pipeline parameters to describe themselves. This information is then printed to the Nextflow log when you run a pipeline. You should not need to change these values when you run a pipeline.", + "properties": { + "custom_config_version": { + "type": "string", + "description": "Git commit id for Institutional configs.", + "default": "master", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "custom_config_base": { + "type": "string", + "description": "Base directory for Institutional configs.", + "default": "https://raw.githubusercontent.com/nf-core/configs/master", + "hidden": true, + "help_text": "If you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.", + "fa_icon": "fas fa-users-cog" + }, + "config_profile_name": { + "type": "string", + "description": "Institutional config name.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_description": { + "type": "string", + "description": "Institutional config description.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_contact": { + "type": "string", + "description": "Institutional config contact information.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_url": { + "type": "string", + "description": "Institutional config URL link.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Do not use coloured log outputs.", + "fa_icon": "fas fa-palette", + "hidden": true + }, + "validate_params": { + "type": "boolean", + "description": "Boolean whether to validate parameters against the schema at runtime", + "default": true, + "fa_icon": "fas fa-check-square", + "hidden": true + }, + "pipelines_testdata_base_path": { + "type": "string", + "fa_icon": "far fa-check-circle", + "description": "Base URL or local path to location of pipeline test dataset files", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", + "hidden": true + }, + "trace_report_suffix": { + "type": "string", + "fa_icon": "far calendar", + "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", + "hidden": true + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/institutional_config_options" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/local/utils_nfcore_hello_pipeline/main.nf b/hello-nf-core/solutions/core-hello-end/subworkflows/local/utils_nfcore_hello_pipeline/main.nf new file mode 100644 index 000000000..53ba38fae --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/local/utils_nfcore_hello_pipeline/main.nf @@ -0,0 +1,123 @@ +// +// Subworkflow with functionality specific to the core/hello pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { samplesheetToList } from 'plugin/nf-schema' +include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW TO INITIALISE PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_INITIALISATION { + + take: + version // boolean: Display version and exit + validate_params // boolean: Boolean whether to validate parameters against the schema at runtime + monochrome_logs // boolean: Do not use coloured log outputs + nextflow_cli_args // array: List of positional nextflow CLI args + outdir // string: The output directory where the results will be saved + input // string: Path to input samplesheet + + main: + + ch_versions = Channel.empty() + + // + // Print version and exit if required and dump pipeline parameters to JSON file + // + UTILS_NEXTFLOW_PIPELINE ( + version, + true, + outdir, + workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 + ) + + // + // Validate parameters and generate parameter summary to stdout + // + UTILS_NFSCHEMA_PLUGIN ( + workflow, + validate_params, + null + ) + + // + // Check config provided to the pipeline + // + UTILS_NFCORE_PIPELINE ( + nextflow_cli_args + ) + + // + // Create channel from input file provided through params.input + // + ch_samplesheet = Channel.fromPath(params.input) + .splitCsv() + .map { line -> line[0] } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW FOR PIPELINE COMPLETION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_COMPLETION { + + take: + outdir // path: Path to output directory where results will be published + monochrome_logs // boolean: Disable ANSI colour codes in log output + + main: + summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + + // + // Completion email and summary + // + workflow.onComplete { + + completionSummary(monochrome_logs) + } + + workflow.onError { + log.error "Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting" + } +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Validate channels from input samplesheet +// +def validateInputSamplesheet(input) { + def (metas, fastqs) = input[1..2] + + // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end + def endedness_ok = metas.collect{ meta -> meta.single_end }.unique().size == 1 + if (!endedness_ok) { + error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") + } + + return [ metas[0], fastqs ] +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/main.nf new file mode 100644 index 000000000..d6e593e85 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/main.nf @@ -0,0 +1,126 @@ +// +// Subworkflow with functionality that may be useful for any Nextflow pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NEXTFLOW_PIPELINE { + take: + print_version // boolean: print version + dump_parameters // boolean: dump parameters + outdir // path: base directory used to publish pipeline results + check_conda_channels // boolean: check conda channels + + main: + + // + // Print workflow version and exit on --version + // + if (print_version) { + log.info("${workflow.manifest.name} ${getWorkflowVersion()}") + System.exit(0) + } + + // + // Dump pipeline parameters to a JSON file + // + if (dump_parameters && outdir) { + dumpParametersToJSON(outdir) + } + + // + // When running with Conda, warn if channels have not been set-up appropriately + // + if (check_conda_channels) { + checkCondaChannels() + } + + emit: + dummy_emit = true +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Generate version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Dump pipeline parameters to a JSON file +// +def dumpParametersToJSON(outdir) { + def timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + def filename = "params_${timestamp}.json" + def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") + def jsonStr = groovy.json.JsonOutput.toJson(params) + temp_pf.text = groovy.json.JsonOutput.prettyPrint(jsonStr) + + nextflow.extension.FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") + temp_pf.delete() +} + +// +// When running with -profile conda, warn if channels have not been set-up appropriately +// +def checkCondaChannels() { + def parser = new org.yaml.snakeyaml.Yaml() + def channels = [] + try { + def config = parser.load("conda config --show channels".execute().text) + channels = config.channels + } + catch (NullPointerException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + catch (IOException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + + // Check that all channels are present + // This channel list is ordered by required channel priority. + def required_channels_in_order = ['conda-forge', 'bioconda'] + def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean + + // Check that they are in the right order + def channel_priority_violation = required_channels_in_order != channels.findAll { ch -> ch in required_channels_in_order } + + if (channels_missing | channel_priority_violation) { + log.warn """\ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + There is a problem with your Conda configuration! + You will need to set-up the conda-forge and bioconda channels correctly. + Please refer to https://bioconda.github.io/ + The observed channel order is + ${channels} + but the following channel order is required: + ${required_channels_in_order} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + """.stripIndent(true) + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml new file mode 100644 index 000000000..e5c3a0a82 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NEXTFLOW_PIPELINE" +description: Subworkflow with functionality that may be useful for any Nextflow pipeline +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - print_version: + type: boolean + description: | + Print the version of the pipeline and exit + - dump_parameters: + type: boolean + description: | + Dump the parameters of the pipeline to a JSON file + - output_directory: + type: directory + description: Path to output dir to write JSON file to. + pattern: "results/" + - check_conda_channel: + type: boolean + description: | + Check if the conda channel priority is correct. +output: + - dummy_emit: + type: boolean + description: | + Dummy emit to make nf-core subworkflows lint happy +authors: + - "@adamrtalbot" + - "@drpatelh" +maintainers: + - "@adamrtalbot" + - "@drpatelh" + - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test new file mode 100644 index 000000000..68718e4f5 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test @@ -0,0 +1,54 @@ + +nextflow_function { + + name "Test Functions" + script "subworkflows/nf-core/utils_nextflow_pipeline/main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Test Function getWorkflowVersion") { + + function "getWorkflowVersion" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function dumpParametersToJSON") { + + function "dumpParametersToJSON" + + when { + function { + """ + // define inputs of the function here. Example: + input[0] = "$outputDir" + """.stripIndent() + } + } + + then { + assertAll( + { assert function.success } + ) + } + } + + test("Test Function checkCondaChannels") { + + function "checkCondaChannels" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 000000000..846287c41 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,20 @@ +{ + "Test Function getWorkflowVersion": { + "content": [ + "v9.9.9" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:05.308243" + }, + "Test Function checkCondaChannels": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:12.425833" + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test new file mode 100644 index 000000000..02dbf094c --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,113 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NEXTFLOW_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + workflow "UTILS_NEXTFLOW_PIPELINE" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Should run no inputs") { + + when { + workflow { + """ + print_version = false + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should print version") { + + when { + workflow { + """ + print_version = true + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + expect { + with(workflow) { + assert success + assert "nextflow_workflow v9.9.9" in stdout + } + } + } + } + + test("Should dump params") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = 'results' + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should not create params JSON if no output directory") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = null + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config new file mode 100644 index 000000000..a09572e5b --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml new file mode 100644 index 000000000..f84761125 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml @@ -0,0 +1,2 @@ +subworkflows/utils_nextflow_pipeline: + - subworkflows/nf-core/utils_nextflow_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/main.nf new file mode 100644 index 000000000..bfd258760 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -0,0 +1,419 @@ +// +// Subworkflow with utility functions specific to the nf-core pipeline template +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NFCORE_PIPELINE { + take: + nextflow_cli_args + + main: + valid_config = checkConfigProvided() + checkProfileProvided(nextflow_cli_args) + + emit: + valid_config +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Warn if a -profile or Nextflow config has not been provided to run the pipeline +// +def checkConfigProvided() { + def valid_config = true as Boolean + if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { + log.warn( + "[${workflow.manifest.name}] You are attempting to run the pipeline without any custom configuration!\n\n" + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + "Please refer to the quick start section and usage docs for the pipeline.\n " + ) + valid_config = false + } + return valid_config +} + +// +// Exit pipeline if --profile contains spaces +// +def checkProfileProvided(nextflow_cli_args) { + if (workflow.profile.endsWith(',')) { + error( + "The `-profile` option cannot end with a trailing comma, please remove it and re-run the pipeline!\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } + if (nextflow_cli_args[0]) { + log.warn( + "nf-core pipelines do not accept positional arguments. The positional argument `${nextflow_cli_args[0]}` has been detected.\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } +} + +// +// Generate workflow version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Get software versions for pipeline +// +def processVersionsFromYAML(yaml_file) { + def yaml = new org.yaml.snakeyaml.Yaml() + def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } + return yaml.dumpAsMap(versions).trim() +} + +// +// Get workflow version for pipeline +// +def workflowVersionToYAML() { + return """ + Workflow: + ${workflow.manifest.name}: ${getWorkflowVersion()} + Nextflow: ${workflow.nextflow.version} + """.stripIndent().trim() +} + +// +// Get channel of software versions used in pipeline in YAML format +// +def softwareVersionsToYAML(ch_versions) { + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) +} + +// +// Get workflow summary for MultiQC +// +def paramsSummaryMultiqc(summary_params) { + def summary_section = '' + summary_params + .keySet() + .each { group -> + def group_params = summary_params.get(group) + // This gets the parameters of that particular group + if (group_params) { + summary_section += "

${group}

\n" + summary_section += "
\n" + group_params + .keySet() + .sort() + .each { param -> + summary_section += "
${param}
${group_params.get(param) ?: 'N/A'}
\n" + } + summary_section += "
\n" + } + } + + def yaml_file_text = "id: '${workflow.manifest.name.replace('/', '-')}-summary'\n" as String + yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" + yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" + yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" + yaml_file_text += "plot_type: 'html'\n" + yaml_file_text += "data: |\n" + yaml_file_text += "${summary_section}" + + return yaml_file_text +} + +// +// ANSII colours used for terminal logging +// +def logColours(monochrome_logs=true) { + def colorcodes = [:] as Map + + // Reset / Meta + colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" + colorcodes['bold'] = monochrome_logs ? '' : "\033[1m" + colorcodes['dim'] = monochrome_logs ? '' : "\033[2m" + colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m" + colorcodes['blink'] = monochrome_logs ? '' : "\033[5m" + colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m" + colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" + + // Regular Colors + colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" + colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" + colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" + colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" + colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" + colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" + colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" + colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" + + // Bold + colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" + colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" + colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" + colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" + colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" + colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" + colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" + colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" + + // Underline + colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" + colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" + colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" + colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" + colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" + colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" + colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" + colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" + + // High Intensity + colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" + colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" + colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" + colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" + colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" + colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" + colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" + colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" + + // Bold High Intensity + colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" + colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" + colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" + colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" + colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" + colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" + colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" + colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" + + return colorcodes +} + +// Return a single report from an object that may be a Path or List +// +def getSingleReport(multiqc_reports) { + if (multiqc_reports instanceof Path) { + return multiqc_reports + } else if (multiqc_reports instanceof List) { + if (multiqc_reports.size() == 0) { + log.warn("[${workflow.manifest.name}] No reports found from process 'MULTIQC'") + return null + } else if (multiqc_reports.size() == 1) { + return multiqc_reports.first() + } else { + log.warn("[${workflow.manifest.name}] Found multiple reports from process 'MULTIQC', will use only one") + return multiqc_reports.first() + } + } else { + return null + } +} + +// +// Construct and send completion email +// +def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs=true, multiqc_report=null) { + + // Set up the e-mail variables + def subject = "[${workflow.manifest.name}] Successful: ${workflow.runName}" + if (!workflow.success) { + subject = "[${workflow.manifest.name}] FAILED: ${workflow.runName}" + } + + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['Date Started'] = workflow.start + misc_fields['Date Completed'] = workflow.complete + misc_fields['Pipeline script file path'] = workflow.scriptFile + misc_fields['Pipeline script hash ID'] = workflow.scriptId + if (workflow.repository) { + misc_fields['Pipeline repository Git URL'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['Pipeline repository Git Commit'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['Pipeline Git branch/tag'] = workflow.revision + } + misc_fields['Nextflow Version'] = workflow.nextflow.version + misc_fields['Nextflow Build'] = workflow.nextflow.build + misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp + + def email_fields = [:] + email_fields['version'] = getWorkflowVersion() + email_fields['runName'] = workflow.runName + email_fields['success'] = workflow.success + email_fields['dateComplete'] = workflow.complete + email_fields['duration'] = workflow.duration + email_fields['exitStatus'] = workflow.exitStatus + email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + email_fields['errorReport'] = (workflow.errorReport ?: 'None') + email_fields['commandLine'] = workflow.commandLine + email_fields['projectDir'] = workflow.projectDir + email_fields['summary'] = summary << misc_fields + + // On success try attach the multiqc report + def mqc_report = getSingleReport(multiqc_report) + + // Check if we are only sending emails on failure + def email_address = email + if (!email && email_on_fail && !workflow.success) { + email_address = email_on_fail + } + + // Render the TXT template + def engine = new groovy.text.GStringTemplateEngine() + def tf = new File("${workflow.projectDir}/assets/email_template.txt") + def txt_template = engine.createTemplate(tf).make(email_fields) + def email_txt = txt_template.toString() + + // Render the HTML template + def hf = new File("${workflow.projectDir}/assets/email_template.html") + def html_template = engine.createTemplate(hf).make(email_fields) + def email_html = html_template.toString() + + // Render the sendmail template + def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as MemoryUnit + def smail_fields = [email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "${workflow.projectDir}", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes()] + def sf = new File("${workflow.projectDir}/assets/sendmail_template.txt") + def sendmail_template = engine.createTemplate(sf).make(smail_fields) + def sendmail_html = sendmail_template.toString() + + // Send the HTML e-mail + def colors = logColours(monochrome_logs) as Map + if (email_address) { + try { + if (plaintext_email) { + new org.codehaus.groovy.GroovyException('Send plaintext e-mail, not HTML') + } + // Try to send HTML e-mail using sendmail + def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") + sendmail_tf.withWriter { w -> w << sendmail_html } + ['sendmail', '-t'].execute() << sendmail_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (sendmail)-") + } + catch (Exception msg) { + log.debug(msg.toString()) + log.debug("Trying with mail instead of sendmail") + // Catch failures and try with plaintext + def mail_cmd = ['mail', '-s', subject, '--content-type=text/html', email_address] + mail_cmd.execute() << email_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (mail)-") + } + } + + // Write summary e-mail HTML to a file + def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") + output_hf.withWriter { w -> w << email_html } + nextflow.extension.FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html") + output_hf.delete() + + // Write summary e-mail TXT to a file + def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") + output_tf.withWriter { w -> w << email_txt } + nextflow.extension.FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt") + output_tf.delete() +} + +// +// Print pipeline summary on completion +// +def completionSummary(monochrome_logs=true) { + def colors = logColours(monochrome_logs) as Map + if (workflow.success) { + if (workflow.stats.ignoredCount == 0) { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Pipeline completed successfully${colors.reset}-") + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-") + } + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.red} Pipeline completed with errors${colors.reset}-") + } +} + +// +// Construct and send a notification to a web server as JSON e.g. Microsoft Teams and Slack +// +def imNotification(summary_params, hook_url) { + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['start'] = workflow.start + misc_fields['complete'] = workflow.complete + misc_fields['scriptfile'] = workflow.scriptFile + misc_fields['scriptid'] = workflow.scriptId + if (workflow.repository) { + misc_fields['repository'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['commitid'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['revision'] = workflow.revision + } + misc_fields['nxf_version'] = workflow.nextflow.version + misc_fields['nxf_build'] = workflow.nextflow.build + misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp + + def msg_fields = [:] + msg_fields['version'] = getWorkflowVersion() + msg_fields['runName'] = workflow.runName + msg_fields['success'] = workflow.success + msg_fields['dateComplete'] = workflow.complete + msg_fields['duration'] = workflow.duration + msg_fields['exitStatus'] = workflow.exitStatus + msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + msg_fields['errorReport'] = (workflow.errorReport ?: 'None') + msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") + msg_fields['projectDir'] = workflow.projectDir + msg_fields['summary'] = summary << misc_fields + + // Render the JSON template + def engine = new groovy.text.GStringTemplateEngine() + // Different JSON depending on the service provider + // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format + def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" + def hf = new File("${workflow.projectDir}/assets/${json_path}") + def json_template = engine.createTemplate(hf).make(msg_fields) + def json_message = json_template.toString() + + // POST + def post = new URL(hook_url).openConnection() + post.setRequestMethod("POST") + post.setDoOutput(true) + post.setRequestProperty("Content-Type", "application/json") + post.getOutputStream().write(json_message.getBytes("UTF-8")) + def postRC = post.getResponseCode() + if (!postRC.equals(200)) { + log.warn(post.getErrorStream().getText()) + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml new file mode 100644 index 000000000..d08d24342 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NFCORE_PIPELINE" +description: Subworkflow with utility functions specific to the nf-core pipeline template +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - nextflow_cli_args: + type: list + description: | + Nextflow CLI positional arguments +output: + - success: + type: boolean + description: | + Dummy output to indicate success +authors: + - "@adamrtalbot" +maintainers: + - "@adamrtalbot" + - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test new file mode 100644 index 000000000..f117040cb --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test @@ -0,0 +1,126 @@ + +nextflow_function { + + name "Test Functions" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Test Function checkConfigProvided") { + + function "checkConfigProvided" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function checkProfileProvided") { + + function "checkProfileProvided" + + when { + function { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function without logColours") { + + function "logColours" + + when { + function { + """ + input[0] = true + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function with logColours") { + function "logColours" + + when { + function { + """ + input[0] = false + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function getSingleReport with a single file") { + function "getSingleReport" + + when { + function { + """ + input[0] = file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") } + ) + } + } + + test("Test Function getSingleReport with multiple files") { + function "getSingleReport" + + when { + function { + """ + input[0] = [ + file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/network.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/expression.tsv', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") }, + { assert !function.result.contains("network.tsv") }, + { assert !function.result.contains("expression.tsv") } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 000000000..b13b31121 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,136 @@ +{ + "Test Function checkProfileProvided": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:03.360873" + }, + "Test Function checkConfigProvided": { + "content": [ + true + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:59.729647" + }, + "Test Function without logColours": { + "content": [ + { + "reset": "", + "bold": "", + "dim": "", + "underlined": "", + "blink": "", + "reverse": "", + "hidden": "", + "black": "", + "red": "", + "green": "", + "yellow": "", + "blue": "", + "purple": "", + "cyan": "", + "white": "", + "bblack": "", + "bred": "", + "bgreen": "", + "byellow": "", + "bblue": "", + "bpurple": "", + "bcyan": "", + "bwhite": "", + "ublack": "", + "ured": "", + "ugreen": "", + "uyellow": "", + "ublue": "", + "upurple": "", + "ucyan": "", + "uwhite": "", + "iblack": "", + "ired": "", + "igreen": "", + "iyellow": "", + "iblue": "", + "ipurple": "", + "icyan": "", + "iwhite": "", + "biblack": "", + "bired": "", + "bigreen": "", + "biyellow": "", + "biblue": "", + "bipurple": "", + "bicyan": "", + "biwhite": "" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:17.969323" + }, + "Test Function with logColours": { + "content": [ + { + "reset": "\u001b[0m", + "bold": "\u001b[1m", + "dim": "\u001b[2m", + "underlined": "\u001b[4m", + "blink": "\u001b[5m", + "reverse": "\u001b[7m", + "hidden": "\u001b[8m", + "black": "\u001b[0;30m", + "red": "\u001b[0;31m", + "green": "\u001b[0;32m", + "yellow": "\u001b[0;33m", + "blue": "\u001b[0;34m", + "purple": "\u001b[0;35m", + "cyan": "\u001b[0;36m", + "white": "\u001b[0;37m", + "bblack": "\u001b[1;30m", + "bred": "\u001b[1;31m", + "bgreen": "\u001b[1;32m", + "byellow": "\u001b[1;33m", + "bblue": "\u001b[1;34m", + "bpurple": "\u001b[1;35m", + "bcyan": "\u001b[1;36m", + "bwhite": "\u001b[1;37m", + "ublack": "\u001b[4;30m", + "ured": "\u001b[4;31m", + "ugreen": "\u001b[4;32m", + "uyellow": "\u001b[4;33m", + "ublue": "\u001b[4;34m", + "upurple": "\u001b[4;35m", + "ucyan": "\u001b[4;36m", + "uwhite": "\u001b[4;37m", + "iblack": "\u001b[0;90m", + "ired": "\u001b[0;91m", + "igreen": "\u001b[0;92m", + "iyellow": "\u001b[0;93m", + "iblue": "\u001b[0;94m", + "ipurple": "\u001b[0;95m", + "icyan": "\u001b[0;96m", + "iwhite": "\u001b[0;97m", + "biblack": "\u001b[1;90m", + "bired": "\u001b[1;91m", + "bigreen": "\u001b[1;92m", + "biyellow": "\u001b[1;93m", + "biblue": "\u001b[1;94m", + "bipurple": "\u001b[1;95m", + "bicyan": "\u001b[1;96m", + "biwhite": "\u001b[1;97m" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:21.714424" + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test new file mode 100644 index 000000000..8940d32d1 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,29 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NFCORE_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + workflow "UTILS_NFCORE_PIPELINE" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Should run without failures") { + + when { + workflow { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap new file mode 100644 index 000000000..84ee1e1d1 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -0,0 +1,19 @@ +{ + "Should run without failures": { + "content": [ + { + "0": [ + true + ], + "valid_config": [ + true + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:25.726491" + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config new file mode 100644 index 000000000..d0a926bf6 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml new file mode 100644 index 000000000..ac8523c9a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml @@ -0,0 +1,2 @@ +subworkflows/utils_nfcore_pipeline: + - subworkflows/nf-core/utils_nfcore_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/main.nf new file mode 100644 index 000000000..93de2a524 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -0,0 +1,45 @@ +// +// Subworkflow that uses the nf-schema plugin to validate parameters and render the parameter summary +// + +include { paramsSummaryLog } from 'plugin/nf-schema' +include { validateParameters } from 'plugin/nf-schema' + +workflow UTILS_NFSCHEMA_PLUGIN { + + take: + input_workflow // workflow: the workflow object used by nf-schema to get metadata from the workflow + validate_params // boolean: validate the parameters + parameters_schema // string: path to the parameters JSON schema. + // this has to be the same as the schema given to `validation.parametersSchema` + // when this input is empty it will automatically use the configured schema or + // "${projectDir}/nextflow_schema.json" as default. This input should not be empty + // for meta pipelines + + main: + + // + // Print parameter summary to stdout. This will display the parameters + // that differ from the default given in the JSON schema + // + if(parameters_schema) { + log.info paramsSummaryLog(input_workflow, parameters_schema:parameters_schema) + } else { + log.info paramsSummaryLog(input_workflow) + } + + // + // Validate the parameters using nextflow_schema.json or the schema + // given via the validation.parametersSchema configuration option + // + if(validate_params) { + if(parameters_schema) { + validateParameters(parameters_schema:parameters_schema) + } else { + validateParameters() + } + } + + emit: + dummy_emit = true +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/meta.yml b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/meta.yml new file mode 100644 index 000000000..f7d9f0288 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/meta.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "utils_nfschema_plugin" +description: Run nf-schema to validate parameters and create a summary of changed parameters +keywords: + - validation + - JSON schema + - plugin + - parameters + - summary +components: [] +input: + - input_workflow: + type: object + description: | + The workflow object of the used pipeline. + This object contains meta data used to create the params summary log + - validate_params: + type: boolean + description: Validate the parameters and error if invalid. + - parameters_schema: + type: string + description: | + Path to the parameters JSON schema. + This has to be the same as the schema given to the `validation.parametersSchema` config + option. When this input is empty it will automatically use the configured schema or + "${projectDir}/nextflow_schema.json" as default. The schema should not be given in this way + for meta pipelines. +output: + - dummy_emit: + type: boolean + description: Dummy emit to make nf-core subworkflows lint happy +authors: + - "@nvnieuwk" +maintainers: + - "@nvnieuwk" diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test new file mode 100644 index 000000000..8fb301648 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -0,0 +1,117 @@ +nextflow_workflow { + + name "Test Subworkflow UTILS_NFSCHEMA_PLUGIN" + script "../main.nf" + workflow "UTILS_NFSCHEMA_PLUGIN" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/utils_nfschema_plugin" + tag "plugin/nf-schema" + + config "./nextflow.config" + + test("Should run nothing") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } + + test("Should run nothing - custom schema") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params - custom schema") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config new file mode 100644 index 000000000..478fb8a05 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -0,0 +1,8 @@ +plugins { + id "nf-schema@2.1.0" +} + +validation { + parametersSchema = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + monochromeLogs = true +} diff --git a/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json new file mode 100644 index 000000000..91e26fc4a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/./master/nextflow_schema.json", + "title": ". pipeline parameters", + "description": "", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["outdir"], + "properties": { + "validate_params": { + "type": "boolean", + "description": "Validate parameters?", + "default": true, + "hidden": true + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + }, + "test_data_base": { + "type": "string", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/modules", + "description": "Base for test data directory", + "hidden": true + }, + "test_data": { + "type": "string", + "description": "Fake test data param", + "hidden": true + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "help": { + "type": "boolean", + "description": "Display help text.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "logo": { + "type": "boolean", + "default": true, + "description": "Display nf-core logo in console output.", + "fa_icon": "fas fa-image", + "hidden": true + }, + "singularity_pull_docker_container": { + "type": "boolean", + "description": "Pull Singularity container from Docker?", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Use monochrome_logs", + "hidden": true + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/hello-nf-core/solutions/core-hello-end/workflows/hello.nf b/hello-nf-core/solutions/core-hello-end/workflows/hello.nf new file mode 100644 index 000000000..4ce0fae56 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-end/workflows/hello.nf @@ -0,0 +1,61 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +include { paramsSummaryMap } from 'plugin/nf-schema' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { sayHello } from '../modules/local/sayHello.nf' +include { convertToUpper } from '../modules/local/convertToUpper.nf' +include { collectGreetings } from '../modules/local/collectGreetings.nf' +include { cowpy } from '../modules/local/cowpy.nf' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow HELLO { + + take: + ch_samplesheet // channel: samplesheet read in from --input + main: + + // emit a greeting + sayHello(ch_samplesheet) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // collect all the greetings into one file + collectGreetings(convertToUpper.out.collect(), params.batch) + + // generate ASCII art of the greetings with cowpy + cowpy(collectGreetings.out.outfile, params.character) + + ch_versions = Channel.empty() + + // + // Collate and save software versions + // + softwareVersionsToYAML(ch_versions) + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'hello_software_' + 'versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } + + + emit: + final_result = cowpy.out + versions = ch_versions // channel: [ path(versions.yml) ] + +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ diff --git a/hello-nf-core/solutions/core-hello-start/README.md b/hello-nf-core/solutions/core-hello-start/README.md new file mode 100644 index 000000000..0a533c4c4 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/README.md @@ -0,0 +1,75 @@ +# core/hello + +## Introduction + +**core/hello** is a bioinformatics pipeline that ... + + + + + + +## 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. + + + +Now, you can run the pipeline using: + + + +```bash +nextflow run core/hello \ + -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/docs/usage/getting_started/configuration#custom-configuration-files). + +## Credits + +core/hello was originally written by GG. + +We thank the following people for their extensive assistance in the development of this pipeline: + + + +## Contributions and Support + +If you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md). + +## Citations + + + + +This pipeline uses code and infrastructure developed and maintained by the [nf-core](https://nf-co.re) community, reused here under the [MIT license](https://github.com/nf-core/tools/blob/main/LICENSE). + +> **The nf-core framework for community-curated bioinformatics pipelines.** +> +> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen. +> +> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x). diff --git a/hello-nf-core/solutions/core-hello-start/assets/samplesheet.csv b/hello-nf-core/solutions/core-hello-start/assets/samplesheet.csv new file mode 100644 index 000000000..5f653ab7b --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/assets/samplesheet.csv @@ -0,0 +1,3 @@ +sample,fastq_1,fastq_2 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, diff --git a/hello-nf-core/solutions/core-hello-start/assets/schema_input.json b/hello-nf-core/solutions/core-hello-start/assets/schema_input.json new file mode 100644 index 000000000..bc0261f32 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/assets/schema_input.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/assets/schema_input.json", + "title": "core/hello pipeline - params.input schema", + "description": "Schema for the file provided with params.input", + "type": "array", + "items": { + "type": "object", + "properties": { + "sample": { + "type": "string", + "pattern": "^\\S+$", + "errorMessage": "Sample name must be provided and cannot contain spaces", + "meta": ["id"] + }, + "fastq_1": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + }, + "fastq_2": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.f(ast)?q\\.gz$", + "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + } + }, + "required": ["sample", "fastq_1"] + } +} diff --git a/hello-nf-core/solutions/core-hello-start/conf/base.config b/hello-nf-core/solutions/core-hello-start/conf/base.config new file mode 100644 index 000000000..1abcd9876 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/conf/base.config @@ -0,0 +1,62 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello Nextflow base config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A 'blank slate' config file, appropriate for general use on most high performance + compute environments. Assumes that all software is installed and available on + the PATH. Runs in `local` mode - all jobs will be run on the logged in environment. +---------------------------------------------------------------------------------------- +*/ + +process { + + // TODO nf-core: Check the defaults for all processes + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + + errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } + maxRetries = 1 + maxErrors = '-1' + + // Process-specific resource requirements + // NOTE - Please try and reuse the labels below as much as possible. + // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. + // If possible, it would be nice to keep the same label naming convention when + // adding in your local modules too. + // TODO nf-core: Customise requirements for specific processes. + // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors + withLabel:process_single { + cpus = { 1 } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_low { + cpus = { 2 * task.attempt } + memory = { 12.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_medium { + cpus = { 6 * task.attempt } + memory = { 36.GB * task.attempt } + time = { 8.h * task.attempt } + } + withLabel:process_high { + cpus = { 12 * task.attempt } + memory = { 72.GB * task.attempt } + time = { 16.h * task.attempt } + } + withLabel:process_long { + time = { 20.h * task.attempt } + } + withLabel:process_high_memory { + memory = { 200.GB * task.attempt } + } + withLabel:error_ignore { + errorStrategy = 'ignore' + } + withLabel:error_retry { + errorStrategy = 'retry' + maxRetries = 2 + } +} diff --git a/hello-nf-core/solutions/core-hello-start/conf/modules.config b/hello-nf-core/solutions/core-hello-start/conf/modules.config new file mode 100644 index 000000000..e27fd2826 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/conf/modules.config @@ -0,0 +1,21 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Config file for defining DSL2 per module options and publishing paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Available keys to override module options: + ext.args = Additional arguments appended to command in module. + ext.args2 = Second set of arguments appended to command in module (multi-tool modules). + ext.args3 = Third set of arguments appended to command in module (multi-tool modules). + ext.prefix = File name prefix for output files. +---------------------------------------------------------------------------------------- +*/ + +process { + + publishDir = [ + path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + +} diff --git a/hello-nf-core/solutions/core-hello-start/conf/test.config b/hello-nf-core/solutions/core-hello-start/conf/test.config new file mode 100644 index 000000000..6e1641cef --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/conf/test.config @@ -0,0 +1,29 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running minimal tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a fast and simple pipeline test. + + Use as follows: + nextflow run core/hello -profile test, --outdir + +---------------------------------------------------------------------------------------- +*/ + +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} + +params { + config_profile_name = 'Test profile' + config_profile_description = 'Minimal test dataset to check pipeline function' + + // Input data + // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv' +} diff --git a/hello-nf-core/solutions/core-hello-start/conf/test_full.config b/hello-nf-core/solutions/core-hello-start/conf/test_full.config new file mode 100644 index 000000000..ceeaf40ca --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/conf/test_full.config @@ -0,0 +1,24 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running full-size tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a full size pipeline test. + + Use as follows: + nextflow run core/hello -profile test_full, --outdir + +---------------------------------------------------------------------------------------- +*/ + +params { + config_profile_name = 'Full test profile' + config_profile_description = 'Full test dataset to check pipeline function' + + // Input data for full size test + // TODO nf-core: Specify the paths to your full test data ( on nf-core/test-datasets or directly in repositories, e.g. SRA) + // TODO nf-core: Give any required params for the test so that command line flags are not needed + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_full_illumina_amplicon.csv' + + // Fasta references + fasta = params.pipelines_testdata_base_path + 'viralrecon/genome/NC_045512.2/GCF_009858895.2_ASM985889v3_genomic.200409.fna.gz' +} diff --git a/hello-nf-core/solutions/core-hello-start/docs/README.md b/hello-nf-core/solutions/core-hello-start/docs/README.md new file mode 100644 index 000000000..593e4a39e --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/docs/README.md @@ -0,0 +1,8 @@ +# core/hello: Documentation + +The core/hello documentation is split into the following pages: + +- [Usage](usage.md) + - An overview of how the pipeline works, how to run it and a description of all of the different command-line flags. +- [Output](output.md) + - An overview of the different results produced by the pipeline and how to interpret them. diff --git a/hello-nf-core/solutions/core-hello-start/docs/output.md b/hello-nf-core/solutions/core-hello-start/docs/output.md new file mode 100644 index 000000000..7a49820c8 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/docs/output.md @@ -0,0 +1,29 @@ +# core/hello: Output + +## Introduction + +This document describes the output produced by the pipeline. + +The directories listed below will be created in the results directory after the pipeline has finished. All paths are relative to the top-level results directory. + + + +## Pipeline overview + +The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: + +- [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution + +### Pipeline information + +
+Output files + +- `pipeline_info/` + - Reports generated by Nextflow: `execution_report.html`, `execution_timeline.html`, `execution_trace.txt` and `pipeline_dag.dot`/`pipeline_dag.svg`. + - Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. + - Parameters used by the pipeline run: `params.json`. + +
+ +[Nextflow](https://www.nextflow.io/docs/latest/tracing.html) provides excellent functionality for generating various reports relevant to the running and execution of the pipeline. This will allow you to troubleshoot errors with the running of the pipeline, and also provide you with other information such as launch commands, run times and resource usage. diff --git a/hello-nf-core/solutions/core-hello-start/docs/usage.md b/hello-nf-core/solutions/core-hello-start/docs/usage.md new file mode 100644 index 000000000..78b55f9af --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/docs/usage.md @@ -0,0 +1,211 @@ +# core/hello: Usage + +> _Documentation of pipeline parameters is generated automatically from the pipeline schema and can no longer be found in markdown files._ + +## Introduction + + + +## Samplesheet input + +You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. + +```bash +--input '[path to samplesheet file]' +``` + +### Multiple runs of the same sample + +The `sample` identifiers have to be the same when you have re-sequenced the same sample more than once e.g. to increase sequencing depth. The pipeline will concatenate the raw reads before performing any downstream analysis. Below is an example for the same sample sequenced across 3 lanes: + +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L003_R1_001.fastq.gz,AEG588A1_S1_L003_R2_001.fastq.gz +CONTROL_REP1,AEG588A1_S1_L004_R1_001.fastq.gz,AEG588A1_S1_L004_R2_001.fastq.gz +``` + +### Full samplesheet + +The pipeline will auto-detect whether a sample is single- or paired-end using the information provided in the samplesheet. The samplesheet can have as many columns as you desire, however, there is a strict requirement for the first 3 columns to match those defined in the table below. + +A final samplesheet file consisting of both single- and paired-end data may look something like the one below. This is for 6 samples, where `TREATMENT_REP3` has been sequenced twice. + +```csv title="samplesheet.csv" +sample,fastq_1,fastq_2 +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz +CONTROL_REP2,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz +CONTROL_REP3,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz +TREATMENT_REP1,AEG588A4_S4_L003_R1_001.fastq.gz, +TREATMENT_REP2,AEG588A5_S5_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L003_R1_001.fastq.gz, +TREATMENT_REP3,AEG588A6_S6_L004_R1_001.fastq.gz, +``` + +| Column | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | +| `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | +| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | + +An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. + +## Running the pipeline + +The typical command for running the pipeline is as follows: + +```bash +nextflow run core/hello --input ./samplesheet.csv --outdir ./results -profile docker +``` + +This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. + +Note that the pipeline will create the following files in your working directory: + +```bash +work # Directory containing the nextflow working files + # Finished results in specified location (defined with --outdir) +.nextflow_log # Log file from Nextflow +# Other nextflow hidden files, eg. history of pipeline runs and old logs. +``` + +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 `. + +> [!WARNING] +> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). + +The above pipeline run specified with a params file in yaml format: + +```bash +nextflow run core/hello -profile docker -params-file params.yaml +``` + +with: + +```yaml title="params.yaml" +input: './samplesheet.csv' +outdir: './results/' +<...> +``` + +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: + +```bash +nextflow pull core/hello +``` + +### Reproducibility + +It is a good idea to specify the 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 [core/hello releases page](https://github.com/core/hello/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. + +To further assist in reproducibility, you can use share and reuse [parameter files](#running-the-pipeline) to repeat pipeline runs with the same settings without having to write out a command with every single parameter. + +> [!TIP] +> If you wish to share such profile (such as upload as supplementary material for academic publications), make sure to NOT include cluster specific paths to files, nor institutional specific profiles. + +## Core Nextflow arguments + +> [!NOTE] +> These options are part of Nextflow and use a _single_ hyphen (pipeline parameters use a double-hyphen) + +### `-profile` + +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, Apptainer, Conda) - see below. + +> [!IMPORTANT] +> We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, however when this is not possible, Conda is also supported. + +The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to check if your system is supported, please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). + +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, since it can lead to different results on different machines dependent on the computer environment. + +- `test` + - A profile with a complete configuration for automated testing + - Includes links to test data so needs no other parameters +- `docker` + - A generic configuration profile to be used with [Docker](https://docker.com/) +- `singularity` + - A generic configuration profile to be used with [Singularity](https://sylabs.io/docs/) +- `podman` + - A generic configuration profile to be used with [Podman](https://podman.io/) +- `shifter` + - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) +- `charliecloud` + - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) +- `apptainer` + - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) +- `wave` + - A generic configuration profile to enable [Wave](https://seqera.io/wave/) containers. Use together with one of the above (requires Nextflow ` 24.03.0-edge` or later). +- `conda` + - A generic configuration profile to be used with [Conda](https://conda.io/docs/). Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker, Singularity, Podman, Shifter, Charliecloud, or Apptainer. + +### `-resume` + +Specify this when restarting a pipeline. Nextflow will use cached results from any pipeline steps where the inputs are the same, continuing from where it got to previously. For input to be considered the same, not only the names must be identical but the files' contents as well. For more info about this parameter, see [this blog post](https://www.nextflow.io/blog/2019/demystifying-nextflow-resume.html). + +You can also supply a run name to resume a specific run: `-resume [run-name]`. Use the `nextflow log` command to show previous run names. + +### `-c` + +Specify the path to a specific config file (this is a core Nextflow command). See the [nf-core website documentation](https://nf-co.re/usage/configuration) for more information. + +## Custom configuration + +### Resource requests + +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 pipeline steps, 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 resources request (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. + +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. + +### Custom Containers + +In some cases, you may wish to change the container or conda environment used by a pipeline steps 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. + +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. + +### Custom Tool Arguments + +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. + +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 + +In most cases, you will only need to create a custom config as a one-off but if you and others within your organisation are likely to be running nf-core pipelines regularly and need to use the same settings regularly it may be a good idea to request that your custom config file is uploaded to the `nf-core/configs` git repository. Before you do this please can you test that the config file works with your pipeline of choice using the `-c` parameter. You can then create a pull request to the `nf-core/configs` repository with the addition of your config file, associated documentation file (see examples in [`nf-core/configs/docs`](https://github.com/nf-core/configs/tree/master/docs)), and amending [`nfcore_custom.config`](https://github.com/nf-core/configs/blob/master/nfcore_custom.config) to include your custom profile. + +See the main [Nextflow documentation](https://www.nextflow.io/docs/latest/config.html) for more information about creating your own configuration files. + +If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack) on the [`#configs` channel](https://nfcore.slack.com/channels/configs). + +## Running in the background + +Nextflow handles job submissions and supervises the running jobs. The Nextflow process must run until the pipeline is finished. + +The Nextflow `-bg` flag launches Nextflow in the background, detached from your terminal so that the workflow does not stop if you log out of your session. The logs are saved to a file. + +Alternatively, you can use `screen` / `tmux` or similar tool to create a detached session which you can log back into at a later time. +Some HPC setups also allow you to run nextflow within a cluster job submitted your job scheduler (from where it submits more jobs). + +## Nextflow memory requirements + +In some cases, the Nextflow Java virtual machines can start to request a large amount of memory. +We recommend adding the following line to your environment to limit this (typically in `~/.bashrc` or `~./bash_profile`): + +```bash +NXF_OPTS='-Xms1g -Xmx4g' +``` diff --git a/hello-nf-core/solutions/core-hello-start/main.nf b/hello-nf-core/solutions/core-hello-start/main.nf new file mode 100644 index 000000000..f72a23666 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/main.nf @@ -0,0 +1,82 @@ +#!/usr/bin/env nextflow +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Github : https://github.com/core/hello +---------------------------------------------------------------------------------------- +*/ + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { HELLO } from './workflows/hello' +include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_hello_pipeline' +include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_hello_pipeline' +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + NAMED WORKFLOWS FOR PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// WORKFLOW: Run main analysis pipeline depending on type of input +// +workflow CORE_HELLO { + + take: + samplesheet // channel: samplesheet read in from --input + + main: + + // + // WORKFLOW: Run pipeline + // + HELLO ( + samplesheet + ) +} +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow { + + main: + // + // SUBWORKFLOW: Run initialisation tasks + // + PIPELINE_INITIALISATION ( + params.version, + params.validate_params, + params.monochrome_logs, + args, + params.outdir, + params.input + ) + + // + // WORKFLOW: Run main workflow + // + CORE_HELLO ( + PIPELINE_INITIALISATION.out.samplesheet + ) + // + // SUBWORKFLOW: Run completion tasks + // + PIPELINE_COMPLETION ( + params.outdir, + params.monochrome_logs, + ) +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ diff --git a/hello-nf-core/solutions/core-hello-start/modules.json b/hello-nf-core/solutions/core-hello-start/modules.json new file mode 100644 index 000000000..e36947ce0 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/modules.json @@ -0,0 +1,30 @@ +{ + "name": "core/hello", + "homePage": "https://github.com/core/hello", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": {} + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "c2b22d85f30a706a3073387f30380704fcae013b", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "51ae5406a030d4da1e49e4dab49756844fdd6c7a", + "installed_by": ["subworkflows"] + }, + "utils_nfschema_plugin": { + "branch": "master", + "git_sha": "2fd2cd6d0e7b273747f32e465fdc6bcc3ae0814e", + "installed_by": ["subworkflows"] + } + } + } + } + } +} diff --git a/hello-nf-core/solutions/core-hello-start/nextflow.config b/hello-nf-core/solutions/core-hello-start/nextflow.config new file mode 100644 index 000000000..d633adb98 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/nextflow.config @@ -0,0 +1,238 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + core/hello Nextflow config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Default config options for all compute environments +---------------------------------------------------------------------------------------- +*/ + +// Global default params, used in configs +params { + + // TODO nf-core: Specify your pipeline's command line flags + // Input options + input = null + + // Boilerplate options + outdir = null + publish_dir_mode = 'copy' + monochrome_logs = false + help = false + help_full = false + show_hidden = false + version = false + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss')// Config options + config_profile_name = null + config_profile_description = null + + custom_config_version = 'master' + custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" + config_profile_contact = null + config_profile_url = null + + // Schema validation default options + validate_params = true +} + +// Load base.config by default for all pipelines +includeConfig 'conf/base.config' + +profiles { + debug { + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false + nextflow.enable.configProcessNamesValidation = true + } + conda { + conda.enabled = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + conda.channels = ['conda-forge', 'bioconda'] + apptainer.enabled = false + } + mamba { + conda.enabled = true + conda.useMamba = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + docker { + docker.enabled = true + conda.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + docker.runOptions = '-u $(id -u):$(id -g)' + } + arm { + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + } + singularity { + singularity.enabled = true + singularity.autoMounts = true + conda.enabled = false + docker.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + podman { + podman.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + shifter { + shifter.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + charliecloud { + charliecloud.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + apptainer.enabled = false + } + apptainer { + apptainer.enabled = true + apptainer.autoMounts = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + } + wave { + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + test { includeConfig 'conf/test.config' } + test_full { includeConfig 'conf/test_full.config' } +} + +// Load nf-core custom profiles from different Institutions +includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" + +// Load core/hello custom profiles from different institutions. +// TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs +// includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/hello.config" : "/dev/null" + +// Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile +// Will not be used unless Apptainer / Docker / Podman / Charliecloud / 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' +charliecloud.registry = 'quay.io' + + + +// 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. + +env { + PYTHONNOUSERSITE = 1 + R_PROFILE_USER = "/.Rprofile" + R_ENVIRON_USER = "/.Renviron" + JULIA_DEPOT_PATH = "/usr/local/share/julia" +} + +// Set bash options +process.shell = [ + "bash", + "-C", // No clobber - prevent output redirection from overwriting files. + "-e", // Exit if a tool returns a non-zero status/exit code + "-u", // Treat unset variables and parameters as an error + "-o", // Returns the status of the last command to exit.. + "pipefail" // ..with a non-zero status or zero if all successfully execute +] + +// Disable process selector warnings by default. Use debug profile to enable warnings. +nextflow.enable.configProcessNamesValidation = false + +timeline { + enabled = true + file = "${params.outdir}/pipeline_info/execution_timeline_${params.trace_report_suffix}.html" +} +report { + enabled = true + file = "${params.outdir}/pipeline_info/execution_report_${params.trace_report_suffix}.html" +} +trace { + enabled = true + file = "${params.outdir}/pipeline_info/execution_trace_${params.trace_report_suffix}.txt" +} +dag { + enabled = true + file = "${params.outdir}/pipeline_info/pipeline_dag_${params.trace_report_suffix}.html" +} + +manifest { + name = 'core/hello' + author = """GG""" // The author field is deprecated from Nextflow version 24.10.0, use contributors instead + contributors = [ + // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 + [ + name: 'GG', + affiliation: '', + email: '', + github: '', + contribution: [], // List of contribution types ('author', 'maintainer' or 'contributor') + orcid: '' + ], + ] + homePage = 'https://github.com/core/hello' + description = """basic nf-core style version of Hello Nextflow""" + mainScript = 'main.nf' + defaultBranch = 'main' + nextflowVersion = '!>=24.04.2' + version = '1.0.0dev' + doi = '' +} + +// Nextflow plugins +plugins { + id 'nf-schema@2.2.0' // Validation of pipeline parameters and creation of an input channel from a sample sheet +} + +validation { + defaultIgnoreParams = ["genomes"] + monochromeLogs = params.monochrome_logs + help { + enabled = true + command = "nextflow run core/hello -profile --input samplesheet.csv --outdir " + fullParameter = "help_full" + showHiddenParameter = "show_hidden" + } +} + +// Load modules.config for DSL2 module specific options +includeConfig 'conf/modules.config' diff --git a/hello-nf-core/solutions/core-hello-start/nextflow_schema.json b/hello-nf-core/solutions/core-hello-start/nextflow_schema.json new file mode 100644 index 000000000..5ee5ec357 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/nextflow_schema.json @@ -0,0 +1,151 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/core/hello/main/nextflow_schema.json", + "title": "core/hello pipeline parameters", + "description": "basic nf-core style version of Hello Nextflow", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["input", "outdir"], + "properties": { + "input": { + "type": "string", + "format": "file-path", + "exists": true, + "schema": "assets/schema_input.json", + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "Path to comma-separated file containing information about the samples in the experiment.", + "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.", + "fa_icon": "fas fa-file-csv" + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + } + } + }, + "institutional_config_options": { + "title": "Institutional config options", + "type": "object", + "fa_icon": "fas fa-university", + "description": "Parameters used to describe centralised config profiles. These should not be edited.", + "help_text": "The centralised nf-core configuration profiles use a handful of pipeline parameters to describe themselves. This information is then printed to the Nextflow log when you run a pipeline. You should not need to change these values when you run a pipeline.", + "properties": { + "custom_config_version": { + "type": "string", + "description": "Git commit id for Institutional configs.", + "default": "master", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "custom_config_base": { + "type": "string", + "description": "Base directory for Institutional configs.", + "default": "https://raw.githubusercontent.com/nf-core/configs/master", + "hidden": true, + "help_text": "If you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.", + "fa_icon": "fas fa-users-cog" + }, + "config_profile_name": { + "type": "string", + "description": "Institutional config name.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_description": { + "type": "string", + "description": "Institutional config description.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_contact": { + "type": "string", + "description": "Institutional config contact information.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + }, + "config_profile_url": { + "type": "string", + "description": "Institutional config URL link.", + "hidden": true, + "fa_icon": "fas fa-users-cog" + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Do not use coloured log outputs.", + "fa_icon": "fas fa-palette", + "hidden": true + }, + "validate_params": { + "type": "boolean", + "description": "Boolean whether to validate parameters against the schema at runtime", + "default": true, + "fa_icon": "fas fa-check-square", + "hidden": true + }, + "pipelines_testdata_base_path": { + "type": "string", + "fa_icon": "far fa-check-circle", + "description": "Base URL or local path to location of pipeline test dataset files", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", + "hidden": true + }, + "trace_report_suffix": { + "type": "string", + "fa_icon": "far calendar", + "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", + "hidden": true + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/institutional_config_options" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/local/utils_nfcore_hello_pipeline/main.nf b/hello-nf-core/solutions/core-hello-start/subworkflows/local/utils_nfcore_hello_pipeline/main.nf new file mode 100644 index 000000000..95488af39 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/local/utils_nfcore_hello_pipeline/main.nf @@ -0,0 +1,140 @@ +// +// Subworkflow with functionality specific to the core/hello pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { samplesheetToList } from 'plugin/nf-schema' +include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW TO INITIALISE PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_INITIALISATION { + + take: + version // boolean: Display version and exit + validate_params // boolean: Boolean whether to validate parameters against the schema at runtime + monochrome_logs // boolean: Do not use coloured log outputs + nextflow_cli_args // array: List of positional nextflow CLI args + outdir // string: The output directory where the results will be saved + input // string: Path to input samplesheet + + main: + + ch_versions = Channel.empty() + + // + // Print version and exit if required and dump pipeline parameters to JSON file + // + UTILS_NEXTFLOW_PIPELINE ( + version, + true, + outdir, + workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 + ) + + // + // Validate parameters and generate parameter summary to stdout + // + UTILS_NFSCHEMA_PLUGIN ( + workflow, + validate_params, + null + ) + + // + // Check config provided to the pipeline + // + UTILS_NFCORE_PIPELINE ( + nextflow_cli_args + ) + + // + // Create channel from input file provided through params.input + // + + Channel + .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + .map { + meta, fastq_1, fastq_2 -> + if (!fastq_2) { + return [ meta.id, meta + [ single_end:true ], [ fastq_1 ] ] + } else { + return [ meta.id, meta + [ single_end:false ], [ fastq_1, fastq_2 ] ] + } + } + .groupTuple() + .map { samplesheet -> + validateInputSamplesheet(samplesheet) + } + .map { + meta, fastqs -> + return [ meta, fastqs.flatten() ] + } + .set { ch_samplesheet } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW FOR PIPELINE COMPLETION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_COMPLETION { + + take: + outdir // path: Path to output directory where results will be published + monochrome_logs // boolean: Disable ANSI colour codes in log output + + main: + summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + + // + // Completion email and summary + // + workflow.onComplete { + + completionSummary(monochrome_logs) + } + + workflow.onError { + log.error "Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting" + } +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Validate channels from input samplesheet +// +def validateInputSamplesheet(input) { + def (metas, fastqs) = input[1..2] + + // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end + def endedness_ok = metas.collect{ meta -> meta.single_end }.unique().size == 1 + if (!endedness_ok) { + error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") + } + + return [ metas[0], fastqs ] +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/main.nf new file mode 100644 index 000000000..d6e593e85 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/main.nf @@ -0,0 +1,126 @@ +// +// Subworkflow with functionality that may be useful for any Nextflow pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NEXTFLOW_PIPELINE { + take: + print_version // boolean: print version + dump_parameters // boolean: dump parameters + outdir // path: base directory used to publish pipeline results + check_conda_channels // boolean: check conda channels + + main: + + // + // Print workflow version and exit on --version + // + if (print_version) { + log.info("${workflow.manifest.name} ${getWorkflowVersion()}") + System.exit(0) + } + + // + // Dump pipeline parameters to a JSON file + // + if (dump_parameters && outdir) { + dumpParametersToJSON(outdir) + } + + // + // When running with Conda, warn if channels have not been set-up appropriately + // + if (check_conda_channels) { + checkCondaChannels() + } + + emit: + dummy_emit = true +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Generate version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Dump pipeline parameters to a JSON file +// +def dumpParametersToJSON(outdir) { + def timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + def filename = "params_${timestamp}.json" + def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") + def jsonStr = groovy.json.JsonOutput.toJson(params) + temp_pf.text = groovy.json.JsonOutput.prettyPrint(jsonStr) + + nextflow.extension.FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") + temp_pf.delete() +} + +// +// When running with -profile conda, warn if channels have not been set-up appropriately +// +def checkCondaChannels() { + def parser = new org.yaml.snakeyaml.Yaml() + def channels = [] + try { + def config = parser.load("conda config --show channels".execute().text) + channels = config.channels + } + catch (NullPointerException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + catch (IOException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + + // Check that all channels are present + // This channel list is ordered by required channel priority. + def required_channels_in_order = ['conda-forge', 'bioconda'] + def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean + + // Check that they are in the right order + def channel_priority_violation = required_channels_in_order != channels.findAll { ch -> ch in required_channels_in_order } + + if (channels_missing | channel_priority_violation) { + log.warn """\ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + There is a problem with your Conda configuration! + You will need to set-up the conda-forge and bioconda channels correctly. + Please refer to https://bioconda.github.io/ + The observed channel order is + ${channels} + but the following channel order is required: + ${required_channels_in_order} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + """.stripIndent(true) + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml new file mode 100644 index 000000000..e5c3a0a82 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NEXTFLOW_PIPELINE" +description: Subworkflow with functionality that may be useful for any Nextflow pipeline +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - print_version: + type: boolean + description: | + Print the version of the pipeline and exit + - dump_parameters: + type: boolean + description: | + Dump the parameters of the pipeline to a JSON file + - output_directory: + type: directory + description: Path to output dir to write JSON file to. + pattern: "results/" + - check_conda_channel: + type: boolean + description: | + Check if the conda channel priority is correct. +output: + - dummy_emit: + type: boolean + description: | + Dummy emit to make nf-core subworkflows lint happy +authors: + - "@adamrtalbot" + - "@drpatelh" +maintainers: + - "@adamrtalbot" + - "@drpatelh" + - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test new file mode 100644 index 000000000..68718e4f5 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test @@ -0,0 +1,54 @@ + +nextflow_function { + + name "Test Functions" + script "subworkflows/nf-core/utils_nextflow_pipeline/main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Test Function getWorkflowVersion") { + + function "getWorkflowVersion" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function dumpParametersToJSON") { + + function "dumpParametersToJSON" + + when { + function { + """ + // define inputs of the function here. Example: + input[0] = "$outputDir" + """.stripIndent() + } + } + + then { + assertAll( + { assert function.success } + ) + } + } + + test("Test Function checkCondaChannels") { + + function "checkCondaChannels" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 000000000..846287c41 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,20 @@ +{ + "Test Function getWorkflowVersion": { + "content": [ + "v9.9.9" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:05.308243" + }, + "Test Function checkCondaChannels": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:12.425833" + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test new file mode 100644 index 000000000..02dbf094c --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,113 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NEXTFLOW_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + workflow "UTILS_NEXTFLOW_PIPELINE" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Should run no inputs") { + + when { + workflow { + """ + print_version = false + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should print version") { + + when { + workflow { + """ + print_version = true + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + expect { + with(workflow) { + assert success + assert "nextflow_workflow v9.9.9" in stdout + } + } + } + } + + test("Should dump params") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = 'results' + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should not create params JSON if no output directory") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = null + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config new file mode 100644 index 000000000..a09572e5b --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml new file mode 100644 index 000000000..f84761125 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml @@ -0,0 +1,2 @@ +subworkflows/utils_nextflow_pipeline: + - subworkflows/nf-core/utils_nextflow_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/main.nf new file mode 100644 index 000000000..bfd258760 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -0,0 +1,419 @@ +// +// Subworkflow with utility functions specific to the nf-core pipeline template +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NFCORE_PIPELINE { + take: + nextflow_cli_args + + main: + valid_config = checkConfigProvided() + checkProfileProvided(nextflow_cli_args) + + emit: + valid_config +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Warn if a -profile or Nextflow config has not been provided to run the pipeline +// +def checkConfigProvided() { + def valid_config = true as Boolean + if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { + log.warn( + "[${workflow.manifest.name}] You are attempting to run the pipeline without any custom configuration!\n\n" + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + "Please refer to the quick start section and usage docs for the pipeline.\n " + ) + valid_config = false + } + return valid_config +} + +// +// Exit pipeline if --profile contains spaces +// +def checkProfileProvided(nextflow_cli_args) { + if (workflow.profile.endsWith(',')) { + error( + "The `-profile` option cannot end with a trailing comma, please remove it and re-run the pipeline!\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } + if (nextflow_cli_args[0]) { + log.warn( + "nf-core pipelines do not accept positional arguments. The positional argument `${nextflow_cli_args[0]}` has been detected.\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } +} + +// +// Generate workflow version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Get software versions for pipeline +// +def processVersionsFromYAML(yaml_file) { + def yaml = new org.yaml.snakeyaml.Yaml() + def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } + return yaml.dumpAsMap(versions).trim() +} + +// +// Get workflow version for pipeline +// +def workflowVersionToYAML() { + return """ + Workflow: + ${workflow.manifest.name}: ${getWorkflowVersion()} + Nextflow: ${workflow.nextflow.version} + """.stripIndent().trim() +} + +// +// Get channel of software versions used in pipeline in YAML format +// +def softwareVersionsToYAML(ch_versions) { + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) +} + +// +// Get workflow summary for MultiQC +// +def paramsSummaryMultiqc(summary_params) { + def summary_section = '' + summary_params + .keySet() + .each { group -> + def group_params = summary_params.get(group) + // This gets the parameters of that particular group + if (group_params) { + summary_section += "

${group}

\n" + summary_section += "
\n" + group_params + .keySet() + .sort() + .each { param -> + summary_section += "
${param}
${group_params.get(param) ?: 'N/A'}
\n" + } + summary_section += "
\n" + } + } + + def yaml_file_text = "id: '${workflow.manifest.name.replace('/', '-')}-summary'\n" as String + yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" + yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" + yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" + yaml_file_text += "plot_type: 'html'\n" + yaml_file_text += "data: |\n" + yaml_file_text += "${summary_section}" + + return yaml_file_text +} + +// +// ANSII colours used for terminal logging +// +def logColours(monochrome_logs=true) { + def colorcodes = [:] as Map + + // Reset / Meta + colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" + colorcodes['bold'] = monochrome_logs ? '' : "\033[1m" + colorcodes['dim'] = monochrome_logs ? '' : "\033[2m" + colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m" + colorcodes['blink'] = monochrome_logs ? '' : "\033[5m" + colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m" + colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" + + // Regular Colors + colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" + colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" + colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" + colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" + colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" + colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" + colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" + colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" + + // Bold + colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" + colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" + colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" + colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" + colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" + colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" + colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" + colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" + + // Underline + colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" + colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" + colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" + colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" + colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" + colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" + colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" + colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" + + // High Intensity + colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" + colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" + colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" + colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" + colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" + colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" + colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" + colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" + + // Bold High Intensity + colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" + colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" + colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" + colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" + colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" + colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" + colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" + colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" + + return colorcodes +} + +// Return a single report from an object that may be a Path or List +// +def getSingleReport(multiqc_reports) { + if (multiqc_reports instanceof Path) { + return multiqc_reports + } else if (multiqc_reports instanceof List) { + if (multiqc_reports.size() == 0) { + log.warn("[${workflow.manifest.name}] No reports found from process 'MULTIQC'") + return null + } else if (multiqc_reports.size() == 1) { + return multiqc_reports.first() + } else { + log.warn("[${workflow.manifest.name}] Found multiple reports from process 'MULTIQC', will use only one") + return multiqc_reports.first() + } + } else { + return null + } +} + +// +// Construct and send completion email +// +def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs=true, multiqc_report=null) { + + // Set up the e-mail variables + def subject = "[${workflow.manifest.name}] Successful: ${workflow.runName}" + if (!workflow.success) { + subject = "[${workflow.manifest.name}] FAILED: ${workflow.runName}" + } + + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['Date Started'] = workflow.start + misc_fields['Date Completed'] = workflow.complete + misc_fields['Pipeline script file path'] = workflow.scriptFile + misc_fields['Pipeline script hash ID'] = workflow.scriptId + if (workflow.repository) { + misc_fields['Pipeline repository Git URL'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['Pipeline repository Git Commit'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['Pipeline Git branch/tag'] = workflow.revision + } + misc_fields['Nextflow Version'] = workflow.nextflow.version + misc_fields['Nextflow Build'] = workflow.nextflow.build + misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp + + def email_fields = [:] + email_fields['version'] = getWorkflowVersion() + email_fields['runName'] = workflow.runName + email_fields['success'] = workflow.success + email_fields['dateComplete'] = workflow.complete + email_fields['duration'] = workflow.duration + email_fields['exitStatus'] = workflow.exitStatus + email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + email_fields['errorReport'] = (workflow.errorReport ?: 'None') + email_fields['commandLine'] = workflow.commandLine + email_fields['projectDir'] = workflow.projectDir + email_fields['summary'] = summary << misc_fields + + // On success try attach the multiqc report + def mqc_report = getSingleReport(multiqc_report) + + // Check if we are only sending emails on failure + def email_address = email + if (!email && email_on_fail && !workflow.success) { + email_address = email_on_fail + } + + // Render the TXT template + def engine = new groovy.text.GStringTemplateEngine() + def tf = new File("${workflow.projectDir}/assets/email_template.txt") + def txt_template = engine.createTemplate(tf).make(email_fields) + def email_txt = txt_template.toString() + + // Render the HTML template + def hf = new File("${workflow.projectDir}/assets/email_template.html") + def html_template = engine.createTemplate(hf).make(email_fields) + def email_html = html_template.toString() + + // Render the sendmail template + def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as MemoryUnit + def smail_fields = [email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "${workflow.projectDir}", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes()] + def sf = new File("${workflow.projectDir}/assets/sendmail_template.txt") + def sendmail_template = engine.createTemplate(sf).make(smail_fields) + def sendmail_html = sendmail_template.toString() + + // Send the HTML e-mail + def colors = logColours(monochrome_logs) as Map + if (email_address) { + try { + if (plaintext_email) { + new org.codehaus.groovy.GroovyException('Send plaintext e-mail, not HTML') + } + // Try to send HTML e-mail using sendmail + def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") + sendmail_tf.withWriter { w -> w << sendmail_html } + ['sendmail', '-t'].execute() << sendmail_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (sendmail)-") + } + catch (Exception msg) { + log.debug(msg.toString()) + log.debug("Trying with mail instead of sendmail") + // Catch failures and try with plaintext + def mail_cmd = ['mail', '-s', subject, '--content-type=text/html', email_address] + mail_cmd.execute() << email_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (mail)-") + } + } + + // Write summary e-mail HTML to a file + def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") + output_hf.withWriter { w -> w << email_html } + nextflow.extension.FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html") + output_hf.delete() + + // Write summary e-mail TXT to a file + def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") + output_tf.withWriter { w -> w << email_txt } + nextflow.extension.FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt") + output_tf.delete() +} + +// +// Print pipeline summary on completion +// +def completionSummary(monochrome_logs=true) { + def colors = logColours(monochrome_logs) as Map + if (workflow.success) { + if (workflow.stats.ignoredCount == 0) { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Pipeline completed successfully${colors.reset}-") + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-") + } + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.red} Pipeline completed with errors${colors.reset}-") + } +} + +// +// Construct and send a notification to a web server as JSON e.g. Microsoft Teams and Slack +// +def imNotification(summary_params, hook_url) { + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['start'] = workflow.start + misc_fields['complete'] = workflow.complete + misc_fields['scriptfile'] = workflow.scriptFile + misc_fields['scriptid'] = workflow.scriptId + if (workflow.repository) { + misc_fields['repository'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['commitid'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['revision'] = workflow.revision + } + misc_fields['nxf_version'] = workflow.nextflow.version + misc_fields['nxf_build'] = workflow.nextflow.build + misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp + + def msg_fields = [:] + msg_fields['version'] = getWorkflowVersion() + msg_fields['runName'] = workflow.runName + msg_fields['success'] = workflow.success + msg_fields['dateComplete'] = workflow.complete + msg_fields['duration'] = workflow.duration + msg_fields['exitStatus'] = workflow.exitStatus + msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + msg_fields['errorReport'] = (workflow.errorReport ?: 'None') + msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") + msg_fields['projectDir'] = workflow.projectDir + msg_fields['summary'] = summary << misc_fields + + // Render the JSON template + def engine = new groovy.text.GStringTemplateEngine() + // Different JSON depending on the service provider + // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format + def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" + def hf = new File("${workflow.projectDir}/assets/${json_path}") + def json_template = engine.createTemplate(hf).make(msg_fields) + def json_message = json_template.toString() + + // POST + def post = new URL(hook_url).openConnection() + post.setRequestMethod("POST") + post.setDoOutput(true) + post.setRequestProperty("Content-Type", "application/json") + post.getOutputStream().write(json_message.getBytes("UTF-8")) + def postRC = post.getResponseCode() + if (!postRC.equals(200)) { + log.warn(post.getErrorStream().getText()) + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml new file mode 100644 index 000000000..d08d24342 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NFCORE_PIPELINE" +description: Subworkflow with utility functions specific to the nf-core pipeline template +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - nextflow_cli_args: + type: list + description: | + Nextflow CLI positional arguments +output: + - success: + type: boolean + description: | + Dummy output to indicate success +authors: + - "@adamrtalbot" +maintainers: + - "@adamrtalbot" + - "@maxulysse" diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test new file mode 100644 index 000000000..f117040cb --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test @@ -0,0 +1,126 @@ + +nextflow_function { + + name "Test Functions" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Test Function checkConfigProvided") { + + function "checkConfigProvided" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function checkProfileProvided") { + + function "checkProfileProvided" + + when { + function { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function without logColours") { + + function "logColours" + + when { + function { + """ + input[0] = true + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function with logColours") { + function "logColours" + + when { + function { + """ + input[0] = false + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function getSingleReport with a single file") { + function "getSingleReport" + + when { + function { + """ + input[0] = file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") } + ) + } + } + + test("Test Function getSingleReport with multiple files") { + function "getSingleReport" + + when { + function { + """ + input[0] = [ + file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/network.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/expression.tsv', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") }, + { assert !function.result.contains("network.tsv") }, + { assert !function.result.contains("expression.tsv") } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 000000000..b13b31121 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,136 @@ +{ + "Test Function checkProfileProvided": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:03.360873" + }, + "Test Function checkConfigProvided": { + "content": [ + true + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:59.729647" + }, + "Test Function without logColours": { + "content": [ + { + "reset": "", + "bold": "", + "dim": "", + "underlined": "", + "blink": "", + "reverse": "", + "hidden": "", + "black": "", + "red": "", + "green": "", + "yellow": "", + "blue": "", + "purple": "", + "cyan": "", + "white": "", + "bblack": "", + "bred": "", + "bgreen": "", + "byellow": "", + "bblue": "", + "bpurple": "", + "bcyan": "", + "bwhite": "", + "ublack": "", + "ured": "", + "ugreen": "", + "uyellow": "", + "ublue": "", + "upurple": "", + "ucyan": "", + "uwhite": "", + "iblack": "", + "ired": "", + "igreen": "", + "iyellow": "", + "iblue": "", + "ipurple": "", + "icyan": "", + "iwhite": "", + "biblack": "", + "bired": "", + "bigreen": "", + "biyellow": "", + "biblue": "", + "bipurple": "", + "bicyan": "", + "biwhite": "" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:17.969323" + }, + "Test Function with logColours": { + "content": [ + { + "reset": "\u001b[0m", + "bold": "\u001b[1m", + "dim": "\u001b[2m", + "underlined": "\u001b[4m", + "blink": "\u001b[5m", + "reverse": "\u001b[7m", + "hidden": "\u001b[8m", + "black": "\u001b[0;30m", + "red": "\u001b[0;31m", + "green": "\u001b[0;32m", + "yellow": "\u001b[0;33m", + "blue": "\u001b[0;34m", + "purple": "\u001b[0;35m", + "cyan": "\u001b[0;36m", + "white": "\u001b[0;37m", + "bblack": "\u001b[1;30m", + "bred": "\u001b[1;31m", + "bgreen": "\u001b[1;32m", + "byellow": "\u001b[1;33m", + "bblue": "\u001b[1;34m", + "bpurple": "\u001b[1;35m", + "bcyan": "\u001b[1;36m", + "bwhite": "\u001b[1;37m", + "ublack": "\u001b[4;30m", + "ured": "\u001b[4;31m", + "ugreen": "\u001b[4;32m", + "uyellow": "\u001b[4;33m", + "ublue": "\u001b[4;34m", + "upurple": "\u001b[4;35m", + "ucyan": "\u001b[4;36m", + "uwhite": "\u001b[4;37m", + "iblack": "\u001b[0;90m", + "ired": "\u001b[0;91m", + "igreen": "\u001b[0;92m", + "iyellow": "\u001b[0;93m", + "iblue": "\u001b[0;94m", + "ipurple": "\u001b[0;95m", + "icyan": "\u001b[0;96m", + "iwhite": "\u001b[0;97m", + "biblack": "\u001b[1;90m", + "bired": "\u001b[1;91m", + "bigreen": "\u001b[1;92m", + "biyellow": "\u001b[1;93m", + "biblue": "\u001b[1;94m", + "bipurple": "\u001b[1;95m", + "bicyan": "\u001b[1;96m", + "biwhite": "\u001b[1;97m" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:21.714424" + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test new file mode 100644 index 000000000..8940d32d1 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,29 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NFCORE_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + workflow "UTILS_NFCORE_PIPELINE" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Should run without failures") { + + when { + workflow { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap new file mode 100644 index 000000000..84ee1e1d1 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -0,0 +1,19 @@ +{ + "Should run without failures": { + "content": [ + { + "0": [ + true + ], + "valid_config": [ + true + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:25.726491" + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config new file mode 100644 index 000000000..d0a926bf6 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml new file mode 100644 index 000000000..ac8523c9a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml @@ -0,0 +1,2 @@ +subworkflows/utils_nfcore_pipeline: + - subworkflows/nf-core/utils_nfcore_pipeline/** diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/main.nf new file mode 100644 index 000000000..93de2a524 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -0,0 +1,45 @@ +// +// Subworkflow that uses the nf-schema plugin to validate parameters and render the parameter summary +// + +include { paramsSummaryLog } from 'plugin/nf-schema' +include { validateParameters } from 'plugin/nf-schema' + +workflow UTILS_NFSCHEMA_PLUGIN { + + take: + input_workflow // workflow: the workflow object used by nf-schema to get metadata from the workflow + validate_params // boolean: validate the parameters + parameters_schema // string: path to the parameters JSON schema. + // this has to be the same as the schema given to `validation.parametersSchema` + // when this input is empty it will automatically use the configured schema or + // "${projectDir}/nextflow_schema.json" as default. This input should not be empty + // for meta pipelines + + main: + + // + // Print parameter summary to stdout. This will display the parameters + // that differ from the default given in the JSON schema + // + if(parameters_schema) { + log.info paramsSummaryLog(input_workflow, parameters_schema:parameters_schema) + } else { + log.info paramsSummaryLog(input_workflow) + } + + // + // Validate the parameters using nextflow_schema.json or the schema + // given via the validation.parametersSchema configuration option + // + if(validate_params) { + if(parameters_schema) { + validateParameters(parameters_schema:parameters_schema) + } else { + validateParameters() + } + } + + emit: + dummy_emit = true +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/meta.yml b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/meta.yml new file mode 100644 index 000000000..f7d9f0288 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/meta.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "utils_nfschema_plugin" +description: Run nf-schema to validate parameters and create a summary of changed parameters +keywords: + - validation + - JSON schema + - plugin + - parameters + - summary +components: [] +input: + - input_workflow: + type: object + description: | + The workflow object of the used pipeline. + This object contains meta data used to create the params summary log + - validate_params: + type: boolean + description: Validate the parameters and error if invalid. + - parameters_schema: + type: string + description: | + Path to the parameters JSON schema. + This has to be the same as the schema given to the `validation.parametersSchema` config + option. When this input is empty it will automatically use the configured schema or + "${projectDir}/nextflow_schema.json" as default. The schema should not be given in this way + for meta pipelines. +output: + - dummy_emit: + type: boolean + description: Dummy emit to make nf-core subworkflows lint happy +authors: + - "@nvnieuwk" +maintainers: + - "@nvnieuwk" diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test new file mode 100644 index 000000000..8fb301648 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -0,0 +1,117 @@ +nextflow_workflow { + + name "Test Subworkflow UTILS_NFSCHEMA_PLUGIN" + script "../main.nf" + workflow "UTILS_NFSCHEMA_PLUGIN" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/utils_nfschema_plugin" + tag "plugin/nf-schema" + + config "./nextflow.config" + + test("Should run nothing") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } + + test("Should run nothing - custom schema") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params - custom schema") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config new file mode 100644 index 000000000..478fb8a05 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -0,0 +1,8 @@ +plugins { + id "nf-schema@2.1.0" +} + +validation { + parametersSchema = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + monochromeLogs = true +} diff --git a/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json new file mode 100644 index 000000000..91e26fc4a --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/./master/nextflow_schema.json", + "title": ". pipeline parameters", + "description": "", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["outdir"], + "properties": { + "validate_params": { + "type": "boolean", + "description": "Validate parameters?", + "default": true, + "hidden": true + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + }, + "test_data_base": { + "type": "string", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/modules", + "description": "Base for test data directory", + "hidden": true + }, + "test_data": { + "type": "string", + "description": "Fake test data param", + "hidden": true + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "help": { + "type": "boolean", + "description": "Display help text.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "logo": { + "type": "boolean", + "default": true, + "description": "Display nf-core logo in console output.", + "fa_icon": "fas fa-image", + "hidden": true + }, + "singularity_pull_docker_container": { + "type": "boolean", + "description": "Pull Singularity container from Docker?", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Use monochrome_logs", + "hidden": true + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/hello-nf-core/solutions/core-hello-start/workflows/hello.nf b/hello-nf-core/solutions/core-hello-start/workflows/hello.nf new file mode 100644 index 000000000..d14610a61 --- /dev/null +++ b/hello-nf-core/solutions/core-hello-start/workflows/hello.nf @@ -0,0 +1,44 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +include { paramsSummaryMap } from 'plugin/nf-schema' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow HELLO { + + take: + ch_samplesheet // channel: samplesheet read in from --input + main: + + ch_versions = Channel.empty() + + // + // Collate and save software versions + // + softwareVersionsToYAML(ch_versions) + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'hello_software_' + 'versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } + + + emit: + versions = ch_versions // channel: [ path(versions.yml) ] + +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + THE END +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ From 95db085eb685401ce47d9a2db4d09b96e5aa2f90 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 3 Jun 2025 16:32:30 -0400 Subject: [PATCH 25/43] Improvements based on Summit trial run --- docs/hello_nf-core/01_run_demo.md | 2 +- docs/hello_nf-core/02_rewrite_hello.md | 274 +++++++++++++++++++------ 2 files changed, 207 insertions(+), 69 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 51c28d187..9a0d86296 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -118,7 +118,7 @@ ln -s $NXF_HOME/assets pipelines This creates a shortcut that makes it easier to explore the code we just downloaded. ```bash -tree -L2 pipelines +tree -L 2 pipelines ``` ```bash diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index a285f6e95..1d31328a1 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -39,27 +39,25 @@ Running this command will open a Text User Interface (TUI) for pipeline creation This TUI will ask you to provide basic information about your pipeline and will provide you with a choice of features to include or exclude in your pipeline scaffold. -1. Select **Let's go!** on the welcome screen -2. Select **Custom** on the `Choose pipeline type` screen -3. Enter your pipeline details, replacing < YOUR NAME > with your own name, then select **Next** +1. On the welcome screen, click **Let's go!**. +2. On the `Choose pipeline type` screen, click **Custom**. +3. Enter your pipeline details as follows (replacing `< YOUR NAME >` with your own name), then click **Next**. -**GitHub organisation:** core -**Workflow name:** hello -**A short description of your pipeline:** basic nf-core style version of Hello Nextflow -**Name of the main author / authors:** < YOUR NAME > + - **GitHub organisation:** core + - **Workflow name:** hello + - **A short description of your pipeline:** A basic nf-core style version of Hello Nextflow + - **Name of the main author / authors:** < YOUR NAME > -4. On the Template features screen, set "Toggle all features" to **off**, then selectively **enable** the following: +4. On the Template features screen, set `Toggle all features` to **off**, then selectively **enable** the following. Check your selections and click **Continue**. -- `Add configuration files` -- `Use nf-core components` -- `Use nf-schema` -- `Add documentation` -- `Add testing profiles` + - `Add configuration files` + - `Use nf-core components` + - `Use nf-schema` + - `Add documentation` + - `Add testing profiles` -5. Select **Finish** on the Final details screen -6. Wait for the pipeline to be created, then select **Continue** -7. Select **Finish without creating a repo** on the Create GitHub repository screen -8. Select **Close** on the HowTo create a GitHub repository page +5. On the `Final details` screen, click **Finish**. Wait for the pipeline to be created, then click **Continue**. +6. On the Create GitHub repository screen, click **Finish without creating a repo**. This will display instructions for creating a GitHub repository later. Ignore these and click **Close**. Once the TUI closes, you should see the following console output. @@ -142,7 +140,7 @@ core-hello/ That's a lot of files! - +Don't worry too much right now about what they all are; we are going to walk through the important parts together in the course of this training. !!! note @@ -193,8 +191,9 @@ This shows you that all the basic wiring is in place. You can take a look at the reports in the `pipeline_info` directory to see what was run; not much at all! !!! note -The nf-core pipeline template includes an example samplesheet, but at time of writing it is very domain-specific. -Future work will aim to produce something more generic. + + The nf-core pipeline template includes an example samplesheet, but at time of writing it is very domain-specific. + Future work will aim to produce something more generic. ### 1.3. Examine the placeholder workflow @@ -308,7 +307,7 @@ executor > local (8) There were 3 greetings in this batch ``` -For reference, here is the complete workflow code (not counting the processes, which are in modules): +Open the `hello.nf` workflow file to inspect the code, which is shown in full below (not counting the processes, which are in modules): ```groovy title="original-hello/hello.nf" linenums="1" #!/usr/bin/env nextflow @@ -377,18 +376,18 @@ Now, replace the channel construction with a simple `take` statement declaring e === "After" ```groovy title="original-hello/hello.nf" linenums="18" - take: - // channel of greetings - greeting_ch + take: + // channel of greetings + greeting_ch ``` === "Before" ```groovy title="original-hello/hello.nf" linenums="18" - // create a channel for inputs from a CSV file - greeting_ch = Channel.fromPath(params.greeting) - .splitCsv() - .map { line -> line[0] } + // create a channel for inputs from a CSV file + greeting_ch = Channel.fromPath(params.greeting) + .splitCsv() + .map { line -> line[0] } ``` This leaves the details of how the inputs are provided up to the parent workflow. @@ -404,6 +403,7 @@ Next, add a `main` statement before the rest of the operations called in the bod ```groovy title="original-hello/hello.nf" linenums="21" hl_lines="1" main: + // emit a greeting sayHello(greeting_ch) @@ -446,8 +446,8 @@ This basically says 'this is what this workflow _does_'. Finally, add an `emit` statement declaring what are the final outputs of the workflow. ```groovy title="original-hello/hello.nf" linenums="37" - emit: - final_result = cowpy.out + emit: + final_result = cowpy.out ``` This is a net new addition to the code compared to the original workflow. @@ -478,6 +478,7 @@ workflow HELLO { greeting_ch main: + // emit a greeting sayHello(greeting_ch) @@ -536,14 +537,20 @@ workflow { } ``` -You can see that the syntax for calling the imported workflow is essentially the same as the syntax for calling modules. +There are two important observations to make here: -You should also note that everything that has to do with pulling the inputs into the workflow (input parameter and channel construction) is now declared in this parent workflow. +- The syntax for calling the imported workflow (line 16) is essentially the same as the syntax for calling modules. +- Everything that is related to pulling the inputs into the workflow (input parameter and channel construction) is now declared in this parent workflow. !!! note - You can name the entrypoint workflow file whatever you want, it does not have to be named `main.nf`. - The advantage of naming it `main.nf` is that if you don't specify a workflow file, Nextflow will automatically look for a file named `main.nf` in the specified directory. + Naming the entrypoint workflow file `main.nf` is a convention, not a requirement. + + If you follow this convention, you can omit specifying the workflow file name in your `nextflow run` command. + Nextflow will automatically look for a file named `main.nf` in the execution directory. + + However, you can name the entrypoint workflow file something else if you prefer. + In that case, be sure to specify the workflow file name in your `nextflow run` command. ### 2.7. Test that the workflow runs @@ -648,11 +655,12 @@ We're going to tackle this in the following stages: 1. Copy over the modules and set up module imports 2. Leave the `take` declaration as is -3. Update the `main` block +3. Add the workflow logic to the `main` block 4. Update the `emit` block !!! note -We're going to ignore the version capture for this first pass and will look at how to wire that up in a later section. + + We're going to ignore the version capture for this first pass and will look at how to wire that up in a later section. ### 3.1. Copy over the modules and set up module imports @@ -680,7 +688,19 @@ core-hello/modules └── sayHello.nf ``` -Finally, copy the import statements from the `original-hello/hello.nf` workflow to the `core-hello/workflows/hello.nf` version. +Now let's set up the module import statements. + +These were the import statements in the `original-hello/hello.nf` workflow: + +```groovy title="original-hello/hello.nf" linenums="9" +// Include modules +include { sayHello } from './modules/sayHello.nf' +include { convertToUpper } from './modules/convertToUpper.nf' +include { collectGreetings } from './modules/collectGreetings.nf' +include { cowpy } from './modules/cowpy.nf' +``` + +Open the `core-hello/workflows/hello.nf` file and transpose those import statements into it as shown below. === "After" @@ -700,7 +720,7 @@ Finally, copy the import statements from the `original-hello/hello.nf` workflow === "Before" - ```groovy title="core-hello/workflows/hello.nf" linenums="1" hl_lines="8-11" + ```groovy title="core-hello/workflows/hello.nf" linenums="1" /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS @@ -710,35 +730,60 @@ Finally, copy the import statements from the `original-hello/hello.nf` workflow include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' ``` -Notice that here we've adapted the spacing of the import statements to follow the nf-core style convention, and we've updated the relative paths to the modules to reflect that they're now stored at a different level of nesting. +Two more interesting observations here: + +- We've adapted the formatting of the import statements to follow the nf-core style convention. +- We've updated the relative paths to the modules to reflect that they're now stored at a different level of nesting. ### 3.2. Leave the `take` declaration as is The nf-core project has a lot of prebuilt functionality around the concept of the samplesheet, which is typically a CSV file containing columnar data. Since that is essentially what our `greetings.csv` file is, we'll keep the current `take` declaration as is, and simply update the name of the input channel in the next step. -```groovy title="core-hello/workflows/hello.nf" linenums="16" +```groovy title="core-hello/workflows/hello.nf" linenums="21" take: ch_samplesheet // channel: samplesheet read in from --input ``` The input handling will be done upstream of this workflow (not in this code file). -### 3.3. Update the `main` block +### 3.3. Add the workflow logic to the `main` block Now that our modules are available to the workflow, we can plug the workflow logic into the `main` block. -There is already some code in there that has to do with capturing the versions of the tools that get run by the workflow; we're going to leave that alone for now and simply insert our code right after the `main:` line. +As a reminder, this is the relevant code in the original workflow, which didn't change much when we made it composable (we just added the `main:` line): + +```groovy title="original-hello/hello.nf" linenums="21" + main: -Importantly, we have to update the name of the channel we're passing to the `sayHello()` process from `greeting_ch` to `ch_samplesheet` (see highlighted lines). + // emit a greeting + sayHello(greeting_ch) + + // convert the greeting to uppercase + convertToUpper(sayHello.out) + + // collect all the greetings into one file + collectGreetings(convertToUpper.out.collect(), params.batch) + + // emit a message about the size of the batch + collectGreetings.out.count.view { "There were $it greetings in this batch" } + + // generate ASCII art of the greetings with cowpy + cowpy(collectGreetings.out.outfile, params.character) +``` + +We need to copy this code into the new version of the workflow (minus the `main:` keyword which is already there). + +There is already some code in there that has to do with capturing the versions of the tools that get run by the workflow. We're going to leave that alone for now (we'll deal with the tool versions later) and simply insert our code right after the `main:` line. === "After" - ```groovy title="core-hello/workflows/hello.nf" linenums="16" hl_lines="3 4" + ```groovy title="core-hello/workflows/hello.nf" linenums="23" + main: - // emit a greeting (updated to use the default ch_samplesheet name) - sayHello(ch_samplesheet) + // emit a greeting + sayHello(greeting_ch) // convert the greeting to uppercase convertToUpper(sayHello.out) @@ -769,7 +814,7 @@ Importantly, we have to update the name of the channel we're passing to the `say === "Before" - ```groovy title="core-hello/workflows/hello.nf" linenums="16" + ```groovy title="core-hello/workflows/hello.nf" linenums="23" main: ch_versions = Channel.empty() @@ -787,7 +832,23 @@ Importantly, we have to update the name of the channel we're passing to the `say ``` -We'll address the tool versions in a later section of this training. +This looks great, but we still need to update the name of the channel we're passing to the `sayHello()` process from `greeting_ch` to `ch_samplesheet` (see highlighted lines), to match what is written under the `take:` keyword. + +=== "After" + + ```groovy title="core-hello/workflows/hello.nf" linenums="26" + // emit a greeting + sayHello(greeting_ch) + ``` + +=== "Before" + + ```groovy title="core-hello/workflows/hello.nf" linenums="23" + // emit a greeting (updated to use the nf-core convention for samplesheets) + sayHello(ch_samplesheet) + ``` + +Now the workflow logic is correctly wired up. ### 3.4. Update the `emit` block @@ -822,7 +883,7 @@ Learn how to adapt how the inputs are handle in the nf-core pipeline scaffold. ## 4. Adapt the input handling -Now that the HELLO workflow is ready to go, we need to adapt how the inputs are handled (to make sure our `greetings.csv` will be handled appropriately). +Now that the HELLO workflow is ready to go, we need to adapt how the inputs are handled to make sure our `greetings.csv` will be handled appropriately. ### 4.1. Identify where inputs are handled @@ -963,7 +1024,8 @@ This is the channel factory that parses the samplesheet and passes it on in a fo It is quite complex because it does a lot of parsing and validation work. !!! note -The syntax above is a little different from what we've used previously, but basically this: + + The syntax above is a little different from what we've used previously, but basically this: ```groovy Channel.<...>.set { ch_samplesheet } @@ -979,23 +1041,68 @@ The syntax above is a little different from what we've used previously, but basi The good news is that our pipeline's needs are much simpler, so we can replace all of that by the channel construction code we developed in the original Hello Nextflow workflow. -```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" hl_lines="4" - // - // Create channel from input file provided through params.input - // - ch_samplesheet = Channel.fromPath(params.input) +As a reminder, this is what the channel construction looked like (as seen in the solutions directory): + +```groovy title="solutions/composable-hello/main.nf" linenums="10" hl_lines="4" + // create a channel for inputs from a CSV file + greeting_ch = Channel.fromPath(params.greeting) .splitCsv() .map { line -> line[0] } - - emit: - samplesheet = ch_samplesheet - versions = ch_versions ``` -Importantly, in that highlighted line, we've updated the channel name from `greeting_ch` to `ch_samplesheet`, and the parameter name from `params.greeting` to `params.input`. +So we just need to plug that into the initialisation workflow, with minor changes: we update the channel name from `greeting_ch` to `ch_samplesheet`, and the parameter name from `params.greeting` to `params.input` (see highlighted line). + +=== "After" + + ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" hl_lines="4" + // + // Create channel from input file provided through params.input + // + ch_samplesheet = Channel.fromPath(params.input) + .splitCsv() + .map { line -> line[0] } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions + ``` + +=== "Before" + + ```groovy title="core-hello/subworkflows/local/utils_nfcore_hello_pipeline/main.nf" linenums="64" + // + // Create channel from input file provided through params.input + // + + Channel + .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + .map { + meta, fastq_1, fastq_2 -> + if (!fastq_2) { + return [ meta.id, meta + [ single_end:true ], [ fastq_1 ] ] + } else { + return [ meta.id, meta + [ single_end:false ], [ fastq_1, fastq_2 ] ] + } + } + .groupTuple() + .map { samplesheet -> + validateInputSamplesheet(samplesheet) + } + .map { + meta, fastqs -> + return [ meta, fastqs.flatten() ] + } + .set { ch_samplesheet } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions + ``` + +That completes the changes we need to make the input processing work. In its current form, this won't let us take advantage of nf-core's built-in capabilities for schema validation, but we can add that in later. -For now, let's focus on keeping it as simple as possible to get to something we can run successfully on test data. +For now, we're focused on keeping it as simple as possible to get to something we can run successfully on test data. ### 4.3. Update the test profile @@ -1016,7 +1123,7 @@ Now we can update the `test.config` file as follows: === "After" - ```groovy title="core-hello/config/test.config" linenums="21" + ```groovy title="core-hello/conf/test.config" linenums="21" hl_lines="5-10" params { config_profile_name = 'Test profile' config_profile_description = 'Minimal test dataset to check pipeline function' @@ -1044,15 +1151,16 @@ Now we can update the `test.config` file as follows: } ``` -And while we're at it, let's lower the default resource allocations: +And while we're at it, let's lower the default resource limitations: === "After" ```groovy title="core-hello/config/test.config" linenums="13" process { resourceLimits = [ - cpus: 1, - memory: '1.GB' + cpus: 2, + memory: '4.GB', + time: '1.h' ] } ``` @@ -1080,7 +1188,7 @@ Note that we have to add `--validate_params false` to the command line because w nextflow run core-hello --outdir core-hello-results -profile test,docker --validate_params false ``` -If you've done all of this correctly, it should produce the typical nf-core summary at the start (thanks to the initialisation subworkflow) and run to completion. +If you've done all of the modifications correctly, it should run to completion. ```console title="Output" N E X T F L O W ~ version 24.10.4 @@ -1119,9 +1227,39 @@ executor > local (8) -[core/hello] Pipeline completed successfully- ``` -And there it is! It may seem like a low of work to accomplish the same result as the original pipeline, but if you check out the results directory, you'll see that in addition to the results produced by the Hello pipeline, you still get the `pipeline_info` directory containing the various reports produced by the nf-core utility subworkflows. +As you can see, this produced the typical nf-core summary at the start thanks to the initialisation subworkflow, and the lines for each module now show the full PIPELINE:WORKFLOW:module names. + +### 4.5. Find the pipeline outputs + +The question now is: where are the outputs of the pipeline? +And the answer is quite interesting: there are now two different places to look for the results. + +We didn't change anything to the modules themselves, so the outputs handled by module-level `publishDir` directives are still going to a `results` directory as specified in the original pipeline. + +```bash +tree results +``` + +```console title="Output" +results +├── Bonjour-output.txt +├── COLLECTED-test-batch-output.txt +├── COLLECTED-test-output.txt +├── cowpy-COLLECTED-test-batch-output.txt +├── cowpy-COLLECTED-test-output.txt +├── Hello-output.txt +├── Holà-output.txt +├── UPPER-Bonjour-output.txt +├── UPPER-Hello-output.txt +└── UPPER-Holà-output.txt +``` + +Anything that is hooked up to the nf-core template code gets put into a directory generated automatically, called `core-hello-results/`. +This includes the various reports produced by the nf-core utility subworkflows, which you can find under `core-hello-results/pipeline_info`. + +In our case, we didn't explicitly mark anything else as an output, so there's nothing else there. -On top of that, this gives you a solid foundation to adopting key additional benefits of nf-core, including input validation and some neat metadata handling capabilities that we'll cover in a later section. +And there it is! It may seem like a lot of work to accomplish the same result as the original pipeline, but you do get all those lovely reports generated automatically, and you now have a solid foundation for taking advantage of additional features of nf-core, including input validation and some neat metadata handling capabilities that we'll cover in a later section. --- From e95bc18caed3f38b651d7db2089d576345a2b511 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 3 Jun 2025 16:49:15 -0400 Subject: [PATCH 26/43] fixed headings --- docs/hello_nf-core/01_run_demo.md | 6 +++--- docs/hello_nf-core/next_steps.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 9a0d86296..49fe68a97 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -364,7 +364,7 @@ The pipeline code organization follows a modular structure that is designed to m We won't go over the actual code for how these modular components are connected, because there is some additional complexity associated with the use of subworkflows that can be confusing, and understanding that is not necessary at this stage of the training. For now, we're going to focus on the logic of this modular organization. -### 3.1.1. Overall organization and `main.nf` script +#### 3.1.1. Overall organization and `main.nf` script At the top level, there is the `main.nf` script, which is the entrypoint Nextflow starts from when we execute `nextflow run nf-core/demo`. That means when you run `nextflow run nf-core/demo` to run the pipeline, Nextflow automatically finds and executes the `main.nf` script, and everything else will flow from there. @@ -398,7 +398,7 @@ Usually these are operations that very specific to that pipeline. Let's take a peek into those directories. -### 3.1.2. Modules +#### 3.1.2. Modules The modules are where the process code lives, as described in [Part 4 of the Hello Nextflow training course](../hello_nextflow/04_hello_modules.md). @@ -433,7 +433,7 @@ pipelines/nf-core/demo/modules Here you see that the `fastqc` and `multiqc` modules sit at the top level within the `nf-core` modules, whereas the `trim` module sits under the toolkit that it belongs to, `seqtk`. In this case there are no `local` modules. -### 3.1.3. Subworkflows +#### 3.1.3. Subworkflows As noted above, subworkflows function as wrappers that call two or more modules. diff --git a/docs/hello_nf-core/next_steps.md b/docs/hello_nf-core/next_steps.md index b8108e996..b92b8fb89 100644 --- a/docs/hello_nf-core/next_steps.md +++ b/docs/hello_nf-core/next_steps.md @@ -6,13 +6,13 @@ Congrats again on completing the Hello nf-core training course and thank you for TODO: UPDATE -### 1. See how what you just learned applies to a scientific analysis use case +## 1. See how what you just learned applies to a scientific analysis use case **Check out the [Nextflow for Science](../nf4_science/index.md) page** for a list of short standalone courses that demonstrate how to apply the basic concepts and mechanisms presented in Hello Nextflow to common scientific analysis use cases. If you don't see your domain represented by a relatable use case, let us know in the [Community forum](https://community.seqera.io/) so we can add it to our development list. -### 2. Delve into the details +## 2. Delve into the details In the Hello Nextflow course, we keep the level of technical complexity low on purpose to avoid overloading you with information you don't need in order to get started with Nextflow. As you move forward with your work, you're going to want to learn how to use the full feature set and power of Nextflow. @@ -21,7 +21,7 @@ To that end, we are currently working on a collection of Side Quests, which are In the meantime, feel free to **browse the [Fundamentals Training](../basic_training/index.md) and [Advanced Training](../advanced/index.md)** to find training exercises about the topics that interest you. -### 3. Learn how to use nf-core resources and the Seqera Platform +## 3. Learn how to use nf-core resources and the Seqera Platform **The [nf-core project](https://nf-co.re/) is a worldwide collaborative effort to develop standardized open-source pipelines for a wide range of scientific research applications.** It includes [over 100 pipelines](https://nf-co.re/pipelines/) that are available for use out of the box and [well over 1400 process modules](https://nf-co.re/modules/) that can be integrated into your own projects, as well as a rich set of developer tools. From ce429c08c52123334783c95076df557736fb7f3c Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 3 Jun 2025 22:52:48 -0400 Subject: [PATCH 27/43] Apply suggestions from Ken's code review Small improvements Co-authored-by: Ken Brewer --- docs/hello_nf-core/00_orientation.md | 6 ++++++ docs/hello_nf-core/02_rewrite_hello.md | 8 ++++---- hello-nf-core/solutions/composable-hello/hello.nf | 2 +- hello-nf-core/solutions/core-hello-end/workflows/hello.nf | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md index 63b81cb49..417c9bc85 100644 --- a/docs/hello_nf-core/00_orientation.md +++ b/docs/hello_nf-core/00_orientation.md @@ -7,6 +7,12 @@ However, you do need a (free) GitHub account to log in, and you should take a fe If you have not yet done so, please go through the [Environment Setup](../../envsetup/) mini-course before going any further. +!!! warning + + This training is designed for nf-core tools version 3.2.1, which should be the version installed in the codespace. If you use a different version of nf-core tooling you may have difficulty following along. + + You can check what version is installed using the command`nf-core --version`. + ## Working directory Throughout this training course, we'll be working in the `hello-nf-core/` directory. diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 1d31328a1..cc67f320a 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -152,7 +152,7 @@ Don't worry too much right now about what they all are; we are going to walk thr Believe it or not, even though you haven't yet added any modules to make it do real work, the pipeline scaffold can actually be run using the test profile, the same way we ran the `nf-core/demo` pipeline. ```bash -nextflow run core-hello -profile docker,test --outdir core-hello-results +nextflow run ./core-hello -profile docker,test --outdir core-hello-results ``` ```console title="Output" @@ -495,7 +495,7 @@ workflow HELLO { cowpy(collectGreetings.out.outfile, params.character) emit: - final_result = cowpy.out + cowpy_hellos = cowpy.out } ``` @@ -557,7 +557,7 @@ There are two important observations to make here: We finally have all the pieces we need to verify that the composable workflow works. ```bash -nextflow run original-hello +nextflow run ./original-hello ``` !!! note @@ -858,7 +858,7 @@ Finally, we need to update the `emit` block to include the declaration of the wo ```groovy title="core-hello/workflows/hello.nf" linenums="55" hl_lines="2" emit: - final_result = cowpy.out + cowpy_hellos = cowpy.out versions = ch_versions // channel: [ path(versions.yml) ] ``` diff --git a/hello-nf-core/solutions/composable-hello/hello.nf b/hello-nf-core/solutions/composable-hello/hello.nf index 738a0e826..d41f1e754 100644 --- a/hello-nf-core/solutions/composable-hello/hello.nf +++ b/hello-nf-core/solutions/composable-hello/hello.nf @@ -36,5 +36,5 @@ workflow HELLO { cowpy(collectGreetings.out.outfile, params.character) emit: - final_result = cowpy.out + cowpy_hellos = cowpy.out } diff --git a/hello-nf-core/solutions/core-hello-end/workflows/hello.nf b/hello-nf-core/solutions/core-hello-end/workflows/hello.nf index 4ce0fae56..3e3af1c2a 100644 --- a/hello-nf-core/solutions/core-hello-end/workflows/hello.nf +++ b/hello-nf-core/solutions/core-hello-end/workflows/hello.nf @@ -49,7 +49,7 @@ workflow HELLO { emit: - final_result = cowpy.out + cowpy_hellos = cowpy.out versions = ch_versions // channel: [ path(versions.yml) ] } From dabcc5d0acb880bbf173fe4a89e7c765ddfde35c Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 3 Jun 2025 22:55:55 -0400 Subject: [PATCH 28/43] caught a straggler --- docs/hello_nf-core/02_rewrite_hello.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index cc67f320a..f5b41744f 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -447,7 +447,7 @@ Finally, add an `emit` statement declaring what are the final outputs of the wor ```groovy title="original-hello/hello.nf" linenums="37" emit: - final_result = cowpy.out + cowpy_hellos = cowpy.out ``` This is a net new addition to the code compared to the original workflow. From c4b37e0cd01a5406f989003508b1456481d4029d Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 3 Jun 2025 23:00:59 -0400 Subject: [PATCH 29/43] prettier fix --- docs/hello_nf-core/00_orientation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hello_nf-core/00_orientation.md b/docs/hello_nf-core/00_orientation.md index 417c9bc85..27808cfde 100644 --- a/docs/hello_nf-core/00_orientation.md +++ b/docs/hello_nf-core/00_orientation.md @@ -10,7 +10,7 @@ If you have not yet done so, please go through the [Environment Setup](../../env !!! warning This training is designed for nf-core tools version 3.2.1, which should be the version installed in the codespace. If you use a different version of nf-core tooling you may have difficulty following along. - + You can check what version is installed using the command`nf-core --version`. ## Working directory From 220e200df4be930eb867433d7caf2832aff74d0c Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 3 Jun 2025 23:07:31 -0400 Subject: [PATCH 30/43] note formatting fix --- docs/hello_nf-core/01_run_demo.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 49fe68a97..4be6357b4 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -104,7 +104,8 @@ tree -L 2 $NXF_HOME/assets/ ``` !!! note -The full path may differ on your system if you're not using our training environment. + + The full path may differ on your system if you're not using our training environment. The location of the downloaded source code is intentionally 'out of the way' on the principle that these pipelines should be used more like libraries than code that you would directly interact with. @@ -596,9 +597,10 @@ SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, ``` -!!! Note -The paths in this example samplesheet are not real. -For paths to real data files, you should look in the test profiles, which link to data in the `nf-core/test-datasets` repository. +!!! note + + The paths in this example samplesheet are not real. + For paths to real data files, you should look in the test profiles, which link to data in the `nf-core/test-datasets` repository. In general, it's considered good practice to link out to example data rather than include it in the pipeline code repository, unless the example data is of trivial size (as is the case for the `greetings.csv` in the Hello Nextflow training series). From a8d5142893c65246e5fc29e025c6aa8c9116f32e Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 3 Jun 2025 23:26:37 -0400 Subject: [PATCH 31/43] Improve meta pages (especially next steps) --- docs/hello_nf-core/index.md | 12 ++++++------ docs/hello_nf-core/next_steps.md | 25 +++++++------------------ docs/hello_nf-core/survey.md | 4 ++++ 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/docs/hello_nf-core/index.md b/docs/hello_nf-core/index.md index 74520a144..5505683a5 100644 --- a/docs/hello_nf-core/index.md +++ b/docs/hello_nf-core/index.md @@ -6,19 +6,19 @@ hide: # Hello nf-core -nf-core is a community effort to develop and maintain a curated set of scientific pipelines built using Nextflow, as well as relevant tooling and guidelines that promote open development, testing, and peer review. +**nf-core** is a community effort to develop and maintain a curated set of scientific pipelines built using Nextflow, as well as relevant tooling and guidelines that promote open development, testing, and peer review. These pipelines are designed to be modular, scalable, and portable, allowing researchers to easily adapt and execute them using their own data and compute resources. The best practices guidelines enforced by the project further ensure that the pipelines are robust, well-documented, and validated against real-world datasets. This helps to increase the reliability and reproducibility of scientific analyses and ultimately enables researchers to accelerate their scientific discoveries. -nf-core is published in Nature Biotechnology: [Nat Biotechnol 38, 276–278 (2020). Nature Biotechnology](https://www.nature.com/articles/s41587-020-0439-x). -An updated preprint is available at [bioRxiv](https://www.biorxiv.org/content/10.1101/2024.05.10.592912v1). +During this training, you will be introduced to nf-core in a series of hands-on exercises. -You can learn more about the project's origins and governance at https://nf-co.re/about. +**Additional information:** You can learn more about the project's origins and governance at https://nf-co.re/about. -During this training, you will be introduced to nf-core in a series of hands-on exercises. +**Reference publication:** nf-core is published in Nature Biotechnology: [Nat Biotechnol 38, 276–278 (2020). Nature Biotechnology](https://www.nature.com/articles/s41587-020-0439-x). +An updated preprint is available at [bioRxiv](https://www.biorxiv.org/content/10.1101/2024.05.10.592912v1). -Let's get started! Click on the "Open in GitHub Codespaces" button below to launch the training environment (preferably in a separate tab), then read on while it loads. +**Let's get started!** Click on the "Open in GitHub Codespaces" button below to launch the training environment (preferably in a separate tab), then read on while it loads. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/nextflow-io/training?quickstart=1&ref=master) diff --git a/docs/hello_nf-core/next_steps.md b/docs/hello_nf-core/next_steps.md index b92b8fb89..31cf0b0ea 100644 --- a/docs/hello_nf-core/next_steps.md +++ b/docs/hello_nf-core/next_steps.md @@ -1,39 +1,28 @@ # Next Steps -Congrats again on completing the Hello nf-core training course and thank you for completing our survey! +Congrats again on completing the **Hello nf-core** training course and thank you for completing our survey! **Here are our top 3 recommendations for what you can do next to take your Nextflow skills to the next level.** -TODO: UPDATE +## 1. Get involved with the nf-core community -## 1. See how what you just learned applies to a scientific analysis use case +**The nf-core community is a welcoming, collaborative space** where you can keep learning, connect with others, and make meaningful contributions. Join the [nf-core Slack workspace](https://nf-co.re/join/slack) to ask questions, share ideas, and stay updated. Whether you attend a hackathon, help improve a pipeline, or just chat with fellow community members, getting involved is a great way to grow your skills and be part of the future of open bioinformatics. + +## 2. See how Nextflow can be applied to a scientific analysis use case **Check out the [Nextflow for Science](../nf4_science/index.md) page** for a list of short standalone courses that demonstrate how to apply the basic concepts and mechanisms presented in Hello Nextflow to common scientific analysis use cases. If you don't see your domain represented by a relatable use case, let us know in the [Community forum](https://community.seqera.io/) so we can add it to our development list. -## 2. Delve into the details +## 3. Learn to master more advanced Nextflow features -In the Hello Nextflow course, we keep the level of technical complexity low on purpose to avoid overloading you with information you don't need in order to get started with Nextflow. +In the Hello courses, we keep the level of technical complexity low on purpose to avoid overloading you with information you don't need in order to get started with Nextflow. As you move forward with your work, you're going to want to learn how to use the full feature set and power of Nextflow. To that end, we are currently working on a collection of Side Quests, which are meant to be short standalone courses that go deep into specific topics like testing, metadata handling, using conditional statements and the differences between working on HPC _vs._ cloud. In the meantime, feel free to **browse the [Fundamentals Training](../basic_training/index.md) and [Advanced Training](../advanced/index.md)** to find training exercises about the topics that interest you. -## 3. Learn how to use nf-core resources and the Seqera Platform - -**The [nf-core project](https://nf-co.re/) is a worldwide collaborative effort to develop standardized open-source pipelines for a wide range of scientific research applications.** -It includes [over 100 pipelines](https://nf-co.re/pipelines/) that are available for use out of the box and [well over 1400 process modules](https://nf-co.re/modules/) that can be integrated into your own projects, as well as a rich set of developer tools. - -**[Seqera Platform](https://seqera.io/) is the best way to run Nextflow in practice.** -It is a cloud-based platform that you can connect to your own compute infrastructure to make it much easier to launch and manage your workflows. -The Free Tier is available for free use by everyone (with usage quotas). -Qualifying academics can get free Pro-level access (no usage limitations) through the [Academic Program](https://seqera.typeform.com/to/SRB8Ci3n). - -We are currently developing a short training course demonstrating how to use both of these resources (either independently or in combination). -In the meantime, check out the [nf-core docs](https://nf-co.re/docs/) and the [Seqera Platform tutorials](https://docs.seqera.io/platform/latest/getting-started/quickstart-demo/comm-showcase) - ### That's it for now! **Good luck in your Nextflow journey and don't hesitate to let us know in the [Community forum](https://community.seqera.io/) what else we could do to help.** diff --git a/docs/hello_nf-core/survey.md b/docs/hello_nf-core/survey.md index f88bfe501..3f0f7729c 100644 --- a/docs/hello_nf-core/survey.md +++ b/docs/hello_nf-core/survey.md @@ -5,3 +5,7 @@ Before you move on, please complete this short 4-question survey to rate the tra This should take you less than a minute to complete. Thank you for helping us improve our training materials for everyone!
+ +TODO: THIS IS A WIP AND SHOULD NOT BE ADDED TO THE NAVIGATION + +(NOT SUBJECT TO REVIEW) From 5a8e1132521492a22146f2d60edfc64b045a00ab Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Tue, 3 Jun 2025 23:48:07 -0400 Subject: [PATCH 32/43] Add survey page for hello nf-core --- docs/hello_nf-core/survey.md | 6 +----- mkdocs.yml | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/hello_nf-core/survey.md b/docs/hello_nf-core/survey.md index 3f0f7729c..029a755e2 100644 --- a/docs/hello_nf-core/survey.md +++ b/docs/hello_nf-core/survey.md @@ -4,8 +4,4 @@ Before you move on, please complete this short 4-question survey to rate the tra This should take you less than a minute to complete. Thank you for helping us improve our training materials for everyone! -
- -TODO: THIS IS A WIP AND SHOULD NOT BE ADDED TO THE NAVIGATION - -(NOT SUBJECT TO REVIEW) +
diff --git a/mkdocs.yml b/mkdocs.yml index 2725c49e5..8182c87d7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,7 @@ nav: - hello_nf-core/00_orientation.md - hello_nf-core/01_run_demo.md - hello_nf-core/02_rewrite_hello.md + - hello_nf-core/survey.md - hello_nf-core/next_steps.md - Nextflow for Genomics: - nf4_science/genomics/index.md From b08d71f0ff194d1ab29bff3e9a291e2e5bab9ee9 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 00:17:18 -0400 Subject: [PATCH 33/43] Improve next steps pages --- docs/hello_nextflow/next_steps.md | 35 ++++++++++++++++-------- docs/hello_nf-core/next_steps.md | 31 ++++++++++++++++----- docs/nf4_science/rnaseq/next_steps.md | 39 ++++++++++++++++++--------- 3 files changed, 75 insertions(+), 30 deletions(-) diff --git a/docs/hello_nextflow/next_steps.md b/docs/hello_nextflow/next_steps.md index 28d344df1..89a41cf6b 100644 --- a/docs/hello_nextflow/next_steps.md +++ b/docs/hello_nextflow/next_steps.md @@ -10,35 +10,48 @@ Congrats again on completing the Hello Nextflow training course and thank you for completing our survey! -**Here are our top 3 recommendations for what you can do next to take your Nextflow skills to the next level.** +--- -### 1. See how what you just learned applies to a scientific analysis use case +## 1. Top 3 ways to level up your Nextflow skills + +Here are our top three recommendations for what to do next based on the course you just completed. + +### 1.1. Apply Nextflow to a scientific analysis use case **Check out the [Nextflow for Science](../nf4_science/index.md) page** for a list of short standalone courses that demonstrate how to apply the basic concepts and mechanisms presented in Hello Nextflow to common scientific analysis use cases. If you don't see your domain represented by a relatable use case, let us know in the [Community forum](https://community.seqera.io/) so we can add it to our development list. -### 2. Delve into the details +### 1.2. Get started with nf-core + +**[nf-core](https://nf-co.re/)** is a worldwide collaborative effort to develop standardized open-source pipelines for a wide range of scientific research applications.\*\* +The project includes [over 100 pipelines](https://nf-co.re/pipelines/) that are available for use out of the box and [well over 1400 process modules](https://nf-co.re/modules/) that can be integrated into your own projects, as well as a rich set of developer tools. + +The **[Hello nf-core](../../hello_nf-core/index.md)** training course will introduce you to the nf-core community-curated pipelines and development framework, designed to help you write reproducible, scalable, and standardized workflows. You’ll learn how to use existing nf-core pipelines, contribute to their development, and even start building your own, supported by best practices and a vibrant community. If you’re ready to apply your Nextflow skills in real-world projects, this is the perfect next step. + +### 1.3. Master more advanced Nextflow features In the Hello Nextflow course, we keep the level of technical complexity low on purpose to avoid overloading you with information you don't need in order to get started with Nextflow. As you move forward with your work, you're going to want to learn how to use the full feature set and power of Nextflow. -To that end, we are currently working on a collection of Side Quests, which are meant to be short standalone courses that go deep into specific topics like testing, metadata handling, using conditional statements and the differences between working on HPC _vs._ cloud. +To that end, we are currently working on a **collection of [Side Quests](../side_quests/index.md)**, which are meant to be short standalone courses that go deep into specific topics like testing, metadata handling, using conditional statements and the differences between working on HPC _vs._ cloud. -In the meantime, feel free to **browse the [Fundamentals Training](../basic_training/index.md) and [Advanced Training](../advanced/index.md)** to find training exercises about the topics that interest you. +For any topics that's not covered there yet, **browse the [Fundamentals Training](../basic_training/index.md) and [Advanced Training](../advanced/index.md)** to find training materials about the topics that interest you. -### 3. Learn how to use nf-core resources and the Seqera Platform +--- -**The [nf-core project](https://nf-co.re/) is a worldwide collaborative effort to develop standardized open-source pipelines for a wide range of scientific research applications.** -It includes [over 100 pipelines](https://nf-co.re/pipelines/) that are available for use out of the box and [well over 1400 process modules](https://nf-co.re/modules/) that can be integrated into your own projects, as well as a rich set of developer tools. +## 2. Check out Seqera Platform **[Seqera Platform](https://seqera.io/) is the best way to run Nextflow in practice.** -It is a cloud-based platform that you can connect to your own compute infrastructure to make it much easier to launch and manage your workflows. + +It is a cloud-based platform developed by the creators of Nextflow that you can connect to your own compute infrastructure (whether local, HPC or cloud) to make it much easier to launch and manage your workflows, as well as manage your data and run analyses interactively in a cloud environment. + The Free Tier is available for free use by everyone (with usage quotas). Qualifying academics can get free Pro-level access (no usage limitations) through the [Academic Program](https://seqera.typeform.com/to/SRB8Ci3n). -We are currently developing a short training course demonstrating how to use both of these resources (either independently or in combination). -In the meantime, check out the [nf-core docs](https://nf-co.re/docs/) and the [Seqera Platform tutorials](https://docs.seqera.io/platform/latest/getting-started/quickstart-demo/comm-showcase) +Have a look at the [Seqera Platform tutorials](https://docs.seqera.io/platform/latest/getting-started/quickstart-demo/comm-showcase) to see if this might be useful to you. + +--- ### That's it for now! diff --git a/docs/hello_nf-core/next_steps.md b/docs/hello_nf-core/next_steps.md index 31cf0b0ea..ab36a9958 100644 --- a/docs/hello_nf-core/next_steps.md +++ b/docs/hello_nf-core/next_steps.md @@ -2,26 +2,45 @@ Congrats again on completing the **Hello nf-core** training course and thank you for completing our survey! -**Here are our top 3 recommendations for what you can do next to take your Nextflow skills to the next level.** +--- -## 1. Get involved with the nf-core community +## 1. Top 3 ways to level up your Nextflow skills + +Here are our top three recommendations for what to do next based on the course you just completed. + +### 1.1. Get involved with the nf-core community **The nf-core community is a welcoming, collaborative space** where you can keep learning, connect with others, and make meaningful contributions. Join the [nf-core Slack workspace](https://nf-co.re/join/slack) to ask questions, share ideas, and stay updated. Whether you attend a hackathon, help improve a pipeline, or just chat with fellow community members, getting involved is a great way to grow your skills and be part of the future of open bioinformatics. -## 2. See how Nextflow can be applied to a scientific analysis use case +### 1.2. Apply Nextflow to a scientific analysis use case **Check out the [Nextflow for Science](../nf4_science/index.md) page** for a list of short standalone courses that demonstrate how to apply the basic concepts and mechanisms presented in Hello Nextflow to common scientific analysis use cases. If you don't see your domain represented by a relatable use case, let us know in the [Community forum](https://community.seqera.io/) so we can add it to our development list. -## 3. Learn to master more advanced Nextflow features +### 1.3. Master more advanced Nextflow features In the Hello courses, we keep the level of technical complexity low on purpose to avoid overloading you with information you don't need in order to get started with Nextflow. As you move forward with your work, you're going to want to learn how to use the full feature set and power of Nextflow. -To that end, we are currently working on a collection of Side Quests, which are meant to be short standalone courses that go deep into specific topics like testing, metadata handling, using conditional statements and the differences between working on HPC _vs._ cloud. +To that end, we are currently working on a **collection of [Side Quests](../side_quests/index.md)**, which are meant to be short standalone courses that go deep into specific topics like testing, metadata handling, using conditional statements and the differences between working on HPC _vs._ cloud. + +For any topics that's not covered there yet, **browse the [Fundamentals Training](../basic_training/index.md) and [Advanced Training](../advanced/index.md)** to find training materials about the topics that interest you. + +--- + +## 2. Check out Seqera Platform + +**[Seqera Platform](https://seqera.io/) is the best way to run Nextflow in practice.** + +It is a cloud-based platform developed by the creators of Nextflow that you can connect to your own compute infrastructure (whether local, HPC or cloud) to make it much easier to launch and manage your workflows, as well as manage your data and run analyses interactively in a cloud environment. + +The Free Tier is available for free use by everyone (with usage quotas). +Qualifying academics can get free Pro-level access (no usage limitations) through the [Academic Program](https://seqera.typeform.com/to/SRB8Ci3n). + +Have a look at the [Seqera Platform tutorials](https://docs.seqera.io/platform/latest/getting-started/quickstart-demo/comm-showcase) to see if this might be useful to you. -In the meantime, feel free to **browse the [Fundamentals Training](../basic_training/index.md) and [Advanced Training](../advanced/index.md)** to find training exercises about the topics that interest you. +--- ### That's it for now! diff --git a/docs/nf4_science/rnaseq/next_steps.md b/docs/nf4_science/rnaseq/next_steps.md index c800b3dcf..357c9e50b 100644 --- a/docs/nf4_science/rnaseq/next_steps.md +++ b/docs/nf4_science/rnaseq/next_steps.md @@ -2,35 +2,48 @@ Congrats again on completing the Nextflow For RNAseq training course and thank you for completing our survey! -**Here are our top 3 recommendations for what you can do next to take your Nextflow skills to the next level.** +--- -### 1. See how what you just learned applies to another scientific analysis use case +## 1. Top 3 ways to level up your Nextflow skills -**Check out the [Nextflow for Science](../index.md) page** for a list of short standalone courses that demonstrate how to apply the basic concepts and mechanisms presented in Hello Nextflow to common scientific analysis use cases. +Here are our top three recommendations for what to do next based on the course you just completed. + +### 1.1. Apply Nextflow to other scientific analysis use cases + +**Check out the [Nextflow for Science](../nf4_science/index.md) page** for a list of other short standalone courses that demonstrate how to apply the basic concepts and mechanisms presented in Hello Nextflow to common scientific analysis use cases. If you don't see your domain represented by a relatable use case, let us know in the [Community forum](https://community.seqera.io/) so we can add it to our development list. -### 2. Delve into the details +### 1.2. Get started with nf-core + +**[nf-core](https://nf-co.re/)** is a worldwide collaborative effort to develop standardized open-source pipelines for a wide range of scientific research applications.\*\* +The project includes [over 100 pipelines](https://nf-co.re/pipelines/) that are available for use out of the box and [well over 1400 process modules](https://nf-co.re/modules/) that can be integrated into your own projects, as well as a rich set of developer tools. + +The **[Hello nf-core](../../hello_nf-core/index.md)** training course will introduce you to the nf-core community-curated pipelines and development framework, designed to help you write reproducible, scalable, and standardized workflows. You’ll learn how to use existing nf-core pipelines, contribute to their development, and even start building your own, supported by best practices and a vibrant community. If you’re ready to apply your Nextflow skills in real-world projects, this is the perfect next step. -In the Hello Nextflow course, we keep the level of technical complexity low on purpose to avoid overloading you with information you don't need in order to get started with Nextflow. +### 1.3. Master more advanced Nextflow features + +In the Hello courses, we keep the level of technical complexity low on purpose to avoid overloading you with information you don't need in order to get started with Nextflow. As you move forward with your work, you're going to want to learn how to use the full feature set and power of Nextflow. -To that end, we are currently working on a collection of Side Quests, which are meant to be short standalone courses that go deep into specific topics like testing, metadata handling, using conditional statements and the differences between working on HPC _vs._ cloud. +To that end, we are currently working on a **collection of [Side Quests](../side_quests/index.md)**, which are meant to be short standalone courses that go deep into specific topics like testing, metadata handling, using conditional statements and the differences between working on HPC _vs._ cloud. -In the meantime, feel free to **browse the [Fundamentals Training](../../basic_training/index.md) and [Advanced Training](../../advanced/index.md)** to find training exercises about the topics that interest you. +For any topics that's not covered there yet, **browse the [Fundamentals Training](../basic_training/index.md) and [Advanced Training](../advanced/index.md)** to find training materials about the topics that interest you. -### 3. Learn how to use nf-core resources and the Seqera Platform +--- -**The [nf-core project](https://nf-co.re/) is a worldwide collaborative effort to develop standardized open-source pipelines for a wide range of scientific research applications.** -It includes [over 100 pipelines](https://nf-co.re/pipelines/) that are available for use out of the box and [well over 1400 process modules](https://nf-co.re/modules/) that can be integrated into your own projects, as well as a rich set of developer tools. +## 2. Check out Seqera Platform **[Seqera Platform](https://seqera.io/) is the best way to run Nextflow in practice.** -It is a cloud-based platform that you can connect to your own compute infrastructure to make it much easier to launch and manage your workflows. + +It is a cloud-based platform developed by the creators of Nextflow that you can connect to your own compute infrastructure (whether local, HPC or cloud) to make it much easier to launch and manage your workflows, as well as manage your data and run analyses interactively in a cloud environment. + The Free Tier is available for free use by everyone (with usage quotas). Qualifying academics can get free Pro-level access (no usage limitations) through the [Academic Program](https://seqera.typeform.com/to/SRB8Ci3n). -We are currently developing a short training course demonstrating how to use both of these resources (either independently or in combination). -In the meantime, check out the [nf-core docs](https://nf-co.re/docs/) and the [Seqera Platform tutorials](https://docs.seqera.io/platform/latest/getting-started/quickstart-demo/comm-showcase) +Have a look at the [Seqera Platform tutorials](https://docs.seqera.io/platform/latest/getting-started/quickstart-demo/comm-showcase) to see if this might be useful to you. + +--- ### That's it for now! From 70cd57ceaeb861f0996dbc52c1913cc1de783851 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 00:24:15 -0400 Subject: [PATCH 34/43] Add next steps page to Genomics course too --- docs/nf4_science/genomics/next_steps.md | 50 +++++++++++++++++++++++++ docs/nf4_science/genomics/survey.md | 7 ++++ mkdocs.yml | 2 + 3 files changed, 59 insertions(+) create mode 100644 docs/nf4_science/genomics/next_steps.md create mode 100644 docs/nf4_science/genomics/survey.md diff --git a/docs/nf4_science/genomics/next_steps.md b/docs/nf4_science/genomics/next_steps.md new file mode 100644 index 000000000..3058564df --- /dev/null +++ b/docs/nf4_science/genomics/next_steps.md @@ -0,0 +1,50 @@ +# Next Steps + +Congrats again on completing the Nextflow For Genomics training course and thank you for completing our survey! + +--- + +## 1. Top 3 ways to level up your Nextflow skills + +Here are our top three recommendations for what to do next based on the course you just completed. + +### 1.1. Apply Nextflow to other scientific analysis use cases + +**Check out the [Nextflow for Science](../nf4_science/index.md) page** for a list of other short standalone courses that demonstrate how to apply the basic concepts and mechanisms presented in Hello Nextflow to common scientific analysis use cases. + +If you don't see your domain represented by a relatable use case, let us know in the [Community forum](https://community.seqera.io/) so we can add it to our development list. + +### 1.2. Get started with nf-core + +**[nf-core](https://nf-co.re/)** is a worldwide collaborative effort to develop standardized open-source pipelines for a wide range of scientific research applications.\*\* +The project includes [over 100 pipelines](https://nf-co.re/pipelines/) that are available for use out of the box and [well over 1400 process modules](https://nf-co.re/modules/) that can be integrated into your own projects, as well as a rich set of developer tools. + +The **[Hello nf-core](../../hello_nf-core/index.md)** training course will introduce you to the nf-core community-curated pipelines and development framework, designed to help you write reproducible, scalable, and standardized workflows. You’ll learn how to use existing nf-core pipelines, contribute to their development, and even start building your own, supported by best practices and a vibrant community. If you’re ready to apply your Nextflow skills in real-world projects, this is the perfect next step. + +### 1.3. Master more advanced Nextflow features + +In the Hello courses, we keep the level of technical complexity low on purpose to avoid overloading you with information you don't need in order to get started with Nextflow. +As you move forward with your work, you're going to want to learn how to use the full feature set and power of Nextflow. + +To that end, we are currently working on a **collection of [Side Quests](../side_quests/index.md)**, which are meant to be short standalone courses that go deep into specific topics like testing, metadata handling, using conditional statements and the differences between working on HPC _vs._ cloud. + +For any topics that's not covered there yet, **browse the [Fundamentals Training](../basic_training/index.md) and [Advanced Training](../advanced/index.md)** to find training materials about the topics that interest you. + +--- + +## 2. Check out Seqera Platform + +**[Seqera Platform](https://seqera.io/) is the best way to run Nextflow in practice.** + +It is a cloud-based platform developed by the creators of Nextflow that you can connect to your own compute infrastructure (whether local, HPC or cloud) to make it much easier to launch and manage your workflows, as well as manage your data and run analyses interactively in a cloud environment. + +The Free Tier is available for free use by everyone (with usage quotas). +Qualifying academics can get free Pro-level access (no usage limitations) through the [Academic Program](https://seqera.typeform.com/to/SRB8Ci3n). + +Have a look at the [Seqera Platform tutorials](https://docs.seqera.io/platform/latest/getting-started/quickstart-demo/comm-showcase) to see if this might be useful to you. + +--- + +### That's it for now! + +**Good luck in your Nextflow journey and don't hesitate to let us know in the [Community forum](https://community.seqera.io/) what else we could do to help.** diff --git a/docs/nf4_science/genomics/survey.md b/docs/nf4_science/genomics/survey.md new file mode 100644 index 000000000..3d813b744 --- /dev/null +++ b/docs/nf4_science/genomics/survey.md @@ -0,0 +1,7 @@ +# Feedback survey + +Before you move on, please complete this short 4-question survey to rate the training, share any feedback you may have about your experience, and let us know what else we could do to help you in your Nextflow journey. + +This should take you less than a minute to complete. Thank you for helping us improve our training materials for everyone! + +
diff --git a/mkdocs.yml b/mkdocs.yml index 8182c87d7..33bcd7d58 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,6 +35,8 @@ nav: - nf4_science/genomics/02_joint_calling.md - nf4_science/genomics/03_modules.md - nf4_science/genomics/04_testing.md + - nf4_science/genomics/survey.md + - nf4_science/genomics/next_steps.md - Nextflow for RNAseq: - nf4_science/rnaseq/index.md - nf4_science/rnaseq/00_orientation.md From fb70c7f60b6ea01622a087a376dbbf36ac418d4b Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 01:03:02 -0400 Subject: [PATCH 35/43] Add logo to index page --- docs/hello_nf-core/img/nf-core-logo.png | Bin 0 -> 84414 bytes docs/hello_nf-core/index.md | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 docs/hello_nf-core/img/nf-core-logo.png diff --git a/docs/hello_nf-core/img/nf-core-logo.png b/docs/hello_nf-core/img/nf-core-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..91ddb58d8ada2682b8edb9528c09479f248e3010 GIT binary patch literal 84414 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 Date: Wed, 4 Jun 2025 01:09:33 -0400 Subject: [PATCH 36/43] formatting fixes --- docs/hello_nf-core/01_run_demo.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 4be6357b4..6f2d8ee4b 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -1,4 +1,4 @@ -# Part 1: Run nf-core/demo +# Part 1: Run a demo pipeline In this first part of the Hello nf-core training course, we show you how to find and try out an nf-core pipeline, understand how the code is organized, and recognize how it differs from plain Nextflow code as shown in [Hello Nextflow](../hello_nextflow/index.md). @@ -122,7 +122,7 @@ This creates a shortcut that makes it easier to explore the code we just downloa tree -L 2 pipelines ``` -```bash +```console title="Output" pipelines └── nf-core └── demo @@ -130,7 +130,7 @@ pipelines Now we can more easily peek into the source code as needed. -But first, let's try running your first nf-core pipeline! +But first, let's try running our first nf-core pipeline! ### Takeaway @@ -142,7 +142,7 @@ Learn how to try out an nf-core pipeline with minimal effort. --- -## 2. Run the pipeline with the provided test profile +## 2. Run the pipeline with its test profile Conveniently, every nf-core pipeline comes with a `test` profile. This is a minimal set of configuration settings for the pipeline to run using a small test dataset hosted in the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. It's a great way to quickly try out a pipeline at small scale. @@ -302,7 +302,7 @@ This is standard for nf-core pipelines. Congratulations! You have just run your first nf-core pipeline. -#### Takeaway +### Takeaway You know how to run an nf-core pipeline using its built-in test profile. From bffe780f219f9c2a5182eea8b67292e79811917f Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 01:11:06 -0400 Subject: [PATCH 37/43] formatting sub lists... --- docs/hello_nf-core/02_rewrite_hello.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index f5b41744f..9a8a16cd6 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -43,10 +43,10 @@ This TUI will ask you to provide basic information about your pipeline and will 2. On the `Choose pipeline type` screen, click **Custom**. 3. Enter your pipeline details as follows (replacing `< YOUR NAME >` with your own name), then click **Next**. - - **GitHub organisation:** core - - **Workflow name:** hello - - **A short description of your pipeline:** A basic nf-core style version of Hello Nextflow - - **Name of the main author / authors:** < YOUR NAME > +- **GitHub organisation:** core +- **Workflow name:** hello +- **A short description of your pipeline:** A basic nf-core style version of Hello Nextflow +- **Name of the main author / authors:** < YOUR NAME > 4. On the Template features screen, set `Toggle all features` to **off**, then selectively **enable** the following. Check your selections and click **Continue**. From c5ed364018239163b19ec771df0b11d8e046bcfb Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 01:18:14 -0400 Subject: [PATCH 38/43] nested lists fix? --- docs/hello_nf-core/02_rewrite_hello.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 9a8a16cd6..55ee68c54 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -43,10 +43,10 @@ This TUI will ask you to provide basic information about your pipeline and will 2. On the `Choose pipeline type` screen, click **Custom**. 3. Enter your pipeline details as follows (replacing `< YOUR NAME >` with your own name), then click **Next**. -- **GitHub organisation:** core -- **Workflow name:** hello -- **A short description of your pipeline:** A basic nf-core style version of Hello Nextflow -- **Name of the main author / authors:** < YOUR NAME > + - **GitHub organisation:** core + - **Workflow name:** hello + - **A short description of your pipeline:** A basic nf-core style version of Hello Nextflow + - **Name of the main author(s):** < YOUR NAME > 4. On the Template features screen, set `Toggle all features` to **off**, then selectively **enable** the following. Check your selections and click **Continue**. From 50a684378b22afb85cebf56b629af144768890d2 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 01:34:23 -0400 Subject: [PATCH 39/43] workaround for nested lists not formatting correctly --- docs/hello_nf-core/02_rewrite_hello.md | 60 ++++++++++++++++++-------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 55ee68c54..8a75dee1d 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -43,18 +43,22 @@ This TUI will ask you to provide basic information about your pipeline and will 2. On the `Choose pipeline type` screen, click **Custom**. 3. Enter your pipeline details as follows (replacing `< YOUR NAME >` with your own name), then click **Next**. - - **GitHub organisation:** core - - **Workflow name:** hello - - **A short description of your pipeline:** A basic nf-core style version of Hello Nextflow - - **Name of the main author(s):** < YOUR NAME > +``` +[ ] GitHub organisation: core +[ ] Workflow name: hello +[ ] A short description of your pipeline: A basic nf-core style version of Hello Nextflow +[ ] Name of the main author(s): < YOUR NAME > +``` 4. On the Template features screen, set `Toggle all features` to **off**, then selectively **enable** the following. Check your selections and click **Continue**. - - `Add configuration files` - - `Use nf-core components` - - `Use nf-schema` - - `Add documentation` - - `Add testing profiles` +``` +[ ] Add configuration files +[ ] Use nf-core components +[ ] Use nf-schema +[ ] Add documentation +[ ] Add testing profiles +``` 5. On the `Final details` screen, click **Finish**. Wait for the pipeline to be created, then click **Continue**. 6. On the Create GitHub repository screen, click **Finish without creating a repo**. This will display instructions for creating a GitHub repository later. Ignore these and click **Close**. @@ -562,8 +566,8 @@ nextflow run ./original-hello !!! note - Here you see the advantage of using the `main.nf` naming convention, which allows us to omit including the name of the workflow file in the command. - If we had named it `something_else.nf`, we would have had to do `nextflow run original-hello/something_else.nf`. + Here you see the advantage of using the `main.nf` naming convention. + If we had named the entrypoint workflow `something_else.nf`, we would have had to do `nextflow run original-hello/something_else.nf`. If you made all the changes correctly, this should run to completion. @@ -662,7 +666,7 @@ We're going to tackle this in the following stages: We're going to ignore the version capture for this first pass and will look at how to wire that up in a later section. -### 3.1. Copy over the modules and set up module imports +### 3.1. Copy the modules and set up module imports In the original workflow, the four processes are stored in modules, so we need to copy those over to this new project (into a new `local` directory) and add import statements to the workflow file. @@ -837,15 +841,15 @@ This looks great, but we still need to update the name of the channel we're pass === "After" ```groovy title="core-hello/workflows/hello.nf" linenums="26" - // emit a greeting - sayHello(greeting_ch) + // emit a greeting (updated to use the nf-core convention for samplesheets) + sayHello(ch_samplesheet) ``` === "Before" - ```groovy title="core-hello/workflows/hello.nf" linenums="23" - // emit a greeting (updated to use the nf-core convention for samplesheets) - sayHello(ch_samplesheet) + ```groovy title="core-hello/workflows/hello.nf" linenums="26" + // emit a greeting + sayHello(greeting_ch) ``` Now the workflow logic is correctly wired up. @@ -1257,6 +1261,28 @@ results Anything that is hooked up to the nf-core template code gets put into a directory generated automatically, called `core-hello-results/`. This includes the various reports produced by the nf-core utility subworkflows, which you can find under `core-hello-results/pipeline_info`. +```bash +tree core-hello-results +``` + +```console title="Output" +core-hello-results +└── pipeline_info + ├── execution_report_2025-06-03_18-22-28.html + ├── execution_report_2025-06-03_20-11-39.html + ├── execution_timeline_2025-06-03_18-22-28.html + ├── execution_timeline_2025-06-03_20-11-39.html + ├── execution_trace_2025-06-03_18-22-28.txt + ├── execution_trace_2025-06-03_20-10-11.txt + ├── execution_trace_2025-06-03_20-11-39.txt + ├── hello_software_versions.yml + ├── params_2025-06-03_18-22-32.json + ├── params_2025-06-03_20-10-15.json + ├── params_2025-06-03_20-11-43.json + ├── pipeline_dag_2025-06-03_18-22-28.html + └── pipeline_dag_2025-06-03_20-11-39.html +``` + In our case, we didn't explicitly mark anything else as an output, so there's nothing else there. And there it is! It may seem like a lot of work to accomplish the same result as the original pipeline, but you do get all those lovely reports generated automatically, and you now have a solid foundation for taking advantage of additional features of nf-core, including input validation and some neat metadata handling capabilities that we'll cover in a later section. From ea70e940cb7787b0c8e765fd899dcbd0732ef53e Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 01:39:39 -0400 Subject: [PATCH 40/43] aaaargh --- docs/hello_nf-core/02_rewrite_hello.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 8a75dee1d..60b61ca54 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -39,9 +39,9 @@ Running this command will open a Text User Interface (TUI) for pipeline creation This TUI will ask you to provide basic information about your pipeline and will provide you with a choice of features to include or exclude in your pipeline scaffold. -1. On the welcome screen, click **Let's go!**. -2. On the `Choose pipeline type` screen, click **Custom**. -3. Enter your pipeline details as follows (replacing `< YOUR NAME >` with your own name), then click **Next**. +- On the welcome screen, click **Let's go!**. +- On the `Choose pipeline type` screen, click **Custom**. +- Enter your pipeline details as follows (replacing `< YOUR NAME >` with your own name), then click **Next**. ``` [ ] GitHub organisation: core @@ -50,7 +50,7 @@ This TUI will ask you to provide basic information about your pipeline and will [ ] Name of the main author(s): < YOUR NAME > ``` -4. On the Template features screen, set `Toggle all features` to **off**, then selectively **enable** the following. Check your selections and click **Continue**. +- On the Template features screen, set `Toggle all features` to **off**, then selectively **enable** the following. Check your selections and click **Continue**. ``` [ ] Add configuration files @@ -60,8 +60,8 @@ This TUI will ask you to provide basic information about your pipeline and will [ ] Add testing profiles ``` -5. On the `Final details` screen, click **Finish**. Wait for the pipeline to be created, then click **Continue**. -6. On the Create GitHub repository screen, click **Finish without creating a repo**. This will display instructions for creating a GitHub repository later. Ignore these and click **Close**. +- On the `Final details` screen, click **Finish**. Wait for the pipeline to be created, then click **Continue**. +- On the Create GitHub repository screen, click **Finish without creating a repo**. This will display instructions for creating a GitHub repository later. Ignore these and click **Close**. Once the TUI closes, you should see the following console output. From 98cdfa75204a2de6511da645fb477a7b98bb5d74 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 01:42:46 -0400 Subject: [PATCH 41/43] alternate attempt to previous --- docs/hello_nf-core/02_rewrite_hello.md | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 60b61ca54..28b97fd92 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -42,24 +42,16 @@ This TUI will ask you to provide basic information about your pipeline and will - On the welcome screen, click **Let's go!**. - On the `Choose pipeline type` screen, click **Custom**. - Enter your pipeline details as follows (replacing `< YOUR NAME >` with your own name), then click **Next**. - -``` -[ ] GitHub organisation: core -[ ] Workflow name: hello -[ ] A short description of your pipeline: A basic nf-core style version of Hello Nextflow -[ ] Name of the main author(s): < YOUR NAME > -``` - + - GitHub organisation: core + - Workflow name: hello + - A short description of your pipeline: A basic nf-core style version of Hello Nextflow + - Name of the main author(s): < YOUR NAME > - On the Template features screen, set `Toggle all features` to **off**, then selectively **enable** the following. Check your selections and click **Continue**. - -``` -[ ] Add configuration files -[ ] Use nf-core components -[ ] Use nf-schema -[ ] Add documentation -[ ] Add testing profiles -``` - + - Add configuration files + - Use nf-core components + - Use nf-schema + - Add documentation + - Add testing profiles - On the `Final details` screen, click **Finish**. Wait for the pipeline to be created, then click **Continue**. - On the Create GitHub repository screen, click **Finish without creating a repo**. This will display instructions for creating a GitHub repository later. Ignore these and click **Close**. From 7dd20f70b5535b29042a2ee6e2d4bb9bd6ce4108 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 01:45:46 -0400 Subject: [PATCH 42/43] least objectionable workaround --- docs/hello_nf-core/02_rewrite_hello.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/hello_nf-core/02_rewrite_hello.md b/docs/hello_nf-core/02_rewrite_hello.md index 28b97fd92..60b61ca54 100644 --- a/docs/hello_nf-core/02_rewrite_hello.md +++ b/docs/hello_nf-core/02_rewrite_hello.md @@ -42,16 +42,24 @@ This TUI will ask you to provide basic information about your pipeline and will - On the welcome screen, click **Let's go!**. - On the `Choose pipeline type` screen, click **Custom**. - Enter your pipeline details as follows (replacing `< YOUR NAME >` with your own name), then click **Next**. - - GitHub organisation: core - - Workflow name: hello - - A short description of your pipeline: A basic nf-core style version of Hello Nextflow - - Name of the main author(s): < YOUR NAME > + +``` +[ ] GitHub organisation: core +[ ] Workflow name: hello +[ ] A short description of your pipeline: A basic nf-core style version of Hello Nextflow +[ ] Name of the main author(s): < YOUR NAME > +``` + - On the Template features screen, set `Toggle all features` to **off**, then selectively **enable** the following. Check your selections and click **Continue**. - - Add configuration files - - Use nf-core components - - Use nf-schema - - Add documentation - - Add testing profiles + +``` +[ ] Add configuration files +[ ] Use nf-core components +[ ] Use nf-schema +[ ] Add documentation +[ ] Add testing profiles +``` + - On the `Final details` screen, click **Finish**. Wait for the pipeline to be created, then click **Continue**. - On the Create GitHub repository screen, click **Finish without creating a repo**. This will display instructions for creating a GitHub repository later. Ignore these and click **Close**. From 88213b3b52410fa98d64c2dac3ebcb632148b404 Mon Sep 17 00:00:00 2001 From: Geraldine Van der Auwera Date: Wed, 4 Jun 2025 01:58:09 -0400 Subject: [PATCH 43/43] a few more tweaks --- docs/hello_nf-core/01_run_demo.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/hello_nf-core/01_run_demo.md b/docs/hello_nf-core/01_run_demo.md index 6f2d8ee4b..a6628c097 100644 --- a/docs/hello_nf-core/01_run_demo.md +++ b/docs/hello_nf-core/01_run_demo.md @@ -142,11 +142,14 @@ Learn how to try out an nf-core pipeline with minimal effort. --- -## 2. Run the pipeline with its test profile +## 2. Try out the pipeline with its test profile -Conveniently, every nf-core pipeline comes with a `test` profile. +Conveniently, every nf-core pipeline comes with a test profile. This is a minimal set of configuration settings for the pipeline to run using a small test dataset hosted in the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. It's a great way to quickly try out a pipeline at small scale. +### 2.1. Examine the test profile + +It's good practice to check what a pipeline's test profile specifies before running it. The `test` profile for `nf-core/demo` is shown below: ```groovy title="conf/test.config" linenums="1" hl_lines="26" @@ -180,10 +183,14 @@ params { } ``` -This tells us that the `nf-core/demo` `test` profile already specifies the input parameter, so you don't have to provide any input yourself. -However, the `outdir` parameter is not included in the `test` profile, so you have to add it to the execution command using the `--outdir` flag. +This tells us that the `nf-core/demo` test profile already specifies the input parameter, so you don't have to provide any input yourself. +However, the `outdir` parameter is not included in the test profile, so we will have to add it to the execution command using the `--outdir` flag. + +### 2.2. Run the pipeline + +Our examination of the test profile above told us what pipeline argument(s) we need to specify: just `--outdir`. -Here, we're also going to specify `-profile docker`, which by nf-core convention enables the use of Docker containers. +We're also going to specify `-profile docker,test`, which by nf-core convention enables the use of Docker containers, and of course, invokes the test profile. Let's try it! @@ -266,6 +273,8 @@ This tells us that three processes were run, corresponding to the three tools sh These includes the names of their parent workflows and reflect the modularity of the pipeline code. We will go into more detail about that shortly. +### 2.3. Examine the pipeline's outputs + Finally, let's have a look at the `demo-results` directory produced by the pipeline. ```bash @@ -346,13 +355,9 @@ pipelines/nf-core/demo There's a lot going on in there, so we'll tackle this in stages. We're going to look at the following categories: -1. Pipeline code components - -- main script in `main.nf` -- modular components in `workflows`, `subworkflows` and `modules` - +1. Pipeline code components (`main.nf`, `workflows`, `subworkflows`, `modules`) 2. Configuration, parameters and inputs -3. Documentation and other stuff +3. Documentation and related assets Let's start with the code proper, though note that for now, we're going to focus on how everything is organized, without looking at the actual code just yet.