Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
- TEMPLATE: ignore nf-core components during prettier linting ([#3858](https://github.com/nf-core/tools/pull/3858))
- update json schema store URL ([#3877](https://github.com/nf-core/tools/pull/3877))
- add word boundary for input, output and topic linting ([#3894](https://github.com/nf-core/tools/pull/3894))
- Add linting of topics ([#3902](https://github.com/nf-core/tools/pull/3902))

### Modules

Expand Down
16 changes: 10 additions & 6 deletions nf_core/components/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,10 @@ def generate_meta_yml_file(self) -> None:
{"${task.process}": {"type": "string", "description": "The name of the process"}},
{f"{self.component}": {"type": "string", "description": "The name of the tool"}},
{
f"{self.component} --version": {"type": "string", "description": "The version of the tool"},
f"{self.component} --version": {
"type": "eval",
"description": "The expression to obtain the version of the tool",
},
},
]
]
Expand All @@ -543,12 +546,13 @@ def generate_meta_yml_file(self) -> None:
versions_topic: dict[str, list | dict] = {
"versions": [
[
{"process": {"type": "string", "description": "The process the versions were collected from"}},
{
"tool": {"type": "string", "description": "The tool name the version was collected for"},
},
{"${task.process}": {"type": "string", "description": "The name of the process"}},
{f"{self.component}": {"type": "string", "description": "The name of the tool"}},
{
"version": {"type": "string", "description": "The version of the tool"},
f"{self.component} --version": {
"type": "eval",
"description": "The expression to obtain the version of the tool",
},
},
]
]
Expand Down
17 changes: 11 additions & 6 deletions nf_core/components/nfcore_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def __init__(
self.failed: list[tuple[str, str, str, Path]] = []
self.inputs: list[list[dict[str, dict[str, str]]]] = []
self.outputs: list[str] = []
self.topics: dict[str, list[dict[str, dict] | list[dict[str, dict[str, str]]]]]
self.has_meta: bool = False
self.git_sha: str | None = None
self.is_patched: bool = False
Expand Down Expand Up @@ -322,14 +323,18 @@ def get_topics_from_main_nf(self) -> None:
if topic_name in topics:
continue
topics[match_topic.group(1)] = []
for count, match_element in enumerate(matches_elements, start=1):
output_val = None
for _, match_element in enumerate(matches_elements, start=1):
topic_val = None
if match_element.group(3):
output_val = match_element.group(3)
topic_val = match_element.group(3)
elif match_element.group(4):
output_val = match_element.group(4)
if output_val:
channel_elements.append({f"value{count}": {}})
topic_val = match_element.group(4)
if topic_val:
topic_val = re.split(r',(?=(?:[^\'"]*[\'"][^\'"]*[\'"])*[^\'"]*$)', topic_val)[
0
] # Takes only first part, avoid commas in quotes
topic_val = topic_val.strip().strip("'").strip('"') # remove quotes and whitespaces
channel_elements.append({topic_val: {}})
if len(channel_elements) == 1:
topics[match_topic.group(1)].append(channel_elements[0])
elif len(channel_elements) > 1:
Expand Down
18 changes: 9 additions & 9 deletions nf_core/module-template/meta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,20 @@ output:
type: string
description: The name of the tool
- "{{ component }} --version":
type: string
description: The version of the tool
type: eval
description: The expression to obtain the version of the tool

topics:
versions:
- - process:
type: string
description: The process the versions were collected from
- tool:
- - "${task.process}":
type: string
description: The tool name the version was collected for
- version:
description: The name of the process
- "{{ component }}":
type: string
description: The version of the tool
description: The name of the tool
- "{{ component }} --version":
type: eval
description: The expression to obtain the version of the tool

authors:
- "{{ author }}"
Expand Down
23 changes: 22 additions & 1 deletion nf_core/modules/lint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

from .environment_yml import environment_yml
from .main_nf import main_nf
from .meta_yml import meta_yml, obtain_inputs, obtain_outputs, read_meta_yml
from .meta_yml import meta_yml, obtain_inputs, obtain_outputs, obtain_topics, read_meta_yml
from .module_changes import module_changes
from .module_deprecations import module_deprecations
from .module_patch import module_patch
Expand All @@ -48,6 +48,7 @@ class ModuleLint(ComponentLint):
meta_yml = meta_yml
obtain_inputs = obtain_inputs
obtain_outputs = obtain_outputs
obtain_topics = obtain_topics
read_meta_yml = read_meta_yml
module_changes = module_changes
module_deprecations = module_deprecations
Expand Down Expand Up @@ -302,6 +303,8 @@ def update_meta_yml_file(self, mod):
if "output" in meta_yml:
correct_outputs = self.obtain_outputs(mod.outputs)
meta_outputs = self.obtain_outputs(meta_yml["output"])
correct_topics = self.obtain_topics(mod.topics)
meta_topics = self.obtain_topics(meta_yml["topics"])

def _find_meta_info(meta_yml, element_name, is_output=False) -> dict:
"""Find the information specified in the meta.yml file to update the corrected meta.yml content"""
Expand Down Expand Up @@ -367,6 +370,24 @@ def _find_meta_info(meta_yml, element_name, is_output=False) -> dict:
corrected_meta_yml["output"][ch_name][i][element_name] = _find_meta_info(
meta_yml["output"], element_name, is_output=True
)
elif "topics" in meta_yml and correct_topics != meta_topics:
log.debug(
f"Correct topics: '{correct_topics}' differ from current topics: '{meta_topics}' in '{mod.meta_yml}'"
)
corrected_meta_yml["topics"] = mod.topics.copy()
for t_name in corrected_meta_yml["topics"].keys():
for i, t_content in enumerate(corrected_meta_yml["topics"][t_name]):
if isinstance(t_content, list):
for j, element in enumerate(t_content):
element_name = list(element.keys())[0]
corrected_meta_yml["topics"][t_name][i][j][element_name] = _find_meta_info(
meta_yml["topics"], element_name, is_output=True
)
elif isinstance(t_content, dict):
element_name = list(t_content.keys())[0]
corrected_meta_yml["topics"][t_name][i][element_name] = _find_meta_info(
meta_yml["topics"], element_name, is_output=True
)

def _add_edam_ontologies(section, edam_formats, desc):
expected_ontologies = []
Expand Down
49 changes: 49 additions & 0 deletions nf_core/modules/lint/meta_yml.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,29 @@ def meta_yml(module_lint_object: ComponentLint, module: NFCoreComponent, allow_m
module.meta_yml,
)
)
# Check that all topics are correctly specified
if "topics" in meta_yaml:
correct_topics = obtain_topics(module_lint_object, module.topics)
meta_topics = obtain_topics(module_lint_object, meta_yaml["topics"])

if correct_topics == meta_topics:
module.passed.append(
(
"meta_yml",
"correct_meta_topics",
"Correct topics specified in module `meta.yml`",
module.meta_yml,
)
)
else:
module.failed.append(
(
"meta_yml",
"correct_meta_topics",
f"Module `meta.yml` does not match `main.nf`. Topics should contain: {correct_topics}\nRun `nf-core modules lint --fix` to update the `meta.yml` file.",
module.meta_yml,
)
)


def read_meta_yml(module_lint_object: ComponentLint, module: NFCoreComponent) -> dict | None:
Expand Down Expand Up @@ -301,3 +324,29 @@ def obtain_outputs(_, outputs: dict | list) -> dict | list:
return [{k: v} for k, v in formatted_outputs.items()]
else:
return formatted_outputs


def obtain_topics(_, topics: dict) -> dict:
"""
Obtain the dictionary of topics and elements of each topic.

Args:
topics (dict): The dictionary of topics from main.nf or meta.yml files.

Returns:
formatted_topics (dict): A dictionary containing the topics and their elements obtained from main.nf or meta.yml files.
"""
formatted_topics: dict = {}
for name in topics.keys():
content = topics[name]
t_elements: list = []
for element in content:
if isinstance(element, list):
t_elements.append([])
for e in element:
t_elements[-1].append(list(e.keys())[0])
else:
t_elements.append(list(element.keys())[0])
formatted_topics[name] = t_elements

return formatted_topics
Loading