4343from docutils import nodes
4444from docutils .parsers .rst import Directive , directives
4545from docutils .statemachine import StringList
46+ from sphinx .util .nodes import make_refnode
4647
4748# ---------------------------------------------------------------------------
4849# Custom node types
@@ -53,12 +54,12 @@ class ScenarioAppendixPlaceholder(nodes.General, nodes.Element):
5354 """Replaced by the full appendix content during the resolve phase."""
5455
5556
56- class ScenarioAppendixRef (nodes .General , nodes .Inline , nodes . Element ):
57+ class ScenarioAppendixRef (nodes .General , nodes .Element ):
5758 """Deferred cross-reference to a scenario appendix entry.
5859
59- Stores ``label`` and ``reftitle`` as attributes. Resolved to a proper
60- ``nodes.reference`` with ``refdocname `` set during ``doctree-resolved``,
61- once the appendix document name is known from the environment .
60+ Stores ``label``, ``reftitle``, ``group_tag``, and ``group_label`` as
61+ attributes. Resolved to a paragraph with ``make_refnode `` links during
62+ ``doctree-resolved``, once the appendix document name is known.
6263 """
6364
6465
@@ -284,21 +285,19 @@ def _render_pdf(
284285 existing ["scenarios" ].append (s )
285286
286287 # ----------------------------------------------------------
287- # Return a short paragraph pointing to the appendix.
288- # Use a deferred ScenarioAppendixRef node; it is resolved to a
289- # nodes.reference with refdocname set in doctree-resolved, once
290- # the appendix document name is known. Sphinx's LaTeX writer
291- # requires refdocname to construct the \hyperref[docname:id]
292- # label correctly — omitting it silently drops the hyperlink.
288+ # Return a deferred ScenarioAppendixRef block node. It is
289+ # resolved to a full paragraph with make_refnode links during
290+ # doctree-resolved, once the appendix document name is known.
291+ # Sphinx's make_refnode handles LaTeX/HTML builder differences,
292+ # producing a proper \hyperref in PDF output.
293293 # ----------------------------------------------------------
294294 ref_node = ScenarioAppendixRef ()
295295 ref_node ["label" ] = label
296296 ref_node ["reftitle" ] = title
297- para = nodes .paragraph ()
298- para += nodes .Text ("See the example \u201c " )
299- para += ref_node
300- para += nodes .Text ("\u201d in the Appendix." )
301- return [para ]
297+ ref_node ["group_tag" ] = tag
298+ ref_node ["group_label" ] = f"appendix-{ tag } "
299+ ref_node ["scenario_count" ] = len (scenario_titles )
300+ return [ref_node ]
302301
303302 # ------------------------------------------------------------------
304303 # Entry point
@@ -404,26 +403,60 @@ def _build_appendix_nodes(entries: Dict) -> List[nodes.Node]:
404403
405404
406405def resolve_scenario_appendix_refs (
407- app , doctree : nodes .document , _fromdocname : str
406+ app , doctree : nodes .document , fromdocname : str
408407) -> None :
409- """Replace ScenarioAppendixRef nodes with resolved cross-references .
408+ """Replace ScenarioAppendixRef nodes with a paragraph containing clickable links .
410409
411410 Called for every document during doctree-resolved. By that point all
412- source files have been read, so ``env.scenario_appendix_docname`` is set.
413- Sphinx's LaTeX writer needs ``refdocname`` on internal references to build
414- the ``docname:id`` label key used for ``\\ hyperref`` targets.
411+ source files have been read so ``env.scenario_appendix_docname`` is set.
412+ ``make_refnode`` is used so that both the HTML and LaTeX (PDF) builders
413+ receive a properly formed internal reference — LaTeX needs this to emit a
414+ working ``\\ hyperref``.
415+
416+ The paragraph text names the group section so readers can locate the entry
417+ without guessing where in the appendix it lives, e.g.:
418+
419+ See "Fetch Git Repo" example in the Fetch section of the Appendix.
415420 """
416421 appendix_docname = getattr (app .env , "scenario_appendix_docname" , None )
417422 for ref_node in doctree .traverse (ScenarioAppendixRef ):
418423 label = ref_node ["label" ]
419424 title = ref_node ["reftitle" ]
425+ group_tag = ref_node .get ("group_tag" , "" )
426+ group_label = ref_node .get ("group_label" , "" )
427+
428+ count = ref_node .get ("scenario_count" , 1 )
429+ examples = "examples" if count > 1 else "example"
430+
431+ para = nodes .paragraph ()
420432 if appendix_docname :
421- ref = nodes .reference (
422- "" , title , internal = True , refid = label , refdocname = appendix_docname
433+ feat_ref = make_refnode (
434+ app .builder ,
435+ fromdocname ,
436+ appendix_docname ,
437+ label ,
438+ nodes .inline ("" , title ),
423439 )
440+ para += nodes .Text ("See \u201c " )
441+ para += feat_ref
442+ para += nodes .Text (f"\u201d { examples } " )
443+ if group_label and group_tag :
444+ group_ref = make_refnode (
445+ app .builder ,
446+ fromdocname ,
447+ appendix_docname ,
448+ group_label ,
449+ nodes .inline ("" , _tag_section_title (group_tag )),
450+ )
451+ para += nodes .Text (" in the " )
452+ para += group_ref
453+ para += nodes .Text (" section of the Appendix." )
454+ else :
455+ para += nodes .Text (" in the Appendix." )
424456 else :
425- ref = nodes .inline ("" , title )
426- ref_node .replace_self (ref )
457+ para += nodes .Text (f"See \u201c { title } \u201d { examples } in the Appendix." )
458+
459+ ref_node .replace_self (para )
427460
428461
429462def process_scenario_appendix (app , doctree : nodes .document , _fromdocname : str ) -> None :
0 commit comments