Skip to content

Instantly share code, notes, and snippets.

@gamesbook
Created September 6, 2019 14:05
Show Gist options
  • Save gamesbook/03d4b51db258882015495922198ea450 to your computer and use it in GitHub Desktop.
Save gamesbook/03d4b51db258882015495922198ea450 to your computer and use it in GitHub Desktop.
Create reStructuredText complex table from list of dictionaries
# coding: utf-8
"""Purpose: Create reStructuredText complex table from list of dictionaries
Created: 2019-09-05
Authors: dhohls <dhohls@csir.co.za> Derek Hohls
Example:
code::
string = "Please type this:\n'The quick brown fox jumps over the lazy dog.'"
one = {'Item':'Simple','Details':'One-liner'}
two = {'Item':'Typing test','Details':string}
result = create_table([one, two], column_widths=[15, 30])
for line in result: print(line)
result::
+---------------+------------------------------+
|Item |Details |
+===============+==============================+
|Simple |One-liner |
+---------------+------------------------------+
|Typing test |Please type this: |
| |'The quick brown fox jumps |
| |over the lazy dog.' |
+---------------+------------------------------+
"""
import collections
DEFAUlT_COL_WIDTH = 15
def split_string(string, limit, sep=" "):
"""Split a string by whitespace and prevent splitting words.
Args:
string (str): to be split update
limit (int): maximum number of characters in string
sep (string): the character on which to split
Note:
A string will automatically be split on a line-break character.
"""
result = []
_string = string.split('\n')
for sub_string in _string:
words = sub_string.split()
if max(map(len, words)) > limit:
raise ValueError("limit is too small")
part, others = words[0], words[1:]
for word in others:
if len(sep) + len(word) > limit - len(part):
result.append(part)
part = word
else:
part += sep + word
if part:
result.append(part)
return result
def create_table(the_list, column_widths=None, default_width=None):
"""Create lines for a complex reStructured text table.
Args:
the_list (list): Python dictionaries with the same keys
column_widths (list): int representing column width in fixed-characters
default_width (int): common column width in fixed-characters
"""
if not the_list or not isinstance(the_list, list):
return ''
for item in the_list:
if not isinstance(item, dict):
return ''
# setup configuration
try:
default = int(default_width)
except:
default = DEFAUlT_COL_WIDTH
settings = []
headers = the_list[0].keys()
column_widths = column_widths if column_widths else []
settings = {}
for key, header in enumerate(headers):
settings[header] = column_widths[key] if key < len(column_widths) else default
# create header as print-ready lines
lines = []
lines.append("+")
lines.append("|")
lines.append("+")
for key, val in settings.items():
lines[1] += key.ljust(val) + '|'
lines[0] += '-'*val + '+'
lines[2] += '='*val + '+'
# set up blocks of text ("multi-line") for each row/col
formatted = []
for rec_no, record in enumerate(the_list):
formatted.append({'max_rows': 1, 'text': {}})
for key, val in record.items():
split_text = split_string(string=val, limit=settings[key])
formatted[rec_no]['text'][key] = split_text
if len(split_text) > formatted[rec_no]['max_rows']:
formatted[rec_no]['max_rows'] = len(split_text)
# create body as print-ready lines
line_no = 3
for format_item in formatted:
rows = format_item['max_rows']
for row in range(0, rows):
lines.append("|")
for col_key, col_text in format_item['text'].items():
col_size = settings[col_key]
for row in range(0, rows):
if len(col_text) <= row:
lines[line_no + row] += ' '.ljust(col_size) + '|'
else:
lines[line_no + row] += col_text[row].ljust(col_size) + '|'
lines.append(lines[0])
line_no += 1 + rows
return lines
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment