Repository: astanin/python-tabulate
Pull request #194 (2022-09-01 12:47:49)

Simple asciidoc support
Adds support to a simple subset of AsciiDoc's table format (https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/#tables)


Patch:
diff --git a/README.md b/README.md
index ba65384..2b1c27d 100644
--- a/README.md
+++ b/README.md
@@ -166,6 +166,7 @@ Supported table formats are:
 -   "fancy\_outline"
 -   "pipe"
 -   "orgtbl"
+-   "asciidoc"
 -   "jira"
 -   "presto"
 -   "pretty"
@@ -399,6 +400,21 @@ indicate column alignment:
 | bacon  |     0 |
 ```
 
+`asciidoc` formats data like a simple table of the
+[AsciiDoctor](https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/#tables)
+format:
+
+```pycon
+>>> print(tabulate(table, headers, tablefmt="asciidoc"))
+[cols="8<,7>",options="header"]
+|====
+| item   |   qty 
+| spam   |    42 
+| eggs   |   451 
+| bacon  |     0 
+|====
+```
+
 `orgtbl` follows the conventions of Emacs
 [org-mode](http://orgmode.org/manual/Tables.html), and is editable also
 in the minor orgtbl-mode. Hence its name:
diff --git a/tabulate.py b/tabulate.py
index a611658..fcc97ca 100644
--- a/tabulate.py
+++ b/tabulate.py
@@ -207,6 +207,54 @@ def _latex_line_begin_tabular(colwidths, colaligns, booktabs=False, longtable=Fa
         ]
     )
 
+def _asciidoc_row(is_header, *args):
+    """ handle header and data rows for asciidoc format """
+
+    def make_header_line(is_header, colwidths, colaligns):
+        # generate the column specifiers
+        
+        alignment = {"left": "<", "right": ">", "center": "^", "decimal": ">"}
+        # use the column widths generated by tabulate for the asciidoc column width specifiers
+        asciidoc_alignments        = zip(colwidths,[alignment[colalign] for colalign in colaligns])
+        asciidoc_column_specifiers = ["{:d}{}".format(width,align) for width, align in asciidoc_alignments]
+        header_list=['cols="'+(','.join(asciidoc_column_specifiers))+'"']
+        
+        # generate the list of options (currently only "header")
+        options_list = []
+        
+        if is_header:
+            options_list.append('header')
+            
+        if options_list:
+            header_list += ['options="' + ','.join(options_list) + '"']
+    
+        # generate the list of entries in the table header field
+
+        return '[{}]\n|===='.format(','.join(header_list))
+
+
+    if len(args) == 2:
+        # two arguments are passed if called in the context of aboveline
+        # print the table header with column widths and optional header tag
+        return make_header_line(False,*args)
+
+    elif len(args) == 3:
+        # three arguments are passed if called in the context of dataline or headerline
+        # print the table line and make the aboveline if it is a header
+    
+        cell_values, colwidths, colaligns = args
+        data_line = '|' + '|'.join(cell_values)
+        
+        if is_header:
+            return make_header_line(True, colwidths, colaligns) + '\n' + data_line
+        else:
+            return data_line
+
+    else:
+        raise ValueError(
+            " _asciidoc_row() requires two (colwidths, colaligns) or three (cell_values, colwidths, colaligns) arguments) "
+        )
+
 
 LATEX_ESCAPE_RULES = {
     r"&": r"\&",
@@ -568,6 +616,16 @@ _table_formats = {
         padding=1,
         with_header_hide=None,
     ),
+    "asciidoc": TableFormat(
+        lineabove=partial(_asciidoc_row, False),
+        linebelowheader=None,
+        linebetweenrows=None,
+        linebelow=Line("|====","","",""),
+        headerrow=partial(_asciidoc_row, True),
+        datarow=partial(_asciidoc_row,   False),
+        padding=1,
+        with_header_hide=["lineabove"],
+    ),
 }
 
 


Test patch:
diff --git a/test/test_output.py b/test/test_output.py
index 82ecfb7..d7be88a 100644
--- a/test/test_output.py
+++ b/test/test_output.py
@@ -1536,6 +1536,35 @@ def test_orgtbl_headerless():
     result = tabulate(_test_table, tablefmt="orgtbl")
     assert_equal(expected, result)
 
+def test_asciidoc():
+    "Output: asciidoc with headers"
+    expected = "\n".join(
+        [
+            '[cols="11<,11>",options="header"]',
+            '|====',
+            '| strings   |   numbers ',
+            '| spam      |   41.9999 ',
+            '| eggs      |  451      ',
+            '|===='
+        ]
+    )
+    result = tabulate(_test_table, _test_table_headers, tablefmt="asciidoc")
+    assert_equal(expected, result)
+
+
+def test_asciidoc_headerless():
+    "Output: asciidoc without headers"
+    expected = "\n".join(
+        [
+            '[cols="6<,10>"]',
+            '|====',
+            '| spam |  41.9999 ',
+            '| eggs | 451      ',
+            '|===='
+        ]
+    )
+    result = tabulate(_test_table, tablefmt="asciidoc")
+    assert_equal(expected, result)
 
 def test_psql():
     "Output: psql with headers"