From 135878107d87873bc2f539fe2d4dd0c179c757de Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Thu, 19 Aug 2010 16:37:33 +0200 Subject: Add support for multi-line cell formatting. For example, when given the following table: [['Foo', 'Bar'], ['x', 'This is a long line\nthat is spread out\nover multiple lines']] It will render as: +=====+=====================+ | Foo | Bar | +=====+=====================+ | x | This is a long line | | | that is spread out | | | over multiple lines | +-----+---------------------+ --- ftplugin/rst_tables.vim | 62 ++++++++++++++++++++++++++++++++---------------- src/rst_tables.py | 62 ++++++++++++++++++++++++++++++++---------------- tests/test_rst_tables.py | 48 +++++++++++++++++++++++++++++++++++-- 3 files changed, 130 insertions(+), 42 deletions(-) diff --git a/ftplugin/rst_tables.vim b/ftplugin/rst_tables.vim index c793dac..1077557 100644 --- a/ftplugin/rst_tables.vim +++ b/ftplugin/rst_tables.vim @@ -66,6 +66,7 @@ def join_rows(rows, sep='\n'): def line_is_separator(line): return re.match('^[\t +=-]+$', line) + def has_line_seps(raw_lines): for line in raw_lines: if line_is_separator(line): @@ -157,6 +158,25 @@ def table_line(widths, header=False): return sep.join(parts) +def get_field_width(field_text): + return max(map(lambda s: len(s), field_text.split('\n'))) + + +def split_row_into_lines(row): + row = map(lambda field: field.split('\n'), row) + height = max(map(lambda field_lines: len(field_lines), row)) + turn_table = [] + for i in range(height): + fields = [] + for field_lines in row: + if i < len(field_lines): + fields.append(field_lines[i]) + else: + fields.append('') + turn_table.append(fields) + return turn_table + + def get_column_widths(table): widths = [] for row in table: @@ -166,30 +186,25 @@ def get_column_widths(table): widths.extend([0] * (num_fields - len(widths))) for i in range(num_fields): field_text = row[i] - field_width = len(field_text) + field_width = get_field_width(field_text) widths[i] = max(widths[i], field_width) return widths -def pad_fields(table, widths=None): - """Pads fields of the table, so each row lines up nicely with the others. - If the widths param is None, the widths are calculated automatically. +def pad_fields(row, widths): + """Pads fields of the given row, so each field lines up nicely with the + others. """ - if widths is None: - widths = get_column_widths(table) widths = map(lambda w: ' %-' + str(w) + 's ', widths) # Pad all fields using the calculated widths - output = [] - for row in table: - new_row = [] - for i in range(len(row)): - col = row[i] - col = widths[i] % col.strip() - new_row.append(col) - output.append(new_row) - return output + new_row = [] + for i in range(len(row)): + col = row[i] + col = widths[i] % col.strip() + new_row.append(col) + return new_row def draw_table(table): @@ -197,17 +212,24 @@ def draw_table(table): return [] col_widths = get_column_widths(table) - table = pad_fields(table, col_widths) # Reserve room for the spaces - col_widths = map(lambda x: x + 2, col_widths) - header_line = table_line(col_widths, header=True) - normal_line = table_line(col_widths, header=False) + sep_col_widths = map(lambda x: x + 2, col_widths) + header_line = table_line(sep_col_widths, header=True) + normal_line = table_line(sep_col_widths, header=False) output = [header_line] first = True for row in table: - output.append("|".join([''] + row + [''])) + + row_lines = split_row_into_lines(row) + + # draw the lines (num_lines) for this row + for row_line in row_lines: + row_line = pad_fields(row_line, col_widths) + output.append("|".join([''] + row_line + [''])) + + # then, draw the separator if first: output.append(header_line) first = False diff --git a/src/rst_tables.py b/src/rst_tables.py index 952a2b2..f689b35 100644 --- a/src/rst_tables.py +++ b/src/rst_tables.py @@ -47,6 +47,7 @@ def join_rows(rows, sep='\n'): def line_is_separator(line): return re.match('^[\t +=-]+$', line) + def has_line_seps(raw_lines): for line in raw_lines: if line_is_separator(line): @@ -138,6 +139,25 @@ def table_line(widths, header=False): return sep.join(parts) +def get_field_width(field_text): + return max(map(lambda s: len(s), field_text.split('\n'))) + + +def split_row_into_lines(row): + row = map(lambda field: field.split('\n'), row) + height = max(map(lambda field_lines: len(field_lines), row)) + turn_table = [] + for i in range(height): + fields = [] + for field_lines in row: + if i < len(field_lines): + fields.append(field_lines[i]) + else: + fields.append('') + turn_table.append(fields) + return turn_table + + def get_column_widths(table): widths = [] for row in table: @@ -147,30 +167,25 @@ def get_column_widths(table): widths.extend([0] * (num_fields - len(widths))) for i in range(num_fields): field_text = row[i] - field_width = len(field_text) + field_width = get_field_width(field_text) widths[i] = max(widths[i], field_width) return widths -def pad_fields(table, widths=None): - """Pads fields of the table, so each row lines up nicely with the others. - If the widths param is None, the widths are calculated automatically. +def pad_fields(row, widths): + """Pads fields of the given row, so each field lines up nicely with the + others. """ - if widths is None: - widths = get_column_widths(table) widths = map(lambda w: ' %-' + str(w) + 's ', widths) # Pad all fields using the calculated widths - output = [] - for row in table: - new_row = [] - for i in range(len(row)): - col = row[i] - col = widths[i] % col.strip() - new_row.append(col) - output.append(new_row) - return output + new_row = [] + for i in range(len(row)): + col = row[i] + col = widths[i] % col.strip() + new_row.append(col) + return new_row def draw_table(table): @@ -178,17 +193,24 @@ def draw_table(table): return [] col_widths = get_column_widths(table) - table = pad_fields(table, col_widths) # Reserve room for the spaces - col_widths = map(lambda x: x + 2, col_widths) - header_line = table_line(col_widths, header=True) - normal_line = table_line(col_widths, header=False) + sep_col_widths = map(lambda x: x + 2, col_widths) + header_line = table_line(sep_col_widths, header=True) + normal_line = table_line(sep_col_widths, header=False) output = [header_line] first = True for row in table: - output.append("|".join([''] + row + [''])) + + row_lines = split_row_into_lines(row) + + # draw the lines (num_lines) for this row + for row_line in row_lines: + row_line = pad_fields(row_line, col_widths) + output.append("|".join([''] + row_line + [''])) + + # then, draw the separator if first: output.append(header_line) first = False diff --git a/tests/test_rst_tables.py b/tests/test_rst_tables.py index eac65fe..7d72e25 100644 --- a/tests/test_rst_tables.py +++ b/tests/test_rst_tables.py @@ -23,7 +23,7 @@ import unittest from rst_tables import get_table_bounds, reformat_table, parse_table, \ draw_table, table_line, get_column_widths, \ pad_fields, unify_table, join_rows, \ - partition_raw_lines + partition_raw_lines, split_row_into_lines class TestRSTTableFormatter(unittest.TestCase): @@ -146,6 +146,43 @@ class TestRSTTableFormatter(unittest.TestCase): ['x\nblah', 'This became somewhat larger\nA new line']] self.assertEquals(expect, parse_table(input)) + def testParseMultiLineFields(self): + input = """\ ++=====+=====================+ +| Foo | Bar | ++=====+=====================+ +| x | This is a long line | +| | that is spread out | +| | over multiple lines | ++-----+---------------------+""".split('\n') + expect = [['Foo', 'Bar'], + ['x', 'This is a long line\nthat is spread out\nover multiple lines']] + self.assertEquals(expect, parse_table(input)) + + def testSplitRowIntoLines(self): + input = ['Foo', 'Bar'] + expect = [['Foo', 'Bar']] + self.assertEquals(expect, split_row_into_lines(input)) + input = ['One\nTwo\nThree', 'Only one'] + expect = [['One', 'Only one'], ['Two', ''], ['Three', '']] + self.assertEquals(expect, split_row_into_lines(input)) + input = ['One\n\n\nThree', 'Foo\nBar'] + expect = [['One', 'Foo'], ['', 'Bar'], ['', ''], ['Three', '']] + self.assertEquals(expect, split_row_into_lines(input)) + + def testDrawMultiLineFields(self): + input = [['Foo', 'Bar'], + ['x', 'This is a long line\nthat is spread out\nover multiple lines']] + expect = """\ ++=====+=====================+ +| Foo | Bar | ++=====+=====================+ +| x | This is a long line | +| | that is spread out | +| | over multiple lines | ++-----+---------------------+""".split('\n') + self.assertEquals(expect, draw_table(input)) + def testTableLine(self): self.assertEquals('', table_line([], True)) self.assertEquals('++', table_line([0], True)) @@ -168,6 +205,11 @@ class TestRSTTableFormatter(unittest.TestCase): ['xx','yyy','zz'], ])) + def testGetColumnWidthsForMultiLineFields(self): + self.assertEquals([3,6], + get_column_widths([['Foo\nBar\nQux', + 'This\nis\nreally\nneat!']])) + def testPadFields(self): table = [['Name', 'Type', 'Description'], ['Lollypop', 'Candy', 'Yummy'], @@ -176,7 +218,9 @@ class TestRSTTableFormatter(unittest.TestCase): [' Name ', ' Type ', ' Description '], [' Lollypop ', ' Candy ', ' Yummy '], [' Crisps ', ' Snacks ', ' Even more yummy, I tell you! ']] - self.assertEquals(expected_padding, pad_fields(table)) + widths = get_column_widths(table) + for input, expect in zip(table, expected_padding): + self.assertEquals(expect, pad_fields(input, widths)) def testDrawTable(self): -- cgit v1.2.3