Refactor EvForm code for readability and style
This commit is contained in:
parent
9c0f6a1b0f
commit
f48db64a7c
2 changed files with 270 additions and 237 deletions
|
|
@ -132,11 +132,12 @@ form will raise an error.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
|
||||||
import copy
|
import copy
|
||||||
from evennia.utils.evtable import EvCell, EvTable
|
import re
|
||||||
from evennia.utils.utils import all_from_module, to_str, is_iter
|
|
||||||
from evennia.utils.ansi import ANSIString
|
from evennia.utils.ansi import ANSIString
|
||||||
|
from evennia.utils.evtable import EvCell, EvTable
|
||||||
|
from evennia.utils.utils import all_from_module, is_iter, to_str
|
||||||
|
|
||||||
# non-valid form-identifying characters (which can thus be
|
# non-valid form-identifying characters (which can thus be
|
||||||
# used as separators between forms without being detected
|
# used as separators between forms without being detected
|
||||||
|
|
@ -148,39 +149,6 @@ INVALID_FORMCHARS = r"\s\/\|\\\*\_\-\#\<\>\~\^\:\;\.\,"
|
||||||
_ANSI_ESCAPE = re.compile(r"\|\|")
|
_ANSI_ESCAPE = re.compile(r"\|\|")
|
||||||
|
|
||||||
|
|
||||||
def _to_rect(lines):
|
|
||||||
"""
|
|
||||||
Forces all lines to be as long as the longest
|
|
||||||
|
|
||||||
Args:
|
|
||||||
lines (list): list of `ANSIString`s
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(list): list of `ANSIString`s of
|
|
||||||
same length as the longest input line
|
|
||||||
|
|
||||||
"""
|
|
||||||
maxl = max(len(line) for line in lines)
|
|
||||||
return [line + " " * (maxl - len(line)) for line in lines]
|
|
||||||
|
|
||||||
|
|
||||||
def _to_ansi(obj, regexable=False):
|
|
||||||
"convert to ANSIString"
|
|
||||||
if isinstance(obj, ANSIString):
|
|
||||||
return obj
|
|
||||||
elif isinstance(obj, str):
|
|
||||||
# since ansi will be parsed twice (here and in the normal ansi send), we have to
|
|
||||||
# escape the |-structure twice. TODO: This is tied to the default color-tag syntax
|
|
||||||
# which is not ideal for those wanting to replace/extend it ...
|
|
||||||
obj = _ANSI_ESCAPE.sub(r"||||", obj)
|
|
||||||
if isinstance(obj, dict):
|
|
||||||
return dict((key, _to_ansi(value, regexable=regexable)) for key, value in obj.items())
|
|
||||||
elif is_iter(obj):
|
|
||||||
return [_to_ansi(o) for o in obj]
|
|
||||||
else:
|
|
||||||
return ANSIString(obj, regexable=regexable)
|
|
||||||
|
|
||||||
|
|
||||||
class EvForm:
|
class EvForm:
|
||||||
"""
|
"""
|
||||||
This object is instantiated with a text file and parses
|
This object is instantiated with a text file and parses
|
||||||
|
|
@ -190,25 +158,48 @@ class EvForm:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filename=None, cells=None, tables=None, form=None, **kwargs):
|
# cell option defaults
|
||||||
|
cell_options = {
|
||||||
|
"pad_left": 0,
|
||||||
|
"pad_right": 0,
|
||||||
|
"pad_top": 0,
|
||||||
|
"pad_bottom": 0,
|
||||||
|
"align": "l",
|
||||||
|
"valign": "t",
|
||||||
|
"enforce_size": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# table option defaults
|
||||||
|
table_options = {
|
||||||
|
"pad_left": 0,
|
||||||
|
"pad_right": 0,
|
||||||
|
"pad_top": 0,
|
||||||
|
"pad_bottom": 0,
|
||||||
|
"align": "l",
|
||||||
|
"valign": "t",
|
||||||
|
"enforce_size": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, data=None, cells=None, tables=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initiate the form
|
Initiate the form
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
filename (str): Path to template file.
|
data (str or dict): Path to template file or a dict with
|
||||||
|
"formchar", "tablechar" and "form" keys (not case sensitive, so FORM etc
|
||||||
|
also works, to stay compatible with the in-file names). While "form/FORM"
|
||||||
|
is required, if FORMCHAR/TABLECHAR are not given, they will default to
|
||||||
|
'x' and 'c' respectively.
|
||||||
cells (dict): A dictionary mapping `{id: text}`
|
cells (dict): A dictionary mapping `{id: text}`
|
||||||
tables (dict): A dictionary mapping `{id: EvTable}`.
|
tables (dict): A dictionary mapping `{id: EvTable}`.
|
||||||
form (dict): A dictionary
|
|
||||||
`{"FORMCHAR":char, "TABLECHAR":char, "FORM":templatestring}`.
|
|
||||||
If this is given, filename is not read.
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
Other kwargs are fed as options to the EvCells and EvTables
|
Other kwargs are fed as options to the EvCells and EvTables
|
||||||
(see `evtable.EvCell` and `evtable.EvTable` for more info).
|
(see `evtable.EvCell` and `evtable.EvTable` for more info).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.filename = filename
|
self.indata = data # storing here so we can reload later in case of a filename
|
||||||
self.input_form_dict = form
|
self.options = self._parse_inkwargs(**kwargs)
|
||||||
|
|
||||||
self.cells_mapping = (
|
self.cells_mapping = (
|
||||||
dict((to_str(key), value) for key, value in cells.items()) if cells else {}
|
dict((to_str(key), value) for key, value in cells.items()) if cells else {}
|
||||||
|
|
@ -217,253 +208,295 @@ class EvForm:
|
||||||
dict((to_str(key), value) for key, value in tables.items()) if tables else {}
|
dict((to_str(key), value) for key, value in tables.items()) if tables else {}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.cellchar = "x"
|
# work arrays
|
||||||
self.tablechar = "c"
|
self.mapping = {}
|
||||||
|
|
||||||
self.raw_form = []
|
self.raw_form = []
|
||||||
self.form = []
|
self.form = []
|
||||||
|
|
||||||
# clean kwargs (these cannot be overridden)
|
# will parse and build the form
|
||||||
|
self.reload()
|
||||||
|
|
||||||
|
def _parse_indata(self):
|
||||||
|
"""
|
||||||
|
Parse and validate the `self.indata` property. We do this in order to be able to
|
||||||
|
re-load the evform module if indata is a filename and catch any on-file changes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The data dict parsed/generated from the in-data.
|
||||||
|
|
||||||
|
"""
|
||||||
|
data = self.indata
|
||||||
|
|
||||||
|
default_formchar = "x"
|
||||||
|
default_tablechar = "c"
|
||||||
|
|
||||||
|
if isinstance(data, str):
|
||||||
|
# a module path - read all variables from it
|
||||||
|
data = all_from_module(data)
|
||||||
|
|
||||||
|
if isinstance(data, dict):
|
||||||
|
data = {
|
||||||
|
"form": str(data.get("form", data.get("FORM", None))),
|
||||||
|
"formchar": str(data.get("formchar", data.get("FORMCHAR", default_formchar))),
|
||||||
|
"tablechar": str(data.get("tablechar", data.get("TABLECHAR", default_tablechar))),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"EvForm invalid input: {data}.")
|
||||||
|
|
||||||
|
if not data or data["form"] is None:
|
||||||
|
raise RuntimeError("Evform data must specify a valid 'form' or 'FORM'.")
|
||||||
|
|
||||||
|
# handle empty or multi-character form/tablechars (not supported)
|
||||||
|
data["formchar"] = data["formchar"][0] if data["formchar"] else default_formchar
|
||||||
|
data["tablechar"] = data["tablechar"][0] if data["tablechar"] else default_tablechar
|
||||||
|
if re.match(rf"[{INVALID_FORMCHARS}]", data["formchar"]):
|
||||||
|
raise RuntimeError(f"Invalid formchar: {data['formchar']}")
|
||||||
|
if re.match(rf"[{INVALID_FORMCHARS}]", data["tablechar"]):
|
||||||
|
raise RuntimeError(f"Invalid tablechar: {data['tablechar']}")
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _parse_inkwargs(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Validate incoming kwargs that will be passed on to become cell/table options.
|
||||||
|
|
||||||
|
Keyword Args:
|
||||||
|
any: Kwargs to process.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A validated/cleaned kwarg to use for options.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if "filename" in kwargs:
|
||||||
|
raise DeprecationWarning(
|
||||||
|
"EvForm's 'filename' kwarg was renamed to 'data' and can now accept both "
|
||||||
|
"a python path and a dict with 'FORMCHAR', 'TABLECHAR' and 'FORM' keys."
|
||||||
|
)
|
||||||
|
if "form" in kwargs:
|
||||||
|
raise DeprecationWarning(
|
||||||
|
"EvForms's 'form' kwarg was renamed to 'data' and can now accept both "
|
||||||
|
"a ptyhon path and a dict detailing the form."
|
||||||
|
)
|
||||||
|
|
||||||
|
# clean cell kwarg options (these cannot be overridden on the cell but must be controlled
|
||||||
|
# by the evform itself)
|
||||||
kwargs.pop("enforce_size", None)
|
kwargs.pop("enforce_size", None)
|
||||||
kwargs.pop("width", None)
|
kwargs.pop("width", None)
|
||||||
kwargs.pop("height", None)
|
kwargs.pop("height", None)
|
||||||
# table/cell options
|
|
||||||
self.options = kwargs
|
|
||||||
|
|
||||||
self.reload()
|
return kwargs
|
||||||
|
|
||||||
def _parse_rectangles(self, cellchar, tablechar, form, **kwargs):
|
def _parse_to_raw_form(self):
|
||||||
"""
|
"""
|
||||||
Parse a form for rectangular formfields identified by formchar
|
Forces all lines to be as long as the longest line, filling with whitespace.
|
||||||
enclosing an identifier.
|
|
||||||
|
Args:
|
||||||
|
lines (list): list of `ANSIString`s
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(list): list of `ANSIString`s of
|
||||||
|
same length as the longest input line
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
raw_form = EvForm._to_ansi(self.data["form"].split("\n"))
|
||||||
|
maxl = max(len(line) for line in raw_form)
|
||||||
|
raw_form = [line + " " * (maxl - len(line)) for line in raw_form]
|
||||||
|
if raw_form and not raw_form[0].strip():
|
||||||
|
# the first line is normally empty, we strip it.
|
||||||
|
raw_form = raw_form[1:]
|
||||||
|
return raw_form
|
||||||
|
|
||||||
# update options given at creation with new input - this
|
@staticmethod
|
||||||
# allows e.g. self.map() to add custom settings for individual
|
def _to_ansi(obj, regexable=False):
|
||||||
# cells/tables
|
"convert anything to ANSIString"
|
||||||
custom_options = copy.copy(self.options)
|
|
||||||
custom_options.update(kwargs)
|
if isinstance(obj, ANSIString):
|
||||||
|
return obj
|
||||||
|
elif isinstance(obj, str):
|
||||||
|
# since ansi will be parsed twice (here and in the normal ansi send), we have to
|
||||||
|
# escape the |-structure twice. TODO: This is tied to the default color-tag syntax
|
||||||
|
# which is not ideal for those wanting to replace/extend it ...
|
||||||
|
obj = _ANSI_ESCAPE.sub(r"||||", obj)
|
||||||
|
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return dict(
|
||||||
|
(key, EvForm._to_ansi(value, regexable=regexable)) for key, value in obj.items()
|
||||||
|
)
|
||||||
|
# regular _to_ansi (from EvTable)
|
||||||
|
elif is_iter(obj):
|
||||||
|
return [EvForm._to_ansi(o) for o in obj]
|
||||||
|
else:
|
||||||
|
return ANSIString(obj, regexable=regexable)
|
||||||
|
|
||||||
|
def _rectangles_to_mapping(self):
|
||||||
|
"""
|
||||||
|
Parse a form for rectangular formfields identified by formchar/tablechar enclosing an
|
||||||
|
identifier.
|
||||||
|
|
||||||
|
"""
|
||||||
|
formchar = self.data["formchar"]
|
||||||
|
tablechar = self.data["tablechar"]
|
||||||
|
form = self.raw_form
|
||||||
|
|
||||||
|
cell_options = copy.copy(self.cell_options)
|
||||||
|
cell_options.update(self.options)
|
||||||
|
|
||||||
|
table_options = copy.copy(self.table_options)
|
||||||
|
table_options.update(self.options)
|
||||||
|
|
||||||
nform = len(form)
|
nform = len(form)
|
||||||
|
|
||||||
mapping = {}
|
mapping = {}
|
||||||
cell_coords = {}
|
|
||||||
table_coords = {}
|
|
||||||
|
|
||||||
# Locate the identifier tags and the horizontal end coords for all forms
|
def _get_rectangles(char):
|
||||||
re_cellchar = re.compile(
|
"""Find all identified rectangles marked with given char"""
|
||||||
r"%s+([^%s%s]+)%s+" % (cellchar, INVALID_FORMCHARS, cellchar, cellchar)
|
rects = []
|
||||||
)
|
coords = {}
|
||||||
re_tablechar = re.compile(
|
regex = re.compile(rf"{char}+([^{INVALID_FORMCHARS}{char}]+){char}+")
|
||||||
r"%s+([^%s%s|+])%s+" % (tablechar, INVALID_FORMCHARS, tablechar, tablechar)
|
|
||||||
)
|
|
||||||
for iy, line in enumerate(_to_ansi(form, regexable=True)):
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# get rectangles and assign EvCells
|
# find the start/width of rectangles for each line
|
||||||
for key, (iy, leftix, rightix) in cell_coords.items():
|
for iy, line in enumerate(EvForm._to_ansi(form, regexable=True)):
|
||||||
# scan up to find top of rectangle
|
ix0 = 0
|
||||||
dy_up = 0
|
while True:
|
||||||
if iy > 0:
|
match = regex.search(line, ix0)
|
||||||
for i in range(1, iy):
|
if match:
|
||||||
if all(form[iy - i][ix] == cellchar for ix in range(leftix, rightix)):
|
# get the width of the rectangle directly from the match
|
||||||
dy_up += 1
|
coords[match.group(1)] = [iy, match.start(), match.end()]
|
||||||
else:
|
ix0 = match.end()
|
||||||
break
|
|
||||||
# find bottom edge of rectangle
|
|
||||||
dy_down = 0
|
|
||||||
if iy < nform - 1:
|
|
||||||
for i in range(1, nform - iy - 1):
|
|
||||||
if all(form[iy + i][ix] == cellchar for ix in range(leftix, rightix)):
|
|
||||||
dy_down += 1
|
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
# we have our rectangle. Calculate size of EvCell.
|
for key, (iy, leftix, rightix) in coords.items():
|
||||||
iyup = iy - dy_up
|
# scan up to find top of rectangle
|
||||||
iydown = iy + dy_down
|
dy_up = 0
|
||||||
width = rightix - leftix
|
if iy > 0:
|
||||||
height = abs(iyup - iydown) + 1
|
for i in range(1, iy):
|
||||||
|
if all(form[iy - i][ix] == char 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):
|
||||||
|
if all(form[iy + i][ix] == char for ix in range(leftix, rightix)):
|
||||||
|
dy_down += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
# we have all the coordinates we need. Create EvCell.
|
# we have our rectangle. Calculate size
|
||||||
|
iyup = iy - dy_up
|
||||||
|
iydown = iy + dy_down
|
||||||
|
width = rightix - leftix
|
||||||
|
height = abs(iyup - iydown) + 1
|
||||||
|
|
||||||
|
# store (key, y, x, width, height) of triangle
|
||||||
|
rects.append((key, iyup, leftix, width, height))
|
||||||
|
|
||||||
|
return rects
|
||||||
|
|
||||||
|
# Map EvCells into form rectangles
|
||||||
|
for (key, y, x, width, height) in _get_rectangles(formchar):
|
||||||
|
|
||||||
|
# get data to populate cell
|
||||||
data = self.cells_mapping.get(key, "")
|
data = self.cells_mapping.get(key, "")
|
||||||
# if key == "1":
|
# generate Cell on the fly
|
||||||
|
cell = EvCell(data, width=width, height=height, **cell_options)
|
||||||
|
|
||||||
options = {
|
mapping[key] = (y, x, width, height, cell)
|
||||||
"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":
|
|
||||||
|
|
||||||
mapping[key] = (
|
# Map EvTables into form rectangles
|
||||||
iyup,
|
for (key, y, x, width, height) in _get_rectangles(tablechar):
|
||||||
leftix,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
EvCell(data, width=width, height=height, **options),
|
|
||||||
)
|
|
||||||
|
|
||||||
# get rectangles and assign Tables
|
# get EvTable from mapping
|
||||||
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):
|
|
||||||
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):
|
|
||||||
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)
|
table = self.tables_mapping.get(key, None)
|
||||||
|
|
||||||
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 table:
|
if table:
|
||||||
table.reformat(width=width, height=height, **options)
|
table.reformat(width=width, height=height, **table_options)
|
||||||
else:
|
else:
|
||||||
table = EvTable(width=width, height=height, **options)
|
table = EvTable(width=width, height=height, **table_options)
|
||||||
mapping[key] = (iyup, leftix, width, height, table)
|
|
||||||
|
mapping[key] = (y, x, width, height, table)
|
||||||
|
|
||||||
return mapping
|
return mapping
|
||||||
|
|
||||||
def _populate_form(self, raw_form, mapping):
|
def _build_form(self):
|
||||||
"""
|
"""
|
||||||
Insert cell contents into form at given locations
|
Insert cell/table contents into form at given locations to create
|
||||||
|
the final result.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
form = copy.copy(raw_form)
|
form = copy.copy(self.raw_form)
|
||||||
for key, (iy0, ix0, width, height, cell_or_table) in mapping.items():
|
mapping = self.mapping
|
||||||
|
|
||||||
|
for key, (y, x, width, height, cell_or_table) in mapping.items():
|
||||||
|
|
||||||
# rect is a list of <height> lines, each <width> wide
|
# rect is a list of <height> lines, each <width> wide
|
||||||
rect = cell_or_table.get()
|
rect = cell_or_table.get()
|
||||||
for il, rectline in enumerate(rect):
|
for il, rectline in enumerate(rect):
|
||||||
formline = form[iy0 + il]
|
formline = form[y + il]
|
||||||
# insert new content, replacing old
|
# insert new content, replacing old
|
||||||
form[iy0 + il] = formline[:ix0] + rectline + formline[ix0 + width :]
|
form[y + il] = formline[:x] + rectline + formline[x + width :]
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def map(self, cells=None, tables=None, **kwargs):
|
def reload(self):
|
||||||
"""
|
"""
|
||||||
Add mapping for form.
|
Creates the form from a filename or data structure.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cells (dict): A dictionary of {identifier:celltext}
|
data (str or dict): Can be used to update an existing form using
|
||||||
tables (dict): A dictionary of {identifier:table}
|
the same cells/tables provided on initialization or using `.map()`.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Kwargs are passed through to Cel creation.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.data = self._parse_indata()
|
||||||
|
|
||||||
|
# Create raw form matrix, indexable with (y, x) coords
|
||||||
|
self.raw_form = self._parse_to_raw_form()
|
||||||
|
# parse and identify all rectangles in the form
|
||||||
|
self.mapping = self._rectangles_to_mapping()
|
||||||
|
# combine mapping with form template into a final result
|
||||||
|
self.form = self._build_form()
|
||||||
|
|
||||||
|
def map(self, cells=None, tables=None, data=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Add mapping for form. This allows for updating an existing
|
||||||
|
evform.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cells (dict): A dictionary of {identifier:celltext}. These
|
||||||
|
will be appended to the existing mappings.
|
||||||
|
tables (dict): A dictionary of {identifier:table}. Will
|
||||||
|
be appended to the existing mapping.
|
||||||
|
data (str or dict): A path to a evform module or a dict with
|
||||||
|
the needed "FORM", "TABLE/FORMCHAR" keys. Will replace
|
||||||
|
the originally initialized form.
|
||||||
|
|
||||||
|
Keyword Args:
|
||||||
|
These will be appended to the existing cell/table options.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
kwargs will be forwarded to tables/cells. See
|
kwargs will be forwarded to tables/cells. See
|
||||||
`evtable.EvCell` and `evtable.EvTable` for info.
|
`evtable.EvCell` and `evtable.EvTable` for info.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# clean kwargs (these cannot be overridden)
|
if data:
|
||||||
kwargs.pop("enforce_size", None)
|
# storing so ._parse_indata will find it during reload
|
||||||
kwargs.pop("width", None)
|
self.indata = data
|
||||||
kwargs.pop("height", None)
|
|
||||||
|
|
||||||
new_cells = dict((to_str(key), value) for key, value in cells.items()) if cells else {}
|
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 {}
|
new_tables = dict((to_str(key), value) for key, value in tables.items()) if tables else {}
|
||||||
|
|
||||||
self.cells_mapping.update(new_cells)
|
self.cells_mapping.update(new_cells)
|
||||||
self.tables_mapping.update(new_tables)
|
self.tables_mapping.update(new_tables)
|
||||||
|
|
||||||
|
self.options.update(self._parse_inkwargs(**kwargs))
|
||||||
|
|
||||||
|
# parse and build the form
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
def reload(self, filename=None, form=None, **kwargs):
|
|
||||||
"""
|
|
||||||
Creates the form from a stored file name.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
filename (str): The file to read from.
|
|
||||||
form (dict): A mapping for the form.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
Kwargs are passed through to Cel creation.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# 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]
|
|
||||||
raw_form = _to_ansi(datadict.get("FORM", "").split("\n"))
|
|
||||||
self.raw_form = _to_rect(raw_form)
|
|
||||||
|
|
||||||
# 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):
|
def __str__(self):
|
||||||
"Prints the form"
|
"Prints the form"
|
||||||
return str(ANSIString("\n").join([line for line in self.form]))
|
return str(ANSIString("\n").join([line for line in self.form]))
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ Unit tests for the EvForm text form generator
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from evennia.utils import evform, ansi, evtable
|
from evennia.utils import ansi, evform, evtable
|
||||||
|
|
||||||
|
|
||||||
class TestEvForm(TestCase):
|
class TestEvForm(TestCase):
|
||||||
|
|
@ -51,8 +51,8 @@ class TestEvForm(TestCase):
|
||||||
def _simple_form(self, form):
|
def _simple_form(self, form):
|
||||||
cellsdict = {1: "Apple", 2: "Banana", 3: "Citrus", 4: "Durian"}
|
cellsdict = {1: "Apple", 2: "Banana", 3: "Citrus", 4: "Durian"}
|
||||||
formdict = {"FORMCHAR": "x", "TABLECHAR": "c", "FORM": form}
|
formdict = {"FORMCHAR": "x", "TABLECHAR": "c", "FORM": form}
|
||||||
form = evform.EvForm(form=formdict)
|
form = evform.EvForm(formdict)
|
||||||
form.map(cellsdict)
|
form.map(cells=cellsdict)
|
||||||
form = ansi.strip_ansi(str(form))
|
form = ansi.strip_ansi(str(form))
|
||||||
# this is necessary since editors/black tend to strip lines spaces
|
# this is necessary since editors/black tend to strip lines spaces
|
||||||
# from the end of lines for the comparison strings.
|
# from the end of lines for the comparison strings.
|
||||||
|
|
@ -112,7 +112,7 @@ class TestEvForm(TestCase):
|
||||||
def test_ansi_escape(self):
|
def test_ansi_escape(self):
|
||||||
# note that in a msg() call, the result would be the correct |-----,
|
# note that in a msg() call, the result would be the correct |-----,
|
||||||
# in a print, ansi only gets called once, so ||----- is the result
|
# in a print, ansi only gets called once, so ||----- is the result
|
||||||
self.assertEqual(str(evform.EvForm(form={"FORM": "\n||-----"})), "||-----")
|
self.assertEqual(str(evform.EvForm({"FORM": "\n||-----"})), "||-----")
|
||||||
|
|
||||||
def test_stacked_form(self):
|
def test_stacked_form(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -241,7 +241,7 @@ class TestEvFormParallelTables(TestCase):
|
||||||
"""
|
"""
|
||||||
Build form to check for error.
|
Build form to check for error.
|
||||||
"""
|
"""
|
||||||
form = evform.EvForm(form=self.formdict)
|
form = evform.EvForm(self.formdict)
|
||||||
form.map(
|
form.map(
|
||||||
cells={
|
cells={
|
||||||
"1": self.text1,
|
"1": self.text1,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue