commit
7fced8af5d
2 changed files with 689 additions and 104 deletions
388
src/utils/mudform.py
Normal file
388
src/utils/mudform.py
Normal file
|
|
@ -0,0 +1,388 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
Evform - a way to create advanced ascii forms
|
||||||
|
|
||||||
|
|
||||||
|
This is intended for creating advanced ascii game forms, such as a
|
||||||
|
large pretty character sheet or info document.
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
absolute size of the field and will be filled with an evtable.Cell
|
||||||
|
object when displaying the form.
|
||||||
|
|
||||||
|
Example of input file testform.py:
|
||||||
|
|
||||||
|
FORMCHAR = "x"
|
||||||
|
TABLECHAR = "c"
|
||||||
|
|
||||||
|
FORM = '''
|
||||||
|
.------------------------------------------------.
|
||||||
|
| |
|
||||||
|
| Name: xxxxx1xxxxx Player: xxxxxxx2xxxxxxx |
|
||||||
|
| xxxxxxxxxxx |
|
||||||
|
| |
|
||||||
|
>----------------------------------------------<
|
||||||
|
| |
|
||||||
|
| Desc: xxxxxxxxxxx STR: x4x DEX: x5x |
|
||||||
|
| xxxxx3xxxxx INT: x6x STA: x7x |
|
||||||
|
| xxxxxxxxxxx LUC: x8x MAG: x9x |
|
||||||
|
| |
|
||||||
|
>----------------------------------------------<
|
||||||
|
| | |
|
||||||
|
| cccccccc | ccccccccccccccccccccccccccccccccccc |
|
||||||
|
| cccccccc | ccccccccccccccccccccccccccccccccccc |
|
||||||
|
| cccAcccc | ccccccccccccccccccccccccccccccccccc |
|
||||||
|
| cccccccc | ccccccccccccccccccccccccccccccccccc |
|
||||||
|
| cccccccc | cccccccccccccccccBccccccccccccccccc |
|
||||||
|
| | |
|
||||||
|
`-----------------------------------------------´
|
||||||
|
'''
|
||||||
|
|
||||||
|
The first line of the FORM string is ignored. The forms and table
|
||||||
|
markers must mark out complete, unbroken rectangles, each containing
|
||||||
|
one embedded single-character identifier (so the smallest element
|
||||||
|
possible is a 3-character wide form). The identifier can be any
|
||||||
|
character except for the FORM_CHAR and TABLE_CHAR and some of the
|
||||||
|
common ascii-art elements, like space, _ | * etc (see
|
||||||
|
INVALID_FORMCHARS in this module). Form Rectangles can have any size,
|
||||||
|
but must be separated from each other by at least one other
|
||||||
|
character's width.
|
||||||
|
|
||||||
|
Use as follows:
|
||||||
|
|
||||||
|
import mudform
|
||||||
|
|
||||||
|
# create a new form from the template
|
||||||
|
form = mudform.MudForm("path/to/testform.py")
|
||||||
|
|
||||||
|
# add data to each tagged form cell
|
||||||
|
form.map(cells={1: "Tom the Bouncer",
|
||||||
|
2: "Griatch",
|
||||||
|
3: "A sturdy fellow",
|
||||||
|
4: 12,
|
||||||
|
5: 10,
|
||||||
|
6: 5,
|
||||||
|
7: 18,
|
||||||
|
8: 10,
|
||||||
|
9: 3})
|
||||||
|
# create the MudTables
|
||||||
|
tableA = mudform.MudTable("HP","MV","MP",
|
||||||
|
table=[["**"], ["*****"], ["***"]],
|
||||||
|
border="incols")
|
||||||
|
tableB = mudform.MudTable("Skill", "Value", "Exp",
|
||||||
|
table=[["Shooting", "Herbalism", "Smithing"],
|
||||||
|
[12,14,9],["550/1200", "990/1400", "205/900"]],
|
||||||
|
border="incols")
|
||||||
|
# add the tables to the proper ids in the form
|
||||||
|
form.map(tables={"A": tableA,
|
||||||
|
"B": tableB}
|
||||||
|
print form
|
||||||
|
|
||||||
|
This produces the following result:
|
||||||
|
|
||||||
|
.------------------------------------------------.
|
||||||
|
| |
|
||||||
|
| Name: Tom the Player: Griatch |
|
||||||
|
| Bouncer |
|
||||||
|
| |
|
||||||
|
>----------------------------------------------<
|
||||||
|
| |
|
||||||
|
| Desc: A sturdy STR: 12 DEX: 10 |
|
||||||
|
| fellow INT: 5 STA: 18 |
|
||||||
|
| LUC: 10 MAG: 3 |
|
||||||
|
| |
|
||||||
|
>----------------------------------------------<
|
||||||
|
| | |
|
||||||
|
| HP|MV|MP | Skill |Value |Exp |
|
||||||
|
| ~~+~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~ |
|
||||||
|
| **|**|** | Shooting |12 |550/1200 |
|
||||||
|
| |**|* | Herbalism |14 |990/1400 |
|
||||||
|
| |* | | Smithing |9 |205/900 |
|
||||||
|
| | |
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
The marked forms have been replaced with Cells of text and with
|
||||||
|
MudTables. The form can be updated by simply re-applying form.map()
|
||||||
|
with the updated data.
|
||||||
|
|
||||||
|
When working with the template ascii file, you can use form.reload()
|
||||||
|
to re-read the template and re-apply all existing mappings.
|
||||||
|
|
||||||
|
Each component is restrained to the width and height specified by the
|
||||||
|
template, so it will resize to fit (or crop text if the area is too
|
||||||
|
small for it. If you try to fit a table into an area it cannot fit
|
||||||
|
into (when including its borders and at least one line of text), the
|
||||||
|
form will raise an error.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import copy
|
||||||
|
from src.utils.mudtable import Cell, MudTable
|
||||||
|
from src.utils.utils import all_from_module, to_str, to_unicode
|
||||||
|
|
||||||
|
# non-valid form-identifying characters (which can thus be
|
||||||
|
# used as separators between forms without being detected
|
||||||
|
# as an identifier). These should be listed in regex form.
|
||||||
|
|
||||||
|
INVALID_FORMCHARS = r"\s\/\|\\\*\_\-\#\<\>\~\^\:\;\.\,"
|
||||||
|
|
||||||
|
|
||||||
|
class MudForm(object):
|
||||||
|
"""
|
||||||
|
This object is instantiated with a text file and parses
|
||||||
|
it for rectangular form fields. It can then be fed a
|
||||||
|
mapping so as to populate the fields with fixed-width
|
||||||
|
Cell or Tablets.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, filename=None, cells=None, tables=None, form=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Initiate the form
|
||||||
|
|
||||||
|
keywords:
|
||||||
|
filename - path to template file
|
||||||
|
form - dictionary of {"CELLCHAR":char,
|
||||||
|
"TABLECHAR":char,
|
||||||
|
"FORM":templatestring}
|
||||||
|
if this is given, filename is not read.
|
||||||
|
cells - a dictionary mapping of {id:text}
|
||||||
|
tables - dictionary mapping of {id:MudTable}
|
||||||
|
|
||||||
|
other kwargs are fed as options to the Cells and MudTablets
|
||||||
|
(see mudtablet.Cell and mudtablet.MudTablet for more info).
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.filename = filename
|
||||||
|
self.input_form_dict = form
|
||||||
|
|
||||||
|
self.cells_mapping = dict((str(key), value) for key, value in cells.items()) if cells else {}
|
||||||
|
self.tables_mapping = dict((str(key), value) for key, value in tables.items()) if tables else {}
|
||||||
|
|
||||||
|
self.cellchar = "x"
|
||||||
|
self.tablechar = "c"
|
||||||
|
|
||||||
|
self.raw_form = []
|
||||||
|
self.form = []
|
||||||
|
|
||||||
|
# clean kwargs (these cannot be overridden)
|
||||||
|
kwargs.pop("enforce_size", None)
|
||||||
|
kwargs.pop("width", None)
|
||||||
|
kwargs.pop("height", None)
|
||||||
|
# table/cell options
|
||||||
|
self.options = kwargs
|
||||||
|
|
||||||
|
self.reload()
|
||||||
|
|
||||||
|
def _parse_rectangles(self, cellchar, tablechar, form, **kwargs):
|
||||||
|
"""
|
||||||
|
Parse a form for rectangular formfields identified by
|
||||||
|
formchar enclosing an identifier.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# update options given at creation with new input - this
|
||||||
|
# allows e.g. self.map() to add custom settings for individual
|
||||||
|
# cells/tables
|
||||||
|
custom_options = copy.copy(self.options)
|
||||||
|
custom_options.update(kwargs)
|
||||||
|
|
||||||
|
nform = len(form)
|
||||||
|
|
||||||
|
mapping = {}
|
||||||
|
cell_coords = {}
|
||||||
|
table_coords = {}
|
||||||
|
|
||||||
|
# Locate the identifier tags and the horizontal end coords for all forms
|
||||||
|
re_cellchar = re.compile(r"%s+([^%s%s])%s+" % (cellchar, INVALID_FORMCHARS, cellchar, cellchar))
|
||||||
|
re_tablechar = re.compile(r"%s+([^%s%s|])%s+" % (tablechar, INVALID_FORMCHARS, tablechar, tablechar))
|
||||||
|
for iy, line in enumerate(form):
|
||||||
|
# find cells
|
||||||
|
ix0 = 0
|
||||||
|
while True:
|
||||||
|
match = re_cellchar.search(line, ix0)
|
||||||
|
if match:
|
||||||
|
# get the width of the rectangle directly from the match
|
||||||
|
cell_coords[match.group(1)] = [iy, match.start(), match.end()]
|
||||||
|
ix0 = match.end()
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
# find tables
|
||||||
|
ix0 = 0
|
||||||
|
while True:
|
||||||
|
match = re_tablechar.search(line, ix0)
|
||||||
|
if match:
|
||||||
|
# get the width of the rectangle directly from the match
|
||||||
|
table_coords[match.group(1)] = [iy, match.start(), match.end()]
|
||||||
|
ix0 = match.end()
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
#print "table_coords:", table_coords
|
||||||
|
|
||||||
|
# get rectangles and assign Cells
|
||||||
|
for key, (iy, leftix, rightix) in cell_coords.items():
|
||||||
|
|
||||||
|
# scan up to find top of rectangle
|
||||||
|
dy_up = 0
|
||||||
|
if iy > 0:
|
||||||
|
for i in range(1,iy):
|
||||||
|
#print "dy_up:", [form[iy-i][ix] for ix in range(leftix, rightix)]
|
||||||
|
if all(form[iy-i][ix] == cellchar for ix in range(leftix, rightix)):
|
||||||
|
dy_up += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
# find bottom edge of rectangle
|
||||||
|
dy_down = 0
|
||||||
|
if iy < nform-1:
|
||||||
|
for i in range(1,nform-iy-1):
|
||||||
|
#print "dy_down:", [form[iy+i][ix]for ix in range(leftix, rightix)]
|
||||||
|
if all(form[iy+i][ix] == cellchar for ix in range(leftix, rightix)):
|
||||||
|
dy_down += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# we have our rectangle. Calculate size of Cell.
|
||||||
|
iyup = iy - dy_up
|
||||||
|
iydown = iy + dy_down
|
||||||
|
width = rightix - leftix
|
||||||
|
height = abs(iyup - iydown) + 1
|
||||||
|
|
||||||
|
# we have all the coordinates we need. Create Cell.
|
||||||
|
data = self.cells_mapping.get(key, "")
|
||||||
|
#if key == "1":
|
||||||
|
#print "creating cell '%s' (%s):" % (key, data)
|
||||||
|
#print "iy=%s, iyup=%s, iydown=%s, leftix=%s, rightix=%s, width=%s, height=%s" % (iy, iyup, iydown, leftix, rightix, width, height)
|
||||||
|
|
||||||
|
options = { "pad_left":0, "pad_right":0, "pad_top":0, "pad_bottom":0, "align":"l", "valign":"t", "enforce_size":True}
|
||||||
|
options.update(custom_options)
|
||||||
|
#if key=="4":
|
||||||
|
#print "options:", options
|
||||||
|
|
||||||
|
mapping[key] = (iyup, leftix, width, height, Cell(data, width=width, height=height,**options))
|
||||||
|
|
||||||
|
# get rectangles and assign Tables
|
||||||
|
for key, (iy, leftix, rightix) in table_coords.items():
|
||||||
|
|
||||||
|
# scan up to find top of rectangle
|
||||||
|
dy_up = 0
|
||||||
|
if iy > 0:
|
||||||
|
for i in range(1,iy):
|
||||||
|
#print "dy_up:", [form[iy-i][ix] for ix in range(leftix, rightix)]
|
||||||
|
if all(form[iy-i][ix] == tablechar for ix in range(leftix, rightix)):
|
||||||
|
dy_up += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
# find bottom edge of rectangle
|
||||||
|
dy_down = 0
|
||||||
|
if iy < nform-1:
|
||||||
|
for i in range(1,nform-iy-1):
|
||||||
|
#print "dy_down:", [form[iy+i][ix]for ix in range(leftix, rightix)]
|
||||||
|
if all(form[iy+i][ix] == tablechar for ix in range(leftix, rightix)):
|
||||||
|
dy_down += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# we have our rectangle. Calculate size of Table.
|
||||||
|
iyup = iy - dy_up
|
||||||
|
iydown = iy + dy_down
|
||||||
|
width = rightix - leftix
|
||||||
|
height = abs(iyup - iydown) + 1
|
||||||
|
|
||||||
|
# we have all the coordinates we need. Create Table.
|
||||||
|
table = self.tables_mapping.get(key, None)
|
||||||
|
#print "creating table '%s' (%s):" % (key, data)
|
||||||
|
#print "iy=%s, iyup=%s, iydown=%s, leftix=%s, rightix=%s, width=%s, height=%s" % (iy, iyup, iydown, leftix, rightix, width, height)
|
||||||
|
|
||||||
|
options = { "pad_left":0, "pad_right":0, "pad_top":0, "pad_bottom":0,
|
||||||
|
"align":"l", "valign":"t", "enforce_size":True}
|
||||||
|
options.update(custom_options)
|
||||||
|
#print "options:", options
|
||||||
|
|
||||||
|
if table:
|
||||||
|
table.reformat(width=width, height=height, **options)
|
||||||
|
else:
|
||||||
|
table = MudTable(width=width, height=height, **options)
|
||||||
|
mapping[key] = (iyup, leftix, width, height, table)
|
||||||
|
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
def _populate_form(self, raw_form, mapping):
|
||||||
|
"""
|
||||||
|
Insert cell contents into form at given locations
|
||||||
|
"""
|
||||||
|
form = copy.copy(raw_form)
|
||||||
|
for key, (iy0, ix0, width, height, cell_or_table) in mapping.items():
|
||||||
|
# rect is a list of <height> lines, each <width> wide
|
||||||
|
rect = cell_or_table.get()
|
||||||
|
for il, rectline in enumerate(rect):
|
||||||
|
formline = form[iy0+il]
|
||||||
|
# insert new content, replacing old
|
||||||
|
form[iy0+il] = formline = formline[:ix0] + rectline + formline[ix0+width:]
|
||||||
|
return form
|
||||||
|
|
||||||
|
def map(self, cells=None, tables=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Add mapping for form.
|
||||||
|
|
||||||
|
cells - a dictionary of {identifier:celltext}
|
||||||
|
tables - a dictionary of {identifier:table}
|
||||||
|
|
||||||
|
kwargs will be forwarded to tables/cells. See
|
||||||
|
mudtable.Cell and mudtable.MudTable for info.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# clean kwargs (these cannot be overridden)
|
||||||
|
kwargs.pop("enforce_size", None)
|
||||||
|
kwargs.pop("width", None)
|
||||||
|
kwargs.pop("height", None)
|
||||||
|
|
||||||
|
new_cells = dict((to_str(key), value) for key, value in cells.items()) if cells else {}
|
||||||
|
new_tables = dict((to_str(key), value) for key, value in tables.items()) if tables else {}
|
||||||
|
|
||||||
|
self.cells_mapping.update(new_cells)
|
||||||
|
self.tables_mapping.update(new_tables)
|
||||||
|
self.reload()
|
||||||
|
|
||||||
|
def reload(self, filename=None, form=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Creates the form from a stored file name
|
||||||
|
"""
|
||||||
|
# clean kwargs (these cannot be overridden)
|
||||||
|
kwargs.pop("enforce_size", None)
|
||||||
|
kwargs.pop("width", None)
|
||||||
|
kwargs.pop("height", None)
|
||||||
|
|
||||||
|
if form or self.input_form_dict:
|
||||||
|
datadict = form if form else self.input_form_dict
|
||||||
|
self.input_form_dict = datadict
|
||||||
|
elif filename or self.filename:
|
||||||
|
filename = filename if filename else self.filename
|
||||||
|
datadict = all_from_module(filename)
|
||||||
|
self.filename = filename
|
||||||
|
else:
|
||||||
|
datadict = {}
|
||||||
|
|
||||||
|
cellchar = to_str(datadict.get("FORMCHAR", "x"))
|
||||||
|
self.cellchar = to_str(cellchar[0] if len(cellchar) > 1 else cellchar)
|
||||||
|
tablechar = datadict.get("TABLECHAR", "c")
|
||||||
|
self.tablechar = tablechar[0] if len(tablechar) > 1 else tablechar
|
||||||
|
|
||||||
|
# split into a list of list of lines. Form can be indexed with form[iy][ix]
|
||||||
|
self.raw_form = to_unicode(datadict.get("FORM", "")).split("\n")
|
||||||
|
# strip first line
|
||||||
|
self.raw_form = self.raw_form[1:] if self.raw_form else self.raw_form
|
||||||
|
|
||||||
|
self.options.update(kwargs)
|
||||||
|
|
||||||
|
# parse and replace
|
||||||
|
self.mapping = self._parse_rectangles(self.cellchar, self.tablechar, self.raw_form, **kwargs)
|
||||||
|
self.form = self._populate_form(self.raw_form, self.mapping)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"Prints the form"
|
||||||
|
return "\n".join([to_str(line) for line in self.form])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# coding=utf-8
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Mudtable
|
Mudtable
|
||||||
|
|
@ -32,39 +32,44 @@ As seen, the table will automatically expand with empty cells to
|
||||||
make the table symmetric.
|
make the table symmetric.
|
||||||
|
|
||||||
Tables can be restricted to a given width.
|
Tables can be restricted to a given width.
|
||||||
If we created the above table with the width=50 keyword to MudTable()
|
|
||||||
and then added the extra column and row, the result would be
|
|
||||||
|
|
||||||
|
table.reformat(width=50, align="l")
|
||||||
|
|
||||||
|
(We could just have added these keywords to the table
|
||||||
|
creation call) yields the following result:
|
||||||
+-----------+------------+-----------+-----------+
|
+-----------+------------+-----------+-----------+
|
||||||
| Heading1 | Heading2 | | |
|
| Heading1 | Heading2 | | |
|
||||||
+===========+============+===========+===========+
|
+===========+============+===========+===========+
|
||||||
| 1 | 4 | 7 | This is |
|
| 1 | 4 | 7 | This is |
|
||||||
| | | | long data |
|
| | | | long data |
|
||||||
+-----------+------------+-----------+-----------+
|
+-----------+------------+-----------+-----------+
|
||||||
| | | | This is |
|
| | | | This is |
|
||||||
| 2 | 5 | 8 | even |
|
| 2 | 5 | 8 | even |
|
||||||
| | | | longer |
|
| | | | longer |
|
||||||
| | | | data |
|
| | | | data |
|
||||||
+-----------+------------+-----------+-----------+
|
+-----------+------------+-----------+-----------+
|
||||||
| 3 | 6 | 9 | |
|
| 3 | 6 | 9 | |
|
||||||
+-----------+------------+-----------+-----------+
|
+-----------+------------+-----------+-----------+
|
||||||
| This is a | | | |
|
| This is a | | | |
|
||||||
| single | | | |
|
| single | | | |
|
||||||
| row | | | |
|
| row | | | |
|
||||||
+-----------+------------+-----------+-----------+
|
+-----------+------------+-----------+-----------+
|
||||||
|
|
||||||
When adding new rows/columns their data can have its
|
When adding new rows/columns their data can have its
|
||||||
own alignments (left/center/right, top/center/bottom).
|
own alignments (left/center/right, top/center/bottom).
|
||||||
|
|
||||||
Contrary to prettytable, Mudtable does not allow
|
If the height is restricted, cells will be restricted
|
||||||
for importing from files.
|
from expanding vertically. This will lead to text
|
||||||
|
contents being cropped. Each cell can only shrink
|
||||||
|
to a minimum width and height of 1.
|
||||||
|
|
||||||
|
|
||||||
It is intended to be used with ANSIString for supporting
|
It is intended to be used with ANSIString for supporting
|
||||||
ANSI-coloured string types.
|
ANSI-coloured string types.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from textwrap import wrap
|
from textwrap import wrap
|
||||||
from copy import deepcopy
|
from copy import deepcopy, copy
|
||||||
|
|
||||||
#from src.utils.ansi import ANSIString
|
#from src.utils.ansi import ANSIString
|
||||||
|
|
||||||
|
|
@ -89,34 +94,47 @@ class Cell(object):
|
||||||
to this size.
|
to this size.
|
||||||
height - desired height of cell. it will pad
|
height - desired height of cell. it will pad
|
||||||
to this size
|
to this size
|
||||||
|
pad_width - general padding width. This can be overruled
|
||||||
|
by individual settings below
|
||||||
pad_left - number of extra pad characters on the left
|
pad_left - number of extra pad characters on the left
|
||||||
pad_right - extra pad characters on the right
|
pad_right - extra pad characters on the right
|
||||||
pad_top - extra pad lines top (will pad with vpad_char)
|
pad_top - extra pad lines top (will pad with vpad_char)
|
||||||
pad_bottom - extra pad lines bottom (will pad with vpad_char)
|
pad_bottom - extra pad lines bottom (will pad with vpad_char)
|
||||||
pad_char - pad character to use both for extra horizontal
|
|
||||||
padding
|
pad_char - pad character to use for padding. This is overruled
|
||||||
|
by individual settings below (default " ")
|
||||||
|
hpad_char - pad character to use both for extra horizontal
|
||||||
|
padding (default " ")
|
||||||
vpad_char - pad character to use for extra vertical padding
|
vpad_char - pad character to use for extra vertical padding
|
||||||
and for vertical fill (default " ")
|
and for vertical fill (default " ")
|
||||||
fill_char - character used for horizontal fill (default " ")
|
|
||||||
|
fill_char - character used to filling (expanding cells to
|
||||||
|
desired size). This can be overruled by individual
|
||||||
|
settings below.
|
||||||
|
hfill_char - character used for horizontal fill (default " ")
|
||||||
vfill_char - character used for vertical fill (default " ")
|
vfill_char - character used for vertical fill (default " ")
|
||||||
|
|
||||||
align - "l", "r" or "c", default is centered
|
align - "l", "r" or "c", default is centered
|
||||||
valign - "t", "b" or "c", default is centered
|
valign - "t", "b" or "c", default is centered
|
||||||
|
|
||||||
|
border_width -general border width. This is overruled
|
||||||
|
- by individual settings below.
|
||||||
border_left - left border width
|
border_left - left border width
|
||||||
border_right - right border width
|
border_right - right border width
|
||||||
border_top - top border width
|
border_top - top border width
|
||||||
border_bottom - bottom border width
|
border_bottom - bottom border width
|
||||||
|
|
||||||
|
border_char - this will use a single border char for all borders.
|
||||||
|
overruled by individual settings below
|
||||||
border_left_char - char used for left border
|
border_left_char - char used for left border
|
||||||
border_right_char
|
border_right_char - char used for right border
|
||||||
border_top_char
|
border_top_char - char used for top border
|
||||||
border_bottom_char
|
border_bottom_char - char user for bottom border
|
||||||
cornerchar - character used when two borders cross.
|
|
||||||
(default is "")
|
corner_char - character used when two borders cross.
|
||||||
corner_top_left - if this is given, it replaces the
|
(default is ""). This is overruled by
|
||||||
cornerchar in the upper left
|
individual settings below.
|
||||||
corner
|
corner_top_left
|
||||||
corner_top_right
|
corner_top_right
|
||||||
corner_bottom_left
|
corner_bottom_left
|
||||||
corner_bottom_right
|
corner_bottom_right
|
||||||
|
|
@ -127,38 +145,48 @@ class Cell(object):
|
||||||
than the cell growing vertically.
|
than the cell growing vertically.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.pad_left = int(kwargs.get("pad_left", 1))
|
padwidth = kwargs.get("pad_width", None)
|
||||||
self.pad_right = int(kwargs.get("pad_right", 1))
|
padwidth = int(padwidth) if padwidth is not None else None
|
||||||
self.pad_top = int( kwargs.get("pad_top", 0))
|
self.pad_left = int(kwargs.get("pad_left", padwidth if padwidth is not None else 1))
|
||||||
self.pad_bottom = int(kwargs.get("pad_bottom", 0))
|
self.pad_right = int(kwargs.get("pad_right", padwidth if padwidth is not None else 1))
|
||||||
|
self.pad_top = int( kwargs.get("pad_top", padwidth if padwidth is not None else 0))
|
||||||
|
self.pad_bottom = int(kwargs.get("pad_bottom", padwidth if padwidth is not None else 0))
|
||||||
|
|
||||||
self.enforce_size = kwargs.get("enforce_size", False)
|
self.enforce_size = kwargs.get("enforce_size", False)
|
||||||
|
|
||||||
# avoid multi-char pad_chars messing up counting
|
# avoid multi-char pad_chars messing up counting
|
||||||
pad_char = kwargs.get("pad_char", " ")
|
pad_char = kwargs.get("pad_char", " ")
|
||||||
self.pad_char = pad_char[0] if pad_char else " "
|
pad_char = pad_char[0] if pad_char else " "
|
||||||
vpad_char = kwargs.get("vpad_char", " ")
|
hpad_char = kwargs.get("hpad_char", pad_char)
|
||||||
self.vpad_char = vpad_char[0] if vpad_char else " "
|
self.hpad_char = hpad_char[0] if hpad_char else pad_char
|
||||||
|
vpad_char = kwargs.get("vpad_char", pad_char)
|
||||||
|
self.vpad_char = vpad_char[0] if vpad_char else pad_char
|
||||||
|
|
||||||
fill_char = kwargs.get("fill_char", " ")
|
fill_char = kwargs.get("fill_char", " ")
|
||||||
self.fill_char = fill_char[0] if fill_char else " "
|
fill_char = fill_char[0] if fill_char else " "
|
||||||
vfill_char = kwargs.get("vfill_char", " ")
|
hfill_char = kwargs.get("hfill_char", fill_char)
|
||||||
|
self.hfill_char = hfill_char[0] if hfill_char else " "
|
||||||
|
vfill_char = kwargs.get("vfill_char", fill_char)
|
||||||
self.vfill_char = vfill_char[0] if vfill_char else " "
|
self.vfill_char = vfill_char[0] if vfill_char else " "
|
||||||
|
|
||||||
# borders and corners
|
# borders and corners
|
||||||
self.border_left = kwargs.get("border_left", 0)
|
borderwidth = kwargs.get("border_width", 0)
|
||||||
self.border_right = kwargs.get("border_right", 0)
|
self.border_left = kwargs.get("border_left", borderwidth)
|
||||||
self.border_top = kwargs.get("border_top", 0)
|
self.border_right = kwargs.get("border_right", borderwidth)
|
||||||
self.border_bottom = kwargs.get("border_bottom", 0)
|
self.border_top = kwargs.get("border_top", borderwidth)
|
||||||
self.border_left_char = kwargs.get("border_left_char", "|")
|
self.border_bottom = kwargs.get("border_bottom", borderwidth)
|
||||||
self.border_right_char = kwargs.get("border_right_char", "|")
|
|
||||||
self.border_top_char = kwargs.get("border_topchar", "-")
|
|
||||||
self.border_bottom_char = kwargs.get("border_bottom_char", "-")
|
|
||||||
|
|
||||||
self.corner = kwargs.get("corner", "+")
|
borderchar = kwargs.get("border_char", None)
|
||||||
self.corner_top_left = kwargs.get("corner_top_left", self.corner)
|
self.border_left_char = kwargs.get("border_left_char", borderchar if borderchar else "|")
|
||||||
self.corner_top_right = kwargs.get("corner_top_right", self.corner)
|
self.border_right_char = kwargs.get("border_right_char", borderchar if borderchar else "|")
|
||||||
self.corner_bottom_left = kwargs.get("corner_bottom_left", self.corner)
|
self.border_top_char = kwargs.get("border_topchar", borderchar if borderchar else "-")
|
||||||
self.corner_bottom_right = kwargs.get("corner_bottom_right", self.corner)
|
self.border_bottom_char = kwargs.get("border_bottom_char", borderchar if borderchar else "-")
|
||||||
|
|
||||||
|
corner = kwargs.get("corner_char", "+")
|
||||||
|
self.corner_top_left = kwargs.get("corner_top_left", corner)
|
||||||
|
self.corner_top_right = kwargs.get("corner_top_right", corner)
|
||||||
|
self.corner_bottom_left = kwargs.get("corner_bottom_left", corner)
|
||||||
|
self.corner_bottom_right = kwargs.get("corner_bottom_right", corner)
|
||||||
|
|
||||||
# alignments
|
# alignments
|
||||||
self.align = kwargs.get("align", "c")
|
self.align = kwargs.get("align", "c")
|
||||||
|
|
@ -244,11 +272,11 @@ class Cell(object):
|
||||||
"Align list of rows of cell"
|
"Align list of rows of cell"
|
||||||
align = self.align
|
align = self.align
|
||||||
if align == "l":
|
if align == "l":
|
||||||
return [line.ljust(self.width, self.fill_char) for line in data]
|
return [line.ljust(self.width, self.hfill_char) for line in data]
|
||||||
elif align == "r":
|
elif align == "r":
|
||||||
return [line.rjust(self.width, self.fill_char) for line in data]
|
return [line.rjust(self.width, self.hfill_char) for line in data]
|
||||||
else:
|
else:
|
||||||
return [self._center(line, self.width, self.fill_char) for line in data]
|
return [self._center(line, self.width, self.hfill_char) for line in data]
|
||||||
|
|
||||||
def _valign(self, data):
|
def _valign(self, data):
|
||||||
"align cell vertically"
|
"align cell vertically"
|
||||||
|
|
@ -280,8 +308,8 @@ class Cell(object):
|
||||||
|
|
||||||
def _pad(self, data):
|
def _pad(self, data):
|
||||||
"Pad data with extra characters on all sides"
|
"Pad data with extra characters on all sides"
|
||||||
left = self.pad_char * self.pad_left
|
left = self.hpad_char * self.pad_left
|
||||||
right = self.pad_char * self.pad_right
|
right = self.hpad_char * self.pad_right
|
||||||
vfill = (self.width + self.pad_left + self.pad_right) * self.vpad_char
|
vfill = (self.width + self.pad_left + self.pad_right) * self.vpad_char
|
||||||
top = [vfill for i in range(self.pad_top)]
|
top = [vfill for i in range(self.pad_top)]
|
||||||
bottom = [vfill for i in range(self.pad_bottom)]
|
bottom = [vfill for i in range(self.pad_bottom)]
|
||||||
|
|
@ -293,7 +321,8 @@ class Cell(object):
|
||||||
left = self.border_left_char * self.border_left
|
left = self.border_left_char * self.border_left
|
||||||
right = self.border_right_char * self.border_right
|
right = self.border_right_char * self.border_right
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
vfill = self.corner_top_left if left else ""
|
vfill = self.corner_top_left if left else ""
|
||||||
vfill += cwidth * self.border_top_char
|
vfill += cwidth * self.border_top_char
|
||||||
|
|
@ -307,12 +336,26 @@ class Cell(object):
|
||||||
|
|
||||||
return top + [left + line + right for line in data] + bottom
|
return top + [left + line + right for line in data] + bottom
|
||||||
|
|
||||||
|
def get_min_height(self):
|
||||||
|
"""
|
||||||
|
Get the minimum possible height of cell, including at least
|
||||||
|
one line for data.
|
||||||
|
"""
|
||||||
|
return self.pad_top + self.pad_bottom + self.border_bottom + self.border_top + 1
|
||||||
|
|
||||||
|
def get_min_width(self):
|
||||||
|
"""
|
||||||
|
Get the minimum possible width of cell, including at least one
|
||||||
|
character-width for data.
|
||||||
|
"""
|
||||||
|
return self.pad_left + self.pad_right + self.border_left + self.border_right + 1
|
||||||
|
|
||||||
def get_height(self):
|
def get_height(self):
|
||||||
"Get height of cell, including padding"
|
"Get natural height of cell, including padding"
|
||||||
return len(self.formatted)
|
return len(self.formatted)
|
||||||
|
|
||||||
def get_width(self):
|
def get_width(self):
|
||||||
"Get width of cell, including padding"
|
"Get natural width of cell, including padding"
|
||||||
return len(self.formatted[0]) if self.formatted else 0
|
return len(self.formatted[0]) if self.formatted else 0
|
||||||
|
|
||||||
def replace_data(self, data, **kwargs):
|
def replace_data(self, data, **kwargs):
|
||||||
|
|
@ -333,7 +376,51 @@ class Cell(object):
|
||||||
kwargs:
|
kwargs:
|
||||||
as the class __init__
|
as the class __init__
|
||||||
"""
|
"""
|
||||||
# keywords that require manipulations
|
|
||||||
|
# keywords that require manipulation
|
||||||
|
|
||||||
|
padwidth = kwargs.get("pad_width", None)
|
||||||
|
padwidth = int(padwidth) if padwidth is not None else None
|
||||||
|
self.pad_left = int(kwargs.get("pad_left", padwidth if padwidth is not None else self.pad_left))
|
||||||
|
self.pad_right = int(kwargs.get("pad_right", padwidth if padwidth is not None else self.pad_right))
|
||||||
|
self.pad_top = int( kwargs.get("pad_top", padwidth if padwidth is not None else self.pad_top))
|
||||||
|
self.pad_bottom = int(kwargs.get("pad_bottom", padwidth if padwidth is not None else self.pad_bottom))
|
||||||
|
|
||||||
|
padchar = kwargs.pop("pad_char", None)
|
||||||
|
hpad_char = kwargs.pop("hpad_char", padchar)
|
||||||
|
self.hpad_char = hpad_char[0] if hpad_char else self.hpad_char
|
||||||
|
vpad_char = kwargs.pop("vpad_char", padchar)
|
||||||
|
self.vpad_char = vpad_char[0] if vpad_char else self.vpad_char
|
||||||
|
|
||||||
|
fillchar = kwargs.pop("fill_char", None)
|
||||||
|
hfill_char = kwargs.pop("hfill_char", fillchar)
|
||||||
|
self.hfill_char = hfill_char[0] if hfill_char else self.hfill_char
|
||||||
|
vfill_char = kwargs.pop("vfill_char", fillchar)
|
||||||
|
self.vfill_char = vfill_char[0] if vfill_char else self.vfill_char
|
||||||
|
|
||||||
|
borderwidth = kwargs.get("border_width", None)
|
||||||
|
self.border_left = kwargs.pop("border_left", borderwidth if borderwidth is not None else self.border_left)
|
||||||
|
self.border_right = kwargs.get("border_right", borderwidth if borderwidth is not None else self.border_right)
|
||||||
|
self.border_top = kwargs.get("border_top", borderwidth if borderwidth is not None else self.border_top)
|
||||||
|
self.border_bottom = kwargs.get("border_bottom", borderwidth if borderwidth is not None else self.border_bottom)
|
||||||
|
|
||||||
|
borderchar = kwargs.get("border_char", None)
|
||||||
|
self.border_left_char = kwargs.get("border_left_char", borderchar if borderchar else self.border_left_char)
|
||||||
|
self.border_right_char = kwargs.get("border_right_char", borderchar if borderchar else self.border_right_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)
|
||||||
|
|
||||||
|
corner = 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_right = kwargs.get("corner_top_right", corner if corner is not None else self.corner_top_right)
|
||||||
|
self.corner_bottom_left = kwargs.get("corner_bottom_left", corner if corner is not None else self.corner_bottom_left)
|
||||||
|
self.corner_bottom_right = kwargs.get("corner_bottom_right", corner if corner is not None else self.corner_bottom_right)
|
||||||
|
|
||||||
|
# fill all other properties
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
# Handle sizes
|
||||||
if "width" in kwargs:
|
if "width" in kwargs:
|
||||||
width = kwargs.pop("width")
|
width = kwargs.pop("width")
|
||||||
self.width = width - self.pad_left - self.pad_right - self.border_left - self.border_right
|
self.width = width - self.pad_left - self.pad_right - self.border_left - self.border_right
|
||||||
|
|
@ -345,20 +432,7 @@ class Cell(object):
|
||||||
if self.height <= 0:
|
if self.height <= 0:
|
||||||
raise Exception("Cell height too small, no room for data.")
|
raise Exception("Cell height too small, no room for data.")
|
||||||
|
|
||||||
pad_char = kwargs.pop("padchar", None)
|
# reformat (to new sizes, padding, header and borders)
|
||||||
self.pad_char = pad_char[0] if pad_char else self.pad_char
|
|
||||||
vpad_char = kwargs.pop("vpadchar", None)
|
|
||||||
self.vpad_char = vpad_char[0] if vpad_char else self.vpad_char
|
|
||||||
fill_char = kwargs.pop("fillchar", None)
|
|
||||||
self.fill_char = fill_char[0] if fill_char else self.fill_char
|
|
||||||
vfill_char = kwargs.pop("vfillchar", None)
|
|
||||||
self.vfill_char = vfill_char[0] if vfill_char else self.vfill_char
|
|
||||||
|
|
||||||
# fill all other properties
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
setattr(self, key, value)
|
|
||||||
|
|
||||||
# reformat (this is with padding)
|
|
||||||
self.formatted = self._reformat()
|
self.formatted = self._reformat()
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
|
|
@ -391,19 +465,43 @@ class MudTable(object):
|
||||||
table - list of columns (list of lists) for seeding
|
table - list of columns (list of lists) 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
|
||||||
|
as a header (like extra underlining)
|
||||||
|
|
||||||
|
pad_width - how much empty space to pad your cells with
|
||||||
|
(default is 1)
|
||||||
border - None, or one of
|
border - None, or one of
|
||||||
"table" - only a border around the whole table
|
"table" - only a border around the whole table
|
||||||
"tablecols" - table and column borders
|
"tablecols" - table and column borders
|
||||||
"header" - only border under header
|
"header" - only border under header
|
||||||
"cols" - only borders between columns
|
"cols" - only vertical borders
|
||||||
|
"incols" - vertical borders, no outer edges
|
||||||
"rows" - only borders between rows
|
"rows" - only borders between rows
|
||||||
"cells" - border around all cells
|
"cells" - border around all cells
|
||||||
|
border_width - width of table borders, if border is active.
|
||||||
|
Note that widths wider than 1 may give artifacts in the
|
||||||
|
corners. Default is 1.
|
||||||
|
corner_char - character to use in corners when border is
|
||||||
|
active.
|
||||||
|
header_line_char - characters to use for underlining
|
||||||
|
the header row (default is '~')
|
||||||
|
Requires border to be active.
|
||||||
|
|
||||||
width - fixed width of table. If not set, width is
|
width - fixed width of table. If not set, width is
|
||||||
set by the total width of each column.
|
set by the total width of each column.
|
||||||
This will resize individual columns to fit.
|
This will resize individual columns in
|
||||||
|
the vertical direction to fit.
|
||||||
|
height - fixed height of table. Defaults to unset.
|
||||||
|
Width is still given precedence. If
|
||||||
|
height is given, table cells will crop
|
||||||
|
text rather than expand vertically.
|
||||||
|
evenwidth - (default True). Used with the width keyword.
|
||||||
|
Adjusts collumns to have as even width as
|
||||||
|
possible. This often looks best also for
|
||||||
|
mixed-length tables.
|
||||||
|
|
||||||
See also Cell class for kwargs to apply to each
|
See Cell class for further kwargs. These will be passed
|
||||||
individual data cell in the table.
|
to each cell in the table.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# table itself is a 2D grid - a list of columns
|
# table itself is a 2D grid - a list of columns
|
||||||
|
|
@ -426,14 +524,28 @@ class MudTable(object):
|
||||||
self.table[ix].insert(0, heading)
|
self.table[ix].insert(0, heading)
|
||||||
else:
|
else:
|
||||||
self.table = [[heading] for heading in header]
|
self.table = [[heading] for heading in header]
|
||||||
|
# even though we inserted the header, we can still turn off
|
||||||
|
# header border underling etc. We only allow this if a header
|
||||||
|
# was actually set
|
||||||
|
self.header = kwargs.pop("header", self.header) if self.header else False
|
||||||
|
hchar = kwargs.pop("header_line_char", "~")
|
||||||
|
self.header_line_char = hchar[0] if hchar else "~"
|
||||||
|
|
||||||
border = kwargs.pop("border", None)
|
border = kwargs.pop("border", "none")
|
||||||
if not border in (None, "table", "tablecols", "header", "cols", "rows", "cells"):
|
if border is None:
|
||||||
|
border = "none"
|
||||||
|
if not border in ("none", "table", "tablecols",
|
||||||
|
"header", "incols", "cols", "rows", "cells"):
|
||||||
raise Exception("Unsupported border type: '%s'" % border)
|
raise Exception("Unsupported border type: '%s'" % border)
|
||||||
self.border = border
|
self.border = border
|
||||||
|
|
||||||
|
# border settings are passed into Cell as well (so kwargs.get and not pop)
|
||||||
|
self.border_width = kwargs.get("border_width", 1)
|
||||||
|
self.corner_char = kwargs.get("corner_char", "+")
|
||||||
|
|
||||||
self.width = kwargs.pop("width", None)
|
self.width = kwargs.pop("width", None)
|
||||||
self.horizontal = kwargs.pop("horizontal", False)
|
self.height = kwargs.pop("height", None)
|
||||||
|
self.evenwidth = kwargs.pop("evenwidth", True)
|
||||||
# size in cell cols/rows
|
# size in cell cols/rows
|
||||||
self.ncols = 0
|
self.ncols = 0
|
||||||
self.nrows = 0
|
self.nrows = 0
|
||||||
|
|
@ -480,42 +592,36 @@ class MudTable(object):
|
||||||
"add vertical border along left table edge"
|
"add vertical border along left table edge"
|
||||||
if ix == 0:
|
if ix == 0:
|
||||||
ret["border_left"] = bwidth
|
ret["border_left"] = bwidth
|
||||||
ret["border_left_char"] = vchar
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def top_edge(ret):
|
def top_edge(ret):
|
||||||
"add border along top table edge"
|
"add border along top table edge"
|
||||||
if iy == 0:
|
if iy == 0:
|
||||||
ret["border_top"] = bwidth
|
ret["border_top"] = bwidth
|
||||||
ret["border_top_char"] = hchar
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def right_edge(ret):
|
def right_edge(ret):
|
||||||
"add vertical border along right table edge"
|
"add vertical border along right table edge"
|
||||||
if ix == nx:# and 0 < iy < ny:
|
if ix == nx:# and 0 < iy < ny:
|
||||||
ret["border_right"] = bwidth
|
ret["border_right"] = bwidth
|
||||||
ret["border_right_char"] = vchar
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def bottom_edge(ret):
|
def bottom_edge(ret):
|
||||||
"add border along bottom table edge"
|
"add border along bottom table edge"
|
||||||
if iy == ny:
|
if iy == ny:
|
||||||
ret["border_bottom"] = bwidth
|
ret["border_bottom"] = bwidth
|
||||||
ret["border_bottom_char"] = hchar
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def cols(ret):
|
def cols(ret):
|
||||||
"Adding vertical borders inside the table"
|
"Adding vertical borders inside the table"
|
||||||
if 0 <= ix < nx:
|
if 0 <= ix < nx:
|
||||||
ret["border_right"] = bwidth
|
ret["border_right"] = bwidth
|
||||||
ret["border_right_char"] = vchar
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def rows(ret):
|
def rows(ret):
|
||||||
"Adding horizontal borders inside the table"
|
"Adding horizontal borders inside the table"
|
||||||
if 0 <= iy < ny:
|
if 0 <= iy < ny:
|
||||||
ret["border_bottom"] = bwidth
|
ret["border_bottom"] = bwidth
|
||||||
ret["border_bottom_char"] = hchar
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def head(ret):
|
def head(ret):
|
||||||
|
|
@ -531,22 +637,22 @@ class MudTable(object):
|
||||||
border = self.border
|
border = self.border
|
||||||
header = self.header
|
header = self.header
|
||||||
|
|
||||||
bwidth = 1
|
bwidth = self.border_width
|
||||||
headchar = "="
|
headchar = self.header_line_char
|
||||||
cchar = "+"
|
cchar = self.corner_char
|
||||||
vchar = "|"
|
|
||||||
hchar = "-"
|
# use the helper functions to define various
|
||||||
|
# table "styles"
|
||||||
|
|
||||||
if border in ("table", "tablecols","cells"):
|
if border in ("table", "tablecols","cells"):
|
||||||
ret = bottom_edge(right_edge(top_edge(left_edge(corners(ret)))))
|
ret = bottom_edge(right_edge(top_edge(left_edge(corners(ret)))))
|
||||||
headchar = "-"
|
|
||||||
if border in ("cols", "tablecols", "cells"):
|
if border in ("cols", "tablecols", "cells"):
|
||||||
ret = cols(right_edge(left_edge(ret)))
|
ret = cols(right_edge(left_edge(ret)))
|
||||||
headchar = "-"
|
if border in ("incols"):
|
||||||
|
ret = cols(ret)
|
||||||
if border in ("rows", "cells"):
|
if border in ("rows", "cells"):
|
||||||
headchar = "="
|
|
||||||
ret = rows(bottom_edge(top_edge(ret)))
|
ret = rows(bottom_edge(top_edge(ret)))
|
||||||
if header:
|
if header and not border in ("none", None):
|
||||||
ret = head(ret)
|
ret = head(ret)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -573,6 +679,7 @@ class MudTable(object):
|
||||||
# actual table. This allows us to add columns/rows
|
# actual table. This allows us to add columns/rows
|
||||||
# and re-balance over and over without issue.
|
# and re-balance over and over without issue.
|
||||||
self.worktable = deepcopy(self.table)
|
self.worktable = deepcopy(self.table)
|
||||||
|
options = copy(self.options)
|
||||||
|
|
||||||
# balance number of rows
|
# balance number of rows
|
||||||
ncols = len(self.worktable)
|
ncols = len(self.worktable)
|
||||||
|
|
@ -586,39 +693,104 @@ class MudTable(object):
|
||||||
self.ncols = ncols
|
self.ncols = ncols
|
||||||
self.nrows = nrowmax
|
self.nrows = nrowmax
|
||||||
|
|
||||||
# equalize heights for each row
|
|
||||||
cheights = [max(cell.get_height() for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)]
|
|
||||||
|
|
||||||
# add borders - these add to the width/height, so we must do this before calculating width/height
|
# add borders - these add to the width/height, so we must do this before calculating width/height
|
||||||
self._borders()
|
self._borders()
|
||||||
|
|
||||||
# equalize widths within each column
|
# equalize widths within each column
|
||||||
cwidths = [max(cell.get_width() for cell in col) for col in self.worktable]
|
cwidths = [max(cell.get_width() for cell in col) for col in self.worktable]
|
||||||
|
|
||||||
# width of worktable
|
|
||||||
if self.width:
|
if self.width:
|
||||||
# adjust widths of columns to fit in worktable width
|
# we set a table width. Horizontal cells will be evenly distributed and
|
||||||
cwidth = self.width // ncols
|
# expand vertically as needed (unless self.height is set, see below)
|
||||||
rest = self.width % ncols
|
|
||||||
# get the width of each col, spreading the rest among the first cols
|
if ncols:
|
||||||
cwidths = [cwidth + 1 if icol < rest else cwidth for icol, width in enumerate(cwidths)]
|
# get minimum possible cell widths for each row
|
||||||
|
cwidths_min = [max(cell.get_min_width() for cell in col) for col in self.worktable]
|
||||||
|
cwmin = sum(cwidths_min)
|
||||||
|
|
||||||
|
if cwmin > self.width:
|
||||||
|
# we cannot shrink any more
|
||||||
|
raise Exception("Cannot shrink table width to %s. Minimum size is %s." % (self.width, cwmin))
|
||||||
|
|
||||||
|
excess = self.width - cwmin
|
||||||
|
if self.evenwidth:
|
||||||
|
# make each collumn of equal width
|
||||||
|
for i in range(excess):
|
||||||
|
# flood-fill the minimum table starting with the smallest collumns
|
||||||
|
ci = cwidths_min.index(min(cwidths_min))
|
||||||
|
cwidths_min[ci] += 1
|
||||||
|
cwidths = cwidths_min
|
||||||
|
else:
|
||||||
|
# make each collumn expand more proportional to their data size
|
||||||
|
for i in range(excess):
|
||||||
|
# fill wider collumns first
|
||||||
|
ci = cwidths.index(max(cwidths))
|
||||||
|
cwidths_min[ci] += 1
|
||||||
|
cwidths[ci] -= 3
|
||||||
|
cwidths = cwidths_min
|
||||||
|
|
||||||
# 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):
|
for iy, cell in enumerate(col):
|
||||||
cell.reformat(width=cwidths[ix], **self.options)
|
try:
|
||||||
|
cell.reformat(width=cwidths[ix], **options)
|
||||||
|
except Exception, e:
|
||||||
|
msg = "ix=%s, iy=%s, width=%s: %s" % (ix, iy, cwidths[ix], e.message)
|
||||||
|
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)]
|
||||||
|
|
||||||
|
if self.height:
|
||||||
|
# if we are fixing the table height, it means cells must crop text instead of resizing.
|
||||||
|
if nrowmax:
|
||||||
|
|
||||||
|
# get minimum possible cell heights for each collumn
|
||||||
|
cheights_min = [max(cell.get_min_height() for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)]
|
||||||
|
chmin = sum(cheights_min)
|
||||||
|
#print "cheights_min:", cheights_min
|
||||||
|
|
||||||
|
if chmin > self.height:
|
||||||
|
# we cannot shrink any more
|
||||||
|
raise Exception("Cannot shrink table height to %s. Minimum size is %s." % (self.height, chmin))
|
||||||
|
|
||||||
|
# now we add all the extra height up to the desired table-height.
|
||||||
|
# We do this so that the tallest cells gets expanded first (and
|
||||||
|
# thus avoid getting cropped)
|
||||||
|
|
||||||
|
excess = self.height - chmin
|
||||||
|
even = self.height % 2 == 0
|
||||||
|
for i in range(excess):
|
||||||
|
# expand the cells with the most rows first
|
||||||
|
if 0 <= i < nrowmax and nrowmax > 1:
|
||||||
|
# avoid adding to header first round (looks bad on very small tables)
|
||||||
|
ci = cheights[1:].index(max(cheights[1:])) + 1
|
||||||
|
else:
|
||||||
|
ci = cheights.index(max(cheights))
|
||||||
|
cheights_min[ci] += 1
|
||||||
|
if ci == 0 and self.header:
|
||||||
|
# it doesn't look very good if header expands too fast
|
||||||
|
cheights[ci] -= 2 if even else 3
|
||||||
|
cheights[ci] -= 2 if even else 1
|
||||||
|
cheights = cheights_min
|
||||||
|
|
||||||
|
# we must tell cells to crop instead of expanding
|
||||||
|
options["enforce_size"] = True
|
||||||
|
#print "cheights2:", cheights
|
||||||
|
|
||||||
# reformat table (for vertical align)
|
# reformat table (for vertical align)
|
||||||
for ix, col in enumerate(self.worktable):
|
for ix, col in enumerate(self.worktable):
|
||||||
for iy, cell in enumerate(col):
|
for iy, cell in enumerate(col):
|
||||||
cell.reformat(height=cheights[iy], **self.options)
|
try:
|
||||||
|
cell.reformat(height=cheights[iy], **options)
|
||||||
|
except Exception, e:
|
||||||
|
msg = "ix=%s, iy=%s, height=%s: %s" % (ix, iy, cheights[iy], e.message)
|
||||||
|
raise Exception ("Error in vertical allign:\n %s" % msg)
|
||||||
|
|
||||||
# calculate actual table width/height in characters
|
# calculate actual table width/height in characters
|
||||||
self.cwidth = sum(cwidths)
|
self.cwidth = sum(cwidths)
|
||||||
self.cheight = sum(cheights)
|
self.cheight = sum(cheights)
|
||||||
|
#print "actual table width, height:", self.cwidth, self.cheight, self.width, self.height
|
||||||
|
|
||||||
def _generate_lines(self):
|
def _generate_lines(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -631,7 +803,6 @@ class MudTable(object):
|
||||||
cell_row = [col[iy] for col in self.worktable]
|
cell_row = [col[iy] for col in self.worktable]
|
||||||
# this produces a list of lists, each of equal length
|
# this produces a list of lists, each of equal length
|
||||||
cell_data = [cell.get() for cell in cell_row]
|
cell_data = [cell.get() for cell in cell_row]
|
||||||
print [len(lines) for lines in cell_data]
|
|
||||||
cell_height = min(len(lines) for lines in cell_data)
|
cell_height = min(len(lines) for lines in cell_data)
|
||||||
for iline in range(cell_height):
|
for iline in range(cell_height):
|
||||||
yield "".join(celldata[iline] for celldata in cell_data)
|
yield "".join(celldata[iline] for celldata in cell_data)
|
||||||
|
|
@ -735,6 +906,32 @@ class MudTable(object):
|
||||||
col.insert(ypos, row[icol])
|
col.insert(ypos, row[icol])
|
||||||
self._balance()
|
self._balance()
|
||||||
|
|
||||||
|
def reformat(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Force a re-shape of the entire table
|
||||||
|
"""
|
||||||
|
self.width = kwargs.pop("width", self.width)
|
||||||
|
self.height = kwargs.pop("height", self.height)
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
hchar = kwargs.pop("header_line_char", self.header_line_char)
|
||||||
|
|
||||||
|
# border settings are also passed on into Cells (so kwargs.get, not kwargs.pop)
|
||||||
|
self.header_line_char = hchar[0] if hchar else self.header_line_char
|
||||||
|
self.border_width = kwargs.get("border_width", self.border_width)
|
||||||
|
self.corner_char = kwargs.get("corner_char", self.corner_char)
|
||||||
|
self.header_line_char = kwargs.get("header_line_char", self.header_line_char)
|
||||||
|
|
||||||
|
self.options.update(kwargs)
|
||||||
|
self._balance()
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
"""
|
||||||
|
Return lines of table as a list
|
||||||
|
"""
|
||||||
|
return [line for line in self._generate_lines()]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"print table"
|
"print table"
|
||||||
return "\n".join([line for line in self._generate_lines()])
|
return "\n".join([line for line in self._generate_lines()])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue