Display TTY  1
Customise your terminal's output
Loading...
Searching...
No Matches
my_disp.py
Go to the documentation of this file.
7
8"""
9@file my_disp.py
10@brief The file in charge of managing the beautified output on the terminal.
11"""
12
13import sys
14import time
15import inspect
16
17from typing import List, Dict, Union
18
19import logging
20import colorlog
21
22# Check if the script is being run directly or imported
23if __name__ == "__main__":
24 from colours import LoggerColours
25 from constants import ERR, SUCCESS, OUT_TTY, OUT_STRING, OUT_FILE, OUT_DEFAULT, KEY_OUTPUT_MODE, KEY_PRETTIFY_OUTPUT, KEY_PRETTIFY_OUTPUT_IN_BLOCKS, KEY_ANIMATION_DELAY, KEY_ANIMATION_DELAY_BLOCKY, TOML_CONF, FORBIDDEN_NUMBER_LOG_LEVELS_CORRESPONDANCE, FORBIDDEN_NUMBER_LOG_LEVELS
26 from log_level_tracker import LogLevelTracker
27else:
28 from .colours import LoggerColours
29 from .constants import ERR, SUCCESS, OUT_TTY, OUT_STRING, OUT_FILE, OUT_DEFAULT, KEY_OUTPUT_MODE, KEY_PRETTIFY_OUTPUT, KEY_PRETTIFY_OUTPUT_IN_BLOCKS, KEY_ANIMATION_DELAY, KEY_ANIMATION_DELAY_BLOCKY, TOML_CONF, FORBIDDEN_NUMBER_LOG_LEVELS_CORRESPONDANCE, FORBIDDEN_NUMBER_LOG_LEVELS
30 from .log_level_tracker import LogLevelTracker
31
32
33class Logging:
34 """
35 @class Logging
36 @brief Represents a placeholder for the logging library. This is not a functioning class.
37 """
38
39 def __init__(self) -> None:
40 pass
41
42
43class Disp:
44 """
45 @class Disp
46 @brief The class in charge of displaying messages with various styles and animations.
47
48 @details This class provides methods to display messages in different formats, log messages, and manage output configurations.
49 """
50
51 def __init__(self, toml_content: Dict[str, any], save_to_file: bool = False, file_name: str = "text_output_run.txt", file_descriptor: any = None, debug: bool = False, logger: Union[Logging, str, None] = None, success: int = SUCCESS, error: int = ERR, log_warning_when_present: bool = True, log_errors_when_present: bool = True) -> None:
52 """
53 @brief Constructor for the Disp class.
54
55 @param toml_content Dictionary containing configuration values.
56 @param save_to_file Boolean indicating whether to save output to a file.
57 @param file_name Name of the file to save output to.
58 @param file_descriptor File descriptor for the output file.
59 @param debug Boolean indicating whether debug mode is enabled.
60 @param logger Logger instance or name to use for logging.
61 @param success Integer representing the success status code.
62 @param error Integer representing the error status code.
63 @param log_warning_when_present Boolean indicating whether to log warnings when they arise in one of the function calls.
64 @param log_errors_when_present Boolean indicating whether to log errors when they arise in one of the function calls.
65 """
66 self.__version__ = "1.0.0"
67 self.toml_contenttoml_content = toml_content
68 self.background_colour_key = 'background_colour'
69 self.author = "(c) Created by Henry Letellier"
70 self.nb_chr = 40
71 self.debugdebug = debug
72 self.error = error
73 self.success = success
75 self.log_error_when_present = log_errors_when_present
76 self.log_warning_when_present = log_warning_when_present
79 self.title_wall_chr = self.toml_contenttoml_content["TITLE_WALL_CHARACTER"]
80 self.sub_title_wall_chr = self.toml_contenttoml_content["SUB_TITLE_WALL_CHARACTER"]
81 self.sub_sub_title_wall_chr = self.toml_contenttoml_content["SUB_SUB_TITLE_WALL_CHARACTER"]
82 self.message_char = self.toml_contenttoml_content["MESSAGE_CHARACTER"]
83 self.message_error_char = self.toml_contenttoml_content["MESSAGE_ERROR_CHARACTER"]
84 self.message_success_char = self.toml_contenttoml_content["MESSAGE_SUCCESS_CHARACTER"]
85 self.message_inform_char = self.toml_contenttoml_content["MESSAGE_INFORM_CHARACTER"]
86 self.message_warning_char = self.toml_contenttoml_content["MESSAGE_WARNING_CHARACTER"]
87 self.message_question_char = self.toml_contenttoml_content["MESSAGE_QUESTION_CHARACTER"]
88 if self.toml_contenttoml_content[KEY_PRETTIFY_OUTPUT_IN_BLOCKS] is True:
89 self.message_animation_delay = self.toml_contenttoml_content[KEY_ANIMATION_DELAY_BLOCKY]
90 else:
91 self.message_animation_delay = self.toml_contenttoml_content[KEY_ANIMATION_DELAY]
92 self.tree_node_char = self.toml_contenttoml_content["TREE_NODE_CHAR"]
93 self.tree_node_end_char = self.toml_contenttoml_content["TREE_NODE_END_CHAR"]
94 self.tree_line_seperator_char = self.toml_contenttoml_content["TREE_LINE_SEPERATOR_CHAR"]
95 self.tree_column_seperator_char = self.toml_contenttoml_content["TREE_COLUMN_SEPERATOR_CHAR"]
96 self.file_name = file_name
97 self.save_to_file = save_to_file
98 if self.toml_contenttoml_content[KEY_OUTPUT_MODE] == OUT_FILE:
99 self.save_to_file = True
100 self.file_descriptor = file_descriptor
102 if self.toml_contenttoml_content[KEY_OUTPUT_MODE] not in (OUT_FILE, OUT_STRING, OUT_TTY, OUT_DEFAULT):
103 msg = f"Invalid output mode. Must be one of '{OUT_FILE}', "
104 msg += f"'{OUT_STRING}', '{OUT_TTY}', '{OUT_DEFAULT}'"
105 raise ValueError(msg)
106 if self.toml_contenttoml_content[KEY_OUTPUT_MODE] == OUT_FILE:
107 self._open_file()
108 self._setup_logger(logger)
109
110 def _setup_logger(self, logger: Union[Logging, str, None]) -> None:
111 """
112 @brief Setup the logger for the class.
113
114 @param logger The logger to use. If None, a default logger will be used.
115 """
116 # ---- Logging data ----
117 if callable(logger) and hasattr(logger, "debug"):
118 self.logger = logger
119 else:
120 if isinstance(logger, str) is True:
121 self.logger = logging.getLogger(logger)
122 else:
123 self.logger = logging.getLogger(self.__class__.__name__)
124 if not self.logger.hasHandlers():
125 handler = colorlog.StreamHandler()
126 format_string = '[%(asctime)s] %(log_color)s%('
127 format_string += self.background_colour_key
128 format_string += '_log_color)s%(levelname)s%(reset)s %(name)s: \'%(message)s\''
129 formatter = colorlog.ColoredFormatter(
130 fmt=format_string,
131 datefmt=None,
132 reset=True,
133 style='%',
134 log_colors={
135 'DEBUG': 'cyan',
136 'INFO': 'green',
137 'WARNING': 'yellow',
138 'ERROR': 'red',
139 'CRITICAL': 'bold_red'
140 },
141 secondary_log_colors={
143 'DEBUG': 'bg_black',
144 'INFO': 'bg_black',
145 'WARNING': 'bg_black',
146 'ERROR': 'bg_black',
147 'CRITICAL': 'bg_black',
148 }
149 },
150 )
151 handler.setFormatter(formatter)
152 self.logger.addHandler(handler)
153 # This is what controls the importance of the log that will be allowed to be displayed, the higher the number, the more important the log is.
154 self.update_logger_level(1)
155 node = LogLevelTracker()
156 if node.check_presence() is False:
157 node.inject_class()
158
159 def _create_function(self, name, func_code):
160 # Create a namespace for the function
161 namespace = {}
162
163 # Execute the code that defines the function
164 exec(func_code, globals(), namespace)
165
166 # Extract the function from namespace
167 func = namespace[name]
168 return func
169
170 def _add_function_to_instance(self, func_dest: object, func_name: str, func_code: str) -> None:
171 """
172 @brief Add a dynamically created function to an instance.
173
174 @param func_dest The destination object to add the function to.
175 @param func_name Name of the function.
176 @param func_code Code of the function.
177 """
178 function_instance = self._create_function(func_name, func_code)
179 setattr(func_dest, func_name, function_instance)
180
181 def update_disp_debug(self, debug: bool) -> None:
182 """
183 @brief Update the debug mode.
184
185 @param debug Boolean indicating whether debug mode is enabled.
186 """
187 self.debugdebug = debug
188
189 def update_logger_level(self, level: Union[int, LogLevelTracker.Levels] = LogLevelTracker.Levels.NOTSET) -> None:
190 """
191 @brief Update the logger level.
192
193 @param level The log importance level. Defaults to NOTSET.
194
195 @return The status code of the operation.
196 """
197 _func_name = inspect.currentframe().f_code.co_name
198
199 if isinstance(level, str):
200 level = level.upper()
201 if hasattr(logging, "LogLevelTracker"):
202 level = logging.LogLevelTracker.get_level(level)
203 if isinstance(level, int) is False or level not in LogLevelTracker.Levels.__all__:
204 level = LogLevelTracker.Levels.NOTSET
205 if self.log_warning_when_present is True:
206 self.log_warning(
207 f"The level is not valid, defaulting to {level}",
208 _func_name
209 )
210 self.logger.setLevel(level)
211
212 def _check_the_logging_instance(self, logger_instance: logging.Logger = None) -> logging.Logger:
213 """
214 @brief Check if the logger instance is valid.
215
216 @param logger_instance The logger instance to validate.
217 @return A valid logger instance.
218 """
219 # Get the parent function name if present
220 _func_name = inspect.currentframe()
221 if _func_name.f_back is not None:
222 _func_name = _func_name.f_back.f_code.co_name
223 else:
224 _func_name = _func_name.f_code.co_name
225
226 # Checking the logger instance
227 if logger_instance is None or not isinstance(logger_instance, logging.Logger):
228 if self.log_warning_when_present is True:
229 self.log_warning(
230 "No logger instance provided, using the default logger",
231 _func_name
232 )
233 logger_instance = self.logger
234 return logger_instance
235
236 def _check_colour_data(self, colour: Union[str, int], logger_instance: logging.Logger = None) -> Union[int, str]:
237 """
238 @brief Check if the provided colour data is valid.
239
240 @param colour The colour to validate.
241 @param logger_instance The logger instance to use for logging errors.
242 @return The validated colour or an error code.
243 """
244 _func_name = inspect.currentframe()
245 if _func_name.f_back is not None:
246 _func_name = _func_name.f_back.f_code.co_name
247 else:
248 _func_name = _func_name.f_code.co_name
249
250 # Checking if the colour exists
251 if isinstance(colour, int):
252 colour = LoggerColours.get_colour_string(LoggerColours, colour)
253 if LoggerColours.check_if_colour_present(LoggerColours, colour) is False:
254 if self.log_error_when_present is True:
255 self.log_error(
256 "The provided colour is not valid",
257 _func_name
258 )
259 return self.error
260 return colour
261
262 def _check_level_data(self, level_name: Union[str, int], logger_instance: logging.Logger = None) -> Union[int, str]:
263 """
264 @brief Check if the provided level data is valid.
265
266 @param level_name The level name or number to validate.
267 @param logger_instance The logger instance to use for logging errors.
268 @return The validated level name or an error code.
269 """
270 _func_name = inspect.currentframe()
271 if _func_name.f_back is not None:
272 _func_name = _func_name.f_back.f_code.co_name
273 else:
274 _func_name = _func_name.f_code.co_name
275
276 # Check if there are any handlers in use.
277 if len(logger_instance.handlers) == 0:
278 if self.log_error_when_present is True:
279 self.log_error(
280 'No handlers are present in this logging instance',
281 _func_name
282 )
283 return self.error
284
285 # Checking for the presence of the logging level in the logger instance
286 if isinstance(level_name, str):
287 name_string = level_name.upper()
288 elif isinstance(level_name, int):
289 name_string = logging.getLevelName(level_name)
290 else:
291 if self.log_error_when_present is True:
292 self.log_error(
293 "The level name must be a string or an integer",
294 _func_name
295 )
296 return self.error
297 return name_string
298
299 def _get_colour_formatter(self, logger_instance: logging.Logger = None) -> Union[None, colorlog.ColoredFormatter]:
300 """
301 @brief Get the colour formatter from the logger instance.
302
303 @param logger_instance The logger instance to retrieve the formatter from.
304 @return The colour formatter or an error code.
305 """
306 _func_name = inspect.currentframe()
307 if _func_name.f_back is not None:
308 _func_name = _func_name.f_back.f_code.co_name
309 else:
310 _func_name = _func_name.f_code.co_name
311
312 # iterate through the handlers to find the colour handler
313 colour_handler = None
314 for i in logger_instance.handlers:
315 if isinstance(i, colorlog.StreamHandler):
316 colour_handler = i
317 break
318 if not colour_handler:
319 if self.log_error_when_present is True:
320 self.log_error(
321 'No colour handler is present in this logging instance',
322 _func_name
323 )
324 return self.error
325
326 # Check if the colour is a string or an integer
327 if hasattr(colour_handler, "formatter") is False:
328 if self.log_error_when_present is True:
329 self.log_error(
330 'The colour handler has no formatter',
331 _func_name
332 )
333 return self.error
334 colour_formatter: colorlog.ColoredFormatter = colour_handler.formatter
335 if isinstance(colour_formatter, colorlog.ColoredFormatter) is False:
336 if self.log_error_when_present is True:
337 self.log_error(
338 'The formatter is not a ColoredFormatter',
339 _func_name
340 )
341 return self.error
342 if hasattr(colour_formatter, "log_colors") is False:
343 if self.log_error_when_present is True:
344 self.log_error(
345 'The formatter has no log_colors',
346 _func_name
347 )
348 return self.error
349 return colour_formatter
350
351 def update_logging_colour_text(self, colour: Union[str, int], level_name: Union[str, int], logger_instance: logging.Logger = None) -> int:
352 """
353 @brief Update or insert a logging colour for the text of the specified level.
354
355 @param colour The colour to use (string or number).
356 @param level_name The level name or number.
357 @param logger_instance The logger instance to update.
358 @return The status code of the operation.
359 """
360 _func_name = inspect.currentframe().f_code.co_name
361 # Checking the logger instance
362 logger_instance = self._check_the_logging_instance(logger_instance)
363
364 name_string = self._check_level_data(
365 level_name,
366 logger_instance
367 )
368 if name_string == self.error:
369 if self.log_error_when_present is True:
370 self.log_error(
371 f"The level name {level_name} is not valid",
372 _func_name
373 )
374 return self.error
375
376 name_string = name_string.upper()
377
378 # Checking if the colour exists
379 colour_input = colour
380 colour = self._check_colour_data(
381 colour,
382 logger_instance
383 )
384 if colour == self.error:
385 if self.log_error_when_present is True:
386 self.log_error(
387 f"The colour {colour_input} is not valid",
388 _func_name
389 )
390 return self.error
391
392 internal_log_colors = self._get_colour_formatter(logger_instance)
393 if internal_log_colors == self.error or internal_log_colors is None:
394 if self.log_error_when_present is True:
395 self.log_error(
396 'The colour logging library is not valid',
397 _func_name
398 )
399 return self.error
400 lib_log_colors = internal_log_colors.log_colors
401 if isinstance(lib_log_colors, dict) is False:
402 if self.log_error_when_present is True:
403 self.log_error(
404 'The log_colors is not a dictionary',
405 _func_name
406 )
407 return self.error
408 for i in lib_log_colors:
409 if i.upper() == name_string:
410 lib_log_colors[i] = colour
411 return self.success
412 lib_log_colors[name_string.upper()] = colour
413 return self.success
414
415 def update_logging_colour_background(self, colour: Union[str, int], level_name: Union[str, int], logger_instance: logging.Logger = None) -> int:
416 """
417 @brief Update or insert a logging colour for the background of the specified level.
418
419 @param colour The colour to use (string or number).
420 @param level_name The level name or number.
421 @param logger_instance The logger instance to update.
422 @return The status code of the operation.
423 """
424 _func_name = inspect.currentframe().f_code.co_name
425 # Checking the logger instance
426 logger_instance = self._check_the_logging_instance(logger_instance)
427
428 name_string = self._check_level_data(
429 level_name,
430 logger_instance
431 )
432 if name_string == self.error:
433 if self.log_error_when_present is True:
434 self.log_error(
435 f"The level name {level_name} is not valid",
436 _func_name
437 )
438 return self.error
439 name_string = name_string.upper()
440
441 # Checking if the colour exists
442 colour_input = colour
443 colour = self._check_colour_data(
444 colour,
445 logger_instance
446 )
447 if colour == self.error:
448 if self.log_error_when_present is True:
449 self.log_error(
450 f"The colour {colour_input} is not valid",
451 _func_name
452 )
453 return self.error
454
455 # Checking if the colour is a background colour
456 if colour.startswith("bg_") is False:
457 colour = "bg_" + colour
458
459 secondary_log_colors = self._get_colour_formatter(logger_instance)
460 if secondary_log_colors == self.error:
461 if self.log_error_when_present is True:
462 self.log_error(
463 'The secondary_log_colors is not valid',
464 _func_name
465 )
466 return self.error
467 secondary_log_colors = secondary_log_colors.secondary_log_colors
468 if isinstance(secondary_log_colors, dict) is False:
469 if self.log_error_when_present is True:
470 self.log_error(
471 'The secondary_log_colors is not a dictionary',
472 _func_name
473 )
474 return self.error
475
476 # Add the level and colour to the secondary log colours
477 if hasattr(secondary_log_colors, self.background_colour_key) is False:
478 secondary_log_colors[self.background_colour_key] = {
479 name_string.upper(): colour
480 }
481 return self.success
482 for i in secondary_log_colors[self.background_colour_key]:
483 if i.upper() == name_string:
484 secondary_log_colors[self.background_colour_key][i.upper(
485 )] = colour
486 return self.success
487 secondary_log_colors[
489 ][name_string.upper()] = colour
490 return self.success
491
492 def add_custom_level(self, level: int, name: str, colour_text: Union[int, str] = "", colour_bg: Union[int, str] = "") -> int:
493 """
494 @brief Add a custom level to the logger.
495
496 @param level The integer value of the custom level.
497 @param name The name of the custom level.
498 @param colour_text The text colour for the custom level.
499 @param colour_bg The background colour for the custom level.
500 @return The status code of the operation.
501 """
502 _func_name = inspect.currentframe().f_code.co_name
503 logger = self._check_the_logging_instance(self.logger)
504 # Check if the level is already taken
505 if level in FORBIDDEN_NUMBER_LOG_LEVELS:
506 if self.log_error_when_present is True:
507 self.log_error(
508 f"The provided level is forbidden because already taken '{level}'",
509 _func_name
510 )
511 return self.error
512 if name in FORBIDDEN_NUMBER_LOG_LEVELS_CORRESPONDANCE:
513 if self.log_error_when_present is True:
514 self.log_error(
515 f"The provided name is forbidden because already taken '{name}'",
516 _func_name
517 )
518 return self.error
519 # Add the level to the logger
520 logging.addLevelName(level, name.upper())
521 if hasattr(logging.getLogger(), "log_level_tracker") is False:
522 if self.log_warning_when_present is True:
523 self.log_warning(
524 "The log level tracker is not present, adding",
525 _func_name
526 )
527 logging.getLogger().log_level_tracker = self.log_level_trackerlog_level_tracker
528 else:
529 self.log_level_trackerlog_level_tracker = logging.getLogger().log_level_tracker
530 if logging.getLogger().log_level_tracker.add_level(name, level) is False:
531 if self.log_error_when_present is True:
532 self.log_warning(
533 "The level could not be added to the log level tracker",
534 _func_name
535 )
536 return self.error
537 # Check the colours
538 if colour_text != "" or colour_text < 0:
539 colour_text_status = self.update_logging_colour_text(
540 colour_text,
541 level,
542 logger
543 )
544 if colour_text_status == self.error:
545 if self.log_error_when_present is True:
546 self.log_warning(
547 "The colour for the text could not be set",
548 _func_name
549 )
550 if colour_bg != "" or colour_bg < 0:
551 colour_bg_status = self.update_logging_colour_background(
552 colour_bg,
553 level,
554 logger
555 )
556 if colour_bg_status == self.error:
557 if self.log_error_when_present is True:
558 self.log_warning(
559 "The colour for the background could not be set",
560 _func_name
561 )
562
563 # generate the function name
564 func_name = name.lower()
565# # Generate the function for the logger
566# function_code = f"""
567# def {func_name}(self, message: str):
568# self.logger.log({level}, message)
569# """
570# self._add_function_to_instance(
571# logger,
572# func_name,
573# function_code
574# )
575 # Generate the function for the display class
576 func_disp_name = f"disp_print_{func_name}"
577 function_disp_code = f"""
578def {func_disp_name}(self, string: str = "", func_name: Union[str, None] = None) -> None:
579 if isinstance(func_name, str) is False or func_name is None:
580 _func_name = inspect.currentframe()
581 if _func_name.f_back is not None:
582 func_name = _func_name.f_back.f_code.co_name
583 else:
584 func_name = _func_name.f_code.co_name
585 self.log_custom_level({level}, string, func_name)
586"""
588 self,
589 func_disp_name,
590 function_disp_code
591 )
592 # Generate the shorthand name for the display class
593 func_log_name = f"log_{func_name}"
594 function_disp_short_code = f"""
595def {func_log_name}(self, string: str = "", func_name: Union[str, None] = None) -> None:
596 if isinstance(func_name, str) is False or func_name is None:
597 _func_name = inspect.currentframe()
598 if _func_name.f_back is not None:
599 _func_name = _func_name.f_back.f_code.co_name
600 else:
601 _func_name = _func_name.f_code.co_name
602 self.log_custom_level({level}, string, _func_name)
603"""
605 self,
606 func_log_name,
607 function_disp_short_code
608 )
609 return self.success
610
611 def disp_print_custom_level(self, level: Union[int, str], string: str, func_name: Union[str, None] = None) -> None:
612 """
613 @brief Print a message with a custom level.
614
615 @param level The custom level to use.
616 @param string The message to print.
617 @param func_name The name of the calling function.
618 """
619 if isinstance(func_name, str) is False or func_name is None:
620 _func_name = inspect.currentframe()
621 if _func_name.f_back is not None:
622 func_name = _func_name.f_back.f_code.co_name
623 else:
624 func_name = _func_name.f_code.co_name
625 if isinstance(level, str):
626 log_level_tracker: LogLevelTracker = logging.getLogger().log_level_tracker
627 level = log_level_tracker.get_level(level)
628 if level is None:
629 self.logger.error(
630 "The provided level is not valid"
631 )
632 return
633 if self.logger.isEnabledFor(level):
634 self.logger.log(level, "(%s) %s", func_name, string)
635
636 def disp_print_debug(self, string: str = "", func_name: Union[str, None] = None) -> None:
637 """
638 @brief Print a debug message (using logger).
639
640 @param string The message to print.
641 @param func_name The name of the calling function.
642 """
643 if isinstance(func_name, str) is False or func_name is None:
644 _func_name = inspect.currentframe()
645 if _func_name.f_back is not None:
646 func_name = _func_name.f_back.f_code.co_name
647 else:
648 func_name = _func_name.f_code.co_name
649 if self.debugdebug is True:
650 self.logger.debug("(%s) %s", func_name, string)
651
652 def disp_print_info(self, string: str = "", func_name: Union[str, None] = None) -> None:
653 """
654 @brief Print an information message (using logger).
655
656 @param string The message to print.
657 @param func_name The name of the calling function.
658 """
659 if isinstance(func_name, str) is False or func_name is None:
660 _func_name = inspect.currentframe()
661 if _func_name.f_back is not None:
662 func_name = _func_name.f_back.f_code.co_name
663 else:
664 func_name = _func_name.f_code.co_name
665 self.logger.info("(%s) %s", func_name, string)
666
667 def disp_print_warning(self, string: str = "", func_name: Union[str, None] = None) -> None:
668 """
669 @brief Print a warning message (using logger).
670
671 @param string The message to print.
672 @param func_name The name of the calling function.
673 """
674 if isinstance(func_name, str) is False or func_name is None:
675 _func_name = inspect.currentframe()
676 if _func_name.f_back is not None:
677 func_name = _func_name.f_back.f_code.co_name
678 else:
679 func_name = _func_name.f_code.co_name
680 self.logger.warning("(%s) %s", func_name, string)
681
682 def disp_print_error(self, string: str = "", func_name: Union[str, None] = None) -> None:
683 """
684 @brief Print an error message (using logger).
685
686 @param string The message to print.
687 @param func_name The name of the calling function.
688 """
689 if isinstance(func_name, str) is False or func_name is None:
690 _func_name = inspect.currentframe()
691 if _func_name.f_back is not None:
692 func_name = _func_name.f_back.f_code.co_name
693 else:
694 func_name = _func_name.f_code.co_name
695 self.logger.error("(%s) %s", func_name, string)
696
697 def disp_print_critical(self, string: str = "", func_name: Union[str, None] = None) -> None:
698 """
699 @brief Print a critical message (using logger).
700
701 @param string The message to print.
702 @param func_name The name of the calling function.
703 """
704 if isinstance(func_name, str) is False or func_name is None:
705 _func_name = inspect.currentframe()
706 if _func_name.f_back is not None:
707 func_name = _func_name.f_back.f_code.co_name
708 else:
709 func_name = _func_name.f_code.co_name
710 self.logger.critical("(%s) %s", func_name, string)
711
712 def log_custom_level(self, level: Union[int, str], string: str, func_name: Union[str, None] = None) -> None:
713 """
714 @brief Log a message with a custom level.
715
716 @param level The custom level to use.
717 @param string The message to log.
718 @param func_name The name of the calling function.
719 """
720 if isinstance(func_name, str) is False or func_name is None:
721 _func_name = inspect.currentframe()
722 if _func_name.f_back is not None:
723 func_name = _func_name.f_back.f_code.co_name
724 else:
725 func_name = _func_name.f_code.co_name
726 self.disp_print_custom_level(level, string, func_name)
727
728 def log_debug(self, string: str = "", func_name: Union[str, None] = None) -> None:
729 """
730 @brief Log a debug message.
731
732 @param string The message to log.
733 @param func_name The name of the calling function.
734 """
735 if isinstance(func_name, str) is False or func_name is None:
736 _func_name = inspect.currentframe()
737 if _func_name.f_back is not None:
738 func_name = _func_name.f_back.f_code.co_name
739 else:
740 func_name = _func_name.f_code.co_name
741 self.disp_print_debug(string, func_name)
742
743 def log_info(self, string: str = "", func_name: Union[str, None] = None) -> None:
744 """
745 @brief Log an info message.
746
747 @param string The message to log.
748 @param func_name The name of the calling function.
749 """
750 if isinstance(func_name, str) is False or func_name is None:
751 _func_name = inspect.currentframe()
752 if _func_name.f_back is not None:
753 func_name = _func_name.f_back.f_code.co_name
754 else:
755 func_name = _func_name.f_code.co_name
756 self.disp_print_info(string, func_name)
757
758 def log_warning(self, string: str = "", func_name: Union[str, None] = None) -> None:
759 """
760 @brief Log a warning message.
761
762 @param string The message to log.
763 @param func_name The name of the calling function.
764 """
765 if isinstance(func_name, str) is False or func_name is None:
766 _func_name = inspect.currentframe()
767 if _func_name.f_back is not None:
768 func_name = _func_name.f_back.f_code.co_name
769 else:
770 func_name = _func_name.f_code.co_name
771 self.disp_print_warning(string, func_name)
772
773 def log_error(self, string: str = "", func_name: Union[str, None] = None) -> None:
774 """
775 @brief Log an error message.
776
777 @param string The message to log.
778 @param func_name The name of the calling function.
779 """
780 if isinstance(func_name, str) is False or func_name is None:
781 _func_name = inspect.currentframe()
782 if _func_name.f_back is not None:
783 func_name = _func_name.f_back.f_code.co_name
784 else:
785 func_name = _func_name.f_code.co_name
786 self.disp_print_error(string, func_name)
787
788 def log_critical(self, string: str = "", func_name: Union[str, None] = None) -> None:
789 """
790 @brief Log a critical message.
791
792 @param string The message to log.
793 @param func_name The name of the calling function.
794 """
795 if isinstance(func_name, str) is False or func_name is None:
796 _func_name = inspect.currentframe()
797 if _func_name.f_back is not None:
798 func_name = _func_name.f_back.f_code.co_name
799 else:
800 func_name = _func_name.f_code.co_name
801 self.disp_print_critical(string, func_name)
802
803 def close_file(self) -> None:
804 """
805 @brief Close the log file if it was opened.
806 """
807 if self.toml_contenttoml_content[KEY_OUTPUT_MODE] != OUT_FILE:
808 if self.log_warning_when_present is True:
809 self.log_warning(
810 "The file was not opened, no need to close it",
811 inspect.currentframe().f_code.co_name
812 )
813 return
814 if self.file_descriptor is not None:
815 self.file_descriptor.close()
816
817 def get_generated_content(self) -> str:
818 """
819 @brief Return the generated string.
820
821 @return The generated content.
822 """
823 data = self.generated_content
824 self.generated_content = ""
825 return data
826
827 def _calculate_required_spaces(self, string_length: int) -> str:
828 """
829 @brief Generate the required amount of spaces for the padding of the shape.
830
831 @param string_length The length of the provided string.
832 @return The number of spaces required for the padding.
833 """
834 if string_length >= self.max_whitespace:
835 white_spaces = " "
836 else:
837 calculated_length = int(
838 (self.max_whitespace - string_length)/2
839 )
840 if calculated_length % 2 == 1 and calculated_length != 0:
841 calculated_length += 1
842 white_spaces = self.create_string(
843 calculated_length,
844 " "
845 )
846 return white_spaces
847
848 def _open_file(self) -> None:
849 """
850 @brief Open the file if required and add the current date and time.
851 """
852 if self.save_to_file is True and self.file_descriptor is None:
853 self.file_descriptor = open(
854 self.file_name,
855 "a",
856 encoding="utf-8",
857 newline="\n"
858 )
859 if self.file_descriptor is not None:
860 self.append_run_date()
861
862 def _is_safe(self, content: any) -> bool:
863 """
864 @brief Check if an item is safe to write or not.
865
866 @param content The item to check.
867 @return True if the item is safe to write, False otherwise.
868 """
869 if isinstance(content, (str, int, float, tuple, complex, bytes, bytearray, memoryview)) is False:
870 return False
871 return True
872
873 def create_string(self, length, character) -> str:
874 """
875 @brief Create a string based on a character and a length.
876
877 @param length The length of the string.
878 @param character The character to use.
879 @return The created string.
880 """
881 line = [character for i in range(0, length)]
882 string = "".join(line)
883 return string
884
885 def display_animation(self, message: str = "Hello World!", delay: float = 0.02) -> None:
886 """
887 @brief Print the message letter by letter while applying a provided delay.
888
889 @param message The message to display.
890 @param delay The delay between each letter.
891 """
892 if " " in message and self.toml_contenttoml_content[KEY_PRETTIFY_OUTPUT] is True and self.toml_contenttoml_content[KEY_PRETTIFY_OUTPUT_IN_BLOCKS] is True:
893 for letter in message.split(" "):
894 sys.stdout.write(letter)
895 sys.stdout.flush()
896 sys.stdout.write(" ")
897 time.sleep(delay)
898 elif self.toml_contenttoml_content[KEY_PRETTIFY_OUTPUT] is True:
899 for letter in message:
900 sys.stdout.write(letter)
901 sys.stdout.flush()
902 time.sleep(delay)
903 else:
904 sys.stdout.write(message)
905 print()
906
907 def animate_message(self, message: str = "Hello World!", delay: float = 0.02) -> None:
908 """
909 @brief Display or dump (to file) a message.
910
911 @param message The message to display or dump.
912 @param delay The delay between each letter.
913 """
914 if self._is_safe(message) is False:
915 message = f"{message}"
916 if self.save_to_file is True and self.file_descriptor is not None:
917 self.file_descriptor.write(f"{message}\n")
918 elif self.toml_contenttoml_content[KEY_OUTPUT_MODE] == OUT_STRING:
919 self.generated_content = f"{message}\n"
920 else:
921 self.display_animation(message, delay)
922
923 def disp_message_box(self, msg: str, char: str = "#") -> None:
924 """
925 @brief Display a message in a box.
926
927 @param msg The message to display.
928 @param char The character to use for the box.
929
930 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
931 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
932 @example #############################\n
933 @example # Sample text #\n
934 @example #############################
935 """
936
937 box_wall = self.create_string(self.nb_chr, char)
938
939 title_content = ""
940 if "\n" in msg:
941 lines = msg.split("\n")
942 for i in lines:
943 string_length = len(i)
944 white_spaces = self._calculate_required_spaces(string_length)
945 title_content += char
946 title_content += white_spaces
947 title_content += i
948 if string_length % 2 == 1 and string_length != 0:
949 white_spaces = white_spaces[:-1]
950 title_content += white_spaces
951 title_content += char
952 title_content += '\n'
953 else:
954 string_length = len(msg)
955 white_spaces = self._calculate_required_spaces(string_length)
956 box_wall = self.create_string(self.nb_chr, char)
957 title_content += char
958 title_content += white_spaces
959 title_content += msg
960 if string_length % 2 == 1 and string_length != 0:
961 white_spaces = white_spaces[:-1]
962 title_content += white_spaces
963 title_content += char
964 title_content += "\n"
965
966 generated_content = f"{box_wall}\n"
967 generated_content += f"{title_content}"
968 generated_content += f"{box_wall}"
969 self.animate_message(
970 f"{generated_content}",
972 )
973
974 def disp_round_message_box(self, msg: str = "Sample text") -> None:
975 """
976 @brief Display a message in a rounded box.
977
978 @param msg The message to display.
979
980 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
981 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
982 @example ╔══════════════════════╗\n
983 @example ║ Sample text ║\n
984 @example ╚══════════════════════╝\n
985 """
986
987 offset_reset = 2
988
989 # Generate the top line
990 top_wall = ""
991 if 'ROUND_BOX_CORNER_LEFT' in self.toml_contenttoml_content:
992 top_wall += self.toml_contenttoml_content['ROUND_BOX_CORNER_LEFT']
993 else:
994 if self.log_warning_when_present is True:
995 self.log_warning(
996 "The top left corner is not defined, using the default one",
997 inspect.currentframe().f_code.co_name
998 )
999 top_wall += "╔"
1000 if 'ROUND_BOX_HORIZONTAL' in self.toml_contenttoml_content:
1001 top_wall += self.create_string(
1002 self.nb_chr - offset_reset,
1003 self.toml_contenttoml_content['ROUND_BOX_HORIZONTAL']
1004 )
1005 else:
1006 if self.log_warning_when_present is True:
1007 self.log_warning(
1008 "The horizontal line is not defined, using the default one",
1009 inspect.currentframe().f_code.co_name
1010 )
1011 top_wall += self.create_string(
1012 self.nb_chr-offset_reset,
1013 "═"
1014 )
1015 if 'ROUND_BOX_CORNER_RIGHT' in self.toml_contenttoml_content:
1016 top_wall += self.toml_contenttoml_content['ROUND_BOX_CORNER_RIGHT']
1017 else:
1018 if self.log_warning_when_present is True:
1019 self.log_warning(
1020 "The top right corner is not defined, using the default one",
1021 inspect.currentframe().f_code.co_name
1022 )
1023 top_wall += "╗"
1024
1025 # Generate the bottom line
1026 bottom_wall = ""
1027 if 'ROUND_BOX_CORNER_BOTTOM_LEFT' in self.toml_contenttoml_content:
1028 bottom_wall += self.toml_contenttoml_content['ROUND_BOX_CORNER_BOTTOM_LEFT']
1029 else:
1030 if self.log_warning_when_present is True:
1031 self.log_warning(
1032 "The bottom left corner is not defined, using the default one",
1033 inspect.currentframe().f_code.co_name
1034 )
1035 bottom_wall += "╚"
1036
1037 if 'ROUND_BOX_HORIZONTAL' in self.toml_contenttoml_content:
1038 bottom_wall += self.create_string(
1039 self.nb_chr-offset_reset,
1040 self.toml_contenttoml_content['ROUND_BOX_HORIZONTAL']
1041 )
1042 else:
1043 if self.log_warning_when_present is True:
1044 self.log_warning(
1045 "The horizontal line is not defined, using the default one",
1046 inspect.currentframe().f_code.co_name
1047 )
1048 bottom_wall += self.create_string(
1049 self.nb_chr-offset_reset,
1050 "═"
1051 )
1052 if 'ROUND_BOX_CORNER_BOTTOM_RIGHT' in self.toml_contenttoml_content:
1053 bottom_wall += self.toml_contenttoml_content['ROUND_BOX_CORNER_BOTTOM_RIGHT']
1054 else:
1055 if self.log_warning_when_present is True:
1056 self.log_warning(
1057 "The bottom right corner is not defined, using the default one",
1058 inspect.currentframe().f_code.co_name
1059 )
1060 bottom_wall += "╝"
1061
1062 border_character = ""
1063 if 'ROUND_BOX_VERTICAL' in self.toml_contenttoml_content:
1064 border_character = self.toml_contenttoml_content['ROUND_BOX_VERTICAL']
1065 else:
1066 if self.log_warning_when_present is True:
1067 self.log_warning(
1068 "The vertical line is not defined, using the default one",
1069 inspect.currentframe().f_code.co_name
1070 )
1071 border_character = "║"
1072
1073 center_content = ""
1074 if "\n" in msg:
1075 lines = msg.split("\n")
1076 for i in lines:
1077 string_length = len(i)
1078 white_spaces = self._calculate_required_spaces(string_length)
1079 center_content += border_character
1080 center_content += white_spaces
1081 center_content += i
1082 if string_length % 2 == 1 and string_length != 0:
1083 white_spaces = white_spaces[:-1]
1084 center_content += white_spaces
1085 center_content += border_character
1086 center_content += '\n'
1087 else:
1088 string_length = len(msg)
1089 white_spaces = self._calculate_required_spaces(string_length)
1090 center_content += border_character
1091 center_content += white_spaces
1092 center_content += msg
1093 if string_length % 2 == 1 and string_length != 0:
1094 white_spaces = white_spaces[:-1]
1095 center_content += white_spaces
1096 center_content += border_character
1097 center_content += "\n"
1098
1099 generated_content = f"{top_wall}\n"
1100 generated_content += f"{center_content}"
1101 generated_content += f"{bottom_wall}"
1102 self.animate_message(
1103 f"{generated_content}",
1105 )
1106
1107 def disp_diff_side_and_top_message_box(self, msg: str) -> None:
1108 """
1109 @brief Display a message in a box with different side and top characters.
1110
1111 @param msg The message to display.
1112
1113 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1114 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1115 @example _____________________________\n
1116 @example | Sample text |\n
1117 @example _____________________________
1118 """
1119 ceiling_boxes = ""
1120 if 'DIFF_BORDER_LINE_CHARACTER_BOX' in self.toml_contenttoml_content:
1121 ceiling_boxes = self.toml_contenttoml_content['DIFF_BORDER_LINE_CHARACTER_BOX']
1122 else:
1123 if self.log_warning_when_present is True:
1124 self.log_warning(
1125 "The ceiling boxes are not defined, using the default one",
1126 inspect.currentframe().f_code.co_name
1127 )
1128 ceiling_boxes = "-"
1129
1130 border_character = ""
1131 if 'DIFF_SIDE_LINE_CHARACTER_BOX' in self.toml_contenttoml_content:
1132 border_character = self.toml_contenttoml_content['DIFF_SIDE_LINE_CHARACTER_BOX']
1133 else:
1134 if self.log_warning_when_present is True:
1135 self.log_warning(
1136 "The border character is not defined, using the default one",
1137 inspect.currentframe().f_code.co_name
1138 )
1139 border_character = "|"
1140
1141 box_wall = self.create_string(self.nb_chr, ceiling_boxes)
1142
1143 title_content = ""
1144 if "\n" in msg:
1145 lines = msg.split("\n")
1146 for i in lines:
1147 string_length = len(i)
1148 white_spaces = self._calculate_required_spaces(string_length)
1149 title_content += border_character
1150 title_content += white_spaces
1151 title_content += i
1152 if string_length % 2 == 1 and string_length != 0:
1153 white_spaces = white_spaces[:-1]
1154 title_content += white_spaces
1155 title_content += border_character
1156 title_content += '\n'
1157 else:
1158 string_length = len(msg)
1159 white_spaces = self._calculate_required_spaces(string_length)
1160 title_content += border_character
1161 title_content += white_spaces
1162 title_content += msg
1163 if string_length % 2 == 1 and string_length != 0:
1164 white_spaces = white_spaces[:-1]
1165 title_content += white_spaces
1166 title_content += border_character
1167 title_content += "\n"
1168
1169 generated_content = f"{box_wall}\n"
1170 generated_content += f"{title_content}"
1171 generated_content += f"{box_wall}"
1172 self.animate_message(
1173 f"{generated_content}",
1175 )
1176
1177 def disp_box_no_vertical(self, message: str, character: str = "@") -> None:
1178 """
1179 @brief Print a box format without internal vertical bars.
1180
1181 @param message The message to display.
1182 @param character The character to use for the box.
1183 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1184 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1185 @example #############################\n
1186 @example Sample text \n
1187 @example #############################
1188 """
1189 if 'BOX_NO_VERTICAL' in self.toml_contenttoml_content:
1190 char = self.toml_contenttoml_content['BOX_NO_VERTICAL']
1191 else:
1192 if self.log_warning_when_present is True:
1193 self.log_warning(
1194 "The box character is not defined, using the default one",
1195 inspect.currentframe().f_code.co_name
1196 )
1197 char = character
1198
1199 box_wall = self.create_string(self.nb_chr, char)
1200
1201 title_content = ""
1202 if "\n" in message:
1203 lines = message.split("\n")
1204 for i in lines:
1205 string_length = len(i)
1206 white_spaces = self._calculate_required_spaces(string_length)
1207 title_content += white_spaces
1208 title_content += i
1209 if string_length % 2 == 1 and string_length != 0:
1210 white_spaces = white_spaces[:-1]
1211 title_content += white_spaces
1212 title_content += '\n'
1213 else:
1214 string_length = len(message)
1215 white_spaces = self._calculate_required_spaces(string_length)
1216 title_content += white_spaces
1217 title_content += message
1218 if string_length % 2 == 1 and string_length != 0:
1219 white_spaces = white_spaces[:-1]
1220 title_content += white_spaces
1221 title_content += "\n"
1222
1223 generated_content = f"{box_wall}\n"
1224 generated_content += f"{title_content}"
1225 generated_content += f"{box_wall}"
1226 self.animate_message(
1227 f"{generated_content}",
1229 )
1230
1231 def disp_vertical_message_box(self, msg: str, character: str = '') -> None:
1232 """
1233 @brief Display a message in a box with vertical bars.
1234
1235 @param msg The message to display.
1236 @param character The character to use for the box.
1237 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1238 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1239 @example ###############\n
1240 @example # #\n
1241 @example # #\n
1242 @example # #\n
1243 @example # Sample text #\n
1244 @example # #\n
1245 @example # #\n
1246 @example # #\n
1247 @example ###############
1248 """
1249
1250 if 'BOX_NO_VERTICAL' in self.toml_contenttoml_content:
1251 character = self.toml_contenttoml_content['BOX_NO_VERTICAL']
1252 elif character == '':
1253 if self.log_warning_when_present is True:
1254 self.log_warning(
1255 "The box character is not defined, using the default one",
1256 inspect.currentframe().f_code.co_name
1257 )
1258 character = "#"
1259
1260 box_wall = self.create_string(self.nb_chr, character)
1261
1262 title_content = ""
1263 if "\n" in msg:
1264 lines = msg.split("\n")
1265 for i in lines:
1266 string_length = len(i)
1267 white_spaces = self._calculate_required_spaces(string_length)
1268 title_content += character
1269 title_content += white_spaces
1270 title_content += i
1271 if string_length % 2 == 1 and string_length != 0:
1272 white_spaces = white_spaces[:-1]
1273 title_content += white_spaces
1274 title_content += character
1275 title_content += "\n"
1276 msg = lines
1277 else:
1278 string_length = len(msg)
1279 white_spaces = self._calculate_required_spaces(string_length)
1280 title_content += character
1281 title_content += white_spaces
1282 title_content += msg
1283 if string_length % 2 == 1 and string_length != 0:
1284 white_spaces = white_spaces[:-1]
1285 title_content += white_spaces
1286 title_content += character
1287 title_content += "\n"
1288
1289 inner_length = int(self.max_whitespace)
1290 inner_line = self.create_string(
1291 inner_length,
1292 " "
1293 )
1294 inner_line = f"{character}{inner_line}{character}"
1295
1296 generated_content = f"{box_wall}\n"
1297 if "\n" in msg:
1298 max_height = (inner_length / 4) - len(msg)
1299 if max_height <= 2:
1300 max_height = 2
1301 else:
1302 max_height = 2
1303 i = 0
1304 while i < max_height:
1305 generated_content += f"{inner_line}\n"
1306 i += 1
1307 generated_content += f"{title_content}"
1308 i = 0
1309 while i < max_height:
1310 generated_content += f"{inner_line}\n"
1311 i += 1
1312
1313 generated_content += f"{box_wall}"
1314
1315 self.animate_message(
1316 f"{generated_content}",
1318 )
1319
1320 def box_vertical_no_horizontal(self, message: str, character: str = "") -> None:
1321 """
1322 @brief Print a box format without internal horizontal bars.
1323
1324 @param message The message to display.
1325 @param character The character to use for the box.
1326 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1327 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1328 @example # #\n
1329 @example # #\n
1330 @example # #\n
1331 @example # #\n
1332 @example # #\n
1333 @example # Sample text #\n
1334 @example # #\n
1335 @example # #\n
1336 @example # #\n
1337 @example # #\n
1338 @example # #\n
1339 """
1340 if 'BOX_VERTICAL_NO_HORIZONTAL' in self.toml_contenttoml_content:
1341 character = self.toml_contenttoml_content['BOX_VERTICAL_NO_HORIZONTAL']
1342 elif character == '':
1343 if self.log_warning_when_present is True:
1344 self.log_warning(
1345 "The box character is not defined, using the default one",
1346 inspect.currentframe().f_code.co_name
1347 )
1348 character = "#"
1349
1350 title_content = ""
1351 if "\n" in message:
1352 lines = message.split("\n")
1353 for i in lines:
1354 string_length = len(i)
1355 white_spaces = self._calculate_required_spaces(string_length)
1356 title_content += character
1357 title_content += white_spaces
1358 title_content += i
1359 if string_length % 2 == 1 and string_length != 0:
1360 white_spaces = white_spaces[:-1]
1361 title_content += white_spaces
1362 title_content += character
1363 title_content += "\n"
1364 message = lines
1365 else:
1366 string_length = len(message)
1367 white_spaces = self._calculate_required_spaces(string_length)
1368 title_content += character
1369 title_content += white_spaces
1370 title_content += message
1371 if string_length % 2 == 1 and string_length != 0:
1372 white_spaces = white_spaces[:-1]
1373 title_content += white_spaces
1374 title_content += character
1375 title_content += "\n"
1376 inner_length = int(self.max_whitespace)
1377 if len(message) > self.max_whitespace:
1378 inner_length = self.max_whitespace
1379 inner_line = self.create_string(
1380 inner_length,
1381 " "
1382 )
1383 inner_line = f"{character}{inner_line}{character}"
1384
1385 generated_content = ""
1386 if "\n" in message:
1387 max_height = (inner_length / 4) - len(message)
1388 if max_height <= 2:
1389 max_height = 2
1390 else:
1391 max_height = 2
1392 i = 0
1393 while i < max_height:
1394 generated_content += f"{inner_line}\n"
1395 i += 1
1396 generated_content += f"{title_content}"
1397 i = 0
1398 while i < max_height:
1399 if i+1 >= max_height:
1400 generated_content += f"{inner_line}"
1401 else:
1402 generated_content += f"{inner_line}\n"
1403 i += 1
1404
1405 self.animate_message(
1406 f"{generated_content}",
1408 )
1409
1410 def title(self, title: str) -> None:
1411 """
1412 @brief Print a beautified title.
1413
1414 @param title The title to display.
1415 """
1416 self.disp_message_box(title, self.title_wall_chr)
1417
1418 def sub_title(self, sub_title: str) -> None:
1419 """
1420 @brief Print a beautified subtitle.
1421
1422 @param sub_title The subtitle to display.
1423 """
1424 self.disp_message_box(sub_title, self.sub_title_wall_chr)
1425
1426 def sub_sub_title(self, sub_sub_title: str) -> None:
1427 """
1428 @brief Print a beautified sub-subtitle.
1429
1430 @param sub_sub_title The sub-subtitle to display.
1431 """
1432 self.disp_message_box(sub_sub_title, self.sub_sub_title_wall_chr)
1433
1434 def message(self, message: Union[str, list]) -> None:
1435 """
1436 @brief Print a beautified message.
1437
1438 @param message The message to display.
1439 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1440 @note Here is an example for the output (This is determined by the key repeated twice)\n
1441 @example @@ This is an example message @@
1442 """
1443 if isinstance(message, list) is True:
1444 for msg in message:
1445 m_msg = f"{self.message_char}{self.message_char} {msg} "
1446 m_msg += f"{self.message_char}{self.message_char}"
1447 self.animate_message(
1448 m_msg,
1450 )
1451 else:
1452 msg = f"{self.message_char}{self.message_char} "
1453 msg += f" {message} {self.message_char}{self.message_char}"
1454 self.animate_message(
1455 msg,
1457 )
1458
1459 def error_message(self, message: Union[str, list]) -> None:
1460 """
1461 @brief Print a beautified error message.
1462
1463 @param message The error message to display.
1464 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1465 @note Here is an example for the output (This is determined by the key repeated twice)\n
1466 @example ## This is an example message ##
1467 """
1468 if isinstance(message, list) is True:
1469 m_msg = f"{self.message_error_char}{self.message_error_char} Error: "
1470 m_msg += f"{self.message_error_char}{self.message_error_char}"
1471 for msg in message:
1472 m_msg = f"{self.message_error_char}{self.message_error_char} {msg} "
1473 m_msg += f"{self.message_error_char}{self.message_error_char}"
1474 self.animate_message(
1475 m_msg,
1477 )
1478 else:
1479 msg = f"{self.message_error_char}{self.message_error_char} Error:"
1480 msg += f" {message} {self.message_error_char}{self.message_error_char}"
1481 self.animate_message(
1482 msg,
1484 )
1485
1486 def success_message(self, message: Union[str, list]) -> None:
1487 """
1488 @brief Print a beautified success message.
1489
1490 @param message The success message to display.
1491 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1492 @note Here is an example for the output (This is determined by the key repeated twice)\n
1493 @example // This is an example message //
1494 """
1495 if isinstance(message, list) is True:
1496 m_msg = f"{self.message_success_char}{self.message_success_char} Success: "
1497 m_msg += f"{self.message_success_char}{self.message_success_char}"
1498 for msg in message:
1499 m_msg = f"{self.message_success_char}{self.message_success_char} {msg} "
1500 m_msg += f"{self.message_success_char}{self.message_success_char}"
1501 self.animate_message(
1502 m_msg,
1504 )
1505 else:
1506 msg = f"{self.message_success_char}{self.message_success_char} Success:"
1507 msg += f" {message} {self.message_success_char}{self.message_success_char}"
1508 self.animate_message(
1509 msg,
1511 )
1512
1513 def warning_message(self, message: Union[str, list]) -> None:
1514 """
1515 @brief Print a beautified warning message.
1516
1517 @param message The warning message to display.
1518 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1519 @note Here is an example for the output (This is determined by the key repeated twice)\n
1520 @example !! This is an example message !!
1521 """
1522 if isinstance(message, list) is True:
1523 m_msg = f"{self.message_warning_char}{self.message_warning_char} Warning: "
1524 m_msg += f"{self.message_warning_char}{self.message_warning_char}"
1525 for msg in message:
1526 m_msg = f"{self.message_warning_char}{self.message_warning_char} {msg} "
1527 m_msg += f"{self.message_warning_char}{self.message_warning_char}"
1528 self.animate_message(
1529 m_msg,
1531 )
1532 else:
1533 msg = f"{self.message_warning_char}{self.message_warning_char} Warning:"
1534 msg += f" {message} {self.message_warning_char}{self.message_warning_char}"
1535 self.animate_message(
1536 msg,
1538 )
1539
1540 def question_message(self, message: Union[str, list]) -> None:
1541 """
1542 @brief Print a beautified question message.
1543
1544 @param message The question message to display.
1545 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1546 @note Here is an example for the output (This is determined by the key repeated twice)\n
1547 @example ?? This is an example message ??
1548 """
1549 if isinstance(message, list) is True:
1550 m_msg = f"{self.message_question_char}{self.message_question_char} Question: "
1551 m_msg += f"{self.message_question_char}{self.message_question_char}"
1552 for msg in message:
1553 m_msg = f"{self.message_question_char}{self.message_question_char} {msg} "
1554 m_msg += f"{self.message_question_char}{self.message_question_char}"
1555 self.animate_message(
1556 m_msg,
1558 )
1559 else:
1560 msg = f"{self.message_question_char}{self.message_question_char} Question:"
1561 msg += f" {message} {self.message_question_char}{self.message_question_char}"
1562 self.animate_message(
1563 msg,
1565 )
1566
1567 def inform_message(self, message: Union[str, List]) -> None:
1568 """
1569 @brief Print a beautified information message.
1570
1571 @param message The information message to display.
1572 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1573 @note Here is an example for the output (This is determined by the key repeated twice)\n
1574 @example ii This is an example message ii
1575 """
1576 if isinstance(message, list) is True:
1577 for msg in message:
1578 m_msg = f"{self.message_inform_char}{self.message_inform_char} {msg} "
1579 m_msg += f"{self.message_inform_char}{self.message_inform_char}"
1580 self.animate_message(
1581 m_msg,
1583 )
1584 else:
1585 msg = f"{self.message_inform_char}{self.message_inform_char} {message} "
1586 msg += f"{self.message_inform_char}{self.message_inform_char}"
1587 self.animate_message(
1588 msg,
1590 )
1591
1592 def _tree_node(self, line: str, offset: int, index: int, max_lenght: int) -> str:
1593 """
1594 @brief Display a line of the tree.
1595
1596 @param line The line to display.
1597 @param offset The offset for the line.
1598 @param index The index of the line.
1599 @param max_lenght The maximum length of the tree.
1600 @return The processed line.
1601 @note The characters displayed in this tree function is managed by the following keys:\n
1602 @note * TREE_NODE_CHAR\n
1603 @note * TREE_NODE_END_CHAR\n
1604 @note * TREE_LINE_SEPERATOR_CHAR\n
1605 @note * TREE_COLUMN_SEPERATOR_CHAR\n
1606 @example Here is an example generated by this function:\n
1607 @example ├─── data1\n
1608 @example └─── data2
1609 """
1610 processed_line = str()
1611 i = 0
1612 while i < offset:
1613 processed_line += f"{self.tree_column_seperator_char} "
1614 i += 1
1615 if index is max_lenght:
1616 processed_line += f"{self.tree_node_end_char}{self.tree_line_seperator_char}"
1617 processed_line += f"{self.tree_line_seperator_char}{self.tree_line_seperator_char}"
1618 else:
1619 processed_line += f"{self.tree_node_char}{self.tree_line_seperator_char}"
1620 processed_line += f"{self.tree_line_seperator_char}{self.tree_line_seperator_char}"
1621 if self._is_safe(line) is False:
1622 line = f"{line}"
1623 processed_line += " "
1624 processed_line += line
1625 processed_line += '\n'
1626 return processed_line
1627
1628 def tree(self, title: str, data: List[str], offset: int = 0) -> Union[str, None]:
1629 """
1630 @brief Print a list under the form of a beautified tree.
1631
1632 @param title The title of the tree.
1633 @param data The data to display in the tree.
1634 @param offset The offset for the tree.
1635 @note The characters displayed in this tree function is managed by the following keys:\n
1636 @note * TREE_NODE_CHAR\n
1637 @note * TREE_NODE_END_CHAR\n
1638 @note * TREE_LINE_SEPERATOR_CHAR\n
1639 @note * TREE_COLUMN_SEPERATOR_CHAR\n
1640 @example Here is an example generated by this function:\n
1641 @example ├─── data1\n
1642 @example └─── data2
1643 @return A stringified version of the tree if not set to be displayed.
1644 """
1645 generated_content = ""
1646 if offset == 0:
1647 generated_content += f"{title}\n"
1648 length = len(data) - 1
1649
1650 for line in enumerate(data):
1651 if isinstance(data, list) and isinstance(line[1], (list, dict)):
1652 generated_content += self._tree_node(
1653 "<list instance>",
1654 offset,
1655 line[0],
1656 length
1657 )
1658 generated_content += self.tree(line[0], line[1], offset + 1)
1659 continue
1660 if isinstance(data, dict) and isinstance(data[line[1]], (list, dict)):
1661 generated_content += self._tree_node(
1662 line[1],
1663 offset,
1664 line[0],
1665 length
1666 )
1667 generated_content += self.tree(
1668 line[0],
1669 data[line[1]],
1670 offset + 1
1671 )
1672 continue
1673 if isinstance(data, dict) and isinstance(data[line[1]], dict) is False:
1674 generated_content += self._tree_node(
1675 f"{line[1]}: {data[line[1]]}",
1676 offset,
1677 line[0],
1678 length
1679 )
1680 else:
1681 generated_content += self._tree_node(
1682 line[1],
1683 offset,
1684 line[0],
1685 length
1686 )
1687 if offset == 0:
1688 self.animate_message(
1689 f"{generated_content}",
1691 )
1692 else:
1693 return generated_content
1694
1695 def append_run_date(self) -> None:
1696 """
1697 @brief Add the date and time at which the program was launched.
1698 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1699 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1700 @example ########################################\n
1701 @example # Run date: 07/06/2024 22:26:10 #\n
1702 @example ########################################
1703 """
1704 self.title(f"Run date: {time.strftime('%d/%m/%Y %H:%M:%S')} ")
1705
1706 def test_the_class(self) -> None:
1707 """
1708 @brief Test function to ensure all implemented methods work as expected.
1709 """
1710 test_data = {
1711 "test_data1": "test_data1.1",
1712 "test_data2": "test_data2.1",
1713 "test_data3": [
1714 "test_data_list3.1",
1715 "test_data_list3.2",
1716 "test_data_list3.3",
1717 "test_data_list3.4",
1718 "test_data_list3.5"
1719 ],
1720 "test_data4": "test_data4.1",
1721 "test_data5": {
1722 "test_data5.1": "test_data5.1.1",
1723 "test_data5.2": "test_data5.2.1",
1724 "test_data5.3": "test_data5.3.1",
1725 "test_data5.4": "test_data5.4.1"
1726 },
1727 "test_data6": [
1728 {
1729 "test_data6.1": "test_data6.1.1",
1730 "test_data6.2": "test_data6.2.1"
1731 },
1732 [
1733 "test_data_list6.3.1",
1734 "test_data_list6.3.1",
1735 "test_data_list6.3.1",
1736 "test_data_list6.3.1"
1737 ]
1738 ],
1739 "test_data7": {
1740 "test_data7.1": {
1741 "test_data7.1.1": "test_data7.1.1.1",
1742 "test_data7.1.2": "test_data7.1.2.1"
1743 },
1744 "test_data7.2": [
1745 "test_data7.2.1",
1746 "test_data7.2.2",
1747 "test_data7.2.3",
1748 "test_data7.2.4",
1749 "test_data7.2.5"
1750 ]
1751 }
1752 }
1753 self.append_run_date()
1754 self.animate_message("Test Message !", 0.01)
1755 self.question_message("Test Question message !")
1756 self.error_message("Test Error !")
1757 self.inform_message("Test Inform !")
1758 self.success_message("Test Success !")
1759 self.warning_message("Test Warning !")
1760 self.title("Test title")
1761 self.sub_title("Test sub title")
1762 self.sub_sub_title("Test sub sub title")
1763 self.disp_box_no_vertical('Test Box no vertical')
1764 self.disp_round_message_box("Test Disp round message box")
1766 "Test Disp diff side and top message box"
1767 )
1768 self.disp_vertical_message_box("Test Disp vertical message box")
1769 self.box_vertical_no_horizontal("Test Box vertical no horizontal")
1770 self.tree("Test data", test_data)
1771 prev_debug = self.debugdebug
1772 self.debugdebug = True
1773 self.disp_print_debug("This is a test for debug messages")
1774 self.debugdebug = prev_debug
1775 self.disp_print_info("This is a test for info messages")
1776 self.disp_print_warning("This is a test for warning messages")
1777 self.disp_print_error("This is a test for error messages")
1778 self.disp_print_critical("This is a test for critical messages")
1779 self.log_custom_level(
1780 logging.WARNING, "This is a test warning for custom level messages"
1781 )
1782 custom_level_int = 2
1783 level_name = "DARLING"
1784 if self.add_custom_level(
1785 custom_level_int,
1786 level_name,
1787 "purple",
1788 LoggerColours.BLACK
1789 ) == self.error:
1790 self.log_error(
1791 f"The custom level '{level_name}' could not be added, please check the configuration"
1792 )
1793 else:
1794 self.log_custom_level(
1795 custom_level_int,
1796 f"This is a test for custom level message \"{logging.getLevelName(custom_level_int)}\""
1797 )
1798 custom_level_int = 196
1799 level_name = "Ikuno"
1800 if self.add_custom_level(
1801 custom_level_int,
1802 level_name,
1803 "cyan",
1804 LoggerColours.BLACK
1805 ) == self.error:
1806 self.log_error(
1807 f"The custom level '{level_name}' could not be added, please check the configuration"
1808 )
1809 else:
1810 self.log_custom_level(
1811 custom_level_int,
1812 f"This is a test for custom level message \"{logging.getLevelName(custom_level_int)}\""
1813 )
1814 self.close_file()
1815
1816
1817if __name__ == "__main__":
1818 DI = Disp(
1819 toml_content=TOML_CONF,
1820 save_to_file=False,
1821 file_name="test_run.tmp",
1822 file_descriptor=None,
1823 debug=False
1824 )
1825 DI.test_the_class()
Union[int, str] _check_level_data(self, Union[str, int] level_name, logging.Logger logger_instance=None)
Definition my_disp.py:262
str create_string(self, length, character)
Definition my_disp.py:873
None disp_print_custom_level(self, Union[int, str] level, str string, Union[str, None] func_name=None)
Definition my_disp.py:611
None log_info(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:743
None disp_round_message_box(self, str msg="Sample text")
Definition my_disp.py:974
None question_message(self, Union[str, list] message)
Definition my_disp.py:1540
None disp_print_error(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:682
int update_logging_colour_background(self, Union[str, int] colour, Union[str, int] level_name, logging.Logger logger_instance=None)
Definition my_disp.py:415
Union[str, None] tree(self, str title, List[str] data, int offset=0)
Definition my_disp.py:1628
None box_vertical_no_horizontal(self, str message, str character="")
Definition my_disp.py:1320
None log_error(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:773
bool _is_safe(self, any content)
Definition my_disp.py:862
None log_warning(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:758
None sub_sub_title(self, str sub_sub_title)
Definition my_disp.py:1426
None disp_print_warning(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:667
None _add_function_to_instance(self, object func_dest, str func_name, str func_code)
Definition my_disp.py:170
None disp_print_critical(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:697
None animate_message(self, str message="Hello World!", float delay=0.02)
Definition my_disp.py:907
None success_message(self, Union[str, list] message)
Definition my_disp.py:1486
None disp_print_info(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:652
None __init__(self, Dict[str, any] toml_content, bool save_to_file=False, str file_name="text_output_run.txt", any file_descriptor=None, bool debug=False, Union[Logging, str, None] logger=None, int success=SUCCESS, int error=ERR, bool log_warning_when_present=True, bool log_errors_when_present=True)
Definition my_disp.py:51
str _tree_node(self, str line, int offset, int index, int max_lenght)
Definition my_disp.py:1592
_create_function(self, name, func_code)
Definition my_disp.py:159
logging.Logger _check_the_logging_instance(self, logging.Logger logger_instance=None)
Definition my_disp.py:212
None log_critical(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:788
None log_custom_level(self, Union[int, str] level, str string, Union[str, None] func_name=None)
Definition my_disp.py:712
Union[None, colorlog.ColoredFormatter] _get_colour_formatter(self, logging.Logger logger_instance=None)
Definition my_disp.py:299
None sub_title(self, str sub_title)
Definition my_disp.py:1418
None disp_message_box(self, str msg, str char="#")
Definition my_disp.py:923
None update_disp_debug(self, bool debug)
Definition my_disp.py:181
None log_debug(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:728
Union[int, str] _check_colour_data(self, Union[str, int] colour, logging.Logger logger_instance=None)
Definition my_disp.py:236
None disp_box_no_vertical(self, str message, str character="@")
Definition my_disp.py:1177
str _calculate_required_spaces(self, int string_length)
Definition my_disp.py:827
None _setup_logger(self, Union[Logging, str, None] logger)
Definition my_disp.py:110
None display_animation(self, str message="Hello World!", float delay=0.02)
Definition my_disp.py:885
None title(self, str title)
Definition my_disp.py:1410
None error_message(self, Union[str, list] message)
Definition my_disp.py:1459
None message(self, Union[str, list] message)
Definition my_disp.py:1434
int update_logging_colour_text(self, Union[str, int] colour, Union[str, int] level_name, logging.Logger logger_instance=None)
Definition my_disp.py:351
None warning_message(self, Union[str, list] message)
Definition my_disp.py:1513
None disp_diff_side_and_top_message_box(self, str msg)
Definition my_disp.py:1107
None update_logger_level(self, Union[int, LogLevelTracker.Levels] level=LogLevelTracker.Levels.NOTSET)
Definition my_disp.py:189
None disp_print_debug(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:636
None disp_vertical_message_box(self, str msg, str character='')
Definition my_disp.py:1231
None inform_message(self, Union[str, List] message)
Definition my_disp.py:1567
int add_custom_level(self, int level, str name, Union[int, str] colour_text="", Union[int, str] colour_bg="")
Definition my_disp.py:492