From 487a1e11f1cb340917bc155adc7fe13a67648b84 Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Wed, 18 Aug 2010 23:50:23 +0200 Subject: Add main logic to creating RST tables. --- ftplugin/rst_tables.vim | 137 ++++++++++++++++++++++++++++++++++++++++++++- src/rst_tables.py | 137 ++++++++++++++++++++++++++++++++++++++++++++- tests/mocks/__init__.py | 0 tests/mocks/vim.py | 4 ++ tests/test_rst_tables.py | 143 +++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 412 insertions(+), 9 deletions(-) create mode 100644 tests/mocks/__init__.py create mode 100644 tests/mocks/vim.py diff --git a/ftplugin/rst_tables.vim b/ftplugin/rst_tables.vim index c847c70..43e6daf 100644 --- a/ftplugin/rst_tables.vim +++ b/ftplugin/rst_tables.vim @@ -14,11 +14,144 @@ let loaded_rst_tables_ftplugin = 1 python << endpython import vim -import os -import os.path +import re from vim_bridge import bridged +@bridged # {{{ +def get_table_bounds(): + row, col = vim.current.window.cursor + upper = lower = row + try: + while vim.buffer[upper - 1].strip(): + upper -= 1 + except IndexError: + pass + else: + upper += 1 + + try: + while vim.buffer[lower - 1].strip(): + lower += 1 + except IndexError: + pass + else: + lower -= 1 + + return (upper, lower) + # }}} + + +def unify_table(table): + max_fields = max(map(lambda row: len(row), table)) + output = [] + for row in table: + curr_len = len(row) + if curr_len < max_fields: + row += [''] * (max_fields - curr_len) + output.append(row) + return output + + +def parse_table(raw_lines): + mkfields = lambda line: re.split('\s\s+', line) + output = map(mkfields, raw_lines) + output = unify_table(output) + return output + + +def table_line(widths, header=False): + if header: + linechar = '=' + else: + linechar = '-' + sep = '+' + parts = [] + for width in widths: + parts.append(linechar * width) + if parts: + parts = [''] + parts + [''] + return sep.join(parts) + + +def get_column_widths(table): + widths = [] + for row in table: + num_fields = len(row) + # dynamically grow + if num_fields >= len(widths): + widths.extend([0] * (num_fields - len(widths))) + for i in range(num_fields): + field_text = row[i] + field_width = len(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. + + """ + 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 + + +def draw_table(table): + if 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) + + output = [header_line] + first = True + for row in table: + output.append("|".join([''] + row + [''])) + if first: + output.append(header_line) + first = False + else: + output.append(normal_line) + + return output + + +def create_table(): + upper, lower = get_table_bounds() + slice = vim.buffer[upper - 1:lower] + slice = """\ ++==========+================================================================================================+ +| Column 1 | Column 2 | ++==========+================================================================================================+ +| Foo | Put two (or more) spaces as a field separator. | ++----------+------------------------------------------------------------------------------------------------+ +| Bar | Even very very long lines like these are fine, as long as you do not put in line endings here. | ++----------+------------------------------------------------------------------------------------------------+ +| Qux | This is the last line. | ++----------+------------------------------------------------------------------------------------------------+ +""".strip().split('\n') + + #table = parse_table(slice) + #slice = draw_table(table) + vim.buffer[upper - 1:lower] = slice endpython diff --git a/src/rst_tables.py b/src/rst_tables.py index 4fd8d0f..7a0bf99 100644 --- a/src/rst_tables.py +++ b/src/rst_tables.py @@ -1,6 +1,139 @@ import vim -import os -import os.path +import re from vim_bridge import bridged +@bridged # {{{ +def get_table_bounds(): + row, col = vim.current.window.cursor + upper = lower = row + try: + while vim.buffer[upper - 1].strip(): + upper -= 1 + except IndexError: + pass + else: + upper += 1 + + try: + while vim.buffer[lower - 1].strip(): + lower += 1 + except IndexError: + pass + else: + lower -= 1 + + return (upper, lower) + # }}} + + +def unify_table(table): + max_fields = max(map(lambda row: len(row), table)) + output = [] + for row in table: + curr_len = len(row) + if curr_len < max_fields: + row += [''] * (max_fields - curr_len) + output.append(row) + return output + + +def parse_table(raw_lines): + mkfields = lambda line: re.split('\s\s+', line) + output = map(mkfields, raw_lines) + output = unify_table(output) + return output + + +def table_line(widths, header=False): + if header: + linechar = '=' + else: + linechar = '-' + sep = '+' + parts = [] + for width in widths: + parts.append(linechar * width) + if parts: + parts = [''] + parts + [''] + return sep.join(parts) + + +def get_column_widths(table): + widths = [] + for row in table: + num_fields = len(row) + # dynamically grow + if num_fields >= len(widths): + widths.extend([0] * (num_fields - len(widths))) + for i in range(num_fields): + field_text = row[i] + field_width = len(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. + + """ + 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 + + +def draw_table(table): + if 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) + + output = [header_line] + first = True + for row in table: + output.append("|".join([''] + row + [''])) + if first: + output.append(header_line) + first = False + else: + output.append(normal_line) + + return output + + +def create_table(): + upper, lower = get_table_bounds() + slice = vim.buffer[upper - 1:lower] + slice = """\ ++==========+================================================================================================+ +| Column 1 | Column 2 | ++==========+================================================================================================+ +| Foo | Put two (or more) spaces as a field separator. | ++----------+------------------------------------------------------------------------------------------------+ +| Bar | Even very very long lines like these are fine, as long as you do not put in line endings here. | ++----------+------------------------------------------------------------------------------------------------+ +| Qux | This is the last line. | ++----------+------------------------------------------------------------------------------------------------+ +""".strip().split('\n') + + #table = parse_table(slice) + #slice = draw_table(table) + vim.buffer[upper - 1:lower] = slice diff --git a/tests/mocks/__init__.py b/tests/mocks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/mocks/vim.py b/tests/mocks/vim.py new file mode 100644 index 0000000..a8cf591 --- /dev/null +++ b/tests/mocks/vim.py @@ -0,0 +1,4 @@ +from mock import Mock + +eval = Mock() +command = Mock() diff --git a/tests/test_rst_tables.py b/tests/test_rst_tables.py index 55aaffc..e31cab9 100644 --- a/tests/test_rst_tables.py +++ b/tests/test_rst_tables.py @@ -1,14 +1,147 @@ +# Mock out the vim library +import sys +sys.path = ['tests/mocks'] + sys.path +import vim +import mock + +vimvar = {} + + +def fake_eval(x): + global vimvar + return vimvar[x] + +vim.eval = fake_eval +vim.current = mock.Mock() +vimvar['foo'] = 'bar' + import unittest -import rst_tables +from rst_tables import get_table_bounds, create_table, parse_table, \ + draw_table, table_line, get_column_widths, \ + pad_fields class TestRSTTableFormatter(unittest.TestCase): def setUp(self): - pass + self.plain = """\ +This is paragraph text *before* the table. + +Column 1 Column 2 +Foo Put two (or more) spaces as a field separator. +Bar Even very very long lines like these are fine, as long as you do not put in line endings here. +Qux This is the last line. + +This is paragraph text *after* the table, with +a line ending. +""" def tearDown(self): pass - def testSomeThing(self): - input = "foo" - self.assertEquals("foo", input) + def testGetBounds(self): + input = self.plain + vim.buffer = input.split('\n') + vim.current.window.cursor = (4, 0) + self.assertEquals((3, 6), get_table_bounds()) + + def testGetBoundsOnBeginOfFile(self): + input = self.plain + vim.buffer = input.split('\n') + vim.current.window.cursor = (1, 0) + self.assertEquals((1, 1), get_table_bounds()) + + def testGetBoundsOnEndOfFile(self): + input = self.plain + vim.buffer = input.split('\n') + vim.current.window.cursor = (8, 0) + self.assertEquals((8, 9), get_table_bounds()) + + def testParseSimpleTable(self): + self.assertEquals([['x y z']], parse_table(['x y z'])) + self.assertEquals([['x', 'y z']], parse_table(['x y z'])) + self.assertEquals([['x', 'y', 'z']], parse_table(['x y z'])) + + def testParseTable(self): + input = self.plain + vim.buffer = input.split('\n') + expected = [ + ['Column 1', 'Column 2'], + ['Foo', 'Put two (or more) spaces as a field separator.'], + ['Bar', 'Even very very long lines like these are fine, as long as you do not put in line endings here.'], + ['Qux', 'This is the last line.'], + ] + self.assertEquals(expected, parse_table(vim.buffer[2:6])) + + def testParseTableUnifiesColumns(self): + input = ['x y', 'a b c', 'only one'] + expected = [['x', 'y', ''], ['a', 'b', 'c'], ['only one', '', '']] + self.assertEquals(expected, parse_table(input)) + + def testTableLine(self): + self.assertEquals('', table_line([], True)) + self.assertEquals('++', table_line([0], True)) + self.assertEquals('+++', table_line([0,0], True)) + self.assertEquals('++-+', table_line([0,1])) + self.assertEquals('+===+', table_line([3], True)) + self.assertEquals('+===+====+', table_line([3,4], True)) + self.assertEquals('+------------------+---+--------------------+', + table_line([18,3,20])) + + def testGetColumnWidths(self): + self.assertEquals([], get_column_widths([[]])) + self.assertEquals([0], get_column_widths([['']])) + self.assertEquals([1,2,3], get_column_widths([['x','yy','zzz']])) + self.assertEquals([3,3,3], + get_column_widths( + [ + ['x','y','zzz'], + ['xxx','yy','z'], + ['xx','yyy','zz'], + ])) + + def testPadFields(self): + table = [['Name', 'Type', 'Description'], + ['Lollypop', 'Candy', 'Yummy'], + ['Crisps', 'Snacks', 'Even more yummy, I tell you!']] + expected_padding = [ + [' Name ', ' Type ', ' Description '], + [' Lollypop ', ' Candy ', ' Yummy '], + [' Crisps ', ' Snacks ', ' Even more yummy, I tell you! ']] + self.assertEquals(expected_padding, pad_fields(table)) + + + def testDrawTable(self): + self.assertEquals([], draw_table([])) + self.assertEquals(['+==+', '| |', '+==+'], draw_table([['']])) + self.assertEquals(['+=====+', '| Foo |', '+=====+'], + draw_table([['Foo']])) + self.assertEquals( + ['+=====+====+', + '| Foo | Mu |', + '+=====+====+', + '| x | y |', + '+-----+----+'], + draw_table([['Foo', 'Mu'], ['x', 'y']])) + + def testCreateTable(self): + input = self.plain + expect = """\ +This is paragraph text *before* the table. + ++==========+================================================================================================+ +| Column 1 | Column 2 | ++==========+================================================================================================+ +| Foo | Put two (or more) spaces as a field separator. | ++----------+------------------------------------------------------------------------------------------------+ +| Bar | Even very very long lines like these are fine, as long as you do not put in line endings here. | ++----------+------------------------------------------------------------------------------------------------+ +| Qux | This is the last line. | ++----------+------------------------------------------------------------------------------------------------+ + +This is paragraph text *after* the table, with +a line ending. +""".split('\n') + vim.buffer = input.split('\n') + vim.current.window.cursor = (4, 10) + create_table() + self.assertEquals(expect, vim.buffer) -- cgit v1.2.3