aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives')
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/__init__.py466
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/admonitions.py101
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/body.py305
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/html.py21
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/images.py173
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py642
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/parts.py126
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/references.py29
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/tables.py538
9 files changed, 2401 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/__init__.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/__init__.py
new file mode 100644
index 00000000..ebbdfe3b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/__init__.py
@@ -0,0 +1,466 @@
+# $Id: __init__.py 9426 2023-07-03 12:38:54Z milde $
+# Author: David Goodger <goodger@python.org>
+# Copyright: This module has been placed in the public domain.
+
+"""
+This package contains directive implementation modules.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import re
+import codecs
+from importlib import import_module
+
+from docutils import nodes, parsers
+from docutils.utils import split_escaped_whitespace, escape2null
+from docutils.parsers.rst.languages import en as _fallback_language_module
+
+
+_directive_registry = {
+ 'attention': ('admonitions', 'Attention'),
+ 'caution': ('admonitions', 'Caution'),
+ 'code': ('body', 'CodeBlock'),
+ 'danger': ('admonitions', 'Danger'),
+ 'error': ('admonitions', 'Error'),
+ 'important': ('admonitions', 'Important'),
+ 'note': ('admonitions', 'Note'),
+ 'tip': ('admonitions', 'Tip'),
+ 'hint': ('admonitions', 'Hint'),
+ 'warning': ('admonitions', 'Warning'),
+ 'admonition': ('admonitions', 'Admonition'),
+ 'sidebar': ('body', 'Sidebar'),
+ 'topic': ('body', 'Topic'),
+ 'line-block': ('body', 'LineBlock'),
+ 'parsed-literal': ('body', 'ParsedLiteral'),
+ 'math': ('body', 'MathBlock'),
+ 'rubric': ('body', 'Rubric'),
+ 'epigraph': ('body', 'Epigraph'),
+ 'highlights': ('body', 'Highlights'),
+ 'pull-quote': ('body', 'PullQuote'),
+ 'compound': ('body', 'Compound'),
+ 'container': ('body', 'Container'),
+ # 'questions': ('body', 'question_list'),
+ 'table': ('tables', 'RSTTable'),
+ 'csv-table': ('tables', 'CSVTable'),
+ 'list-table': ('tables', 'ListTable'),
+ 'image': ('images', 'Image'),
+ 'figure': ('images', 'Figure'),
+ 'contents': ('parts', 'Contents'),
+ 'sectnum': ('parts', 'Sectnum'),
+ 'header': ('parts', 'Header'),
+ 'footer': ('parts', 'Footer'),
+ # 'footnotes': ('parts', 'footnotes'),
+ # 'citations': ('parts', 'citations'),
+ 'target-notes': ('references', 'TargetNotes'),
+ 'meta': ('misc', 'Meta'),
+ # 'imagemap': ('html', 'imagemap'),
+ 'raw': ('misc', 'Raw'),
+ 'include': ('misc', 'Include'),
+ 'replace': ('misc', 'Replace'),
+ 'unicode': ('misc', 'Unicode'),
+ 'class': ('misc', 'Class'),
+ 'role': ('misc', 'Role'),
+ 'default-role': ('misc', 'DefaultRole'),
+ 'title': ('misc', 'Title'),
+ 'date': ('misc', 'Date'),
+ 'restructuredtext-test-directive': ('misc', 'TestDirective'),
+ }
+"""Mapping of directive name to (module name, class name). The
+directive name is canonical & must be lowercase. Language-dependent
+names are defined in the ``language`` subpackage."""
+
+_directives = {}
+"""Cache of imported directives."""
+
+
+def directive(directive_name, language_module, document):
+ """
+ Locate and return a directive function from its language-dependent name.
+ If not found in the current language, check English. Return None if the
+ named directive cannot be found.
+ """
+ normname = directive_name.lower()
+ messages = []
+ msg_text = []
+ if normname in _directives:
+ return _directives[normname], messages
+ canonicalname = None
+ try:
+ canonicalname = language_module.directives[normname]
+ except AttributeError as error:
+ msg_text.append('Problem retrieving directive entry from language '
+ 'module %r: %s.' % (language_module, error))
+ except KeyError:
+ msg_text.append('No directive entry for "%s" in module "%s".'
+ % (directive_name, language_module.__name__))
+ if not canonicalname:
+ try:
+ canonicalname = _fallback_language_module.directives[normname]
+ msg_text.append('Using English fallback for directive "%s".'
+ % directive_name)
+ except KeyError:
+ msg_text.append('Trying "%s" as canonical directive name.'
+ % directive_name)
+ # The canonical name should be an English name, but just in case:
+ canonicalname = normname
+ if msg_text:
+ message = document.reporter.info(
+ '\n'.join(msg_text), line=document.current_line)
+ messages.append(message)
+ try:
+ modulename, classname = _directive_registry[canonicalname]
+ except KeyError:
+ # Error handling done by caller.
+ return None, messages
+ try:
+ module = import_module('docutils.parsers.rst.directives.'+modulename)
+ except ImportError as detail:
+ messages.append(document.reporter.error(
+ 'Error importing directive module "%s" (directive "%s"):\n%s'
+ % (modulename, directive_name, detail),
+ line=document.current_line))
+ return None, messages
+ try:
+ directive = getattr(module, classname)
+ _directives[normname] = directive
+ except AttributeError:
+ messages.append(document.reporter.error(
+ 'No directive class "%s" in module "%s" (directive "%s").'
+ % (classname, modulename, directive_name),
+ line=document.current_line))
+ return None, messages
+ return directive, messages
+
+
+def register_directive(name, directive):
+ """
+ Register a nonstandard application-defined directive function.
+ Language lookups are not needed for such functions.
+ """
+ _directives[name] = directive
+
+
+# conversion functions for `Directive.option_spec`
+# ------------------------------------------------
+#
+# see also `parsers.rst.Directive` in ../__init__.py.
+
+
+def flag(argument):
+ """
+ Check for a valid flag option (no argument) and return ``None``.
+ (Directive option conversion function.)
+
+ Raise ``ValueError`` if an argument is found.
+ """
+ if argument and argument.strip():
+ raise ValueError('no argument is allowed; "%s" supplied' % argument)
+ else:
+ return None
+
+
+def unchanged_required(argument):
+ """
+ Return the argument text, unchanged.
+ (Directive option conversion function.)
+
+ Raise ``ValueError`` if no argument is found.
+ """
+ if argument is None:
+ raise ValueError('argument required but none supplied')
+ else:
+ return argument # unchanged!
+
+
+def unchanged(argument):
+ """
+ Return the argument text, unchanged.
+ (Directive option conversion function.)
+
+ No argument implies empty string ("").
+ """
+ if argument is None:
+ return ''
+ else:
+ return argument # unchanged!
+
+
+def path(argument):
+ """
+ Return the path argument unwrapped (with newlines removed).
+ (Directive option conversion function.)
+
+ Raise ``ValueError`` if no argument is found.
+ """
+ if argument is None:
+ raise ValueError('argument required but none supplied')
+ else:
+ return ''.join(s.strip() for s in argument.splitlines())
+
+
+def uri(argument):
+ """
+ Return the URI argument with unescaped whitespace removed.
+ (Directive option conversion function.)
+
+ Raise ``ValueError`` if no argument is found.
+ """
+ if argument is None:
+ raise ValueError('argument required but none supplied')
+ else:
+ parts = split_escaped_whitespace(escape2null(argument))
+ return ' '.join(''.join(nodes.unescape(part).split())
+ for part in parts)
+
+
+def nonnegative_int(argument):
+ """
+ Check for a nonnegative integer argument; raise ``ValueError`` if not.
+ (Directive option conversion function.)
+ """
+ value = int(argument)
+ if value < 0:
+ raise ValueError('negative value; must be positive or zero')
+ return value
+
+
+def percentage(argument):
+ """
+ Check for an integer percentage value with optional percent sign.
+ (Directive option conversion function.)
+ """
+ try:
+ argument = argument.rstrip(' %')
+ except AttributeError:
+ pass
+ return nonnegative_int(argument)
+
+
+length_units = ['em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc']
+
+
+def get_measure(argument, units):
+ """
+ Check for a positive argument of one of the units and return a
+ normalized string of the form "<value><unit>" (without space in
+ between).
+ (Directive option conversion function.)
+
+ To be called from directive option conversion functions.
+ """
+ match = re.match(r'^([0-9.]+) *(%s)$' % '|'.join(units), argument)
+ try:
+ float(match.group(1))
+ except (AttributeError, ValueError):
+ raise ValueError(
+ 'not a positive measure of one of the following units:\n%s'
+ % ' '.join('"%s"' % i for i in units))
+ return match.group(1) + match.group(2)
+
+
+def length_or_unitless(argument):
+ return get_measure(argument, length_units + [''])
+
+
+def length_or_percentage_or_unitless(argument, default=''):
+ """
+ Return normalized string of a length or percentage unit.
+ (Directive option conversion function.)
+
+ Add <default> if there is no unit. Raise ValueError if the argument is not
+ a positive measure of one of the valid CSS units (or without unit).
+
+ >>> length_or_percentage_or_unitless('3 pt')
+ '3pt'
+ >>> length_or_percentage_or_unitless('3%', 'em')
+ '3%'
+ >>> length_or_percentage_or_unitless('3')
+ '3'
+ >>> length_or_percentage_or_unitless('3', 'px')
+ '3px'
+ """
+ try:
+ return get_measure(argument, length_units + ['%'])
+ except ValueError:
+ try:
+ return get_measure(argument, ['']) + default
+ except ValueError:
+ # raise ValueError with list of valid units:
+ return get_measure(argument, length_units + ['%'])
+
+
+def class_option(argument):
+ """
+ Convert the argument into a list of ID-compatible strings and return it.
+ (Directive option conversion function.)
+
+ Raise ``ValueError`` if no argument is found.
+ """
+ if argument is None:
+ raise ValueError('argument required but none supplied')
+ names = argument.split()
+ class_names = []
+ for name in names:
+ class_name = nodes.make_id(name)
+ if not class_name:
+ raise ValueError('cannot make "%s" into a class name' % name)
+ class_names.append(class_name)
+ return class_names
+
+
+unicode_pattern = re.compile(
+ r'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re.IGNORECASE)
+
+
+def unicode_code(code):
+ r"""
+ Convert a Unicode character code to a Unicode character.
+ (Directive option conversion function.)
+
+ 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;``). Other text remains as-is.
+
+ Raise ValueError for illegal Unicode code values.
+ """
+ try:
+ if code.isdigit(): # decimal number
+ return chr(int(code))
+ else:
+ match = unicode_pattern.match(code)
+ if match: # hex number
+ value = match.group(1) or match.group(2)
+ return chr(int(value, 16))
+ else: # other text
+ return code
+ except OverflowError as detail:
+ raise ValueError('code too large (%s)' % detail)
+
+
+def single_char_or_unicode(argument):
+ """
+ A single character is returned as-is. Unicode character codes are
+ converted as in `unicode_code`. (Directive option conversion function.)
+ """
+ char = unicode_code(argument)
+ if len(char) > 1:
+ raise ValueError('%r invalid; must be a single character or '
+ 'a Unicode code' % char)
+ return char
+
+
+def single_char_or_whitespace_or_unicode(argument):
+ """
+ As with `single_char_or_unicode`, but "tab" and "space" are also supported.
+ (Directive option conversion function.)
+ """
+ if argument == 'tab':
+ char = '\t'
+ elif argument == 'space':
+ char = ' '
+ else:
+ char = single_char_or_unicode(argument)
+ return char
+
+
+def positive_int(argument):
+ """
+ Converts the argument into an integer. Raises ValueError for negative,
+ zero, or non-integer values. (Directive option conversion function.)
+ """
+ value = int(argument)
+ if value < 1:
+ raise ValueError('negative or zero value; must be positive')
+ return value
+
+
+def positive_int_list(argument):
+ """
+ Converts a space- or comma-separated list of values into a Python list
+ of integers.
+ (Directive option conversion function.)
+
+ Raises ValueError for non-positive-integer values.
+ """
+ if ',' in argument:
+ entries = argument.split(',')
+ else:
+ entries = argument.split()
+ return [positive_int(entry) for entry in entries]
+
+
+def encoding(argument):
+ """
+ Verifies the encoding argument by lookup.
+ (Directive option conversion function.)
+
+ Raises ValueError for unknown encodings.
+ """
+ try:
+ codecs.lookup(argument)
+ except LookupError:
+ raise ValueError('unknown encoding: "%s"' % argument)
+ return argument
+
+
+def choice(argument, values):
+ """
+ Directive option utility function, supplied to enable options whose
+ argument must be a member of a finite set of possible values (must be
+ lower case). A custom conversion function must be written to use it. For
+ example::
+
+ from docutils.parsers.rst import directives
+
+ def yesno(argument):
+ return directives.choice(argument, ('yes', 'no'))
+
+ Raise ``ValueError`` if no argument is found or if the argument's value is
+ not valid (not an entry in the supplied list).
+ """
+ try:
+ value = argument.lower().strip()
+ except AttributeError:
+ raise ValueError('must supply an argument; choose from %s'
+ % format_values(values))
+ if value in values:
+ return value
+ else:
+ raise ValueError('"%s" unknown; choose from %s'
+ % (argument, format_values(values)))
+
+
+def format_values(values):
+ return '%s, or "%s"' % (', '.join('"%s"' % s for s in values[:-1]),
+ values[-1])
+
+
+def value_or(values, other):
+ """
+ Directive option conversion function.
+
+ The argument can be any of `values` or `argument_type`.
+ """
+ def auto_or_other(argument):
+ if argument in values:
+ return argument
+ else:
+ return other(argument)
+ return auto_or_other
+
+
+def parser_name(argument):
+ """
+ Return a docutils parser whose name matches the argument.
+ (Directive option conversion function.)
+
+ Return `None`, if the argument evaluates to `False`.
+ Raise `ValueError` if importing the parser module fails.
+ """
+ if not argument:
+ return None
+ try:
+ return parsers.get_parser_class(argument)
+ except ImportError as err:
+ raise ValueError(str(err))
diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/admonitions.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/admonitions.py
new file mode 100644
index 00000000..1990099e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/admonitions.py
@@ -0,0 +1,101 @@
+# $Id: admonitions.py 9475 2023-11-13 22:30:00Z milde $
+# Author: David Goodger <goodger@python.org>
+# Copyright: This module has been placed in the public domain.
+
+"""
+Admonition directives.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+from docutils.parsers.rst import Directive
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.roles import set_classes
+from docutils import nodes
+
+
+class BaseAdmonition(Directive):
+
+ final_argument_whitespace = True
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged}
+ has_content = True
+
+ node_class = None
+ """Subclasses must set this to the appropriate admonition node class."""
+
+ def run(self):
+ set_classes(self.options)
+ self.assert_has_content()
+ text = '\n'.join(self.content)
+ admonition_node = self.node_class(text, **self.options)
+ self.add_name(admonition_node)
+ admonition_node.source, admonition_node.line = \
+ self.state_machine.get_source_and_line(self.lineno)
+ if self.node_class is nodes.admonition:
+ title_text = self.arguments[0]
+ textnodes, messages = self.state.inline_text(title_text,
+ self.lineno)
+ title = nodes.title(title_text, '', *textnodes)
+ title.source, title.line = (
+ self.state_machine.get_source_and_line(self.lineno))
+ admonition_node += title
+ admonition_node += messages
+ if 'classes' not in self.options:
+ admonition_node['classes'] += ['admonition-'
+ + nodes.make_id(title_text)]
+ self.state.nested_parse(self.content, self.content_offset,
+ admonition_node)
+ return [admonition_node]
+
+
+class Admonition(BaseAdmonition):
+
+ required_arguments = 1
+ node_class = nodes.admonition
+
+
+class Attention(BaseAdmonition):
+
+ node_class = nodes.attention
+
+
+class Caution(BaseAdmonition):
+
+ node_class = nodes.caution
+
+
+class Danger(BaseAdmonition):
+
+ node_class = nodes.danger
+
+
+class Error(BaseAdmonition):
+
+ node_class = nodes.error
+
+
+class Hint(BaseAdmonition):
+
+ node_class = nodes.hint
+
+
+class Important(BaseAdmonition):
+
+ node_class = nodes.important
+
+
+class Note(BaseAdmonition):
+
+ node_class = nodes.note
+
+
+class Tip(BaseAdmonition):
+
+ node_class = nodes.tip
+
+
+class Warning(BaseAdmonition):
+
+ node_class = nodes.warning
diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/body.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/body.py
new file mode 100644
index 00000000..5cb90416
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/body.py
@@ -0,0 +1,305 @@
+# $Id: body.py 9500 2023-12-14 22:38:49Z milde $
+# Author: David Goodger <goodger@python.org>
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for additional body elements.
+
+See `docutils.parsers.rst.directives` for API details.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+from docutils import nodes
+from docutils.parsers.rst import Directive
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.roles import set_classes
+from docutils.utils.code_analyzer import Lexer, LexerError, NumberLines
+
+
+class BasePseudoSection(Directive):
+
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged}
+ has_content = True
+
+ node_class = None
+ """Node class to be used (must be set in subclasses)."""
+
+ def run(self):
+ if not (self.state_machine.match_titles
+ or isinstance(self.state_machine.node, nodes.sidebar)):
+ raise self.error('The "%s" directive may not be used within '
+ 'topics or body elements.' % self.name)
+ self.assert_has_content()
+ if self.arguments: # title (in sidebars optional)
+ title_text = self.arguments[0]
+ textnodes, messages = self.state.inline_text(
+ title_text, self.lineno)
+ titles = [nodes.title(title_text, '', *textnodes)]
+ # Sidebar uses this code.
+ if 'subtitle' in self.options:
+ textnodes, more_messages = self.state.inline_text(
+ self.options['subtitle'], self.lineno)
+ titles.append(nodes.subtitle(self.options['subtitle'], '',
+ *textnodes))
+ messages.extend(more_messages)
+ else:
+ titles = []
+ messages = []
+ text = '\n'.join(self.content)
+ node = self.node_class(text, *(titles + messages))
+ node['classes'] += self.options.get('class', [])
+ self.add_name(node)
+ if text:
+ self.state.nested_parse(self.content, self.content_offset, node)
+ return [node]
+
+
+class Topic(BasePseudoSection):
+
+ node_class = nodes.topic
+
+
+class Sidebar(BasePseudoSection):
+
+ node_class = nodes.sidebar
+
+ required_arguments = 0
+ optional_arguments = 1
+ option_spec = BasePseudoSection.option_spec.copy()
+ option_spec['subtitle'] = directives.unchanged_required
+
+ def run(self):
+ if isinstance(self.state_machine.node, nodes.sidebar):
+ raise self.error('The "%s" directive may not be used within a '
+ 'sidebar element.' % self.name)
+ if 'subtitle' in self.options and not self.arguments:
+ raise self.error('The "subtitle" option may not be used '
+ 'without a title.')
+
+ return BasePseudoSection.run(self)
+
+
+class LineBlock(Directive):
+
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged}
+ has_content = True
+
+ def run(self):
+ self.assert_has_content()
+ block = nodes.line_block(classes=self.options.get('class', []))
+ self.add_name(block)
+ node_list = [block]
+ for line_text in self.content:
+ text_nodes, messages = self.state.inline_text(
+ line_text.strip(), self.lineno + self.content_offset)
+ line = nodes.line(line_text, '', *text_nodes)
+ if line_text.strip():
+ line.indent = len(line_text) - len(line_text.lstrip())
+ block += line
+ node_list.extend(messages)
+ self.content_offset += 1
+ self.state.nest_line_block_lines(block)
+ return node_list
+
+
+class ParsedLiteral(Directive):
+
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged}
+ has_content = True
+
+ def run(self):
+ set_classes(self.options)
+ self.assert_has_content()
+ text = '\n'.join(self.content)
+ text_nodes, messages = self.state.inline_text(text, self.lineno)
+ node = nodes.literal_block(text, '', *text_nodes, **self.options)
+ node.line = self.content_offset + 1
+ self.add_name(node)
+ return [node] + messages
+
+
+class CodeBlock(Directive):
+ """Parse and mark up content of a code block.
+
+ Configuration setting: syntax_highlight
+ Highlight Code content with Pygments?
+ Possible values: ('long', 'short', 'none')
+
+ """
+ optional_arguments = 1
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged,
+ 'number-lines': directives.unchanged # integer or None
+ }
+ has_content = True
+
+ def run(self):
+ self.assert_has_content()
+ if self.arguments:
+ language = self.arguments[0]
+ else:
+ language = ''
+ set_classes(self.options)
+ classes = ['code']
+ if language:
+ classes.append(language)
+ if 'classes' in self.options:
+ classes.extend(self.options['classes'])
+
+ # set up lexical analyzer
+ try:
+ tokens = Lexer('\n'.join(self.content), language,
+ self.state.document.settings.syntax_highlight)
+ except LexerError as error:
+ if self.state.document.settings.report_level > 2:
+ # don't report warnings -> insert without syntax highlight
+ tokens = Lexer('\n'.join(self.content), language, 'none')
+ else:
+ raise self.warning(error)
+
+ if 'number-lines' in self.options:
+ # optional argument `startline`, defaults to 1
+ try:
+ startline = int(self.options['number-lines'] or 1)
+ except ValueError:
+ raise self.error(':number-lines: with non-integer start value')
+ endline = startline + len(self.content)
+ # add linenumber filter:
+ tokens = NumberLines(tokens, startline, endline)
+
+ node = nodes.literal_block('\n'.join(self.content), classes=classes)
+ self.add_name(node)
+ # if called from "include", set the source
+ if 'source' in self.options:
+ node.attributes['source'] = self.options['source']
+ # analyze content and add nodes for every token
+ for classes, value in tokens:
+ if classes:
+ node += nodes.inline(value, value, classes=classes)
+ else:
+ # insert as Text to decrease the verbosity of the output
+ node += nodes.Text(value)
+
+ return [node]
+
+
+class MathBlock(Directive):
+
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged,
+ # TODO: Add Sphinx' ``mathbase.py`` option 'nowrap'?
+ # 'nowrap': directives.flag,
+ }
+ has_content = True
+
+ def run(self):
+ set_classes(self.options)
+ self.assert_has_content()
+ # join lines, separate blocks
+ content = '\n'.join(self.content).split('\n\n')
+ _nodes = []
+ for block in content:
+ if not block:
+ continue
+ node = nodes.math_block(self.block_text, block, **self.options)
+ (node.source,
+ node.line) = self.state_machine.get_source_and_line(self.lineno)
+ self.add_name(node)
+ _nodes.append(node)
+ return _nodes
+
+
+class Rubric(Directive):
+
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged}
+
+ def run(self):
+ set_classes(self.options)
+ rubric_text = self.arguments[0]
+ textnodes, messages = self.state.inline_text(rubric_text, self.lineno)
+ rubric = nodes.rubric(rubric_text, '', *textnodes, **self.options)
+ self.add_name(rubric)
+ return [rubric] + messages
+
+
+class BlockQuote(Directive):
+
+ has_content = True
+ classes = []
+
+ def run(self):
+ self.assert_has_content()
+ elements = self.state.block_quote(self.content, self.content_offset)
+ for element in elements:
+ if isinstance(element, nodes.block_quote):
+ element['classes'] += self.classes
+ return elements
+
+
+class Epigraph(BlockQuote):
+
+ classes = ['epigraph']
+
+
+class Highlights(BlockQuote):
+
+ classes = ['highlights']
+
+
+class PullQuote(BlockQuote):
+
+ classes = ['pull-quote']
+
+
+class Compound(Directive):
+
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged}
+ has_content = True
+
+ def run(self):
+ self.assert_has_content()
+ text = '\n'.join(self.content)
+ node = nodes.compound(text)
+ node['classes'] += self.options.get('class', [])
+ self.add_name(node)
+ self.state.nested_parse(self.content, self.content_offset, node)
+ return [node]
+
+
+class Container(Directive):
+
+ optional_arguments = 1
+ final_argument_whitespace = True
+ option_spec = {'name': directives.unchanged}
+ has_content = True
+
+ def run(self):
+ self.assert_has_content()
+ text = '\n'.join(self.content)
+ try:
+ if self.arguments:
+ classes = directives.class_option(self.arguments[0])
+ else:
+ classes = []
+ except ValueError:
+ raise self.error(
+ 'Invalid class attribute value for "%s" directive: "%s".'
+ % (self.name, self.arguments[0]))
+ node = nodes.container(text)
+ node['classes'].extend(classes)
+ self.add_name(node)
+ self.state.nested_parse(self.content, self.content_offset, node)
+ return [node]
diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/html.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/html.py
new file mode 100644
index 00000000..c22a26f2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/html.py
@@ -0,0 +1,21 @@
+# $Id: html.py 9062 2022-05-30 21:09:09Z milde $
+# Author: David Goodger <goodger@python.org>
+# Copyright: This module has been placed in the public domain.
+
+"""
+Dummy module for backwards compatibility.
+
+This module is provisional: it will be removed in Docutils 2.0.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import warnings
+
+from docutils.parsers.rst.directives.misc import MetaBody, Meta # noqa: F401
+
+warnings.warn('The `docutils.parsers.rst.directive.html` module'
+ ' will be removed in Docutils 2.0.'
+ ' Since Docutils 0.18, the "Meta" node is defined in'
+ ' `docutils.parsers.rst.directives.misc`.',
+ DeprecationWarning, stacklevel=2)
diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/images.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/images.py
new file mode 100644
index 00000000..bcde3a39
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/images.py
@@ -0,0 +1,173 @@
+# $Id: images.py 9500 2023-12-14 22:38:49Z milde $
+# Author: David Goodger <goodger@python.org>
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for figures and simple images.
+"""
+
+__docformat__ = 'reStructuredText'
+
+from urllib.request import url2pathname
+
+try: # check for the Python Imaging Library
+ import PIL.Image
+except ImportError:
+ try: # sometimes PIL modules are put in PYTHONPATH's root
+ import Image
+ class PIL: pass # noqa:E701 dummy wrapper
+ PIL.Image = Image
+ except ImportError:
+ PIL = None
+
+from docutils import nodes
+from docutils.nodes import fully_normalize_name, whitespace_normalize_name
+from docutils.parsers.rst import Directive
+from docutils.parsers.rst import directives, states
+from docutils.parsers.rst.roles import set_classes
+
+
+class Image(Directive):
+
+ align_h_values = ('left', 'center', 'right')
+ align_v_values = ('top', 'middle', 'bottom')
+ align_values = align_v_values + align_h_values
+ loading_values = ('embed', 'link', 'lazy')
+
+ def align(argument):
+ # This is not callable as `self.align()`. We cannot make it a
+ # staticmethod because we're saving an unbound method in
+ # option_spec below.
+ return directives.choice(argument, Image.align_values)
+
+ def loading(argument):
+ # This is not callable as `self.loading()` (see above).
+ return directives.choice(argument, Image.loading_values)
+
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec = {'alt': directives.unchanged,
+ 'height': directives.length_or_unitless,
+ 'width': directives.length_or_percentage_or_unitless,
+ 'scale': directives.percentage,
+ 'align': align,
+ 'target': directives.unchanged_required,
+ 'loading': loading,
+ 'class': directives.class_option,
+ 'name': directives.unchanged}
+
+ def run(self):
+ if 'align' in self.options:
+ if isinstance(self.state, states.SubstitutionDef):
+ # Check for align_v_values.
+ if self.options['align'] not in self.align_v_values:
+ raise self.error(
+ 'Error in "%s" directive: "%s" is not a valid value '
+ 'for the "align" option within a substitution '
+ 'definition. Valid values for "align" are: "%s".'
+ % (self.name, self.options['align'],
+ '", "'.join(self.align_v_values)))
+ elif self.options['align'] not in self.align_h_values:
+ raise self.error(
+ 'Error in "%s" directive: "%s" is not a valid value for '
+ 'the "align" option. Valid values for "align" are: "%s".'
+ % (self.name, self.options['align'],
+ '", "'.join(self.align_h_values)))
+ messages = []
+ reference = directives.uri(self.arguments[0])
+ self.options['uri'] = reference
+ reference_node = None
+ if 'target' in self.options:
+ block = states.escape2null(
+ self.options['target']).splitlines()
+ block = [line for line in block]
+ target_type, data = self.state.parse_target(
+ block, self.block_text, self.lineno)
+ if target_type == 'refuri':
+ reference_node = nodes.reference(refuri=data)
+ elif target_type == 'refname':
+ reference_node = nodes.reference(
+ refname=fully_normalize_name(data),
+ name=whitespace_normalize_name(data))
+ reference_node.indirect_reference_name = data
+ self.state.document.note_refname(reference_node)
+ else: # malformed target
+ messages.append(data) # data is a system message
+ del self.options['target']
+ set_classes(self.options)
+ image_node = nodes.image(self.block_text, **self.options)
+ (image_node.source,
+ image_node.line) = self.state_machine.get_source_and_line(self.lineno)
+ self.add_name(image_node)
+ if reference_node:
+ reference_node += image_node
+ return messages + [reference_node]
+ else:
+ return messages + [image_node]
+
+
+class Figure(Image):
+
+ def align(argument):
+ return directives.choice(argument, Figure.align_h_values)
+
+ def figwidth_value(argument):
+ if argument.lower() == 'image':
+ return 'image'
+ else:
+ return directives.length_or_percentage_or_unitless(argument, 'px')
+
+ option_spec = Image.option_spec.copy()
+ option_spec['figwidth'] = figwidth_value
+ option_spec['figclass'] = directives.class_option
+ option_spec['align'] = align
+ has_content = True
+
+ def run(self):
+ figwidth = self.options.pop('figwidth', None)
+ figclasses = self.options.pop('figclass', None)
+ align = self.options.pop('align', None)
+ (image_node,) = Image.run(self)
+ if isinstance(image_node, nodes.system_message):
+ return [image_node]
+ figure_node = nodes.figure('', image_node)
+ (figure_node.source, figure_node.line
+ ) = self.state_machine.get_source_and_line(self.lineno)
+ if figwidth == 'image':
+ if PIL and self.state.document.settings.file_insertion_enabled:
+ imagepath = url2pathname(image_node['uri'])
+ try:
+ with PIL.Image.open(imagepath) as img:
+ figure_node['width'] = '%dpx' % img.size[0]
+ except (OSError, UnicodeEncodeError):
+ pass # TODO: warn/info?
+ else:
+ self.state.document.settings.record_dependencies.add(
+ imagepath.replace('\\', '/'))
+ elif figwidth is not None:
+ figure_node['width'] = figwidth
+ if figclasses:
+ figure_node['classes'] += figclasses
+ if align:
+ figure_node['align'] = align
+ if self.content:
+ node = nodes.Element() # anonymous container for parsing
+ self.state.nested_parse(self.content, self.content_offset, node)
+ first_node = node[0]
+ if isinstance(first_node, nodes.paragraph):
+ caption = nodes.caption(first_node.rawsource, '',
+ *first_node.children)
+ caption.source = first_node.source
+ caption.line = first_node.line
+ figure_node += caption
+ elif not (isinstance(first_node, nodes.comment)
+ and len(first_node) == 0):
+ error = self.reporter.error(
+ 'Figure caption must be a paragraph or empty comment.',
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ return [figure_node, error]
+ if len(node) > 1:
+ figure_node += nodes.legend('', *node[1:])
+ return [figure_node]
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]
diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/parts.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/parts.py
new file mode 100644
index 00000000..adb01d03
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/parts.py
@@ -0,0 +1,126 @@
+# $Id: parts.py 8993 2022-01-29 13:20:04Z milde $
+# Authors: David Goodger <goodger@python.org>; Dmitry Jemerov
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for document parts.
+"""
+
+__docformat__ = 'reStructuredText'
+
+from docutils import nodes, languages
+from docutils.transforms import parts
+from docutils.parsers.rst import Directive
+from docutils.parsers.rst import directives
+
+
+class Contents(Directive):
+
+ """
+ Table of contents.
+
+ The table of contents is generated in two passes: initial parse and
+ transform. During the initial parse, a 'pending' element is generated
+ which acts as a placeholder, storing the TOC title and any options
+ internally. At a later stage in the processing, the 'pending' element is
+ replaced by a 'topic' element, a title and the table of contents proper.
+ """
+
+ backlinks_values = ('top', 'entry', 'none')
+
+ def backlinks(arg):
+ value = directives.choice(arg, Contents.backlinks_values)
+ if value == 'none':
+ return None
+ else:
+ return value
+
+ optional_arguments = 1
+ final_argument_whitespace = True
+ option_spec = {'depth': directives.nonnegative_int,
+ 'local': directives.flag,
+ 'backlinks': backlinks,
+ 'class': directives.class_option}
+
+ def run(self):
+ if not (self.state_machine.match_titles
+ or isinstance(self.state_machine.node, nodes.sidebar)):
+ raise self.error('The "%s" directive may not be used within '
+ 'topics or body elements.' % self.name)
+ document = self.state_machine.document
+ language = languages.get_language(document.settings.language_code,
+ document.reporter)
+ if self.arguments:
+ title_text = self.arguments[0]
+ text_nodes, messages = self.state.inline_text(title_text,
+ self.lineno)
+ title = nodes.title(title_text, '', *text_nodes)
+ else:
+ messages = []
+ if 'local' in self.options:
+ title = None
+ else:
+ title = nodes.title('', language.labels['contents'])
+ topic = nodes.topic(classes=['contents'])
+ topic['classes'] += self.options.get('class', [])
+ # the latex2e writer needs source and line for a warning:
+ topic.source, topic.line = self.state_machine.get_source_and_line()
+ topic.line -= 1
+ if 'local' in self.options:
+ topic['classes'].append('local')
+ if title:
+ name = title.astext()
+ topic += title
+ else:
+ name = language.labels['contents']
+ name = nodes.fully_normalize_name(name)
+ if not document.has_name(name):
+ topic['names'].append(name)
+ document.note_implicit_target(topic)
+ pending = nodes.pending(parts.Contents, rawsource=self.block_text)
+ pending.details.update(self.options)
+ document.note_pending(pending)
+ topic += pending
+ return [topic] + messages
+
+
+class Sectnum(Directive):
+
+ """Automatic section numbering."""
+
+ option_spec = {'depth': int,
+ 'start': int,
+ 'prefix': directives.unchanged_required,
+ 'suffix': directives.unchanged_required}
+
+ def run(self):
+ pending = nodes.pending(parts.SectNum)
+ pending.details.update(self.options)
+ self.state_machine.document.note_pending(pending)
+ return [pending]
+
+
+class Header(Directive):
+
+ """Contents of document header."""
+
+ has_content = True
+
+ def run(self):
+ self.assert_has_content()
+ header = self.state_machine.document.get_decoration().get_header()
+ self.state.nested_parse(self.content, self.content_offset, header)
+ return []
+
+
+class Footer(Directive):
+
+ """Contents of document footer."""
+
+ has_content = True
+
+ def run(self):
+ self.assert_has_content()
+ footer = self.state_machine.document.get_decoration().get_footer()
+ self.state.nested_parse(self.content, self.content_offset, footer)
+ return []
diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/references.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/references.py
new file mode 100644
index 00000000..96921f9d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/references.py
@@ -0,0 +1,29 @@
+# $Id: references.py 7062 2011-06-30 22:14:29Z milde $
+# Authors: David Goodger <goodger@python.org>; Dmitry Jemerov
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for references and targets.
+"""
+
+__docformat__ = 'reStructuredText'
+
+from docutils import nodes
+from docutils.transforms import references
+from docutils.parsers.rst import Directive
+from docutils.parsers.rst import directives
+
+
+class TargetNotes(Directive):
+
+ """Target footnote generation."""
+
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged}
+
+ def run(self):
+ pending = nodes.pending(references.TargetNotes)
+ self.add_name(pending)
+ pending.details.update(self.options)
+ self.state_machine.document.note_pending(pending)
+ return [pending]
diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/tables.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/tables.py
new file mode 100644
index 00000000..2cc266ff
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/tables.py
@@ -0,0 +1,538 @@
+# $Id: tables.py 9492 2023-11-29 16:58:13Z milde $
+# Authors: David Goodger <goodger@python.org>; David Priest
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for table elements.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import csv
+from urllib.request import urlopen
+from urllib.error import URLError
+import warnings
+
+from docutils import nodes, statemachine
+from docutils.io import FileInput, StringInput
+from docutils.parsers.rst import Directive
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.directives.misc import adapt_path
+from docutils.utils import SystemMessagePropagation
+
+
+def align(argument):
+ return directives.choice(argument, ('left', 'center', 'right'))
+
+
+class Table(Directive):
+
+ """
+ Generic table base class.
+ """
+
+ optional_arguments = 1
+ final_argument_whitespace = True
+ option_spec = {'class': directives.class_option,
+ 'name': directives.unchanged,
+ 'align': align,
+ 'width': directives.length_or_percentage_or_unitless,
+ 'widths': directives.value_or(('auto', 'grid'),
+ directives.positive_int_list)}
+ has_content = True
+
+ def make_title(self):
+ if self.arguments:
+ title_text = self.arguments[0]
+ text_nodes, messages = self.state.inline_text(title_text,
+ self.lineno)
+ title = nodes.title(title_text, '', *text_nodes)
+ (title.source,
+ title.line) = self.state_machine.get_source_and_line(self.lineno)
+ else:
+ title = None
+ messages = []
+ return title, messages
+
+ def check_table_dimensions(self, rows, header_rows, stub_columns):
+ if len(rows) < header_rows:
+ error = self.reporter.error('%s header row(s) specified but '
+ 'only %s row(s) of data supplied ("%s" directive).'
+ % (header_rows, len(rows), self.name),
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ if len(rows) == header_rows > 0:
+ error = self.reporter.error(
+ f'Insufficient data supplied ({len(rows)} row(s)); '
+ 'no data remaining for table body, '
+ f'required by "{self.name}" directive.',
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ for row in rows:
+ if len(row) < stub_columns:
+ error = self.reporter.error(
+ f'{stub_columns} stub column(s) specified '
+ f'but only {len(row)} columns(s) of data supplied '
+ f'("{self.name}" directive).',
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ if len(row) == stub_columns > 0:
+ error = self.reporter.error(
+ 'Insufficient data supplied (%s columns(s)); '
+ 'no data remaining for table body, required '
+ 'by "%s" directive.' % (len(row), self.name),
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+
+ def set_table_width(self, table_node):
+ if 'width' in self.options:
+ table_node['width'] = self.options.get('width')
+
+ @property
+ def widths(self):
+ return self.options.get('widths', '')
+
+ def get_column_widths(self, n_cols):
+ if isinstance(self.widths, list):
+ if len(self.widths) != n_cols:
+ # TODO: use last value for missing columns?
+ error = self.reporter.error('"%s" widths do not match the '
+ 'number of columns in table (%s).' % (self.name, n_cols),
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ col_widths = self.widths
+ elif n_cols:
+ col_widths = [100 // n_cols] * n_cols
+ else:
+ error = self.reporter.error('No table data detected in CSV file.',
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ return col_widths
+
+ def extend_short_rows_with_empty_cells(self, columns, parts):
+ for part in parts:
+ for row in part:
+ if len(row) < columns:
+ row.extend([(0, 0, 0, [])] * (columns - len(row)))
+
+
+class RSTTable(Table):
+ """
+ Class for the `"table" directive`__ for formal tables using rST syntax.
+
+ __ https://docutils.sourceforge.io/docs/ref/rst/directives.html
+ """
+
+ def run(self):
+ if not self.content:
+ warning = self.reporter.warning('Content block expected '
+ 'for the "%s" directive; none found.' % self.name,
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ return [warning]
+ title, messages = self.make_title()
+ node = nodes.Element() # anonymous container for parsing
+ self.state.nested_parse(self.content, self.content_offset, node)
+ if len(node) != 1 or not isinstance(node[0], nodes.table):
+ error = self.reporter.error('Error parsing content block for the '
+ '"%s" directive: exactly one table expected.' % self.name,
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ return [error]
+ table_node = node[0]
+ table_node['classes'] += self.options.get('class', [])
+ self.set_table_width(table_node)
+ if 'align' in self.options:
+ table_node['align'] = self.options.get('align')
+ if isinstance(self.widths, list):
+ tgroup = table_node[0]
+ try:
+ col_widths = self.get_column_widths(tgroup["cols"])
+ except SystemMessagePropagation as detail:
+ return [detail.args[0]]
+ colspecs = [child for child in tgroup.children
+ if child.tagname == 'colspec']
+ for colspec, col_width in zip(colspecs, col_widths):
+ colspec['colwidth'] = col_width
+ if self.widths == 'auto':
+ table_node['classes'] += ['colwidths-auto']
+ elif self.widths: # "grid" or list of integers
+ table_node['classes'] += ['colwidths-given']
+ self.add_name(table_node)
+ if title:
+ table_node.insert(0, title)
+ return [table_node] + messages
+
+
+class CSVTable(Table):
+
+ option_spec = {'header-rows': directives.nonnegative_int,
+ 'stub-columns': directives.nonnegative_int,
+ 'header': directives.unchanged,
+ 'width': directives.length_or_percentage_or_unitless,
+ 'widths': directives.value_or(('auto', ),
+ directives.positive_int_list),
+ 'file': directives.path,
+ 'url': directives.uri,
+ 'encoding': directives.encoding,
+ 'class': directives.class_option,
+ 'name': directives.unchanged,
+ 'align': align,
+ # field delimiter char
+ 'delim': directives.single_char_or_whitespace_or_unicode,
+ # treat whitespace after delimiter as significant
+ 'keepspace': directives.flag,
+ # text field quote/unquote char:
+ 'quote': directives.single_char_or_unicode,
+ # char used to escape delim & quote as-needed:
+ 'escape': directives.single_char_or_unicode}
+
+ class DocutilsDialect(csv.Dialect):
+
+ """CSV dialect for `csv_table` directive."""
+
+ delimiter = ','
+ quotechar = '"'
+ doublequote = True
+ skipinitialspace = True
+ strict = True
+ lineterminator = '\n'
+ quoting = csv.QUOTE_MINIMAL
+
+ def __init__(self, options):
+ if 'delim' in options:
+ self.delimiter = options['delim']
+ if 'keepspace' in options:
+ self.skipinitialspace = False
+ if 'quote' in options:
+ self.quotechar = options['quote']
+ if 'escape' in options:
+ self.doublequote = False
+ self.escapechar = options['escape']
+ super().__init__()
+
+ class HeaderDialect(csv.Dialect):
+ """
+ CSV dialect used for the "header" option data.
+
+ Deprecated. Will be removed in Docutils 0.22.
+ """
+ # The separate HeaderDialect was introduced in revision 2294
+ # (2004-06-17) in the sandbox before the "csv-table" directive moved
+ # to the trunk in r2309. Discussion in docutils-devel around this time
+ # did not mention a rationale (part of the discussion was in private
+ # mail).
+ # This is in conflict with the documentation, which always said:
+ # "Must use the same CSV format as the main CSV data."
+ # and did not change in this aspect.
+ #
+ # Maybe it was intended to have similar escape rules for rST and CSV,
+ # however with the current implementation this means we need
+ # `\\` for rST markup and ``\\\\`` for a literal backslash
+ # in the "option" header but ``\`` and ``\\`` in the header-lines and
+ # table cells of the main CSV data.
+ delimiter = ','
+ quotechar = '"'
+ escapechar = '\\'
+ doublequote = False
+ skipinitialspace = True
+ strict = True
+ lineterminator = '\n'
+ quoting = csv.QUOTE_MINIMAL
+
+ def __init__(self):
+ warnings.warn('CSVTable.HeaderDialect will be removed '
+ 'in Docutils 0.22.',
+ PendingDeprecationWarning, stacklevel=2)
+ super().__init__()
+
+ @staticmethod
+ def check_requirements():
+ warnings.warn('CSVTable.check_requirements()'
+ ' is not required with Python 3'
+ ' and will be removed in Docutils 0.22.',
+ DeprecationWarning, stacklevel=2)
+
+ def process_header_option(self):
+ source = self.state_machine.get_source(self.lineno - 1)
+ table_head = []
+ max_header_cols = 0
+ if 'header' in self.options: # separate table header in option
+ rows, max_header_cols = self.parse_csv_data_into_rows(
+ self.options['header'].split('\n'),
+ self.DocutilsDialect(self.options),
+ source)
+ table_head.extend(rows)
+ return table_head, max_header_cols
+
+ def run(self):
+ try:
+ if (not self.state.document.settings.file_insertion_enabled
+ and ('file' in self.options
+ or 'url' in self.options)):
+ warning = self.reporter.warning('File and URL access '
+ 'deactivated; ignoring "%s" directive.' % self.name,
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ return [warning]
+ title, messages = self.make_title()
+ csv_data, source = self.get_csv_data()
+ table_head, max_header_cols = self.process_header_option()
+ rows, max_cols = self.parse_csv_data_into_rows(
+ csv_data, self.DocutilsDialect(self.options), source)
+ max_cols = max(max_cols, max_header_cols)
+ header_rows = self.options.get('header-rows', 0)
+ stub_columns = self.options.get('stub-columns', 0)
+ self.check_table_dimensions(rows, header_rows, stub_columns)
+ table_head.extend(rows[:header_rows])
+ table_body = rows[header_rows:]
+ col_widths = self.get_column_widths(max_cols)
+ self.extend_short_rows_with_empty_cells(max_cols,
+ (table_head, table_body))
+ except SystemMessagePropagation as detail:
+ return [detail.args[0]]
+ except csv.Error as detail:
+ message = str(detail)
+ error = self.reporter.error('Error with CSV data'
+ ' in "%s" directive:\n%s' % (self.name, message),
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ return [error]
+ table = (col_widths, table_head, table_body)
+ table_node = self.state.build_table(table, self.content_offset,
+ stub_columns, widths=self.widths)
+ table_node['classes'] += self.options.get('class', [])
+ if 'align' in self.options:
+ table_node['align'] = self.options.get('align')
+ self.set_table_width(table_node)
+ self.add_name(table_node)
+ if title:
+ table_node.insert(0, title)
+ return [table_node] + messages
+
+ def get_csv_data(self):
+ """
+ Get CSV data from the directive content, from an external
+ file, or from a URL reference.
+ """
+ settings = self.state.document.settings
+ encoding = self.options.get('encoding', settings.input_encoding)
+ error_handler = settings.input_encoding_error_handler
+ if self.content:
+ # CSV data is from directive content.
+ if 'file' in self.options or 'url' in self.options:
+ error = self.reporter.error('"%s" directive may not both '
+ 'specify an external file and have content.' % self.name,
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ source = self.content.source(0)
+ csv_data = self.content
+ elif 'file' in self.options:
+ # CSV data is from an external file.
+ if 'url' in self.options:
+ error = self.reporter.error('The "file" and "url" options '
+ 'may not be simultaneously specified '
+ 'for the "%s" directive.' % self.name,
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ source = adapt_path(self.options['file'],
+ self.state.document.current_source,
+ settings.root_prefix)
+ try:
+ csv_file = FileInput(source_path=source,
+ encoding=encoding,
+ error_handler=error_handler)
+ csv_data = csv_file.read().splitlines()
+ except OSError as error:
+ severe = self.reporter.severe(
+ 'Problems with "%s" directive path:\n%s.'
+ % (self.name, error),
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(severe)
+ else:
+ settings.record_dependencies.add(source)
+ elif 'url' in self.options:
+ source = self.options['url']
+ try:
+ with urlopen(source) as response:
+ csv_text = response.read()
+ except (URLError, OSError, ValueError) as error:
+ severe = self.reporter.severe(
+ 'Problems with "%s" directive URL "%s":\n%s.'
+ % (self.name, self.options['url'], error),
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(severe)
+ csv_file = StringInput(source=csv_text, source_path=source,
+ encoding=encoding,
+ error_handler=error_handler)
+ csv_data = csv_file.read().splitlines()
+ else:
+ error = self.reporter.warning(
+ 'The "%s" directive requires content; none supplied.'
+ % self.name,
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ return csv_data, source
+
+ @staticmethod
+ def decode_from_csv(s):
+ warnings.warn('CSVTable.decode_from_csv()'
+ ' is not required with Python 3'
+ ' and will be removed in Docutils 0.21 or later.',
+ DeprecationWarning, stacklevel=2)
+ return s
+
+ @staticmethod
+ def encode_for_csv(s):
+ warnings.warn('CSVTable.encode_from_csv()'
+ ' is not required with Python 3'
+ ' and will be removed in Docutils 0.21 or later.',
+ DeprecationWarning, stacklevel=2)
+ return s
+
+ def parse_csv_data_into_rows(self, csv_data, dialect, source):
+ csv_reader = csv.reader((line + '\n' for line in csv_data),
+ dialect=dialect)
+ rows = []
+ max_cols = 0
+ for row in csv_reader:
+ row_data = []
+ for cell in row:
+ cell_data = (0, 0, 0, statemachine.StringList(
+ cell.splitlines(), source=source))
+ row_data.append(cell_data)
+ rows.append(row_data)
+ max_cols = max(max_cols, len(row))
+ return rows, max_cols
+
+
+class ListTable(Table):
+
+ """
+ Implement tables whose data is encoded as a uniform two-level bullet list.
+ For further ideas, see
+ https://docutils.sourceforge.io/docs/dev/rst/alternatives.html#list-driven-tables
+ """
+
+ option_spec = {'header-rows': directives.nonnegative_int,
+ 'stub-columns': directives.nonnegative_int,
+ 'width': directives.length_or_percentage_or_unitless,
+ 'widths': directives.value_or(('auto', ),
+ directives.positive_int_list),
+ 'class': directives.class_option,
+ 'name': directives.unchanged,
+ 'align': align}
+
+ def run(self):
+ if not self.content:
+ error = self.reporter.error('The "%s" directive is empty; '
+ 'content required.' % self.name,
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ return [error]
+ title, messages = self.make_title()
+ node = nodes.Element() # anonymous container for parsing
+ self.state.nested_parse(self.content, self.content_offset, node)
+ try:
+ num_cols, col_widths = self.check_list_content(node)
+ table_data = [[item.children for item in row_list[0]]
+ for row_list in node[0]]
+ header_rows = self.options.get('header-rows', 0)
+ stub_columns = self.options.get('stub-columns', 0)
+ self.check_table_dimensions(table_data, header_rows, stub_columns)
+ except SystemMessagePropagation as detail:
+ return [detail.args[0]]
+ table_node = self.build_table_from_list(table_data, col_widths,
+ header_rows, stub_columns)
+ if 'align' in self.options:
+ table_node['align'] = self.options.get('align')
+ table_node['classes'] += self.options.get('class', [])
+ self.set_table_width(table_node)
+ self.add_name(table_node)
+ if title:
+ table_node.insert(0, title)
+ return [table_node] + messages
+
+ def check_list_content(self, node):
+ if len(node) != 1 or not isinstance(node[0], nodes.bullet_list):
+ error = self.reporter.error(
+ 'Error parsing content block for the "%s" directive: '
+ 'exactly one bullet list expected.' % self.name,
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ list_node = node[0]
+ num_cols = 0
+ # Check for a uniform two-level bullet list:
+ for item_index in range(len(list_node)):
+ item = list_node[item_index]
+ if len(item) != 1 or not isinstance(item[0], nodes.bullet_list):
+ error = self.reporter.error(
+ 'Error parsing content block for the "%s" directive: '
+ 'two-level bullet list expected, but row %s does not '
+ 'contain a second-level bullet list.'
+ % (self.name, item_index + 1),
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ elif item_index:
+ if len(item[0]) != num_cols:
+ error = self.reporter.error(
+ 'Error parsing content block for the "%s" directive: '
+ 'uniform two-level bullet list expected, but row %s '
+ 'does not contain the same number of items as row 1 '
+ '(%s vs %s).'
+ % (self.name, item_index + 1, len(item[0]), num_cols),
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno)
+ raise SystemMessagePropagation(error)
+ else:
+ num_cols = len(item[0])
+ col_widths = self.get_column_widths(num_cols)
+ return num_cols, col_widths
+
+ def build_table_from_list(self, table_data,
+ col_widths, header_rows, stub_columns):
+ table = nodes.table()
+ if self.widths == 'auto':
+ table['classes'] += ['colwidths-auto']
+ elif self.widths: # explicitly set column widths
+ table['classes'] += ['colwidths-given']
+ tgroup = nodes.tgroup(cols=len(col_widths))
+ table += tgroup
+ for col_width in col_widths:
+ colspec = nodes.colspec()
+ if col_width is not None:
+ colspec.attributes['colwidth'] = col_width
+ if stub_columns:
+ colspec.attributes['stub'] = 1
+ stub_columns -= 1
+ tgroup += colspec
+ rows = []
+ for row in table_data:
+ row_node = nodes.row()
+ for cell in row:
+ entry = nodes.entry()
+ entry += cell
+ row_node += entry
+ rows.append(row_node)
+ if header_rows:
+ thead = nodes.thead()
+ thead.extend(rows[:header_rows])
+ tgroup += thead
+ tbody = nodes.tbody()
+ tbody.extend(rows[header_rows:])
+ tgroup += tbody
+ return table