Skip to content

Commit

Permalink
✨ [+feature] Added CreateTable to TextwrapEx (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrownell authored Jul 7, 2024
2 parents 042358d + 181ee03 commit cb243d9
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 0 deletions.
97 changes: 97 additions & 0 deletions src/dbrownell_Common/TextwrapEx.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import math
import textwrap

from enum import auto, Enum
from typing import Callable, Optional


Expand Down Expand Up @@ -45,6 +46,37 @@
COLOR_OFF = "\033[0m" # Reset


# ----------------------------------------------------------------------
class Justify(Enum):
"""Line justification"""

# ----------------------------------------------------------------------
Left = auto()
Center = auto()
Right = auto()

# ----------------------------------------------------------------------
def Justify(
self,
value: str,
padding: int,
) -> str:
if self == Justify.Left:
return value.ljust(padding)

if self == Justify.Center:
return value.center(padding)

if self == Justify.Right:
return value.rjust(padding)

assert False, self # pragma: no cover


# ----------------------------------------------------------------------
# |
# | Public Functions
# |
# ----------------------------------------------------------------------
def _CreateCustomPrefixFunc(
header: str,
Expand Down Expand Up @@ -308,6 +340,71 @@ def CreateAnsiHyperLink(
return "\033]8;;{}\033\\{}\033]8;;\033\\".format(url, value)


# ----------------------------------------------------------------------
def CreateTable(
headers: list[str],
all_values: list[list[str]],
col_justifications: list[Justify] | None = None,
decorate_values_func: Callable[[int, list[str]], list[str]] | None = None,
on_col_sizes_calculated: Callable[[list[int]], None] | None = None,
col_padding: str = " ",
*,
decorate_headers: bool = False,
) -> str:
"""Prints a table with the provided headers and values."""

assert col_justifications is None or len(col_justifications) == len(headers)
assert decorate_headers is False or decorate_values_func

col_justifications = col_justifications or [Justify.Left] * len(headers)
decorate_values_func = decorate_values_func or (lambda _, row: row)
on_col_sizes_calculated = on_col_sizes_calculated or (lambda _: None)

# Calculate the col sizes
col_sizes = [len(header) for header in headers]

# Get the column size for each row
for row in all_values:
assert len(row) == len(headers)
for index, col_value in enumerate(row):
col_sizes[index] = max(len(col_value), col_sizes[index])

on_col_sizes_calculated(col_sizes)

# Create the template
row_template = col_padding.join(
"{{:<{}}}".format(col_size) if col_size != 0 else "{}" for col_size in col_sizes
)

# Create the rows
rows: list[str] = []

# ----------------------------------------------------------------------
def CreateRow(
index: int,
values: list[str],
) -> None:
decorated_values: list[str] = []

for col_justification, col_value, col_size in zip(col_justifications, values, col_sizes):
decorated_values.append(col_justification.Justify(col_value, col_size))

if index >= 0 or decorate_headers:
decorated_values = decorate_values_func(index, decorated_values)

rows.append(row_template.format(*decorated_values).rstrip())

# ----------------------------------------------------------------------

CreateRow(-2, headers)
CreateRow(-1, ["-" * col_size for col_size in col_sizes])

for index, values in enumerate(all_values):
CreateRow(index, values)

return "\n".join(rows) + "\n"


# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
Expand Down
112 changes: 112 additions & 0 deletions tests/TextwrapEx_UnitTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
from dbrownell_Common.TextwrapEx import *


# ----------------------------------------------------------------------
def test_Justify():
assert Justify.Left.Justify("test", 10) == "test "
assert Justify.Center.Justify("test", 10) == " test "
assert Justify.Right.Justify("test", 10) == " test"


# ----------------------------------------------------------------------
def test_CreateErrorPrefix():
assert CreateErrorPrefix(False) == "ERROR: "
Expand Down Expand Up @@ -258,3 +265,108 @@ def test_CreateAnsiHyperlink():
CreateAnsiHyperLink("https://test.com", "Test Link")
== "\x1b]8;;https://test.com\x1b\\Test Link\x1b]8;;\x1b\\"
)


# ----------------------------------------------------------------------
class TestCreateTable:
# ----------------------------------------------------------------------
def test_Simple(self):
assert CreateTable(
["One", "Two", "Three"],
[
["AAAAA", "aaaaa", "1"],
["BBBBB", "bbbbb", "2"],
["CCCCCCCCC", "cccccccc", "3"],
],
) == textwrap.dedent(
"""\
One Two Three
--------- -------- -----
AAAAA aaaaa 1
BBBBB bbbbb 2
CCCCCCCCC cccccccc 3
""",
)

# ----------------------------------------------------------------------
def test_Centered(self):
assert CreateTable(
["One", "Two", "Three"],
[
["AAAAA", "aaaaa", "1"],
["BBBBB", "bbbbb", "2"],
["CCCCCCCCC", "cccccccc", "3"],
],
[Justify.Center, Justify.Center, Justify.Center],
) == textwrap.dedent(
"""\
One Two Three
--------- -------- -----
AAAAA aaaaa 1
BBBBB bbbbb 2
CCCCCCCCC cccccccc 3
""",
)

# ----------------------------------------------------------------------
def test_Right(self):
assert CreateTable(
["One", "Two", "Three"],
[
["AAAAA", "aaaaa", "1"],
["BBBBB", "bbbbb", "2"],
["CCCCCCCCC", "cccccccc", "3"],
],
[Justify.Right, Justify.Right, Justify.Right],
) == textwrap.dedent(
"""\
One Two Three
--------- -------- -----
AAAAA aaaaa 1
BBBBB bbbbb 2
CCCCCCCCC cccccccc 3
""",
)

# ----------------------------------------------------------------------
def test_Decorate(self):
assert CreateTable(
["One", "Two", "Three"],
[
["AAAAA", "aaaaa", "1"],
["BBBBB", "bbbbb", "2"],
["CCCCCCCCC", "cccccccc", "3"],
],
[Justify.Right, Justify.Right, Justify.Right],
lambda index, values: [values[0].lower(), values[1].upper(), values[2]],
) == textwrap.dedent(
"""\
One Two Three
--------- -------- -----
aaaaa AAAAA 1
bbbbb BBBBB 2
ccccccccc CCCCCCCC 3
""",
)

# ----------------------------------------------------------------------
def test_DecorateHeaders(self):
assert CreateTable(
["One", "Two", "Three"],
[
["AAAAA", "aaaaa", "1"],
["BBBBB", "bbbbb", "2"],
["CCCCCCCCC", "cccccccc", "3"],
],
[Justify.Right, Justify.Right, Justify.Right],
lambda index, values: [values[0].lower(), values[1].upper(), values[2]],
decorate_headers=True,
) == textwrap.dedent(
"""\
one TWO Three
--------- -------- -----
aaaaa AAAAA 1
bbbbb BBBBB 2
ccccccccc CCCCCCCC 3
""",
)

0 comments on commit cb243d9

Please sign in to comment.