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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
|
# -*- coding: utf-8 -*-
"""
sphinx.roles
~~~~~~~~~~~~
Handlers for additional ReST roles.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import warnings
from docutils import nodes, utils
from docutils.parsers.rst import roles
from sphinx import addnodes
from sphinx.locale import _
from sphinx.util import ws_re
from sphinx.util.nodes import split_explicit_title, process_index_entry, \
set_role_source_info
generic_docroles = {
'command' : nodes.strong,
'dfn' : nodes.emphasis,
'kbd' : nodes.literal,
'mailheader' : addnodes.literal_emphasis,
'makevar' : nodes.strong,
'manpage' : addnodes.literal_emphasis,
'mimetype' : addnodes.literal_emphasis,
'newsgroup' : addnodes.literal_emphasis,
'program' : nodes.strong, # XXX should be an x-ref
'regexp' : nodes.literal,
}
for rolename, nodeclass in generic_docroles.iteritems():
generic = roles.GenericRole(rolename, nodeclass)
role = roles.CustomRole(rolename, generic, {'classes': [rolename]})
roles.register_local_role(rolename, role)
# -- generic cross-reference role ----------------------------------------------
class XRefRole(object):
"""
A generic cross-referencing role. To create a callable that can be used as
a role function, create an instance of this class.
The general features of this role are:
* Automatic creation of a reference and a content node.
* Optional separation of title and target with `title <target>`.
* The implementation is a class rather than a function to make
customization easier.
Customization can be done in two ways:
* Supplying constructor parameters:
* `fix_parens` to normalize parentheses (strip from target, and add to
title if configured)
* `lowercase` to lowercase the target
* `nodeclass` and `innernodeclass` select the node classes for
the reference and the content node
* Subclassing and overwriting `process_link()` and/or `result_nodes()`.
"""
nodeclass = addnodes.pending_xref
innernodeclass = nodes.literal
def __init__(self, fix_parens=False, lowercase=False,
nodeclass=None, innernodeclass=None, warn_dangling=False):
self.fix_parens = fix_parens
self.lowercase = lowercase
self.warn_dangling = warn_dangling
if nodeclass is not None:
self.nodeclass = nodeclass
if innernodeclass is not None:
self.innernodeclass = innernodeclass
def _fix_parens(self, env, has_explicit_title, title, target):
if not has_explicit_title:
if title.endswith('()'):
# remove parentheses
title = title[:-2]
if env.config.add_function_parentheses:
# add them back to all occurrences if configured
title += '()'
# remove parentheses from the target too
if target.endswith('()'):
target = target[:-2]
return title, target
def __call__(self, typ, rawtext, text, lineno, inliner,
options={}, content=[]):
env = inliner.document.settings.env
if not typ:
typ = env.config.default_role
else:
typ = typ.lower()
if ':' not in typ:
domain, role = '', typ
classes = ['xref', role]
else:
domain, role = typ.split(':', 1)
classes = ['xref', domain, '%s-%s' % (domain, role)]
# if the first character is a bang, don't cross-reference at all
if text[0:1] == '!':
text = utils.unescape(text)[1:]
if self.fix_parens:
text, tgt = self._fix_parens(env, False, text, "")
innernode = self.innernodeclass(rawtext, text, classes=classes)
return self.result_nodes(inliner.document, env, innernode,
is_ref=False)
# split title and target in role content
has_explicit_title, title, target = split_explicit_title(text)
title = utils.unescape(title)
target = utils.unescape(target)
# fix-up title and target
if self.lowercase:
target = target.lower()
if self.fix_parens:
title, target = self._fix_parens(
env, has_explicit_title, title, target)
# create the reference node
refnode = self.nodeclass(rawtext, reftype=role, refdomain=domain,
refexplicit=has_explicit_title)
# we may need the line number for warnings
set_role_source_info(inliner, lineno, refnode)
title, target = self.process_link(
env, refnode, has_explicit_title, title, target)
# now that the target and title are finally determined, set them
refnode['reftarget'] = target
refnode += self.innernodeclass(rawtext, title, classes=classes)
# we also need the source document
refnode['refdoc'] = env.docname
refnode['refwarn'] = self.warn_dangling
# result_nodes allow further modification of return values
return self.result_nodes(inliner.document, env, refnode, is_ref=True)
# methods that can be overwritten
def process_link(self, env, refnode, has_explicit_title, title, target):
"""Called after parsing title and target text, and creating the
reference node (given in *refnode*). This method can alter the
reference node and must return a new (or the same) ``(title, target)``
tuple.
"""
return title, ws_re.sub(' ', target)
def result_nodes(self, document, env, node, is_ref):
"""Called before returning the finished nodes. *node* is the reference
node if one was created (*is_ref* is then true), else the content node.
This method can add other nodes and must return a ``(nodes, messages)``
tuple (the usual return value of a role function).
"""
return [node], []
def indexmarkup_role(typ, rawtext, etext, lineno, inliner,
options={}, content=[]):
"""Role for PEP/RFC references that generate an index entry."""
env = inliner.document.settings.env
if not typ:
typ = env.config.default_role
else:
typ = typ.lower()
text = utils.unescape(etext)
targetid = 'index-%s' % env.new_serialno('index')
indexnode = addnodes.index()
targetnode = nodes.target('', '', ids=[targetid])
inliner.document.note_explicit_target(targetnode)
if typ == 'pep':
indexnode['entries'] = [
('single', _('Python Enhancement Proposals; PEP %s') % text,
targetid, '')]
anchor = ''
anchorindex = text.find('#')
if anchorindex > 0:
text, anchor = text[:anchorindex], text[anchorindex:]
try:
pepnum = int(text)
except ValueError:
msg = inliner.reporter.error('invalid PEP number %s' % text,
line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
ref = inliner.document.settings.pep_base_url + 'pep-%04d' % pepnum
sn = nodes.strong('PEP '+text, 'PEP '+text)
rn = nodes.reference('', '', internal=False, refuri=ref+anchor,
classes=[typ])
rn += sn
return [indexnode, targetnode, rn], []
elif typ == 'rfc':
indexnode['entries'] = [('single', 'RFC; RFC %s' % text, targetid, '')]
anchor = ''
anchorindex = text.find('#')
if anchorindex > 0:
text, anchor = text[:anchorindex], text[anchorindex:]
try:
rfcnum = int(text)
except ValueError:
msg = inliner.reporter.error('invalid RFC number %s' % text,
line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum
sn = nodes.strong('RFC '+text, 'RFC '+text)
rn = nodes.reference('', '', internal=False, refuri=ref+anchor,
classes=[typ])
rn += sn
return [indexnode, targetnode, rn], []
_amp_re = re.compile(r'(?<!&)&(?![&\s])')
def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
text = utils.unescape(text)
if typ == 'menuselection':
text = text.replace('-->', u'\N{TRIANGULAR BULLET}')
spans = _amp_re.split(text)
node = nodes.emphasis(rawtext=rawtext)
for i, span in enumerate(spans):
span = span.replace('&&', '&')
if i == 0:
if len(span) > 0:
textnode = nodes.Text(span)
node += textnode
continue
accel_node = nodes.inline()
letter_node = nodes.Text(span[0])
accel_node += letter_node
accel_node['classes'].append('accelerator')
node += accel_node
textnode = nodes.Text(span[1:])
node += textnode
node['classes'].append(typ)
return [node], []
_litvar_re = re.compile('{([^}]+)}')
def emph_literal_role(typ, rawtext, text, lineno, inliner,
options={}, content=[]):
text = utils.unescape(text)
pos = 0
retnode = nodes.literal(role=typ.lower(), classes=[typ])
for m in _litvar_re.finditer(text):
if m.start() > pos:
txt = text[pos:m.start()]
retnode += nodes.Text(txt, txt)
retnode += nodes.emphasis(m.group(1), m.group(1))
pos = m.end()
if pos < len(text):
retnode += nodes.Text(text[pos:], text[pos:])
return [retnode], []
_abbr_re = re.compile('\((.*)\)$', re.S)
def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
text = utils.unescape(text)
m = _abbr_re.search(text)
if m is None:
return [addnodes.abbreviation(text, text)], []
abbr = text[:m.start()].strip()
expl = m.group(1)
return [addnodes.abbreviation(abbr, abbr, explanation=expl)], []
def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# create new reference target
env = inliner.document.settings.env
targetid = 'index-%s' % env.new_serialno('index')
targetnode = nodes.target('', '', ids=[targetid])
# split text and target in role content
has_explicit_title, title, target = split_explicit_title(text)
title = utils.unescape(title)
target = utils.unescape(target)
# if an explicit target is given, we can process it as a full entry
if has_explicit_title:
entries = process_index_entry(target, targetid)
# otherwise we just create a "single" entry
else:
# but allow giving main entry
main = ''
if target.startswith('!'):
target = target[1:]
title = title[1:]
main = 'main'
entries = [('single', target, targetid, main)]
indexnode = addnodes.index()
indexnode['entries'] = entries
set_role_source_info(inliner, lineno, indexnode)
textnode = nodes.Text(title, title)
return [indexnode, targetnode, textnode], []
specific_docroles = {
# links to download references
'download': XRefRole(nodeclass=addnodes.download_reference),
# links to documents
'doc': XRefRole(warn_dangling=True),
'pep': indexmarkup_role,
'rfc': indexmarkup_role,
'guilabel': menusel_role,
'menuselection': menusel_role,
'file': emph_literal_role,
'samp': emph_literal_role,
'abbr': abbr_role,
'index': index_role,
}
for rolename, func in specific_docroles.iteritems():
roles.register_local_role(rolename, func)
# backwards compatibility alias
def xfileref_role(*args, **kwds):
warnings.warn('xfileref_role is deprecated, use XRefRole',
DeprecationWarning, stacklevel=2)
return XRefRole()(*args, **kwds)
|