diff options
Diffstat (limited to 'docutils/parsers/rst/directives/misc.py')
-rw-r--r-- | docutils/parsers/rst/directives/misc.py | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/docutils/parsers/rst/directives/misc.py b/docutils/parsers/rst/directives/misc.py new file mode 100644 index 000000000..42f642fee --- /dev/null +++ b/docutils/parsers/rst/directives/misc.py @@ -0,0 +1,408 @@ +# Authors: David Goodger, Dethe Elza +# Contact: goodger@users.sourceforge.net +# Revision: $Revision$ +# Date: $Date$ +# Copyright: This module has been placed in the public domain. + +"""Miscellaneous directives.""" + +__docformat__ = 'reStructuredText' + +import sys +import os.path +import re +import time +from docutils import io, nodes, statemachine, utils +from docutils.parsers.rst import directives, roles, states +from docutils.transforms import misc + +try: + import urllib2 +except ImportError: + urllib2 = None + + +standard_include_path = os.path.join(os.path.dirname(states.__file__), + 'include') + +def include(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """Include a reST file as part of the content of this reST file.""" + if not state.document.settings.file_insertion_enabled: + warning = state_machine.reporter.warning( + '"%s" directive disabled.' % name, + nodes.literal_block(block_text, block_text), line=lineno) + return [warning] + source = state_machine.input_lines.source( + lineno - state_machine.input_offset - 1) + source_dir = os.path.dirname(os.path.abspath(source)) + path = directives.path(arguments[0]) + if path.startswith('<') and path.endswith('>'): + path = os.path.join(standard_include_path, path[1:-1]) + path = os.path.normpath(os.path.join(source_dir, path)) + path = utils.relative_path(None, path) + encoding = options.get('encoding', state.document.settings.input_encoding) + try: + state.document.settings.record_dependencies.add(path) + include_file = io.FileInput( + source_path=path, encoding=encoding, + error_handler=state.document.settings.input_encoding_error_handler, + handle_io_errors=None) + except IOError, error: + severe = state_machine.reporter.severe( + 'Problems with "%s" directive path:\n%s: %s.' + % (name, error.__class__.__name__, error), + nodes.literal_block(block_text, block_text), line=lineno) + return [severe] + try: + include_text = include_file.read() + except UnicodeError, error: + severe = state_machine.reporter.severe( + 'Problem with "%s" directive:\n%s: %s' + % (name, error.__class__.__name__, error), + nodes.literal_block(block_text, block_text), line=lineno) + return [severe] + if options.has_key('literal'): + literal_block = nodes.literal_block(include_text, include_text, + source=path) + literal_block.line = 1 + return literal_block + else: + include_lines = statemachine.string2lines(include_text, + convert_whitespace=1) + state_machine.insert_input(include_lines, path) + return [] + +include.arguments = (1, 0, 1) +include.options = {'literal': directives.flag, + 'encoding': directives.encoding} + +def raw(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """ + Pass through content unchanged + + Content is included in output based on type argument + + Content may be included inline (content section of directive) or + imported from a file or url. + """ + if ( not state.document.settings.raw_enabled + or (not state.document.settings.file_insertion_enabled + and (options.has_key('file') or options.has_key('url'))) ): + warning = state_machine.reporter.warning( + '"%s" directive disabled.' % name, + nodes.literal_block(block_text, block_text), line=lineno) + return [warning] + attributes = {'format': ' '.join(arguments[0].lower().split())} + encoding = options.get('encoding', state.document.settings.input_encoding) + if content: + if options.has_key('file') or options.has_key('url'): + error = state_machine.reporter.error( + '"%s" directive may not both specify an external file and ' + 'have content.' % name, + nodes.literal_block(block_text, block_text), line=lineno) + return [error] + text = '\n'.join(content) + elif options.has_key('file'): + if options.has_key('url'): + error = state_machine.reporter.error( + 'The "file" and "url" options may not be simultaneously ' + 'specified for the "%s" directive.' % name, + nodes.literal_block(block_text, block_text), line=lineno) + return [error] + source_dir = os.path.dirname( + os.path.abspath(state.document.current_source)) + path = os.path.normpath(os.path.join(source_dir, options['file'])) + path = utils.relative_path(None, path) + try: + state.document.settings.record_dependencies.add(path) + raw_file = io.FileInput( + source_path=path, encoding=encoding, + error_handler=state.document.settings.input_encoding_error_handler, + handle_io_errors=None) + except IOError, error: + severe = state_machine.reporter.severe( + 'Problems with "%s" directive path:\n%s.' % (name, error), + nodes.literal_block(block_text, block_text), line=lineno) + return [severe] + try: + text = raw_file.read() + except UnicodeError, error: + severe = state_machine.reporter.severe( + 'Problem with "%s" directive:\n%s: %s' + % (name, error.__class__.__name__, error), + nodes.literal_block(block_text, block_text), line=lineno) + return [severe] + attributes['source'] = path + elif options.has_key('url'): + if not urllib2: + severe = state_machine.reporter.severe( + 'Problems with the "%s" directive and its "url" option: ' + 'unable to access the required functionality (from the ' + '"urllib2" module).' % name, + nodes.literal_block(block_text, block_text), line=lineno) + return [severe] + source = options['url'] + try: + raw_text = urllib2.urlopen(source).read() + except (urllib2.URLError, IOError, OSError), error: + severe = state_machine.reporter.severe( + 'Problems with "%s" directive URL "%s":\n%s.' + % (name, options['url'], error), + nodes.literal_block(block_text, block_text), line=lineno) + return [severe] + raw_file = io.StringInput( + source=raw_text, source_path=source, encoding=encoding, + error_handler=state.document.settings.input_encoding_error_handler) + try: + text = raw_file.read() + except UnicodeError, error: + severe = state_machine.reporter.severe( + 'Problem with "%s" directive:\n%s: %s' + % (name, error.__class__.__name__, error), + nodes.literal_block(block_text, block_text), line=lineno) + return [severe] + attributes['source'] = source + else: + error = state_machine.reporter.warning( + 'The "%s" directive requires content; none supplied.' % (name), + nodes.literal_block(block_text, block_text), line=lineno) + return [error] + raw_node = nodes.raw('', text, **attributes) + return [raw_node] + +raw.arguments = (1, 0, 1) +raw.options = {'file': directives.path, + 'url': directives.uri, + 'encoding': directives.encoding} +raw.content = 1 + +def replace(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + if not isinstance(state, states.SubstitutionDef): + error = state_machine.reporter.error( + 'Invalid context: the "%s" directive can only be used within a ' + 'substitution definition.' % (name), + nodes.literal_block(block_text, block_text), line=lineno) + return [error] + text = '\n'.join(content) + element = nodes.Element(text) + if text: + state.nested_parse(content, content_offset, element) + if len(element) != 1 or not isinstance(element[0], nodes.paragraph): + messages = [] + for node in element: + if isinstance(node, nodes.system_message): + node['backrefs'] = [] + messages.append(node) + error = state_machine.reporter.error( + 'Error in "%s" directive: may contain a single paragraph ' + 'only.' % (name), line=lineno) + messages.append(error) + return messages + else: + return element[0].children + else: + error = state_machine.reporter.error( + 'The "%s" directive is empty; content required.' % (name), + line=lineno) + return [error] + +replace.content = 1 + +def unicode_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + r""" + Convert Unicode character codes (numbers) to characters. Codes may be + decimal numbers, hexadecimal numbers (prefixed by ``0x``, ``x``, ``\x``, + ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style numeric character + entities (e.g. ``☮``). Text following ".." is a comment and is + ignored. Spaces are ignored, and any other text remains as-is. + """ + if not isinstance(state, states.SubstitutionDef): + error = state_machine.reporter.error( + 'Invalid context: the "%s" directive can only be used within a ' + 'substitution definition.' % (name), + nodes.literal_block(block_text, block_text), line=lineno) + return [error] + substitution_definition = state_machine.node + if options.has_key('trim'): + substitution_definition.attributes['ltrim'] = 1 + substitution_definition.attributes['rtrim'] = 1 + if options.has_key('ltrim'): + substitution_definition.attributes['ltrim'] = 1 + if options.has_key('rtrim'): + substitution_definition.attributes['rtrim'] = 1 + codes = unicode_comment_pattern.split(arguments[0])[0].split() + element = nodes.Element() + for code in codes: + try: + decoded = directives.unicode_code(code) + except ValueError, err: + error = state_machine.reporter.error( + 'Invalid character code: %s\n%s: %s' + % (code, err.__class__.__name__, err), + nodes.literal_block(block_text, block_text), line=lineno) + return [error] + element += nodes.Text(decoded) + return element.children + +unicode_directive.arguments = (1, 0, 1) +unicode_directive.options = {'trim': directives.flag, + 'ltrim': directives.flag, + 'rtrim': directives.flag} +unicode_comment_pattern = re.compile(r'( |\n|^)\.\. ') + +def class_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """ + Set a "class" attribute on the directive content or the next element. + When applied to the next element, a "pending" element is inserted, and a + transform does the work later. + """ + try: + class_value = directives.class_option(arguments[0]) + except ValueError: + error = state_machine.reporter.error( + 'Invalid class attribute value for "%s" directive: "%s".' + % (name, arguments[0]), + nodes.literal_block(block_text, block_text), line=lineno) + return [error] + node_list = [] + if content: + container = nodes.Element() + state.nested_parse(content, content_offset, container) + for node in container: + node['classes'].extend(class_value) + node_list.extend(container.children) + else: + pending = nodes.pending(misc.ClassAttribute, + {'class': class_value, 'directive': name}, + block_text) + state_machine.document.note_pending(pending) + node_list.append(pending) + return node_list + +class_directive.arguments = (1, 0, 1) +class_directive.content = 1 + +role_arg_pat = re.compile(r'(%s)\s*(\(\s*(%s)\s*\)\s*)?$' + % ((states.Inliner.simplename,) * 2)) +def role(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """Dynamically create and register a custom interpreted text role.""" + if content_offset > lineno or not content: + error = state_machine.reporter.error( + '"%s" directive requires arguments on the first line.' + % name, nodes.literal_block(block_text, block_text), line=lineno) + return [error] + args = content[0] + match = role_arg_pat.match(args) + if not match: + error = state_machine.reporter.error( + '"%s" directive arguments not valid role names: "%s".' + % (name, args), nodes.literal_block(block_text, block_text), + line=lineno) + return [error] + new_role_name = match.group(1) + base_role_name = match.group(3) + messages = [] + if base_role_name: + base_role, messages = roles.role( + base_role_name, state_machine.language, lineno, state.reporter) + if base_role is None: + error = state.reporter.error( + 'Unknown interpreted text role "%s".' % base_role_name, + nodes.literal_block(block_text, block_text), line=lineno) + return messages + [error] + else: + base_role = roles.generic_custom_role + assert not hasattr(base_role, 'arguments'), ( + 'Supplemental directive arguments for "%s" directive not supported' + '(specified by "%r" role).' % (name, base_role)) + try: + (arguments, options, content, content_offset) = ( + state.parse_directive_block(content[1:], content_offset, base_role, + option_presets={})) + except states.MarkupError, detail: + error = state_machine.reporter.error( + 'Error in "%s" directive:\n%s.' % (name, detail), + nodes.literal_block(block_text, block_text), line=lineno) + return messages + [error] + if not options.has_key('class'): + try: + options['class'] = directives.class_option(new_role_name) + except ValueError, detail: + error = state_machine.reporter.error( + 'Invalid argument for "%s" directive:\n%s.' + % (name, detail), + nodes.literal_block(block_text, block_text), line=lineno) + return messages + [error] + role = roles.CustomRole(new_role_name, base_role, options, content) + roles.register_local_role(new_role_name, role) + return messages + +role.content = 1 + +def default_role(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """Set the default interpreted text role.""" + if not arguments: + if roles._roles.has_key(''): + # restore the "default" default role + del roles._roles[''] + return [] + role_name = arguments[0] + role, messages = roles.role( + role_name, state_machine.language, lineno, state.reporter) + if role is None: + error = state.reporter.error( + 'Unknown interpreted text role "%s".' % role_name, + nodes.literal_block(block_text, block_text), line=lineno) + return messages + [error] + roles._roles[''] = role + # @@@ should this be local to the document, not the parser? + return messages + +default_role.arguments = (0, 1, 0) + +def title(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + state_machine.document['title'] = arguments[0] + return [] + +title.arguments = (1, 0, 1) + +def date(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + if not isinstance(state, states.SubstitutionDef): + error = state_machine.reporter.error( + 'Invalid context: the "%s" directive can only be used within a ' + 'substitution definition.' % (name), + nodes.literal_block(block_text, block_text), line=lineno) + return [error] + format = '\n'.join(content) or '%Y-%m-%d' + text = time.strftime(format) + return [nodes.Text(text)] + +date.content = 1 + +def directive_test_function(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """This directive is useful only for testing purposes.""" + if content: + text = '\n'.join(content) + info = state_machine.reporter.info( + 'Directive processed. Type="%s", arguments=%r, options=%r, ' + 'content:' % (name, arguments, options), + nodes.literal_block(text, text), line=lineno) + else: + info = state_machine.reporter.info( + 'Directive processed. Type="%s", arguments=%r, options=%r, ' + 'content: None' % (name, arguments, options), line=lineno) + return [info] + +directive_test_function.arguments = (0, 1, 1) +directive_test_function.options = {'option': directives.unchanged_required} +directive_test_function.content = 1 |