[3] | 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'] |
---|