Mercurial > repos > guerler > springsuite
diff planemo/lib/python3.7/site-packages/docutils/parsers/rst/directives/misc.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:18:57 -0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/planemo/lib/python3.7/site-packages/docutils/parsers/rst/directives/misc.py Fri Jul 31 00:18:57 2020 -0400 @@ -0,0 +1,556 @@ +# $Id: misc.py 8370 2019-08-27 12:10:39Z milde $ +# Authors: David Goodger <goodger@python.org>; Dethe Elza +# 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.utils.error_reporting import SafeString, ErrorString +from docutils.utils.error_reporting import locale_encoding +from docutils.parsers.rst import Directive, convert_directive_function +from docutils.parsers.rst import directives, roles, states +from docutils.parsers.rst.directives.body import CodeBlock, NumberLines +from docutils.parsers.rst.roles import set_classes +from docutils.transforms import misc + +class Include(Directive): + + """ + Include content read from a separate source file. + + Content may be parsed by the parser, or included as a literal + block. The encoding of the included file can be specified. Only + a part of the given file argument may be included by specifying + start and end line or text to match before and/or after the text + to be used. + """ + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'literal': directives.flag, + 'code': directives.unchanged, + 'encoding': directives.encoding, + 'tab-width': int, + 'start-line': int, + 'end-line': int, + 'start-after': directives.unchanged_required, + 'end-before': directives.unchanged_required, + # ignored except for 'literal' or 'code': + 'number-lines': directives.unchanged, # integer or None + 'class': directives.class_option, + 'name': directives.unchanged} + + standard_include_path = os.path.join(os.path.dirname(states.__file__), + 'include') + + def run(self): + """Include a file as part of the content of this reST file.""" + if not self.state.document.settings.file_insertion_enabled: + raise self.warning('"%s" directive disabled.' % self.name) + source = self.state_machine.input_lines.source( + self.lineno - self.state_machine.input_offset - 1) + source_dir = os.path.dirname(os.path.abspath(source)) + path = directives.path(self.arguments[0]) + if path.startswith('<') and path.endswith('>'): + path = os.path.join(self.standard_include_path, path[1:-1]) + path = os.path.normpath(os.path.join(source_dir, path)) + path = utils.relative_path(None, path) + path = nodes.reprunicode(path) + encoding = self.options.get( + 'encoding', self.state.document.settings.input_encoding) + e_handler=self.state.document.settings.input_encoding_error_handler + tab_width = self.options.get( + 'tab-width', self.state.document.settings.tab_width) + try: + self.state.document.settings.record_dependencies.add(path) + include_file = io.FileInput(source_path=path, + encoding=encoding, + error_handler=e_handler) + except UnicodeEncodeError as error: + raise self.severe(u'Problems with "%s" directive path:\n' + 'Cannot encode input file path "%s" ' + '(wrong locale?).' % + (self.name, SafeString(path))) + except IOError as error: + raise self.severe(u'Problems with "%s" directive path:\n%s.' % + (self.name, ErrorString(error))) + startline = self.options.get('start-line', None) + endline = self.options.get('end-line', None) + try: + if startline or (endline is not None): + lines = include_file.readlines() + rawtext = ''.join(lines[startline:endline]) + else: + rawtext = include_file.read() + except UnicodeError as error: + raise self.severe(u'Problem with "%s" directive:\n%s' % + (self.name, ErrorString(error))) + # start-after/end-before: no restrictions on newlines in match-text, + # and no restrictions on matching inside lines vs. line boundaries + after_text = self.options.get('start-after', None) + if after_text: + # skip content in rawtext before *and incl.* a matching text + after_index = rawtext.find(after_text) + if after_index < 0: + raise self.severe('Problem with "start-after" option of "%s" ' + 'directive:\nText not found.' % self.name) + rawtext = rawtext[after_index + len(after_text):] + before_text = self.options.get('end-before', None) + if before_text: + # skip content in rawtext after *and incl.* a matching text + before_index = rawtext.find(before_text) + if before_index < 0: + raise self.severe('Problem with "end-before" option of "%s" ' + 'directive:\nText not found.' % self.name) + rawtext = rawtext[:before_index] + + include_lines = statemachine.string2lines(rawtext, tab_width, + convert_whitespace=True) + if 'literal' in self.options: + # Don't convert tabs to spaces, if `tab_width` is positive. + if tab_width >= 0: + text = rawtext.expandtabs(tab_width) + else: + text = rawtext + literal_block = nodes.literal_block(rawtext, source=path, + classes=self.options.get('class', [])) + literal_block.line = 1 + self.add_name(literal_block) + if 'number-lines' in self.options: + try: + startline = int(self.options['number-lines'] or 1) + except ValueError: + raise self.error(':number-lines: with non-integer ' + 'start value') + endline = startline + len(include_lines) + if text.endswith('\n'): + text = text[:-1] + tokens = NumberLines([([], text)], startline, endline) + for classes, value in tokens: + if classes: + literal_block += nodes.inline(value, value, + classes=classes) + else: + literal_block += nodes.Text(value) + else: + literal_block += nodes.Text(text) + return [literal_block] + if 'code' in self.options: + self.options['source'] = path + # Don't convert tabs to spaces, if `tab_width` is negative: + if tab_width < 0: + include_lines = rawtext.splitlines() + codeblock = CodeBlock(self.name, + [self.options.pop('code')], # arguments + self.options, + include_lines, # content + self.lineno, + self.content_offset, + self.block_text, + self.state, + self.state_machine) + return codeblock.run() + self.state_machine.insert_input(include_lines, path) + return [] + + +class Raw(Directive): + + """ + 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. + """ + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'file': directives.path, + 'url': directives.uri, + 'encoding': directives.encoding} + has_content = True + + def run(self): + if (not self.state.document.settings.raw_enabled + or (not self.state.document.settings.file_insertion_enabled + and ('file' in self.options + or 'url' in self.options))): + raise self.warning('"%s" directive disabled.' % self.name) + attributes = {'format': ' '.join(self.arguments[0].lower().split())} + encoding = self.options.get( + 'encoding', self.state.document.settings.input_encoding) + e_handler=self.state.document.settings.input_encoding_error_handler + if self.content: + if 'file' in self.options or 'url' in self.options: + raise self.error( + '"%s" directive may not both specify an external file ' + 'and have content.' % self.name) + text = '\n'.join(self.content) + elif 'file' in self.options: + if 'url' in self.options: + raise self.error( + 'The "file" and "url" options may not be simultaneously ' + 'specified for the "%s" directive.' % self.name) + source_dir = os.path.dirname( + os.path.abspath(self.state.document.current_source)) + path = os.path.normpath(os.path.join(source_dir, + self.options['file'])) + path = utils.relative_path(None, path) + try: + raw_file = io.FileInput(source_path=path, + encoding=encoding, + error_handler=e_handler) + # TODO: currently, raw input files are recorded as + # dependencies even if not used for the chosen output format. + self.state.document.settings.record_dependencies.add(path) + except IOError as error: + raise self.severe(u'Problems with "%s" directive path:\n%s.' + % (self.name, ErrorString(error))) + try: + text = raw_file.read() + except UnicodeError as error: + raise self.severe(u'Problem with "%s" directive:\n%s' + % (self.name, ErrorString(error))) + attributes['source'] = path + elif 'url' in self.options: + source = self.options['url'] + # Do not import urllib2 at the top of the module because + # it may fail due to broken SSL dependencies, and it takes + # about 0.15 seconds to load. + if sys.version_info >= (3, 0): + from urllib.request import urlopen + from urllib.error import URLError + else: + from urllib2 import urlopen, URLError + try: + raw_text = urlopen(source).read() + except (URLError, IOError, OSError) as error: + raise self.severe(u'Problems with "%s" directive URL "%s":\n%s.' + % (self.name, self.options['url'], ErrorString(error))) + raw_file = io.StringInput(source=raw_text, source_path=source, + encoding=encoding, + error_handler=e_handler) + try: + text = raw_file.read() + except UnicodeError as error: + raise self.severe(u'Problem with "%s" directive:\n%s' + % (self.name, ErrorString(error))) + attributes['source'] = source + else: + # This will always fail because there is no content. + self.assert_has_content() + raw_node = nodes.raw('', text, **attributes) + (raw_node.source, + raw_node.line) = self.state_machine.get_source_and_line(self.lineno) + return [raw_node] + + +class Replace(Directive): + + has_content = True + + def run(self): + if not isinstance(self.state, states.SubstitutionDef): + raise self.error( + 'Invalid context: the "%s" directive can only be used within ' + 'a substitution definition.' % self.name) + self.assert_has_content() + text = '\n'.join(self.content) + element = nodes.Element(text) + self.state.nested_parse(self.content, self.content_offset, + element) + # element might contain [paragraph] + system_message(s) + node = None + messages = [] + for elem in element: + if not node and isinstance(elem, nodes.paragraph): + node = elem + elif isinstance(elem, nodes.system_message): + elem['backrefs'] = [] + messages.append(elem) + else: + return [ + self.state_machine.reporter.error( + 'Error in "%s" directive: may contain a single paragraph ' + 'only.' % (self.name), line=self.lineno) ] + if node: + return messages + node.children + return messages + +class Unicode(Directive): + + 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. + """ + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'trim': directives.flag, + 'ltrim': directives.flag, + 'rtrim': directives.flag} + + comment_pattern = re.compile(r'( |\n|^)\.\. ') + + def run(self): + if not isinstance(self.state, states.SubstitutionDef): + raise self.error( + 'Invalid context: the "%s" directive can only be used within ' + 'a substitution definition.' % self.name) + substitution_definition = self.state_machine.node + if 'trim' in self.options: + substitution_definition.attributes['ltrim'] = 1 + substitution_definition.attributes['rtrim'] = 1 + if 'ltrim' in self.options: + substitution_definition.attributes['ltrim'] = 1 + if 'rtrim' in self.options: + substitution_definition.attributes['rtrim'] = 1 + codes = self.comment_pattern.split(self.arguments[0])[0].split() + element = nodes.Element() + for code in codes: + try: + decoded = directives.unicode_code(code) + except ValueError as error: + raise self.error(u'Invalid character code: %s\n%s' + % (code, ErrorString(error))) + element += nodes.Text(decoded) + return element.children + + +class Class(Directive): + + """ + 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. + """ + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + has_content = True + + def run(self): + try: + class_value = directives.class_option(self.arguments[0]) + except ValueError: + raise self.error( + 'Invalid class attribute value for "%s" directive: "%s".' + % (self.name, self.arguments[0])) + node_list = [] + if self.content: + container = nodes.Element() + self.state.nested_parse(self.content, self.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': self.name}, + self.block_text) + self.state_machine.document.note_pending(pending) + node_list.append(pending) + return node_list + + +class Role(Directive): + + has_content = True + + argument_pattern = re.compile(r'(%s)\s*(\(\s*(%s)\s*\)\s*)?$' + % ((states.Inliner.simplename,) * 2)) + + def run(self): + """Dynamically create and register a custom interpreted text role.""" + if self.content_offset > self.lineno or not self.content: + raise self.error('"%s" directive requires arguments on the first ' + 'line.' % self.name) + args = self.content[0] + match = self.argument_pattern.match(args) + if not match: + raise self.error('"%s" directive arguments not valid role names: ' + '"%s".' % (self.name, args)) + 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, self.state_machine.language, self.lineno, + self.state.reporter) + if base_role is None: + error = self.state.reporter.error( + 'Unknown interpreted text role "%s".' % base_role_name, + nodes.literal_block(self.block_text, self.block_text), + line=self.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).' % (self.name, base_role)) + try: + converted_role = convert_directive_function(base_role) + (arguments, options, content, content_offset) = ( + self.state.parse_directive_block( + self.content[1:], self.content_offset, converted_role, + option_presets={})) + except states.MarkupError as detail: + error = self.state_machine.reporter.error( + 'Error in "%s" directive:\n%s.' % (self.name, detail), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return messages + [error] + if 'class' not in options: + try: + options['class'] = directives.class_option(new_role_name) + except ValueError as detail: + error = self.state_machine.reporter.error( + u'Invalid argument for "%s" directive:\n%s.' + % (self.name, SafeString(detail)), nodes.literal_block( + self.block_text, self.block_text), line=self.lineno) + return messages + [error] + role = roles.CustomRole(new_role_name, base_role, options, content) + roles.register_local_role(new_role_name, role) + return messages + + +class DefaultRole(Directive): + + """Set the default interpreted text role.""" + + optional_arguments = 1 + final_argument_whitespace = False + + def run(self): + if not self.arguments: + if '' in roles._roles: + # restore the "default" default role + del roles._roles[''] + return [] + role_name = self.arguments[0] + role, messages = roles.role(role_name, self.state_machine.language, + self.lineno, self.state.reporter) + if role is None: + error = self.state.reporter.error( + 'Unknown interpreted text role "%s".' % role_name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return messages + [error] + roles._roles[''] = role + return messages + + +class Title(Directive): + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + + def run(self): + self.state_machine.document['title'] = self.arguments[0] + return [] + + +class Date(Directive): + + has_content = True + + def run(self): + if not isinstance(self.state, states.SubstitutionDef): + raise self.error( + 'Invalid context: the "%s" directive can only be used within ' + 'a substitution definition.' % self.name) + format_str = '\n'.join(self.content) or '%Y-%m-%d' + if sys.version_info< (3, 0): + try: + format_str = format_str.encode(locale_encoding or 'utf-8') + except UnicodeEncodeError: + raise self.warning(u'Cannot encode date format string ' + u'with locale encoding "%s".' % locale_encoding) + # @@@ + # Use timestamp from the `SOURCE_DATE_EPOCH`_ environment variable? + # Pro: Docutils-generated documentation + # can easily be part of `reproducible software builds`__ + # + # __ https://reproducible-builds.org/ + # + # Con: Changes the specs, hard to predict behaviour, + # + # See also the discussion about \date \time \year in TeX + # http://tug.org/pipermail/tex-k/2016-May/002704.html + # source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') + # if (source_date_epoch): + # text = time.strftime(format_str, + # time.gmtime(int(source_date_epoch))) + # else: + text = time.strftime(format_str) + if sys.version_info< (3, 0): + # `text` is a byte string that may contain non-ASCII characters: + try: + text = text.decode(locale_encoding or 'utf-8') + except UnicodeDecodeError: + text = text.decode(locale_encoding or 'utf-8', 'replace') + raise self.warning(u'Error decoding "%s"' + u'with locale encoding "%s".' % (text, locale_encoding)) + return [nodes.Text(text)] + + +class TestDirective(Directive): + + """This directive is useful only for testing purposes.""" + + optional_arguments = 1 + final_argument_whitespace = True + option_spec = {'option': directives.unchanged_required} + has_content = True + + def run(self): + if self.content: + text = '\n'.join(self.content) + info = self.state_machine.reporter.info( + 'Directive processed. Type="%s", arguments=%r, options=%r, ' + 'content:' % (self.name, self.arguments, self.options), + nodes.literal_block(text, text), line=self.lineno) + else: + info = self.state_machine.reporter.info( + 'Directive processed. Type="%s", arguments=%r, options=%r, ' + 'content: None' % (self.name, self.arguments, self.options), + line=self.lineno) + return [info] + +# Old-style, functional definition: +# +# 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