1 #!/usr/bin/env python3 1 #!/usr/bin/env python3 2 # -*- coding: utf-8; mode: python -*- 2 # -*- coding: utf-8; mode: python -*- 3 # pylint: disable=C0330, R0903, R0912 3 # pylint: disable=C0330, R0903, R0912 4 4 5 u""" 5 u""" 6 flat-table 6 flat-table 7 ~~~~~~~~~~ 7 ~~~~~~~~~~ 8 8 9 Implementation of the ``flat-table`` reST- 9 Implementation of the ``flat-table`` reST-directive. 10 10 11 :copyright: Copyright (C) 2016 Markus He 11 :copyright: Copyright (C) 2016 Markus Heiser 12 :license: GPL Version 2, June 1991 see 12 :license: GPL Version 2, June 1991 see linux/COPYING for details. 13 13 14 The ``flat-table`` (:py:class:`FlatTable`) 14 The ``flat-table`` (:py:class:`FlatTable`) is a double-stage list similar to 15 the ``list-table`` with some additional fe 15 the ``list-table`` with some additional features: 16 16 17 * *column-span*: with the role ``cspan`` a 17 * *column-span*: with the role ``cspan`` a cell can be extended through 18 additional columns 18 additional columns 19 19 20 * *row-span*: with the role ``rspan`` a ce 20 * *row-span*: with the role ``rspan`` a cell can be extended through 21 additional rows 21 additional rows 22 22 23 * *auto span* rightmost cell of a table ro 23 * *auto span* rightmost cell of a table row over the missing cells on the 24 right side of that table-row. With Opti 24 right side of that table-row. With Option ``:fill-cells:`` this behavior 25 can be changed from *auto span* to *auto !! 25 can changed from *auto span* to *auto fill*, which automaticly inserts 26 (empty) cells instead of spanning the la 26 (empty) cells instead of spanning the last cell. 27 27 28 Options: 28 Options: 29 29 30 * header-rows: [int] count of header row 30 * header-rows: [int] count of header rows 31 * stub-columns: [int] count of stub colum 31 * stub-columns: [int] count of stub columns 32 * widths: [[int] [int] ... ] widths 32 * widths: [[int] [int] ... ] widths of columns 33 * fill-cells: instead of autospann miss 33 * fill-cells: instead of autospann missing cells, insert missing cells 34 34 35 roles: 35 roles: 36 36 37 * cspan: [int] additionale columns (*morec 37 * cspan: [int] additionale columns (*morecols*) 38 * rspan: [int] additionale rows (*morerows 38 * rspan: [int] additionale rows (*morerows*) 39 """ 39 """ 40 40 41 # ============================================ 41 # ============================================================================== 42 # imports 42 # imports 43 # ============================================ 43 # ============================================================================== 44 44 >> 45 import sys >> 46 45 from docutils import nodes 47 from docutils import nodes 46 from docutils.parsers.rst import directives, r 48 from docutils.parsers.rst import directives, roles 47 from docutils.parsers.rst.directives.tables im 49 from docutils.parsers.rst.directives.tables import Table 48 from docutils.utils import SystemMessagePropag 50 from docutils.utils import SystemMessagePropagation 49 51 50 # ============================================ 52 # ============================================================================== 51 # common globals 53 # common globals 52 # ============================================ 54 # ============================================================================== 53 55 >> 56 # The version numbering follows numbering of the specification >> 57 # (Documentation/books/kernel-doc-HOWTO). 54 __version__ = '1.0' 58 __version__ = '1.0' 55 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 56 # ============================================ 68 # ============================================================================== 57 def setup(app): 69 def setup(app): 58 # ============================================ 70 # ============================================================================== 59 71 60 app.add_directive("flat-table", FlatTable) 72 app.add_directive("flat-table", FlatTable) 61 roles.register_local_role('cspan', c_span) 73 roles.register_local_role('cspan', c_span) 62 roles.register_local_role('rspan', r_span) 74 roles.register_local_role('rspan', r_span) 63 75 64 return dict( 76 return dict( 65 version = __version__, 77 version = __version__, 66 parallel_read_safe = True, 78 parallel_read_safe = True, 67 parallel_write_safe = True 79 parallel_write_safe = True 68 ) 80 ) 69 81 70 # ============================================ 82 # ============================================================================== 71 def c_span(name, rawtext, text, lineno, inline 83 def c_span(name, rawtext, text, lineno, inliner, options=None, content=None): 72 # ============================================ 84 # ============================================================================== 73 # pylint: disable=W0613 85 # pylint: disable=W0613 74 86 75 options = options if options is not None 87 options = options if options is not None else {} 76 content = content if content is not None 88 content = content if content is not None else [] 77 nodelist = [colSpan(span=int(text))] 89 nodelist = [colSpan(span=int(text))] 78 msglist = [] 90 msglist = [] 79 return nodelist, msglist 91 return nodelist, msglist 80 92 81 # ============================================ 93 # ============================================================================== 82 def r_span(name, rawtext, text, lineno, inline 94 def r_span(name, rawtext, text, lineno, inliner, options=None, content=None): 83 # ============================================ 95 # ============================================================================== 84 # pylint: disable=W0613 96 # pylint: disable=W0613 85 97 86 options = options if options is not None 98 options = options if options is not None else {} 87 content = content if content is not None 99 content = content if content is not None else [] 88 nodelist = [rowSpan(span=int(text))] 100 nodelist = [rowSpan(span=int(text))] 89 msglist = [] 101 msglist = [] 90 return nodelist, msglist 102 return nodelist, msglist 91 103 92 104 93 # ============================================ 105 # ============================================================================== 94 class rowSpan(nodes.General, nodes.Element): p 106 class rowSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C0321 95 class colSpan(nodes.General, nodes.Element): p 107 class colSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C0321 96 # ============================================ 108 # ============================================================================== 97 109 98 # ============================================ 110 # ============================================================================== 99 class FlatTable(Table): 111 class FlatTable(Table): 100 # ============================================ 112 # ============================================================================== 101 113 102 u"""FlatTable (``flat-table``) directive"" 114 u"""FlatTable (``flat-table``) directive""" 103 115 104 option_spec = { 116 option_spec = { 105 'name': directives.unchanged 117 'name': directives.unchanged 106 , 'class': directives.class_option 118 , 'class': directives.class_option 107 , 'header-rows': directives.nonnegativ 119 , 'header-rows': directives.nonnegative_int 108 , 'stub-columns': directives.nonnegati 120 , 'stub-columns': directives.nonnegative_int 109 , 'widths': directives.positive_int_li 121 , 'widths': directives.positive_int_list 110 , 'fill-cells' : directives.flag } 122 , 'fill-cells' : directives.flag } 111 123 112 def run(self): 124 def run(self): 113 125 114 if not self.content: 126 if not self.content: 115 error = self.state_machine.reporte 127 error = self.state_machine.reporter.error( 116 'The "%s" directive is empty; 128 'The "%s" directive is empty; content required.' % self.name, 117 nodes.literal_block(self.block 129 nodes.literal_block(self.block_text, self.block_text), 118 line=self.lineno) 130 line=self.lineno) 119 return [error] 131 return [error] 120 132 121 title, messages = self.make_title() 133 title, messages = self.make_title() 122 node = nodes.Element() # anon 134 node = nodes.Element() # anonymous container for parsing 123 self.state.nested_parse(self.content, 135 self.state.nested_parse(self.content, self.content_offset, node) 124 136 125 tableBuilder = ListTableBuilder(self) 137 tableBuilder = ListTableBuilder(self) 126 tableBuilder.parseFlatTableNode(node) 138 tableBuilder.parseFlatTableNode(node) 127 tableNode = tableBuilder.buildTableNod 139 tableNode = tableBuilder.buildTableNode() 128 # SDK.CONSOLE() # print --> tableNode 140 # SDK.CONSOLE() # print --> tableNode.asdom().toprettyxml() 129 if title: 141 if title: 130 tableNode.insert(0, title) 142 tableNode.insert(0, title) 131 return [tableNode] + messages 143 return [tableNode] + messages 132 144 133 145 134 # ============================================ 146 # ============================================================================== 135 class ListTableBuilder(object): 147 class ListTableBuilder(object): 136 # ============================================ 148 # ============================================================================== 137 149 138 u"""Builds a table from a double-stage lis 150 u"""Builds a table from a double-stage list""" 139 151 140 def __init__(self, directive): 152 def __init__(self, directive): 141 self.directive = directive 153 self.directive = directive 142 self.rows = [] 154 self.rows = [] 143 self.max_cols = 0 155 self.max_cols = 0 144 156 145 def buildTableNode(self): 157 def buildTableNode(self): 146 158 147 colwidths = self.directive.get_colu 159 colwidths = self.directive.get_column_widths(self.max_cols) 148 if isinstance(colwidths, tuple): 160 if isinstance(colwidths, tuple): 149 # Since docutils 0.13, get_column_ 161 # Since docutils 0.13, get_column_widths returns a (widths, 150 # colwidths) tuple, where widths i 162 # colwidths) tuple, where widths is a string (i.e. 'auto'). 151 # See https://sourceforge.net/p/do 163 # See https://sourceforge.net/p/docutils/patches/120/. 152 colwidths = colwidths[1] 164 colwidths = colwidths[1] 153 stub_columns = self.directive.options. 165 stub_columns = self.directive.options.get('stub-columns', 0) 154 header_rows = self.directive.options. 166 header_rows = self.directive.options.get('header-rows', 0) 155 167 156 table = nodes.table() 168 table = nodes.table() 157 tgroup = nodes.tgroup(cols=len(colwidt 169 tgroup = nodes.tgroup(cols=len(colwidths)) 158 table += tgroup 170 table += tgroup 159 171 160 172 161 for colwidth in colwidths: 173 for colwidth in colwidths: 162 colspec = nodes.colspec(colwidth=c 174 colspec = nodes.colspec(colwidth=colwidth) 163 # FIXME: It seems, that the stub m 175 # FIXME: It seems, that the stub method only works well in the 164 # absence of rowspan (observed by !! 176 # absence of rowspan (observed by the html buidler, the docutils-xml 165 # build seems OK). This is not ex 177 # build seems OK). This is not extraordinary, because there exists 166 # no table directive (except *this 178 # no table directive (except *this* flat-table) which allows to 167 # define coexistent of rowspan and 179 # define coexistent of rowspan and stubs (there was no use-case 168 # before flat-table). This should 180 # before flat-table). This should be reviewed (later). 169 if stub_columns: 181 if stub_columns: 170 colspec.attributes['stub'] = 1 182 colspec.attributes['stub'] = 1 171 stub_columns -= 1 183 stub_columns -= 1 172 tgroup += colspec 184 tgroup += colspec 173 stub_columns = self.directive.options. 185 stub_columns = self.directive.options.get('stub-columns', 0) 174 186 175 if header_rows: 187 if header_rows: 176 thead = nodes.thead() 188 thead = nodes.thead() 177 tgroup += thead 189 tgroup += thead 178 for row in self.rows[:header_rows] 190 for row in self.rows[:header_rows]: 179 thead += self.buildTableRowNod 191 thead += self.buildTableRowNode(row) 180 192 181 tbody = nodes.tbody() 193 tbody = nodes.tbody() 182 tgroup += tbody 194 tgroup += tbody 183 195 184 for row in self.rows[header_rows:]: 196 for row in self.rows[header_rows:]: 185 tbody += self.buildTableRowNode(ro 197 tbody += self.buildTableRowNode(row) 186 return table 198 return table 187 199 188 def buildTableRowNode(self, row_data, clas 200 def buildTableRowNode(self, row_data, classes=None): 189 classes = [] if classes is None else c 201 classes = [] if classes is None else classes 190 row = nodes.row() 202 row = nodes.row() 191 for cell in row_data: 203 for cell in row_data: 192 if cell is None: 204 if cell is None: 193 continue 205 continue 194 cspan, rspan, cellElements = cell 206 cspan, rspan, cellElements = cell 195 207 196 attributes = {"classes" : classes} 208 attributes = {"classes" : classes} 197 if rspan: 209 if rspan: 198 attributes['morerows'] = rspan 210 attributes['morerows'] = rspan 199 if cspan: 211 if cspan: 200 attributes['morecols'] = cspan 212 attributes['morecols'] = cspan 201 entry = nodes.entry(**attributes) 213 entry = nodes.entry(**attributes) 202 entry.extend(cellElements) 214 entry.extend(cellElements) 203 row += entry 215 row += entry 204 return row 216 return row 205 217 206 def raiseError(self, msg): 218 def raiseError(self, msg): 207 error = self.directive.state_machine. 219 error = self.directive.state_machine.reporter.error( 208 msg 220 msg 209 , nodes.literal_block(self.directi 221 , nodes.literal_block(self.directive.block_text 210 , self.direc 222 , self.directive.block_text) 211 , line = self.directive.lineno ) 223 , line = self.directive.lineno ) 212 raise SystemMessagePropagation(error) 224 raise SystemMessagePropagation(error) 213 225 214 def parseFlatTableNode(self, node): 226 def parseFlatTableNode(self, node): 215 u"""parses the node from a :py:class:` 227 u"""parses the node from a :py:class:`FlatTable` directive's body""" 216 228 217 if len(node) != 1 or not isinstance(no 229 if len(node) != 1 or not isinstance(node[0], nodes.bullet_list): 218 self.raiseError( 230 self.raiseError( 219 'Error parsing content block f 231 'Error parsing content block for the "%s" directive: ' 220 'exactly one bullet list expec 232 'exactly one bullet list expected.' % self.directive.name ) 221 233 222 for rowNum, rowItem in enumerate(node[ 234 for rowNum, rowItem in enumerate(node[0]): 223 row = self.parseRowItem(rowItem, r 235 row = self.parseRowItem(rowItem, rowNum) 224 self.rows.append(row) 236 self.rows.append(row) 225 self.roundOffTableDefinition() 237 self.roundOffTableDefinition() 226 238 227 def roundOffTableDefinition(self): 239 def roundOffTableDefinition(self): 228 u"""Round off the table definition. 240 u"""Round off the table definition. 229 241 230 This method rounds off the table defin 242 This method rounds off the table definition in :py:member:`rows`. 231 243 232 * This method inserts the needed ``Non 244 * This method inserts the needed ``None`` values for the missing cells 233 arising from spanning cells over rows 245 arising from spanning cells over rows and/or columns. 234 246 235 * recount the :py:member:`max_cols` 247 * recount the :py:member:`max_cols` 236 248 237 * Autospan or fill (option ``fill-cell 249 * Autospan or fill (option ``fill-cells``) missing cells on the right 238 side of the table-row 250 side of the table-row 239 """ 251 """ 240 252 241 y = 0 253 y = 0 242 while y < len(self.rows): 254 while y < len(self.rows): 243 x = 0 255 x = 0 244 256 245 while x < len(self.rows[y]): 257 while x < len(self.rows[y]): 246 cell = self.rows[y][x] 258 cell = self.rows[y][x] 247 if cell is None: 259 if cell is None: 248 x += 1 260 x += 1 249 continue 261 continue 250 cspan, rspan = cell[:2] 262 cspan, rspan = cell[:2] 251 # handle colspan in current ro 263 # handle colspan in current row 252 for c in range(cspan): 264 for c in range(cspan): 253 try: 265 try: 254 self.rows[y].insert(x+ 266 self.rows[y].insert(x+c+1, None) 255 except: # pylint: disable= 267 except: # pylint: disable=W0702 256 # the user sets ambigu 268 # the user sets ambiguous rowspans 257 pass # SDK.CONSOLE() 269 pass # SDK.CONSOLE() 258 # handle colspan in spanned ro 270 # handle colspan in spanned rows 259 for r in range(rspan): 271 for r in range(rspan): 260 for c in range(cspan + 1): 272 for c in range(cspan + 1): 261 try: 273 try: 262 self.rows[y+r+1].i 274 self.rows[y+r+1].insert(x+c, None) 263 except: # pylint: disa 275 except: # pylint: disable=W0702 264 # the user sets am 276 # the user sets ambiguous rowspans 265 pass # SDK.CONSOLE 277 pass # SDK.CONSOLE() 266 x += 1 278 x += 1 267 y += 1 279 y += 1 268 280 269 # Insert the missing cells on the righ 281 # Insert the missing cells on the right side. For this, first 270 # re-calculate the max columns. 282 # re-calculate the max columns. 271 283 272 for row in self.rows: 284 for row in self.rows: 273 if self.max_cols < len(row): 285 if self.max_cols < len(row): 274 self.max_cols = len(row) 286 self.max_cols = len(row) 275 287 276 # fill with empty cells or cellspan? 288 # fill with empty cells or cellspan? 277 289 278 fill_cells = False 290 fill_cells = False 279 if 'fill-cells' in self.directive.opti 291 if 'fill-cells' in self.directive.options: 280 fill_cells = True 292 fill_cells = True 281 293 282 for row in self.rows: 294 for row in self.rows: 283 x = self.max_cols - len(row) 295 x = self.max_cols - len(row) 284 if x and not fill_cells: 296 if x and not fill_cells: 285 if row[-1] is None: 297 if row[-1] is None: 286 row.append( ( x - 1, 0, [] 298 row.append( ( x - 1, 0, []) ) 287 else: 299 else: 288 cspan, rspan, content = ro 300 cspan, rspan, content = row[-1] 289 row[-1] = (cspan + x, rspa 301 row[-1] = (cspan + x, rspan, content) 290 elif x and fill_cells: 302 elif x and fill_cells: 291 for i in range(x): 303 for i in range(x): 292 row.append( (0, 0, nodes.c 304 row.append( (0, 0, nodes.comment()) ) 293 305 294 def pprint(self): 306 def pprint(self): 295 # for debugging 307 # for debugging 296 retVal = "[ " 308 retVal = "[ " 297 for row in self.rows: 309 for row in self.rows: 298 retVal += "[ " 310 retVal += "[ " 299 for col in row: 311 for col in row: 300 if col is None: 312 if col is None: 301 retVal += ('%r' % col) 313 retVal += ('%r' % col) 302 retVal += "\n , " 314 retVal += "\n , " 303 else: 315 else: 304 content = col[2][0].astext 316 content = col[2][0].astext() 305 if len (content) > 30: 317 if len (content) > 30: 306 content = content[:30] 318 content = content[:30] + "..." 307 retVal += ('(cspan=%s, rsp 319 retVal += ('(cspan=%s, rspan=%s, %r)' 308 % (col[0], col[ 320 % (col[0], col[1], content)) 309 retVal += "]\n , " 321 retVal += "]\n , " 310 retVal = retVal[:-2] 322 retVal = retVal[:-2] 311 retVal += "]\n , " 323 retVal += "]\n , " 312 retVal = retVal[:-2] 324 retVal = retVal[:-2] 313 return retVal + "]" 325 return retVal + "]" 314 326 315 def parseRowItem(self, rowItem, rowNum): 327 def parseRowItem(self, rowItem, rowNum): 316 row = [] 328 row = [] 317 childNo = 0 329 childNo = 0 318 error = False 330 error = False 319 cell = None 331 cell = None 320 target = None 332 target = None 321 333 322 for child in rowItem: 334 for child in rowItem: 323 if (isinstance(child , nodes.comme 335 if (isinstance(child , nodes.comment) 324 or isinstance(child, nodes.sys 336 or isinstance(child, nodes.system_message)): 325 pass 337 pass 326 elif isinstance(child , nodes.targ 338 elif isinstance(child , nodes.target): 327 target = child 339 target = child 328 elif isinstance(child, nodes.bulle 340 elif isinstance(child, nodes.bullet_list): 329 childNo += 1 341 childNo += 1 330 cell = child 342 cell = child 331 else: 343 else: 332 error = True 344 error = True 333 break 345 break 334 346 335 if childNo != 1 or error: 347 if childNo != 1 or error: 336 self.raiseError( 348 self.raiseError( 337 'Error parsing content block f 349 'Error parsing content block for the "%s" directive: ' 338 'two-level bullet list expecte 350 'two-level bullet list expected, but row %s does not ' 339 'contain a second-level bullet 351 'contain a second-level bullet list.' 340 % (self.directive.name, rowNum 352 % (self.directive.name, rowNum + 1)) 341 353 342 for cellItem in cell: 354 for cellItem in cell: 343 cspan, rspan, cellElements = self. 355 cspan, rspan, cellElements = self.parseCellItem(cellItem) 344 if target is not None: 356 if target is not None: 345 cellElements.insert(0, target) 357 cellElements.insert(0, target) 346 row.append( (cspan, rspan, cellEle 358 row.append( (cspan, rspan, cellElements) ) 347 return row 359 return row 348 360 349 def parseCellItem(self, cellItem): 361 def parseCellItem(self, cellItem): 350 # search and remove cspan, rspan colsp 362 # search and remove cspan, rspan colspec from the first element in 351 # this listItem (field). 363 # this listItem (field). 352 cspan = rspan = 0 364 cspan = rspan = 0 353 if not len(cellItem): 365 if not len(cellItem): 354 return cspan, rspan, [] 366 return cspan, rspan, [] 355 for elem in cellItem[0]: 367 for elem in cellItem[0]: 356 if isinstance(elem, colSpan): 368 if isinstance(elem, colSpan): 357 cspan = elem.get("span") 369 cspan = elem.get("span") 358 elem.parent.remove(elem) 370 elem.parent.remove(elem) 359 continue 371 continue 360 if isinstance(elem, rowSpan): 372 if isinstance(elem, rowSpan): 361 rspan = elem.get("span") 373 rspan = elem.get("span") 362 elem.parent.remove(elem) 374 elem.parent.remove(elem) 363 continue 375 continue 364 return cspan, rspan, cellItem[:] 376 return cspan, rspan, cellItem[:]
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.