brintos

brintos / linux-shallow public Read only

0
0
Text · 13.0 KiB · 16bea06 Raw
365 lines · python
1#!/usr/bin/env python32# -*- coding: utf-8; mode: python -*-3# pylint: disable=C0330, R0903, R09124 5u"""6    flat-table7    ~~~~~~~~~~8 9    Implementation of the ``flat-table`` reST-directive.10 11    :copyright:  Copyright (C) 2016  Markus Heiser12    :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 to15    the ``list-table`` with some additional features:16 17    * *column-span*: with the role ``cspan`` a cell can be extended through18      additional columns19 20    * *row-span*: with the role ``rspan`` a cell can be extended through21      additional rows22 23    * *auto span* rightmost cell of a table row over the missing cells on the24      right side of that table-row.  With Option ``:fill-cells:`` this behavior25      can be changed from *auto span* to *auto fill*, which automatically inserts26      (empty) cells instead of spanning the last cell.27 28    Options:29 30    * header-rows:   [int] count of header rows31    * stub-columns:  [int] count of stub columns32    * widths:        [[int] [int] ... ] widths of columns33    * fill-cells:    instead of autospann missing cells, insert missing cells34 35    roles:36 37    * cspan: [int] additionale columns (*morecols*)38    * rspan: [int] additionale rows (*morerows*)39"""40 41# ==============================================================================42# imports43# ==============================================================================44 45from docutils import nodes46from docutils.parsers.rst import directives, roles47from docutils.parsers.rst.directives.tables import Table48from docutils.utils import SystemMessagePropagation49 50# ==============================================================================51# common globals52# ==============================================================================53 54__version__  = '1.0'55 56# ==============================================================================57def setup(app):58# ==============================================================================59 60    app.add_directive("flat-table", FlatTable)61    roles.register_local_role('cspan', c_span)62    roles.register_local_role('rspan', r_span)63 64    return dict(65        version = __version__,66        parallel_read_safe = True,67        parallel_write_safe = True68    )69 70# ==============================================================================71def c_span(name, rawtext, text, lineno, inliner, options=None, content=None):72# ==============================================================================73    # pylint: disable=W061374 75    options  = options if options is not None else {}76    content  = content if content is not None else []77    nodelist = [colSpan(span=int(text))]78    msglist  = []79    return nodelist, msglist80 81# ==============================================================================82def r_span(name, rawtext, text, lineno, inliner, options=None, content=None):83# ==============================================================================84    # pylint: disable=W061385 86    options  = options if options is not None else {}87    content  = content if content is not None else []88    nodelist = [rowSpan(span=int(text))]89    msglist  = []90    return nodelist, msglist91 92 93# ==============================================================================94class rowSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C032195class colSpan(nodes.General, nodes.Element): pass # pylint: disable=C0103,C032196# ==============================================================================97 98# ==============================================================================99class FlatTable(Table):100# ==============================================================================101 102    u"""FlatTable (``flat-table``) directive"""103 104    option_spec = {105        'name': directives.unchanged106        , 'class': directives.class_option107        , 'header-rows': directives.nonnegative_int108        , 'stub-columns': directives.nonnegative_int109        , 'widths': directives.positive_int_list110        , 'fill-cells' : directives.flag }111 112    def run(self):113 114        if not self.content:115            error = self.state_machine.reporter.error(116                'The "%s" directive is empty; content required.' % self.name,117                nodes.literal_block(self.block_text, self.block_text),118                line=self.lineno)119            return [error]120 121        title, messages = self.make_title()122        node = nodes.Element()          # anonymous container for parsing123        self.state.nested_parse(self.content, self.content_offset, node)124 125        tableBuilder = ListTableBuilder(self)126        tableBuilder.parseFlatTableNode(node)127        tableNode = tableBuilder.buildTableNode()128        # SDK.CONSOLE()  # print --> tableNode.asdom().toprettyxml()129        if title:130            tableNode.insert(0, title)131        return [tableNode] + messages132 133 134# ==============================================================================135class ListTableBuilder(object):136# ==============================================================================137 138    u"""Builds a table from a double-stage list"""139 140    def __init__(self, directive):141        self.directive = directive142        self.rows      = []143        self.max_cols  = 0144 145    def buildTableNode(self):146 147        colwidths    = self.directive.get_column_widths(self.max_cols)148        if isinstance(colwidths, tuple):149            # Since docutils 0.13, get_column_widths returns a (widths,150            # colwidths) tuple, where widths is a string (i.e. 'auto').151            # See https://sourceforge.net/p/docutils/patches/120/.152            colwidths = colwidths[1]153        stub_columns = self.directive.options.get('stub-columns', 0)154        header_rows  = self.directive.options.get('header-rows', 0)155 156        table = nodes.table()157        tgroup = nodes.tgroup(cols=len(colwidths))158        table += tgroup159 160 161        for colwidth in colwidths:162            colspec = nodes.colspec(colwidth=colwidth)163            # FIXME: It seems, that the stub method only works well in the164            # absence of rowspan (observed by the html builder, the docutils-xml165            # build seems OK).  This is not extraordinary, because there exists166            # no table directive (except *this* flat-table) which allows to167            # define coexistent of rowspan and stubs (there was no use-case168            # before flat-table). This should be reviewed (later).169            if stub_columns:170                colspec.attributes['stub'] = 1171                stub_columns -= 1172            tgroup += colspec173        stub_columns = self.directive.options.get('stub-columns', 0)174 175        if header_rows:176            thead = nodes.thead()177            tgroup += thead178            for row in self.rows[:header_rows]:179                thead += self.buildTableRowNode(row)180 181        tbody = nodes.tbody()182        tgroup += tbody183 184        for row in self.rows[header_rows:]:185            tbody += self.buildTableRowNode(row)186        return table187 188    def buildTableRowNode(self, row_data, classes=None):189        classes = [] if classes is None else classes190        row = nodes.row()191        for cell in row_data:192            if cell is None:193                continue194            cspan, rspan, cellElements = cell195 196            attributes = {"classes" : classes}197            if rspan:198                attributes['morerows'] = rspan199            if cspan:200                attributes['morecols'] = cspan201            entry = nodes.entry(**attributes)202            entry.extend(cellElements)203            row += entry204        return row205 206    def raiseError(self, msg):207        error =  self.directive.state_machine.reporter.error(208            msg209            , nodes.literal_block(self.directive.block_text210                                  , self.directive.block_text)211            , line = self.directive.lineno )212        raise SystemMessagePropagation(error)213 214    def parseFlatTableNode(self, node):215        u"""parses the node from a :py:class:`FlatTable` directive's body"""216 217        if len(node) != 1 or not isinstance(node[0], nodes.bullet_list):218            self.raiseError(219                'Error parsing content block for the "%s" directive: '220                'exactly one bullet list expected.' % self.directive.name )221 222        for rowNum, rowItem in enumerate(node[0]):223            row = self.parseRowItem(rowItem, rowNum)224            self.rows.append(row)225        self.roundOffTableDefinition()226 227    def roundOffTableDefinition(self):228        u"""Round off the table definition.229 230        This method rounds off the table definition in :py:member:`rows`.231 232        * This method inserts the needed ``None`` values for the missing cells233        arising from spanning cells over rows and/or columns.234 235        * recount the :py:member:`max_cols`236 237        * Autospan or fill (option ``fill-cells``) missing cells on the right238          side of the table-row239        """240 241        y = 0242        while y < len(self.rows):243            x = 0244 245            while x < len(self.rows[y]):246                cell = self.rows[y][x]247                if cell is None:248                    x += 1249                    continue250                cspan, rspan = cell[:2]251                # handle colspan in current row252                for c in range(cspan):253                    try:254                        self.rows[y].insert(x+c+1, None)255                    except: # pylint: disable=W0702256                        # the user sets ambiguous rowspans257                        pass # SDK.CONSOLE()258                # handle colspan in spanned rows259                for r in range(rspan):260                    for c in range(cspan + 1):261                        try:262                            self.rows[y+r+1].insert(x+c, None)263                        except: # pylint: disable=W0702264                            # the user sets ambiguous rowspans265                            pass # SDK.CONSOLE()266                x += 1267            y += 1268 269        # Insert the missing cells on the right side. For this, first270        # re-calculate the max columns.271 272        for row in self.rows:273            if self.max_cols < len(row):274                self.max_cols = len(row)275 276        # fill with empty cells or cellspan?277 278        fill_cells = False279        if 'fill-cells' in self.directive.options:280            fill_cells = True281 282        for row in self.rows:283            x =  self.max_cols - len(row)284            if x and not fill_cells:285                if row[-1] is None:286                    row.append( ( x - 1, 0, []) )287                else:288                    cspan, rspan, content = row[-1]289                    row[-1] = (cspan + x, rspan, content)290            elif x and fill_cells:291                for i in range(x):292                    row.append( (0, 0, nodes.comment()) )293 294    def pprint(self):295        # for debugging296        retVal = "[   "297        for row in self.rows:298            retVal += "[ "299            for col in row:300                if col is None:301                    retVal += ('%r' % col)302                    retVal += "\n    , "303                else:304                    content = col[2][0].astext()305                    if len (content) > 30:306                        content = content[:30] + "..."307                    retVal += ('(cspan=%s, rspan=%s, %r)'308                               % (col[0], col[1], content))309                    retVal += "]\n    , "310            retVal = retVal[:-2]311            retVal += "]\n  , "312        retVal = retVal[:-2]313        return retVal + "]"314 315    def parseRowItem(self, rowItem, rowNum):316        row = []317        childNo = 0318        error   = False319        cell    = None320        target  = None321 322        for child in rowItem:323            if (isinstance(child , nodes.comment)324                or isinstance(child, nodes.system_message)):325                pass326            elif isinstance(child , nodes.target):327                target = child328            elif isinstance(child, nodes.bullet_list):329                childNo += 1330                cell = child331            else:332                error = True333                break334 335        if childNo != 1 or error:336            self.raiseError(337                'Error parsing content block for the "%s" directive: '338                'two-level bullet list expected, but row %s does not '339                'contain a second-level bullet list.'340                % (self.directive.name, rowNum + 1))341 342        for cellItem in cell:343            cspan, rspan, cellElements = self.parseCellItem(cellItem)344            if target is not None:345                cellElements.insert(0, target)346            row.append( (cspan, rspan, cellElements) )347        return row348 349    def parseCellItem(self, cellItem):350        # search and remove cspan, rspan colspec from the first element in351        # this listItem (field).352        cspan = rspan = 0353        if not len(cellItem):354            return cspan, rspan, []355        for elem in cellItem[0]:356            if isinstance(elem, colSpan):357                cspan = elem.get("span")358                elem.parent.remove(elem)359                continue360            if isinstance(elem, rowSpan):361                rspan = elem.get("span")362                elem.parent.remove(elem)363                continue364        return cspan, rspan, cellItem[:]365