Source code for pyRestTable.rest_table

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

#-----------------------------------------------------------------------------
# :author:    Pete R. Jemian
# :email:     prjemian@gmail.com
# :copyright: (c) 2014-2019, Pete R. Jemian
#
# Distributed under the terms of the Creative Commons Attribution 4.0 International Public License.
#
# The full license is in the file LICENSE, distributed with this software.
#-----------------------------------------------------------------------------

"""
Format a nice table in reST (restructured text)

===========================  ============================================================
User Interface               Description
===========================  ============================================================
:class:`Table`               Construct a table in reST
:meth:`addLabel`             add label for one additional column
:meth:`addRow`               add list of items for one additional row
:meth:`setLongtable`         set `longtable` attribute
:meth:`setTabularColumns`    set `use_tabular_columns` & `alignment` attributes
:meth:`reST`                 render the table in reST format
===========================  ============================================================

.. autosummary::

    ~Table
    ~example_minimal
    ~example_basic
    ~example_complicated

"""


def _prepare_results_(t):
    s = [
        t.reST(fmt='plain'),
        t.reST(fmt='simple'),
        t.reST(fmt='grid'),
        t.reST(fmt='markdown'),
        t.reST(fmt='list-table'),
        t.reST(fmt='html')
    ]
    return "\n".join(s)


[docs]def example_minimal(): """minimal example table""" t = Table() t.labels = ['x', 'y'] t.addRow([1,2]) return t
[docs]def example_basic(): """basic example table""" t = Table() t.addLabel('one') t.addLabel('two') t.addLabel('three') t.addRow( ['1,1', '1,2', '1,3',] ) t.addRow( ['2,1', '2,2', '2,3',] ) t.addRow( ['3,1', '3,2', '3,3',] ) t.addRow( ['4,1', '4,2', '4,3',] ) return t
[docs]def example_complicated(): """complicated example table""" t = Table() t.addLabel('Name\nand\nAttributes') t.addLabel('Type') t.addLabel('Units') t.addLabel('Description\n(and Occurrences)') t.addRow( ['one,\ntwo', "buckle my", "shoe.\n\n\nthree,\nfour", "..."] ) t.addRow( ['class', 'NX_FLOAT', '', None, ] ) t.addRow( range(0,4) ) t.addRow( [None, {'a':1, 'b': 'dreamy'}, 1.234, list(range(3))] ) t.setLongtable() t.setTabularColumns(True, 'l L c r'.split()) return t
[docs]class Table(object): """ Construct a table in reST (no row or column spans). :param bool use_tabular_columns: if True, embed table in Sphinx `'.. tabularcolumns:: |%s|' % alignment'` role :param [str] alignment: with `use_tabular_columns`, each list item is a column format string, as specified by LaTeX *tabulary* package format: http://sphinx-doc.org/markup/misc.html?highlight=tabularcolumns#directive-tabularcolumns :param bool longtable: with `use_tabular_columns`, if True, add Sphinx `:longtable:` directive MAIN METHODS .. autosummary:: ~addLabel ~addRow ~reST SUPPORTING METHODS .. autosummary:: ~setLongtable ~setTabularColumns ~plain_table ~simple_table ~grid_table ~list_table ~html_table """ def __init__(self): self.rows = [] self.labels = [] self.use_tabular_columns = False self.alignment = [] self.longtable = False def __str__(self): return self.reST()
[docs] def addLabel(self, text): """ add label for one additional column :param str text: column label text :return int: number of labels """ self.labels.append(text) return len(self.labels)
[docs] def addRow(self, list_of_items): """ add list of items for one additional row :param [obj] list_of_items: list of items for one complete row :return int: number of rows """ self.rows.append(list_of_items) return len(self.rows)
[docs] def setLongtable(self, state = True): """ set `longtable` attribute :param bool longtable: True | False """ self.longtable = state
[docs] def setTabularColumns(self, state=True, column_spec=None): """ set `use_tabular_columns` & `alignment` attributes :param bool state: True | False :param [str] column_spec: list of column specifications """ self.use_tabular_columns = state if state: self.alignment = column_spec or []
[docs] def reST(self, indentation = '', fmt = 'simple'): """render the table in reST format""" if len(self.alignment) == 0: # set the default column alignments self.alignment = str('L '*len(self.labels)).strip().split() if not len(self.labels) == len(self.alignment): msg = "Number of column labels is different from column width specifiers" raise IndexError(msg) return { 'complex': self.grid_table, # alias for `grid`, keep 'grid': self.grid_table, 'html': self.html_table, 'list-table': self.list_table, 'markdown' : self.markdown_table, 'md' : self.markdown_table, # alias for `markdown`, keep 'plain': self.plain_table, 'simple': self.simple_table, }[fmt](indentation)
[docs] def plain_table(self, indentation = ''): """render the table in *plain* reST format""" # maximum column widths, considering possible line breaks in each cell width = self.find_widths() # build the row format strings fmt = " ".join(["%%-%ds" % w for w in width]) + '\n' rest = '' if self.use_tabular_columns: rest += indentation rest += '.. tabularcolumns:: |%s|' % '|'.join(self.alignment) if self.longtable: rest += '\n%s%s' % (' '*4, ':longtable:') rest += '\n\n' rest += self._row(self.labels, fmt, indentation) # labels for row in self.rows: rest += self._row(row, fmt, indentation) # each row return rest
[docs] def simple_table(self, indentation = ''): """render the table in *simple* reST format""" # maximum column widths, considering possible line breaks in each cell width = self.find_widths() # build the row separators separator = " ".join(['='*w for w in width]) + '\n' fmt = " ".join(["%%-%ds" % w for w in width]) + '\n' rest = '' if self.use_tabular_columns: rest += indentation rest += '.. tabularcolumns:: |%s|' % '|'.join(self.alignment) if self.longtable: rest += '\n%s%s' % (' '*4, ':longtable:') rest += '\n\n' rest += '%s%s' % (indentation, separator) # top line of table rest += self._row(self.labels, fmt, indentation) # labels rest += '%s%s' % (indentation, separator) # end of the labels for row in self.rows: rest += self._row(row, fmt, indentation) # each row rest += '%s%s' % (indentation, separator) # end of table return rest
[docs] def grid_table(self, indentation = ''): """render the table in *grid* reST format""" # maximum column widths, considering possible line breaks in each cell width = self.find_widths() # build the row separators separator = '+' + "".join(['-'*(w+2) + '+' for w in width]) + '\n' label_sep = '+' + "".join(['='*(w+2) + '+' for w in width]) + '\n' fmt = '|' + "".join([" %%-%ds |" % w for w in width]) + '\n' rest = '' if self.use_tabular_columns: rest += indentation rest += '.. tabularcolumns:: |%s|' % '|'.join(self.alignment) if self.longtable: rest += '\n%s%s' % (' '*4, ':longtable:') rest += '\n\n' rest += '%s%s' % (indentation, separator) # top line of table rest += self._row(self.labels, fmt, indentation) # labels rest += '%s%s' % (indentation, label_sep) # end of the labels for row in self.rows: rest += self._row(row, fmt, indentation) # each row rest += '%s%s' % (indentation, separator) # row separator return rest
[docs] def markdown_table(self, indentation = ''): """ render the table in GitHub-flavored *markdown* (not reST) format see: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#tables """ # build the row separators # maximum column widths, considering possible line breaks in each cell width = [max(w,3) for w in self.find_widths()] separator = "| " +" | ".join(['-'*w for w in width]) + ' |\n' fmt = "| " + " | ".join(["%%-%ds" % w for w in width]) + ' |\n' md = '' md += self._row(self.labels, fmt, indentation) # labels md += '%s%s' % (indentation, separator) # end of the labels for row in self.rows: md += self._row(row, fmt, indentation) # each row return md
[docs] def list_table(self, indentation = ''): """ render the table in *list-table* reST format: :see: http://docutils.sourceforge.net/docs/ref/rst/directives.html .. list-table:: Frozen Delights! :widths: 15 10 30 :header-rows: 1 * - Treat - Quantity - Description * - Albatross - 2.99 - On a stick! * - Crunchy Frog - 1.49 - If we took the bones out, it wouldn't be crunchy, now would it? * - Gannet Ripple - 1.99 - On a stick! .. ... and, Yes, it _does_ work. """ def multiline(cell, prefix, indentation, fmt): r = [] for i, line in enumerate(str(cell).splitlines()): if i > 0: s = '' else: s = prefix r.append(indentation + fmt % s + line) return r widths = self.find_widths() rest = [indentation + '.. list-table:: ', ] rest.append(indentation + ' :header-rows: 1') rest.append(indentation + ' :widths: ' + ' '.join(map(str, widths))) rest.append('') fmt = '%7s' rest += multiline(self.labels[0], '* - ', indentation, fmt) for cell in self.labels[1:]: rest += multiline(cell, '- ', indentation, fmt) for row in self.rows: rest += multiline(str(row[0]), '* - ', indentation, fmt) for cell in row[1:]: if cell is None or len(str(cell).strip()) == 0: rest.append(indentation + fmt % '- ' + '') else: rest += multiline(cell, '- ', indentation, fmt) return '\n'.join(rest)
[docs] def html_table(self, indentation = ''): """render the table in *HTML*""" html = "<table>\n" html += ' <tr>\n' # start the labels html += "".join([" <th>{}</th>\n".format(k) for k in self.labels]) # labels html += ' </tr>\n' # end the labels for row in self.rows: html += ' <tr>\n' # start each row html += "".join([" <td>{}</td>\n".format(k) for k in row]) # each row html += ' </tr>\n' # end each row html += '</table>' # end of table return html
def _row(self, row, fmt, indentation = ''): """ Given a list of entry nodes in this table row, build one line of the table with one text from each entry element. The lines are separated by line breaks. """ def pick_line(text, lineNum): """ Pick the specific line of text or supply an empty string. Convenience routine when analyzing tables. """ if lineNum < len(text): s = text[lineNum] else: s = "" return s text = "" if len(row) > 0: for line_num in range( max(map(len, [str(_).split("\n") for _ in row])) ): item = [pick_line(str(r).split("\n"), line_num) for r in row] text += indentation + fmt % tuple(item) return text
[docs] def find_widths(self): """ measure the maximum width of each column, considering possible line breaks in each cell """ def col_widths(columns): result = [] for s in columns: # if multi-line, get width of biggest line widths = [len(p) for p in str(s).split("\n")] result.append(max(widths)) return result width = [] if len(self.labels) > 0: width = col_widths(self.labels) for row in self.rows: row_width = col_widths(row) if len(width) == 0: width = row_width width = list(map(max, zip(width, row_width) )) return width