diff --git a/CHANGES b/CHANGES index 394d29ac2..110968669 100644 --- a/CHANGES +++ b/CHANGES @@ -60,6 +60,7 @@ Features added Also don't check anchors starting with ``!``. * #2300: enhance autoclass:: to use the docstring of __new__ if __init__ method's is missing of empty +* #1858: Add Sphinx.add_enumerable_node() to add enumerable nodes for numfig feature Bugs fixed ---------- diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index d16f3a597..a7c9f7e9a 100644 --- a/doc/extdev/appapi.rst +++ b/doc/extdev/appapi.rst @@ -125,6 +125,18 @@ package. .. versionchanged:: 0.5 Added the support for keyword arguments giving visit functions. +.. method:: Sphinx.add_enumerable_node(node, figtype, **kwds) + + Register a Docutils node class as a numfig target. Sphinx treats the node as + figure, table or code-block. And then the node is numbered automatically. + + *figtype* should be one of ``figure``, ``table`` or ``code-block``. + + Other keyword arguments are used for node visitor functions. See the + :meth:`Sphinx.add_node` for details. + + .. versionadded:: 1.4 + .. method:: Sphinx.add_directive(name, func, content, arguments, **options) Sphinx.add_directive(name, directiveclass) diff --git a/sphinx/application.py b/sphinx/application.py index 7f4aaa1bb..9277b89ec 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -86,6 +86,7 @@ class Sphinx(object): self.builderclasses = BUILTIN_BUILDERS.copy() self.builder = None self.env = None + self.enumerable_nodes = {} self.srcdir = srcdir self.confdir = confdir @@ -193,6 +194,8 @@ class Sphinx(object): self._init_env(freshenv) # set up the builder self._init_builder(self.buildername) + # set up the enumerable nodes + self._init_enumerable_nodes() def _init_i18n(self): """Load translated strings from the configured localedirs if enabled in @@ -264,6 +267,10 @@ class Sphinx(object): self.builder = builderclass(self) self.emit('builder-inited') + def _init_enumerable_nodes(self): + for node, settings in iteritems(self.enumerable_nodes): + self.env.domains['std'].enumerable_nodes[node] = settings + # ---- main "build" method ------------------------------------------------- def build(self, force_all=False, filenames=None): @@ -601,6 +608,10 @@ class Sphinx(object): if depart: setattr(translator, 'depart_'+node.__name__, depart) + def add_enumerable_node(self, node, figtype, **kwds): + self.enumerable_nodes[node] = figtype + self.add_node(node, **kwds) + def _directive_helper(self, obj, content=None, arguments=None, **options): if isinstance(obj, (types.FunctionType, types.MethodType)): obj.content = content diff --git a/tests/roots/test-add_enumerable_node/conf.py b/tests/roots/test-add_enumerable_node/conf.py new file mode 100644 index 000000000..d433def93 --- /dev/null +++ b/tests/roots/test-add_enumerable_node/conf.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +import os +import sys + +sys.path.insert(0, os.path.abspath('.')) +extensions = ['test_enumerable_node'] + +master_doc = 'index' +numfig = True diff --git a/tests/roots/test-add_enumerable_node/index.rst b/tests/roots/test-add_enumerable_node/index.rst new file mode 100644 index 000000000..ea1734aa9 --- /dev/null +++ b/tests/roots/test-add_enumerable_node/index.rst @@ -0,0 +1,38 @@ +======================== +test-add_enumerable_node +======================== + +.. toctree:: + :numbered: + + +First section +============= + +.. _first_figure: + +.. figure:: rimg.png + + First figure + +.. _first_my_figure: + +.. my-figure:: rimg.png + + First my figure + +Second section +============== + +.. _second_my_figure: + +.. my-figure:: rimg.png + + Second my figure + +Reference section +================= + +* first_figure is :numref:`first_figure` +* first_my_figure is :numref:`first_my_figure` +* second_my_figure is :numref:`second_my_figure` diff --git a/tests/roots/test-add_enumerable_node/rimg.png b/tests/roots/test-add_enumerable_node/rimg.png new file mode 100644 index 000000000..1081dc143 Binary files /dev/null and b/tests/roots/test-add_enumerable_node/rimg.png differ diff --git a/tests/roots/test-add_enumerable_node/test_enumerable_node.py b/tests/roots/test-add_enumerable_node/test_enumerable_node.py new file mode 100644 index 000000000..5b075fa69 --- /dev/null +++ b/tests/roots/test-add_enumerable_node/test_enumerable_node.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +from docutils import nodes +from docutils.parsers.rst import Directive +from sphinx.util.nodes import split_explicit_title + + +class my_figure(nodes.figure): + pass + + +def visit_my_figure(self, node): + self.visit_figure(node) + + +def depart_my_figure(self, node): + self.depart_figure(node) + + +class MyFigure(Directive): + required_arguments = 1 + has_content = True + + def run(self): + figure_node = my_figure() + figure_node += nodes.image(uri=self.arguments[0]) + figure_node += nodes.caption(text=''.join(self.content)) + return [figure_node] + + +def setup(app): + # my-figure + app.add_enumerable_node(my_figure, 'figure', + html=(visit_my_figure, depart_my_figure)) + app.add_directive('my-figure', MyFigure) diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 2afeaace6..21ec4bb48 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -941,6 +941,34 @@ def test_numfig_with_secnum_depth(app, status, warning): yield check_xpath, etree, fname, xpath, check, be_found +@gen_with_app(buildername='html', testroot='add_enumerable_node') +def test_enumerable_node(app, status, warning): + app.builder.build_all() + + expects = { + 'index.html': [ + (".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", + "Fig. 1", True), + (".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", + "Fig. 2", True), + (".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']", + "Fig. 3", True), + (".//li/a/span", 'Fig. 1', True), + (".//li/a/span", 'Fig. 2', True), + (".//li/a/span", 'Fig. 3', True), + ], + } + + for fname, paths in iteritems(expects): + parser = NslessParser() + parser.entity.update(html_entities.entitydefs) + with (app.outdir / fname).open('rb') as fp: + etree = ET.parse(fp, parser) + + for xpath, check, be_found in paths: + yield check_xpath, etree, fname, xpath, check, be_found + + @with_app(buildername='html') def test_jsmath(app, status, warning): app.builder.build_all()