mew77 commited on
Commit
95fdc69
·
verified ·
1 Parent(s): 9a8affc

Upload 8 files

Browse files
mew_log/__init__.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .ansi_utils import AnsiCodeHelper, ansi_color_str, ansi_format_str, ansi_link_str, ansi_rainbow_str, print_to_ansi_alt_screen_buffer
2
+ from .attr_utils import get_caller_info, get_class_attributes, get_class_name, get_method_args, get_method_info, get_partial_argspec, get_prev_caller_info, get_prev_frame, get_prev_frame_from_frame, get_signature, get_source_info, get_thread_info, get_type_name, get_var_value, get_variable_info, is_bytes_like_type, is_complex_type, is_of_type, list_class_attributes
3
+ from .extended_symbols import ExtendedSymbols, draw_arrow, draw_box, format_arrow
4
+ from .log_utils import LogSpinner, allow_curly_braces, ensure_directory_exists, ensure_file_exists, format_arg, format_args, format_duration, format_kwarg, format_path, format_traceback, get_timestamp, init_log, log_error, log_file, log_function, log_info, log_text_input, log_warning, make_formatted_msg, parse_duration, run_unit_test, stringf, trace_error
5
+ from .mem_utils import bytes_to_kb, bytes_to_mb, format_mem_size_value, format_size_bytes, get_mem_size, get_mem_size_breakdown, save_obj_graph_img, take_mem_growth_snapshot
6
+ from .profile_utils import ProfileSection, allow_curly_braces, create_or_get_profile, format_profile_registry, get_profile_registry, get_profile_reports, log_profile_registry, make_time_str, profile_function, profile_start, profile_stop
7
+ from .table_utils import format_data_as_table, format_dict_as_table, format_list_as_table, format_obj_as_table, format_property, format_square_layout, format_table, print_as_square, print_square_layout, print_table, transpose_dict
8
+
9
+ __all__ = [
10
+ 'AnsiCodeHelper', 'ansi_color_str', 'ansi_format_str', 'ansi_link_str', 'ansi_rainbow_str', 'print_to_ansi_alt_screen_buffer',
11
+ 'get_caller_info', 'get_class_attributes', 'get_class_name', 'get_method_args', 'get_method_info', 'get_partial_argspec', 'get_prev_caller_info', 'get_prev_frame', 'get_prev_frame_from_frame', 'get_signature',
12
+ 'get_source_info', 'get_thread_info', 'get_type_name', 'get_var_value', 'get_variable_info', 'is_bytes_like_type', 'is_complex_type', 'is_of_type', 'list_class_attributes',
13
+ 'ExtendedSymbols', 'draw_arrow', 'draw_box', 'format_arrow',
14
+ 'LogSpinner', 'allow_curly_braces', 'ensure_directory_exists', 'ensure_file_exists', 'format_arg', 'format_args', 'format_duration', 'format_kwarg', 'format_path', 'format_traceback',
15
+ 'get_timestamp', 'init_log', 'log_error', 'log_file', 'log_function', 'log_info', 'log_text_input', 'log_warning', 'make_formatted_msg', 'parse_duration',
16
+ 'run_unit_test', 'stringf', 'trace_error',
17
+ 'bytes_to_kb', 'bytes_to_mb', 'format_mem_size_value', 'format_size_bytes', 'get_mem_size', 'get_mem_size_breakdown', 'save_obj_graph_img', 'take_mem_growth_snapshot',
18
+ 'ProfileSection', 'allow_curly_braces', 'create_or_get_profile', 'format_profile_registry', 'get_profile_registry', 'get_profile_reports', 'log_profile_registry', 'make_time_str', 'profile_function', 'profile_start',
19
+ 'profile_stop',
20
+ 'format_data_as_table', 'format_dict_as_table', 'format_list_as_table', 'format_obj_as_table', 'format_property', 'format_square_layout', 'format_table', 'print_as_square', 'print_square_layout', 'print_table',
21
+ 'transpose_dict',
22
+ ]
mew_log/ansi_utils.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import time
3
+
4
+ # from colorama import Fore, Back, Style
5
+ # from colorama import just_fix_windows_console, init
6
+
7
+ # init(autoreset=True)
8
+ # # use Colorama to make Termcolor work on Windows too
9
+ # just_fix_windows_console()
10
+
11
+
12
+ class AnsiCodeHelper:
13
+ ansi_codes = {
14
+ 'fg_colors': {
15
+ 'black': '\033[30m',
16
+ 'red': '\033[31m',
17
+ 'green': '\033[32m',
18
+ 'yellow': '\033[33m',
19
+ 'blue': '\033[34m',
20
+ 'magenta': '\033[35m',
21
+ 'cyan': '\033[36m',
22
+ 'white': '\033[37m',
23
+ 'bright_black': '\033[1;30m',
24
+ 'bright_red': '\033[1;31m',
25
+ 'bright_green': '\033[1;32m',
26
+ 'bright_yellow': '\033[1;33m',
27
+ 'bright_blue': '\033[1;34m',
28
+ 'bright_magenta': '\033[1;35m',
29
+ 'bright_cyan': '\033[1;36m',
30
+ 'bright_white': '\033[1;37m',
31
+ 'reset_color': '\033[39m',
32
+ },
33
+ 'bg_colors': {
34
+ 'black': '\033[40m',
35
+ 'red': '\033[41m',
36
+ 'green': '\033[42m',
37
+ 'yellow': '\033[43m',
38
+ 'blue': '\033[44m',
39
+ 'magenta': '\033[45m',
40
+ 'cyan': '\033[46m',
41
+ 'white': '\033[47m',
42
+ 'reset_background': '\033[49m',
43
+ },
44
+ 'text_format': {
45
+ 'bold': '\033[1m',
46
+ 'underline': '\033[4m',
47
+ 'italic': '\033[3m',
48
+ 'inverse': '\033[7m',
49
+ 'blink': '\033[5m',
50
+ 'hidden': '\033[8m',
51
+ 'strike_through': '\033[9m',
52
+ 'frame': '\033[51m',
53
+ 'encircled': '\033[52m',
54
+ 'overlined': '\033[53m',
55
+ 'reset_format': '\033[0m',
56
+ },
57
+ 'link': {
58
+ 'url_start': '\033]8;;',
59
+ 'url_end': '\033]8;;\033\\',
60
+ },
61
+ 'utility': {
62
+ 'escape_sequence_start': '\033]',
63
+ 'escape_sequence_end': '\033\\',
64
+ 'enable_alternate_screen_buffer': '\033[?1049h',
65
+ 'disable_alternate_screen_buffer': '\033[?1049l',
66
+ }
67
+ }
68
+
69
+ @staticmethod
70
+ def get_ansi_code(category, name):
71
+ return AnsiCodeHelper.ansi_codes.get(category, {}).get(name, '')
72
+
73
+
74
+ def ansi_link_str(url, link_text="", link_color='bright_green'):
75
+ # Get the ANSI escape sequence for the default color
76
+ # Check if link text is empty
77
+ if link_text == '':
78
+ link_text = url
79
+
80
+ # Check if stdout is a terminal
81
+ if sys.stdout.isatty():
82
+ # get ansi codes
83
+ # default_color_code = AnsiCodeHelper.get_ansi_code('fg_colors', link_color)
84
+ # format_reset_code = AnsiCodeHelper.get_ansi_code('text_format', 'reset_format')
85
+ start_sequence = AnsiCodeHelper.get_ansi_code('utility', 'escape_sequence_start')
86
+ end_sequence = AnsiCodeHelper.get_ansi_code('utility', 'escape_sequence_end')
87
+ # create url link sequence
88
+ url_start = AnsiCodeHelper.get_ansi_code('link', 'url_start')
89
+ url_end = AnsiCodeHelper.get_ansi_code('link', 'url_end')
90
+ link_color_code = AnsiCodeHelper.get_ansi_code('fg_colors', link_color)
91
+ reset_format_code = AnsiCodeHelper.get_ansi_code('text_format', 'reset_format')
92
+
93
+ formatted_link_text = f"{url_start}{url}{end_sequence}{link_color_code}{link_text}{reset_format_code}{url_end}"
94
+
95
+ # log_info(f'formatted_link_text={formatted_link_text}\n\n')
96
+
97
+ return formatted_link_text
98
+ else:
99
+ # If stdout is not a terminal, return the default formatted link text
100
+ return link_text
101
+
102
+
103
+ def ansi_color_str(s, fg='white', bg=None):
104
+ if bg is None:
105
+ color_type = 'fg_colors'
106
+ color_code = AnsiCodeHelper.get_ansi_code(color_type, fg)
107
+ reset_color_code = AnsiCodeHelper.get_ansi_code(color_type, 'reset_color')
108
+ return f"{color_code}{s}{reset_color_code}"
109
+ else:
110
+ fg_color_code = AnsiCodeHelper.get_ansi_code('fg_colors', fg)
111
+ bg_color_code = AnsiCodeHelper.get_ansi_code('bg_colors', bg)
112
+ reset_bg_color_code = AnsiCodeHelper.get_ansi_code('bg_colors', 'reset_background')
113
+ reset_fg_color_code = AnsiCodeHelper.get_ansi_code('fg_colors', 'reset_color')
114
+ return f"{bg_color_code}{fg_color_code}{s}{reset_fg_color_code}{reset_bg_color_code}"
115
+
116
+
117
+ def ansi_format_str(s, format_name):
118
+ format_code = AnsiCodeHelper.get_ansi_code('text_format', format_name)
119
+ reset_format_code = AnsiCodeHelper.get_ansi_code('text_format', 'reset_format')
120
+ return f"{format_code}{s}{reset_format_code}"
121
+
122
+
123
+ def ansi_rainbow_str(message, delay=0.1, iterations=10):
124
+ # init_thread(ansi_rainbow_anim, message, delay)
125
+ rainbow_colors = ['red', 'yellow', 'green', 'blue', 'magenta', 'cyan']
126
+ for _ in range(iterations):
127
+ for color in rainbow_colors:
128
+ color_code = AnsiCodeHelper.get_ansi_code('fg_colors', color)
129
+ reset_color_code = AnsiCodeHelper.get_ansi_code('fg_colors', 'reset_color')
130
+ rainbow_str = ansi_color_str(message, color)
131
+ log_info(rainbow_str, end='\r', flush=True)
132
+ time.sleep(delay)
133
+ log_info('\n')
134
+
135
+
136
+ def print_to_ansi_alt_screen_buffer(info):
137
+ # Enable alternate screen buffer
138
+ sys.stdout.write(
139
+ f"{AnsiCodeHelper.get_ansi_code('utility', 'escape_sequence_start')}{AnsiCodeHelper.get_ansi_code('utility', 'enable_alternate_screen_buffer')}")
140
+
141
+ # Display additional information on the alternate screen buffer
142
+ log_info(info)
143
+
144
+ # Disable alternate screen buffer
145
+ sys.stdout.write(
146
+ f"{AnsiCodeHelper.get_ansi_code('utility', 'escape_sequence_start')}{AnsiCodeHelper.get_ansi_code('utility', 'disable_alternate_screen_buffer')}")
147
+
148
+
mew_log/attr_utils.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ..mew_log.ansi_utils import ansi_link_str, ansi_color_str
2
+
3
+
4
+ def get_type_name(attr):
5
+ return type(attr).__name__
6
+
7
+ def is_complex_type(obj):
8
+ return is_of_type(variable, (list, set, dict)) or hasattr(variable, '__dict__')
9
+
10
+
11
+ def is_bytes_like_type(obj, bytes_like_types=(memoryview, bytes, bytearray), raise_err=False):
12
+ try:
13
+ return is_of_type(obj, bytes_like_types)
14
+ except TypeError as e:
15
+ if raise_err:
16
+ raise TypeError(f"Provided object does not match the provided types: {bytes_like_types}")
17
+ return False
18
+
19
+ def is_of_type(obj, types, raise_err=False):
20
+ if isinstance(obj, (types)):
21
+ return True
22
+ elif raise_err:
23
+ raise TypeError(f"Provided object does not match the provided types: {tuple(types)}")
24
+ return False
25
+
26
+
27
+ def get_partial_argspec(method):
28
+ if not callable(method):
29
+ return None # Not a callable object
30
+ try:
31
+ full_argspec = inspect.getfullargspec(method)
32
+ return full_argspec
33
+ except TypeError:
34
+ # Fallback to using inspect.signature
35
+ signature = get_signature(method)
36
+ if signature:
37
+ parameters = signature.parameters
38
+ args = [param for param in parameters if parameters[param].default == parameters[param].empty]
39
+ varargs = signature.varargs
40
+ varkw = signature.varkw
41
+ defaults = [parameters[param].default for param in args]
42
+ return inspect.FullArgSpec(args, varargs, varkw, defaults)
43
+
44
+ def get_signature(method):
45
+ try:
46
+ signature = inspect.signature(method)
47
+ return signature
48
+ except (TypeError, ValueError):
49
+ return None
50
+
51
+ def get_method_args(method):
52
+
53
+ full_arg_spec = get_partial_argspec(method)
54
+ if full_arg_spec:
55
+ args = [arg for arg in full_arg_spec.args if
56
+ getattr(method, arg, None) is not None and getattr(method, arg, "") != ""]
57
+ kwargs = {key: getattr(method, key, None) for key in full_arg_spec.kwonlyargs}
58
+ kwargs_defaults = {key: value for key, value in
59
+ zip(full_arg_spec.kwonlyargs, full_arg_spec.kwonlydefaults or ())}
60
+ args.extend(f"{key}={value}" for key, value in kwargs.items() if value is not None and value != "")
61
+ return args
62
+ return None
63
+
64
+
65
+ def get_source_info(attr):
66
+ try:
67
+ source_lines, line_number = inspect.getsourcelines(attr)
68
+ source_file_path = inspect.getsourcefile(attr)
69
+ source_file = os.path.relpath(source_file_path)
70
+ return f"/{source_file}::{line_number}"
71
+ except Exception as e:
72
+ return "Source info not available!"
73
+
74
+
75
+ def get_method_info(method):
76
+ args = get_method_args(method)
77
+ args_str = ", ".join(args) if args else ''
78
+ signature = get_signature(method)
79
+ return_str = f' -> {signature.return_annotation}' if signature and signature.return_annotation is not inspect.Signature.empty else ''
80
+
81
+ try:
82
+ source_info = get_source_info(method)
83
+ except Exception as e:
84
+ raise Exception("Source info not available!", e)
85
+
86
+ # Construct the file:// URL with line number for the method if available
87
+ method_file_url = f"file://{inspect.getsourcefile(method)}#L{inspect.getsourcelines(method)[1]}"
88
+ method_link = ansi_link_str(method_file_url, "Source")
89
+
90
+ # Include the link in the method signature string
91
+ method_signature = f"{signature}{return_str}: {method_link}\n-->{source_info}"
92
+
93
+ return method_signature
94
+ def get_var_value(variable):
95
+ return f'{str(variable)}' if not is_of_type(variable, (list, set, dict)) or hasattr(variable, '__dict__') else '...'
96
+
97
+ def get_variable_info(variable):
98
+ return f"<{ get_type_name(variable) }>: { get_var_value(variable) }"
99
+
100
+ def list_class_attributes(cls, verbose=True, use_color=False):
101
+ def format_str(s, fg=None):
102
+ return ansi_color_str(s, fg=fg) if use_color else s
103
+
104
+ # Determine whether cls is a class or an instance of a class
105
+ if inspect.isclass(cls):
106
+ class_name = cls.__name__
107
+ else:
108
+ class_name = cls.__class__.__name__
109
+
110
+ variables = [
111
+ f'{attribute}{get_variable_info(getattr(cls, attribute))}' if verbose else f'{attribute}<{get_type_name(getattr(cls, attribute))}>'
112
+ for attribute in dir(cls) if not attribute.startswith('__') and not callable(getattr(cls, attribute))]
113
+ methods = [f'{attribute}{get_method_info(getattr(cls, attribute))}' if verbose else f'{attribute}<method>' for
114
+ attribute in dir(cls) if not attribute.startswith('__') and callable(getattr(cls, attribute))]
115
+
116
+ variables_str = '\n'.join([f' - {format_str(var, fg="green")}' for var in variables])
117
+ methods_str = '\n'.join([f' - {format_str(method, fg="blue")}' for method in methods])
118
+
119
+ cls_name = format_str(class_name, fg="cyan") if use_color else class_name
120
+ return f'===list_class_attributes of: {cls_name}:\n===<variables>===\n{variables_str}\n===<methods>===\n{methods_str}'
121
+
122
+ def get_class_attributes(cls, verbose=True, use_color=True):
123
+ def format_str(s, fg=None):
124
+ return ansi_color_str(s, fg=fg) if use_color else s
125
+
126
+ attributes_dict = {'variables': {}, 'methods': {}}
127
+
128
+ for attribute_v in vars(cls):
129
+ if not attribute_v.startswith('__') and 'stat' not in attribute_v:
130
+ attr = getattr(cls, attribute_v)
131
+ if not callable(attr):
132
+ attr_info = get_variable_info(attr) if verbose else ''
133
+ formatted_key = format_str(attribute_v, fg="green")
134
+ formatted_value = format_str(attr_info, fg="cyan") if verbose else ''
135
+ attributes_dict['variables'][attribute_v] = f'\n ~ {formatted_key}{formatted_value}'
136
+
137
+ for attribute in dir(cls):
138
+ if not attribute.startswith('__'):
139
+ attr = getattr(cls, attribute)
140
+ if callable(attr):
141
+ method_info = get_method_info(attr) if verbose else ''
142
+ formatted_key = format_str(attribute, fg="blue")
143
+ formatted_value = format_str(method_info, fg="cyan") if verbose else ''
144
+ attributes_dict['methods'][attribute] = f'\n ~ {formatted_key}{formatted_value}'
145
+
146
+ return attributes_dict
147
+
148
+
149
+ import threading
150
+
151
+
152
+ def get_thread_info(message='', use_color=True):
153
+ current_thread = threading.current_thread()
154
+ current_thread_name = current_thread.name
155
+ current_thread_id = current_thread.ident
156
+ current_thread_alive = current_thread.is_alive()
157
+
158
+ # Construct the colored thread info
159
+ thread_info = f'thread:{current_thread_name}::{current_thread_id}::{current_thread_alive}'
160
+ if use_color:
161
+ thread_info = ansi_color_str(thread_info,fg='yellow', bg='bright_yellow')
162
+
163
+ formatted_message = f'{thread_info}:{message}'
164
+
165
+ return formatted_message
166
+
167
+
168
+ import inspect
169
+ import os
170
+
171
+ # Get the current frame
172
+ def get_prev_frame(steps=1):
173
+ curr_frame = inspect.currentframe()
174
+
175
+ # Traverse back the specified number of steps in the call stack
176
+ for _ in range(steps):
177
+ if curr_frame is not None:
178
+ curr_frame = curr_frame.f_back
179
+
180
+ if curr_frame is None:
181
+ return None
182
+
183
+ return curr_frame
184
+
185
+
186
+ def get_prev_frame_from_frame(frame, steps=1):
187
+ curr_frame = frame
188
+
189
+ # Traverse back the specified number of steps in the call stack
190
+ for _ in range(steps):
191
+ if frame is not None:
192
+ curr_frame = curr_frame.f_back
193
+
194
+ if curr_frame is None:
195
+ return None
196
+
197
+ return curr_frame
198
+
199
+ def get_prev_caller_info(message='', use_color=True, steps=99):
200
+ # Get the current frame
201
+ curr_frame = inspect.currentframe()
202
+ caller_frame = curr_frame.f_back
203
+
204
+ while not caller_frame.f_back is None:
205
+ caller_frame = caller_frame.f_back
206
+ steps -=1
207
+ if steps <= 0:
208
+ break
209
+
210
+ previous_frame = caller_frame
211
+
212
+ # Retrieve the information about the previous frame
213
+ frame_info = inspect.getframeinfo(previous_frame)
214
+ # Get the file name where the function was called
215
+ filename_with_path = frame_info.filename
216
+ # Extract only the file name
217
+ filename = os.path.basename(filename_with_path)
218
+ # Get the line number in the file where the function was called
219
+ linenumber = frame_info.lineno
220
+ # Get the function name
221
+ function = frame_info.function
222
+ # Format the string to include the passed message
223
+ caller_link = ansi_link_str(f"file:///{filename_with_path}", f"{filename}::{linenumber}::{function}") if use_color else f"{filename}::{linenumber}::{function}"
224
+ info_str = f"{caller_link}: {message}"
225
+ # Clean up to prevent reference cycles
226
+ del curr_frame
227
+ del caller_frame
228
+ del previous_frame
229
+ return info_str
230
+
231
+
232
+ def get_caller_info(message='', use_color=True):
233
+ # Get the current frame
234
+ curr_frame = inspect.currentframe()
235
+ # Get the caller's frame
236
+ caller_frame = curr_frame.f_back
237
+ # Retrieve the information about the caller's frame
238
+ frame_info = inspect.getframeinfo(caller_frame)
239
+ # Get the file name where the function was called
240
+ filename_with_path = frame_info.filename
241
+ # Extract only the file name
242
+ filename = os.path.basename(filename_with_path)
243
+ # Get the line number in the file where the function was called
244
+ linenumber = frame_info.lineno
245
+ # get the function name
246
+ function = frame_info.function
247
+ # Format the string to include the passed message
248
+ #caller_link = f"file:///{filename_with_path}", f"{filename}::{linenumber}::{function}"
249
+ caller_link = ansi_link_str(f"file:///{filename_with_path}", f"{filename}::{linenumber}::{function}") if use_color else f"{filename}::{linenumber}::{function}"
250
+ #caller_link = f"{filename}::{linenumber}::{function}"
251
+
252
+ info_str = f"{caller_link}: {message}" # file://
253
+ # Clean up to prevent reference cycles
254
+ del curr_frame
255
+ del caller_frame
256
+ return info_str
257
+
258
+
259
+ def get_class_name(obj):
260
+ return obj.__class__.__name__
mew_log/extended_symbols.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import enum
2
+
3
+ class ExtendedSymbols:
4
+ class Symbol(enum.Enum):
5
+ # Correctly differentiating between single and double line symbols
6
+ TOP_RIGHT_L = '\u2510' # ┐ Single line top right corner
7
+ BOTTOM_RIGHT_L = '\u2518' # ┘ Single line bottom right corner
8
+ TOP_LEFT_L = '\u250C' # ┌ Single line top left corner
9
+ BOTTOM_LEFT_L = '\u2514' # └ Single line bottom left corner
10
+ RIGHT_T = '\u2524' # ┤ Single line right T
11
+ LEFT_T = '\u251C' # ├ Single line left T
12
+ TOP_T = '\u252C' # ┬ Single line top T
13
+ BOTTOM_T = '\u2534' # ┴ Single line bottom T
14
+ CROSS = '\u253C' # ┼ Single line cross
15
+ SINGLE_HORIZONTAL = '\u2500' # ─ Single horizontal line
16
+ SINGLE_VERTICAL = '\u2502' # │ Single vertical line
17
+
18
+ # Double line versions
19
+ DOUBLE_TOP_RIGHT_L = '\u2557' # ╗ Double line top right corner
20
+ DOUBLE_BOTTOM_RIGHT_L = '\u255D' # ╝ Double line bottom right corner
21
+ DOUBLE_TOP_LEFT_L = '\u2554' # ╔ Double line top left corner
22
+ DOUBLE_BOTTOM_LEFT_L = '\u255A' # ╚ Double line bottom left corner
23
+ DOUBLE_RIGHT_T = '\u2563' # ╣ Double line right T
24
+ DOUBLE_LEFT_T = '\u2560' # ╠ Double line left T
25
+ DOUBLE_TOP_T = '\u2566' # ╦ Double line top T
26
+ DOUBLE_BOTTOM_T = '\u2569' # ╩ Double line bottom T
27
+ DOUBLE_CROSS = '\u256C' # ╬ Double line cross
28
+ DOUBLE_HORIZONTAL = '\u2550' # ═ Double horizontal line
29
+ DOUBLE_VERTICAL = '\u2551' # ║ Double vertical line
30
+
31
+ SOLID_BLOCK = '\u2588' # █ Solid block
32
+ TOP_HALF_BLOCK = '\u2580' # ▀ Top half block
33
+ BOTTOM_HALF_BLOCK = '\u2584' # ▄ Bottom half block
34
+ SHADED_BLOCK = '\u2592' # ▒ Shaded block
35
+
36
+ def __getattr__(self, name):
37
+ try:
38
+ return self.Symbol[name].value
39
+ except KeyError:
40
+ raise AttributeError(f"No such symbol: {name}")
41
+
42
+ # Make ExtendedSymbols an instance so it can be used directly
43
+ ExtendedSymbols = ExtendedSymbols()
44
+
45
+
46
+
47
+
48
+ def draw_box(msg=None, width=None, height=None, title=None, line_type='single'):
49
+ # Use extended symbols correctly with Unicode
50
+ line_char = ExtendedSymbols.SINGLE_HORIZONTAL if line_type == 'single' else ExtendedSymbols.DOUBLE_HORIZONTAL
51
+ corner_tl = ExtendedSymbols.TOP_LEFT_L if line_type == 'single' else ExtendedSymbols.DOUBLE_TOP_LEFT_L
52
+ corner_tr = ExtendedSymbols.TOP_RIGHT_L if line_type == 'single' else ExtendedSymbols.DOUBLE_TOP_RIGHT_L
53
+ corner_bl = ExtendedSymbols.BOTTOM_LEFT_L if line_type == 'single' else ExtendedSymbols.DOUBLE_BOTTOM_LEFT_L
54
+ corner_br = ExtendedSymbols.BOTTOM_RIGHT_L if line_type == 'single' else ExtendedSymbols.DOUBLE_BOTTOM_RIGHT_L
55
+ vertical_line = ExtendedSymbols.SINGLE_VERTICAL if line_type == 'single' else ExtendedSymbols.DOUBLE_VERTICAL
56
+
57
+ # Determine width based on message length if not specified
58
+ if msg and (width is None or height is None):
59
+ width = max(len(msg), width if width else 0) + 2 # Add padding
60
+ height = 3 # Minimum box height with top, middle, and bottom
61
+
62
+ # Use default dimensions if none provided
63
+ if width is None:
64
+ width = 10 # Default width
65
+ if height is None:
66
+ height = 3 # Default minimum height
67
+
68
+ # Draw top border
69
+ top_border = f"{corner_tl}{line_char * width}{corner_tr}"
70
+ if title:
71
+ title_str = f" {title} "
72
+ title_length = len(title_str)
73
+ pre_title_length = (width - title_length) // 2
74
+ post_title_length = width - title_length - pre_title_length
75
+ top_border = f"{corner_tl}{line_char * pre_title_length}{title_str}{line_char * post_title_length}{corner_tr}"
76
+
77
+ # Prepare message line or blank lines
78
+ if msg:
79
+ message_line = f"{vertical_line} {msg} {vertical_line}"
80
+ if len(msg) < width - 2:
81
+ msg = msg + ' ' * (width - 2 - len(msg))
82
+ message_line = f"{vertical_line}{msg}{vertical_line}"
83
+ else:
84
+ message_line = f"{vertical_line}{' ' * (width)}{vertical_line}"
85
+
86
+ # Draw middle lines
87
+ middle_lines = (f"{vertical_line}{' ' * width}{vertical_line}\n") * (height - 2)
88
+ middle_lines = middle_lines[:-1] # Remove the last newline for proper formatting
89
+
90
+ # Draw bottom border
91
+ bottom_border = f"{corner_bl}{line_char * width}{corner_br}"
92
+
93
+ # Combine all parts
94
+ box = f"{top_border}\n{message_line}\n{middle_lines}\n{bottom_border}"
95
+ print(box)
96
+
97
+ def format_arrow(root_symbol=None, line_type='single', length=10):
98
+ """
99
+ Draw an arrow with a specified root symbol, line type, and length.
100
+ """
101
+ symbol_map = {
102
+ 'L': {
103
+ 'single': ExtendedSymbols.BOTTOM_LEFT_L,
104
+ 'double': ExtendedSymbols.DOUBLE_BOTTOM_LEFT_L,
105
+ },
106
+ 'T': {
107
+ 'single': ExtendedSymbols.LEFT_T,
108
+ 'double': ExtendedSymbols.DOUBLE_LEFT_T
109
+ },
110
+ '+': {
111
+ 'single': ExtendedSymbols.CROSS,
112
+ 'double': ExtendedSymbols.DOUBLE_CROSS
113
+ }
114
+ }
115
+
116
+ # Default to a single horizontal line if undefined symbol or line type
117
+ chosen_symbol = symbol_map.get(root_symbol, {}).get(line_type, '')
118
+ line_char = ExtendedSymbols.DOUBLE_HORIZONTAL if line_type == 'double' else ExtendedSymbols.SINGLE_HORIZONTAL
119
+
120
+ # Construct the arrow string
121
+ return chosen_symbol + line_char * length + ">"
122
+
123
+
124
+ def draw_arrow(root_symbol=None, line_type='single', length=10):
125
+ format_arrow_str = format_arrow(root_symbol, line_type, length)
126
+ print(format_arrow_str)
127
+
mew_log/log_utils.py ADDED
@@ -0,0 +1,502 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Standard Library Imports
2
+ import datetime
3
+ import threading
4
+ import time
5
+ import traceback
6
+ from pathlib import Path
7
+
8
+ import enum
9
+
10
+ # Third-Party Imports
11
+ from loguru import logger
12
+
13
+ logger.add("./data/log/file_{time}.log", rotation="500 MB") # Automatically rotate too big log files
14
+
15
+
16
+ # Local Imports
17
+ from ..mew_log.ansi_utils import ansi_color_str, ansi_link_str
18
+ from ..mew_log.attr_utils import get_prev_caller_info, get_caller_info, get_thread_info, get_prev_frame, get_prev_frame_from_frame, is_of_type
19
+ from ..mew_log.table_utils import format_property, format_table
20
+ from ..mew_log.extended_symbols import ExtendedSymbols, draw_box, draw_arrow, format_arrow
21
+ #from ..mew_log.mew_context import MewContext, resolve_context
22
+
23
+ # Constants
24
+ lastLog = None # Stores the most recent log entry
25
+ debug_log = [] # Stores all log entries
26
+ startTime=time.time()
27
+ #settings vars
28
+ enable_log = True
29
+ enable_log_to_file = False
30
+ log_filename = "log"
31
+ log_file_path = "data/log/log.txt"
32
+ enable_fancy = True
33
+ enable_use_color = True
34
+
35
+ type_color='yellow'
36
+ value_color='bright_yellow'
37
+ arrow_color = 'bright_white'
38
+
39
+ def allow_curly_braces(original_string):
40
+ if "{" in original_string or "}" in original_string:
41
+ escaped_string = original_string.replace("{", "{{").replace("}", "}}")
42
+ #print("Escaped String:", escaped_string) # Debug output
43
+ return escaped_string
44
+ return original_string
45
+
46
+ def format_arg(arg, use_color, fancy):
47
+ def is_complex_type(obj):
48
+ return is_of_type(obj, (list, set, dict)) or hasattr(obj, '__dict__')
49
+
50
+ type_str = f"<{type(arg).__name__}>"
51
+ value_str = repr(arg) if not isinstance(arg, str) and not fancy else format_table(arg, use_color=use_color)
52
+ newline_if_needed = '\n' if is_complex_type(arg) else ""
53
+ formatted_arg = f"{type_str}:{newline_if_needed}{value_str}"
54
+ formatted_arg = allow_curly_braces(formatted_arg)
55
+ return ansi_color_str(formatted_arg, fg=value_color) if use_color else formatted_arg
56
+
57
+
58
+ def format_kwarg(kw, arg, use_color, fancy):
59
+ name_str = ansi_color_str(kw, fg=kw_color) if use_color else kw
60
+ arg_str = format_arg(arg, use_color, fancy)
61
+ formatted_arg = f"{name_str}: {arg_str}"
62
+ return ansi_color_str(formatted_arg, fg=value_color) if use_color else formatted_arg
63
+
64
+ def format_args(use_color=enable_use_color, fancy=enable_fancy, *args, **kwargs):
65
+ formatted_args = [ format_arg(arg, use_color, fancy) for arg in args]
66
+ formatted_kwargs = {k: format_kwarg(k, v, use_color, fancy) for k, v in kwargs.items()}
67
+ return formatted_args, formatted_kwargs
68
+
69
+ def make_formatted_msg(msg, *args, **kwargs):
70
+ #fetch format vars from kwargs
71
+ fancy = kwargs.pop('fancy', enable_fancy)
72
+ use_color = kwargs.pop('use_color', enable_use_color)
73
+ context_depth = kwargs.pop('context_depth', None)
74
+ context = kwargs.pop('context', None)
75
+ # print(f"args={args}")
76
+ # print(f"kwargs={kwargs}")
77
+
78
+ formatted_args, formatted_kwargs = format_args(use_color, fancy, *args, **kwargs)
79
+
80
+ msgf = stringf(msg, *formatted_args, **formatted_kwargs)
81
+
82
+ #context = MewContext.resolve_context(context, depth, steps=5)
83
+ if fancy:
84
+ formatted_msg = f"\n ~ | {get_timestamp()} | {get_thread_info(get_prev_caller_info(msgf, use_color=use_color, steps=2))}"
85
+ else:
86
+ formatted_msg = f"\n ~ | {get_timestamp()} | {context}:{msgf}"
87
+
88
+ return formatted_msg
89
+
90
+ def stringf(s: str, *args, **kwargs):
91
+ # s = allow_curly_braces(s) # Uncomment or modify as needed
92
+ if not args and not kwargs:
93
+ #print("Both args and kwargs are empty.")
94
+ return s
95
+ else:
96
+ return s.format(*args, **kwargs)
97
+ # if not args:
98
+ # print("Args is empty.")
99
+ # if not kwargs:
100
+ # print("Kwargs is empty.")
101
+
102
+ # print(s)
103
+ # print(f"args: {args}")
104
+ # print(f"kwargs: {kwargs}")
105
+ # return s.format(*args, **kwargs)
106
+
107
+ # No arguments or keyword arguments
108
+ # stringf("Hello, World!")
109
+
110
+ # # With positional arguments
111
+ # stringf("Hello, {}!", "World")
112
+
113
+ # # With keyword arguments
114
+ # stringf("Hello, {name}!", name="Alice")
115
+
116
+ # # With both args and kwargs empty (only prints "Hello, World!")
117
+ # stringf("Hello, World!")
118
+
119
+ def ensure_directory_exists(directory):
120
+ path = Path(directory)
121
+ path.mkdir(parents=True, exist_ok=True)
122
+
123
+ def ensure_file_exists(file_path):
124
+ path = Path(file_path)
125
+ path.touch(exist_ok=True)
126
+
127
+ #from ..mew_log.mew_log_helper import MewLogHelper
128
+
129
+ # Logging Configuration
130
+
131
+ # Configure Loguru's logger to use a custom format
132
+ import sys
133
+ import io
134
+
135
+ # Set UTF-8 encoding for stdout and stderr
136
+ #sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding='utf-8', line_buffering=True)
137
+ #sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding='utf-8', line_buffering=True)
138
+
139
+ #logger.add(sys.stdout, format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | {level} | {message}", backtrace=True)
140
+
141
+ logger.configure(handlers=[
142
+ {
143
+ "sink": sys.stdout,
144
+ "format": "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | {level} | {message}", # | <cyan>{file}:{line}:{function}</cyan>
145
+ "level": "INFO"
146
+ },
147
+ {
148
+ "sink": sys.stdout,
149
+ "format": "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | {level} | {message}", # | <yellow>{file}:{line}:{function}</yellow>
150
+ "level": "WARNING"
151
+ },
152
+ {
153
+ "sink": sys.stderr,
154
+ "format": "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | {level} | {message}", #| <red>{file}:{line}:{function}</red>
155
+ "level": "ERROR"
156
+ }
157
+ ])
158
+
159
+
160
+ # Logging Configuration
161
+ def init_log(log_config):
162
+ global enable_log_to_file, enable_log, log_filename, log_file_path
163
+ enable_log_to_file = log_config['log_to_file']
164
+ clear_file_first = log_config['clear_file_first']
165
+ enable_log = log_config['enable_log']
166
+ log_filename = log_config['log_filename']
167
+ log_dir = 'data/log/'
168
+ ensure_directory_exists(log_dir)
169
+ log_file_path = f"data/log/{log_filename}.txt"
170
+ ensure_file_exists(log_file_path)
171
+ with open(log_file_path, 'w') as fp:
172
+ pass
173
+
174
+
175
+ print(f'init_log: \n ~ enable_log: {enable_log} \n ~ enable_log_to_file: {enable_log_to_file} \n ~ log_file_path: {log_file_path}')
176
+
177
+
178
+ def _update_log(formatted_msg_str):
179
+ global lastLog, debug_log, enable_log_to_file
180
+
181
+ lastLog = formatted_msg_str
182
+
183
+ debug_log.append(lastLog)
184
+
185
+ if enable_log_to_file:
186
+ log_file(lastLog)
187
+
188
+
189
+ def log_file(msg, file_path = None):
190
+ global log_file_path
191
+ if not enable_log:
192
+ return
193
+ if file_path is None:
194
+ file_path = log_file_path
195
+
196
+ curTime = time.time()
197
+ elapsedTime = curTime - startTime
198
+ with open(log_file_path, 'w') as fp:
199
+ fp.write(f"\n ~ | time={elapsedTime:.2f}s ~ {msg}")
200
+ #logger.add(log_file_path, format="time={elapsedTime:.2f}s ~ {msg}")
201
+
202
+
203
+
204
+ def log_info(msg, *args,**kwargs):
205
+ if not enable_log:
206
+ return
207
+
208
+ formatted_msg_str = make_formatted_msg(msg, *args,**kwargs)
209
+
210
+ _update_log(formatted_msg_str)
211
+
212
+ formatted_log_str = lastLog
213
+ print(formatted_log_str)
214
+ #logger.info(formatted_log_str)
215
+
216
+ def log_warning(msg, *args,**kwargs):
217
+ if not enable_log:
218
+ return
219
+
220
+ formatted_msg_str = make_formatted_msg(msg, *args,**kwargs)
221
+
222
+ _update_log(formatted_msg_str)
223
+
224
+ formatted_log_str = lastLog
225
+
226
+ #print(formatted_log_str)
227
+
228
+ logger.warning(formatted_log_str)
229
+
230
+ def log_error(msg, e, *args,**kwargs):
231
+ if not enable_log:
232
+ return
233
+
234
+ formatted_msg_str = f"\n===^_^===\n{make_formatted_msg(msg, *args, **kwargs)}"
235
+ formatted_msg_str += f"{trace_error(e)}\n===>_<==="
236
+
237
+ _update_log(formatted_msg_str)
238
+
239
+ formatted_log_str = lastLog
240
+
241
+ #print(formatted_log_str)
242
+
243
+ logger.error(formatted_log_str)
244
+
245
+
246
+ import functools
247
+ import traceback
248
+
249
+ def log_function(func):
250
+ @functools.wraps(func)
251
+ def wrapper(*args, **kwargs):
252
+ def get_func_args():
253
+ arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
254
+ func_args = dict(zip(arg_names, args))
255
+ func_args.update(kwargs)
256
+
257
+ try:
258
+ arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
259
+ func_args = dict(zip(arg_names, args))
260
+ func_args.update(kwargs)
261
+ func_args_str = format_table(func_args, headers=None, tablefmt='simple')
262
+ #print(get_prev_caller_info(f"({func_args_str} )"))
263
+ result = func(*args, **kwargs)
264
+ print(get_thread_info(get_prev_caller_info(f"({func_args} ) -> result: {format_arg(result, use_color=True, fancy=True)}")))
265
+ return result
266
+ except Exception as e:
267
+ arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
268
+ func_args = dict(zip(arg_names, args))
269
+ func_args.update(kwargs)
270
+
271
+ logger.error(f"Exception: {str(e)}")
272
+ logger.error(f"Format Traceback: {traceback.format_exc()}")
273
+ raise # Re-raise the exception after logging
274
+ return wrapper
275
+
276
+ # @log_function
277
+ # def dive(a, b):
278
+ # return a / b
279
+
280
+ # dive(2,3)
281
+
282
+ # dive(12,3)
283
+
284
+ # dive(14,3)
285
+
286
+ # dive(21,3)
287
+
288
+ # dive(21,0)
289
+
290
+ error_color = 'bright_red'
291
+
292
+ def format_traceback(tb_entry, depth=0, use_color=enable_use_color):
293
+ arrow_str = format_arrow('T', 'double', 3 + 2 * depth)
294
+
295
+ filename, lineno, funcname, line = tb_entry
296
+ file_path = f"file:///{filename}"
297
+ file_link = f"{filename}::{lineno}::{funcname}"
298
+ trace_link = ansi_link_str(file_path, file_link, link_color="bright_magenta") if use_color else file_link
299
+
300
+ arrow_str2 = format_arrow('L', 'double', 3 + 2 * (depth+1))
301
+ #(" " * (3 + 2 * depth))+
302
+ formatted_tb_str = f'\n ~ | {arrow_str}trace({depth}): {trace_link}\n ~ | {arrow_str2} Line: "{line}"'
303
+ return ansi_color_str(formatted_tb_str, fg=error_color) if use_color else formatted_tb_str
304
+
305
+ def trace_error(e, use_color=True, fancy=True):
306
+ exception_str = f"\n ~ | {ExtendedSymbols.DOUBLE_VERTICAL}Exception: {repr(e)}"
307
+ out_str = ansi_color_str(exception_str, fg=error_color) if use_color else exception_str
308
+ if isinstance(e, BaseException):
309
+ arrow_str = format_arrow('T', 'double', 3)
310
+ traceback_obj = e.__traceback__
311
+ if traceback_obj:
312
+ file_path = f"file:///{traceback_obj.tb_frame.f_code.co_filename}"
313
+ file_link = f"{traceback_obj.tb_frame.f_code.co_filename}::{traceback_obj.tb_lineno}"
314
+ trace_link = ansi_link_str(file_path, file_link, link_color="bright_magenta") if use_color else file_link
315
+ formatted_tb_str = f"\n ~ | {arrow_str}trace(0): {trace_link}"
316
+ out_str += ansi_color_str(formatted_tb_str, fg=error_color) if use_color else formatted_tb_str
317
+
318
+ tb = traceback.extract_tb(traceback_obj)
319
+ for i, tb_entry in enumerate(tb):
320
+ out_str += format_traceback(tb_entry, depth=i+1, use_color=use_color)
321
+
322
+ return ansi_color_str(out_str, fg=error_color) if use_color else out_str
323
+
324
+
325
+ # log_info(' ~ | test log_info: {}', 1)
326
+ # log_warning(' ~ | test log_warning: {}', 2)
327
+
328
+
329
+ def log_text_input(key: str):
330
+ log_info(f'\n ~ | Text Input[{key}]: ')
331
+ input_text = input()
332
+ log_info(f'\n ~ | Recv input: "{input_text}"')
333
+ return input_text
334
+
335
+
336
+ def format_duration(seconds):
337
+ hours, remainder = divmod(seconds, 3600)
338
+ minutes, seconds = divmod(remainder, 60)
339
+ return f"{int(hours)}h {int(minutes)}m {int(seconds)}s"
340
+
341
+ def parse_duration(duration_str):
342
+ parts = duration_str.split()
343
+ hours = int(parts[0][:-1]) if 'h' in parts[0] else 0
344
+ minutes = int(parts[1][:-1]) if 'm' in parts[1] else 0
345
+ seconds = int(parts[2][:-1]) if 's' in parts[2] else 0
346
+ return hours * 3600 + minutes * 60 + seconds
347
+
348
+
349
+ def get_timestamp():
350
+ return datetime.datetime.now().strftime("%Y-%b-%d %H:%M:%S")
351
+
352
+
353
+ def format_path(directory, depth=0, max_depth=3, use_color=True):
354
+ """Formats the directory structure as a string up to a specified depth, with optional color.
355
+
356
+ Args:
357
+ directory (str or Path): The directory path to format.
358
+ depth (int): The current depth in the directory structure.
359
+ max_depth (int): The maximum depth to format.
360
+ use_color (bool): Whether to apply color to the output.
361
+
362
+ Returns:
363
+ str: The formatted directory structure.
364
+ """
365
+ if max_depth is not None and depth > max_depth:
366
+ return ""
367
+
368
+ directory_path = Path(directory)
369
+ if not directory_path.is_dir():
370
+ return "The specified path is not a directory.\n"
371
+
372
+ # Build the formatted string, applying color if requested
373
+ line_prefix = "│ " * depth + "├── "
374
+ directory_name = directory_path.name
375
+ if use_color:
376
+ line_prefix = ansi_color_str(line_prefix, fg='cyan') # Assuming color_str function exists
377
+ directory_name = ansi_color_str(directory_name, fg='green') # Adjust colors as needed
378
+
379
+ formatted_str = line_prefix + directory_name + "\n"
380
+
381
+ # Sort directory contents for consistent order
382
+ sorted_items = sorted(directory_path.iterdir(), key=lambda x: (x.is_file(), x.name))
383
+
384
+ for item in sorted_items:
385
+ if item.is_dir():
386
+ # Recursive call for directories, increasing the depth
387
+ formatted_str += format_path(item, depth + 1, max_depth, use_color)
388
+ else:
389
+ # Include file name with indentation, applying color if requested
390
+ file_line = "│ " * (depth + 1) + "├── " + item.name
391
+ if use_color:
392
+ file_line = ansi_color_str(file_line, fg='white') # Example color, adjust as needed
393
+ formatted_str += file_line + "\n"
394
+
395
+ return formatted_str
396
+
397
+
398
+ # Spinner Class
399
+ class LogSpinner:
400
+ def __init__(self, message="", rainbow=True, anim_speed=0.1):
401
+ self.message = message
402
+ self.speed = anim_speed
403
+ self.colors = ['\033[31m', '\033[33m', '\033[32m', '\033[34m', '\033[35m', '\033[36m']
404
+ self.spinner = ['⠇', '⠋', '⠙', '⠸', '⠼', '⠴', '⠦', '⠧']
405
+
406
+ #self.spinner = ['|', '/', '-', '\\']
407
+ self.rainbow = rainbow
408
+
409
+ def __enter__(self):
410
+ self.stop_spinner = False
411
+ self.startTime = time.time()
412
+ # spinner_text = f'^_^ | {self.message} - time: {0.0:.2f}s | Begin!'
413
+ # print(spinner_text)
414
+ self.spinner_thread = threading.Thread(target=self.spin)
415
+ self.spinner_thread.start()
416
+ return self
417
+
418
+ def __exit__(self, exc_type, exc_value, traceback):
419
+ self.stop_spinner = True
420
+ self.spinner_thread.join()
421
+
422
+ curTime = time.time()
423
+ spinner_text = f'>_< | {self.message} - time: {format_duration(curTime - self.startTime)} | Done!'
424
+ print(spinner_text)
425
+
426
+ def spin(self):
427
+ while not self.stop_spinner:
428
+ #self.update_spinner()
429
+ if self.rainbow: self.update_color_spinner()
430
+ else: self.update_spinner()
431
+
432
+ def update_spinner(self):
433
+ for char in self.spinner:
434
+ curTime = time.time()
435
+ spinner_text = f'{self.message} - time: {format_duration(curTime - self.startTime)} |'
436
+ print(f'\r ^_^ | {char} | {spinner_text}', end='\r', flush=True)
437
+ time.sleep(self.speed)
438
+
439
+ def update_color_spinner(self):
440
+ for color in self.colors:
441
+ for char in self.spinner:
442
+ curTime = time.time()
443
+ spinner_text = f'{self.message} - time: {format_duration(curTime - self.startTime)} |'
444
+ print(f'^_^ | {color}{char}\033[0m | {spinner_text}', end='\r', flush=True)
445
+ time.sleep(self.speed)
446
+
447
+
448
+ def run_unit_test():
449
+ # Example usage:
450
+ class Person:
451
+ def __init__(self, name, age, city):
452
+ self.name = name
453
+ self.age = age
454
+ self.city = city
455
+
456
+ # Create instances of Person
457
+ person1 = Person('John', 30, 'New York')
458
+ person2 = Person('Alice', 25, 'Los Angeles')
459
+ person3 = Person('Bob', 30, 'Hong Kong')
460
+ person4 = Person('Charlie', 35, 'Shanghai')
461
+ person5 = Person('David', 40, 'Beijing')
462
+
463
+ # Define other data structures
464
+ data_dict = {'Name': 'John', 'Age': 30, 'City': 'New York'}
465
+ data_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
466
+ data_set = {'apple', 'banana', 'orange'}
467
+ data_dict_of_lists = {'Name': ['John', 'Alice'], 'Age': [30, 25], 'City': ['New York', 'Los Angeles']}
468
+ data_list_of_dicts = [person1.__dict__, person2.__dict__]
469
+ data_dict_of_dicts = {'Person1': person1.__dict__, 'Person2': person2.__dict__}
470
+ list_of_objs = [person3, person4, person5]
471
+ data_list_of_lists = [['John', 30, 'New York'], ['Alice', 25, 'Los Angeles']]
472
+ dict_of_objs = {'Alice': person3, 'Bob': person4, 'Charlie': person5}
473
+ dict_of_list_of_objs = {'Group1': [person3, person4], 'Group2': [person5]}
474
+
475
+ # Combine all data into a complex structure
476
+ complex_data = {
477
+ 'data_dict': data_dict,
478
+ 'data_list': data_list,
479
+ 'data_set': data_set,
480
+ 'data_dict_of_lists': data_dict_of_lists,
481
+ 'data_list_of_dicts': data_list_of_dicts,
482
+ 'data_dict_of_dicts': data_dict_of_dicts,
483
+ 'list_of_objs': list_of_objs,
484
+ 'data_list_of_lists': data_list_of_lists,
485
+ 'dict_of_objs': dict_of_objs,
486
+ 'dict_of_list_of_objs': dict_of_list_of_objs
487
+ }
488
+
489
+ # Log each unique data structure
490
+ log_info("Data Dictionary: {}", data_dict)
491
+ log_info("Data List: {}", data_list)
492
+ log_info("Data Set: {}", data_set)
493
+ log_info("Data Dictionary of Lists: {}", data_dict_of_lists)
494
+ log_info("Data List of Dicts: {}", data_list_of_dicts)
495
+ log_info("Data Dictionary of Dicts: {}", data_dict_of_dicts)
496
+ log_info("List of Objects: {}", list_of_objs)
497
+ log_info("Data List of Lists: {}", data_list_of_lists)
498
+ log_info("Dictionary of Objects: {}", dict_of_objs)
499
+ log_info("Dictionary of List of Objects: {}", dict_of_list_of_objs)
500
+
501
+
502
+ #run_unit_test()
mew_log/mem_utils.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ #from ..mew_core import prettify
3
+ from ..mew_log.ansi_utils import ansi_color_str
4
+ from ..mew_log.attr_utils import get_caller_info, get_var_value
5
+ from ..mew_log.table_utils import format_table
6
+
7
+ import sys
8
+ import types
9
+ import objgraph
10
+
11
+ one_mb = 2**20
12
+
13
+ def bytes_to_mb(size_in_bytes, inv_bytes_in_mb=(1.0 / (1024 * 1024))):
14
+ """
15
+ Convert size from bytes to megabytes.
16
+ """
17
+ return size_in_bytes * inv_bytes_in_mb
18
+
19
+
20
+ def bytes_to_kb(size_in_bytes, inv_bytes_in_kb=(1.0 / (1024))):
21
+ """
22
+ Convert size from bytes to kilobytes.
23
+ """
24
+ return size_in_bytes * inv_bytes_in_kb
25
+
26
+ def format_mem_size_value(size_bytes, use_color=True):
27
+ size_b = size_bytes
28
+ size_mb = bytes_to_mb(size_b)
29
+ size_kb = bytes_to_kb(size_b)
30
+
31
+ size_mb_str = f"{size_mb:.4f} MB"
32
+ size_kb_str = f"{size_kb:.4f} KB"
33
+ size_b_str = f"{size_b} B"
34
+
35
+ # Determine which size to display based on the criteria
36
+ if size_mb > 0.1: # More than 0.1 MB
37
+ display_str = size_mb_str
38
+ elif size_kb > 0.1: # More than 0.1 KB but less than 0.1 MB
39
+ display_str = size_kb_str
40
+ else: # Less than 0.1 KB
41
+ display_str = size_b_str
42
+
43
+ # Apply color if use_color is True
44
+ if use_color:
45
+ display_str = ansi_color_str(display_str, fg='cyan')
46
+
47
+ return f"Mem Size: {display_str}" # Display the selected size
48
+
49
+
50
+ def format_size_bytes(obj, use_color=True):
51
+ size_b, size_mb = get_mem_size(obj)
52
+ size_kb = bytes_to_kb(size_b)
53
+ size_mb_str = f"{size_mb:.4f} MB"
54
+ size_kb_str = f"{size_kb:.4f} KB"
55
+ size_b_str = f"{size_b} B"
56
+
57
+ # Determine which size to display based on the criteria
58
+ if size_mb > 0.1: # More than 0.1 MB
59
+ display_str = size_mb_str
60
+ elif size_kb > 0.1: # More than 0.1 KB but less than 0.1 MB
61
+ display_str = size_kb_str
62
+ else: # Less than 0.1 KB
63
+ display_str = size_b_str
64
+
65
+ # Apply color if use_color is True
66
+ if use_color:
67
+ display_str = ansi_color_str(display_str, fg='cyan')
68
+
69
+ return f"Mem Size: {display_str}" # Display the selected size
70
+
71
+
72
+
73
+ def get_mem_size(obj) -> tuple:
74
+ """
75
+ Get the size of an object as a tuple ( bytes , megabytes )
76
+ """
77
+ def _get_mem_size(obj):
78
+ size = sys.getsizeof(obj)
79
+
80
+ if isinstance(obj, types.GeneratorType):
81
+ # Generators don't have __sizeof__, so calculate size recursively
82
+ size += sum(_get_mem_size(item) for item in obj)
83
+ elif isinstance(obj, dict):
84
+ size += sum(_get_mem_size(key) + _get_mem_size(value) for key, value in obj.items())
85
+ elif hasattr(obj, '__dict__'):
86
+ # For objects with __dict__, include the size of their attributes
87
+ size += _get_mem_size(obj.__dict__)
88
+
89
+ return size
90
+
91
+ size_b = _get_mem_size(obj)
92
+ size_mb = bytes_to_mb(size_b)
93
+
94
+ return size_b, size_mb
95
+
96
+ def get_mem_size_breakdown(obj, do_recursive=False) -> dict:
97
+ """
98
+ Get a breakdown of the sizes of the properties of an object.
99
+ """
100
+
101
+ size_b, size_mb = get_mem_size(obj) #'name': obj, 'value': get_variable_value(obj)
102
+ breakdown = { 'name': obj,'type': type(obj).__name__, 'size (B, MB)': f"{size_b} B | {size_mb:.3f} MB", 'value': get_var_value(obj), }
103
+
104
+ if do_recursive:
105
+ if isinstance(obj, types.GeneratorType):
106
+ for i, item in enumerate(obj):
107
+ breakdown['generator_'+type(item).__name__][i] = get_mem_size_breakdown(item)
108
+ elif isinstance(obj, dict):
109
+ for key, value in obj.items():
110
+ if key != 'stat':
111
+ breakdown[f'[{key}]'] = get_mem_size_breakdown(value)
112
+ elif hasattr(obj, '__dict__'):
113
+ breakdown['vars'] = {}
114
+ for key, value in obj.__dict__.items():
115
+ if key != 'stat':
116
+ breakdown['vars'][f'[{key}]'] = get_mem_size_breakdown(value)
117
+ return breakdown
118
+
119
+ def save_obj_graph_img(obj):
120
+ # Generate and save a graph of objects referencing a specific object
121
+ graph_filename = f'data/image/obj_graph_{obj.__class__.__name__}.png'
122
+ objgraph.show_refs([obj], filename=graph_filename)
123
+ return graph_filename
124
+
125
+ def take_mem_growth_snapshot():
126
+ # Take a snapshot of the objects that have grown since the last snapshot
127
+ growth_info = objgraph.show_growth()
128
+ if growth_info:
129
+ # Convert the growth information to a dictionary
130
+ growth_dict = {}
131
+ for line in growth_info.split('\n'):
132
+ if line.strip(): # Skip empty lines
133
+ parts = line.split()
134
+ obj_type = parts[0]
135
+ prev_count = int(parts[1])
136
+ growth_count = int(parts[2])
137
+ growth_dict[obj_type] = {'prev_count': prev_count, 'growth_count': growth_count}
138
+ #print(format_table(growth_dict))
139
+ return growth_dict
140
+
141
+
142
+
143
+
144
+
mew_log/profile_utils.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+
3
+ from ..mew_log.ansi_utils import ansi_color_str
4
+ from ..mew_log.attr_utils import get_prev_caller_info
5
+ from ..mew_log.log_utils import log_info, LogSpinner
6
+ from ..mew_log.table_utils import format_table
7
+
8
+
9
+ time_color = ''
10
+
11
+ # helper funcs
12
+ class ProfileSection:
13
+ Registry = {} # Class Variable
14
+
15
+ def __init__(self, newMsg=None, enable=True, do_print=False):
16
+ self.msg = newMsg if newMsg else get_prev_caller_info()
17
+ self.enable = enable
18
+ self.startTime = 0.0
19
+ self.endTime = 0.0
20
+ self.elapsedTime = 0.0
21
+ self.totalTime = 0.0
22
+ self.avgTime = 0.0
23
+ self.numCalls = 0
24
+ self.do_print = do_print
25
+ if self.msg in ProfileSection.Registry:
26
+ p = ProfileSection.Registry[self.msg]
27
+ self.__dict__ = p.__dict__
28
+ self.numCalls += 1
29
+ else:
30
+ self.numCalls += 1
31
+
32
+ if (self.enable):
33
+ self.start()
34
+
35
+ def __del__(self):
36
+ if (self.enable):
37
+ self.stop()
38
+
39
+ def __enter__(self):
40
+ # if self.enable:
41
+ self.start()
42
+
43
+ return self
44
+
45
+ def __exit__(self, exc_type, exc_value, traceback):
46
+ # if self.enable:
47
+ self.stop()
48
+
49
+ def start(self):
50
+ self.startTime = time.time()
51
+ # if self.do_print : log_info(str(self))
52
+
53
+ def stop(self):
54
+ self.endTime = time.time()
55
+ self.update_stats()
56
+ stopMsg = str(self)
57
+
58
+ p = create_or_get_profile(self.msg)
59
+ p.__dict__.update(self.__dict__)
60
+ if self.do_print: log_info(str(p))
61
+
62
+ # st.toast(stopMsg)
63
+ return stopMsg
64
+
65
+ def update_stats(self):
66
+ self.elapsedTime = self.endTime - self.startTime
67
+ self.totalTime += self.elapsedTime
68
+ self.avgTime = self.totalTime / float(self.numCalls)
69
+
70
+ def __str__(self, use_color=True):
71
+ msg_str = self.msg #ansi_color_str(self.msg, fg='green') # Green color for message
72
+ elapsed_time_str = make_time_str('elapsed', self.elapsedTime)
73
+ total_time_str = make_time_str('total', self.totalTime)
74
+ avg_time_str = make_time_str('avg', self.avgTime)
75
+ calls_str = f'calls={self.numCalls}'
76
+ if use_color:
77
+ elapsed_time_str = ansi_color_str(elapsed_time_str, fg='bright_cyan') # Cyan color for elapsed time
78
+ total_time_str = ansi_color_str(total_time_str, fg='bright_cyan') # Cyan color for total time
79
+ avg_time_str = ansi_color_str(avg_time_str, fg='bright_cyan') # Cyan color for average time
80
+ calls_str = ansi_color_str(calls_str, fg='yellow') # Cyan color for calls information
81
+
82
+ return f"{msg_str} ~ Elapsed Time: {elapsed_time_str} | Total Time: {total_time_str} | Avg Time: {avg_time_str} | {calls_str}"
83
+
84
+
85
+ from functools import wraps
86
+ from threading import Thread
87
+ from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn, TaskProgressColumn
88
+ from rich.console import Console
89
+ from rich.style import Style
90
+ from rich.panel import Panel
91
+
92
+
93
+ def profile_function(func):
94
+ """
95
+ Decorator to track the progress of a function with a spinner.
96
+ The decorated function should not require explicit progress updates.
97
+ """
98
+ @wraps(func)
99
+ def wrapper(*args, **kwargs):
100
+ profile_section = ProfileSection(func.__name__, enable=True, do_print=False)
101
+ console = Console()
102
+ with LogSpinner(func.__name__):
103
+ with profile_section:
104
+ result = func(*args, **kwargs)
105
+
106
+ #profile_section.stop()
107
+ # Display timing information using rich
108
+ panel = Panel.fit(
109
+ f"[bold green]{profile_section.msg}[/bold green]\n"
110
+ f"[cyan]Elapsed Time:[/cyan] {make_time_str('elapsed', profile_section.elapsedTime)}\n"
111
+ f"[cyan]Total Time:[/cyan] {make_time_str('total', profile_section.totalTime)}\n"
112
+ f"[cyan]Avg Time:[/cyan] {make_time_str('avg', profile_section.avgTime)}\n"
113
+ f"[yellow]Calls:[/yellow] {profile_section.numCalls}",
114
+ title="Profile Report",
115
+ border_style="bright_blue"
116
+ )
117
+ console.print(panel)
118
+ #print(str(profile_section))
119
+
120
+
121
+ return result
122
+ return wrapper
123
+
124
+
125
+ def make_time_str(msg, value):
126
+ # do something fancy
127
+ value, time_unit = (value / 60, 'min') if value >= 60 else (value * 1000, 'ms') if value < 0.01 else (value, 's')
128
+ return f"{msg}={int(value) if value % 1 == 0 else value:.2f} {time_unit}"
129
+
130
+
131
+ def create_or_get_profile(key, enable=False, do_print=False):
132
+ if key not in ProfileSection.Registry:
133
+ ProfileSection.Registry[key] = ProfileSection(key, enable, do_print)
134
+ return ProfileSection.Registry[key]
135
+
136
+
137
+ def profile_start(msg, enable=True, do_print=False):
138
+ p = create_or_get_profile(msg, enable, do_print)
139
+ if not enable: p.start()
140
+
141
+
142
+ def profile_stop(msg):
143
+ if key in ProfileSection.Registry:
144
+ create_or_get_profile(msg).stop()
145
+
146
+
147
+ def get_profile_registry():
148
+ return ProfileSection.Registry
149
+
150
+ from loguru import logger
151
+
152
+
153
+ def get_profile_reports():
154
+ reports = [value for value in ProfileSection.Registry.values()]
155
+ reports.sort(key=lambda x: (x.totalTime, x.avgTime), reverse=True)
156
+ return reports
157
+
158
+ def log_profile_registry(use_color=True):
159
+ formatted_output = format_profile_registry(use_color=use_color)
160
+ print(formatted_output)
161
+ #logger.info(formatted_output)
162
+ #return formatted_output
163
+
164
+
165
+
166
+ def allow_curly_braces(original_string):
167
+ escaped_string = original_string.replace("{", "{{").replace("}", "}}")
168
+ #print("Escaped String:", escaped_string) # Debug output
169
+ return escaped_string
170
+
171
+ def format_profile_registry(use_color=True):
172
+ reports = get_profile_reports()
173
+ out_str = []
174
+
175
+ out_str.append('=== Profile Reports ===\n')
176
+
177
+ for report in reports:
178
+ out_str.append(str(report)+'\n')
179
+
180
+ out_str.append('===>_<===\n')
181
+ return ''.join(out_str)
182
+
183
+
184
+ # import random
185
+
186
+ # def do_this(y):
187
+ # p = ProfileSection("do_this", True) #only way to use the auto destruct method
188
+ # x = random.randint(0, (y+1)*2)
189
+ # print(f'do_this: {x} - {y}')
190
+
191
+ # for index in range(1000):
192
+ # do_this(index)
193
+
194
+ # log_profile_registry()
mew_log/table_utils.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tabulate import tabulate
2
+
3
+ from ..mew_log.ansi_utils import ansi_color_str
4
+ from ..mew_log.attr_utils import get_class_attributes
5
+
6
+
7
+ def transpose_dict(d):
8
+ return {k: [dic[k] for dic in d.values()] for k in d.keys()}
9
+
10
+
11
+ def format_data_as_table(data, headers=None, tablefmt='grid', **kwargs):
12
+ if kwargs.get('use_color', False):
13
+ # Apply color to headers if applicable
14
+ if headers:
15
+ headers = [ansi_color_str(header, fg="yellow") for header in headers]
16
+
17
+
18
+ return tabulate(data, headers=headers, tablefmt=tablefmt)
19
+
20
+ def format_property(name, data, headers=None, tablefmt='simple_grid', use_color=False):
21
+ colored_name = ansi_color_str(f"===[{name}]<{type(data).__name__}>===", fg="yellow") if use_color else f"===[{name}]<{type(data).__name__}>==="
22
+ return f'{colored_name}\n{format_table(data, headers, tablefmt, use_color=use_color)}'
23
+
24
+
25
+
26
+ # note: pretty sure transpose doesn't work correctly
27
+ def format_table(data, headers=None, tablefmt='simple_grid', **kwargs):
28
+ if not data:
29
+ return ""
30
+ if isinstance(data, dict):
31
+ return format_dict_as_table(data, headers, tablefmt, **kwargs)
32
+ elif isinstance(data, (list, set)):
33
+ # transpose = list(zip(*data))
34
+ return format_list_as_table(data, headers, tablefmt, **kwargs)
35
+ elif hasattr(data, '__dict__'):
36
+ return format_obj_as_table(data, headers, tablefmt, **kwargs)
37
+ else:
38
+ use_color = kwargs.get('use_color', False)
39
+ if use_color:
40
+ return ansi_color_str(str(data), fg='bright_yellow')
41
+ else:
42
+ return str(data)
43
+
44
+
45
+
46
+ def format_list_as_table(data, headers=None, tablefmt='simple', **kwargs):
47
+ if not headers:
48
+ headers = ["Index", "Value"] if kwargs.get('show_indexes', False) else []
49
+
50
+ if kwargs.get('show_indexes', False):
51
+ out_dict = {i: item for i, item in enumerate(data)}
52
+ return format_table(out_dict, headers, tablefmt, **kwargs)
53
+ else:
54
+ formatted_str = ansi_color_str(f" | len={len(data)} | ", fg='bright_cyan') if kwargs.get('use_color', False) else f" | len={len(data)} | "
55
+ formatted_str += "["
56
+ for i, item in enumerate(data):
57
+ formatted_str += format_table(item, [], tablefmt, **kwargs)
58
+ if i < len(data) - 1: formatted_str += kwargs.get('delimeter', ', ')
59
+ formatted_str += "]"
60
+ return formatted_str
61
+
62
+
63
+ def format_dict_as_table(data, headers=None, tablefmt='simple_grid', **kwargs):
64
+ if kwargs.get('transpose', False):
65
+ if not headers:
66
+ headers =list(data.keys())
67
+ #Transpose the dictionary: each key-value pair becomes a column
68
+ transposed_data = [list(value) for key, value in zip(list(data.keys()), zip(*data.items()))]
69
+ #intended for values of the same lenth
70
+ #transposed_data = [headers] + [list(row) for row in zip(list(data.values()))]
71
+ return format_data_as_table(transposed_data, headers=headers, tablefmt=tablefmt)
72
+ else:
73
+ if not headers:
74
+ headers = ["Key", "Value"]
75
+
76
+ # Convert the dictionary into a list of lists
77
+ table_data = [[key, format_table(value, [], 'simple',**kwargs)] for key, value in data.items() if value is not None]
78
+
79
+ # Format the table
80
+ return format_data_as_table(table_data, headers=headers, tablefmt=tablefmt)
81
+
82
+
83
+ def format_obj_as_table(data, headers=None, tablefmt='fancy_grid', **kwargs):
84
+ verbose = kwargs.get('verbose', True)
85
+ fancy = kwargs.get('fancy', True)
86
+ use_color = kwargs.get('use_color', True)
87
+
88
+ attributes_dict = get_class_attributes(data, verbose=verbose)
89
+ class_name = data.__class__.__name__
90
+ variables = [*attributes_dict['variables'].values()]
91
+ methods = [*attributes_dict['methods'].values()]
92
+ # Check if headers are provided, if not, construct them
93
+ if not headers:
94
+ headers = [class_name]
95
+ if variables:
96
+ headers.append('variables')
97
+ if methods:
98
+ headers.append('methods')
99
+
100
+ # Initialize an empty list to store the formatted table data
101
+ table_data = []
102
+
103
+ # formatted_vars = []
104
+ # for v in variables:
105
+ # format_v = format_arg(v, use_color=use_color, fancy=fancy)
106
+ # formatted_vars.append(format_v)
107
+
108
+ # Add variables and methods data to the table data
109
+ table_data.append([format_table(variables, ['variables'], 'simple', **kwargs) if variables else None,
110
+ format_table(methods, ['methods'], 'simple', **kwargs) if methods else None])
111
+
112
+ table_data = list(zip(*table_data))
113
+
114
+ # Return the formatted table
115
+ return format_data_as_table(table_data, headers, tablefmt, **kwargs)
116
+
117
+
118
+ def print_table(msg, data, headers=None, tablefmt='fancy_grid'):
119
+ print(f'==={msg}==>\n{format_table(data, headers, tablefmt)}')
120
+
121
+
122
+ def format_square_layout(data):
123
+ if isinstance(data, (list, set)):
124
+ result_count = len(data)
125
+ rows, columns = calc_best_square(result_count)
126
+ table_data = [data[i:i + columns] for i in range(0, len(data), columns)]
127
+ return format_table(table_data)
128
+ elif isinstance(data, dict):
129
+ items = [(k, v) for k, v in data.items()]
130
+ result_count = len(items)
131
+ rows, columns = calc_best_square(result_count)
132
+ table_data = [items[i:i + columns] for i in range(0, len(items), columns)]
133
+ return format_table(table_data)
134
+ elif hasattr(data, '__dict__'):
135
+ items = [(k, v) for k, v in data.__dict__.items()]
136
+ result_count = len(items)
137
+ rows, columns = calc_best_square(result_count)
138
+ table_data = [items[i:i + columns] for i in range(0, len(items), columns)]
139
+ return format_table(table_data)
140
+ else:
141
+ return format_table(data)
142
+
143
+
144
+ def print_square_layout(msg, data):
145
+ print(f'==={msg}==>\n{format_square_layout(data)}')
146
+
147
+
148
+ def print_as_square(strings):
149
+ # Calculate the number of strings in the input list
150
+ result_count = len(strings)
151
+
152
+ # Calculate the best square layout dimensions based on the result count
153
+ rows, columns = calc_best_square(result_count)
154
+
155
+ # Create a grid with empty strings filled in for each cell
156
+ grid = [[' ' for _ in range(columns)] for _ in range(rows)]
157
+
158
+ # Iterate over the strings and populate the grid with them
159
+ for i, string in enumerate(strings):
160
+ # Calculate the row and column index for the current string
161
+ row = i // columns
162
+ col = i % columns
163
+ # Ensure the row and column indices are within the valid range of the grid dimensions
164
+ if row < rows and col < columns:
165
+ # Place the string in the corresponding cell of the grid
166
+ grid[row][col] = string
167
+
168
+ # Determine the maximum width of each column in the grid
169
+ max_widths = [max(len(cell) for cell in row) for row in grid]
170
+
171
+ # Print the grid, ensuring each cell is left-aligned and padded to its maximum width
172
+ for row in grid:
173
+ print(' '.join(cell.ljust(width) for cell, width in zip(row, max_widths)))
174
+