Skip to content

Commit 2d80f70

Browse files
committed
fix(renderer): Add ID prefix to avoid duplicate section IDs
Extract subcommand from parser_info.prog to generate unique section IDs: - "tmuxp load" -> prefix "load" -> IDs like "load-usage", "load-options" - "tmuxp" -> no prefix -> IDs like "usage", "options" (backwards compatible) This prevents duplicate ID warnings when multiple argparse directives exist on the same documentation page (e.g., documenting subcommands). Changes: - Add _extract_id_prefix() static method - Add id_prefix parameter to render_usage_section() - Add id_prefix parameter to render_group_section() - Pass prefix from render() to section methods
1 parent 6bb4b3f commit 2d80f70

1 file changed

Lines changed: 68 additions & 7 deletions

File tree

docs/_ext/sphinx_argparse_neo/renderer.py

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,37 @@ def __init__(
115115
self.config = config or RenderConfig()
116116
self.state = state
117117

118+
@staticmethod
119+
def _extract_id_prefix(prog: str) -> str:
120+
"""Extract subcommand from prog for unique section IDs.
121+
122+
Parameters
123+
----------
124+
prog : str
125+
The program name, potentially with subcommand (e.g., "tmuxp load").
126+
127+
Returns
128+
-------
129+
str
130+
The subcommand part for use as ID prefix, or empty string if none.
131+
132+
Examples
133+
--------
134+
>>> ArgparseRenderer._extract_id_prefix("tmuxp load")
135+
'load'
136+
>>> ArgparseRenderer._extract_id_prefix("tmuxp")
137+
''
138+
>>> ArgparseRenderer._extract_id_prefix("vcspull sync")
139+
'sync'
140+
>>> ArgparseRenderer._extract_id_prefix("myapp sub cmd")
141+
'sub-cmd'
142+
"""
143+
parts = prog.split()
144+
if len(parts) <= 1:
145+
return ""
146+
# Join remaining parts with hyphen for multi-level subcommands
147+
return "-".join(parts[1:])
148+
118149
def render(self, parser_info: ParserInfo) -> list[nodes.Node]:
119150
"""Render a complete parser to docutils nodes.
120151
@@ -159,13 +190,17 @@ def render(self, parser_info: ParserInfo) -> list[nodes.Node]:
159190

160191
result.append(program_node)
161192

193+
# Extract ID prefix from prog for unique section IDs
194+
# e.g., "tmuxp load" -> "load", "myapp" -> ""
195+
id_prefix = self._extract_id_prefix(parser_info.prog)
196+
162197
# Add Usage section as sibling (for TOC visibility)
163-
usage_section = self.render_usage_section(parser_info)
198+
usage_section = self.render_usage_section(parser_info, id_prefix=id_prefix)
164199
result.append(usage_section)
165200

166201
# Add argument groups as sibling sections (for TOC visibility)
167202
for group in parser_info.argument_groups:
168-
group_section = self.render_group_section(group)
203+
group_section = self.render_group_section(group, id_prefix=id_prefix)
169204
result.append(group_section)
170205

171206
# Add subcommands
@@ -197,7 +232,9 @@ def render_usage(self, parser_info: ParserInfo) -> argparse_usage:
197232
usage_node["usage"] = parser_info.bare_usage
198233
return usage_node
199234

200-
def render_usage_section(self, parser_info: ParserInfo) -> nodes.section:
235+
def render_usage_section(
236+
self, parser_info: ParserInfo, *, id_prefix: str = ""
237+
) -> nodes.section:
201238
"""Render usage as a section with heading for TOC visibility.
202239
203240
Creates a proper section node with "Usage" heading containing the
@@ -208,6 +245,10 @@ def render_usage_section(self, parser_info: ParserInfo) -> nodes.section:
208245
----------
209246
parser_info : ParserInfo
210247
The parser information.
248+
id_prefix : str
249+
Optional prefix for the section ID (e.g., "load" -> "load-usage").
250+
Used to ensure unique IDs when multiple argparse directives exist
251+
on the same page.
211252
212253
Returns
213254
-------
@@ -231,11 +272,18 @@ def render_usage_section(self, parser_info: ParserInfo) -> nodes.section:
231272
>>> section = renderer.render_usage_section(info)
232273
>>> section["ids"]
233274
['usage']
275+
276+
With prefix for subcommand pages:
277+
278+
>>> section = renderer.render_usage_section(info, id_prefix="load")
279+
>>> section["ids"]
280+
['load-usage']
234281
>>> section.children[0].astext()
235282
'Usage'
236283
"""
284+
section_id = f"{id_prefix}-usage" if id_prefix else "usage"
237285
section = nodes.section()
238-
section["ids"] = ["usage"]
286+
section["ids"] = [section_id]
239287
section["names"] = [nodes.fully_normalize_name("Usage")]
240288
section += nodes.title("Usage", "Usage")
241289

@@ -245,7 +293,9 @@ def render_usage_section(self, parser_info: ParserInfo) -> nodes.section:
245293

246294
return section
247295

248-
def render_group_section(self, group: ArgumentGroup) -> nodes.section:
296+
def render_group_section(
297+
self, group: ArgumentGroup, *, id_prefix: str = ""
298+
) -> nodes.section:
249299
"""Render an argument group wrapped in a section for TOC visibility.
250300
251301
Creates a proper section node with the group title as heading,
@@ -256,6 +306,10 @@ def render_group_section(self, group: ArgumentGroup) -> nodes.section:
256306
----------
257307
group : ArgumentGroup
258308
The argument group to render.
309+
id_prefix : str
310+
Optional prefix for the section ID (e.g., "load" -> "load-options").
311+
Used to ensure unique IDs when multiple argparse directives exist
312+
on the same page.
259313
260314
Returns
261315
-------
@@ -275,6 +329,12 @@ def render_group_section(self, group: ArgumentGroup) -> nodes.section:
275329
>>> section = renderer.render_group_section(group)
276330
>>> section["ids"]
277331
['positional-arguments']
332+
333+
With prefix for subcommand pages:
334+
335+
>>> section = renderer.render_group_section(group, id_prefix="load")
336+
>>> section["ids"]
337+
['load-positional-arguments']
278338
>>> section.children[0].astext()
279339
'Positional Arguments'
280340
"""
@@ -285,8 +345,9 @@ def render_group_section(self, group: ArgumentGroup) -> nodes.section:
285345
if self.config.group_title_prefix:
286346
title = f"{self.config.group_title_prefix}{title}"
287347

288-
# Generate section ID from title
289-
section_id = title.lower().replace(" ", "-")
348+
# Generate section ID from title (with optional prefix for uniqueness)
349+
base_id = title.lower().replace(" ", "-")
350+
section_id = f"{id_prefix}-{base_id}" if id_prefix else base_id
290351

291352
# Create section wrapper for TOC discovery
292353
section = nodes.section()

0 commit comments

Comments
 (0)