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