diff --git a/src/synthale/markdown.py b/src/synthale/markdown.py index f341a9a..0348dfa 100644 --- a/src/synthale/markdown.py +++ b/src/synthale/markdown.py @@ -1,5 +1,7 @@ """This module contains functions to generate markdown elements.""" +import re + def setext_heading(text, level): """Return an setext heading. @@ -27,3 +29,49 @@ def emphasis(text): def strong(text): """Wrap text with double asterisks.""" return '**{}**'.format(text) + + +def table(headers, rows): + """Generate a table. + + 'headers' is a list/tuple of header cells. 'rows' is a list of lists + containing each cell. If any row has more cells than there are headers, the + extra cells are silently dropped. + + See https://github.github.com/gfm/#tables-extension- for syntax of a GFM + table. + """ + colwidth = [] # Tuple to store width of columns + for idx, val in enumerate(headers): + # Pad each column with spaces and count pipes (they get escaped) + colwidth.insert(idx, len(val) + 1 + val.count('|')) + + # Find max width for each column + for row in rows: + for idx, val in enumerate(row): + if len(val) + 1 + val.count('|') > colwidth[idx]: + colwidth[idx] = len(val) + 1 + val.count('|') + + # Header row + table = '|' + for idx, val in enumerate(headers): + table += ' {}{}|'.format( + re.sub(r'\|', r'\|', val), + ' ' * (colwidth[idx] - len(val) - val.count('|')) + ) + + # Delimiter row + table += '\n|' + for width in colwidth: + table += ' {} |'.format('-' * (width - 1)) + + # Table rows + for row in rows: + table += '\n|' + for idx, val in enumerate(row): + table += ' {}{}|'.format( + re.sub(r'\|', r'\|', val), + ' ' * (colwidth[idx] - len(val) - val.count('|')) + ) + + return table diff --git a/tests/test_markdown.py b/tests/test_markdown.py index c8f4f59..b4e7126 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -4,11 +4,11 @@ import pytest from synthale import markdown -@pytest.mark.parametrize('text,level,expected', [ +@pytest.mark.parametrize('text,level,expected', ( ('test', 1, 'test\n===='), ('foobar', 2, 'foobar\n------'), (' test ', None, 'test\n----') -]) +)) def test_setext_heading(text, level, expected): """Test for valid setext headings from setext_heading function.""" assert markdown.setext_heading(text, level) == expected @@ -22,3 +22,32 @@ def test_emphasis(): def test_strong(): """Test strong.""" assert markdown.strong('Foo') == '**Foo**' + + +@pytest.mark.parametrize('headers,rows,expected', ( + ( + ('one', 'two', 'three'), + ( + ('testing', 'foo', 'bar'), + ('1', '2', '3'), + ), + ('| one | two | three |\n' + '| ------- | --- | ----- |\n' + '| testing | foo | bar |\n' + '| 1 | 2 | 3 |'), + ), + ( + ('pipe|test',), + ( + ('testing',), + ('testin|g',), + ), + ('| pipe\|test |\n' + '| ---------- |\n' + '| testing |\n' + '| testin\|g |') + ) +)) +def test_table(headers, rows, expected): + """Test table generation.""" + assert markdown.table(headers, rows) == expected