Generate Markdown tables?
Question: Is there any way to generate tables from objects (Python/Ruby/Java/C#)?

I'd like to create a simple table programatically. I have some objects and I'd
like to map some properties to headers and the collection to rows.

Why Markdown? Because I'd like to edit that document manually later. Right
now, the whole process looks like this:

  * reporting engine is in C#
  * there are objects from which DOCX are generated (there is intermediate XML or something like that)
  * almost always I have to do minor fixes and I have to open that docx documents in MS Word
  * it's troublesome to ask the developer team to fix every single bug, because they simply have no time to do it instantly and I have to wait for next release.

I've figured out that if I would get Markdown document, I could edit it
easily, insert some variables and use pandoc to replace those variables with
given data. But to get Markdown I have to know how the devs could generate
tables in Markdown.


Answer: I needed to do just about the same thing to generate Doxygen Markdown tables,
so I thought I'd share. I've run the example code successfully in both Python
2.7 and 3.3, although I can't claim I've tested it rigorously.

    
    
    # Generates tables for Doxygen flavored Markdown.  See the Doxygen
    # documentation for details:
    #   http://www.stack.nl/~dimitri/doxygen/manual/markdown.html#md_tables.
    
    # Translation dictionaries for table alignment
    left_rule = {'<': ':', '^': ':', '>': '-'}
    right_rule = {'<': '-', '^': ':', '>': ':'}
    
    def evalute_field(record, field_spec):
        """
        Evalute a field of a record using the type of the field_spec as a guide.
        """
        if type(field_spec) is int:
            return str(record[field_spec])
        elif type(field_spec) is str:
            return str(getattr(record, field_spec))
        else:
            return str(field_spec(record))
    
    def table(file, records, fields, headings, alignment = None):
        """
        Generate a Doxygen-flavor Markdown table from records.
    
        file -- Any object with a 'write' method that takes a single string
            parameter.
        records -- Iterable.  Rows will be generated from this.
        fields -- List of fields for each row.  Each entry may be an integer,
            string or a function.  If the entry is an integer, it is assumed to be
            an index of each record.  If the entry is a string, it is assumed to be
            a field of each record.  If the entry is a function, it is called with
            the record and its return value is taken as the value of the field.
        headings -- List of column headings.
        alignment - List of pairs alignment characters.  The first of the pair
            specifies the alignment of the header, (Doxygen won't respect this, but
            it might look good, the second specifies the alignment of the cells in
            the column.
    
            Possible alignment characters are:
                '<' = Left align (default for cells)
                '>' = Right align
                '^' = Center (default for column headings)
        """
    
        num_columns = len(fields)
        assert len(headings) == num_columns
    
        # Compute the table cell data
        columns = [[] for i in range(num_columns)]
        for record in records:
            for i, field in enumerate(fields):
                columns[i].append(evalute_field(record, field))
    
        # Fill out any missing alignment characters.
        extended_align = alignment if alignment != None else []
        if len(extended_align) > num_columns:
            extended_align = extended_align[0:num_columns]
        elif len(extended_align) < num_columns:
            extended_align += [('^', '<')
                               for i in range[num_columns-len(extended_align)]]
    
        heading_align, cell_align = [x for x in zip(*extended_align)]
    
        field_widths = [len(max(column, key=len)) if len(column) > 0 else 0
                        for column in columns]
        heading_widths = [max(len(head), 2) for head in headings]
        column_widths = [max(x) for x in zip(field_widths, heading_widths)]
    
        _ = ' | '.join(['{:' + a + str(w) + '}'
                        for a, w in zip(heading_align, column_widths)])
        heading_template = '| ' + _ + ' |'
        _ = ' | '.join(['{:' + a + str(w) + '}'
                        for a, w in zip(cell_align, column_widths)])
        row_template = '| ' + _ + ' |'
    
        _ = ' | '.join([left_rule[a] + '-'*(w-2) + right_rule[a]
                        for a, w in zip(cell_align, column_widths)])
        ruling = '| ' + _ + ' |'
    
        file.write(heading_template.format(*headings).rstrip() + '\n')
        file.write(ruling.rstrip() + '\n')
        for row in zip(*columns):
            file.write(row_template.format(*row).rstrip() + '\n')
    

Here's a simple test case:

    
    
    import sys
    
    sys.stdout.write('State Capitals (source: Wikipedia)\n\n')
    
    headings = ['State', 'Abrev.', 'Capital', 'Capital since', 'Population',
                'Largest Population?']
    
    data = [('Alabama', 'AL', '1819', 'Montgomery', '1846', 155.4, False,
             205764),
            ('Alaska', 'AK', '1959', 'Juneau', '1906', 2716.7, False, 31275),
            ('Arizona', 'AZ', '1912', 'Phoenix', '1889',474.9, True, 1445632),
            ('Arkansas', 'AR', '1836', 'Little Rock', '1821', 116.2, True,
             193524)]
    
    fields = [0, 1, 3, 4, 7, lambda rec: 'Yes' if rec[6] else 'No']
    
    align = [('^', '<'), ('^', '^'), ('^', '<'), ('^', '^'), ('^', '>'),
             ('^','^')]
    
    table(sys.stdout, data, fields, headings, align)
    

Gives this output:

    
    
    State Capitals (source: Wikipedia)
    
    |  State   | Abrev. |   Capital   | Capital since | Population | Largest Population? |
    | :------- | :----: | :---------- | :-----------: | ---------: | :-----------------: |
    | Alabama  |   AL   | Montgomery  |     1846      |     205764 |         No          |
    | Alaska   |   AK   | Juneau      |     1906      |      31275 |         No          |
    | Arizona  |   AZ   | Phoenix     |     1889      |    1445632 |         Yes         |
    | Arkansas |   AR   | Little Rock |     1821      |     193524 |         Yes         |
    

Doxygen renders this as:

![Sample](http://i.stack.imgur.com/bUzZs.png)

