diff options
author | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
---|---|---|
committer | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
commit | 4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch) | |
tree | ee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives | |
parent | cc961e04ba734dd72309fb548a2f97d67d578813 (diff) | |
download | gn-ai-master.tar.gz |
Diffstat (limited to '.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives')
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. ``☮``). 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. ``☮``). 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 |