1 # $Id: roles.py 4564 2006-05-21 20:44:42Z wiemann $ |
|
2 # Author: Edward Loper <edloper@gradient.cis.upenn.edu> |
|
3 # Copyright: This module has been placed in the public domain. |
|
4 |
|
5 """ |
|
6 This module defines standard interpreted text role functions, a registry for |
|
7 interpreted text roles, and an API for adding to and retrieving from the |
|
8 registry. |
|
9 |
|
10 The interface for interpreted role functions is as follows:: |
|
11 |
|
12 def role_fn(name, rawtext, text, lineno, inliner, |
|
13 options={}, content=[]): |
|
14 code... |
|
15 |
|
16 # Set function attributes for customization: |
|
17 role_fn.options = ... |
|
18 role_fn.content = ... |
|
19 |
|
20 Parameters: |
|
21 |
|
22 - ``name`` is the local name of the interpreted text role, the role name |
|
23 actually used in the document. |
|
24 |
|
25 - ``rawtext`` is a string containing the entire interpreted text construct. |
|
26 Return it as a ``problematic`` node linked to a system message if there is a |
|
27 problem. |
|
28 |
|
29 - ``text`` is the interpreted text content, with backslash escapes converted |
|
30 to nulls (``\x00``). |
|
31 |
|
32 - ``lineno`` is the line number where the interpreted text beings. |
|
33 |
|
34 - ``inliner`` is the Inliner object that called the role function. |
|
35 It defines the following useful attributes: ``reporter``, |
|
36 ``problematic``, ``memo``, ``parent``, ``document``. |
|
37 |
|
38 - ``options``: A dictionary of directive options for customization, to be |
|
39 interpreted by the role function. Used for additional attributes for the |
|
40 generated elements and other functionality. |
|
41 |
|
42 - ``content``: A list of strings, the directive content for customization |
|
43 ("role" directive). To be interpreted by the role function. |
|
44 |
|
45 Function attributes for customization, interpreted by the "role" directive: |
|
46 |
|
47 - ``options``: A dictionary, mapping known option names to conversion |
|
48 functions such as `int` or `float`. ``None`` or an empty dict implies no |
|
49 options to parse. Several directive option conversion functions are defined |
|
50 in the `directives` module. |
|
51 |
|
52 All role functions implicitly support the "class" option, unless disabled |
|
53 with an explicit ``{'class': None}``. |
|
54 |
|
55 - ``content``: A boolean; true if content is allowed. Client code must handle |
|
56 the case where content is required but not supplied (an empty content list |
|
57 will be supplied). |
|
58 |
|
59 Note that unlike directives, the "arguments" function attribute is not |
|
60 supported for role customization. Directive arguments are handled by the |
|
61 "role" directive itself. |
|
62 |
|
63 Interpreted role functions return a tuple of two values: |
|
64 |
|
65 - A list of nodes which will be inserted into the document tree at the |
|
66 point where the interpreted role was encountered (can be an empty |
|
67 list). |
|
68 |
|
69 - A list of system messages, which will be inserted into the document tree |
|
70 immediately after the end of the current inline block (can also be empty). |
|
71 """ |
|
72 |
|
73 __docformat__ = 'reStructuredText' |
|
74 |
|
75 from docutils import nodes, utils |
|
76 from docutils.parsers.rst import directives |
|
77 from docutils.parsers.rst.languages import en as _fallback_language_module |
|
78 |
|
79 DEFAULT_INTERPRETED_ROLE = 'title-reference' |
|
80 """ |
|
81 The canonical name of the default interpreted role. This role is used |
|
82 when no role is specified for a piece of interpreted text. |
|
83 """ |
|
84 |
|
85 _role_registry = {} |
|
86 """Mapping of canonical role names to role functions. Language-dependent role |
|
87 names are defined in the ``language`` subpackage.""" |
|
88 |
|
89 _roles = {} |
|
90 """Mapping of local or language-dependent interpreted text role names to role |
|
91 functions.""" |
|
92 |
|
93 def role(role_name, language_module, lineno, reporter): |
|
94 """ |
|
95 Locate and return a role function from its language-dependent name, along |
|
96 with a list of system messages. If the role is not found in the current |
|
97 language, check English. Return a 2-tuple: role function (``None`` if the |
|
98 named role cannot be found) and a list of system messages. |
|
99 """ |
|
100 normname = role_name.lower() |
|
101 messages = [] |
|
102 msg_text = [] |
|
103 |
|
104 if _roles.has_key(normname): |
|
105 return _roles[normname], messages |
|
106 |
|
107 if role_name: |
|
108 canonicalname = None |
|
109 try: |
|
110 canonicalname = language_module.roles[normname] |
|
111 except AttributeError, error: |
|
112 msg_text.append('Problem retrieving role entry from language ' |
|
113 'module %r: %s.' % (language_module, error)) |
|
114 except KeyError: |
|
115 msg_text.append('No role entry for "%s" in module "%s".' |
|
116 % (role_name, language_module.__name__)) |
|
117 else: |
|
118 canonicalname = DEFAULT_INTERPRETED_ROLE |
|
119 |
|
120 # If we didn't find it, try English as a fallback. |
|
121 if not canonicalname: |
|
122 try: |
|
123 canonicalname = _fallback_language_module.roles[normname] |
|
124 msg_text.append('Using English fallback for role "%s".' |
|
125 % role_name) |
|
126 except KeyError: |
|
127 msg_text.append('Trying "%s" as canonical role name.' |
|
128 % role_name) |
|
129 # The canonical name should be an English name, but just in case: |
|
130 canonicalname = normname |
|
131 |
|
132 # Collect any messages that we generated. |
|
133 if msg_text: |
|
134 message = reporter.info('\n'.join(msg_text), line=lineno) |
|
135 messages.append(message) |
|
136 |
|
137 # Look the role up in the registry, and return it. |
|
138 if _role_registry.has_key(canonicalname): |
|
139 role_fn = _role_registry[canonicalname] |
|
140 register_local_role(normname, role_fn) |
|
141 return role_fn, messages |
|
142 else: |
|
143 return None, messages # Error message will be generated by caller. |
|
144 |
|
145 def register_canonical_role(name, role_fn): |
|
146 """ |
|
147 Register an interpreted text role by its canonical name. |
|
148 |
|
149 :Parameters: |
|
150 - `name`: The canonical name of the interpreted role. |
|
151 - `role_fn`: The role function. See the module docstring. |
|
152 """ |
|
153 set_implicit_options(role_fn) |
|
154 _role_registry[name] = role_fn |
|
155 |
|
156 def register_local_role(name, role_fn): |
|
157 """ |
|
158 Register an interpreted text role by its local or language-dependent name. |
|
159 |
|
160 :Parameters: |
|
161 - `name`: The local or language-dependent name of the interpreted role. |
|
162 - `role_fn`: The role function. See the module docstring. |
|
163 """ |
|
164 set_implicit_options(role_fn) |
|
165 _roles[name] = role_fn |
|
166 |
|
167 def set_implicit_options(role_fn): |
|
168 """ |
|
169 Add customization options to role functions, unless explicitly set or |
|
170 disabled. |
|
171 """ |
|
172 if not hasattr(role_fn, 'options') or role_fn.options is None: |
|
173 role_fn.options = {'class': directives.class_option} |
|
174 elif not role_fn.options.has_key('class'): |
|
175 role_fn.options['class'] = directives.class_option |
|
176 |
|
177 def register_generic_role(canonical_name, node_class): |
|
178 """For roles which simply wrap a given `node_class` around the text.""" |
|
179 role = GenericRole(canonical_name, node_class) |
|
180 register_canonical_role(canonical_name, role) |
|
181 |
|
182 |
|
183 class GenericRole: |
|
184 |
|
185 """ |
|
186 Generic interpreted text role, where the interpreted text is simply |
|
187 wrapped with the provided node class. |
|
188 """ |
|
189 |
|
190 def __init__(self, role_name, node_class): |
|
191 self.name = role_name |
|
192 self.node_class = node_class |
|
193 |
|
194 def __call__(self, role, rawtext, text, lineno, inliner, |
|
195 options={}, content=[]): |
|
196 set_classes(options) |
|
197 return [self.node_class(rawtext, utils.unescape(text), **options)], [] |
|
198 |
|
199 |
|
200 class CustomRole: |
|
201 |
|
202 """ |
|
203 Wrapper for custom interpreted text roles. |
|
204 """ |
|
205 |
|
206 def __init__(self, role_name, base_role, options={}, content=[]): |
|
207 self.name = role_name |
|
208 self.base_role = base_role |
|
209 self.options = None |
|
210 if hasattr(base_role, 'options'): |
|
211 self.options = base_role.options |
|
212 self.content = None |
|
213 if hasattr(base_role, 'content'): |
|
214 self.content = base_role.content |
|
215 self.supplied_options = options |
|
216 self.supplied_content = content |
|
217 |
|
218 def __call__(self, role, rawtext, text, lineno, inliner, |
|
219 options={}, content=[]): |
|
220 opts = self.supplied_options.copy() |
|
221 opts.update(options) |
|
222 cont = list(self.supplied_content) |
|
223 if cont and content: |
|
224 cont += '\n' |
|
225 cont.extend(content) |
|
226 return self.base_role(role, rawtext, text, lineno, inliner, |
|
227 options=opts, content=cont) |
|
228 |
|
229 |
|
230 def generic_custom_role(role, rawtext, text, lineno, inliner, |
|
231 options={}, content=[]): |
|
232 """""" |
|
233 # Once nested inline markup is implemented, this and other methods should |
|
234 # recursively call inliner.nested_parse(). |
|
235 set_classes(options) |
|
236 return [nodes.inline(rawtext, utils.unescape(text), **options)], [] |
|
237 |
|
238 generic_custom_role.options = {'class': directives.class_option} |
|
239 |
|
240 |
|
241 ###################################################################### |
|
242 # Define and register the standard roles: |
|
243 ###################################################################### |
|
244 |
|
245 register_generic_role('abbreviation', nodes.abbreviation) |
|
246 register_generic_role('acronym', nodes.acronym) |
|
247 register_generic_role('emphasis', nodes.emphasis) |
|
248 register_generic_role('literal', nodes.literal) |
|
249 register_generic_role('strong', nodes.strong) |
|
250 register_generic_role('subscript', nodes.subscript) |
|
251 register_generic_role('superscript', nodes.superscript) |
|
252 register_generic_role('title-reference', nodes.title_reference) |
|
253 |
|
254 def pep_reference_role(role, rawtext, text, lineno, inliner, |
|
255 options={}, content=[]): |
|
256 try: |
|
257 pepnum = int(text) |
|
258 if pepnum < 0 or pepnum > 9999: |
|
259 raise ValueError |
|
260 except ValueError: |
|
261 msg = inliner.reporter.error( |
|
262 'PEP number must be a number from 0 to 9999; "%s" is invalid.' |
|
263 % text, line=lineno) |
|
264 prb = inliner.problematic(rawtext, rawtext, msg) |
|
265 return [prb], [msg] |
|
266 # Base URL mainly used by inliner.pep_reference; so this is correct: |
|
267 ref = (inliner.document.settings.pep_base_url |
|
268 + inliner.document.settings.pep_file_url_template % pepnum) |
|
269 set_classes(options) |
|
270 return [nodes.reference(rawtext, 'PEP ' + utils.unescape(text), refuri=ref, |
|
271 **options)], [] |
|
272 |
|
273 register_canonical_role('pep-reference', pep_reference_role) |
|
274 |
|
275 def rfc_reference_role(role, rawtext, text, lineno, inliner, |
|
276 options={}, content=[]): |
|
277 try: |
|
278 rfcnum = int(text) |
|
279 if rfcnum <= 0: |
|
280 raise ValueError |
|
281 except ValueError: |
|
282 msg = inliner.reporter.error( |
|
283 'RFC number must be a number greater than or equal to 1; ' |
|
284 '"%s" is invalid.' % text, line=lineno) |
|
285 prb = inliner.problematic(rawtext, rawtext, msg) |
|
286 return [prb], [msg] |
|
287 # Base URL mainly used by inliner.rfc_reference, so this is correct: |
|
288 ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum |
|
289 set_classes(options) |
|
290 node = nodes.reference(rawtext, 'RFC ' + utils.unescape(text), refuri=ref, |
|
291 **options) |
|
292 return [node], [] |
|
293 |
|
294 register_canonical_role('rfc-reference', rfc_reference_role) |
|
295 |
|
296 def raw_role(role, rawtext, text, lineno, inliner, options={}, content=[]): |
|
297 if not options.has_key('format'): |
|
298 msg = inliner.reporter.error( |
|
299 'No format (Writer name) is associated with this role: "%s".\n' |
|
300 'The "raw" role cannot be used directly.\n' |
|
301 'Instead, use the "role" directive to create a new role with ' |
|
302 'an associated format.' % role, line=lineno) |
|
303 prb = inliner.problematic(rawtext, rawtext, msg) |
|
304 return [prb], [msg] |
|
305 set_classes(options) |
|
306 node = nodes.raw(rawtext, utils.unescape(text, 1), **options) |
|
307 return [node], [] |
|
308 |
|
309 raw_role.options = {'format': directives.unchanged} |
|
310 |
|
311 register_canonical_role('raw', raw_role) |
|
312 |
|
313 |
|
314 ###################################################################### |
|
315 # Register roles that are currently unimplemented. |
|
316 ###################################################################### |
|
317 |
|
318 def unimplemented_role(role, rawtext, text, lineno, inliner, attributes={}): |
|
319 msg = inliner.reporter.error( |
|
320 'Interpreted text role "%s" not implemented.' % role, line=lineno) |
|
321 prb = inliner.problematic(rawtext, rawtext, msg) |
|
322 return [prb], [msg] |
|
323 |
|
324 register_canonical_role('index', unimplemented_role) |
|
325 register_canonical_role('named-reference', unimplemented_role) |
|
326 register_canonical_role('anonymous-reference', unimplemented_role) |
|
327 register_canonical_role('uri-reference', unimplemented_role) |
|
328 register_canonical_role('footnote-reference', unimplemented_role) |
|
329 register_canonical_role('citation-reference', unimplemented_role) |
|
330 register_canonical_role('substitution-reference', unimplemented_role) |
|
331 register_canonical_role('target', unimplemented_role) |
|
332 |
|
333 # This should remain unimplemented, for testing purposes: |
|
334 register_canonical_role('restructuredtext-unimplemented-role', |
|
335 unimplemented_role) |
|
336 |
|
337 |
|
338 def set_classes(options): |
|
339 """ |
|
340 Auxiliary function to set options['classes'] and delete |
|
341 options['class']. |
|
342 """ |
|
343 if options.has_key('class'): |
|
344 assert not options.has_key('classes') |
|
345 options['classes'] = options['class'] |
|
346 del options['class'] |
|