File indexing completed on 2024-11-03 08:24:26
0001 # -*- coding: UTF-8 -*- 0002 0003 """ 0004 Pretty-printing of tabular data. 0005 0006 @author: Chusslove Illich (Часлав Илић) <caslav.ilic@gmx.net> 0007 @license: GPLv3 0008 """ 0009 0010 import copy 0011 0012 from pology.colors import ColorString, cjoin 0013 0014 0015 def tabulate (data, coln=None, rown=None, dfmt=None, space=" ", none="", 0016 rotated=False, colorize=False, indent="", 0017 colnra=False, rownra=False, colw=0): 0018 """ 0019 Tabulate data in plain text. 0020 0021 All data fields can have missing trailing entries. They will be set to 0022 C{None} according to table extents. 0023 0024 Examples: 0025 0026 >>> print T.tabulate(data=((1, 4), (2, ), (3, 6)), 0027 ... coln=("c1", "c2", "c3"), rown=("r1", "r2"), 0028 ... space=" ", none="-") 0029 - c1 c2 c3 0030 r1 1 2 3 0031 r2 4 - 6 0032 0033 @param data: column entries (cells) by column 0034 @type data: [[string*]*] 0035 @param coln: column names 0036 @type coln: [string*] 0037 @param rown: row names 0038 @type rown: [string*] 0039 @param dfmt: format strings per column (e.g. C{"%+.2f"} for floats) 0040 @type dfmt: [string*] 0041 @param space: fill-in for spacing between cells 0042 @type space: string 0043 @param none: fill-in for displaying empty cells (i.e. C{None}-valued) 0044 @type none: string 0045 @param rotated: whether the table should be transposed 0046 @type rotated: bool 0047 @param colorize: whether the table should have color highlighting 0048 @type colorize: bool 0049 @param indent: indent string for the whole table 0050 @type indent: string 0051 @param colnra: right align column names 0052 @type colnra: bool 0053 @param rownra: right align row names 0054 @type rownra: bool 0055 @param colw: minimal column width 0056 @type colw: integer 0057 @returns: plain text representation of the table (no trailing newline) 0058 @rtype: string/L{ColorString<colors.ColorString>} 0059 """ 0060 0061 # Make local copies, to be able to extend to table extents. 0062 _data = [] 0063 for col in data: 0064 _data.append(list(col)) 0065 _coln = None 0066 if coln: _coln = list(coln) 0067 _rown = None 0068 if rown: _rown = list(rown) 0069 _dfmt = None 0070 if dfmt: _dfmt = list(dfmt) 0071 0072 # Calculate maximum row and column number. 0073 # ...look at data: 0074 nrows = 0 0075 ncols = 0 0076 for col in _data: 0077 if nrows < len(col): 0078 nrows = len(col) 0079 ncols += 1 0080 # ...look at column and row names: 0081 if _coln is not None: 0082 if ncols < len(_coln): 0083 ncols = len(_coln) 0084 if _rown is not None: 0085 if nrows < len(_rown): 0086 nrows = len(_rown) 0087 0088 # Index offsets due to column/row names. 0089 ro = 0 0090 if _coln is not None: 0091 ro = 1 0092 co = 0 0093 if _rown is not None: 0094 co = 1 0095 0096 # Extend all missing table fields. 0097 # ...add columns: 0098 for c in range(len(_data), ncols): 0099 _data.append([]) 0100 # ...add rows: 0101 for col in _data: 0102 for r in range(len(col), nrows): 0103 col.append(None) 0104 # ...add column names: 0105 if _coln is not None: 0106 if _rown is not None: 0107 _coln.insert(0, none) # header corner 0108 for c in range(len(_coln), ncols + co): 0109 _coln.append(None) 0110 # ...add row names: 0111 if _rown is not None: 0112 if _coln is not None: 0113 _rown.insert(0, none) # header corner 0114 for r in range(len(_rown), nrows + ro): 0115 _rown.append(None) 0116 # ...add formats: 0117 if _dfmt is None: 0118 _dfmt = [] 0119 if _rown is not None: 0120 _dfmt.insert(0, "%s") # header corner 0121 for c in range(len(_dfmt), ncols + co): 0122 _dfmt.append("%s") 0123 0124 # Stringize data. 0125 # ...nice fat deep assembly of empty stringized table: 0126 sdata = [["" for i in range(nrows + ro)] for j in range(ncols + co)] 0127 # ...table body: 0128 for c in range(ncols): 0129 for r in range(nrows): 0130 if _data[c][r] is not None: 0131 sdata[c + co][r + ro] = _dfmt[c + co] % (_data[c][r],) 0132 else: 0133 sdata[c + co][r + ro] = none 0134 # ...column names: 0135 if _coln is not None: 0136 for c in range(ncols + co): 0137 if _coln[c] is not None: 0138 sdata[c][0] = "%s" % (_coln[c],) 0139 # ...row names: 0140 if _rown is not None: 0141 for r in range(nrows + ro): 0142 if _rown[r] is not None: 0143 sdata[0][r] = "%s" % (_rown[r],) 0144 0145 # Rotate needed data for output. 0146 if rotated: 0147 _coln, _rown = _rown, _coln 0148 ncols, nrows = nrows, ncols 0149 co, ro = ro, co 0150 sdata_r = [["" for i in range(nrows + ro)] for j in range(ncols + co)] 0151 for c in range(ncols + co): 0152 for r in range(nrows + ro): 0153 sdata_r[c][r] = sdata[r][c] 0154 sdata = sdata_r 0155 0156 # Calculate maximum lengths per screen column. 0157 maxlen = [colw] * (ncols + co) 0158 for c in range(ncols + co): 0159 for r in range(nrows + ro): 0160 l = len(sdata[c][r]) 0161 if maxlen[c] < l: 0162 maxlen[c] = l 0163 0164 # Reformat strings to maximum length per column. 0165 for c in range(co, ncols + co): 0166 lfmt = "%" + str(maxlen[c]) + "s" 0167 for r in range(ro, nrows + ro): 0168 sdata[c][r] = lfmt % (sdata[c][r],) 0169 # ...but column names aligned as requested: 0170 if _coln is not None: 0171 if colnra: 0172 lfmt = "%" + str(maxlen[c]) + "s" 0173 else: 0174 lfmt = "%-" + str(maxlen[c]) + "s" 0175 sdata[c][0] = lfmt % (sdata[c][0],) 0176 if colorize: 0177 sdata[c][0] = ColorString("<purple>%s</purple>") % sdata[c][0] 0178 # ...but row names aligned as requested: 0179 if _rown is not None: 0180 if rownra: 0181 lfmt = "%" + str(maxlen[0]) + "s" 0182 else: 0183 lfmt = "%-" + str(maxlen[0]) + "s" 0184 for r in range(nrows + ro): 0185 sdata[0][r] = lfmt % (sdata[0][r],) 0186 if colorize: 0187 sdata[0][r] = ColorString("<blue>%s</blue>") % sdata[0][r] 0188 0189 # Assemble the table. 0190 lines = [] 0191 for r in range(nrows + ro): 0192 cells = [] 0193 for c in range(ncols + co): 0194 cells.append(sdata[c][r]) 0195 lines.append(indent + cjoin(cells, space)) 0196 0197 return cjoin(lines, "\n") 0198