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,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)
|