Add SphinxDirective as a helper

This commit is contained in:
Takeshi KOMIYA
2018-03-28 22:09:38 +09:00
parent 39ce9d9ab9
commit 5ff7bc6dc6
5 changed files with 67 additions and 51 deletions

View File

@@ -16,6 +16,7 @@ from docutils.parsers.rst import Directive, directives, roles
from sphinx import addnodes from sphinx import addnodes
from sphinx.util.docfields import DocFieldTransformer from sphinx.util.docfields import DocFieldTransformer
from sphinx.util.docutils import SphinxDirective
# import all directives sphinx provides # import all directives sphinx provides
from sphinx.directives.code import ( # noqa from sphinx.directives.code import ( # noqa
@@ -33,6 +34,7 @@ if False:
# For type annotation # For type annotation
from typing import Any, Dict, List # NOQA from typing import Any, Dict, List # NOQA
from sphinx.application import Sphinx # NOQA from sphinx.application import Sphinx # NOQA
from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA from sphinx.environment import BuildEnvironment # NOQA
@@ -41,7 +43,7 @@ nl_escape_re = re.compile(r'\\\n')
strip_backslash_re = re.compile(r'\\(.)') strip_backslash_re = re.compile(r'\\(.)')
class ObjectDescription(Directive): class ObjectDescription(SphinxDirective):
""" """
Directive to describe a class, function or similar object. Not used Directive to describe a class, function or similar object. Not used
directly, but subclassed (in domain-specific directives) to add custom directly, but subclassed (in domain-specific directives) to add custom
@@ -135,7 +137,6 @@ class ObjectDescription(Directive):
self.domain, self.objtype = self.name.split(':', 1) self.domain, self.objtype = self.name.split(':', 1)
else: else:
self.domain, self.objtype = '', self.name self.domain, self.objtype = '', self.name
self.env = self.state.document.settings.env # type: BuildEnvironment
self.indexnode = addnodes.index(entries=[]) self.indexnode = addnodes.index(entries=[])
node = addnodes.desc() node = addnodes.desc()
@@ -187,7 +188,7 @@ class ObjectDescription(Directive):
DescDirective = ObjectDescription DescDirective = ObjectDescription
class DefaultRole(Directive): class DefaultRole(SphinxDirective):
""" """
Set the default interpreted text role. Overridden from docutils. Set the default interpreted text role. Overridden from docutils.
""" """
@@ -212,7 +213,7 @@ class DefaultRole(Directive):
line=self.lineno) line=self.lineno)
return messages + [error] return messages + [error]
roles._roles[''] = role roles._roles[''] = role
self.state.document.settings.env.temp_data['default_role'] = role_name self.env.temp_data['default_role'] = role_name
return messages return messages
@@ -229,7 +230,6 @@ class DefaultDomain(Directive):
def run(self): def run(self):
# type: () -> List[nodes.Node] # type: () -> List[nodes.Node]
env = self.state.document.settings.env
domain_name = self.arguments[0].lower() domain_name = self.arguments[0].lower()
# if domain_name not in env.domains: # if domain_name not in env.domains:
# # try searching by label # # try searching by label
@@ -237,7 +237,7 @@ class DefaultDomain(Directive):
# if domain.label.lower() == domain_name: # if domain.label.lower() == domain_name:
# domain_name = domain.name # domain_name = domain.name
# break # break
env.temp_data['default_domain'] = env.domains.get(domain_name) self.env.temp_data['default_domain'] = self.env.domains.get(domain_name)
return [] return []

View File

@@ -12,7 +12,7 @@ import sys
from difflib import unified_diff from difflib import unified_diff
from docutils import nodes from docutils import nodes
from docutils.parsers.rst import Directive, directives from docutils.parsers.rst import directives
from docutils.statemachine import ViewList from docutils.statemachine import ViewList
from six import text_type from six import text_type
@@ -20,6 +20,7 @@ from sphinx import addnodes
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import logging from sphinx.util import logging
from sphinx.util import parselinenos from sphinx.util import parselinenos
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import set_source_info from sphinx.util.nodes import set_source_info
if False: if False:
@@ -31,7 +32,7 @@ if False:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Highlight(Directive): class Highlight(SphinxDirective):
""" """
Directive to set the highlighting language for code blocks, as well Directive to set the highlighting language for code blocks, as well
as the threshold for line numbers. as the threshold for line numbers.
@@ -77,7 +78,7 @@ def dedent_lines(lines, dedent, location=None):
def container_wrapper(directive, literal_node, caption): def container_wrapper(directive, literal_node, caption):
# type: (Directive, nodes.Node, unicode) -> nodes.container # type: (SphinxDirective, nodes.Node, unicode) -> nodes.container
container_node = nodes.container('', literal_block=True, container_node = nodes.container('', literal_block=True,
classes=['literal-block-wrapper']) classes=['literal-block-wrapper'])
parsed = nodes.Element() parsed = nodes.Element()
@@ -95,7 +96,7 @@ def container_wrapper(directive, literal_node, caption):
return container_node return container_node
class CodeBlock(Directive): class CodeBlock(SphinxDirective):
""" """
Directive for a code block with special highlighting or line numbering Directive for a code block with special highlighting or line numbering
settings. settings.
@@ -372,7 +373,7 @@ class LiteralIncludeReader(object):
return lines return lines
class LiteralInclude(Directive): class LiteralInclude(SphinxDirective):
""" """
Like ``.. include:: :literal:``, but only warns if the include file is Like ``.. include:: :literal:``, but only warns if the include file is
not found, and does not raise errors. Also has several options for not found, and does not raise errors. Also has several options for
@@ -412,19 +413,17 @@ class LiteralInclude(Directive):
if not document.settings.file_insertion_enabled: if not document.settings.file_insertion_enabled:
return [document.reporter.warning('File insertion disabled', return [document.reporter.warning('File insertion disabled',
line=self.lineno)] line=self.lineno)]
env = document.settings.env
# convert options['diff'] to absolute path # convert options['diff'] to absolute path
if 'diff' in self.options: if 'diff' in self.options:
_, path = env.relfn2path(self.options['diff']) _, path = self.env.relfn2path(self.options['diff'])
self.options['diff'] = path self.options['diff'] = path
try: try:
location = self.state_machine.get_source_and_line(self.lineno) location = self.state_machine.get_source_and_line(self.lineno)
rel_filename, filename = env.relfn2path(self.arguments[0]) rel_filename, filename = self.env.relfn2path(self.arguments[0])
env.note_dependency(rel_filename) self.env.note_dependency(rel_filename)
reader = LiteralIncludeReader(filename, self.options, env.config) reader = LiteralIncludeReader(filename, self.options, self.config)
text, lines = reader.read(location=location) text, lines = reader.read(location=location)
retnode = nodes.literal_block(text, text, source=filename) retnode = nodes.literal_block(text, text, source=filename)

View File

@@ -8,7 +8,7 @@
""" """
from docutils import nodes from docutils import nodes
from docutils.parsers.rst import Directive, directives from docutils.parsers.rst import directives
from docutils.parsers.rst.directives.admonitions import BaseAdmonition from docutils.parsers.rst.directives.admonitions import BaseAdmonition
from docutils.parsers.rst.directives.misc import Class from docutils.parsers.rst.directives.misc import Class
from docutils.parsers.rst.directives.misc import Include as BaseInclude from docutils.parsers.rst.directives.misc import Include as BaseInclude
@@ -18,6 +18,7 @@ from sphinx import addnodes, locale
from sphinx.deprecation import DeprecatedDict, RemovedInSphinx30Warning from sphinx.deprecation import DeprecatedDict, RemovedInSphinx30Warning
from sphinx.locale import _ from sphinx.locale import _
from sphinx.util import url_re, docname_join from sphinx.util import url_re, docname_join
from sphinx.util.docutils import SphinxDirective
from sphinx.util.matching import patfilter from sphinx.util.matching import patfilter
from sphinx.util.nodes import explicit_title_re, set_source_info, \ from sphinx.util.nodes import explicit_title_re, set_source_info, \
process_index_entry process_index_entry
@@ -49,7 +50,7 @@ def int_or_nothing(argument):
return int(argument) return int(argument)
class TocTree(Directive): class TocTree(SphinxDirective):
""" """
Directive to notify Sphinx about the hierarchical structure of the docs, Directive to notify Sphinx about the hierarchical structure of the docs,
and to include a table-of-contents like tree in the current document. and to include a table-of-contents like tree in the current document.
@@ -72,8 +73,7 @@ class TocTree(Directive):
def run(self): def run(self):
# type: () -> List[nodes.Node] # type: () -> List[nodes.Node]
env = self.state.document.settings.env suffixes = self.config.source_suffix
suffixes = env.config.source_suffix
glob = 'glob' in self.options glob = 'glob' in self.options
ret = [] ret = []
@@ -81,17 +81,17 @@ class TocTree(Directive):
# and title may be None if the document's title is to be used # and title may be None if the document's title is to be used
entries = [] # type: List[Tuple[unicode, unicode]] entries = [] # type: List[Tuple[unicode, unicode]]
includefiles = [] includefiles = []
all_docnames = env.found_docs.copy() all_docnames = self.env.found_docs.copy()
# don't add the currently visited file in catch-all patterns # don't add the currently visited file in catch-all patterns
all_docnames.remove(env.docname) all_docnames.remove(self.env.docname)
for entry in self.content: for entry in self.content:
if not entry: if not entry:
continue continue
# look for explicit titles ("Some Title <document>") # look for explicit titles ("Some Title <document>")
explicit = explicit_title_re.match(entry) explicit = explicit_title_re.match(entry)
if glob and ('*' in entry or '?' in entry or '[' in entry) and not explicit: if glob and ('*' in entry or '?' in entry or '[' in entry) and not explicit:
patname = docname_join(env.docname, entry) patname = docname_join(self.env.docname, entry)
docnames = sorted(patfilter(all_docnames, patname)) docnames = sorted(patfilter(all_docnames, patname)) # type: ignore
for docname in docnames: for docname in docnames:
all_docnames.remove(docname) # don't include it again all_docnames.remove(docname) # don't include it again
entries.append((None, docname)) entries.append((None, docname))
@@ -114,20 +114,20 @@ class TocTree(Directive):
docname = docname[:-len(suffix)] docname = docname[:-len(suffix)]
break break
# absolutize filenames # absolutize filenames
docname = docname_join(env.docname, docname) docname = docname_join(self.env.docname, docname)
if url_re.match(ref) or ref == 'self': if url_re.match(ref) or ref == 'self':
entries.append((title, ref)) entries.append((title, ref))
elif docname not in env.found_docs: elif docname not in self.env.found_docs:
ret.append(self.state.document.reporter.warning( ret.append(self.state.document.reporter.warning(
'toctree contains reference to nonexisting ' 'toctree contains reference to nonexisting '
'document %r' % docname, line=self.lineno)) 'document %r' % docname, line=self.lineno))
env.note_reread() self.env.note_reread()
else: else:
all_docnames.discard(docname) all_docnames.discard(docname)
entries.append((title, docname)) entries.append((title, docname))
includefiles.append(docname) includefiles.append(docname)
subnode = addnodes.toctree() subnode = addnodes.toctree()
subnode['parent'] = env.docname subnode['parent'] = self.env.docname
# entries contains all entries (self references, external links etc.) # entries contains all entries (self references, external links etc.)
if 'reversed' in self.options: if 'reversed' in self.options:
entries.reverse() entries.reverse()
@@ -149,7 +149,7 @@ class TocTree(Directive):
return ret return ret
class Author(Directive): class Author(SphinxDirective):
""" """
Directive to give the name of the author of the current document Directive to give the name of the author of the current document
or section. Shown in the output only if the show_authors option is on. or section. Shown in the output only if the show_authors option is on.
@@ -162,8 +162,7 @@ class Author(Directive):
def run(self): def run(self):
# type: () -> List[nodes.Node] # type: () -> List[nodes.Node]
env = self.state.document.settings.env if not self.config.show_authors:
if not env.config.show_authors:
return [] return []
para = nodes.paragraph(translatable=False) para = nodes.paragraph(translatable=False)
emph = nodes.emphasis() emph = nodes.emphasis()
@@ -183,7 +182,7 @@ class Author(Directive):
return [para] + messages return [para] + messages
class Index(Directive): class Index(SphinxDirective):
""" """
Directive to add entries to the index. Directive to add entries to the index.
""" """
@@ -196,8 +195,7 @@ class Index(Directive):
def run(self): def run(self):
# type: () -> List[nodes.Node] # type: () -> List[nodes.Node]
arguments = self.arguments[0].split('\n') arguments = self.arguments[0].split('\n')
env = self.state.document.settings.env targetid = 'index-%s' % self.env.new_serialno('index')
targetid = 'index-%s' % env.new_serialno('index')
targetnode = nodes.target('', '', ids=[targetid]) targetnode = nodes.target('', '', ids=[targetid])
self.state.document.note_explicit_target(targetnode) self.state.document.note_explicit_target(targetnode)
indexnode = addnodes.index() indexnode = addnodes.index()
@@ -209,7 +207,7 @@ class Index(Directive):
return [indexnode, targetnode] return [indexnode, targetnode]
class VersionChange(Directive): class VersionChange(SphinxDirective):
""" """
Directive to describe a change/addition/deprecation in a specific version. Directive to describe a change/addition/deprecation in a specific version.
""" """
@@ -252,9 +250,8 @@ class VersionChange(Directive):
classes=['versionmodified']), classes=['versionmodified']),
translatable=False) translatable=False)
node.append(para) node.append(para)
env = self.state.document.settings.env
# XXX should record node.source as well # XXX should record node.source as well
env.note_versionchange(node['type'], node['version'], node, node.line) self.env.note_versionchange(node['type'], node['version'], node, node.line)
return [node] + messages return [node] + messages
@@ -265,7 +262,7 @@ class SeeAlso(BaseAdmonition):
node_class = addnodes.seealso node_class = addnodes.seealso
class TabularColumns(Directive): class TabularColumns(SphinxDirective):
""" """
Directive to give an explicit tabulary column definition to LaTeX. Directive to give an explicit tabulary column definition to LaTeX.
""" """
@@ -283,7 +280,7 @@ class TabularColumns(Directive):
return [node] return [node]
class Centered(Directive): class Centered(SphinxDirective):
""" """
Directive to create a centered line of bold text. Directive to create a centered line of bold text.
""" """
@@ -304,7 +301,7 @@ class Centered(Directive):
return [subnode] + messages return [subnode] + messages
class Acks(Directive): class Acks(SphinxDirective):
""" """
Directive for a list of names. Directive for a list of names.
""" """
@@ -326,7 +323,7 @@ class Acks(Directive):
return [node] return [node]
class HList(Directive): class HList(SphinxDirective):
""" """
Directive for a list that gets compacted horizontally. Directive for a list that gets compacted horizontally.
""" """
@@ -363,7 +360,7 @@ class HList(Directive):
return [newnode] return [newnode]
class Only(Directive): class Only(SphinxDirective):
""" """
Directive to only include text if the given tag(s) are enabled. Directive to only include text if the given tag(s) are enabled.
""" """
@@ -421,7 +418,7 @@ class Only(Directive):
self.state.memo.section_level = surrounding_section_level self.state.memo.section_level = surrounding_section_level
class Include(BaseInclude): class Include(BaseInclude, SphinxDirective):
""" """
Like the standard "Include" directive, but interprets absolute paths Like the standard "Include" directive, but interprets absolute paths
"correctly", i.e. relative to source directory. "correctly", i.e. relative to source directory.
@@ -429,14 +426,13 @@ class Include(BaseInclude):
def run(self): def run(self):
# type: () -> List[nodes.Node] # type: () -> List[nodes.Node]
env = self.state.document.settings.env
if self.arguments[0].startswith('<') and \ if self.arguments[0].startswith('<') and \
self.arguments[0].endswith('>'): self.arguments[0].endswith('>'):
# docutils "standard" includes, do not do path processing # docutils "standard" includes, do not do path processing
return BaseInclude.run(self) return BaseInclude.run(self)
rel_filename, filename = env.relfn2path(self.arguments[0]) rel_filename, filename = self.env.relfn2path(self.arguments[0])
self.arguments[0] = filename self.arguments[0] = filename
env.note_included(filename) self.env.note_included(filename)
return BaseInclude.run(self) return BaseInclude.run(self)

View File

@@ -12,6 +12,7 @@ from docutils.parsers.rst import directives
from docutils.parsers.rst.directives import images, html, tables from docutils.parsers.rst.directives import images, html, tables
from sphinx import addnodes from sphinx import addnodes
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import set_source_info from sphinx.util.nodes import set_source_info
if False: if False:
@@ -44,16 +45,15 @@ class Figure(images.Figure):
return [figure_node] return [figure_node]
class Meta(html.Meta): class Meta(html.Meta, SphinxDirective):
def run(self): def run(self):
# type: () -> List[nodes.Node] # type: () -> List[nodes.Node]
env = self.state.document.settings.env
result = html.Meta.run(self) result = html.Meta.run(self)
for node in result: for node in result:
if (isinstance(node, nodes.pending) and if (isinstance(node, nodes.pending) and
isinstance(node.details['nodes'][0], html.MetaBody.meta)): isinstance(node.details['nodes'][0], html.MetaBody.meta)):
meta = node.details['nodes'][0] meta = node.details['nodes'][0]
meta.source = env.doc2path(env.docname) meta.source = self.env.doc2path(self.env.docname)
meta.line = self.lineno meta.line = self.lineno
meta.rawcontent = meta['content'] meta.rawcontent = meta['content']

View File

@@ -20,7 +20,7 @@ from distutils.version import LooseVersion
import docutils import docutils
from docutils import nodes from docutils import nodes
from docutils.languages import get_language from docutils.languages import get_language
from docutils.parsers.rst import directives, roles, convert_directive_function from docutils.parsers.rst import Directive, directives, roles, convert_directive_function
from docutils.statemachine import StateMachine from docutils.statemachine import StateMachine
from docutils.utils import Reporter from docutils.utils import Reporter
@@ -36,6 +36,7 @@ if False:
# For type annotation # For type annotation
from typing import Any, Callable, Generator, Iterator, List, Set, Tuple # NOQA from typing import Any, Callable, Generator, Iterator, List, Set, Tuple # NOQA
from docutils.statemachine import State, ViewList # NOQA from docutils.statemachine import State, ViewList # NOQA
from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA from sphinx.environment import BuildEnvironment # NOQA
from sphinx.io import SphinxFileInput # NOQA from sphinx.io import SphinxFileInput # NOQA
@@ -273,6 +274,26 @@ def switch_source_input(state, content):
state.memo.reporter.get_source_and_line = get_source_and_line state.memo.reporter.get_source_and_line = get_source_and_line
class SphinxDirective(Directive):
"""A base class for Directives.
Compared with ``docutils.parsers.rst.Directive``, this class improves
accessibility to Sphinx APIs.
"""
@property
def env(self):
# type: () -> BuildEnvironment
"""Reference to the :class:`.BuildEnvironment` object."""
return self.state.document.settings.env
@property
def config(self):
# type: () -> Config
"""Reference to the :class:`.Config` object."""
return self.env.config
# cache a vanilla instance of nodes.document # cache a vanilla instance of nodes.document
# Used in new_document() function # Used in new_document() function
__document_cache__ = None # type: nodes.document __document_cache__ = None # type: nodes.document