diff --git a/CHANGES b/CHANGES index 3ed0656d4..438f8a9fd 100644 --- a/CHANGES +++ b/CHANGES @@ -118,6 +118,7 @@ Features added * #2926: EPUB3 builder supports vertical mode (``epub3_writing_mode`` option) * #2695: ``build_sphinx`` subcommand for setuptools handles exceptions as same as ``sphinx-build`` does +* #326: `numref` role can also refer sections Bugs fixed ---------- diff --git a/doc/config.rst b/doc/config.rst index 7fc91031f..25858ede6 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -294,13 +294,16 @@ General configuration .. confval:: numfig_format - A dictionary mapping ``'figure'``, ``'table'`` and ``'code-block'`` to - strings that are used for format of figure numbers. Default is to use - ``'Fig. %s'`` for ``'figure'``, ``'Table %s'`` for ``'table'`` and - ``'Listing %s'`` for ``'code-block'``. + A dictionary mapping ``'figure'``, ``'table'``, ``'code-block'`` and + ``'section'`` to strings that are used for format of figure numbers. Default + is to use ``'Fig. %s'`` for ``'figure'``, ``'Table %s'`` for ``'table'``, + ``'Listing %s'`` for ``'code-block'`` and ``'Section'`` for ``'section'``. .. versionadded:: 1.3 + .. versionchanged:: 1.5 + Support format of section + .. confval:: numfig_secnum_depth The scope of figure numbers, that is, the numfig feature numbers figures diff --git a/doc/markup/inline.rst b/doc/markup/inline.rst index ae47f820d..300416356 100644 --- a/doc/markup/inline.rst +++ b/doc/markup/inline.rst @@ -212,11 +212,14 @@ Cross-referencing figures by figure number .. versionadded:: 1.3 +.. versionchanged:: 1.5 + `numref` role can also refer sections + .. rst:role:: numref - Link to the specified figures, tables and code-blocks; the standard reST - labels are used. When you use this role, it will insert a reference to the - figure with link text by its figure number like "Fig. 1.1". + Link to the specified figures, tables, code-blocks and sections; the standard + reST labels are used. When you use this role, it will insert a reference to + the figure with link text by its figure number like "Fig. 1.1". If an explicit link text is given (like usual: ``:numref:`Image of Sphinx (Fig. %s) ```), the link caption will be the title of the reference. diff --git a/sphinx/config.py b/sphinx/config.py index fdf341268..9e944c468 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -104,7 +104,8 @@ class Config(object): nitpick_ignore = ([], None), numfig = (False, 'env'), numfig_secnum_depth = (1, 'env'), - numfig_format = ({'figure': l_('Fig. %s'), + numfig_format = ({'section': l_('Section %s'), + 'figure': l_('Fig. %s'), 'table': l_('Table %s'), 'code-block': l_('Listing %s')}, 'env'), diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 95ed17c42..be454fdeb 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -653,11 +653,10 @@ class StandardDomain(Domain): return None try: - figure_id = target_node['ids'][0] - fignumber = env.toc_fignumbers[docname][figtype][figure_id] - except (KeyError, IndexError): - # target_node is found, but fignumber is not assigned. - # Maybe it is defined in orphaned document. + fignumber = self.get_fignumber(env, builder, figtype, docname, target_node) + if fignumber is None: + return contnode + except ValueError: env.warn_node("no number is assigned for %s: %s" % (figtype, labelid), node) return contnode @@ -670,7 +669,7 @@ class StandardDomain(Domain): newtitle = title % '.'.join(map(str, fignumber)) except TypeError: env.warn_node('invalid numfig_format: %s' % title, node) - return None + return contnode return self.build_reference_node(fromdocname, builder, docname, labelid, newtitle, 'numref', @@ -804,7 +803,9 @@ class StandardDomain(Domain): def has_child(node, cls): return any(isinstance(child, cls) for child in node) - if isinstance(node, nodes.container): + if isinstance(node, nodes.section): + return 'section' + elif isinstance(node, nodes.container): if node.get('literal_block') and has_child(node, nodes.literal_block): return 'code-block' else: @@ -813,6 +814,28 @@ class StandardDomain(Domain): figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None)) return figtype + def get_fignumber(self, env, builder, figtype, docname, target_node): + if figtype == 'section': + if builder.name == 'latex': + return tuple() + elif docname not in env.toc_secnumbers: + raise ValueError # no number assigned + else: + anchorname = '#' + target_node['ids'][0] + if anchorname not in env.toc_secnumbers[docname]: + # try first heading which has no anchor + return env.toc_secnumbers[docname].get('') + else: + return env.toc_secnumbers[docname].get(anchorname) + else: + try: + figure_id = target_node['ids'][0] + return env.toc_fignumbers[docname][figtype][figure_id] + except (KeyError, IndexError): + # target_node is found, but fignumber is not assigned. + # Maybe it is defined in orphaned document. + raise ValueError + def setup(app): app.add_domain(StandardDomain) diff --git a/tests/roots/test-numfig/bar.rst b/tests/roots/test-numfig/bar.rst index f86e7475a..c4367c5b8 100644 --- a/tests/roots/test-numfig/bar.rst +++ b/tests/roots/test-numfig/bar.rst @@ -1,7 +1,11 @@ +.. _bar: + === Bar === +.. _bar_a: + Bar A ===== @@ -37,9 +41,13 @@ Bar A print('hello world') +.. _bar_b: + Bar B ===== +.. _bar_b1: + Bar B1 ------ diff --git a/tests/roots/test-numfig/baz.rst b/tests/roots/test-numfig/baz.rst index 42fcb06d1..3ac684b43 100644 --- a/tests/roots/test-numfig/baz.rst +++ b/tests/roots/test-numfig/baz.rst @@ -1,3 +1,5 @@ +.. _baz_a: + Baz A ----- diff --git a/tests/roots/test-numfig/foo.rst b/tests/roots/test-numfig/foo.rst index ef713574a..6b6a8651c 100644 --- a/tests/roots/test-numfig/foo.rst +++ b/tests/roots/test-numfig/foo.rst @@ -1,3 +1,5 @@ +.. _foo: + === Foo === @@ -16,6 +18,8 @@ Foo print('hello world') +.. _foo_a: + Foo A ===== @@ -47,12 +51,18 @@ Foo A print('hello world') +.. _foo_a1: + Foo A1 ------ +.. _foo_b: + Foo B ===== +.. _foo_b1: + Foo B1 ------ diff --git a/tests/roots/test-numfig/index.rst b/tests/roots/test-numfig/index.rst index 6dd39a93a..afd91c296 100644 --- a/tests/roots/test-numfig/index.rst +++ b/tests/roots/test-numfig/index.rst @@ -1,3 +1,5 @@ +.. _index: + test-tocdepth ============= @@ -48,5 +50,8 @@ test-tocdepth * Table.2.2 is :numref:`Table:%s ` * List.1 is :numref:`CODE_1` * List.2.2 is :numref:`Code-%s ` +* Section.1 is :numref:`foo` +* Section.2.1 is :numref:`bar_a` +* Unnumbered section is :numref:`index` * Invalid numfig_format 01: :numref:`invalid ` * Invalid numfig_format 02: :numref:`Fig %s %s ` diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 426776803..4c6bc673e 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -503,10 +503,11 @@ def test_tocdepth_singlehtml(app, status, warning): def test_numfig_disabled(app, status, warning): app.builder.build_all() - assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' - in warning.getvalue()) - assert 'index.rst:51: WARNING: invalid numfig_format: invalid' not in warning.getvalue() - assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' not in warning.getvalue() + warnings = warning.getvalue() + assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' in warnings + assert 'index.rst:55: WARNING: no number is assigned for section: index' not in warnings + assert 'index.rst:56: WARNING: invalid numfig_format: invalid' not in warnings + assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' not in warnings expects = { 'index.html': [ @@ -521,6 +522,8 @@ def test_numfig_disabled(app, status, warning): (".//li/code/span", '^Table:%s$', True), (".//li/code/span", '^CODE_1$', True), (".//li/code/span", '^Code-%s$', True), + (".//li/code/span", '^foo$', True), + (".//li/code/span", '^bar_a$', True), ], 'foo.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -562,10 +565,11 @@ def test_numfig_without_numbered_toctree(app, status, warning): (app.srcdir / 'index.rst').write_text(index, encoding='utf-8') app.builder.build_all() - assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' - not in warning.getvalue()) - assert 'index.rst:51: WARNING: invalid numfig_format: invalid' in warning.getvalue() - assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' in warning.getvalue() + warnings = warning.getvalue() + assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings + assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings + assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings + assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings expects = { 'index.html': [ @@ -587,6 +591,8 @@ def test_numfig_without_numbered_toctree(app, status, warning): (".//li/a/span", '^Table:6$', True), (".//li/a/span", '^Listing 9$', True), (".//li/a/span", '^Code-6$', True), + (".//li/code/span", '^foo$', True), + (".//li/code/span", '^bar_a$', True), ], 'foo.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -657,10 +663,11 @@ def test_numfig_without_numbered_toctree(app, status, warning): def test_numfig_with_numbered_toctree(app, status, warning): app.builder.build_all() - assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' - not in warning.getvalue()) - assert 'index.rst:51: WARNING: invalid numfig_format: invalid' in warning.getvalue() - assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' in warning.getvalue() + warnings = warning.getvalue() + assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings + assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings + assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings + assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings expects = { 'index.html': [ @@ -682,6 +689,8 @@ def test_numfig_with_numbered_toctree(app, status, warning): (".//li/a/span", '^Table:2.2$', True), (".//li/a/span", '^Listing 1$', True), (".//li/a/span", '^Code-2.2$', True), + (".//li/a/span", '^Section.1$', True), + (".//li/a/span", '^Section.2.1$', True), ], 'foo.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -751,14 +760,16 @@ def test_numfig_with_numbered_toctree(app, status, warning): confoverrides={'numfig': True, 'numfig_format': {'figure': 'Figure:%s', 'table': 'Tab_%s', - 'code-block': 'Code-%s'}}) + 'code-block': 'Code-%s', + 'section': 'SECTION-%s'}}) def test_numfig_with_prefix(app, status, warning): app.builder.build_all() - assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' - not in warning.getvalue()) - assert 'index.rst:51: WARNING: invalid numfig_format: invalid' in warning.getvalue() - assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' in warning.getvalue() + warnings = warning.getvalue() + assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings + assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings + assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings + assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings expects = { 'index.html': [ @@ -780,6 +791,8 @@ def test_numfig_with_prefix(app, status, warning): (".//li/a/span", '^Table:2.2$', True), (".//li/a/span", '^Code-1$', True), (".//li/a/span", '^Code-2.2$', True), + (".//li/a/span", '^SECTION-1$', True), + (".//li/a/span", '^SECTION-2.1$', True), ], 'foo.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -850,10 +863,11 @@ def test_numfig_with_prefix(app, status, warning): def test_numfig_with_secnum_depth(app, status, warning): app.builder.build_all() - assert ('index.rst:45: WARNING: numfig is disabled. :numref: is ignored.' - not in warning.getvalue()) - assert 'index.rst:51: WARNING: invalid numfig_format: invalid' in warning.getvalue() - assert 'index.rst:52: WARNING: invalid numfig_format: Fig %s %s' in warning.getvalue() + warnings = warning.getvalue() + assert 'index.rst:47: WARNING: numfig is disabled. :numref: is ignored.' not in warnings + assert 'index.rst:55: WARNING: no number is assigned for section: index' in warnings + assert 'index.rst:56: WARNING: invalid numfig_format: invalid' in warnings + assert 'index.rst:57: WARNING: invalid numfig_format: Fig %s %s' in warnings expects = { 'index.html': [ @@ -875,6 +889,8 @@ def test_numfig_with_secnum_depth(app, status, warning): (".//li/a/span", '^Table:2.1.2$', True), (".//li/a/span", '^Listing 1$', True), (".//li/a/span", '^Code-2.1.2$', True), + (".//li/a/span", '^Section.1$', True), + (".//li/a/span", '^Section.2.1$', True), ], 'foo.html': [ (".//div[@class='figure']/p[@class='caption']/" @@ -965,6 +981,8 @@ def test_numfig_with_singlehtml(app, status, warning): (".//li/a/span", '^Table:2.2$', True), (".//li/a/span", '^Listing 1$', True), (".//li/a/span", '^Code-2.2$', True), + (".//li/a/span", '^Section.1$', True), + (".//li/a/span", '^Section.2.1$', True), (".//div[@class='figure']/p[@class='caption']/" "span[@class='caption-number']", '^Fig. 1.1 $', True), (".//div[@class='figure']/p[@class='caption']/" diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 54ba5811b..1a25676aa 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -151,13 +151,16 @@ def test_numref(app, status, warning): assert '\\hyperref[baz:table22]{Table:\\ref{baz:table22}}' in result assert '\\hyperref[index:code-1]{Listing \\ref{index:code-1}}' in result assert '\\hyperref[baz:code22]{Code-\\ref{baz:code22}}' in result + assert '\\hyperref[foo:foo]{Section \\ref{foo:foo}}' in result + assert '\\hyperref[bar:bar-a]{Section \\ref{bar:bar-a}}' in result @with_app(buildername='latex', testroot='numfig', confoverrides={'numfig': True, 'numfig_format': {'figure': 'Figure:%s', 'table': 'Tab_%s', - 'code-block': 'Code-%s'}}) + 'code-block': 'Code-%s', + 'section': 'SECTION-%s'}}) def test_numref_with_prefix1(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -179,13 +182,16 @@ def test_numref_with_prefix1(app, status, warning): assert '\\hyperref[baz:table22]{Table:\\ref{baz:table22}}' in result assert '\\hyperref[index:code-1]{Code-\\ref{index:code-1}}' in result assert '\\hyperref[baz:code22]{Code-\\ref{baz:code22}}' in result + assert '\\hyperref[foo:foo]{SECTION-\\ref{foo:foo}}' in result + assert '\\hyperref[bar:bar-a]{SECTION-\\ref{bar:bar-a}}' in result @with_app(buildername='latex', testroot='numfig', confoverrides={'numfig': True, 'numfig_format': {'figure': 'Figure:%s.', 'table': 'Tab_%s:', - 'code-block': 'Code-%s | '}}) + 'code-block': 'Code-%s | ', + 'section': 'SECTION_%s_'}}) def test_numref_with_prefix2(app, status, warning): app.builder.build_all() result = (app.outdir / 'Python.tex').text(encoding='utf8') @@ -203,6 +209,8 @@ def test_numref_with_prefix2(app, status, warning): assert '\\hyperref[baz:table22]{Table:\\ref{baz:table22}}' in result assert '\\hyperref[index:code-1]{Code-\\ref{index:code-1} \\textbar{} }' in result assert '\\hyperref[baz:code22]{Code-\\ref{baz:code22}}' in result + assert '\\hyperref[foo:foo]{SECTION\\_\\ref{foo:foo}\\_}' in result + assert '\\hyperref[bar:bar-a]{SECTION\\_\\ref{bar:bar-a}\\_}' in result @with_app(buildername='latex', testroot='numfig', @@ -222,6 +230,8 @@ def test_numref_with_language_ja(app, status, warning): assert '\\hyperref[baz:table22]{Table:\\ref{baz:table22}}' in result assert '\\hyperref[index:code-1]{LIST \\ref{index:code-1}}' in result assert '\\hyperref[baz:code22]{Code-\\ref{baz:code22}}' in result + assert '\\hyperref[foo:foo]{Section \\ref{foo:foo}}' in result + assert '\\hyperref[bar:bar-a]{Section \\ref{bar:bar-a}}' in result @with_app(buildername='latex')