From 9438e5856a6af7bea0120b962c0e4af257bc10e2 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 6 Nov 2022 10:23:20 +0100 Subject: [PATCH] Support direct EvColumn adds to EvTable. Resolves #2762 --- CHANGELOG.md | 1 + evennia/utils/evtable.py | 34 +++++++++++++----- evennia/utils/tests/test_evtable.py | 56 ++++++++++++++++++++++++++++- evennia/utils/utils.py | 31 ++++++++++------ 4 files changed, 102 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e828aab54c..4f0286b25f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -217,6 +217,7 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10 (mainly for custom align/valign). `EvCells` now makes use of `utils.justify`. - `utils.justify` now supports `align="a"` (absolute alignments. This keeps the given left indent but crops/fills to the width. Used in EvCells. +- `EvTable` now supports passing `EvColumn`s as a list directly, (`EvTable(table=[colA,colB])`) ## Evennia 0.9.5 diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index bb60a350a7..6d16a30f83 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -471,9 +471,6 @@ class EvCell: else: self.height = self.raw_height - # prepare data - # self.formatted = self._reformat() - def _crop(self, text, width): """ Apply cropping of text. @@ -853,16 +850,19 @@ class EvCell: Get data, padded and aligned in the form of a list of lines. """ - self.formatted = self._reformat() + if not self.formatted: + self.formatted = self._reformat() return self.formatted def __repr__(self): - self.formatted = self._reformat() + if not self.formatted: + self.formatted = self._reformat() return str(ANSIString("" % self.formatted)) def __str__(self): "returns cell contents on string form" - self.formatted = self._reformat() + if not self.formatted: + self.formatted = self._reformat() return str(ANSIString("\n").join(self.formatted)) @@ -1146,7 +1146,19 @@ class EvTable: self.options = kwargs # use the temporary table to generate the table on the fly, as a list of EvColumns - self.table = [EvColumn(*col, **kwargs) for col in table] + self.table = [] + for col in table: + if isinstance(col, EvColumn): + self.add_column(col, **kwargs) + elif isinstance(col, (list, tuple)): + self.table.append(EvColumn(*col, **kwargs)) + else: + raise RuntimeError( + "EvTable 'table' kwarg must be a list of EvColumns or a list-of-lists of" + f" strings. Found {type(col)}." + ) + + # self.table = [EvColumn(*col, **kwargs) for col in table] # this is the actual working table self.worktable = None @@ -1508,7 +1520,12 @@ class EvTable: options = dict(list(self.options.items()) + list(kwargs.items())) xpos = kwargs.get("xpos", None) - column = EvColumn(*args, **options) + + if args and isinstance(args[0], EvColumn): + column = args[0] + column.reformat(**kwargs) + else: + column = EvColumn(*args, **options) wtable = self.ncols htable = self.nrows @@ -1546,7 +1563,6 @@ class EvTable: xpos = min(wtable - 1, max(0, int(xpos))) self.table.insert(xpos, column) self.ncols += 1 - # self._balance() def add_row(self, *args, **kwargs): """ diff --git a/evennia/utils/tests/test_evtable.py b/evennia/utils/tests/test_evtable.py index e562452412..6c383cb75a 100644 --- a/evennia/utils/tests/test_evtable.py +++ b/evennia/utils/tests/test_evtable.py @@ -196,7 +196,8 @@ class TestEvTable(EvenniaTestCase): def test_multiple_rows(self): """ - adding a lot of rows with `.add_row`. + Adding a lot of rows with `.add_row`. + """ table = evtable.EvTable("|yHeading1|n", "|B|[GHeading2|n", "Heading3") nlines = 12 @@ -222,3 +223,56 @@ class TestEvTable(EvenniaTestCase): expected = "\n".join(expected) self._validate(expected, str(table)) + + def test_2762(self): + """ + Testing https://github.com/evennia/evennia/issues/2762 + + Extra spaces getting added to cell content + + Also testing out adding EvColumns directly to the table kwarg. + + """ + # direct table add + table = evtable.EvTable(table=[["another"]], fill_char=".", pad_char="#", width=8) + + expected = """ ++------+ +|#anot#| +|#her.#| ++------+ + """ + self._validate(expected, str(table)) + + # add with .add_column + table = evtable.EvTable(fill_char=".", pad_char="#") + table.add_column("another", width=8) + + self._validate(expected, str(table)) + + # add by passing a column to constructor directly + + colB = evtable.EvColumn("another", width=8) + + table = evtable.EvTable(table=[colB], fill_char=".", pad_char="#") + + self._validate(expected, str(table)) + + # more complex table + + colA = evtable.EvColumn("this", "is", "a", "column") # no width + colB = evtable.EvColumn("and", "another", "one", "here", width=8) + + table = evtable.EvTable(table=[colA, colB], fill_char=".", pad_char="#") + + expected = """ ++--------+-------+ +|#this..#|#and..#| +|#is....#|#anoth#| +|#......#|#er...#| +|#a.....#|#one..#| +|#column#|#here.#| ++--------+-------+ + """ + + self._validate(expected, str(table)) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 958a00c918..862dc836fe 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -2193,23 +2193,34 @@ def calledby(callerdepth=1): another function; it will print which function called it. Args: - callerdepth (int): Must be larger than 0. When > 1, it will - print the caller of the caller etc. + callerdepth (int or None): If None, show entire stack. If int, must be larger than 0. + When > 1, it will print the sequence to that depth. Returns: - calledby (str): A debug string detailing which routine called - us. + calledby (str): A debug string detailing the code that called us. """ import inspect + def _stack_display(frame): + path = os.path.sep.join(frame[1].rsplit(os.path.sep, 2)[-2:]) + return ( + f"> called by '{frame[3]}': {path}:{frame[2]} >>>" + f" {frame[4][0].strip() if frame[4] else ''}" + ) + stack = inspect.stack() - # we must step one extra level back in stack since we don't want - # to include the call of this function itself. - callerdepth = min(max(2, callerdepth + 1), len(stack) - 1) - frame = inspect.stack()[callerdepth] - path = os.path.sep.join(frame[1].rsplit(os.path.sep, 2)[-2:]) - return "[called by '%s': %s:%s %s]" % (frame[3], path, frame[2], frame[4]) + + out = [] + if callerdepth is None: + callerdepth = len(stack) - 1 + + # show range + for idepth in range(1, max(1, callerdepth + 1)): + # we must step one extra level back in stack since we don't want + # to include the call of this function itself. + out.append(_stack_display(stack[min(idepth + 1, len(stack) - 1)])) + return "\n".join(out[::-1]) def m_len(target):