aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Driessen <vincent@datafox.nl>2010-08-18 23:50:23 +0200
committerVincent Driessen <vincent@datafox.nl>2010-08-18 23:50:23 +0200
commit487a1e11f1cb340917bc155adc7fe13a67648b84 (patch)
treefc56a4e0de65b7f1aad115c4392b330d52c3b4b0
parentd8581527ab1c0a770ce672b1cdd7dcbaaddecea2 (diff)
downloadvim-rst-tables-487a1e11f1cb340917bc155adc7fe13a67648b84.tar.gz
vim-rst-tables-487a1e11f1cb340917bc155adc7fe13a67648b84.tar.bz2
vim-rst-tables-487a1e11f1cb340917bc155adc7fe13a67648b84.zip
Add main logic to creating RST tables.
-rw-r--r--ftplugin/rst_tables.vim137
-rw-r--r--src/rst_tables.py137
-rw-r--r--tests/mocks/__init__.py0
-rw-r--r--tests/mocks/vim.py4
-rw-r--r--tests/test_rst_tables.py143
5 files changed, 412 insertions, 9 deletions
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
--- /dev/null
+++ b/tests/mocks/__init__.py
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)