summaryrefslogtreecommitdiffstats
path: root/sphinx/directives/patches.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/directives/patches.py')
-rw-r--r--sphinx/directives/patches.py189
1 files changed, 189 insertions, 0 deletions
diff --git a/sphinx/directives/patches.py b/sphinx/directives/patches.py
new file mode 100644
index 0000000..965b385
--- /dev/null
+++ b/sphinx/directives/patches.py
@@ -0,0 +1,189 @@
+from __future__ import annotations
+
+import os
+from os import path
+from typing import TYPE_CHECKING, Any, cast
+
+from docutils import nodes
+from docutils.nodes import Node, make_id
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.directives import images, tables
+from docutils.parsers.rst.directives.misc import Meta # type: ignore[attr-defined]
+from docutils.parsers.rst.roles import set_classes
+
+from sphinx.directives import optional_int
+from sphinx.domains.math import MathDomain
+from sphinx.locale import __
+from sphinx.util import logging
+from sphinx.util.docutils import SphinxDirective
+from sphinx.util.nodes import set_source_info
+from sphinx.util.osutil import SEP, os_path, relpath
+
+if TYPE_CHECKING:
+ from sphinx.application import Sphinx
+ from sphinx.util.typing import OptionSpec
+
+
+logger = logging.getLogger(__name__)
+
+
+class Figure(images.Figure):
+ """The figure directive which applies `:name:` option to the figure node
+ instead of the image node.
+ """
+
+ def run(self) -> list[Node]:
+ name = self.options.pop('name', None)
+ result = super().run()
+ if len(result) == 2 or isinstance(result[0], nodes.system_message):
+ return result
+
+ assert len(result) == 1
+ figure_node = cast(nodes.figure, result[0])
+ if name:
+ # set ``name`` to figure_node if given
+ self.options['name'] = name
+ self.add_name(figure_node)
+
+ # copy lineno from image node
+ if figure_node.line is None and len(figure_node) == 2:
+ caption = cast(nodes.caption, figure_node[1])
+ figure_node.line = caption.line
+
+ return [figure_node]
+
+
+class CSVTable(tables.CSVTable):
+ """The csv-table directive which searches a CSV file from Sphinx project's source
+ directory when an absolute path is given via :file: option.
+ """
+
+ def run(self) -> list[Node]:
+ if 'file' in self.options and self.options['file'].startswith((SEP, os.sep)):
+ env = self.state.document.settings.env
+ filename = self.options['file']
+ if path.exists(filename):
+ logger.warning(__('":file:" option for csv-table directive now recognizes '
+ 'an absolute path as a relative path from source directory. '
+ 'Please update your document.'),
+ location=(env.docname, self.lineno))
+ else:
+ abspath = path.join(env.srcdir, os_path(self.options['file'][1:]))
+ docdir = path.dirname(env.doc2path(env.docname))
+ self.options['file'] = relpath(abspath, docdir)
+
+ return super().run()
+
+
+class Code(SphinxDirective):
+ """Parse and mark up content of a code block.
+
+ This is compatible with docutils' :rst:dir:`code` directive.
+ """
+ optional_arguments = 1
+ option_spec: OptionSpec = {
+ 'class': directives.class_option,
+ 'force': directives.flag,
+ 'name': directives.unchanged,
+ 'number-lines': optional_int,
+ }
+ has_content = True
+
+ def run(self) -> list[Node]:
+ self.assert_has_content()
+
+ set_classes(self.options)
+ code = '\n'.join(self.content)
+ node = nodes.literal_block(code, code,
+ classes=self.options.get('classes', []),
+ force='force' in self.options,
+ highlight_args={})
+ self.add_name(node)
+ set_source_info(self, node)
+
+ if self.arguments:
+ # highlight language specified
+ node['language'] = self.arguments[0]
+ else:
+ # no highlight language specified. Then this directive refers the current
+ # highlight setting via ``highlight`` directive or ``highlight_language``
+ # configuration.
+ node['language'] = self.env.temp_data.get('highlight_language',
+ self.config.highlight_language)
+
+ if 'number-lines' in self.options:
+ node['linenos'] = True
+
+ # if number given, treat as lineno-start.
+ if self.options['number-lines']:
+ node['highlight_args']['linenostart'] = self.options['number-lines']
+
+ return [node]
+
+
+class MathDirective(SphinxDirective):
+ has_content = True
+ required_arguments = 0
+ optional_arguments = 1
+ final_argument_whitespace = True
+ option_spec: OptionSpec = {
+ 'label': directives.unchanged,
+ 'name': directives.unchanged,
+ 'class': directives.class_option,
+ 'nowrap': directives.flag,
+ }
+
+ def run(self) -> list[Node]:
+ latex = '\n'.join(self.content)
+ if self.arguments and self.arguments[0]:
+ latex = self.arguments[0] + '\n\n' + latex
+ label = self.options.get('label', self.options.get('name'))
+ node = nodes.math_block(latex, latex,
+ classes=self.options.get('class', []),
+ docname=self.env.docname,
+ number=None,
+ label=label,
+ nowrap='nowrap' in self.options)
+ self.add_name(node)
+ self.set_source_info(node)
+
+ ret: list[Node] = [node]
+ self.add_target(ret)
+ return ret
+
+ def add_target(self, ret: list[Node]) -> None:
+ node = cast(nodes.math_block, ret[0])
+
+ # assign label automatically if math_number_all enabled
+ if node['label'] == '' or (self.config.math_number_all and not node['label']):
+ seq = self.env.new_serialno('sphinx.ext.math#equations')
+ node['label'] = "%s:%d" % (self.env.docname, seq)
+
+ # no targets and numbers are needed
+ if not node['label']:
+ return
+
+ # register label to domain
+ domain = cast(MathDomain, self.env.get_domain('math'))
+ domain.note_equation(self.env.docname, node['label'], location=node)
+ node['number'] = domain.get_equation_number_for(node['label'])
+
+ # add target node
+ node_id = make_id('equation-%s' % node['label'])
+ target = nodes.target('', '', ids=[node_id])
+ self.state.document.note_explicit_target(target)
+ ret.insert(0, target)
+
+
+def setup(app: Sphinx) -> dict[str, Any]:
+ directives.register_directive('figure', Figure)
+ directives.register_directive('meta', Meta)
+ directives.register_directive('csv-table', CSVTable)
+ directives.register_directive('code', Code)
+ directives.register_directive('math', MathDirective)
+
+ return {
+ 'version': 'builtin',
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }