Clean up system message (source, line) reporting.
[docutils.git] / docutils / parsers / rst / roles.py
blob73ec479e6da1c549302401a03bbcec9205256590
1 # $Id$
2 # Author: Edward Loper <edloper@gradient.cis.upenn.edu>
3 # Copyright: This module has been placed in the public domain.
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.
10 The interface for interpreted role functions is as follows::
12 def role_fn(name, rawtext, text, lineno, inliner,
13 options={}, content=[]):
14 code...
16 # Set function attributes for customization:
17 role_fn.options = ...
18 role_fn.content = ...
20 Parameters:
22 - ``name`` is the local name of the interpreted text role, the role name
23 actually used in the document.
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.
29 - ``text`` is the interpreted text content, with backslash escapes converted
30 to nulls (``\x00``).
32 - ``lineno`` is the line number where the interpreted text beings.
34 - ``inliner`` is the Inliner object that called the role function.
35 It defines the following useful attributes: ``reporter``,
36 ``problematic``, ``memo``, ``parent``, ``document``.
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.
42 - ``content``: A list of strings, the directive content for customization
43 ("role" directive). To be interpreted by the role function.
45 Function attributes for customization, interpreted by the "role" directive:
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.
52 All role functions implicitly support the "class" option, unless disabled
53 with an explicit ``{'class': None}``.
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).
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.
63 Interpreted role functions return a tuple of two values:
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).
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 """
73 __docformat__ = 'reStructuredText'
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 from docutils.utils.code_analyzer import Lexer, LexerError
80 DEFAULT_INTERPRETED_ROLE = 'title-reference'
81 """
82 The canonical name of the default interpreted role. This role is used
83 when no role is specified for a piece of interpreted text.
84 """
86 _role_registry = {}
87 """Mapping of canonical role names to role functions. Language-dependent role
88 names are defined in the ``language`` subpackage."""
90 _roles = {}
91 """Mapping of local or language-dependent interpreted text role names to role
92 functions."""
94 def role(role_name, language_module, lineno, reporter):
95 """
96 Locate and return a role function from its language-dependent name, along
97 with a list of system messages. If the role is not found in the current
98 language, check English. Return a 2-tuple: role function (``None`` if the
99 named role cannot be found) and a list of system messages.
101 normname = role_name.lower()
102 messages = []
103 msg_text = []
105 if normname in _roles:
106 return _roles[normname], messages
108 if role_name:
109 canonicalname = None
110 try:
111 canonicalname = language_module.roles[normname]
112 except AttributeError, error:
113 msg_text.append('Problem retrieving role entry from language '
114 'module %r: %s.' % (language_module, error))
115 except KeyError:
116 msg_text.append('No role entry for "%s" in module "%s".'
117 % (role_name, language_module.__name__))
118 else:
119 canonicalname = DEFAULT_INTERPRETED_ROLE
121 # If we didn't find it, try English as a fallback.
122 if not canonicalname:
123 try:
124 canonicalname = _fallback_language_module.roles[normname]
125 msg_text.append('Using English fallback for role "%s".'
126 % role_name)
127 except KeyError:
128 msg_text.append('Trying "%s" as canonical role name.'
129 % role_name)
130 # The canonical name should be an English name, but just in case:
131 canonicalname = normname
133 # Collect any messages that we generated.
134 if msg_text:
135 message = reporter.info('\n'.join(msg_text), line=lineno)
136 messages.append(message)
138 # Look the role up in the registry, and return it.
139 if canonicalname in _role_registry:
140 role_fn = _role_registry[canonicalname]
141 register_local_role(normname, role_fn)
142 return role_fn, messages
143 else:
144 return None, messages # Error message will be generated by caller.
146 def register_canonical_role(name, role_fn):
148 Register an interpreted text role by its canonical name.
150 :Parameters:
151 - `name`: The canonical name of the interpreted role.
152 - `role_fn`: The role function. See the module docstring.
154 set_implicit_options(role_fn)
155 _role_registry[name] = role_fn
157 def register_local_role(name, role_fn):
159 Register an interpreted text role by its local or language-dependent name.
161 :Parameters:
162 - `name`: The local or language-dependent name of the interpreted role.
163 - `role_fn`: The role function. See the module docstring.
165 set_implicit_options(role_fn)
166 _roles[name] = role_fn
168 def set_implicit_options(role_fn):
170 Add customization options to role functions, unless explicitly set or
171 disabled.
173 if not hasattr(role_fn, 'options') or role_fn.options is None:
174 role_fn.options = {'class': directives.class_option}
175 elif 'class' not in role_fn.options:
176 role_fn.options['class'] = directives.class_option
178 def register_generic_role(canonical_name, node_class):
179 """For roles which simply wrap a given `node_class` around the text."""
180 role = GenericRole(canonical_name, node_class)
181 register_canonical_role(canonical_name, role)
184 class GenericRole:
187 Generic interpreted text role, where the interpreted text is simply
188 wrapped with the provided node class.
191 def __init__(self, role_name, node_class):
192 self.name = role_name
193 self.node_class = node_class
195 def __call__(self, role, rawtext, text, lineno, inliner,
196 options={}, content=[]):
197 set_classes(options)
198 return [self.node_class(rawtext, utils.unescape(text), **options)], []
201 class CustomRole:
204 Wrapper for custom interpreted text roles.
207 def __init__(self, role_name, base_role, options={}, content=[]):
208 self.name = role_name
209 self.base_role = base_role
210 self.options = None
211 if hasattr(base_role, 'options'):
212 self.options = base_role.options
213 self.content = None
214 if hasattr(base_role, 'content'):
215 self.content = base_role.content
216 self.supplied_options = options
217 self.supplied_content = content
219 def __call__(self, role, rawtext, text, lineno, inliner,
220 options={}, content=[]):
221 opts = self.supplied_options.copy()
222 opts.update(options)
223 cont = list(self.supplied_content)
224 if cont and content:
225 cont += '\n'
226 cont.extend(content)
227 return self.base_role(role, rawtext, text, lineno, inliner,
228 options=opts, content=cont)
231 def generic_custom_role(role, rawtext, text, lineno, inliner,
232 options={}, content=[]):
233 """"""
234 # Once nested inline markup is implemented, this and other methods should
235 # recursively call inliner.nested_parse().
236 set_classes(options)
237 return [nodes.inline(rawtext, utils.unescape(text), **options)], []
239 generic_custom_role.options = {'class': directives.class_option}
242 ######################################################################
243 # Define and register the standard roles:
244 ######################################################################
246 register_generic_role('abbreviation', nodes.abbreviation)
247 register_generic_role('acronym', nodes.acronym)
248 register_generic_role('emphasis', nodes.emphasis)
249 register_generic_role('literal', nodes.literal)
250 register_generic_role('strong', nodes.strong)
251 register_generic_role('subscript', nodes.subscript)
252 register_generic_role('superscript', nodes.superscript)
253 register_generic_role('title-reference', nodes.title_reference)
255 def pep_reference_role(role, rawtext, text, lineno, inliner,
256 options={}, content=[]):
257 try:
258 pepnum = int(text)
259 if pepnum < 0 or pepnum > 9999:
260 raise ValueError
261 except ValueError:
262 msg = inliner.reporter.error(
263 'PEP number must be a number from 0 to 9999; "%s" is invalid.'
264 % text, line=lineno)
265 prb = inliner.problematic(rawtext, rawtext, msg)
266 return [prb], [msg]
267 # Base URL mainly used by inliner.pep_reference; so this is correct:
268 ref = (inliner.document.settings.pep_base_url
269 + inliner.document.settings.pep_file_url_template % pepnum)
270 set_classes(options)
271 return [nodes.reference(rawtext, 'PEP ' + utils.unescape(text), refuri=ref,
272 **options)], []
274 register_canonical_role('pep-reference', pep_reference_role)
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], []
295 register_canonical_role('rfc-reference', rfc_reference_role)
297 def raw_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
298 if not inliner.document.settings.raw_enabled:
299 msg = inliner.reporter.warning('raw (and derived) roles disabled')
300 prb = inliner.problematic(rawtext, rawtext, msg)
301 return [prb], [msg]
302 if 'format' not in options:
303 msg = inliner.reporter.error(
304 'No format (Writer name) is associated with this role: "%s".\n'
305 'The "raw" role cannot be used directly.\n'
306 'Instead, use the "role" directive to create a new role with '
307 'an associated format.' % role, line=lineno)
308 prb = inliner.problematic(rawtext, rawtext, msg)
309 return [prb], [msg]
310 set_classes(options)
311 node = nodes.raw(rawtext, utils.unescape(text, 1), **options)
312 node.source, node.line = inliner.reporter.get_source_and_line(lineno)
313 return [node], []
315 raw_role.options = {'format': directives.unchanged}
317 register_canonical_role('raw', raw_role)
319 def code_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
320 set_classes(options)
321 language = options.get('language', '')
322 classes = ['code']
323 if language:
324 classes.append(language)
325 if 'classes' in options:
326 classes.extend(options['classes'])
328 try:
329 tokens = Lexer(utils.unescape(text, 1), language,
330 inliner.document.settings.syntax_highlight)
331 except LexerError, error:
332 msg = inliner.reporter.warning(error)
333 prb = inliner.problematic(rawtext, rawtext, msg)
334 return [prb], [msg]
336 node = nodes.literal(rawtext, '', classes=classes)
338 # analyze content and add nodes for every token
339 for classes, value in tokens:
340 # print (classes, value)
341 if classes:
342 node += nodes.inline(value, value, classes=classes)
343 else:
344 # insert as Text to decrease the verbosity of the output
345 node += nodes.Text(value, value)
347 return [node], []
349 code_role.options = {'class': directives.class_option,
350 'language': directives.unchanged}
352 register_canonical_role('code', code_role)
354 def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
355 i = rawtext.find('`')
356 text = rawtext.split('`')[1]
357 node = nodes.math(rawtext, text)
358 return [node], []
360 register_canonical_role('math', math_role)
362 ######################################################################
363 # Register roles that are currently unimplemented.
364 ######################################################################
366 def unimplemented_role(role, rawtext, text, lineno, inliner, attributes={}):
367 msg = inliner.reporter.error(
368 'Interpreted text role "%s" not implemented.' % role, line=lineno)
369 prb = inliner.problematic(rawtext, rawtext, msg)
370 return [prb], [msg]
372 register_canonical_role('index', unimplemented_role)
373 register_canonical_role('named-reference', unimplemented_role)
374 register_canonical_role('anonymous-reference', unimplemented_role)
375 register_canonical_role('uri-reference', unimplemented_role)
376 register_canonical_role('footnote-reference', unimplemented_role)
377 register_canonical_role('citation-reference', unimplemented_role)
378 register_canonical_role('substitution-reference', unimplemented_role)
379 register_canonical_role('target', unimplemented_role)
381 # This should remain unimplemented, for testing purposes:
382 register_canonical_role('restructuredtext-unimplemented-role',
383 unimplemented_role)
386 def set_classes(options):
388 Auxiliary function to set options['classes'] and delete
389 options['class'].
391 if 'class' in options:
392 assert 'classes' not in options
393 options['classes'] = options['class']
394 del options['class']