#!/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()