Skip to content

sphinx-argparse-neo: exemplar.make_section_id ignores page_prefix when term text carries a prefix #17

@tony

Description

@tony

Summary

exemplar.make_section_id in sphinx-argparse-neo derives the example section ID from the term text (e.g. "add examples:""add-examples") and only falls back to the caller-supplied page_prefix when the term-derived prefix is empty. In multi-page docs where the same subcommand name appears in more than one page's parser epilog — typically a top-level index page rendering .. argparse:: :nosubcommands: alongside leaf pages rendering .. argparse:: :path: <sub> — both pages emit the same section ID, docutils promotes the name to an explicit cross-document target (via MyST {eval-rst}), and Sphinx's std domain logs duplicate label <sub>-examples.

Relationship to #15 / PR #16

#15 fixed render_usage_section, render_group_section, and the top of _create_example_section so that section["names"] mirrors section["ids"] with the correct id_prefix. That eliminated 58 of the 64 duplicate-label warnings in vcspull's api-styling docs (-91%, verified via editable install of PR #16). This issue tracks the remaining 6 warnings, which come from a separate mechanism inside make_section_id that #16 intentionally did not touch.

Reproduction

On vcspull with sphinx-argparse-neo built from the vcspull-fixes branch of this repo (PR #16), build the docs:

$ cd ~/work/python/vcspull
$ uv run sphinx-build -a -E -b html docs docs/_build/html 2>&1 | grep "duplicate label"

The remaining warnings form a clean pattern:

docs/cli/index.md: WARNING: duplicate label add-examples, other instance in docs/cli/add.md
docs/cli/index.md: WARNING: duplicate label discover-examples, other instance in docs/cli/discover.md
docs/cli/index.md: WARNING: duplicate label fmt-examples, other instance in docs/cli/fmt.md
docs/cli/list.md:  WARNING: duplicate label list-examples, other instance in docs/cli/index.md
docs/cli/search.md: WARNING: duplicate label search-examples, other instance in docs/cli/index.md
docs/cli/sync.md:  WARNING: duplicate label sync-examples, other instance in docs/cli/index.md

Every warning is a <subcommand>-examples collision between a leaf CLI page and the aggregate index page.

Root cause

packages/sphinx-argparse-neo/src/sphinx_argparse_neo/exemplar.py:405-421:

# Extract prefix before the term suffix (e.g., "Machine-readable output")
lower_text = term_text.lower().rstrip(":")
if term_suffix in lower_text:
    prefix = lower_text.rsplit(term_suffix, 1)[0].strip()
    # Remove trailing colon from prefix (handles ": examples" pattern)
    prefix = prefix.rstrip(":").strip()
    if prefix:
        normalized_prefix = prefix.replace(" ", "-")
        if is_subsection:
            section_id = normalized_prefix
        else:
            section_id = f"{normalized_prefix}-{term_suffix}"
    else:
        # Plain "examples" - add page prefix if provided for uniqueness
        section_id = f"{page_prefix}-{term_suffix}" if page_prefix else term_suffix
else:
    section_id = term_suffix

Two observations:

  1. When the term text contains a prefix (e.g. "add examples:"), the function uses it and ignores page_prefix entirely.
  2. The "plain examples:" case is the only one where page_prefix contributes to section_id.

This means two pages whose argparse directives both surface a "<sub> examples:" term (which is natural for an index page aggregating all subcommands + the leaf page showing its own subcommand) both produce the same section_id and therefore collide on the implicit target that docutils wires into section["names"].

CleanArgParseDirective.run() at exemplar.py:1208-1214 already computes page_prefix = docname.split("/")[-1] and threads it through process_node_create_example_sectionmake_section_id, but the value is only consulted in the no-term-prefix branch.

Proposed fix direction

Make page_prefix authoritative when it's non-empty. Two variants, picking between them is a design call:

(a) Always prepend page_prefix when present

if prefix:
    normalized_prefix = prefix.replace(" ", "-")
    base = normalized_prefix if is_subsection else f"{normalized_prefix}-{term_suffix}"
    section_id = f"{page_prefix}-{base}" if page_prefix else base

Pros: preserves the term-derived prefix in the final ID, so existing cross-references like :ref:add-examples can be migrated mechanically to `:ref:`cli-add-add-examples. Cons: IDs become verbose (cli-add-add-examples), and the term prefix duplicates information already in page_prefix when the page and the term are named the same.

(b) Prefer page_prefix over the term-derived prefix entirely

if prefix:
    normalized_prefix = prefix.replace(" ", "-")
    effective_prefix = page_prefix or normalized_prefix
    section_id = effective_prefix if is_subsection else f"{effective_prefix}-{term_suffix}"

Pros: IDs stay short (add-examples) and are guaranteed unique across pages. Cons: when two leaf pages contain the same term texts with different subcommand names (unlikely but possible), the page-prefix alone might not be expressive enough — but in practice the page name itself already encodes the subcommand.

I'd lean (b) if the plan is still to drop sphinx.ext.napoleon / sphinx_autodoc_typehints from gp-sphinx's default stack, because (b) is the minimal-surface change and produces the same IDs users are already seeing under the common case.

Test coverage requested

A new regression test in the same spirit as tests/ext/argparse_neo/test_multi_page_integration.py, but with a parser whose top-level epilog contains example definition terms naming individual subcommands — mirroring vcspull's pattern. The test should fail on main at tip (post-#16) with three duplicate-label warnings and pass under the proposed fix.

Downstream impact

Same 13 consumers listed in #15. Vcspull is the one where this has been empirically measured; the others likely hit the same pattern whenever their index/aggregate CLI page aggregates subcommands that are also documented on their own leaf pages.

Scope comparison (for this issue only)

Fresh sphinx-build -a -E on vcspull api-styling:

gp-sphinx source duplicate label
published 0.0.1a6 64
vcspull-fixes / PR #16 editable 6
hypothetical fix for this issue on top of #16 0

PR #16 already does the bulk of the work; this issue is the mop-up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions