-
Notifications
You must be signed in to change notification settings - Fork 0
/
sphinx_better_subsection.py
137 lines (112 loc) · 4.39 KB
/
sphinx_better_subsection.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Sphinx extension to prefer explicit IDs in sections
Add this extension using::
extensions += ["sphinx_better_subsection"]
Using the transformer directly is also allowed with::
from sphinx_better_subsection import PreferSectionTarget
app.add_transform(PreferSectionTarget)
"""
from docutils import nodes
from docutils.transforms import Transform
class PreferSectionTarget(Transform):
"""Prefer target IDs over the section's own
Given this input text:
.. code-block:: rest
.. _a:
.. _b:
.. _c:
Section Title
-------------
A paragraph.
This parses into:
.. code-block:: xml
...
<target ids="['a']" names="['a']">
<target ids="['b']" names="['b']">
<target ids="['c']" names="['c']">
<section ids="section-title" names="Section\\ Title">
...
Transforming it gives:
.. code-block:: xml
...
<target ids="['a']" names="['a']">
<target ids="['b']" names="['b']">
<target refid="c">
<section ids="c section-title" names="c Section\\ Title">
...
Note that the other IDs are all preserved; only the order is modified.
Nested subsections are also checked.
"""
# Run before `docutils.transforms.references.PropagateTransform` which has
# priority 260.
default_priority = 255
def apply(self):
"""Docutils transform entry point"""
# `.findall` is new in docutils 0.18. Fallback to `.traverse`.
try:
findall = self.document.findall
except AttributeError:
findall = self.document.traverse
for node in findall(nodes.section):
# Get node directly preceding the section
index = node.parent.index(node)
if index == 0:
continue
last = node.parent[index - 1]
# Targets are part of the previous section so we should look deeper
while isinstance(last, nodes.Node) and last.children:
last = last[-1]
# Filter away nodes that aren't targets
if not isinstance(last, nodes.target):
continue
# Filter away targets with content
if (
isinstance(last.parent, nodes.TextElement)
or last.hasattr("refid")
or last.hasattr("refuri")
or last.hasattr("refname")
):
continue
# Store ID and name
refname = last["names"][0]
refid = last["ids"][0]
# Propagate the previous target
# Source from `PropagateTargets.apply`
node["ids"].extend(last["ids"])
node["names"].extend(last["names"])
# Set defaults for node.expect_referenced_by_name/id.
if not hasattr(node, "expect_referenced_by_name"):
node.expect_referenced_by_name = {}
if not hasattr(node, "expect_referenced_by_id"):
node.expect_referenced_by_id = {}
for id_ in last["ids"]:
node.expect_referenced_by_id[id_] = last
# Update IDs to node mapping.
self.document.ids[id_] = node
last["ids"] = []
for name in last["names"]:
node.expect_referenced_by_name[name] = last
last["names"] = []
# If there are any expect_referenced_by_name/id attributes in
# target set, copy them to node.
node.expect_referenced_by_name.update(
getattr(last, "expect_referenced_by_name", {}))
node.expect_referenced_by_id.update(
getattr(last, "expect_referenced_by_id", {}))
# Set refid to point to the first former ID of target which is now
# an ID of next_node.
last["refid"] = refid
self.document.note_refid(last)
# End source
# Prefer the target's ID
node["ids"].remove(refid)
node["ids"].insert(0, refid)
# Also prefer the target's name
node["names"].remove(refname)
node["names"].insert(0, refname)
def setup(app):
"""Sphinx extension entry point"""
app.add_transform(PreferSectionTarget)
return {
# Probably parallel-read safe (though I'm not sure)
"parallel_read_safe": True,
}