rst-table-span 1.0.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: rst-table-span
3
+ Version: 1.0.0
4
+ Summary: Support for colspan and rowspan in RST tables for docutils and Sphinx
5
+ Author-email: Rasmus Bondesson <raek@raek.se>
6
+ Description-Content-Type: text/x-rst
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Dist: docutils
10
+ Project-URL: Home, https://github.com/raek/rst-table-span/
11
+
12
+ **************
13
+ rst-table-span
14
+ **************
15
+
16
+ reStructuredText provides four syntaxes for tables:
17
+
18
+ * Grid tables
19
+ * Simple tables
20
+ * List tables
21
+ * CSV tables
22
+
23
+ However, only grid and simple tables support cells spanning multiple columns, and only
24
+ grid tables support cells spanning multiple rows.
25
+
26
+ The ``rst-table-span`` package adds the ``:colspan:`C```, ``:rowspan:`R```, and
27
+ ``:cellspan:`CxR``` roles which add support for column and row span *to all
28
+ existing RST table types*.
29
+
30
+ This project was inspired by the ``flat-table`` directive from the LinuxDoc_ project.
31
+
32
+ .. _LinuxDoc: https://return42.github.io/linuxdoc/linuxdoc-howto/table-markup.html#flat-table
33
+
34
+
35
+ Example
36
+ =======
37
+
38
+ To produce a table like this:
39
+
40
+ .. table::
41
+
42
+ +------------------------+------------+----------+----------+
43
+ | Header row, column 1 | Header 2 | Header 3 | Header 4 |
44
+ | (header rows optional) | | | |
45
+ +========================+============+==========+==========+
46
+ | body row 1, column 1 | column 2 | column 3 | column 4 |
47
+ +------------------------+------------+----------+----------+
48
+ | body row 2 | Cells may span columns. |
49
+ +------------------------+------------+---------------------+
50
+ | body row 3 | Cells may | Corner |
51
+ +------------------------+ span rows. | |
52
+ | body row 4 | | |
53
+ +------------------------+------------+---------------------+
54
+
55
+ You would normally need to write this::
56
+
57
+ +------------------------+------------+----------+----------+
58
+ | Header row, column 1 | Header 2 | Header 3 | Header 4 |
59
+ | (header rows optional) | | | |
60
+ +========================+============+==========+==========+
61
+ | body row 1, column 1 | column 2 | column 3 | column 4 |
62
+ +------------------------+------------+----------+----------+
63
+ | body row 2 | Cells may span columns. |
64
+ +------------------------+------------+---------------------+
65
+ | body row 3 | Cells may | Corner |
66
+ +------------------------+ span rows. | |
67
+ | body row 4 | | |
68
+ +------------------------+------------+---------------------+
69
+
70
+ But with ``rst-table-span`` you can instead write it using a ``csv-table``::
71
+
72
+ .. csv-table::
73
+ :header-rows: 1
74
+
75
+ "Header row, column 1 (header rows optional)", Header 2, Header 3, Header 4
76
+ "body row 1, column 1", column 2, column 3, column 4
77
+ body row 2, :colspan:`3` Cells may span columns.
78
+ body row 3, :rowspan:`2` Cells may span rows., :cellspan:`2x2` Corner
79
+ body row 4
80
+
81
+ Or a ``list-table``::
82
+
83
+ .. list-table::
84
+ :header-rows: 1
85
+
86
+ * - Header row, column 1 (header rows optional)
87
+ - Header 2
88
+ - Header 3
89
+ - Header 4
90
+ * - body row 1, column 1
91
+ - column 2
92
+ - column 3
93
+ - column 4
94
+ * - body row 2
95
+ - :colspan:`3` Cells may span columns.
96
+ -
97
+ -
98
+ * - body row 3
99
+ - :rowspan:`2` Cells may span rows.
100
+ - :cellspan:`2x2` Corner
101
+ -
102
+ * - body row 4
103
+ -
104
+ -
105
+ -
106
+
107
+
108
+ Installation and Usage
109
+ ======================
110
+
111
+ Install the package using pip::
112
+
113
+ pip install rst-table-span
114
+
115
+ or add the ``rst-table-span`` package to your project or environment dependency
116
+ list (eg. ``pyproject.toml``, ``requirements.txt``, or similar).
117
+
118
+
119
+ Sphinx
120
+ ------
121
+
122
+ Also add the ``rst_table_span`` module to your list of extensions in your
123
+ ``conf.py`` settings::
124
+
125
+ extensions = [
126
+ "rst_table_span",
127
+ ]
128
+
129
+
130
+ Docutils
131
+ --------
132
+
133
+ .. _Publisher API: https://docutils.sourceforge.io/docs/api/publisher.html
134
+
135
+ Docutils doesn't provide a declarative extension mechanism, as far as I know. If
136
+ you invoke docutils programatically using its `Publisher API`_, then a global
137
+ function is provided to register the roles. Call it before setting up the publisher::
138
+
139
+ import sys
140
+ import docutils.__main__
141
+ import rst_table_span
142
+
143
+ rst_table_span.register_docutils_roles()
144
+ sys.exit(docutils.__main__.main())
145
+
146
+ For convenience, this package installs the above script as the
147
+ ``rst-table-span-docutils`` executable. It behaves the same way as the standard
148
+ ``docutils`` executable. It can then be used used like this::
149
+
150
+ rst-table-span-docutils demo.rst --output demo.html
151
+
152
+
153
+ License
154
+ =======
155
+
156
+ This library is released under the **MIT license** (see the ``LICENSE`` file in
157
+ the source code).
158
+
@@ -0,0 +1,6 @@
1
+ rst_table_span.py,sha256=WMsZwcn2sacCdfd965PugW0HK-fnkEAf9IwfcYqo_i0,4514
2
+ rst_table_span-1.0.0.dist-info/entry_points.txt,sha256=PNjJhz6QNvj8ht8DkbRRNphzCXvQ0i3aOqAHBzpKu2U,63
3
+ rst_table_span-1.0.0.dist-info/licenses/LICENSE,sha256=5ksw4CA6YRYGt6P84r-wnq67GOhydQm9NHvVtOJ1MMQ,1083
4
+ rst_table_span-1.0.0.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
5
+ rst_table_span-1.0.0.dist-info/METADATA,sha256=bDw_Kqj0m2ic_KnyjtTELA0N_LqFZiS_6vxDycrTRHE,4889
6
+ rst_table_span-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.12.0
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ rst-table-span-docutils=rst_table_span:main
3
+
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Rasmus Bondesson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
rst_table_span.py ADDED
@@ -0,0 +1,127 @@
1
+ """Support for colspan and rowspan in RST tables for docutils and Sphinx"""
2
+
3
+ __version__ = "1.0.0"
4
+
5
+
6
+ import re
7
+
8
+ import docutils.nodes as nodes
9
+ from docutils.transforms import Transform, TransformError
10
+
11
+
12
+ def col_span_role(role, rawtext, text, lineno, inliner, options=None, content=None):
13
+ if not text.isnumeric() or int(text) < 1:
14
+ return None, [inliner.reporter.error("colspan must be a positive integer", line=lineno)]
15
+ return make_pending_nodes(inliner, int(text), 1)
16
+
17
+
18
+ def row_span_role(role, rawtext, text, lineno, inliner, options=None, content=None):
19
+ if not text.isnumeric() or int(text) < 1:
20
+ return None, [inliner.reporter.error("colspan must be a positive integer", line=lineno)]
21
+ return make_pending_nodes(inliner, 1, int(text))
22
+
23
+
24
+ def cell_span_role(role, rawtext, text, lineno, inliner, options=None, content=None):
25
+ m = re.match(r"(?P<cols>[1-9][0-9]*)x(?P<rows>[1-9][0-9]*)", text)
26
+ if not m:
27
+ return None, [inliner.reporter.error("cellspan must be of format <COLS>x<ROWS>", line=lineno)]
28
+ return make_pending_nodes(inliner, int(m.group("cols")), int(m.group("rows")))
29
+
30
+
31
+ def make_pending_nodes(inliner, cols, rows):
32
+ details = dict(cols=cols, rows=rows)
33
+ mark = nodes.pending(CellSpanMarkTransform, details)
34
+ inliner.document.note_pending(mark)
35
+ sweep = nodes.pending(CellSpanSweepTransform)
36
+ inliner.document.note_pending(sweep)
37
+ return [mark, sweep], []
38
+
39
+
40
+ class CellSpanMarkTransform(Transform):
41
+ default_priority = 213
42
+
43
+ def apply(self):
44
+ col_span = self.startnode.details["cols"]
45
+ row_span = self.startnode.details["rows"]
46
+ cell_node = pop_pending_and_find_cell(self.startnode)
47
+ row_node = cell_node.parent
48
+ table_node = row_node.parent
49
+ col_index = row_node.children.index(cell_node)
50
+ row_index = table_node.children.index(row_node)
51
+ if col_index + col_span > len(row_node.children):
52
+ raise TransformError("colspan extending outside table")
53
+ if row_index + row_span > len(table_node.children):
54
+ raise TransformError("colspan extending outside table")
55
+
56
+ table_node["needs_sweep"] = True
57
+ for i in range(row_span):
58
+ loop_row = table_node.children[row_index + i]
59
+ for j in range(col_span):
60
+ loop_cell = loop_row.children[col_index + j]
61
+ if i == 0 and j == 0:
62
+ # The top left cell of the span rectangle contains the
63
+ # contents. Keep it and set its "more" attributes.
64
+ loop_cell["morecols"] = col_span - 1
65
+ loop_cell["morerows"] = row_span - 1
66
+ else:
67
+ # Other cells in the span rectangle shall be removed.
68
+ loop_cell["sweep"] = True
69
+
70
+
71
+ class CellSpanSweepTransform(Transform):
72
+ default_priority = 214
73
+
74
+ def apply(self):
75
+ cell_node = pop_pending_and_find_cell(self.startnode)
76
+ row_node = cell_node.parent
77
+ table_node = row_node.parent
78
+
79
+ if not table_node.attributes.get("needs_sweep", False):
80
+ return
81
+ for row in table_node.children:
82
+ to_sweep = [entry for entry in row.children
83
+ if entry.attributes.get("sweep", False)]
84
+ for entry in to_sweep:
85
+ row.children.remove(entry)
86
+ del table_node.attributes["needs_sweep"]
87
+
88
+
89
+ def pop_pending_and_find_cell(pending):
90
+ node = pending.parent
91
+ pending.parent.remove(pending)
92
+ while True:
93
+ if node is None:
94
+ raise TransformError("colspan, rowspan, and cellspan roles must be used in table cells")
95
+ elif isinstance(node, nodes.entry):
96
+ break
97
+ else:
98
+ node = node.parent
99
+ return node
100
+
101
+
102
+ # Entrypoint for the Sphinx extension mechanism
103
+ def setup(app):
104
+ app.add_role("colspan", col_span_role)
105
+ app.add_role("rowspan", row_span_role)
106
+ app.add_role("cellspan", cell_span_role)
107
+
108
+ return {
109
+ "version": __version__,
110
+ "parallel_read_safe": True,
111
+ "parallel_write_safe": True,
112
+ }
113
+
114
+
115
+ # Entrypoint for docutils-style command line utility
116
+ def main():
117
+ import sys
118
+ from docutils.__main__ import main
119
+ register_docutils_roles()
120
+ sys.exit(main())
121
+
122
+
123
+ def register_docutils_roles():
124
+ from docutils.parsers.rst import roles
125
+ roles.register_canonical_role("colspan", col_span_role)
126
+ roles.register_canonical_role("rowspan", row_span_role)
127
+ roles.register_canonical_role("cellspan", cell_span_role)