aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py')
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py642
1 files changed, 642 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py
new file mode 100644
index 00000000..c16e9430
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py
@@ -0,0 +1,642 @@
+# $Id: misc.py 9492 2023-11-29 16:58:13Z milde $
+# Authors: David Goodger <goodger@python.org>; Dethe Elza
+# Copyright: This module has been placed in the public domain.
+
+"""Miscellaneous directives."""
+
+__docformat__ = 'reStructuredText'
+
+from pathlib import Path
+import re
+import time
+from urllib.request import urlopen
+from urllib.error import URLError
+
+from docutils import io, nodes, statemachine, utils
+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.transforms import misc
+
+
+def adapt_path(path, source='', root_prefix='/'):
+ # Adapt path to files to include or embed.
+ # `root_prefix` is prepended to absolute paths (cf. root_prefix setting),
+ # `source` is the `current_source` of the including directive (which may
+ # be a file included by the main document).
+ if path.startswith('/'):
+ base = Path(root_prefix)
+ path = path[1:]
+ else:
+ base = Path(source).parent
+ # pepend "base" and convert to relative path for shorter system messages
+ return utils.relative_path(None, base/path)
+
+
+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.
+
+ https://docutils.sourceforge.io/docs/ref/rst/directives.html#including-an-external-document-fragment
+ """
+
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec = {'literal': directives.flag,
+ 'code': directives.unchanged,
+ 'encoding': directives.encoding,
+ 'parser': directives.parser_name,
+ '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 = Path(states.__file__).parent / 'include'
+
+ def run(self):
+ """Include a file as part of the content of this reST file.
+
+ Depending on the options, the file (or a clipping) is
+ converted to nodes and returned or inserted into the input stream.
+ """
+ settings = self.state.document.settings
+ if not settings.file_insertion_enabled:
+ raise self.warning('"%s" directive disabled.' % self.name)
+ tab_width = self.options.get('tab-width', settings.tab_width)
+ current_source = self.state.document.current_source
+ path = directives.path(self.arguments[0])
+ if path.startswith('<') and path.endswith('>'):
+ path = '/' + path[1:-1]
+ root_prefix = self.standard_include_path
+ else:
+ root_prefix = settings.root_prefix
+ path = adapt_path(path, current_source, root_prefix)
+ encoding = self.options.get('encoding', settings.input_encoding)
+ error_handler = settings.input_encoding_error_handler
+ try:
+ include_file = io.FileInput(source_path=path,
+ encoding=encoding,
+ error_handler=error_handler)
+ except UnicodeEncodeError:
+ raise self.severe(f'Problems with "{self.name}" directive path:\n'
+ f'Cannot encode input file path "{path}" '
+ '(wrong locale?).')
+ except OSError as error:
+ raise self.severe(f'Problems with "{self.name}" directive '
+ f'path:\n{io.error_string(error)}.')
+ else:
+ settings.record_dependencies.add(path)
+
+ # Get to-be-included content
+ 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(f'Problem with "{self.name}" directive:\n'
+ + io.error_string(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)
+ for i, line in enumerate(include_lines):
+ if len(line) > settings.line_length_limit:
+ raise self.warning('"%s": line %d exceeds the'
+ ' line-length-limit.' % (path, i+1))
+
+ if 'literal' in self.options:
+ # Don't convert tabs to spaces, if `tab_width` is negative.
+ 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()
+
+ # Prevent circular inclusion:
+ clip_options = (startline, endline, before_text, after_text)
+ include_log = self.state.document.include_log
+ # log entries are tuples (<source>, <clip-options>)
+ if not include_log: # new document, initialize with document source
+ include_log.append((utils.relative_path(None, current_source),
+ (None, None, None, None)))
+ if (path, clip_options) in include_log:
+ master_paths = (pth for (pth, opt) in reversed(include_log))
+ inclusion_chain = '\n> '.join((path, *master_paths))
+ raise self.warning('circular inclusion in "%s" directive:\n%s'
+ % (self.name, inclusion_chain))
+
+ if 'parser' in self.options:
+ # parse into a dummy document and return created nodes
+ document = utils.new_document(path, settings)
+ document.include_log = include_log + [(path, clip_options)]
+ parser = self.options['parser']()
+ parser.parse('\n'.join(include_lines), document)
+ # clean up doctree and complete parsing
+ document.transformer.populate_from_components((parser,))
+ document.transformer.apply_transforms()
+ return document.children
+
+ # Include as rST source:
+ #
+ # mark end (cf. parsers.rst.states.Body.comment())
+ include_lines += ['', '.. end of inclusion from "%s"' % path]
+ self.state_machine.insert_input(include_lines, path)
+ # update include-log
+ include_log.append((path, clip_options))
+ 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,
+ 'class': directives.class_option}
+ has_content = True
+
+ def run(self):
+ settings = self.state.document.settings
+ if (not settings.raw_enabled
+ or (not 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', settings.input_encoding)
+ error_handler = 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)
+ path = adapt_path(self.options['file'],
+ self.state.document.current_source,
+ settings.root_prefix)
+ try:
+ raw_file = io.FileInput(source_path=path,
+ encoding=encoding,
+ error_handler=error_handler)
+ except OSError as error:
+ raise self.severe(f'Problems with "{self.name}" directive '
+ f'path:\n{io.error_string(error)}.')
+ else:
+ # TODO: currently, raw input files are recorded as
+ # dependencies even if not used for the chosen output format.
+ settings.record_dependencies.add(path)
+ try:
+ text = raw_file.read()
+ except UnicodeError as error:
+ raise self.severe(f'Problem with "{self.name}" directive:\n'
+ + io.error_string(error))
+ attributes['source'] = path
+ elif 'url' in self.options:
+ source = self.options['url']
+ try:
+ raw_text = urlopen(source).read()
+ except (URLError, OSError) as error:
+ raise self.severe(f'Problems with "{self.name}" directive URL '
+ f'"{self.options["url"]}":\n'
+ f'{io.error_string(error)}.')
+ raw_file = io.StringInput(source=raw_text, source_path=source,
+ encoding=encoding,
+ error_handler=error_handler)
+ try:
+ text = raw_file.read()
+ except UnicodeError as error:
+ raise self.severe(f'Problem with "{self.name}" directive:\n'
+ + io.error_string(error))
+ attributes['source'] = source
+ else:
+ # This will always fail because there is no content.
+ self.assert_has_content()
+ raw_node = nodes.raw('', text, classes=self.options.get('class', []),
+ **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.reporter.error(
+ f'Error in "{self.name}" directive: may contain '
+ 'a single paragraph only.', 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. ``&#x262E;``). 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('Invalid character code: %s\n%s'
+ % (code, io.error_string(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.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.reporter.error(
+ 'Invalid argument for "%s" directive:\n%s.'
+ % (self.name, 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 MetaBody(states.SpecializedBody):
+
+ def field_marker(self, match, context, next_state):
+ """Meta element."""
+ node, blank_finish = self.parsemeta(match)
+ self.parent += node
+ return [], next_state, []
+
+ def parsemeta(self, match):
+ name = self.parse_field_marker(match)
+ name = nodes.unescape(utils.escape2null(name))
+ (indented, indent, line_offset, blank_finish
+ ) = self.state_machine.get_first_known_indented(match.end())
+ node = nodes.meta()
+ node['content'] = nodes.unescape(utils.escape2null(
+ ' '.join(indented)))
+ if not indented:
+ line = self.state_machine.line
+ msg = self.reporter.info(
+ 'No content for meta tag "%s".' % name,
+ nodes.literal_block(line, line))
+ return msg, blank_finish
+ tokens = name.split()
+ try:
+ attname, val = utils.extract_name_value(tokens[0])[0]
+ node[attname.lower()] = val
+ except utils.NameValueError:
+ node['name'] = tokens[0]
+ for token in tokens[1:]:
+ try:
+ attname, val = utils.extract_name_value(token)[0]
+ node[attname.lower()] = val
+ except utils.NameValueError as detail:
+ line = self.state_machine.line
+ msg = self.reporter.error(
+ 'Error parsing meta tag attribute "%s": %s.'
+ % (token, detail), nodes.literal_block(line, line))
+ return msg, blank_finish
+ return node, blank_finish
+
+
+class Meta(Directive):
+
+ has_content = True
+
+ SMkwargs = {'state_classes': (MetaBody,)}
+
+ def run(self):
+ self.assert_has_content()
+ node = nodes.Element()
+ new_line_offset, blank_finish = self.state.nested_list_parse(
+ self.content, self.content_offset, node,
+ initial_state='MetaBody', blank_finish=True,
+ state_machine_kwargs=self.SMkwargs)
+ if (new_line_offset - self.content_offset) != len(self.content):
+ # incomplete parse of block?
+ error = self.reporter.error(
+ 'Invalid meta directive.',
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ node += error
+ # insert at begin of document
+ index = self.state.document.first_child_not_matching_class(
+ (nodes.Titular, nodes.meta)) or 0
+ self.state.document[index:index] = node.children
+ 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'
+ # @@@
+ # 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)
+ 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.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.reporter.info(
+ 'Directive processed. Type="%s", arguments=%r, options=%r, '
+ 'content: None' % (self.name, self.arguments, self.options),
+ line=self.lineno)
+ return [info]