Skip to content

Commit fd793f6

Browse files
authored
Merge pull request #15 from dselivanov/newline
2 parents 03e06c2 + 7a1df2d commit fd793f6

File tree

5 files changed

+91
-68
lines changed

5 files changed

+91
-68
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
runs-on: ubuntu-22.04
1414
strategy:
1515
matrix:
16-
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
16+
python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13"]
1717
steps:
1818
- name: Checkout code
1919
uses: actions/checkout@v4

.github/workflows/ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ on:
55
- 'README.md'
66
branches:
77
- '**'
8+
pull_request:
9+
paths-ignore:
10+
- 'README.md'
11+
branches:
12+
- '**'
813

914
jobs:
1015
test:
1116
name: Test with different Python versions
1217
runs-on: ubuntu-22.04
1318
strategy:
1419
matrix:
15-
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
20+
python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13"]
1621
steps:
1722
- name: Checkout code
1823
uses: actions/checkout@v4

py_markdown_table/markdown_table.py

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,21 @@ def __init__(
5151
self.multiline_strategy = "rows"
5252
self.multiline_delimiter = " "
5353
self.quote = True
54+
self.skip_data_validation = skip_data_validation
5455

55-
if not skip_data_validation:
56+
self.__validate_parameters()
57+
58+
if not self.skip_data_validation:
5659
self.__validate_data(data)
5760

58-
self.__validate_parameters()
5961
self.__update_meta_params()
62+
63+
# we need to first update the meta_params for cell width, padding etc
64+
# prior to checking whether the data will fit for multiline rendering
65+
if self.multiline:
66+
self.__validate_multiline(self.data)
6067

68+
6169
def set_params(
6270
self,
6371
row_sep: str = "always",
@@ -128,8 +136,17 @@ def set_params(
128136
if isinstance(padding_weight, str):
129137
self.padding_weight = {key: padding_weight for key in self.data[0].keys()}
130138

139+
131140
self.__validate_parameters()
141+
142+
if not self.skip_data_validation:
143+
self.__validate_data(self.data)
144+
132145
self.__update_meta_params()
146+
147+
if self.multiline:
148+
self.__validate_multiline(self.data)
149+
133150
return self
134151

135152
def __update_meta_params(self):
@@ -142,7 +159,8 @@ def __update_meta_params(self):
142159
else:
143160
self.var_padding = self.__get_padding()
144161
self.var_row_sep = self.__get_row_sep_str()
145-
self.var_row_sep_last = self.__get_row_sep_last()
162+
# self.var_row_sep_last = self.__get_row_sep_last()
163+
self.var_row_sep_last = self.__get_row_sep_str()
146164

147165
def __validate_parameters(self): # noqa: C901
148166
valid_values = {
@@ -196,30 +214,32 @@ def __validate_parameters(self): # noqa: C901
196214
if not isinstance(self.quote, bool):
197215
raise ValueError(f"quote value of '{self.quote}' is not valid. Please use a boolean.")
198216

199-
200-
201217
def __validate_data(self, data):
202218
# Check if all dictionaries in self.data have uniform keys
203-
keys = set(data[0].keys()) # Use set for fast lookup
219+
keys = set(data[0].keys())
204220
for item in data:
205221
if not isinstance(item, dict):
206222
raise TypeError("Each element in data must be a dictionary.")
207223
if set(item.keys()) != keys:
208224
raise ValueError("Dictionary keys are not uniform across data variable.")
209225

210-
if self.multiline:
211-
for row in data:
212-
for key in row.keys():
213-
if key in self.var_padding:
214-
multiline_data = row[key].split(self.multiline_delimiter)
215-
multiline_max_width = max(multiline_data, key=len)
216-
if len(multiline_max_width) + self.padding_width[key] > self.var_padding[key]:
217-
raise ValueError(
218-
f"Contiguous string exists longer than the allocated column width "
219-
f"for column '{key}' and padding_width '{self.padding_width[key]}'."
220-
)
221-
else:
222-
raise KeyError(f"Key '{key}' not found in var_padding.")
226+
def __validate_multiline(self, data):
227+
for i, row in enumerate(data):
228+
for key in row.keys():
229+
if key in self.var_padding:
230+
multiline_data = row[key].split(self.multiline_delimiter)
231+
multiline_max_string = max(multiline_data, key=len)
232+
multiline_max_width = len(multiline_max_string)
233+
if multiline_max_width + self.padding_width[key] > self.var_padding[key]:
234+
raise ValueError(
235+
f"There is a contiguous string:\n"
236+
f"'{multiline_max_string}'\n"
237+
f"in the element [{i}] "
238+
f"which is longer than the allocated column width "
239+
f"for column '{key}' and padding_width '{self.padding_width[key]}'."
240+
)
241+
else:
242+
raise KeyError(f"Key '{key}' not found in var_padding.")
223243

224244
def __get_padding(self):
225245
"""Calculate table-wide padding."""
@@ -249,13 +269,6 @@ def __get_row_sep_str(self):
249269
row_sep_str += "+"
250270
return row_sep_str
251271

252-
def __get_row_sep_last(self):
253-
row_sep_str_last = "+"
254-
for value in self.var_padding.values():
255-
row_sep_str_last += "-" * (value + 1)
256-
row_sep_str_last = row_sep_str_last[:-1] + "+"
257-
return row_sep_str_last
258-
259272
def __get_margin(self, margin, key):
260273
# get column-specific alignment based on the column key (header)
261274
if self.padding_weight[key] == "left":
@@ -278,6 +291,9 @@ def __get_row(self, item):
278291
for key in self.data[0].keys():
279292
if len(item[key]) > self.var_padding[key]:
280293
multiline = True
294+
if "\n" in item[key]:
295+
multiline = True
296+
281297
if multiline:
282298
return self.__get_multiline_row(item)
283299
return self.__get_normal_row(item)
@@ -302,7 +318,7 @@ def __get_normal_row(self, item):
302318
row += "|"
303319
return row
304320

305-
def __get_multiline_row(self, item):
321+
def __get_multiline_row(self, item): # noqa: C901
306322
multiline_items = {}
307323

308324
# Helper function to process each element and split by emojis if present
@@ -315,31 +331,35 @@ def split_and_process_element(element):
315331

316332
# Process each column in the row
317333
for key in self.data[0].keys():
318-
fully_split_cell = []
319-
# Split cell content by the delimiter and process each part
320-
for element in item[key].split(self.multiline_delimiter):
321-
fully_split_cell.extend(split_and_process_element(element))
322-
323-
multiline_row, single_row = [], []
324-
item_prev_length, spacing_between_items = 0, 0
325-
326-
# Create multiline rows from the split elements
327-
while fully_split_cell:
328-
current_element = fully_split_cell[0]
329-
item_length = len(current_element) + len(count_emojis(current_element))
330-
331-
# Check if the current element fits in the row
332-
if item_length + item_prev_length + spacing_between_items + self.padding_width[key] <= self.var_padding[key]:
333-
item_prev_length += item_length
334-
single_row.append(fully_split_cell.pop(0))
335-
spacing_between_items = len(single_row)
336-
else:
337-
# Start a new line if the current element doesn't fit
338-
multiline_row.append(" ".join(single_row))
339-
single_row, item_prev_length, spacing_between_items = [], 0, 0
334+
multiline_row = []
335+
# First we split by embedded line breaks in order to correctly
336+
# render lists and othe markdown elements which depend on newline offset
337+
for line in item[key].split("\n"):
338+
fully_split_cell = []
339+
# Split cell content by the delimiter and process each part
340+
for element in line.split(self.multiline_delimiter):
341+
fully_split_cell.extend(split_and_process_element(element))
342+
343+
single_row = []
344+
item_prev_length, spacing_between_items = 0, 0
345+
346+
# Create multiline rows from the split elements
347+
while fully_split_cell:
348+
current_element = fully_split_cell[0]
349+
item_length = len(current_element) + len(count_emojis(current_element))
350+
351+
# Check if the current element fits in the row
352+
if item_length + item_prev_length + spacing_between_items + self.padding_width[key] <= self.var_padding[key]:
353+
item_prev_length += item_length
354+
single_row.append(fully_split_cell.pop(0))
355+
spacing_between_items = len(single_row)
356+
else:
357+
# Start a new line if the current element doesn't fit
358+
multiline_row.append(" ".join(single_row))
359+
single_row, item_prev_length, spacing_between_items = [], 0, 0
340360

341-
# Add the remaining elements in single_row to multiline_row
342-
multiline_row.append(" ".join(single_row))
361+
# Add the remaining elements in single_row to multiline_row
362+
multiline_row.append(" ".join(single_row))
343363
multiline_items[key] = multiline_row
344364

345365
# Find the maximum number of rows in any column

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "py-markdown-table"
3-
version = "1.1.0"
3+
version = "1.2.0"
44
description = "Package that generates markdown tables from a list of dicts"
55
readme = "README.md"
66
homepage = "https://github.com/hvalev/py-markdown-table"

0 commit comments

Comments
 (0)