You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
4.9 KiB
157 lines
4.9 KiB
#!/usr/bin/env python3 |
|
|
|
""" |
|
csvtomd 0.2.1 |
|
|
|
Convert your CSV files into Markdown tables. |
|
|
|
More info: http://github.com/mplewis/csvtomd |
|
""" |
|
|
|
import argparse |
|
import csv |
|
import sys |
|
|
|
|
|
def check_negative(value): |
|
try: |
|
ivalue = int(value) |
|
except ValueError: |
|
raise argparse.ArgumentTypeError( |
|
'"%s" must be an integer' % value) |
|
if ivalue < 0: |
|
raise argparse.ArgumentTypeError( |
|
'"%s" must not be a negative value' % value) |
|
return ivalue |
|
|
|
|
|
def pad_to(unpadded, target_len): |
|
""" |
|
Pad a string to the target length in characters, or return the original |
|
string if it's longer than the target length. |
|
""" |
|
under = target_len - len(unpadded) |
|
if under <= 0: |
|
return unpadded |
|
return unpadded + (' ' * under) |
|
|
|
|
|
def normalize_cols(table): |
|
""" |
|
Pad short rows to the length of the longest row to help render "jagged" |
|
CSV files |
|
""" |
|
longest_row_len = max([len(row) for row in table]) |
|
for row in table: |
|
while len(row) < longest_row_len: |
|
row.append('') |
|
return table |
|
|
|
|
|
def pad_cells(table): |
|
"""Pad each cell to the size of the largest cell in its column.""" |
|
col_sizes = [max(map(len, col)) for col in zip(*table)] |
|
for row in table: |
|
for cell_num, cell in enumerate(row): |
|
row[cell_num] = pad_to(cell, col_sizes[cell_num]) |
|
return table |
|
|
|
|
|
def horiz_div(col_widths, horiz, vert, padding): |
|
""" |
|
Create the column dividers for a table with given column widths. |
|
|
|
col_widths: list of column widths |
|
horiz: the character to use for a horizontal divider |
|
vert: the character to use for a vertical divider |
|
padding: amount of padding to add to each side of a column |
|
""" |
|
horizs = [horiz * w for w in col_widths] |
|
div = ''.join([padding * horiz, vert, padding * horiz]) |
|
return div.join(horizs) |
|
|
|
|
|
def add_dividers(row, divider, padding): |
|
"""Add dividers and padding to a row of cells and return a string.""" |
|
div = ''.join([padding * ' ', divider, padding * ' ']) |
|
return div.join(row) |
|
|
|
|
|
def md_table(table, *, padding=1, divider='|', header_div='-'): |
|
""" |
|
Convert a 2D array of items into a Markdown table. |
|
|
|
padding: the number of padding spaces on either side of each divider |
|
divider: the vertical divider to place between columns |
|
header_div: the horizontal divider to place between the header row and |
|
body cells |
|
""" |
|
table = normalize_cols(table) |
|
table = pad_cells(table) |
|
header = table[0] |
|
body = table[1:] |
|
|
|
col_widths = [len(cell) for cell in header] |
|
horiz = horiz_div(col_widths, header_div, divider, padding) |
|
|
|
header = add_dividers(header, divider, padding) |
|
body = [add_dividers(row, divider, padding) for row in body] |
|
|
|
table = [header, horiz] |
|
table.extend(body) |
|
table = [row.rstrip() for row in table] |
|
return '\n'.join(table) |
|
|
|
|
|
def csv_to_table(file, delimiter): |
|
return list(csv.reader(file, delimiter=delimiter)) |
|
|
|
|
|
def main(): |
|
parser = argparse.ArgumentParser( |
|
description='Read one or more CSV files and output their contents in ' |
|
'the form of Markdown tables.') |
|
parser.add_argument('files', metavar='csv_file', type=str, nargs='*', |
|
default=['-'], |
|
help="One or more CSV files to be converted. " |
|
"Use - for stdin.") |
|
parser.add_argument('-n', '--no-filenames', action='store_false', |
|
dest='show_filenames', |
|
help="Don't display filenames when outputting " |
|
"multiple Markdown tables.") |
|
parser.add_argument('-p', '--padding', type=check_negative, default=2, |
|
help="The number of spaces to add between table cells " |
|
"and column dividers. Default is 2 spaces.") |
|
parser.add_argument('-d', '--delimiter', default=',', |
|
help='The delimiter to use when parsing CSV data. ' |
|
'Default is "%(default)s"') |
|
|
|
args = parser.parse_args() |
|
first = True |
|
|
|
if '-' in args.files and len(args.files) > 1: |
|
print('Standard input can only be used alone.', file=sys.stderr) |
|
exit(1) |
|
for file_num, filename in enumerate(args.files): |
|
# Print space between consecutive tables |
|
if not first: |
|
print('') |
|
else: |
|
first = False |
|
# Read the CSV files |
|
if filename == '-': |
|
table = csv_to_table(sys.stdin, args.delimiter) |
|
else: |
|
with open(filename, 'rU') as f: |
|
table = csv_to_table(f, args.delimiter) |
|
# Print filename for each table if --no-filenames wasn't passed and |
|
# more than one CSV was provided |
|
file_count = len(args.files) |
|
if args.show_filenames and file_count > 1: |
|
print(filename + '\n') |
|
# Generate and print Markdown table |
|
print(md_table(table, padding=args.padding)) |
|
|
|
|
|
if __name__ == '__main__': |
|
main()
|
|
|