Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

doc-rst: flat-table directive - initial implementation

Implements the reST flat-table directive.

The ``flat-table`` is a double-stage list similar to the ``list-table`` with
some additional features:

* column-span: with the role ``cspan`` a cell can be extended through
additional columns

* row-span: with the role ``rspan`` a cell can be extended through
additional rows

* auto span rightmost cell of a table row over the missing cells on the right
side of that table-row. With Option ``:fill-cells:`` this behavior can
changed from *auto span* to *auto fill*, which automaticly inserts (empty)

list tables

The *list tables* formats are double stage lists. Compared to the
ASCII-art they migth be less comfortable for readers of the
text-files. Their advantage is, that they are easy to create/modify
and that the diff of a modification is much more meaningfull, because
it is limited to the modified content.

The initial implementation was taken from the sphkerneldoc project [1]

[1] https://github.com/return42/sphkerneldoc/commits/master/scripts/site-python/linuxdoc/rstFlatTable.py

Signed-off-by: Markus Heiser <markus.heiser@darmarIT.de>
[jc: fixed typos and misspellings in the docs]
Signed-off-by: Jonathan Corbet <corbet@lwn.net>

authored by

Markus Heiser and committed by
Jonathan Corbet
0249a764 17defc28

+452 -1
+1 -1
Documentation/conf.py
··· 28 28 # Add any Sphinx extension module names here, as strings. They can be 29 29 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 30 # ones. 31 - extensions = ['kernel-doc'] 31 + extensions = ['kernel-doc', 'rstFlatTable'] 32 32 33 33 # Gracefully handle missing rst2pdf. 34 34 try:
+86
Documentation/kernel-documentation.rst
··· 107 107 the order as encountered."), having the higher levels the same overall makes 108 108 it easier to follow the documents. 109 109 110 + list tables 111 + ----------- 112 + 113 + We recommend the use of *list table* formats. The *list table* formats are 114 + double-stage lists. Compared to the ASCII-art they might not be as 115 + comfortable for 116 + readers of the text files. Their advantage is that they are easy to 117 + create or modify and that the diff of a modification is much more meaningful, 118 + because it is limited to the modified content. 119 + 120 + The ``flat-table`` is a double-stage list similar to the ``list-table`` with 121 + some additional features: 122 + 123 + * column-span: with the role ``cspan`` a cell can be extended through 124 + additional columns 125 + 126 + * row-span: with the role ``rspan`` a cell can be extended through 127 + additional rows 128 + 129 + * auto span rightmost cell of a table row over the missing cells on the right 130 + side of that table-row. With Option ``:fill-cells:`` this behavior can 131 + changed from *auto span* to *auto fill*, which automatically inserts (empty) 132 + cells instead of spanning the last cell. 133 + 134 + options: 135 + 136 + * ``:header-rows:`` [int] count of header rows 137 + * ``:stub-columns:`` [int] count of stub columns 138 + * ``:widths:`` [[int] [int] ... ] widths of columns 139 + * ``:fill-cells:`` instead of auto-spanning missing cells, insert missing cells 140 + 141 + roles: 142 + 143 + * ``:cspan:`` [int] additional columns (*morecols*) 144 + * ``:rspan:`` [int] additional rows (*morerows*) 145 + 146 + The example below shows how to use this markup. The first level of the staged 147 + list is the *table-row*. In the *table-row* there is only one markup allowed, 148 + the list of the cells in this *table-row*. Exceptions are *comments* ( ``..`` ) 149 + and *targets* (e.g. a ref to ``:ref:`last row <last row>``` / :ref:`last row 150 + <last row>`). 151 + 152 + .. code-block:: rst 153 + 154 + .. flat-table:: table title 155 + :widths: 2 1 1 3 156 + 157 + * - head col 1 158 + - head col 2 159 + - head col 3 160 + - head col 4 161 + 162 + * - column 1 163 + - field 1.1 164 + - field 1.2 with autospan 165 + 166 + * - column 2 167 + - field 2.1 168 + - :rspan:`1` :cspan:`1` field 2.2 - 3.3 169 + 170 + * .. _`last row`: 171 + 172 + - column 3 173 + 174 + Rendered as: 175 + 176 + .. flat-table:: table title 177 + :widths: 2 1 1 3 178 + 179 + * - head col 1 180 + - head col 2 181 + - head col 3 182 + - head col 4 183 + 184 + * - column 1 185 + - field 1.1 186 + - field 1.2 with autospan 187 + 188 + * - column 2 189 + - field 2.1 190 + - :rspan:`1` :cspan:`1` field 2.2 - 3.3 191 + 192 + * .. _`last row`: 193 + 194 + - column 3 195 + 110 196 111 197 Including kernel-doc comments 112 198 =============================
+365
Documentation/sphinx/rstFlatTable.py
··· 1 + #!/usr/bin/env python3 2 + # -*- coding: utf-8; mode: python -*- 3 + # pylint: disable=C0330, R0903, R0912 4 + 5 + u""" 6 + flat-table 7 + ~~~~~~~~~~ 8 + 9 + Implementation of the ``flat-table`` reST-directive. 10 + 11 + :copyright: Copyright (C) 2016 Markus Heiser 12 + :license: GPL Version 2, June 1991 see linux/COPYING for details. 13 + 14 + The ``flat-table`` (:py:class:`FlatTable`) is a double-stage list similar to 15 + the ``list-table`` with some additional features: 16 + 17 + * *column-span*: with the role ``cspan`` a cell can be extended through 18 + additional columns 19 + 20 + * *row-span*: with the role ``rspan`` a cell can be extended through 21 + additional rows 22 + 23 + * *auto span* rightmost cell of a table row over the missing cells on the 24 + right side of that table-row. With Option ``:fill-cells:`` this behavior 25 + can changed from *auto span* to *auto fill*, which automaticly inserts 26 + (empty) cells instead of spanning the last cell. 27 + 28 + Options: 29 + 30 + * header-rows: [int] count of header rows 31 + * stub-columns: [int] count of stub columns 32 + * widths: [[int] [int] ... ] widths of columns 33 + * fill-cells: instead of autospann missing cells, insert missing cells 34 + 35 + roles: 36 + 37 + * cspan: [int] additionale columns (*morecols*) 38 + * rspan: [int] additionale rows (*morerows*) 39 + """ 40 + 41 + # ============================================================================== 42 + # imports 43 + # ============================================================================== 44 + 45 + import sys 46 + 47 + from docutils import nodes 48 + from docutils.parsers.rst import directives, roles 49 + from docutils.parsers.rst.directives.tables import Table 50 + from docutils.utils import SystemMessagePropagation 51 + 52 + # ============================================================================== 53 + # common globals 54 + # ============================================================================== 55 + 56 + # The version numbering follows numbering of the specification 57 + # (Documentation/books/kernel-doc-HOWTO). 58 + __version__ = '1.0' 59 + 60 + PY3 = sys.version_info[0] == 3 61 + PY2 = sys.version_info[0] == 2 62 + 63 + if PY3: 64 + # pylint: disable=C0103, W0622 65 + unicode = str 66 + basestring = str 67 + 68 + # ============================================================================== 69 + def setup(app): 70 + # ============================================================================== 71 + 72 + app.add_directive("flat-table", FlatTable) 73 + roles.register_local_role('cspan', c_span) 74 + roles.register_local_role('rspan', r_span) 75 + 76 + # ============================================================================== 77 + def c_span(name, rawtext, text, lineno, inliner, options=None, content=None): 78 + # ============================================================================== 79 + # pylint: disable=W0613 80 + 81 + options = options if options is not None else {} 82 + content = content if content is not None else [] 83 + nodelist = [colSpan(span=int(text))] 84 + msglist = [] 85 + return nodelist, msglist 86 + 87 + # ============================================================================== 88 + def r_span(name, rawtext, text, lineno, inliner, options=None, content=None): 89 + # ============================================================================== 90 + # pylint: disable=W0613 91 + 92 + options = options if options is not None else {} 93 + content = content if content is not None else [] 94 + nodelist = [rowSpan(span=int(text))] 95 + msglist = [] 96 + return nodelist, msglist 97 + 98 + 99 + # ============================================================================== 100 + class rowSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C0321 101 + class colSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C0321 102 + # ============================================================================== 103 + 104 + # ============================================================================== 105 + class FlatTable(Table): 106 + # ============================================================================== 107 + 108 + u"""FlatTable (``flat-table``) directive""" 109 + 110 + option_spec = { 111 + 'name': directives.unchanged 112 + , 'class': directives.class_option 113 + , 'header-rows': directives.nonnegative_int 114 + , 'stub-columns': directives.nonnegative_int 115 + , 'widths': directives.positive_int_list 116 + , 'fill-cells' : directives.flag } 117 + 118 + def run(self): 119 + 120 + if not self.content: 121 + error = self.state_machine.reporter.error( 122 + 'The "%s" directive is empty; content required.' % self.name, 123 + nodes.literal_block(self.block_text, self.block_text), 124 + line=self.lineno) 125 + return [error] 126 + 127 + title, messages = self.make_title() 128 + node = nodes.Element() # anonymous container for parsing 129 + self.state.nested_parse(self.content, self.content_offset, node) 130 + 131 + tableBuilder = ListTableBuilder(self) 132 + tableBuilder.parseFlatTableNode(node) 133 + tableNode = tableBuilder.buildTableNode() 134 + # SDK.CONSOLE() # print --> tableNode.asdom().toprettyxml() 135 + if title: 136 + tableNode.insert(0, title) 137 + return [tableNode] + messages 138 + 139 + 140 + # ============================================================================== 141 + class ListTableBuilder(object): 142 + # ============================================================================== 143 + 144 + u"""Builds a table from a double-stage list""" 145 + 146 + def __init__(self, directive): 147 + self.directive = directive 148 + self.rows = [] 149 + self.max_cols = 0 150 + 151 + def buildTableNode(self): 152 + 153 + colwidths = self.directive.get_column_widths(self.max_cols) 154 + stub_columns = self.directive.options.get('stub-columns', 0) 155 + header_rows = self.directive.options.get('header-rows', 0) 156 + 157 + table = nodes.table() 158 + tgroup = nodes.tgroup(cols=len(colwidths)) 159 + table += tgroup 160 + 161 + 162 + for colwidth in colwidths: 163 + colspec = nodes.colspec(colwidth=colwidth) 164 + # FIXME: It seems, that the stub method only works well in the 165 + # absence of rowspan (observed by the html buidler, the docutils-xml 166 + # build seems OK). This is not extraordinary, because there exists 167 + # no table directive (except *this* flat-table) which allows to 168 + # define coexistent of rowspan and stubs (there was no use-case 169 + # before flat-table). This should be reviewed (later). 170 + if stub_columns: 171 + colspec.attributes['stub'] = 1 172 + stub_columns -= 1 173 + tgroup += colspec 174 + stub_columns = self.directive.options.get('stub-columns', 0) 175 + 176 + if header_rows: 177 + thead = nodes.thead() 178 + tgroup += thead 179 + for row in self.rows[:header_rows]: 180 + thead += self.buildTableRowNode(row) 181 + 182 + tbody = nodes.tbody() 183 + tgroup += tbody 184 + 185 + for row in self.rows[header_rows:]: 186 + tbody += self.buildTableRowNode(row) 187 + return table 188 + 189 + def buildTableRowNode(self, row_data, classes=None): 190 + classes = [] if classes is None else classes 191 + row = nodes.row() 192 + for cell in row_data: 193 + if cell is None: 194 + continue 195 + cspan, rspan, cellElements = cell 196 + 197 + attributes = {"classes" : classes} 198 + if rspan: 199 + attributes['morerows'] = rspan 200 + if cspan: 201 + attributes['morecols'] = cspan 202 + entry = nodes.entry(**attributes) 203 + entry.extend(cellElements) 204 + row += entry 205 + return row 206 + 207 + def raiseError(self, msg): 208 + error = self.directive.state_machine.reporter.error( 209 + msg 210 + , nodes.literal_block(self.directive.block_text 211 + , self.directive.block_text) 212 + , line = self.directive.lineno ) 213 + raise SystemMessagePropagation(error) 214 + 215 + def parseFlatTableNode(self, node): 216 + u"""parses the node from a :py:class:`FlatTable` directive's body""" 217 + 218 + if len(node) != 1 or not isinstance(node[0], nodes.bullet_list): 219 + self.raiseError( 220 + 'Error parsing content block for the "%s" directive: ' 221 + 'exactly one bullet list expected.' % self.directive.name ) 222 + 223 + for rowNum, rowItem in enumerate(node[0]): 224 + row = self.parseRowItem(rowItem, rowNum) 225 + self.rows.append(row) 226 + self.roundOffTableDefinition() 227 + 228 + def roundOffTableDefinition(self): 229 + u"""Round off the table definition. 230 + 231 + This method rounds off the table definition in :py:member:`rows`. 232 + 233 + * This method inserts the needed ``None`` values for the missing cells 234 + arising from spanning cells over rows and/or columns. 235 + 236 + * recount the :py:member:`max_cols` 237 + 238 + * Autospan or fill (option ``fill-cells``) missing cells on the right 239 + side of the table-row 240 + """ 241 + 242 + y = 0 243 + while y < len(self.rows): 244 + x = 0 245 + 246 + while x < len(self.rows[y]): 247 + cell = self.rows[y][x] 248 + if cell is None: 249 + x += 1 250 + continue 251 + cspan, rspan = cell[:2] 252 + # handle colspan in current row 253 + for c in range(cspan): 254 + try: 255 + self.rows[y].insert(x+c+1, None) 256 + except: # pylint: disable=W0702 257 + # the user sets ambiguous rowspans 258 + pass # SDK.CONSOLE() 259 + # handle colspan in spanned rows 260 + for r in range(rspan): 261 + for c in range(cspan + 1): 262 + try: 263 + self.rows[y+r+1].insert(x+c, None) 264 + except: # pylint: disable=W0702 265 + # the user sets ambiguous rowspans 266 + pass # SDK.CONSOLE() 267 + x += 1 268 + y += 1 269 + 270 + # Insert the missing cells on the right side. For this, first 271 + # re-calculate the max columns. 272 + 273 + for row in self.rows: 274 + if self.max_cols < len(row): 275 + self.max_cols = len(row) 276 + 277 + # fill with empty cells or cellspan? 278 + 279 + fill_cells = False 280 + if 'fill-cells' in self.directive.options: 281 + fill_cells = True 282 + 283 + for row in self.rows: 284 + x = self.max_cols - len(row) 285 + if x and not fill_cells: 286 + if row[-1] is None: 287 + row.append( ( x - 1, 0, []) ) 288 + else: 289 + cspan, rspan, content = row[-1] 290 + row[-1] = (cspan + x, rspan, content) 291 + elif x and fill_cells: 292 + for i in range(x): 293 + row.append( (0, 0, nodes.comment()) ) 294 + 295 + def pprint(self): 296 + # for debugging 297 + retVal = "[ " 298 + for row in self.rows: 299 + retVal += "[ " 300 + for col in row: 301 + if col is None: 302 + retVal += ('%r' % col) 303 + retVal += "\n , " 304 + else: 305 + content = col[2][0].astext() 306 + if len (content) > 30: 307 + content = content[:30] + "..." 308 + retVal += ('(cspan=%s, rspan=%s, %r)' 309 + % (col[0], col[1], content)) 310 + retVal += "]\n , " 311 + retVal = retVal[:-2] 312 + retVal += "]\n , " 313 + retVal = retVal[:-2] 314 + return retVal + "]" 315 + 316 + def parseRowItem(self, rowItem, rowNum): 317 + row = [] 318 + childNo = 0 319 + error = False 320 + cell = None 321 + target = None 322 + 323 + for child in rowItem: 324 + if (isinstance(child , nodes.comment) 325 + or isinstance(child, nodes.system_message)): 326 + pass 327 + elif isinstance(child , nodes.target): 328 + target = child 329 + elif isinstance(child, nodes.bullet_list): 330 + childNo += 1 331 + cell = child 332 + else: 333 + error = True 334 + break 335 + 336 + if childNo != 1 or error: 337 + self.raiseError( 338 + 'Error parsing content block for the "%s" directive: ' 339 + 'two-level bullet list expected, but row %s does not ' 340 + 'contain a second-level bullet list.' 341 + % (self.directive.name, rowNum + 1)) 342 + 343 + for cellItem in cell: 344 + cspan, rspan, cellElements = self.parseCellItem(cellItem) 345 + if target is not None: 346 + cellElements.insert(0, target) 347 + row.append( (cspan, rspan, cellElements) ) 348 + return row 349 + 350 + def parseCellItem(self, cellItem): 351 + # search and remove cspan, rspan colspec from the first element in 352 + # this listItem (field). 353 + cspan = rspan = 0 354 + if not len(cellItem): 355 + return cspan, rspan, [] 356 + for elem in cellItem[0]: 357 + if isinstance(elem, colSpan): 358 + cspan = elem.get("span") 359 + elem.parent.remove(elem) 360 + continue 361 + if isinstance(elem, rowSpan): 362 + rspan = elem.get("span") 363 + elem.parent.remove(elem) 364 + continue 365 + return cspan, rspan, cellItem[:]