Skip to content

Commit e7e12cb

Browse files
committed
AppTests: Add char args, validation, and bug fixes to table
Signed-off-by: Gabe Goodhart <[email protected]>
1 parent afda9e8 commit e7e12cb

File tree

1 file changed

+44
-28
lines changed

1 file changed

+44
-28
lines changed

scriptit/shape.py

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ def table(
9898
min_width: Optional[int] = None,
9999
row_dividers: bool = True,
100100
header: bool = True,
101+
hframe_char: str = "-",
102+
vframe_char: str = "|",
103+
corner_char: str = "+",
104+
header_char: str = "=",
101105
) -> str:
102106
"""Encode the given columns as an ascii table
103107
@@ -112,17 +116,32 @@ def table(
112116
computed width based on content
113117
row_dividers (bool): Include dividers between rows
114118
header (bool): Include a special divider between header and rows
119+
hframe_char (str): Single character for horizontal frame lines
120+
vframe_char (str): Single character for vertical frame lines
121+
corner_char (str): Single character for corners
122+
header_char (str): Single character for the header horizontal divider
115123
116124
Returns:
117125
table (str): The formatted table string
118126
"""
127+
# Validate arguments
128+
if any(
129+
len(char) != 1 for char in [hframe_char, vframe_char, corner_char, header_char]
130+
):
131+
raise ValueError("*_char args must be a single character")
132+
if len(set([len(col) for col in columns])) > 1:
133+
raise ValueError("All columns must have equal length")
134+
119135
if max_width is None:
120136
max_width = shutil.get_terminal_size().columns
121137
if min_width is None:
122138
min_width = 2 * len(columns) + 1 if width is None else width
123139

124140
# Stringify all column content
125141
columns = [[str(val) for val in col] for col in columns]
142+
empty_cols = [i for i, col in enumerate(columns) if not any(col)]
143+
if empty_cols:
144+
raise ValueError(f"Found empty column(s) when stringified: {empty_cols}")
126145

127146
# Determine the raw max width of each column
128147
max_col_width = max_width - 3 - 2 * (len(columns) - 1)
@@ -138,55 +157,50 @@ def table(
138157
# For each column, determine the width as a percentage of the total width
139158
pcts = [float(w) / float(total_width) for w in widths]
140159
col_widths = [int(p * usable_table_width) + 3 for p in pcts]
141-
col_widths[-1] = usable_table_width - sum(col_widths[:-1])
142-
143-
# Adjust if possible to compensate for collapsed columns
144-
collapsed = [(i, w) for i, w in enumerate(col_widths) if w - 2 < 2]
145-
extra = sorted(
146-
[(w, i) for i, w in enumerate(col_widths) if (i, w) not in collapsed],
147-
key=lambda x: x[0],
148-
)
149-
for (i, w) in collapsed:
150-
assert len(extra) > 0 and extra[0][0] - w > 2, "No extra to borrow from"
151-
padding = 2 - w
152-
col_widths[extra[0][1]] = extra[0][0] - padding
153-
col_widths[i] = w + padding
154-
extra = sorted(extra, key=lambda x: x[0])
160+
if col_widths:
161+
col_widths[-1] = usable_table_width - sum(col_widths[:-1])
162+
else:
163+
col_widths = [0]
155164

156165
# Prepare the rows
157166
wrapped_cols = []
158167
for i, col in enumerate(columns):
159168
wrapped_cols.append([])
160169
for entry in col:
161-
assert col_widths[i] - 2 > 1, f"Column width collapsed for col {i}"
170+
if col_widths[i] - 2 <= 1:
171+
raise ValueError(f"Column width collapsed for col {i}")
162172
wrapped, _ = _word_wrap_to_len(entry, col_widths[i] - 2)
163173
wrapped_cols[-1].append(wrapped)
164174

165175
# Go row-by-row and add to the output
166-
out = _make_hline(table_width)
167-
n_rows = max([len(col) for col in columns])
176+
out = _make_hline(table_width, char=hframe_char, edge=corner_char)
177+
n_rows = max([len(col) for col in columns]) if columns else 0
178+
if not n_rows:
179+
if header:
180+
out += _make_hline(table_width, char=header_char, edge=vframe_char)
181+
out += _make_hline(table_width, char=hframe_char, edge=corner_char)
168182
for r in range(n_rows):
169183
entries = [col[r] if r < len(col) else [""] for col in wrapped_cols]
170184
most_sublines = max([len(e) for e in entries])
171185
for i in range(most_sublines):
172186
line = ""
173187
for c, entry in enumerate(entries):
174188
val = entry[i] if len(entry) > i else ""
175-
line += "| {}{}".format(
176-
val, " " * (col_widths[c] - _printed_len(val) - 2)
189+
line += "{} {}{}".format(
190+
vframe_char, val, " " * (col_widths[c] - _printed_len(val) - 2)
177191
)
178-
line += "|\n"
192+
line += f"{vframe_char}\n"
179193
out += line
180194
if r == 0:
181195
if header:
182-
out += _make_hline(table_width, char="=", edge="|")
196+
out += _make_hline(table_width, char=header_char, edge=vframe_char)
183197
elif row_dividers:
184-
out += _make_hline(table_width, edge="|")
198+
out += _make_hline(table_width, char=hframe_char, edge=vframe_char)
185199
elif r < n_rows - 1:
186200
if row_dividers:
187-
out += _make_hline(table_width, edge="|")
201+
out += _make_hline(table_width, char=hframe_char, edge=vframe_char)
188202
else:
189-
out += _make_hline(table_width)
203+
out += _make_hline(table_width, char=hframe_char, edge=corner_char)
190204

191205
return out
192206

@@ -211,8 +225,8 @@ def _word_wrap_to_len(line: str, max_len: int) -> Tuple[List[str], int]:
211225
sublines (List[str]): The lines wrapped to the target length
212226
longest (int): The length of the longest wrapped line (<= max_len)
213227
"""
214-
if _printed_len(line) <= max_len:
215-
return [line], _printed_len(line)
228+
if (printed_len := _printed_len(line)) <= max_len:
229+
return [line], printed_len
216230
else:
217231
longest = 0
218232
sublines = []
@@ -231,12 +245,14 @@ def _word_wrap_to_len(line: str, max_len: int) -> Tuple[List[str], int]:
231245
subline += words[0][:cutoff] + "- "
232246
words[0] = words[0][cutoff:]
233247
else:
234-
break
248+
# NOTE: This _is_ covered in tests, but the coverage engine
249+
# doesn't pick it up for some reason!
250+
break # pragma: no cover
235251
subline = subline[:-1]
236252
longest = max(longest, _printed_len(subline))
237253
sublines.append(subline)
238254
return sublines, longest
239255

240256

241-
def _make_hline(table_width: int, char: str = "-", edge: str = "+") -> str:
257+
def _make_hline(table_width: int, char: str, edge: str) -> str:
242258
return "{}{}{}\n".format(edge, char * (table_width - 2), edge)

0 commit comments

Comments
 (0)