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
|
# Handle :PG: policy identifiers for Policy Guide
# (c) 2020 Michał Górny
# 2-clause BSD license
import collections
from docutils import nodes
from sphinx.domains import Index
from sphinx.environment.collectors.toctree import TocTreeCollector
from sphinx.util import logging
logger = logging.getLogger(__name__)
Policy = collections.namedtuple('Policy', ('id', 'title', 'docname',
'chapter'))
toccollector = TocTreeCollector()
class PolicyIndex(Index):
name = 'policy-index'
localname = 'Policy Index'
shortname = 'Policy Index'
def generate(self, docnames=None):
env = self.domain.env
if not hasattr(env, 'policy_index'):
env.policy_index = []
entries = collections.defaultdict(list)
for p in env.policy_index:
if docnames is not None and p.docname not in docnames:
continue
entries[p.chapter].append(('PG' + p.id, # name
0, # subtype
p.docname, # docname
'pg' + p.id, # anchor
p.title, # extra
'', # qualifier
'')) # descr
return (sorted([(k, sorted(v)) for k, v in entries.items()],
key=lambda kv: kv[1]),
False)
def find_pg_id(section):
# first child should be title
title = section.children[0]
assert isinstance(title, nodes.title)
# second child should be field list
cl = section.children[1]
if not isinstance(cl, nodes.field_list):
return None, title.astext(), None
for f in cl.traverse(nodes.field):
fn = next(iter(f.traverse(nodes.field_name)))
fv = next(iter(f.traverse(nodes.field_body)))
if fn.astext().upper() == 'PG':
if fn.astext() != 'PG':
raise RuntimeError('PG field must be uppercase')
iv = '{:04d}'.format(int(fv.astext(), 10))
if fv.astext() != iv:
raise RuntimeError('PG value must be 4 digits, zero-padded ({})'
.format(iv))
el = section
titles = []
while el.parent is not None:
title = el.children[0]
assert isinstance(title, nodes.title)
titles.append(title.astext())
el = el.parent
# combine all section titles up to but excluding
# the chapter title
title = ': '.join(reversed(titles[:-1]))
return iv, title, titles[-1]
logger.warning('%s: no PG identifier found', title.astext())
return None, title.astext(), None
def on_doctree_read(app, doctree):
env = app.builder.env
if not hasattr(env, 'policy_index'):
env.policy_index = []
for node in doctree.traverse(nodes.section):
pg_id, title, chapter = find_pg_id(node)
if pg_id is not None:
node['ids'].insert(0, 'pg' + pg_id)
env.policy_index.append(Policy(pg_id, title, env.docname,
chapter))
# update the table of conents to use the 'pgXXXX' ids
toccollector.process_doc(app, doctree)
def on_env_purge_doc(app, env, docname):
if not hasattr(env, 'policy_index'):
return
env.policy_index = [p for p in env.policy_index
if p.docname != docname]
def on_env_merge_info(app, env, docnames, other):
if not hasattr(other, 'policy_index'):
return
if not hasattr(env, 'policy_index'):
env.policy_index = []
env.policy_index.extend(other.policy_index)
def setup(app):
app.connect('doctree-read', on_doctree_read)
app.connect('env-purge-doc', on_env_purge_doc)
app.connect('env-merge-info', on_env_merge_info)
app.add_index_to_domain('std', PolicyIndex)
return {
'version': '0',
'parallel_read_safe': True,
'parallel_write_safe': True,
}
|