""" ReStructured Text utilities * Make ReST table given array of values """ from __future__ import division import numpy as np def rst_table(cell_values, row_names = None, col_names = None, title='', val_fmt = '{0:5.2f}', format_chars = None ): """ Return string for ReST table with entries `cell_values` Parameters ---------- cell_values : (R, C) array-like At least 2D. Can be greater than 2D, in which case you should adapt the `val_fmt` to deal with the multiple entries that will go in each cell row_names : None or (R,) length sequence, optional Row names. If None, use ``row[0]`` etc. col_names : None or (C,) length sequence, optional Column names. If None, use ``col[0]`` etc. title : str, optional Title for table. Add as heading above table val_fmt : str, optional Format string using string ``format`` method mini-language. Converts the result of ``cell_values[r, c]`` to a string to make the cell contents. Default assumes a floating point value in a 2D `cell_values`. format_chars : None or dict, optional With keys 'down', 'along', 'thick_long', 'cross' and 'title_heading'. Values are characters for: lines going down; lines going along; thick lines along; two lines crossing; and the title overline / underline. All missing values filled with rst defaults. Returns ------- table_str : str Multiline string with ascii table, suitable for printing """ # formatting if format_chars is None: format_chars = {} down = format_chars.pop('down', '|') along = format_chars.pop('along', '-') thick_long = format_chars.pop('thick_long', '=') cross = format_chars.pop('cross', '+') title_heading = format_chars.pop('title_heading', '*') if len(format_chars) != 0: raise ValueError('Unexpected ``format_char`` keys {0}'.format( ', '.join(format_chars))) down_joiner = ' ' + down + ' ' down_starter = down + ' ' down_ender = ' ' + down cross_joiner = along + cross + along cross_starter = cross + along cross_ender = along + cross cross_thick_joiner = thick_long + cross + thick_long cross_thick_starter = cross + thick_long cross_thick_ender = thick_long + cross # lengths of row names, column names and values cell_values = np.asarray(cell_values) R, C = cell_values.shape[:2] if row_names is None: row_names = ['row[{0}]'.format(r) for r in range(R)] elif len(row_names) != R: raise ValueError('len(row_names) != number of rows') if col_names is None: col_names = ['col[{0}]'.format(c) for c in range(C)] elif len(col_names) != C: raise ValueError('len(col_names) != number of columns') row_len = max(len(name) for name in row_names) col_len = max(len(name) for name in col_names) # Compile row value strings, find longest, extend col length to match row_str_list = [] for row_no in range(R): row_strs = [val_fmt.format(val) for val in cell_values[row_no]] max_len = max(len(s) for s in row_strs) if max_len > col_len: col_len = max_len row_str_list.append(row_strs) row_name_fmt = "{0:<" + str(row_len) + "}" row_names = [row_name_fmt.format(name) for name in row_names] col_name_fmt = "{0:^" + str(col_len) + "}" col_names = [col_name_fmt.format(name) for name in col_names] col_headings = [' ' * row_len] + col_names col_header = down_joiner.join(col_headings) row_val_fmt = '{0:<' + str(col_len) + '}' table_strs = [] if title != '': table_strs += [title_heading * len(title), title, title_heading * len(title), ''] along_headings = [along * len(h) for h in col_headings] crossed_line = (cross_starter + cross_joiner.join(along_headings) + cross_ender) thick_long_headings = [thick_long * len(h) for h in col_headings] crossed_thick_line = (cross_thick_starter + cross_thick_joiner.join(thick_long_headings) + cross_thick_ender) table_strs += [crossed_line, down_starter + col_header + down_ender, crossed_thick_line] for row_no, row_name in enumerate(row_names): row_vals = [row_val_fmt.format(row_str) for row_str in row_str_list[row_no]] row_line = (down_starter + down_joiner.join([row_name] + row_vals) + down_ender) table_strs.append(row_line) table_strs.append(crossed_line) return '\n'.join(table_strs)