Skip to content

Commit 1ca5177

Browse files
committed
Add ref
1 parent 818df8e commit 1ca5177

1 file changed

Lines changed: 50 additions & 4 deletions

File tree

doc/_ext/scenario_directive.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ class ScenarioAppendixPlaceholder(nodes.General, nodes.Element):
5353
"""Replaced by the full appendix content during the resolve phase."""
5454

5555

56+
class ScenarioAppendixRef(nodes.General, nodes.Inline, nodes.Element):
57+
"""Deferred cross-reference to a scenario appendix entry.
58+
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.
62+
"""
63+
64+
5665
# ---------------------------------------------------------------------------
5766
# Helper functions
5867
# ---------------------------------------------------------------------------
@@ -276,13 +285,18 @@ def _render_pdf(
276285

277286
# ----------------------------------------------------------
278287
# Return a short paragraph pointing to the appendix.
279-
# nodes.reference(internal=True, refid=...) produces a
280-
# \hyperref in LaTeX, which works within one compiled PDF.
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.
281293
# ----------------------------------------------------------
294+
ref_node = ScenarioAppendixRef()
295+
ref_node["label"] = label
296+
ref_node["reftitle"] = title
282297
para = nodes.paragraph()
283298
para += nodes.emphasis(text="Scenarios: see ")
284-
ref = nodes.reference("", title, internal=True, refid=label)
285-
para += ref
299+
para += ref_node
286300
para += nodes.emphasis(text=" in the appendix.")
287301
return [para]
288302

@@ -341,6 +355,9 @@ def run(self) -> List[nodes.Node]:
341355
note += para
342356
return [note]
343357

358+
# Record which document hosts the appendix so that ScenarioAppendixRef
359+
# nodes in other documents can be resolved with the correct refdocname.
360+
env.scenario_appendix_docname = env.docname
344361
node = ScenarioAppendixPlaceholder()
345362
return [node]
346363

@@ -386,6 +403,29 @@ def _build_appendix_nodes(entries: Dict) -> List[nodes.Node]:
386403
return result
387404

388405

406+
def resolve_scenario_appendix_refs(
407+
app, doctree: nodes.document, _fromdocname: str
408+
) -> None:
409+
"""Replace ScenarioAppendixRef nodes with resolved cross-references.
410+
411+
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.
415+
"""
416+
appendix_docname = getattr(app.env, "scenario_appendix_docname", None)
417+
for ref_node in doctree.traverse(ScenarioAppendixRef):
418+
label = ref_node["label"]
419+
title = ref_node["reftitle"]
420+
if appendix_docname:
421+
ref = nodes.reference(
422+
"", title, internal=True, refid=label, refdocname=appendix_docname
423+
)
424+
else:
425+
ref = nodes.inline("", title)
426+
ref_node.replace_self(ref)
427+
428+
389429
def process_scenario_appendix(app, doctree: nodes.document, _fromdocname: str) -> None:
390430
"""Replace ScenarioAppendixPlaceholder nodes with generated content."""
391431
placeholders = list(doctree.traverse(ScenarioAppendixPlaceholder))
@@ -414,6 +454,10 @@ def purge_scenario_appendix(_app, env, docname: str) -> None:
414454

415455
def merge_scenario_appendix(_app, env, _docnames, other) -> None:
416456
"""Merge appendix entries from a parallel read worker."""
457+
if hasattr(other, "scenario_appendix_docname") and not hasattr(
458+
env, "scenario_appendix_docname"
459+
):
460+
env.scenario_appendix_docname = other.scenario_appendix_docname
417461
if not hasattr(env, "scenario_appendix_entries"):
418462
env.scenario_appendix_entries = {}
419463
for key, entry in getattr(other, "scenario_appendix_entries", {}).items():
@@ -438,6 +482,8 @@ def setup(app):
438482
app.add_directive("scenario-include", ScenarioIncludeDirective)
439483
app.add_directive("scenario-appendix", ScenarioAppendixDirective)
440484
app.add_node(ScenarioAppendixPlaceholder)
485+
app.add_node(ScenarioAppendixRef)
486+
app.connect("doctree-resolved", resolve_scenario_appendix_refs)
441487
app.connect("doctree-resolved", process_scenario_appendix)
442488
app.connect("env-purge-doc", purge_scenario_appendix)
443489
app.connect("env-merge-info", merge_scenario_appendix)

0 commit comments

Comments
 (0)