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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions