Skip to content

Commit b43b5b2

Browse files
authored
Merge pull request #114 from EBI-Metagenomics/bugfix/third-party-modules-docs
Claude fixed the issue with the docs and third party modules sbf
2 parents 5dff96e + e716041 commit b43b5b2

File tree

2 files changed

+80
-42
lines changed

2 files changed

+80
-42
lines changed

docs/generate_docs.py

Lines changed: 77 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@
44
"""
55

66
from pathlib import Path
7-
from typing import Any
7+
from typing import Any, Optional
88

99
import click
1010
import yaml
11-
from jinja2 import Environment, FileSystemLoader
11+
from jinja2 import Environment, FileSystemLoader, Template
1212

1313

1414
class ModuleParser:
1515
"""Parse nf-core module meta.yml files and generate documentation."""
1616

17-
def __init__(self, modules_repo_path: str, output_dir: str):
17+
modules_repo_path: Path
18+
output_dir: Path
19+
jinja_env: Environment
20+
21+
def __init__(self, modules_repo_path: str, output_dir: str) -> None:
1822
"""Initialize the parser.
1923
2024
:param modules_repo_path: Path to the nf-core modules repository
@@ -68,32 +72,60 @@ def get_module_path_info(self, meta_file: Path, base_type: str) -> dict[str, str
6872
"subcategory": "/".join(parts[1:-1]) if len(parts) > 2 else "",
6973
}
7074

71-
def _detect_component_types(self, components: list[str]) -> list[dict[str, str]]:
75+
def _detect_component_types(
76+
self, components: list[str | dict[str, Any]]
77+
) -> list[dict[str, str]]:
7278
"""Detect whether components are modules or subworkflows.
7379
74-
:param components: List of component names
75-
:type components: list[str]
80+
:param components: List of component names or dicts with component info
81+
:type components: list[str | dict[str, Any]]
7682
:returns: List of component info with type detection
7783
:rtype: list[dict[str, str]]
7884
"""
79-
component_info = []
85+
component_info: list[dict[str, str]] = []
8086

8187
for component in components:
88+
# Handle both string format and dict format (for third-party modules)
89+
component_name: Optional[str]
90+
git_remote: Optional[str]
91+
92+
if isinstance(component, dict):
93+
# Component is a dict like {"module_name": null, "git_remote": "..."}
94+
# Extract the component name (first key that's not git_remote)
95+
git_remote = component.get("git_remote")
96+
component_name = None
97+
for key in component.keys():
98+
if key != "git_remote":
99+
component_name = key
100+
break
101+
102+
if not component_name:
103+
continue
104+
else:
105+
# Component is a simple string
106+
component_name = component
107+
git_remote = None
108+
82109
# Check if component exists as a subworkflow
83-
subworkflow_path = (
110+
subworkflow_path: Path = (
84111
self.modules_repo_path
85112
/ "subworkflows"
86113
/ "ebi-metagenomics"
87-
/ component
114+
/ component_name
88115
/ "meta.yml"
89116
)
90117

118+
component_type: str
91119
if subworkflow_path.exists():
92120
component_type = "subworkflow"
93121
else:
94122
component_type = "module"
95123

96-
component_info.append({"name": component, "type": component_type})
124+
comp_info: dict[str, str] = {"name": component_name, "type": component_type}
125+
if git_remote:
126+
comp_info["git_remote"] = git_remote
127+
128+
component_info.append(comp_info)
97129

98130
return component_info
99131

@@ -105,19 +137,19 @@ def _process_meta_data(self, meta_data: dict[str, Any]) -> dict[str, Any]:
105137
:returns: Processed meta data with flattened input/output structures
106138
:rtype: dict[str, Any]
107139
"""
108-
processed = meta_data.copy()
140+
processed: dict[str, Any] = meta_data.copy()
109141

110142
# Process tools section if it's a list
111143
if "tools" in processed and isinstance(processed["tools"], list):
112-
processed_tools = {}
144+
processed_tools: dict[str, Any] = {}
113145
for item in processed["tools"]:
114146
if isinstance(item, dict):
115147
processed_tools.update(item)
116148
processed["tools"] = processed_tools
117149

118150
# Process input section if it's a list
119151
if "input" in processed and isinstance(processed["input"], list):
120-
processed_input = {}
152+
processed_input: dict[str, Any] = {}
121153
for item in processed["input"]:
122154
if isinstance(item, list):
123155
# Handle nested list structure (like diamond/blastp)
@@ -133,7 +165,7 @@ def _process_meta_data(self, meta_data: dict[str, Any]) -> dict[str, Any]:
133165
if "output" in processed:
134166
if isinstance(processed["output"], list):
135167
# Handle list format (like antismash)
136-
processed_output = {}
168+
processed_output: dict[str, Any] = {}
137169
for item in processed["output"]:
138170
if isinstance(item, dict):
139171
processed_output.update(item)
@@ -167,34 +199,36 @@ def generate_module_docs(self) -> None:
167199
:returns: None
168200
:rtype: None
169201
"""
170-
meta_files = self.find_meta_files("modules")
171-
template = self.jinja_env.get_template("module.md.j2")
202+
meta_files: list[Path] = self.find_meta_files("modules")
203+
template: Template = self.jinja_env.get_template("module.md.j2")
172204

173-
modules_output_dir = self.output_dir / "docs" / "modules"
205+
modules_output_dir: Path = self.output_dir / "docs" / "modules"
174206
modules_output_dir.mkdir(parents=True, exist_ok=True)
175207

176-
module_index = []
208+
module_index: list[dict[str, str]] = []
177209

178210
for meta_file in meta_files:
179211
try:
180-
meta_data = self.parse_meta_yml(meta_file)
181-
path_info = self.get_module_path_info(meta_file, "modules")
212+
meta_data: dict[str, Any] = self.parse_meta_yml(meta_file)
213+
path_info: dict[str, str] = self.get_module_path_info(
214+
meta_file, "modules"
215+
)
182216

183217
# Process input/output for better template handling
184218
meta_data = self._process_meta_data(meta_data)
185219

186220
# Create subdirectories as needed
187-
module_dir = modules_output_dir / path_info["category"]
221+
module_dir: Path = modules_output_dir / path_info["category"]
188222
if path_info["subcategory"]:
189223
module_dir = module_dir / path_info["subcategory"]
190224
module_dir.mkdir(parents=True, exist_ok=True)
191225

192226
# Generate documentation
193-
content = template.render(
227+
content: str = template.render(
194228
meta=meta_data, path_info=path_info, module_type="module"
195229
)
196230

197-
output_file = module_dir / f"{path_info['name']}.md"
231+
output_file: Path = module_dir / f"{path_info['name']}.md"
198232
with open(output_file, "w") as f:
199233
f.write(content)
200234

@@ -223,34 +257,36 @@ def generate_subworkflow_docs(self) -> None:
223257
:returns: None
224258
:rtype: None
225259
"""
226-
meta_files = self.find_meta_files("subworkflows")
227-
template = self.jinja_env.get_template("subworkflow.md.j2")
260+
meta_files: list[Path] = self.find_meta_files("subworkflows")
261+
template: Template = self.jinja_env.get_template("subworkflow.md.j2")
228262

229-
subworkflows_output_dir = self.output_dir / "docs" / "subworkflows"
263+
subworkflows_output_dir: Path = self.output_dir / "docs" / "subworkflows"
230264
subworkflows_output_dir.mkdir(parents=True, exist_ok=True)
231265

232-
subworkflow_index = []
266+
subworkflow_index: list[dict[str, str]] = []
233267

234268
for meta_file in meta_files:
235269
try:
236-
meta_data = self.parse_meta_yml(meta_file)
237-
path_info = self.get_module_path_info(meta_file, "subworkflows")
270+
meta_data: dict[str, Any] = self.parse_meta_yml(meta_file)
271+
path_info: dict[str, str] = self.get_module_path_info(
272+
meta_file, "subworkflows"
273+
)
238274

239275
# Process input/output for better template handling
240276
meta_data = self._process_meta_data(meta_data)
241277

242278
# Create subdirectories as needed
243-
subworkflow_dir = subworkflows_output_dir / path_info["category"]
279+
subworkflow_dir: Path = subworkflows_output_dir / path_info["category"]
244280
if path_info["subcategory"]:
245281
subworkflow_dir = subworkflow_dir / path_info["subcategory"]
246282
subworkflow_dir.mkdir(parents=True, exist_ok=True)
247283

248284
# Generate documentation
249-
content = template.render(
285+
content: str = template.render(
250286
meta=meta_data, path_info=path_info, module_type="subworkflow"
251287
)
252288

253-
output_file = subworkflow_dir / f"{path_info['name']}.md"
289+
output_file: Path = subworkflow_dir / f"{path_info['name']}.md"
254290
with open(output_file, "w") as f:
255291
f.write(content)
256292

@@ -274,34 +310,34 @@ def generate_subworkflow_docs(self) -> None:
274310
self._generate_index(subworkflow_index, subworkflows_output_dir, "subworkflows")
275311

276312
def _generate_index(
277-
self, items: list[dict], output_dir: Path, item_type: str
313+
self, items: list[dict[str, str]], output_dir: Path, item_type: str
278314
) -> None:
279315
"""Generate index page for modules or subworkflows.
280316
281317
:param items: List of items to include in index
282-
:type items: list[dict]
318+
:type items: list[dict[str, str]]
283319
:param output_dir: Output directory
284320
:type output_dir: Path
285321
:param item_type: 'modules' or 'subworkflows'
286322
:type item_type: str
287323
:returns: None
288324
:rtype: None
289325
"""
290-
template = self.jinja_env.get_template("index.md.j2")
326+
template: Template = self.jinja_env.get_template("index.md.j2")
291327

292328
# Group by category
293-
categories = {}
329+
categories: dict[str, list[dict[str, str]]] = {}
294330
for item in items:
295-
category = item["category"]
331+
category: str = item["category"]
296332
if category not in categories:
297333
categories[category] = []
298334
categories[category].append(item)
299335

300-
content = template.render(
336+
content: str = template.render(
301337
categories=categories, item_type=item_type, title=item_type.capitalize()
302338
)
303339

304-
index_file = output_dir / "index.md"
340+
index_file: Path = output_dir / "index.md"
305341
with open(index_file, "w") as f:
306342
f.write(content)
307343

@@ -315,7 +351,7 @@ def _generate_index(
315351
default=".",
316352
help="Output directory for generated documentation (default: current directory)",
317353
)
318-
def main(modules_repo: str, output_dir: str):
354+
def main(modules_repo: str, output_dir: str) -> None:
319355
"""Generate MkDocs documentation from nf-core modules and subworkflows.
320356
321357
:param modules_repo: Path to the nf-core modules repository
@@ -325,7 +361,7 @@ def main(modules_repo: str, output_dir: str):
325361
:returns: None
326362
:rtype: None
327363
"""
328-
parser = ModuleParser(modules_repo, output_dir)
364+
parser: ModuleParser = ModuleParser(modules_repo, output_dir)
329365

330366
print("Generating module documentation...")
331367
parser.generate_module_docs()

docs/templates/subworkflow.md.j2

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ nf-core modules -g https://www.github.com/ebi-metagenomics/nf-modules install {{
2727
This subworkflow uses the following components:
2828

2929
{% for component in meta.components %}
30-
{% if component.type == "module" %}
30+
{% if component.git_remote %}
31+
- `{{ component.name }}` <small>*({{ component.type }} from [{{ component.git_remote }}]({{ component.git_remote }}))*</small>
32+
{% elif component.type == "module" %}
3133
- [`{{ component.name }}`](../../modules/ebi-metagenomics/{{ component.name }}.md) <small>*(module)*</small>
3234
{% elif component.type == "subworkflow" %}
3335
- [`{{ component.name }}`](../../subworkflows/ebi-metagenomics/{{ component.name }}.md) <small>*(subworkflow)*</small>

0 commit comments

Comments
 (0)