Skip to content

Commit b1330a6

Browse files
committed
Ensure clickable pdf links
1 parent 814a072 commit b1330a6

1 file changed

Lines changed: 57 additions & 24 deletions

File tree

doc/_ext/scenario_directive.py

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from docutils import nodes
4444
from docutils.parsers.rst import Directive, directives
4545
from 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

406405
def 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

429462
def process_scenario_appendix(app, doctree: nodes.document, _fromdocname: str) -> None:

0 commit comments

Comments
 (0)