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.

158 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()