From 2c1a79b23215be40d59f934548e601c8dcffbb39 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Mar 2021 13:09:28 +0100 Subject: [PATCH 01/23] Bump to 1.14dev --- CHANGELOG.md | 4 ++++ setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60fce6a0cd..b2de6bf58d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # nf-core/tools: Changelog +## v1.14dev + +_..nothing yet.._ + ## [v1.13 - Copper Crocodile](https://github.com/nf-core/tools/releases/tag/1.13) - [2021-03-18] ### Template diff --git a/setup.py b/setup.py index b9b40abc8a..f2fa6aa93d 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages import sys -version = "1.13" +version = "1.14dev" with open("README.md") as f: readme = f.read() From fb3f68e92449335cc2190ab8842b111a1e60d1fc Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Mar 2021 19:28:00 +0100 Subject: [PATCH 02/23] Pipeline lint markdown - drop textwrap.dedent and do things manually --- nf_core/lint/__init__.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/nf_core/lint/__init__.py b/nf_core/lint/__init__.py index b52d1dd230..8a37626dc8 100644 --- a/nf_core/lint/__init__.py +++ b/nf_core/lint/__init__.py @@ -16,7 +16,6 @@ import re import rich import rich.progress -import textwrap import yaml import nf_core.utils @@ -431,24 +430,16 @@ def _get_results_md(self): comment_body_text = "Posted for pipeline commit {}".format(self.git_sha[:7]) if self.git_sha is not None else "" timestamp = now.strftime("%Y-%m-%d %H:%M:%S") - markdown = textwrap.dedent( - f""" - #### `nf-core lint` overall result: {overall_result} - - {comment_body_text} - - ```diff{test_passed_count}{test_ignored_count}{test_fixed_count}{test_warning_count}{test_failure_count} - ``` - -
- - {test_failures}{test_warnings}{test_ignored}{test_fixed}{test_passes}### Run details: - - * nf-core/tools version {nf_core.__version__} - * Run at `{timestamp}` - -
- """ + markdown = ( + f"#### `nf-core lint` overall result: {overall_result}\n\n" + f"{comment_body_text}\n\n" + f"```diff{test_passed_count}{test_ignored_count}{test_fixed_count}{test_warning_count}{test_failure_count}\n" + "```\n\n" + "
\n" + f"{test_failures}{test_warnings}{test_ignored}{test_fixed}{test_passes}### Run details\n\n" + f"* nf-core/tools version {nf_core.__version__}\n" + f"* Run at `{timestamp}`\n\n" + "
\n" ) return markdown From 443bf5c7a8de99a598b23a841bfeb5d18db7df1b Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Mar 2021 19:29:23 +0100 Subject: [PATCH 03/23] Changelog, 1.13.1dev version --- CHANGELOG.md | 6 ++++-- setup.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2de6bf58d..2f384657da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ # nf-core/tools: Changelog -## v1.14dev +## v1.13.1dev -_..nothing yet.._ +### Bugs fixed + +* Fixed bug in pipeline linting markdown output that gets posted to PR comments [[#914]](https://github.com/nf-core/tools/issues/914) ## [v1.13 - Copper Crocodile](https://github.com/nf-core/tools/releases/tag/1.13) - [2021-03-18] diff --git a/setup.py b/setup.py index f2fa6aa93d..1a4aa78c7d 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages import sys -version = "1.14dev" +version = "1.13.1dev" with open("README.md") as f: readme = f.read() From 525cad763c73877f73aa94d789a1ec8e3365fce0 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Mar 2021 19:38:58 +0100 Subject: [PATCH 04/23] Branch CI fail comment - make a TLDR section at the top. --- nf_core/lint/__init__.py | 2 +- .../pipeline-template/.github/workflows/branch.yml | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/nf_core/lint/__init__.py b/nf_core/lint/__init__.py index 8a37626dc8..c235bdf14e 100644 --- a/nf_core/lint/__init__.py +++ b/nf_core/lint/__init__.py @@ -431,7 +431,7 @@ def _get_results_md(self): comment_body_text = "Posted for pipeline commit {}".format(self.git_sha[:7]) if self.git_sha is not None else "" timestamp = now.strftime("%Y-%m-%d %H:%M:%S") markdown = ( - f"#### `nf-core lint` overall result: {overall_result}\n\n" + f"## `nf-core lint` overall result: {overall_result}\n\n" f"{comment_body_text}\n\n" f"```diff{test_passed_count}{test_ignored_count}{test_fixed_count}{test_warning_count}{test_failure_count}\n" "```\n\n" diff --git a/nf_core/pipeline-template/.github/workflows/branch.yml b/nf_core/pipeline-template/.github/workflows/branch.yml index 5c880e7c5f..281d1af157 100644 --- a/nf_core/pipeline-template/.github/workflows/branch.yml +++ b/nf_core/pipeline-template/.github/workflows/branch.yml @@ -23,11 +23,19 @@ jobs: uses: mshick/add-pr-comment@v1 with: message: | + ## This PR is against the `master` branch :x: + + * Do not close this PR + * Click _Edit_ and change the `base` to `dev` + * This CI test will remain failed until you push a new commit + + --- + Hi @${{ github.event.pull_request.user.login }}, - It looks like this pull-request is has been made against the ${{github.event.pull_request.base.repo.full_name }} `master` branch. + It looks like this pull-request is has been made against the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) `master` branch. The `master` branch on nf-core repositories should always contain code from the latest release. - Because of this, PRs to `master` are only allowed if they come from the ${{github.event.pull_request.base.repo.full_name }} `dev` branch. + Because of this, PRs to `master` are only allowed if they come from the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) `dev` branch. You do not need to close this PR, you can change the target branch to `dev` by clicking the _"Edit"_ button at the top of this page. Note that even after this, the test will continue to show as failing until you push a new commit. From ee513d8acea33df25d80569c2bf3be3c4325da4a Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Mar 2021 19:41:08 +0100 Subject: [PATCH 05/23] Linting markdown - append :warning: to comment header if any warnings --- nf_core/lint/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nf_core/lint/__init__.py b/nf_core/lint/__init__.py index c235bdf14e..338c908bbb 100644 --- a/nf_core/lint/__init__.py +++ b/nf_core/lint/__init__.py @@ -347,6 +347,8 @@ def _get_results_md(self): """ # Overall header overall_result = "Passed :white_check_mark:" + if len(self.warned) > 0: + overall_result += " :warning:" if len(self.failed) > 0: overall_result = "Failed :x:" From d8a1a482063373d2a1d9e424a06c0e624f2ba64f Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Thu, 18 Mar 2021 19:49:42 +0100 Subject: [PATCH 06/23] Update readme logo to have a white background --- docs/images/nfcore-tools_logo.png | Bin 14038 -> 31762 bytes 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 docs/images/nfcore-tools_logo.png diff --git a/docs/images/nfcore-tools_logo.png b/docs/images/nfcore-tools_logo.png old mode 100755 new mode 100644 index 9a0659d9d1a50498fce7ea4b1855eee4387cbbea..e45813591e90a97ac9db506cfc2d854814e3726c GIT binary patch literal 31762 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 MUQMo0=CR-Z0jKc0jsO4v literal 14038 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 a;WO4yRlgQn5W}89gHx8*kgJuk2>U;G4*s+N From e71d85b5ce9c5000a4d111b2526ed9f7e69c4f7f Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 10:10:42 +0100 Subject: [PATCH 07/23] added check for missing emit statement --- nf_core/modules/lint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 00ef496e9d..fe773690bf 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -261,7 +261,6 @@ def _print_results(self, show_passed=False): log.debug("Printing final results") console = Console(force_terminal=rich_force_colors()) - # Find maximum module name length max_mod_name_len = 40 for idx, tests in enumerate([self.passed, self.warned, self.failed]): @@ -794,7 +793,8 @@ def _parse_output(self, line): output = [] if "meta" in line: output.append("meta") - # TODO: should we ignore outputs without emit statement? + if not "emit" in line: + self.failed.append(("missing_emit", f"Missing emit statement: {line}", self.main_nf)) if "emit" in line: output.append(line.split("emit:")[1].strip()) From 95ea08bc85f28132c0399638ea52d05cc240ee1b Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 10:20:09 +0100 Subject: [PATCH 08/23] fixed conda version warning --- nf_core/modules/lint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index fe773690bf..0c6ade24a5 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -721,9 +721,9 @@ def check_process_section(self, lines): # response = _bioconda_package(bp) response = nf_core.utils.anaconda_package(bp) except LookupError as e: - self.warned.append(e) + self.warned.append(("bioconda_version", "Conda version not specified correctly", self.main_nf)) except ValueError as e: - self.failed.append(e) + self.failed.append(("bioconda_version", "Conda version not specified correctly", self.main_nf)) else: # Check that required version is available at all if bioconda_version not in response.get("versions"): From 1d3f7167d96bd845b6a15124120bd6f21dc17e6d Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 10:35:04 +0100 Subject: [PATCH 09/23] remove extra space --- nf_core/modules/lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 0c6ade24a5..59229cffcd 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -794,7 +794,7 @@ def _parse_output(self, line): if "meta" in line: output.append("meta") if not "emit" in line: - self.failed.append(("missing_emit", f"Missing emit statement: {line}", self.main_nf)) + self.failed.append(("missing_emit", f"Missing emit statement: {line.strip()}", self.main_nf)) if "emit" in line: output.append(line.split("emit:")[1].strip()) From 359a644c0e5c7bf9330b780b9da2e2a41df7f95f Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 10:53:56 +0100 Subject: [PATCH 10/23] fixed output table order and wrapping --- nf_core/modules/lint.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 00ef496e9d..6a1bcd6616 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -290,8 +290,8 @@ def format_result(test_results, table): last_modname = mod.module_name table.add_row( Markdown(f"{mod.module_name}"), - Markdown(f"{result[1]}"), os.path.relpath(result[2], self.dir), + Markdown(f"{result[1]}"), style=row_style, ) return table @@ -308,8 +308,8 @@ def _s(some_list): ) table = Table(style="green", box=rich.box.ROUNDED) table.add_column("Module name", width=max_mod_name_len) - table.add_column("Test message", no_wrap=True) table.add_column("File path", no_wrap=True) + table.add_column("Test message") table = format_result(self.passed, table) console.print(table) @@ -322,8 +322,8 @@ def _s(some_list): ) table = Table(style="yellow", box=rich.box.ROUNDED) table.add_column("Module name", width=max_mod_name_len) - table.add_column("Test message", no_wrap=True) table.add_column("File path", no_wrap=True) + table.add_column("Test message") table = format_result(self.warned, table) console.print(table) @@ -334,8 +334,8 @@ def _s(some_list): ) table = Table(style="red", box=rich.box.ROUNDED) table.add_column("Module name", width=max_mod_name_len) - table.add_column("Test message", no_wrap=True) table.add_column("File path", no_wrap=True) + table.add_column("Test message") table = format_result(self.failed, table) console.print(table) From a7c42d62b5a12347a6e341fc5c32b0e4af9a2830 Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 11:04:42 +0100 Subject: [PATCH 11/23] raise exception when file found in modules/software --- nf_core/modules/lint.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 6a1bcd6616..ce4c1eec28 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -235,6 +235,8 @@ def get_installed_modules(self): # Get nf-core modules if os.path.exists(nfcore_modules_dir): for m in sorted([m for m in os.listdir(nfcore_modules_dir) if not m == "lib"]): + if not os.path.isdir(os.path.join(nfcore_modules_dir, m)): + raise ModuleLintException(f"File found in {nfcore_modules_dir} ({m})! This directly should only contain module directories.") m_content = os.listdir(os.path.join(nfcore_modules_dir, m)) # Not a module, but contains sub-modules if not "main.nf" in m_content: From 42e59285a13252d98d312bed14c8b7f8cae93576 Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 11:20:24 +0100 Subject: [PATCH 12/23] fixed missing key bug in meta.yml --- nf_core/modules/lint.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 00ef496e9d..8212a77a99 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -510,7 +510,7 @@ def lint_module_tests(self): def lint_meta_yml(self): """ Lint a meta yml file """ - required_keys = ["input", "output"] + required_keys = ["name", "input", "output"] try: with open(self.meta_yml, "r") as fh: meta_yaml = yaml.safe_load(fh) @@ -548,13 +548,13 @@ def lint_meta_yml(self): else: self.failed.append(("meta_output", "`{output}` missing in `meta.yml`", self.meta_yml)) - # confirm that the name matches the process name in main.nf - if meta_yaml["name"].upper() == self.process_name: - self.passed.append(("meta_name", "Correct name specified in `meta.yml`", self.meta_yml)) - else: - self.failed.append( - ("meta_name", "Conflicting process name between `meta.yml` and `main.nf`", self.meta_yml) - ) + # confirm that the name matches the process name in main.nf + if meta_yaml["name"].upper() == self.process_name: + self.passed.append(("meta_name", "Correct name specified in `meta.yml`", self.meta_yml)) + else: + self.failed.append( + ("meta_name", "Conflicting process name between `meta.yml` and `main.nf`", self.meta_yml) + ) def lint_main_nf(self): """ From 86e2560bf6db2f08426037876ee4f829e0724cfc Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 11:38:47 +0100 Subject: [PATCH 13/23] fixed issue with missing keys in meta.yml --- nf_core/modules/lint.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 8212a77a99..fa51c52192 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -511,6 +511,7 @@ def lint_module_tests(self): def lint_meta_yml(self): """ Lint a meta yml file """ required_keys = ["name", "input", "output"] + required_keys_lists = ["intput", "output"] try: with open(self.meta_yml, "r") as fh: meta_yaml = yaml.safe_load(fh) @@ -526,7 +527,7 @@ def lint_meta_yml(self): if not rk in meta_yaml.keys(): self.failed.append(("meta_required_keys", f"`{rk}` not specified", self.meta_yml)) contains_required_keys = False - elif not isinstance(meta_yaml[rk], list): + elif not isinstance(meta_yaml[rk], list) and rk in required_keys_lists: self.failed.append(("meta_required_keys", f"`{rk}` is not a list", self.meta_yml)) all_list_children = False if contains_required_keys: From 9b6d387674f08e5812e0f018206ff2bad4121d2a Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 11:57:10 +0100 Subject: [PATCH 14/23] added LintResult object --- nf_core/modules/lint.py | 67 ++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index fa51c52192..b07b28c1e5 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -34,6 +34,17 @@ class ModuleLintException(Exception): pass +class LintResult(object): + """ An object to hold the results of a lint test """ + + def __init__(self, mod, lint_test, message, file_path): + self.mod = mod + self.lint_test = lint_test + self.message = message + self.file_path = file_path + self.module_name = mod.module_name + + class ModuleLint(object): """ An object for linting modules either in a clone of the 'nf-core/modules' @@ -144,8 +155,8 @@ def lint_local_modules(self, local_modules): mod_object.main_nf = mod mod_object.module_name = os.path.basename(mod) mod_object.lint_main_nf() - self.passed = [(mod_object, m) for m in mod_object.passed] - self.warned = [(mod_object, m) for m in mod_object.warned + mod_object.failed] + self.passed += [LintResult(mod_object, m[0], m[1], m[2]) for m in mod_object.passed] + self.warned += [LintResult(mod_object, m[0], m[1], m[2]) for m in mod_object.warned + mod_object.failed] def lint_nfcore_modules(self, nfcore_modules): """ @@ -174,9 +185,9 @@ def lint_nfcore_modules(self, nfcore_modules): for mod in nfcore_modules: progress_bar.update(lint_progress, advance=1, test_name=mod.module_name) passed, warned, failed = mod.lint() - passed = [(mod, m) for m in passed] - warned = [(mod, m) for m in warned] - failed = [(mod, m) for m in failed] + passed = [LintResult(mod, m[0], m[1], m[2]) for m in passed] + warned = [LintResult(mod, m[0], m[1], m[2]) for m in warned] + failed = [LintResult(mod, m[0], m[1], m[2]) for m in failed] self.passed += passed self.warned += warned self.failed += failed @@ -266,8 +277,8 @@ def _print_results(self, show_passed=False): max_mod_name_len = 40 for idx, tests in enumerate([self.passed, self.warned, self.failed]): try: - for mod, msg in tests: - max_mod_name_len = max(len(mod.module_name), max_mod_name_len) + for lint_result in tests: + max_mod_name_len = max(len(lint_result.module_name), max_mod_name_len) except: pass @@ -281,17 +292,17 @@ def format_result(test_results, table): # I'd like to make an issue about this on the rich repo so leaving here in case there is a future fix last_modname = False row_style = None - for mod, result in test_results: - if last_modname and mod.module_name != last_modname: + for lint_result in test_results: + if last_modname and lint_result.module_name != last_modname: if row_style: row_style = None else: row_style = "magenta" - last_modname = mod.module_name + last_modname = lint_result.module_name table.add_row( - Markdown(f"{mod.module_name}"), - Markdown(f"{result[1]}"), - os.path.relpath(result[2], self.dir), + Markdown(f"{lint_result.module_name}"), + Markdown(f"{lint_result.file_path}"), + os.path.relpath(lint_result.message, self.dir), style=row_style, ) return table @@ -390,13 +401,11 @@ def check_module_changes(self, nfcore_modules): if r.status_code != 200: self.warned.append( - ( + LintResult( mod, - ( - "check_local_copy", - f"Could not fetch remote copy, skipping comparison.", - f"{os.path.join(mod.module_dir, f)}", - ), + "check_local_copy", + f"Could not fetch remote copy, skipping comparison.", + f"{os.path.join(mod.module_dir, f)}", ) ) else: @@ -406,24 +415,20 @@ def check_module_changes(self, nfcore_modules): if local_copy != remote_copy: all_modules_up_to_date = False self.warned.append( - ( + LintResult( mod, - ( - "check_local_copy", - "Local copy of module outdated", - f"{os.path.join(mod.module_dir, f)}", - ), + "check_local_copy", + "Local copy of module outdated", + f"{os.path.join(mod.module_dir, f)}", ) ) except UnicodeDecodeError as e: self.warned.append( - ( + LintResult( mod, - ( - "check_local_copy", - f"Could not decode file from {url}. Skipping comparison ({e})", - f"{os.path.join(mod.module_dir, f)}", - ), + "check_local_copy", + f"Could not decode file from {url}. Skipping comparison ({e})", + f"{os.path.join(mod.module_dir, f)}", ) ) From 171315d8365a05a1b1460aec60c1b9730aca9f1c Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 12:15:59 +0100 Subject: [PATCH 15/23] implemented sorting --- nf_core/modules/lint.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index b07b28c1e5..c626353a6e 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -9,6 +9,7 @@ from __future__ import print_function import logging +import operator import os import questionary import re @@ -273,6 +274,11 @@ def _print_results(self, show_passed=False): log.debug("Printing final results") console = Console(force_terminal=rich_force_colors()) + # Sort the results + self.passed.sort(key=operator.attrgetter("message", "module_name")) + self.warned.sort(key=operator.attrgetter("message", "module_name")) + self.failed.sort(key=operator.attrgetter("message", "module_name")) + # Find maximum module name length max_mod_name_len = 40 for idx, tests in enumerate([self.passed, self.warned, self.failed]): @@ -319,8 +325,8 @@ def _s(some_list): ) table = Table(style="green", box=rich.box.ROUNDED) table.add_column("Module name", width=max_mod_name_len) - table.add_column("Test message", no_wrap=True) - table.add_column("File path", no_wrap=True) + table.add_column("File path") + table.add_column("Test message") table = format_result(self.passed, table) console.print(table) @@ -333,8 +339,8 @@ def _s(some_list): ) table = Table(style="yellow", box=rich.box.ROUNDED) table.add_column("Module name", width=max_mod_name_len) - table.add_column("Test message", no_wrap=True) - table.add_column("File path", no_wrap=True) + table.add_column("File path") + table.add_column("Test message") table = format_result(self.warned, table) console.print(table) @@ -345,8 +351,8 @@ def _s(some_list): ) table = Table(style="red", box=rich.box.ROUNDED) table.add_column("Module name", width=max_mod_name_len) - table.add_column("Test message", no_wrap=True) - table.add_column("File path", no_wrap=True) + table.add_column("File path") + table.add_column("Test message") table = format_result(self.failed, table) console.print(table) From 67268774e4eb1a4edcfa95bfe036be04469714d8 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 19 Mar 2021 13:21:41 +0100 Subject: [PATCH 16/23] Black --- nf_core/modules/lint.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index ce4c1eec28..05e64f7d49 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -236,7 +236,9 @@ def get_installed_modules(self): if os.path.exists(nfcore_modules_dir): for m in sorted([m for m in os.listdir(nfcore_modules_dir) if not m == "lib"]): if not os.path.isdir(os.path.join(nfcore_modules_dir, m)): - raise ModuleLintException(f"File found in {nfcore_modules_dir} ({m})! This directly should only contain module directories.") + raise ModuleLintException( + f"File found in {nfcore_modules_dir} ({m})! This directly should only contain module directories." + ) m_content = os.listdir(os.path.join(nfcore_modules_dir, m)) # Not a module, but contains sub-modules if not "main.nf" in m_content: From e0c22bb84d63e80903a961b216b89060af778986 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 19 Mar 2021 13:22:59 +0100 Subject: [PATCH 17/23] Update nf_core/modules/lint.py --- nf_core/modules/lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 05e64f7d49..8da51246c9 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -237,7 +237,7 @@ def get_installed_modules(self): for m in sorted([m for m in os.listdir(nfcore_modules_dir) if not m == "lib"]): if not os.path.isdir(os.path.join(nfcore_modules_dir, m)): raise ModuleLintException( - f"File found in {nfcore_modules_dir} ({m})! This directly should only contain module directories." + f"File found in '{nfcore_modules_dir}': '{m}'! This directly should only contain module directories." ) m_content = os.listdir(os.path.join(nfcore_modules_dir, m)) # Not a module, but contains sub-modules From 0d5ba185a1fdce64896e1b923310004ff4b19f41 Mon Sep 17 00:00:00 2001 From: Kevin Menden Date: Fri, 19 Mar 2021 13:28:52 +0100 Subject: [PATCH 18/23] Update nf_core/modules/lint.py Co-authored-by: Phil Ewels --- nf_core/modules/lint.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 2c535d35d7..7772ea57a8 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -186,12 +186,9 @@ def lint_nfcore_modules(self, nfcore_modules): for mod in nfcore_modules: progress_bar.update(lint_progress, advance=1, test_name=mod.module_name) passed, warned, failed = mod.lint() - passed = [LintResult(mod, m[0], m[1], m[2]) for m in passed] - warned = [LintResult(mod, m[0], m[1], m[2]) for m in warned] - failed = [LintResult(mod, m[0], m[1], m[2]) for m in failed] - self.passed += passed - self.warned += warned - self.failed += failed + self.passed += [LintResult(mod, m[0], m[1], m[2]) for m in passed] + self.warned += [LintResult(mod, m[0], m[1], m[2]) for m in warned] + self.failed += [LintResult(mod, m[0], m[1], m[2]) for m in failed] def get_repo_type(self): """ From e496180c4fb64d13f1e0a7cb888acaef187024d0 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 19 Mar 2021 13:37:18 +0100 Subject: [PATCH 19/23] Copy over new branch CI comment text into tools repo workflow too --- .github/workflows/branch.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 5437eeba8b..1f3d241d5f 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -21,11 +21,19 @@ jobs: uses: mshick/add-pr-comment@v1 with: message: | + ## This PR is against the `master` branch :x: + + * Do not close this PR + * Click _Edit_ and change the `base` to `dev` + * This CI test will remain failed until you push a new commit + + --- + Hi @${{ github.event.pull_request.user.login }}, - It looks like this pull-request has been made against the ${{github.event.pull_request.base.repo.full_name}} `master` branch. + It looks like this pull-request is has been made against the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) `master` branch. The `master` branch on nf-core repositories should always contain code from the latest release. - Because of this, PRs to `master` are only allowed if they come from the ${{github.event.pull_request.base.repo.full_name}} `dev` branch. + Because of this, PRs to `master` are only allowed if they come from the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) `dev` branch. You do not need to close this PR, you can change the target branch to `dev` by clicking the _"Edit"_ button at the top of this page. Note that even after this, the test will continue to show as failing until you push a new commit. From f6d427b9828a73d3da9cb23272b78a15a11f4811 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 19 Mar 2021 13:37:34 +0100 Subject: [PATCH 20/23] Bump to v1.13.1 + changelog --- CHANGELOG.md | 6 +++--- setup.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f384657da..63f32d3164 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # nf-core/tools: Changelog -## v1.13.1dev - -### Bugs fixed +## [v1.13.1 - Copper Crocodile Meltdown](https://github.com/nf-core/tools/releases/tag/1.13.1) - [2021-03-19] * Fixed bug in pipeline linting markdown output that gets posted to PR comments [[#914]](https://github.com/nf-core/tools/issues/914) +* Made text for the PR branch CI check less verbose with a TLDR in bold at the top +* A number of minor tweaks to the new `nf-core modules lint` code ## [v1.13 - Copper Crocodile](https://github.com/nf-core/tools/releases/tag/1.13) - [2021-03-18] diff --git a/setup.py b/setup.py index 1a4aa78c7d..280d21559a 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages import sys -version = "1.13.1dev" +version = "1.13.1" with open("README.md") as f: readme = f.read() From 0acdeef8a037843c1f078096d6cbc7a78915cb55 Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 19 Mar 2021 13:40:52 +0100 Subject: [PATCH 21/23] Less wrapping in module lint table --- CHANGELOG.md | 2 +- nf_core/modules/lint.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f32d3164..7e27d8ca0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # nf-core/tools: Changelog -## [v1.13.1 - Copper Crocodile Meltdown](https://github.com/nf-core/tools/releases/tag/1.13.1) - [2021-03-19] +## [v1.13.1 - Copper Crocodile Patch :crocodile: :pirate_flag:](https://github.com/nf-core/tools/releases/tag/1.13.1) - [2021-03-19] * Fixed bug in pipeline linting markdown output that gets posted to PR comments [[#914]](https://github.com/nf-core/tools/issues/914) * Made text for the PR branch CI check less verbose with a TLDR in bold at the top diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 7772ea57a8..b1c3a8565b 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -326,7 +326,7 @@ def _s(some_list): ) table = Table(style="green", box=rich.box.ROUNDED) table.add_column("Module name", width=max_mod_name_len) - table.add_column("File path", no_wrap=True) + table.add_column("File path") table.add_column("Test message") table = format_result(self.passed, table) console.print(table) @@ -340,7 +340,7 @@ def _s(some_list): ) table = Table(style="yellow", box=rich.box.ROUNDED) table.add_column("Module name", width=max_mod_name_len) - table.add_column("File path", no_wrap=True) + table.add_column("File path") table.add_column("Test message") table = format_result(self.warned, table) console.print(table) @@ -352,7 +352,7 @@ def _s(some_list): ) table = Table(style="red", box=rich.box.ROUNDED) table.add_column("Module name", width=max_mod_name_len) - table.add_column("File path", no_wrap=True) + table.add_column("File path") table.add_column("Test message") table = format_result(self.failed, table) console.print(table) From ac3cbaeb86adccf719e16371bfb89796e191fecd Mon Sep 17 00:00:00 2001 From: Phil Ewels Date: Fri, 19 Mar 2021 13:42:39 +0100 Subject: [PATCH 22/23] Tyop --- nf_core/modules/lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index b1c3a8565b..921cb84735 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -246,7 +246,7 @@ def get_installed_modules(self): for m in sorted([m for m in os.listdir(nfcore_modules_dir) if not m == "lib"]): if not os.path.isdir(os.path.join(nfcore_modules_dir, m)): raise ModuleLintException( - f"File found in '{nfcore_modules_dir}': '{m}'! This directly should only contain module directories." + f"File found in '{nfcore_modules_dir}': '{m}'! This directory should only contain module directories." ) m_content = os.listdir(os.path.join(nfcore_modules_dir, m)) # Not a module, but contains sub-modules From 885c4db628fafc9986be3b30b6bb41ebe55adb93 Mon Sep 17 00:00:00 2001 From: kevinmenden Date: Fri, 19 Mar 2021 13:49:47 +0100 Subject: [PATCH 23/23] fix-table --- nf_core/modules/lint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nf_core/modules/lint.py b/nf_core/modules/lint.py index 921cb84735..909dd91170 100644 --- a/nf_core/modules/lint.py +++ b/nf_core/modules/lint.py @@ -308,8 +308,8 @@ def format_result(test_results, table): last_modname = lint_result.module_name table.add_row( Markdown(f"{lint_result.module_name}"), - Markdown(f"{lint_result.file_path}"), - os.path.relpath(lint_result.message, self.dir), + os.path.relpath(lint_result.file_path, self.dir), + Markdown(f"{lint_result.message}"), style=row_style, ) return table