Skip to content

Commit

Permalink
Fix #1858: Support numbering custom nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
tk0miya committed Feb 14, 2016
1 parent a391b1a commit 2ec60d5
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 21 deletions.
22 changes: 16 additions & 6 deletions doc/extdev/appapi.rst
Expand Up @@ -125,12 +125,22 @@ 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``.
.. method:: Sphinx.add_enumerable_node(node, figtype, title_getter=None, **kwds)

Register a Docutils node class as a numfig target. Sphinx numbers the node
automatically. And then the users can refer it using :rst:role:`numref`.

*figtype* is a type of enumerable nodes. Each figtypes have individual
numbering sequences. As a system figtypes, ``figure``, ``table`` and
``code-block`` are defined. It is able to add custom nodes to these
default figtypes. It is also able to define new custom figtype if new
figtype is given.

*title_getter* is a getter function to obtain the title of node. It takes
an instance of the enumerable node, and it must return its title as string.
The title is used to the default title of references for :rst:role:`ref`.
By default, Sphinx searches ``docutils.nodes.caption`` or
``docutils.nodes.title`` from the node as a title.

Other keyword arguments are used for node visitor functions. See the
:meth:`Sphinx.add_node` for details.
Expand Down
4 changes: 2 additions & 2 deletions sphinx/application.py
Expand Up @@ -608,8 +608,8 @@ def add_node(self, node, **kwds):
if depart:
setattr(translator, 'depart_'+node.__name__, depart)

def add_enumerable_node(self, node, figtype, **kwds):
self.enumerable_nodes[node] = figtype
def add_enumerable_node(self, node, figtype, title_getter=None, **kwds):
self.enumerable_nodes[node] = (figtype, title_getter)
self.add_node(node, **kwds)

def _directive_helper(self, obj, content=None, arguments=None, **options):
Expand Down
20 changes: 12 additions & 8 deletions sphinx/domains/std.py
Expand Up @@ -494,10 +494,10 @@ class StandardDomain(Domain):
'option': 'unknown option: %(target)s',
}

enumerable_nodes = { # node_class -> figtype
nodes.figure: 'figure',
nodes.table: 'table',
nodes.container: 'code-block',
enumerable_nodes = { # node_class -> (figtype, title_getter)
nodes.figure: ('figure', None),
nodes.table: ('table', None),
nodes.container: ('code-block', None),
}

def clear_doc(self, docname):
Expand Down Expand Up @@ -735,9 +735,13 @@ def is_enumerable_node(self, node):
def get_numfig_title(self, node):
"""Get the title of enumerable nodes to refer them using its title"""
if self.is_enumerable_node(node):
for subnode in node:
if subnode.tagname in ('caption', 'title'):
return clean_astext(subnode)
_, title_getter = self.enumerable_nodes.get(node.__class__, (None, None))
if title_getter:
return title_getter(node)
else:
for subnode in node:
if subnode.tagname in ('caption', 'title'):
return clean_astext(subnode)

return None

Expand All @@ -752,5 +756,5 @@ def has_child(node, cls):
else:
return None
else:
figtype = self.enumerable_nodes.get(node.__class__)
figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None))
return figtype
18 changes: 13 additions & 5 deletions sphinx/writers/html.py
Expand Up @@ -265,14 +265,22 @@ def add_fignumber(self, node):
def append_fignumber(figtype, figure_id):
if figure_id in self.builder.fignumbers.get(figtype, {}):
self.body.append('<span class="caption-number">')
prefix = self.builder.config.numfig_format.get(figtype, '')
numbers = self.builder.fignumbers[figtype][figure_id]
self.body.append(prefix % '.'.join(map(str, numbers)) + ' ')
self.body.append('</span>')
prefix = self.builder.config.numfig_format.get(figtype)
if prefix is None:
msg = 'numfig_format is not defined for %s' % figtype
self.builder.warn(msg)
else:
numbers = self.builder.fignumbers[figtype][figure_id]
self.body.append(prefix % '.'.join(map(str, numbers)) + ' ')
self.body.append('</span>')

figtype = self.builder.env.domains['std'].get_figtype(node)
if figtype:
append_fignumber(figtype, node['ids'][0])
if len(node['ids']) == 0:
msg = 'Any IDs not assiend for %s node' % node.tagname
self.builder.env.warn_node(msg, node)
else:
append_fignumber(figtype, node['ids'][0])

def add_permalink_ref(self, node, title):
if node['ids'] and self.permalink_text and self.builder.add_permalinks:
Expand Down
10 changes: 10 additions & 0 deletions tests/roots/test-add_enumerable_node/index.rst
Expand Up @@ -21,6 +21,14 @@ First section

First my figure

.. _first_numbered_text:

.. numbered-text:: Hello world

.. _second_numbered_text:

.. numbered-text:: Hello Sphinx

Second section
==============

Expand All @@ -36,3 +44,5 @@ Reference section
* first_figure is :numref:`first_figure`
* first_my_figure is :numref:`first_my_figure`
* second_my_figure is :numref:`second_my_figure`
* first numbered_text is :numref:`first_numbered_text`
* second numbered_text is :numref:`second_numbered_text`
30 changes: 30 additions & 0 deletions tests/roots/test-add_enumerable_node/test_enumerable_node.py
Expand Up @@ -28,8 +28,38 @@ def run(self):
return [figure_node]


class numbered_text(nodes.Element):
pass


def visit_numbered_text(self, node):
self.body.append(self.starttag(node, 'div'))
self.add_fignumber(node)
self.body.append(node['title'])
self.body.append('</div>')
raise nodes.SkipNode


def get_title(node):
return node['title']


class NumberedText(Directive):
required_arguments = 1
final_argument_whitespace = True

def run(self):
return [numbered_text(title=self.arguments[0])]


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)

# numbered_label
app.add_enumerable_node(numbered_text, 'original', get_title,
html=(visit_numbered_text, None))
app.add_directive('numbered-text', NumberedText)
app.config.numfig_format.setdefault('original', 'No.%s')
4 changes: 4 additions & 0 deletions tests/test_build_html.py
Expand Up @@ -953,9 +953,13 @@ def test_enumerable_node(app, status, warning):
"Fig. 2", True),
(".//div[@class='figure']/p[@class='caption']/span[@class='caption-number']",
"Fig. 3", True),
(".//div//span[@class='caption-number']", "No.1 ", True),
(".//div//span[@class='caption-number']", "No.2 ", True),
(".//li/a/span", 'Fig. 1', True),
(".//li/a/span", 'Fig. 2', True),
(".//li/a/span", 'Fig. 3', True),
(".//li/a/span", 'No.1', True),
(".//li/a/span", 'No.2', True),
],
}

Expand Down

0 comments on commit 2ec60d5

Please sign in to comment.