2016-06-30 15:00:22 +03:00
#!/usr/bin/env python3
# -*- coding: utf-8; mode: python -*-
# pylint: disable=C0330, R0903, R0912
u """
flat - table
~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Implementation of the ` ` flat - table ` ` reST - directive .
: copyright : Copyright ( C ) 2016 Markus Heiser
: license : GPL Version 2 , June 1991 see linux / COPYING for details .
The ` ` flat - table ` ` ( : py : class : ` FlatTable ` ) 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 ) cells instead of spanning the last cell .
Options :
* header - rows : [ int ] count of header rows
* stub - columns : [ int ] count of stub columns
* widths : [ [ int ] [ int ] . . . ] widths of columns
* fill - cells : instead of autospann missing cells , insert missing cells
roles :
* cspan : [ int ] additionale columns ( * morecols * )
* rspan : [ int ] additionale rows ( * morerows * )
"""
# ==============================================================================
# imports
# ==============================================================================
import sys
from docutils import nodes
from docutils . parsers . rst import directives , roles
from docutils . parsers . rst . directives . tables import Table
from docutils . utils import SystemMessagePropagation
# ==============================================================================
# common globals
# ==============================================================================
# The version numbering follows numbering of the specification
# (Documentation/books/kernel-doc-HOWTO).
__version__ = ' 1.0 '
PY3 = sys . version_info [ 0 ] == 3
PY2 = sys . version_info [ 0 ] == 2
if PY3 :
# pylint: disable=C0103, W0622
unicode = str
basestring = str
# ==============================================================================
def setup ( app ) :
# ==============================================================================
app . add_directive ( " flat-table " , FlatTable )
roles . register_local_role ( ' cspan ' , c_span )
roles . register_local_role ( ' rspan ' , r_span )
2016-08-24 16:35:24 +03:00
return dict (
version = __version__ ,
parallel_read_safe = True ,
parallel_write_safe = True
)
2016-06-30 15:00:22 +03:00
# ==============================================================================
def c_span ( name , rawtext , text , lineno , inliner , options = None , content = None ) :
# ==============================================================================
# pylint: disable=W0613
options = options if options is not None else { }
content = content if content is not None else [ ]
nodelist = [ colSpan ( span = int ( text ) ) ]
msglist = [ ]
return nodelist , msglist
# ==============================================================================
def r_span ( name , rawtext , text , lineno , inliner , options = None , content = None ) :
# ==============================================================================
# pylint: disable=W0613
options = options if options is not None else { }
content = content if content is not None else [ ]
nodelist = [ rowSpan ( span = int ( text ) ) ]
msglist = [ ]
return nodelist , msglist
# ==============================================================================
class rowSpan ( nodes . General , nodes . Element ) : pass # pylint: disable=C0103,C0321
class colSpan ( nodes . General , nodes . Element ) : pass # pylint: disable=C0103,C0321
# ==============================================================================
# ==============================================================================
class FlatTable ( Table ) :
# ==============================================================================
u """ FlatTable (``flat-table``) directive """
option_spec = {
' name ' : directives . unchanged
, ' class ' : directives . class_option
, ' header-rows ' : directives . nonnegative_int
, ' stub-columns ' : directives . nonnegative_int
, ' widths ' : directives . positive_int_list
, ' fill-cells ' : directives . flag }
def run ( self ) :
if not self . content :
error = self . state_machine . reporter . error (
' The " %s " directive is empty; content required. ' % self . name ,
nodes . literal_block ( self . block_text , self . block_text ) ,
line = self . lineno )
return [ error ]
title , messages = self . make_title ( )
node = nodes . Element ( ) # anonymous container for parsing
self . state . nested_parse ( self . content , self . content_offset , node )
tableBuilder = ListTableBuilder ( self )
tableBuilder . parseFlatTableNode ( node )
tableNode = tableBuilder . buildTableNode ( )
# SDK.CONSOLE() # print --> tableNode.asdom().toprettyxml()
if title :
tableNode . insert ( 0 , title )
return [ tableNode ] + messages
# ==============================================================================
class ListTableBuilder ( object ) :
# ==============================================================================
u """ Builds a table from a double-stage list """
def __init__ ( self , directive ) :
self . directive = directive
self . rows = [ ]
self . max_cols = 0
def buildTableNode ( self ) :
colwidths = self . directive . get_column_widths ( self . max_cols )
2016-12-18 13:11:46 +03:00
if isinstance ( colwidths , tuple ) :
# Since docutils 0.13, get_column_widths returns a (widths,
# colwidths) tuple, where widths is a string (i.e. 'auto').
# See https://sourceforge.net/p/docutils/patches/120/.
colwidths = colwidths [ 1 ]
2016-06-30 15:00:22 +03:00
stub_columns = self . directive . options . get ( ' stub-columns ' , 0 )
header_rows = self . directive . options . get ( ' header-rows ' , 0 )
table = nodes . table ( )
tgroup = nodes . tgroup ( cols = len ( colwidths ) )
table + = tgroup
for colwidth in colwidths :
colspec = nodes . colspec ( colwidth = colwidth )
# FIXME: It seems, that the stub method only works well in the
# absence of rowspan (observed by the html buidler, the docutils-xml
# build seems OK). This is not extraordinary, because there exists
# no table directive (except *this* flat-table) which allows to
# define coexistent of rowspan and stubs (there was no use-case
# before flat-table). This should be reviewed (later).
if stub_columns :
colspec . attributes [ ' stub ' ] = 1
stub_columns - = 1
tgroup + = colspec
stub_columns = self . directive . options . get ( ' stub-columns ' , 0 )
if header_rows :
thead = nodes . thead ( )
tgroup + = thead
for row in self . rows [ : header_rows ] :
thead + = self . buildTableRowNode ( row )
tbody = nodes . tbody ( )
tgroup + = tbody
for row in self . rows [ header_rows : ] :
tbody + = self . buildTableRowNode ( row )
return table
def buildTableRowNode ( self , row_data , classes = None ) :
classes = [ ] if classes is None else classes
row = nodes . row ( )
for cell in row_data :
if cell is None :
continue
cspan , rspan , cellElements = cell
attributes = { " classes " : classes }
if rspan :
attributes [ ' morerows ' ] = rspan
if cspan :
attributes [ ' morecols ' ] = cspan
entry = nodes . entry ( * * attributes )
entry . extend ( cellElements )
row + = entry
return row
def raiseError ( self , msg ) :
error = self . directive . state_machine . reporter . error (
msg
, nodes . literal_block ( self . directive . block_text
, self . directive . block_text )
, line = self . directive . lineno )
raise SystemMessagePropagation ( error )
def parseFlatTableNode ( self , node ) :
u """ parses the node from a :py:class:`FlatTable` directive ' s body """
if len ( node ) != 1 or not isinstance ( node [ 0 ] , nodes . bullet_list ) :
self . raiseError (
' Error parsing content block for the " %s " directive: '
' exactly one bullet list expected. ' % self . directive . name )
for rowNum , rowItem in enumerate ( node [ 0 ] ) :
row = self . parseRowItem ( rowItem , rowNum )
self . rows . append ( row )
self . roundOffTableDefinition ( )
def roundOffTableDefinition ( self ) :
u """ Round off the table definition.
This method rounds off the table definition in : py : member : ` rows ` .
* This method inserts the needed ` ` None ` ` values for the missing cells
arising from spanning cells over rows and / or columns .
* recount the : py : member : ` max_cols `
* Autospan or fill ( option ` ` fill - cells ` ` ) missing cells on the right
side of the table - row
"""
y = 0
while y < len ( self . rows ) :
x = 0
while x < len ( self . rows [ y ] ) :
cell = self . rows [ y ] [ x ]
if cell is None :
x + = 1
continue
cspan , rspan = cell [ : 2 ]
# handle colspan in current row
for c in range ( cspan ) :
try :
self . rows [ y ] . insert ( x + c + 1 , None )
except : # pylint: disable=W0702
# the user sets ambiguous rowspans
pass # SDK.CONSOLE()
# handle colspan in spanned rows
for r in range ( rspan ) :
for c in range ( cspan + 1 ) :
try :
self . rows [ y + r + 1 ] . insert ( x + c , None )
except : # pylint: disable=W0702
# the user sets ambiguous rowspans
pass # SDK.CONSOLE()
x + = 1
y + = 1
# Insert the missing cells on the right side. For this, first
# re-calculate the max columns.
for row in self . rows :
if self . max_cols < len ( row ) :
self . max_cols = len ( row )
# fill with empty cells or cellspan?
fill_cells = False
if ' fill-cells ' in self . directive . options :
fill_cells = True
for row in self . rows :
x = self . max_cols - len ( row )
if x and not fill_cells :
if row [ - 1 ] is None :
row . append ( ( x - 1 , 0 , [ ] ) )
else :
cspan , rspan , content = row [ - 1 ]
row [ - 1 ] = ( cspan + x , rspan , content )
elif x and fill_cells :
for i in range ( x ) :
row . append ( ( 0 , 0 , nodes . comment ( ) ) )
def pprint ( self ) :
# for debugging
retVal = " [ "
for row in self . rows :
retVal + = " [ "
for col in row :
if col is None :
retVal + = ( ' %r ' % col )
retVal + = " \n , "
else :
content = col [ 2 ] [ 0 ] . astext ( )
if len ( content ) > 30 :
content = content [ : 30 ] + " ... "
retVal + = ( ' (cspan= %s , rspan= %s , %r ) '
% ( col [ 0 ] , col [ 1 ] , content ) )
retVal + = " ] \n , "
retVal = retVal [ : - 2 ]
retVal + = " ] \n , "
retVal = retVal [ : - 2 ]
return retVal + " ] "
def parseRowItem ( self , rowItem , rowNum ) :
row = [ ]
childNo = 0
error = False
cell = None
target = None
for child in rowItem :
if ( isinstance ( child , nodes . comment )
or isinstance ( child , nodes . system_message ) ) :
pass
elif isinstance ( child , nodes . target ) :
target = child
elif isinstance ( child , nodes . bullet_list ) :
childNo + = 1
cell = child
else :
error = True
break
if childNo != 1 or error :
self . raiseError (
' Error parsing content block for the " %s " directive: '
' two-level bullet list expected, but row %s does not '
' contain a second-level bullet list. '
% ( self . directive . name , rowNum + 1 ) )
for cellItem in cell :
cspan , rspan , cellElements = self . parseCellItem ( cellItem )
if target is not None :
cellElements . insert ( 0 , target )
row . append ( ( cspan , rspan , cellElements ) )
return row
def parseCellItem ( self , cellItem ) :
# search and remove cspan, rspan colspec from the first element in
# this listItem (field).
cspan = rspan = 0
if not len ( cellItem ) :
return cspan , rspan , [ ]
for elem in cellItem [ 0 ] :
if isinstance ( elem , colSpan ) :
cspan = elem . get ( " span " )
elem . parent . remove ( elem )
continue
if isinstance ( elem , rowSpan ) :
rspan = elem . get ( " span " )
elem . parent . remove ( elem )
continue
return cspan , rspan , cellItem [ : ]