pyenv-virtualenv for Windows 1.2
A 'pyenv' plugin to manage Python virtual environments, depending on different Python versions, for various Python projects.
Loading...
Searching...
No Matches
tbl.py
Go to the documentation of this file.
1##
2# @package tbl
3# @file tbl.py
4# @author Michael Paul Korthals
5# @date 2025-07-10
6# @version 1.0.0
7# @copyright © 2025 Michael Paul Korthals. All rights reserved.
8# See License details in the documentation.
9#
10# Library to output colored tables in "pyenv-virtualenv".
11#
12
13# --- IMPORTS ----------------------------------------------------------
14
15# Python
16import re
17
18# Community
19# (None)
20
21# My
22try:
23 # noinspection PyUnresolvedReferences
24 import lib.log as log
25except ImportError():
26 try:
27 # noinspection PyUnresolvedReferences
28 import log
29 except ImportError():
30 # noinspection PyUnresolvedReferences
31 import log
32
33
34# --- CONSTANTS --------------------------------------------------------
35
36HEADER: int = 1
37SEPARATOR: int = 2
38DATA: int = 3
39
40ROW_TYPES: list[int] = [HEADER, SEPARATOR, DATA]
41
42# --- HELPER -----------------------------------------------------------
43
44## Calculate the length of cell content skipping ANSI ESC sequences
45# (e.g. for color).
46#
47# @param cell_content Raw cell content including ANSI ESC sequences.
48# @return Length of readable cell content without ANSI ESC sequences.
49# @see https://stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python
50def readableLen(cell_content: str) -> int:
51 ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
52 result = ansi_escape.sub('', cell_content)
53 return len(result)
54
55
56# --- CLASSES ----------------------------------------------------------
57
59
60 ## CONSTRUCTOR.
61 #
62 # @param data List of data row lists to define the table.
63 # @param headline Text of the headline shown above the table.
64 # Default: Empty str = No headline.
65 # @param headline_color ANSI color ESC sequence for the headline.
66 # Default: "\x1b[37m". = blue on black background
67 # @param header_color ANSI color ESC sequence for the column headers.
68 # Default: "\x1b[104m" = white on blue background.
70 self,
71 data: list[list],
72 headline: str='',
73 headline_color: str='\x1b[37m',
74 header_color: str='\x1b[104m'
75 ):
76 # Initialize
77 self.data = data
78 self.headline=headline
79 self.headline_color=headline_color
80 self.header_color = header_color
81 # Check for completeness
82 if len(self.data) < 3:
83 log.warning('Data table length is less than 3.')
84 log.info('To fulfill the table design, it must have header row, separator row and a final separator row.')
85 raise ValueError()
86 if not isinstance(self.data[0][0], int):
87 log.warning('The first column of first row is not as "int".')
88 log.info('To fulfill the table design, each row must start with a row type integer constant.')
89 if self.data[0][0] != HEADER:
90 log.warning('First data row is not the table header.')
91 log.info('To fulfil the table design, the table header must be sorted to the first index position.')
92 # Check row data and add aesthetical spaces
93 row_lengths: list[int] = []
94 for i in range(len(self.data)):
95 row = self.data[i]
96 if self.data[i][0] not in ROW_TYPES:
97 log.warning('Found not implemented value "{}" in row type column.'.format(self.data[i][0]))
98 log.info('The first value in each row must be in "{}" = "[tbl.HEADER, tbl.SEPARATOR, tbl.DATA]".'.format(ROW_TYPES))
99 raise NotImplementedError()
100 if self.data[i][0] is SEPARATOR:
101 self.data[i] = (
102 [self.data[i][0]]
103 +
104 [None for _ in range(len(self.data[0]) - 1)]
105 )
106 row_lengths.append(len(self.data[i]))
107 if (row[0] != SEPARATOR) and (row_lengths[-1] < 2):
108 log.warning('Data row length is less than 2.')
109 log.info('Each data and header row must have a row type column and min. 1 data cell column.')
110 raise ValueError()
111 for j in range(len(row)):
112 if j == 0: continue # Skip row type
113 cell = str(data[i][j])
114 # Add aesthetical spaces to cell,
115 # clone and force as "str".
116 if cell is not None:
117 if not cell.startswith(' '):
118 cell = ' ' + str(cell)
119 if not cell.endswith(' '):
120 cell += ' '
121 data[i][j] = cell
122 # End For
123 # End for
124 # Check if all rows have the same number of cells
125 if not all(item == row_lengths[0] for item in row_lengths):
127 'Length of each row is not identical (see {}).'.format(row_lengths))
128 log.info('Check/repair the deviating rows.')
129 raise ValueError()
130
131 ## Two-dimensional data table.
132 # Default: None.
133 data: (list[list], None) = None
134
135 ## Column width list.
136 # Default: None.
137 cw: (list[int], None) = None
138
139 ## Headline, which is printed before the table.
140 headline: (str, None) = None
141
142 ## ANSI ESC-sequence for the header color.
143 # Default: "\x1b[37m".
144 headline_color: str = '\x1b[37m'
145
146 ## ANSI ESC-sequence for the header color.
147 # Default: "\x1b[104m".
148 header_color: str = '\x1b[104m'
149
150 ## Calculate width of each column except the row type column.
151 # Store the results in the "self.cw" property.
153 self.cw = [int(0) for _ in range(len(self.data[0]))]
154 # Calculate column with
155 for item in self.data:
156 if not isinstance(item[0], int):
157 log.warning('First item in this row "{}" is not as "int".'.format(item))
158 log.info('The first column in each row defines the row type as "int".')
159 raise ValueError()
160 if item[0] in [1, 3]:
161 for i in range(len(self.cw)):
162 if i == 0:
163 continue # Skip row type
164 else:
165 cc: str = item[i]
166 l = readableLen(cc)
167 if l > self.cw[i]:
168 self.cw[i] = l
169 ## Display the data table.
170 def run(self):
171 print('\n{}{}\x1b[0m\n'.format(
172 self.headline_color,
173 self.headline
174 ))
176 for row in self.data:
177 if row[0] == HEADER:
178 row_str = ''
179 for i in range(len(row)):
180 if i == 0:
181 continue # Skip row type
182 else:
183 row_str += '{}{}{}\x1b[0m'.format(
184 self.header_color,
185 row[i],
186 ' ' * (self.cw[i] - len(row[i])),
187 )
188 if i < len(row) - 1:
189 row_str += ' '
190 # End for
191 print(row_str)
192 elif row[0] == SEPARATOR:
193 row_width = sum(self.cw[1:]) + len(self.cw) - 2
194 row_str = '-' * row_width
195 print(row_str)
196 elif row[0] == DATA:
197 row_str = ''
198 for i in range(len(row)):
199 if i == 0:
200 continue # Skip row type
201 else:
202 row_str += '{}{}'.format(
203 row[i],
204 ' ' * (self.cw[i] - readableLen(row[i])),
205 )
206 if i < len(row) - 1:
207 row_str += ' '
208 # End for
209 print(row_str)
210 # End if
211 # End for
212
213
214# --- END OF CODE ------------------------------------------------------
215
run(self)
Display the data table.
Definition tbl.py:170
str header_color
ANSI ESC-sequence for the header color.
Definition tbl.py:148
list cw
Column width list.
Definition tbl.py:137
__init__(self, list[list] data, str headline='', str headline_color='\x1b[37m', str header_color='\x1b[104m')
CONSTRUCTOR.
Definition tbl.py:75
data
Two-dimensional data table.
Definition tbl.py:133
headline
Headline, which is printed before the table.
Definition tbl.py:140
str headline_color
ANSI ESC-sequence for the header color.
Definition tbl.py:144
calculateColumnWidth(self)
Calculate width of each column except the row type column.
Definition tbl.py:152
warning((str, tuple) msg)
Log warning message colored to console only.
Definition log.py:191
info((str, tuple) msg)
Log info message colored to console only.
Definition log.py:203
int readableLen(str cell_content)
Calculate the length of cell content skipping ANSI ESC sequences (e.g.
Definition tbl.py:50