Reworked EvTable to use internal EvColumn objects. This allows a new method on EvTable. Columns with a set width will not be affected by auto-balancing, allowing for mixing fixed and auto-balancing columns as suggested in #550. Changed EvTable keywords involving characters to consistently end with _char, such as corner_top_left -> corner_top_left_char.
This commit is contained in:
parent
fd7bb29505
commit
8b8fbe8101
2 changed files with 247 additions and 109 deletions
|
|
@ -1,11 +1,6 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
"""
|
"""
|
||||||
Mudform - a way to create advanced ascii forms
|
EvForm - a way to create advanced ascii forms
|
||||||
|
|
||||||
WARNING: UNDER DEVELOPMENT. Evform does currently NOT support
|
|
||||||
colour ANSI markers in the table. Non-colour forms should
|
|
||||||
work fully (so make issues if they don't).
|
|
||||||
|
|
||||||
|
|
||||||
This is intended for creating advanced ascii game forms, such as a
|
This is intended for creating advanced ascii game forms, such as a
|
||||||
large pretty character sheet or info document.
|
large pretty character sheet or info document.
|
||||||
|
|
@ -14,7 +9,7 @@ The system works on the basis of a readin template that is given in a
|
||||||
separate python file imported into the handler. This file contains
|
separate python file imported into the handler. This file contains
|
||||||
some optional settings and a string mapping out the form. The template
|
some optional settings and a string mapping out the form. The template
|
||||||
has markers in it to denounce fields to fill. The markers map the
|
has markers in it to denounce fields to fill. The markers map the
|
||||||
absolute size of the field and will be filled with an evtable.Cell
|
absolute size of the field and will be filled with an evtable.EvCell
|
||||||
object when displaying the form.
|
object when displaying the form.
|
||||||
|
|
||||||
Note, when printing examples with ANSI color, you need to wrap
|
Note, when printing examples with ANSI color, you need to wrap
|
||||||
|
|
@ -119,7 +114,7 @@ This produces the following result:
|
||||||
| | |
|
| | |
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
The marked forms have been replaced with Cells of text and with
|
The marked forms have been replaced with EvCells of text and with
|
||||||
EvTables. The form can be updated by simply re-applying form.map()
|
EvTables. The form can be updated by simply re-applying form.map()
|
||||||
with the updated data.
|
with the updated data.
|
||||||
|
|
||||||
|
|
@ -136,7 +131,7 @@ form will raise an error.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import copy
|
import copy
|
||||||
from src.utils.evtable import Cell, EvTable
|
from src.utils.evtable import EvCell, EvTable
|
||||||
from src.utils.utils import all_from_module, to_str, to_unicode
|
from src.utils.utils import all_from_module, to_str, to_unicode
|
||||||
from src.utils.ansi import ANSIString
|
from src.utils.ansi import ANSIString
|
||||||
|
|
||||||
|
|
@ -160,7 +155,7 @@ class EvForm(object):
|
||||||
This object is instantiated with a text file and parses
|
This object is instantiated with a text file and parses
|
||||||
it for rectangular form fields. It can then be fed a
|
it for rectangular form fields. It can then be fed a
|
||||||
mapping so as to populate the fields with fixed-width
|
mapping so as to populate the fields with fixed-width
|
||||||
Cell or Tablets.
|
EvCell or Tablets.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, filename=None, cells=None, tables=None, form=None, **kwargs):
|
def __init__(self, filename=None, cells=None, tables=None, form=None, **kwargs):
|
||||||
|
|
@ -176,8 +171,8 @@ class EvForm(object):
|
||||||
cells - a dictionary mapping of {id:text}
|
cells - a dictionary mapping of {id:text}
|
||||||
tables - dictionary mapping of {id:EvTable}
|
tables - dictionary mapping of {id:EvTable}
|
||||||
|
|
||||||
other kwargs are fed as options to the Cells and EvTables
|
other kwargs are fed as options to the EvCells and EvTables
|
||||||
(see evtablet.Cell and evtable.EvTable for more info).
|
(see evtablet.EvCell and evtable.EvTable for more info).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
|
|
@ -246,7 +241,7 @@ class EvForm(object):
|
||||||
#print "cell_coords:", cell_coords
|
#print "cell_coords:", cell_coords
|
||||||
#print "table_coords:", table_coords
|
#print "table_coords:", table_coords
|
||||||
|
|
||||||
# get rectangles and assign Cells
|
# get rectangles and assign EvCells
|
||||||
for key, (iy, leftix, rightix) in cell_coords.items():
|
for key, (iy, leftix, rightix) in cell_coords.items():
|
||||||
|
|
||||||
# scan up to find top of rectangle
|
# scan up to find top of rectangle
|
||||||
|
|
@ -268,13 +263,13 @@ class EvForm(object):
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
# we have our rectangle. Calculate size of Cell.
|
# we have our rectangle. Calculate size of EvCell.
|
||||||
iyup = iy - dy_up
|
iyup = iy - dy_up
|
||||||
iydown = iy + dy_down
|
iydown = iy + dy_down
|
||||||
width = rightix - leftix
|
width = rightix - leftix
|
||||||
height = abs(iyup - iydown) + 1
|
height = abs(iyup - iydown) + 1
|
||||||
|
|
||||||
# we have all the coordinates we need. Create Cell.
|
# we have all the coordinates we need. Create EvCell.
|
||||||
data = self.cells_mapping.get(key, "")
|
data = self.cells_mapping.get(key, "")
|
||||||
#if key == "1":
|
#if key == "1":
|
||||||
# print "creating cell '%s' (%s):" % (key, data)
|
# print "creating cell '%s' (%s):" % (key, data)
|
||||||
|
|
@ -285,7 +280,7 @@ class EvForm(object):
|
||||||
#if key=="4":
|
#if key=="4":
|
||||||
#print "options:", options
|
#print "options:", options
|
||||||
|
|
||||||
mapping[key] = (iyup, leftix, width, height, Cell(data, width=width, height=height,**options))
|
mapping[key] = (iyup, leftix, width, height, EvCell(data, width=width, height=height,**options))
|
||||||
|
|
||||||
# get rectangles and assign Tables
|
# get rectangles and assign Tables
|
||||||
for key, (iy, leftix, rightix) in table_coords.items():
|
for key, (iy, leftix, rightix) in table_coords.items():
|
||||||
|
|
@ -355,7 +350,7 @@ class EvForm(object):
|
||||||
tables - a dictionary of {identifier:table}
|
tables - a dictionary of {identifier:table}
|
||||||
|
|
||||||
kwargs will be forwarded to tables/cells. See
|
kwargs will be forwarded to tables/cells. See
|
||||||
evtable.Cell and evtable.EvTable for info.
|
evtable.EvCell and evtable.EvTable for info.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# clean kwargs (these cannot be overridden)
|
# clean kwargs (these cannot be overridden)
|
||||||
|
|
@ -441,3 +436,4 @@ def _test():
|
||||||
|
|
||||||
# unicode is required since the example contains non-ascii characters
|
# unicode is required since the example contains non-ascii characters
|
||||||
print unicode(form)
|
print unicode(form)
|
||||||
|
return form
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,17 @@ Result:
|
||||||
| This is a single row | | | |
|
| This is a single row | | | |
|
||||||
+----------------------+----------+---+--------------------------+
|
+----------------------+----------+---+--------------------------+
|
||||||
|
|
||||||
As seen, the table will automatically expand with empty cells to
|
As seen, the table will automatically expand with empty cells to make
|
||||||
make the table symmetric.
|
the table symmetric.
|
||||||
|
|
||||||
Tables can be restricted to a given width.
|
Tables can be restricted to a given width.
|
||||||
|
|
||||||
table.reformat(width=50, align="l")
|
table.reformat(width=50, align="l")
|
||||||
|
|
||||||
(We could just have added these keywords to the table
|
(We could just have added these keywords to the table creation call)
|
||||||
creation call) yields the following result:
|
|
||||||
|
This yields the following result:
|
||||||
|
|
||||||
+-----------+------------+-----------+-----------+
|
+-----------+------------+-----------+-----------+
|
||||||
| Heading1 | Heading2 | | |
|
| Heading1 | Heading2 | | |
|
||||||
+~~~~~~~~~~~+~~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~+
|
+~~~~~~~~~~~+~~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~+
|
||||||
|
|
@ -57,23 +59,45 @@ creation call) yields the following result:
|
||||||
| row | | | |
|
| row | | | |
|
||||||
+-----------+------------+-----------+-----------+
|
+-----------+------------+-----------+-----------+
|
||||||
|
|
||||||
When adding new rows/columns their data can have its
|
Table-columns can be individually formatted. Note that if an
|
||||||
own alignments (left/center/right, top/center/bottom).
|
individual column is set with a specific width, table auto-balancing
|
||||||
|
will not affect this column (this may lead to the full table being too
|
||||||
|
wide, so be careful mixing fixed-width columns with auto- balancing).
|
||||||
|
Here we change the width and alignment of the column at index 3
|
||||||
|
(Python starts from 0):
|
||||||
|
|
||||||
If the height is restricted, cells will be restricted
|
table.reformat_column(3, width=30, align="r")
|
||||||
from expanding vertically. This will lead to text
|
print table
|
||||||
contents being cropped. Each cell can only shrink
|
|
||||||
to a minimum width and height of 1.
|
|
||||||
|
|
||||||
|
+-----------+-------+-----+-----------------------------+---------+
|
||||||
|
| Heading1 | Headi | | | |
|
||||||
|
| | ng2 | | | |
|
||||||
|
+~~~~~~~~~~~+~~~~~~~+~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~+
|
||||||
|
| 1 | 4 | 7 | This is long data | Test1 |
|
||||||
|
+-----------+-------+-----+-----------------------------+---------+
|
||||||
|
| 2 | 5 | 8 | This is even longer data | Test3 |
|
||||||
|
+-----------+-------+-----+-----------------------------+---------+
|
||||||
|
| 3 | 6 | 9 | | Test4 |
|
||||||
|
+-----------+-------+-----+-----------------------------+---------+
|
||||||
|
| This is a | | | | |
|
||||||
|
| single | | | | |
|
||||||
|
| row | | | | |
|
||||||
|
+-----------+-------+-----+-----------------------------+---------+
|
||||||
|
|
||||||
It is intended to be used with ANSIString for supporting
|
When adding new rows/columns their data can have its own alignments
|
||||||
|
(left/center/right, top/center/bottom).
|
||||||
|
|
||||||
|
If the height is restricted, cells will be restricted from expanding
|
||||||
|
vertically. This will lead to text contents being cropped. Each cell
|
||||||
|
can only shrink to a minimum width and height of 1.
|
||||||
|
|
||||||
|
EvTable is intended to be used with ANSIString for supporting
|
||||||
ANSI-coloured string types.
|
ANSI-coloured string types.
|
||||||
|
|
||||||
When a cell is auto-wrapped across multiple lines,
|
When a cell is auto-wrapped across multiple lines, ANSI-reset
|
||||||
ANSI-reset sequences will be put at the end of each
|
sequences will be put at the end of each wrapped line. This means that
|
||||||
wrapped line. This means that the colour of a wrapped
|
the colour of a wrapped cell will not "bleed", but it also means that
|
||||||
cell will not "bleed", but it also means that eventual
|
eventual colour outside
|
||||||
colour outside
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#from textwrap import wrap
|
#from textwrap import wrap
|
||||||
|
|
@ -241,9 +265,9 @@ def fill(text, width=70, **kwargs):
|
||||||
w = ANSITextWrapper(width=width, **kwargs)
|
w = ANSITextWrapper(width=width, **kwargs)
|
||||||
return w.fill(text)
|
return w.fill(text)
|
||||||
|
|
||||||
# Cell class (see further down for the EvTable itself)
|
# EvCell class (see further down for the EvTable itself)
|
||||||
|
|
||||||
class Cell(object):
|
class EvCell(object):
|
||||||
"""
|
"""
|
||||||
Holds a single data cell for the table. A cell has a certain width
|
Holds a single data cell for the table. A cell has a certain width
|
||||||
and height and contains one or more lines of data. It can shrink
|
and height and contains one or more lines of data. It can shrink
|
||||||
|
|
@ -287,11 +311,6 @@ class Cell(object):
|
||||||
border_top - top border width
|
border_top - top border width
|
||||||
border_bottom - bottom border width
|
border_bottom - bottom border width
|
||||||
|
|
||||||
crop_string - string to use when cropping sideways,
|
|
||||||
default is '[...]'
|
|
||||||
crop - crop content of cell rather than expand vertically,
|
|
||||||
default=False
|
|
||||||
|
|
||||||
border_char - this will use a single border char for all borders.
|
border_char - this will use a single border char for all borders.
|
||||||
overruled by individual settings below
|
overruled by individual settings below
|
||||||
border_left_char - char used for left border
|
border_left_char - char used for left border
|
||||||
|
|
@ -302,10 +321,16 @@ class Cell(object):
|
||||||
corner_char - character used when two borders cross.
|
corner_char - character used when two borders cross.
|
||||||
(default is ""). This is overruled by
|
(default is ""). This is overruled by
|
||||||
individual settings below.
|
individual settings below.
|
||||||
corner_top_left
|
corner_top_left_char - char used for "nw" corner
|
||||||
corner_top_right
|
corner_top_right_char - char used for "nw" corner
|
||||||
corner_bottom_left
|
corner_bottom_left_char - char used for "sw" corner
|
||||||
corner_bottom_right
|
corner_bottom_right_char - char used for "se" corner
|
||||||
|
|
||||||
|
crop_string - string to use when cropping sideways,
|
||||||
|
default is '[...]'
|
||||||
|
crop - crop content of cell rather than expand vertically,
|
||||||
|
default=False
|
||||||
|
|
||||||
|
|
||||||
enforce_size - if true, the width/height of the
|
enforce_size - if true, the width/height of the
|
||||||
cell is strictly enforced and
|
cell is strictly enforced and
|
||||||
|
|
@ -352,11 +377,11 @@ class Cell(object):
|
||||||
self.border_top_char = kwargs.get("border_topchar", borderchar if borderchar else "-")
|
self.border_top_char = kwargs.get("border_topchar", borderchar if borderchar else "-")
|
||||||
self.border_bottom_char = kwargs.get("border_bottom_char", borderchar if borderchar else "-")
|
self.border_bottom_char = kwargs.get("border_bottom_char", borderchar if borderchar else "-")
|
||||||
|
|
||||||
corner = kwargs.get("corner_char", "+")
|
corner_char = kwargs.get("corner_char", "+")
|
||||||
self.corner_top_left = kwargs.get("corner_top_left", corner)
|
self.corner_top_left_char = kwargs.get("corner_top_left_char", corner_char)
|
||||||
self.corner_top_right = kwargs.get("corner_top_right", corner)
|
self.corner_top_right_char = kwargs.get("corner_top_right_char", corner_char)
|
||||||
self.corner_bottom_left = kwargs.get("corner_bottom_left", corner)
|
self.corner_bottom_left_char = kwargs.get("corner_bottom_left_char", corner_char)
|
||||||
self.corner_bottom_right = kwargs.get("corner_bottom_right", corner)
|
self.corner_bottom_right_char = kwargs.get("corner_bottom_right_char", corner_char)
|
||||||
|
|
||||||
# alignments
|
# alignments
|
||||||
self.align = kwargs.get("align", "l")
|
self.align = kwargs.get("align", "l")
|
||||||
|
|
@ -504,14 +529,14 @@ class Cell(object):
|
||||||
cwidth = self.width + self.pad_left + self.pad_right + \
|
cwidth = self.width + self.pad_left + self.pad_right + \
|
||||||
max(0,self.border_left-1) + max(0, self.border_right-1)
|
max(0,self.border_left-1) + max(0, self.border_right-1)
|
||||||
|
|
||||||
vfill = self.corner_top_left if left else ""
|
vfill = self.corner_top_left_char if left else ""
|
||||||
vfill += cwidth * self.border_top_char
|
vfill += cwidth * self.border_top_char
|
||||||
vfill += self.corner_top_right if right else ""
|
vfill += self.corner_top_right_char if right else ""
|
||||||
top = [vfill for i in range(self.border_top)]
|
top = [vfill for i in range(self.border_top)]
|
||||||
|
|
||||||
vfill = self.corner_bottom_left if left else ""
|
vfill = self.corner_bottom_left_char if left else ""
|
||||||
vfill += cwidth * self.border_bottom_char
|
vfill += cwidth * self.border_bottom_char
|
||||||
vfill += self.corner_bottom_right if right else ""
|
vfill += self.corner_bottom_right_char if right else ""
|
||||||
bottom = [vfill for i in range(self.border_bottom)]
|
bottom = [vfill for i in range(self.border_bottom)]
|
||||||
|
|
||||||
return top + [left + line + right for line in data] + bottom
|
return top + [left + line + right for line in data] + bottom
|
||||||
|
|
@ -552,7 +577,7 @@ class Cell(object):
|
||||||
|
|
||||||
def reformat(self, **kwargs):
|
def reformat(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Reformat the Cell with new options
|
Reformat the EvCell with new options
|
||||||
kwargs:
|
kwargs:
|
||||||
as the class __init__
|
as the class __init__
|
||||||
"""
|
"""
|
||||||
|
|
@ -590,11 +615,11 @@ class Cell(object):
|
||||||
self.border_top_char = kwargs.get("border_topchar", borderchar if borderchar else self.border_top_char)
|
self.border_top_char = kwargs.get("border_topchar", borderchar if borderchar else self.border_top_char)
|
||||||
self.border_bottom_char = kwargs.get("border_bottom_char", borderchar if borderchar else self.border_bottom_char)
|
self.border_bottom_char = kwargs.get("border_bottom_char", borderchar if borderchar else self.border_bottom_char)
|
||||||
|
|
||||||
corner = kwargs.get("corner_char", None)
|
corner_char = kwargs.get("corner_char", None)
|
||||||
self.corner_top_left = kwargs.get("corner_top_left", corner if corner is not None else self.corner_top_left)
|
self.corner_top_left_char = kwargs.get("corner_top_left", corner_char if corner_char is not None else self.corner_top_left_char)
|
||||||
self.corner_top_right = kwargs.get("corner_top_right", corner if corner is not None else self.corner_top_right)
|
self.corner_top_right_char = kwargs.get("corner_top_right", corner_char if corner_char is not None else self.corner_top_right_char)
|
||||||
self.corner_bottom_left = kwargs.get("corner_bottom_left", corner if corner is not None else self.corner_bottom_left)
|
self.corner_bottom_left_char = kwargs.get("corner_bottom_left", corner_char if corner_char is not None else self.corner_bottom_left_char)
|
||||||
self.corner_bottom_right = kwargs.get("corner_bottom_right", corner if corner is not None else self.corner_bottom_right)
|
self.corner_bottom_right_char = kwargs.get("corner_bottom_right", corner_char if corner_char is not None else self.corner_bottom_right_char)
|
||||||
|
|
||||||
# fill all other properties
|
# fill all other properties
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
|
|
@ -621,6 +646,9 @@ class Cell(object):
|
||||||
"""
|
"""
|
||||||
return self.formatted
|
return self.formatted
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return ANSIString("EvCel<%s>" % self.formatted)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"returns cell contents on string form"
|
"returns cell contents on string form"
|
||||||
return ANSIString("\n").join(self.formatted)
|
return ANSIString("\n").join(self.formatted)
|
||||||
|
|
@ -630,14 +658,107 @@ class Cell(object):
|
||||||
return unicode(ANSIString("\n").join(self.formatted))
|
return unicode(ANSIString("\n").join(self.formatted))
|
||||||
|
|
||||||
|
|
||||||
|
## EvColumn class
|
||||||
|
|
||||||
|
class EvColumn(object):
|
||||||
|
"""
|
||||||
|
Column class
|
||||||
|
|
||||||
|
This class holds a list of Cells to represent a column of a table.
|
||||||
|
It holds operations and settings that affect *all* cells in the
|
||||||
|
column.
|
||||||
|
|
||||||
|
Columns are not intended to be used stand-alone; they should be
|
||||||
|
incorporated into an EvTable (like EvCells)
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
Data for each row in the column
|
||||||
|
Keywords:
|
||||||
|
width - If set, this column will be hardcoded to have
|
||||||
|
this width, regardless of the length of data in
|
||||||
|
each individual cell. The cells will adjust
|
||||||
|
or not depending on other settings. The
|
||||||
|
EvTable will not be allowed to auto-adjust
|
||||||
|
the width of EvColumns with their width set.
|
||||||
|
All EvCell keywords are available, these settings
|
||||||
|
will be applied to every Cell in the column.
|
||||||
|
"""
|
||||||
|
self.width = kwargs.get("width", None)
|
||||||
|
self.options = kwargs
|
||||||
|
self.column = [EvCell(data, **kwargs) for data in args]
|
||||||
|
self._normalize_width()
|
||||||
|
|
||||||
|
def _normalize_width(self):
|
||||||
|
"""
|
||||||
|
Make sure to adjust the width of all cells so we form
|
||||||
|
a coherent and lined-up column.
|
||||||
|
"""
|
||||||
|
col = self.column
|
||||||
|
noptions = copy(self.options)
|
||||||
|
# use fixed width or adjust to the largest cell
|
||||||
|
noptions["width"] = self.width or noptions.get("width") or max(cell.get_width() for cell in col) if col else 0
|
||||||
|
[cell.reformat(**noptions) for cell in col]
|
||||||
|
|
||||||
|
def add_rows(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Add new cells to column. They will be inserted as
|
||||||
|
a series of rows. It will inherit the options
|
||||||
|
of the rest of the column's cells (use update to change
|
||||||
|
options).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data for the new cells
|
||||||
|
Keywords:
|
||||||
|
ypos - index position in table before which to insert the
|
||||||
|
new column. Uses Python indexing, so to insert at the top,
|
||||||
|
use ypos=0. If not given, data will be inserted at the end
|
||||||
|
of the column.
|
||||||
|
"""
|
||||||
|
ypos = kwargs.get("ypos", None)
|
||||||
|
if ypos is None or ypos > len(self.column):
|
||||||
|
# add to the end
|
||||||
|
self.column.extend([EvCell(data, **self.options) for data in args])
|
||||||
|
else:
|
||||||
|
# insert cells before given index
|
||||||
|
ypos = min(len(self.column)-1, max(0, int(ypos)))
|
||||||
|
new_cells = [EvCell(data, **self.options) for data in args]
|
||||||
|
self.column = self.column[:ypos] + new_cells + self.column[ypos:]
|
||||||
|
self._normalize_width()
|
||||||
|
|
||||||
|
def reformat(self, force_width=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Change the options for the collumn. Will not change width unless
|
||||||
|
the force_width keyword is passed (this is only sent by the
|
||||||
|
EvTable.reformat_column() method)
|
||||||
|
"""
|
||||||
|
self.width = kwargs.get("width", self.width) if force_width else self.width
|
||||||
|
self.options.update(kwargs)
|
||||||
|
self._normalize_width()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "EvColumn<%i cels>" % len(self.column)
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.column)
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.column)
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return self.column[index]
|
||||||
|
def __setitem__(self, index, value):
|
||||||
|
self.column[index] = value
|
||||||
|
def __delitem__(self, index):
|
||||||
|
del self.column[index]
|
||||||
|
|
||||||
|
|
||||||
## Main Evtable class
|
## Main Evtable class
|
||||||
|
|
||||||
class EvTable(object):
|
class EvTable(object):
|
||||||
"""
|
"""
|
||||||
Table class.
|
Table class.
|
||||||
|
|
||||||
This table implements an ordered grid of Cells, with
|
The table contains of a list of EvColumns, each consisting of EvCells so
|
||||||
all cell boundaries lining up.
|
that the result is a 2D matrix.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -646,7 +767,7 @@ class EvTable(object):
|
||||||
headers for the table
|
headers for the table
|
||||||
|
|
||||||
Keywords:
|
Keywords:
|
||||||
table - list of columns (list of lists) for seeding
|
table - list of columns (lists of lists, or lists of EvColumns) for seeding
|
||||||
the table. If not given, the table will start
|
the table. If not given, the table will start
|
||||||
out empty
|
out empty
|
||||||
header - True/False - turn off header being treated
|
header - True/False - turn off header being treated
|
||||||
|
|
@ -667,11 +788,11 @@ class EvTable(object):
|
||||||
corners. Default is 1.
|
corners. Default is 1.
|
||||||
corner_char - character to use in corners when border is
|
corner_char - character to use in corners when border is
|
||||||
active.
|
active.
|
||||||
corner_top_left - character to use in upper left corner of table
|
corner_top_left_char - character to use in upper left corner of table
|
||||||
(defaults to corner_char)
|
(defaults to corner_char)
|
||||||
corner_top_right
|
corner_top_right_char
|
||||||
corner_bottom_left
|
corner_bottom_left_char
|
||||||
corner_bottom_right
|
corner_bottom_right_char
|
||||||
pretty_corners - (default True): use custom characters to make
|
pretty_corners - (default True): use custom characters to make
|
||||||
the table corners look "rounded". Uses UTF-8
|
the table corners look "rounded". Uses UTF-8
|
||||||
characters.
|
characters.
|
||||||
|
|
@ -701,16 +822,16 @@ class EvTable(object):
|
||||||
to each cell in the table.
|
to each cell in the table.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# table itself is a 2D grid - a list of columns
|
# at this point table is a 2D grid - a list of columns
|
||||||
# x is the column position, y the row
|
# x is the column position, y the row
|
||||||
self.table = kwargs.pop("table", [])
|
table = kwargs.pop("table", [])
|
||||||
|
|
||||||
# header is a list of texts. We merge it to the table's top
|
# header is a list of texts. We merge it to the table's top
|
||||||
header = list(args)
|
header = list(args)
|
||||||
self.header = header != []
|
self.header = header != []
|
||||||
if self.header:
|
if self.header:
|
||||||
if self.table:
|
if table:
|
||||||
excess = len(header) - len(self.table)
|
excess = len(header) - len(table)
|
||||||
if excess > 0:
|
if excess > 0:
|
||||||
# header bigger than table
|
# header bigger than table
|
||||||
self.table.extend([] for i in range(excess))
|
self.table.extend([] for i in range(excess))
|
||||||
|
|
@ -718,9 +839,9 @@ class EvTable(object):
|
||||||
# too short header
|
# too short header
|
||||||
header.extend(_to_ansi(["" for i in range(abs(excess))]))
|
header.extend(_to_ansi(["" for i in range(abs(excess))]))
|
||||||
for ix, heading in enumerate(header):
|
for ix, heading in enumerate(header):
|
||||||
self.table[ix].insert(0, heading)
|
table[ix].insert(0, heading)
|
||||||
else:
|
else:
|
||||||
self.table = [[heading] for heading in header]
|
table = [[heading] for heading in header]
|
||||||
# even though we inserted the header, we can still turn off
|
# even though we inserted the header, we can still turn off
|
||||||
# header border underling etc. We only allow this if a header
|
# header border underling etc. We only allow this if a header
|
||||||
# was actually set
|
# was actually set
|
||||||
|
|
@ -740,10 +861,10 @@ class EvTable(object):
|
||||||
self.border_width = kwargs.get("border_width", 1)
|
self.border_width = kwargs.get("border_width", 1)
|
||||||
self.corner_char = kwargs.get("corner_char", "+")
|
self.corner_char = kwargs.get("corner_char", "+")
|
||||||
pcorners = kwargs.pop("pretty_corners", False)
|
pcorners = kwargs.pop("pretty_corners", False)
|
||||||
self.corner_top_left = _to_ansi(kwargs.pop("corner_top_left", '.' if pcorners else self.corner_char))
|
self.corner_top_left_char = _to_ansi(kwargs.pop("corner_top_left_char", '.' if pcorners else self.corner_char))
|
||||||
self.corner_top_right = _to_ansi(kwargs.pop("corner_top_right", '.' if pcorners else self.corner_char))
|
self.corner_top_right_char = _to_ansi(kwargs.pop("corner_top_right_char", '.' if pcorners else self.corner_char))
|
||||||
self.corner_bottom_left = _to_ansi(kwargs.pop("corner_bottom_left", ' ' if pcorners else self.corner_char))
|
self.corner_bottom_left_char = _to_ansi(kwargs.pop("corner_bottom_left_char", ' ' if pcorners else self.corner_char))
|
||||||
self.corner_bottom_right = _to_ansi(kwargs.pop("corner_bottom_right", ' ' if pcorners else self.corner_char))
|
self.corner_bottom_right_char = _to_ansi(kwargs.pop("corner_bottom_right_char", ' ' if pcorners else self.corner_char))
|
||||||
|
|
||||||
self.width = kwargs.pop("width", None)
|
self.width = kwargs.pop("width", None)
|
||||||
self.height = kwargs.pop("height", None)
|
self.height = kwargs.pop("height", None)
|
||||||
|
|
@ -760,9 +881,9 @@ class EvTable(object):
|
||||||
# save options
|
# save options
|
||||||
self.options = kwargs
|
self.options = kwargs
|
||||||
|
|
||||||
if self.table:
|
# use the temporary table to generate the table on the fly, as a list of EvColumns.
|
||||||
# generate the table on the fly
|
# If the input is not iterable we assume it is an EvColumn.
|
||||||
self.table = [[Cell(data, **kwargs) for data in col] for col in self.table]
|
self.table = [col if isinstance(col, EvColumn) else EvColumn(*col, **kwargs) for col in table]
|
||||||
|
|
||||||
# this is the actual working table
|
# this is the actual working table
|
||||||
self.worktable = None
|
self.worktable = None
|
||||||
|
|
@ -784,13 +905,13 @@ class EvTable(object):
|
||||||
def corners(ret):
|
def corners(ret):
|
||||||
"Handle corners of table"
|
"Handle corners of table"
|
||||||
if ix == 0 and iy == 0:
|
if ix == 0 and iy == 0:
|
||||||
ret["corner_top_left"] = self.corner_top_left
|
ret["corner_top_left_char"] = self.corner_top_left_char
|
||||||
if ix == nx and iy == 0:
|
if ix == nx and iy == 0:
|
||||||
ret["corner_top_right"] = self.corner_top_right
|
ret["corner_top_right_char"] = self.corner_top_right_char
|
||||||
if ix == 0 and iy == ny:
|
if ix == 0 and iy == ny:
|
||||||
ret["corner_bottom_left"] = self.corner_bottom_left
|
ret["corner_bottom_left_char"] = self.corner_bottom_left_char
|
||||||
if ix == nx and iy == ny:
|
if ix == nx and iy == ny:
|
||||||
ret["corner_bottom_right"] = self.corner_bottom_right
|
ret["corner_bottom_right_char"] = self.corner_bottom_right_char
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def left_edge(ret):
|
def left_edge(ret):
|
||||||
|
|
@ -885,15 +1006,15 @@ class EvTable(object):
|
||||||
self.worktable = deepcopy(self.table)
|
self.worktable = deepcopy(self.table)
|
||||||
options = copy(self.options)
|
options = copy(self.options)
|
||||||
|
|
||||||
# balance number of rows
|
# balance number of rows to make a rectangular table
|
||||||
ncols = len(self.worktable)
|
ncols = len(self.worktable)
|
||||||
nrows = [len(col) for col in self.worktable]
|
nrows = [len(col) for col in self.worktable]
|
||||||
nrowmax = max(nrows) if nrows else 0
|
nrowmax = max(nrows) if nrows else 0
|
||||||
for icol, nrow in enumerate(nrows):
|
for icol, nrow in enumerate(nrows):
|
||||||
if nrow < nrowmax:
|
if nrow < nrowmax:
|
||||||
# add more rows
|
# add more rows to too-short columns
|
||||||
self.worktable[icol].extend([Cell("", **self.options) for i in range(nrowmax-nrow)])
|
empty_rows = ["" for i in range(nrowmax-nrow)]
|
||||||
|
self.worktable[icol].add_rows(*empty_rows)
|
||||||
self.ncols = ncols
|
self.ncols = ncols
|
||||||
self.nrows = nrowmax
|
self.nrows = nrowmax
|
||||||
|
|
||||||
|
|
@ -938,12 +1059,11 @@ class EvTable(object):
|
||||||
|
|
||||||
# reformat worktable (for width align)
|
# reformat worktable (for width align)
|
||||||
for ix, col in enumerate(self.worktable):
|
for ix, col in enumerate(self.worktable):
|
||||||
for iy, cell in enumerate(col):
|
try:
|
||||||
try:
|
col.reformat(width=cwidths[ix], **options)
|
||||||
cell.reformat(width=cwidths[ix], **options)
|
except Exception, e:
|
||||||
except Exception, e:
|
msg = "ix=%s, width=%s: %s" % (ix, cwidths[ix], e.message)
|
||||||
msg = "ix=%s, iy=%s, width=%s: %s" % (ix, iy, cwidths[ix], e.message)
|
raise #Exception ("Error in horizontal allign:\n %s" % msg)
|
||||||
raise Exception ("Error in horizontal allign:\n %s" % msg)
|
|
||||||
|
|
||||||
# equalize heights for each row (we must do this here, since it may have changed to fit new widths)
|
# equalize heights for each row (we must do this here, since it may have changed to fit new widths)
|
||||||
cheights = [max(cell.get_height() for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)]
|
cheights = [max(cell.get_height() for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)]
|
||||||
|
|
@ -1031,6 +1151,9 @@ class EvTable(object):
|
||||||
empty rows in the other columns. If too few,
|
empty rows in the other columns. If too few,
|
||||||
the new column with get new empty rows. All
|
the new column with get new empty rows. All
|
||||||
filling rows are added to the end.
|
filling rows are added to the end.
|
||||||
|
Args:
|
||||||
|
Either a single EvColumn instance or
|
||||||
|
a number of data to be used to create a new column
|
||||||
keyword-
|
keyword-
|
||||||
header - the header text for the column
|
header - the header text for the column
|
||||||
xpos - index position in table before which
|
xpos - index position in table before which
|
||||||
|
|
@ -1043,25 +1166,30 @@ class EvTable(object):
|
||||||
options = dict(self.options.items() + kwargs.items())
|
options = dict(self.options.items() + kwargs.items())
|
||||||
|
|
||||||
xpos = kwargs.get("xpos", None)
|
xpos = kwargs.get("xpos", None)
|
||||||
column = [Cell(data, **options) for data in args]
|
if args and isinstance(args[0], EvColumn):
|
||||||
|
column = args[0]
|
||||||
|
else:
|
||||||
|
column = EvColumn(*args, **options)
|
||||||
htable = self.nrows
|
htable = self.nrows
|
||||||
excess = self.ncols - htable
|
excess = self.ncols - htable
|
||||||
|
|
||||||
if excess > 0:
|
if excess > 0:
|
||||||
# we need to add new rows to table
|
# we need to add new rows to table
|
||||||
for col in self.table:
|
for col in self.table:
|
||||||
col.extend([Cell("", **options) for i in range(excess)])
|
empty_rows = ["" for i in range(excess)]
|
||||||
|
col.add_rows(*empty_rows, **options)
|
||||||
elif excess < 0:
|
elif excess < 0:
|
||||||
# we need to add new rows to new column
|
# we need to add new rows to new column
|
||||||
column.extend([Cell("", **options) for i in range(abs(excess))])
|
empty_rows = ["" for i in range(abs(excess))]
|
||||||
|
column.add_rows(*empty_rows, **options)
|
||||||
|
|
||||||
header = kwargs.get("header", None)
|
header = kwargs.get("header", None)
|
||||||
if header:
|
if header:
|
||||||
column.insert(0, Cell(unicode(header), **options))
|
column.add_rows(unicode(header), ypos=0, **options)
|
||||||
self.header = True
|
self.header = True
|
||||||
elif self.header:
|
elif self.header:
|
||||||
# we have a header already. Offset
|
# we have a header already. Offset
|
||||||
column.insert(0, Cell("", **options))
|
column.add_rows("", ypos=0, **options)
|
||||||
if xpos is None or xpos > len(self.table) - 1:
|
if xpos is None or xpos > len(self.table) - 1:
|
||||||
# add to the end
|
# add to the end
|
||||||
self.table.append(column)
|
self.table.append(column)
|
||||||
|
|
@ -1085,32 +1213,33 @@ class EvTable(object):
|
||||||
input new row. If not given, will be added
|
input new row. If not given, will be added
|
||||||
to the end. Uses Python indexing (so first row is
|
to the end. Uses Python indexing (so first row is
|
||||||
ypos=0)
|
ypos=0)
|
||||||
See Cell class for other keyword arguments
|
See EvCell class for other keyword arguments
|
||||||
"""
|
"""
|
||||||
# this will replace default options with new ones without changing default
|
# this will replace default options with new ones without changing default
|
||||||
|
row = list(args)
|
||||||
options = dict(self.options.items() + kwargs.items())
|
options = dict(self.options.items() + kwargs.items())
|
||||||
|
|
||||||
ypos = kwargs.get("ypos", None)
|
ypos = kwargs.get("ypos", None)
|
||||||
row = [Cell(data, **options) for data in args]
|
|
||||||
htable = len(self.table[0]) if len(self.table)>0 else 0 # assuming balanced table
|
htable = len(self.table[0]) if len(self.table)>0 else 0 # assuming balanced table
|
||||||
excess = len(row) - len(self.table)
|
excess = len(row) - len(self.table)
|
||||||
|
|
||||||
if excess > 0:
|
if excess > 0:
|
||||||
# we need to add new empty columns to table
|
# we need to add new empty columns to table
|
||||||
self.table.extend([[Cell("", **options) for i in range(htable)] for k in range(excess)])
|
empty_rows = ["" for i in range(htable)]
|
||||||
|
self.table.extend([EvColumn(*empty_rows, **options) for i in range(excess)])
|
||||||
elif excess < 0:
|
elif excess < 0:
|
||||||
# we need to add more cells to row
|
# we need to add more cells to row
|
||||||
row.extend([Cell("", **options) for i in range(abs(excess))])
|
row.extend(["" for i in range(abs(excess))])
|
||||||
|
|
||||||
if ypos is None or ypos > htable - 1:
|
if ypos is None or ypos > htable - 1:
|
||||||
# add new row to the end
|
# add new row to the end
|
||||||
for icol, col in enumerate(self.table):
|
for icol, col in enumerate(self.table):
|
||||||
col.append(row[icol])
|
col.add_rows(row[icol], **options)
|
||||||
else:
|
else:
|
||||||
# insert row elsewhere
|
# insert row elsewhere
|
||||||
ypos = min(htable-1, max(0, int(ypos)))
|
ypos = min(htable-1, max(0, int(ypos)))
|
||||||
for icol, col in enumerate(self.table):
|
for icol, col in enumerate(self.table):
|
||||||
col.insert(ypos, row[icol])
|
col.add_rows(row[icol], ypos=ypos, **options)
|
||||||
self._balance()
|
self._balance()
|
||||||
|
|
||||||
def reformat(self, **kwargs):
|
def reformat(self, **kwargs):
|
||||||
|
|
@ -1124,20 +1253,30 @@ class EvTable(object):
|
||||||
|
|
||||||
hchar = kwargs.pop("header_line_char", self.header_line_char)
|
hchar = kwargs.pop("header_line_char", self.header_line_char)
|
||||||
|
|
||||||
# border settings are also passed on into Cells (so kwargs.get, not kwargs.pop)
|
# border settings are also passed on into EvCells (so kwargs.get, not kwargs.pop)
|
||||||
self.header_line_char = hchar[0] if hchar else self.header_line_char
|
self.header_line_char = hchar[0] if hchar else self.header_line_char
|
||||||
self.border_width = kwargs.get("border_width", self.border_width)
|
self.border_width = kwargs.get("border_width", self.border_width)
|
||||||
self.corner_char = kwargs.get("corner_char", self.corner_char)
|
self.corner_char = kwargs.get("corner_char", self.corner_char)
|
||||||
self.header_line_char = kwargs.get("header_line_char", self.header_line_char)
|
self.header_line_char = kwargs.get("header_line_char", self.header_line_char)
|
||||||
|
|
||||||
self.corner_top_left = _to_ansi(kwargs.pop("corner_top_left", self.corner_char))
|
self.corner_top_left_char = _to_ansi(kwargs.pop("corner_top_left_char", self.corner_char))
|
||||||
self.corner_top_right = _to_ansi(kwargs.pop("corner_top_right", self.corner_char))
|
self.corner_top_right_char = _to_ansi(kwargs.pop("corner_top_right_char", self.corner_char))
|
||||||
self.corner_bottom_left = _to_ansi(kwargs.pop("corner_bottom_left", self.corner_char))
|
self.corner_bottom_left_char = _to_ansi(kwargs.pop("corner_bottom_left_char", self.corner_char))
|
||||||
self.corner_bottom_right = _to_ansi(kwargs.pop("corner_bottom_right", self.corner_char))
|
self.corner_bottom_right_char = _to_ansi(kwargs.pop("corner_bottom_right_char", self.corner_char))
|
||||||
|
|
||||||
self.options.update(kwargs)
|
self.options.update(kwargs)
|
||||||
self._balance()
|
self._balance()
|
||||||
|
|
||||||
|
def reformat_column(self, index, **kwargs):
|
||||||
|
"""
|
||||||
|
Sends custom options to a specific column in the table. The column
|
||||||
|
is identified by its index in the table (0-Ncol)
|
||||||
|
"""
|
||||||
|
if index > len(self.table):
|
||||||
|
raise Exception("Not a valid column index")
|
||||||
|
self.table[index].reformat(force_width=True, **kwargs)
|
||||||
|
self._balance()
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""
|
||||||
Return lines of table as a list
|
Return lines of table as a list
|
||||||
|
|
@ -1156,9 +1295,12 @@ def _test():
|
||||||
table = EvTable("{yHeading1{n", "{gHeading2{n", table=[[1,2,3],[4,5,6],[7,8,9]], border="cells")
|
table = EvTable("{yHeading1{n", "{gHeading2{n", table=[[1,2,3],[4,5,6],[7,8,9]], border="cells")
|
||||||
table.add_column("{rThis is long data{n", "{bThis is even longer data{n")
|
table.add_column("{rThis is long data{n", "{bThis is even longer data{n")
|
||||||
table.add_row("This is a single row")
|
table.add_row("This is a single row")
|
||||||
|
col = EvColumn("Test1", "Test3", "Test4", width=10)
|
||||||
|
table.add_column(col)
|
||||||
print unicode(table)
|
print unicode(table)
|
||||||
table.reformat(width=50)
|
table.reformat(width=50)
|
||||||
print unicode(table)
|
print unicode(table)
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue