|
| 1 | +"""Tests the productionlist directive.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import pytest |
| 6 | +from docutils import nodes |
| 7 | + |
| 8 | +from sphinx.addnodes import pending_xref |
| 9 | +from sphinx.testing import restructuredtext |
| 10 | +from sphinx.testing.util import assert_node, etree_parse |
| 11 | + |
| 12 | +TYPE_CHECKING = False |
| 13 | +if TYPE_CHECKING: |
| 14 | + from collections.abc import Callable |
| 15 | + from pathlib import Path |
| 16 | + |
| 17 | + from sphinx.testing.util import SphinxTestApp |
| 18 | + |
| 19 | + |
| 20 | +@pytest.mark.sphinx('html', testroot='productionlist') |
| 21 | +def test_productionlist(app: SphinxTestApp) -> None: |
| 22 | + app.build(force_all=True) |
| 23 | + |
| 24 | + warnings = app.warning.getvalue().split('\n') |
| 25 | + assert len(warnings) == 2 |
| 26 | + assert warnings[-1] == '' |
| 27 | + assert ( |
| 28 | + 'Dup2.rst:4: WARNING: duplicate token description of Dup, other instance in Dup1' |
| 29 | + in warnings[0] |
| 30 | + ) |
| 31 | + |
| 32 | + etree = etree_parse(app.outdir / 'index.html') |
| 33 | + nodes = list(etree.iter('ul')) |
| 34 | + assert len(nodes) >= 3 |
| 35 | + |
| 36 | + ul = nodes[2] |
| 37 | + cases = [] |
| 38 | + for li in list(ul): |
| 39 | + li_list = list(li) |
| 40 | + assert len(li_list) == 1 |
| 41 | + p = li_list[0] |
| 42 | + assert p.tag == 'p' |
| 43 | + text = str(p.text).strip(' :') |
| 44 | + p_list = list(p) |
| 45 | + assert len(p_list) == 1 |
| 46 | + a = p_list[0] |
| 47 | + assert a.tag == 'a' |
| 48 | + link = a.get('href') |
| 49 | + a_list = list(a) |
| 50 | + assert len(a_list) == 1 |
| 51 | + code = a_list[0] |
| 52 | + assert code.tag == 'code' |
| 53 | + code_list = list(code) |
| 54 | + assert len(code_list) == 1 |
| 55 | + span = code_list[0] |
| 56 | + assert span.tag == 'span' |
| 57 | + assert span.text is not None |
| 58 | + link_text = span.text.strip() |
| 59 | + cases.append((text, link, link_text)) |
| 60 | + assert cases == [ |
| 61 | + ('A', 'Bare.html#grammar-token-A', 'A'), |
| 62 | + ('B', 'Bare.html#grammar-token-B', 'B'), |
| 63 | + ('P1:A', 'P1.html#grammar-token-P1-A', 'P1:A'), |
| 64 | + ('P1:B', 'P1.html#grammar-token-P1-B', 'P1:B'), |
| 65 | + ('P2:A', 'P1.html#grammar-token-P1-A', 'P1:A'), |
| 66 | + ('P2:B', 'P2.html#grammar-token-P2-B', 'P2:B'), |
| 67 | + ('Explicit title A, plain', 'Bare.html#grammar-token-A', 'MyTitle'), |
| 68 | + ('Explicit title A, colon', 'Bare.html#grammar-token-A', 'My:Title'), |
| 69 | + ('Explicit title P1:A, plain', 'P1.html#grammar-token-P1-A', 'MyTitle'), |
| 70 | + ('Explicit title P1:A, colon', 'P1.html#grammar-token-P1-A', 'My:Title'), |
| 71 | + ('Tilde A', 'Bare.html#grammar-token-A', 'A'), |
| 72 | + ('Tilde P1:A', 'P1.html#grammar-token-P1-A', 'A'), |
| 73 | + ('Tilde explicit title P1:A', 'P1.html#grammar-token-P1-A', '~MyTitle'), |
| 74 | + ('Tilde, explicit title P1:A', 'P1.html#grammar-token-P1-A', 'MyTitle'), |
| 75 | + ('Dup', 'Dup2.html#grammar-token-Dup', 'Dup'), |
| 76 | + ('FirstLine', 'firstLineRule.html#grammar-token-FirstLine', 'FirstLine'), |
| 77 | + ('SecondLine', 'firstLineRule.html#grammar-token-SecondLine', 'SecondLine'), |
| 78 | + ] |
| 79 | + |
| 80 | + text = (app.outdir / 'LineContinuation.html').read_text(encoding='utf8') |
| 81 | + assert 'A</strong> ::= B C D E F G' in text |
| 82 | + |
| 83 | + |
| 84 | +@pytest.mark.sphinx('html', testroot='root') |
| 85 | +def test_productionlist_xref(app: SphinxTestApp) -> None: |
| 86 | + text = """\ |
| 87 | +.. productionlist:: P2 |
| 88 | + A: `:A` `A` |
| 89 | + B: `P1:B` `~P1:B` |
| 90 | +""" |
| 91 | + doctree = restructuredtext.parse(app, text) |
| 92 | + refnodes = list(doctree.findall(pending_xref)) |
| 93 | + assert_node(refnodes[0], pending_xref, reftarget='A') |
| 94 | + assert_node(refnodes[1], pending_xref, reftarget='P2:A') |
| 95 | + assert_node(refnodes[2], pending_xref, reftarget='P1:B') |
| 96 | + assert_node(refnodes[3], pending_xref, reftarget='P1:B') |
| 97 | + assert_node(refnodes[0], [pending_xref, nodes.literal, 'A']) |
| 98 | + assert_node(refnodes[1], [pending_xref, nodes.literal, 'A']) |
| 99 | + assert_node(refnodes[2], [pending_xref, nodes.literal, 'P1:B']) |
| 100 | + assert_node(refnodes[3], [pending_xref, nodes.literal, 'B']) |
| 101 | + |
| 102 | + |
| 103 | +def test_productionlist_continuation_lines( |
| 104 | + make_app: Callable[..., SphinxTestApp], tmp_path: Path |
| 105 | +) -> None: |
| 106 | + text = """ |
| 107 | +.. productionlist:: python-grammar |
| 108 | + assignment_stmt: (`target_list` "=")+ (`starred_expression` | `yield_expression`) |
| 109 | + target_list: `target` ("," `target`)* [","] |
| 110 | + target: `identifier` |
| 111 | + : | "(" [`target_list`] ")" |
| 112 | + : | "[" [`target_list`] "]" |
| 113 | + : | `attributeref` |
| 114 | + : | `subscription` |
| 115 | + : | `slicing` |
| 116 | + : | "*" `target` |
| 117 | +""" |
| 118 | + (tmp_path / 'conf.py').touch() |
| 119 | + (tmp_path / 'index.rst').write_text(text, encoding='utf-8') |
| 120 | + |
| 121 | + app = make_app(buildername='text', srcdir=tmp_path) |
| 122 | + app.build(force_all=True) |
| 123 | + content = (app.outdir / 'index.txt').read_text(encoding='utf-8') |
| 124 | + expected = """\ |
| 125 | + assignment_stmt ::= (target_list "=")+ (starred_expression | yield_expression) |
| 126 | + target_list ::= target ("," target)* [","] |
| 127 | + target ::= identifier |
| 128 | + | "(" [target_list] ")" |
| 129 | + | "[" [target_list] "]" |
| 130 | + | attributeref |
| 131 | + | subscription |
| 132 | + | slicing |
| 133 | + | "*" target |
| 134 | +""" |
| 135 | + assert content == expected |
| 136 | + |
| 137 | + app = make_app(buildername='html', srcdir=tmp_path) |
| 138 | + app.build(force_all=True) |
| 139 | + content = (app.outdir / 'index.html').read_text(encoding='utf-8') |
| 140 | + _, _, content = content.partition('<pre>') |
| 141 | + content, _, _ = content.partition('</pre>') |
| 142 | + expected = """ |
| 143 | +<strong id="grammar-token-python-grammar-assignment_stmt">assignment_stmt</strong> ::= (<a class="reference internal" href="#grammar-token-python-grammar-target_list"><code class="xref docutils literal notranslate"><span class="pre">target_list</span></code></a> "=")+ (<code class="xref docutils literal notranslate"><span class="pre">starred_expression</span></code> | <code class="xref docutils literal notranslate"><span class="pre">yield_expression</span></code>) |
| 144 | +<strong id="grammar-token-python-grammar-target_list">target_list </strong> ::= <a class="reference internal" href="#grammar-token-python-grammar-target"><code class="xref docutils literal notranslate"><span class="pre">target</span></code></a> ("," <a class="reference internal" href="#grammar-token-python-grammar-target"><code class="xref docutils literal notranslate"><span class="pre">target</span></code></a>)* [","] |
| 145 | +<strong id="grammar-token-python-grammar-target">target </strong> ::= <code class="xref docutils literal notranslate"><span class="pre">identifier</span></code> |
| 146 | + | "(" [<a class="reference internal" href="#grammar-token-python-grammar-target_list"><code class="xref docutils literal notranslate"><span class="pre">target_list</span></code></a>] ")" |
| 147 | + | "[" [<a class="reference internal" href="#grammar-token-python-grammar-target_list"><code class="xref docutils literal notranslate"><span class="pre">target_list</span></code></a>] "]" |
| 148 | + | <code class="xref docutils literal notranslate"><span class="pre">attributeref</span></code> |
| 149 | + | <code class="xref docutils literal notranslate"><span class="pre">subscription</span></code> |
| 150 | + | <code class="xref docutils literal notranslate"><span class="pre">slicing</span></code> |
| 151 | + | "*" <a class="reference internal" href="#grammar-token-python-grammar-target"><code class="xref docutils literal notranslate"><span class="pre">target</span></code></a> |
| 152 | +""" |
| 153 | + assert content == expected |
0 commit comments