You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
6.6 KiB
190 lines
6.6 KiB
from __future__ import annotations
|
|
|
|
import os
|
|
from os import path
|
|
from typing import TYPE_CHECKING, ClassVar, 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
|
|
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 ExtensionMetadata, 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: ClassVar[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: ClassVar[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) -> ExtensionMetadata:
|
|
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,
|
|
}
|
|
|