2# +==== BEGIN display_tty =================+
4# ..@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
5# .@...........................#@
6# @############################.@
7# @...........................@.@
8# @..#######################..@.@
9# @.#########################.@.@
10# @.##>_#####################.@.@
11# @.#########################.@.@
12# @.#########################.@.@
13# @.#########################.@.@
14# @.#########################.@.@
15# @..#######################..@.@
16# @...........................@.@
17# @..+----+______________.....@.@
18# @..+....+______________+....@.@
19# @..+----+...................@.@
20# @...........................@.#
21# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@#.
25# CREATION DATE: 06-11-2025
26# LAST Modified: 12:22:40 06-11-2025
28# A module that allows you to display text with a few boilers (i.e. put your text in a square for titles). It also allows to log to the terminal by wrapping around the logging library.
30# @brief The file in charge of managing the beautified output on the terminal.
32# COPYRIGHT: (c) Henry Letellier
33# PURPOSE: File in charge of holding the actual disp class.
35# +==== END display_tty =================+
41from typing
import List, Dict, Union, Any
48 from colours
import LoggerColours
49 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
50 from log_level_tracker
import LogLevelTracker
53 from .colours
import LoggerColours
54 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
55 from .log_level_tracker
import LogLevelTracker
56 except ImportError
as e:
58 "Display TTY: Failed to import required dependencies"
65 @brief Represents a placeholder for the logging library. This is not a functioning class.
75 @brief The class in charge of displaying messages with various styles and animations.
77 @details This class provides methods to display messages in different formats, log messages, and manage output configurations.
80 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:
82 @brief Constructor for the Disp class.
84 @param toml_content Dictionary containing configuration values.
85 @param save_to_file Boolean indicating whether to save output to a file.
86 @param file_name Name of the file to save output to.
87 @param file_descriptor File descriptor for the output file.
88 @param debug Boolean indicating whether debug mode is enabled.
89 @param logger Logger instance or name to use for logging.
90 @param success Integer representing the success status code.
91 @param error Integer representing the error status code.
92 @param log_warning_when_present Boolean indicating whether to log warnings when they arise in one of the function calls.
93 @param log_errors_when_present Boolean indicating whether to log errors when they arise in one of the function calls.
98 self.
author =
"(c) Created by Henry Letellier"
132 msg = f
"Invalid output mode. Must be one of '{OUT_FILE}', "
133 msg += f
"'{OUT_STRING}', '{OUT_TTY}', '{OUT_DEFAULT}'"
134 raise ValueError(msg)
141 @brief Setup the logger for the class.
143 @param logger The logger to use. If None, a default logger will be used.
146 if callable(logger)
and hasattr(logger,
"debug"):
149 if isinstance(logger, str)
is True:
150 self.
logger = logging.getLogger(logger)
152 self.
logger = logging.getLogger(self.__class__.__name__)
153 if not self.
logger.hasHandlers():
154 handler = colorlog.StreamHandler()
155 format_string =
'[%(asctime)s] %(log_color)s%('
157 format_string +=
'_log_color)s%(levelname)s%(reset)s %(name)s: \'%(message)s\''
158 formatter = colorlog.ColoredFormatter(
168 'CRITICAL':
'bold_red'
170 secondary_log_colors={
174 'WARNING':
'bg_black',
176 'CRITICAL':
'bg_black',
180 handler.setFormatter(formatter)
181 self.
logger.addHandler(handler)
185 if node.check_presence()
is False:
193 exec(func_code, globals(), namespace)
196 func = namespace[name]
201 @brief Add a dynamically created function to an instance.
203 @param func_dest The destination object to add the function to.
204 @param func_name Name of the function.
205 @param func_code Code of the function.
208 setattr(func_dest, func_name, function_instance)
212 @brief Update the debug mode.
214 @param debug Boolean indicating whether debug mode is enabled.
220 @brief Update the logger level.
222 @param level The log importance level. Defaults to NOTSET.
224 @return The status code of the operation.
226 _func_name = inspect.currentframe().f_code.co_name
228 if isinstance(level, str):
229 level = level.upper()
230 if hasattr(logging,
"LogLevelTracker"):
231 level = logging.LogLevelTracker.get_level(level)
232 if isinstance(level, int)
is False or level
not in LogLevelTracker.Levels.__all__:
233 level = LogLevelTracker.Levels.NOTSET
236 f
"The level is not valid, defaulting to {level}",
239 self.
logger.setLevel(level)
243 @brief Check if the logger instance is valid.
245 @param logger_instance The logger instance to validate.
246 @return A valid logger instance.
249 _func_name = inspect.currentframe()
250 if _func_name.f_back
is not None:
251 _func_name = _func_name.f_back.f_code.co_name
253 _func_name = _func_name.f_code.co_name
256 if logger_instance
is None or not isinstance(logger_instance, logging.Logger):
259 "No logger instance provided, using the default logger",
262 logger_instance = self.
logger
263 return logger_instance
265 def _check_colour_data(self, colour: Union[str, int], logger_instance: logging.Logger =
None) -> Union[int, str]:
267 @brief Check if the provided colour data is valid.
269 @param colour The colour to validate.
270 @param logger_instance The logger instance to use for logging errors.
271 @return The validated colour or an error code.
273 _func_name = inspect.currentframe()
274 if _func_name.f_back
is not None:
275 _func_name = _func_name.f_back.f_code.co_name
277 _func_name = _func_name.f_code.co_name
280 if isinstance(colour, int):
281 colour = LoggerColours.get_colour_string(LoggerColours, colour)
282 if LoggerColours.check_if_colour_present(LoggerColours, colour)
is False:
285 "The provided colour is not valid",
291 def _check_level_data(self, level_name: Union[str, int], logger_instance: logging.Logger =
None) -> Union[int, str]:
293 @brief Check if the provided level data is valid.
295 @param level_name The level name or number to validate.
296 @param logger_instance The logger instance to use for logging errors.
297 @return The validated level name or an error code.
299 _func_name = inspect.currentframe()
300 if _func_name.f_back
is not None:
301 _func_name = _func_name.f_back.f_code.co_name
303 _func_name = _func_name.f_code.co_name
306 if len(logger_instance.handlers) == 0:
309 'No handlers are present in this logging instance',
315 if isinstance(level_name, str):
316 name_string = level_name.upper()
317 elif isinstance(level_name, int):
318 name_string = logging.getLevelName(level_name)
322 "The level name must be a string or an integer",
330 @brief Get the colour formatter from the logger instance.
332 @param logger_instance The logger instance to retrieve the formatter from.
333 @return The colour formatter or an error code.
335 _func_name = inspect.currentframe()
336 if _func_name.f_back
is not None:
337 _func_name = _func_name.f_back.f_code.co_name
339 _func_name = _func_name.f_code.co_name
342 colour_handler =
None
343 for i
in logger_instance.handlers:
344 if isinstance(i, colorlog.StreamHandler):
347 if not colour_handler:
350 'No colour handler is present in this logging instance',
356 if hasattr(colour_handler,
"formatter")
is False:
359 'The colour handler has no formatter',
363 colour_formatter: colorlog.ColoredFormatter = colour_handler.formatter
364 if isinstance(colour_formatter, colorlog.ColoredFormatter)
is False:
367 'The formatter is not a ColoredFormatter',
371 if hasattr(colour_formatter,
"log_colors")
is False:
374 'The formatter has no log_colors',
378 return colour_formatter
382 @brief Update or insert a logging colour for the text of the specified level.
384 @param colour The colour to use (string or number).
385 @param level_name The level name or number.
386 @param logger_instance The logger instance to update.
387 @return The status code of the operation.
389 _func_name = inspect.currentframe().f_code.co_name
397 if name_string == self.
error:
400 f
"The level name {level_name} is not valid",
405 name_string = name_string.upper()
408 colour_input = colour
413 if colour == self.
error:
416 f
"The colour {colour_input} is not valid",
422 if internal_log_colors == self.
error or internal_log_colors
is None:
425 'The colour logging library is not valid',
429 lib_log_colors = internal_log_colors.log_colors
430 if isinstance(lib_log_colors, dict)
is False:
433 'The log_colors is not a dictionary',
437 for i
in lib_log_colors:
438 if i.upper() == name_string:
439 lib_log_colors[i] = colour
441 lib_log_colors[name_string.upper()] = colour
446 @brief Update or insert a logging colour for the background of the specified level.
448 @param colour The colour to use (string or number).
449 @param level_name The level name or number.
450 @param logger_instance The logger instance to update.
451 @return The status code of the operation.
453 _func_name = inspect.currentframe().f_code.co_name
461 if name_string == self.
error:
464 f
"The level name {level_name} is not valid",
468 name_string = name_string.upper()
471 colour_input = colour
476 if colour == self.
error:
479 f
"The colour {colour_input} is not valid",
485 if colour.startswith(
"bg_")
is False:
486 colour =
"bg_" + colour
489 if secondary_log_colors == self.
error:
492 'The secondary_log_colors is not valid',
496 secondary_log_colors = secondary_log_colors.secondary_log_colors
497 if isinstance(secondary_log_colors, dict)
is False:
500 'The secondary_log_colors is not a dictionary',
508 name_string.upper(): colour
512 if i.upper() == name_string:
516 secondary_log_colors[
518 ][name_string.upper()] = colour
521 def add_custom_level(self, level: int, name: str, colour_text: Union[int, str] =
"", colour_bg: Union[int, str] =
"") -> int:
523 @brief Add a custom level to the logger.
525 @param level The integer value of the custom level.
526 @param name The name of the custom level.
527 @param colour_text The text colour for the custom level.
528 @param colour_bg The background colour for the custom level.
529 @return The status code of the operation.
531 _func_name = inspect.currentframe().f_code.co_name
534 if level
in FORBIDDEN_NUMBER_LOG_LEVELS:
537 f
"The provided level is forbidden because already taken '{level}'",
541 if name
in FORBIDDEN_NUMBER_LOG_LEVELS_CORRESPONDANCE:
544 f
"The provided name is forbidden because already taken '{name}'",
549 logging.addLevelName(level, name.upper())
550 if hasattr(logging.getLogger(),
"log_level_tracker")
is False:
553 "The log level tracker is not present, adding",
559 if logging.getLogger().log_level_tracker.add_level(name, level)
is False:
562 "The level could not be added to the log level tracker",
567 if colour_text !=
"" or colour_text < 0:
573 if colour_text_status == self.
error:
576 "The colour for the text could not be set",
579 if colour_bg !=
"" or colour_bg < 0:
585 if colour_bg_status == self.
error:
588 "The colour for the background could not be set",
593 func_name = name.lower()
605 func_disp_name = f
"disp_print_{func_name}"
606 function_disp_code = f
"""
607def {func_disp_name}(self, string: str = "", func_name: Union[str, None] = None) -> None:
608 if isinstance(func_name, str) is False or func_name is None:
609 _func_name = inspect.currentframe()
610 if _func_name.f_back is not None:
611 func_name = _func_name.f_back.f_code.co_name
613 func_name = _func_name.f_code.co_name
614 self.log_custom_level({level}, string, func_name)
622 func_log_name = f
"log_{func_name}"
623 function_disp_short_code = f
"""
624def {func_log_name}(self, string: str = "", func_name: Union[str, None] = None) -> None:
625 if isinstance(func_name, str) is False or func_name is None:
626 _func_name = inspect.currentframe()
627 if _func_name.f_back is not None:
628 _func_name = _func_name.f_back.f_code.co_name
630 _func_name = _func_name.f_code.co_name
631 self.log_custom_level({level}, string, _func_name)
636 function_disp_short_code
642 @brief Print a message with a custom level.
644 @param level The custom level to use.
645 @param string The message to print.
646 @param func_name The name of the calling function.
648 if isinstance(func_name, str)
is False or func_name
is None:
649 _func_name = inspect.currentframe()
650 if _func_name.f_back
is not None:
651 func_name = _func_name.f_back.f_code.co_name
653 func_name = _func_name.f_code.co_name
654 if isinstance(level, str):
655 log_level_tracker: LogLevelTracker = logging.getLogger().log_level_tracker
656 level = log_level_tracker.get_level(level)
659 "The provided level is not valid"
662 if self.
logger.isEnabledFor(level):
663 self.
logger.log(level,
"(%s) %s", func_name, string)
667 @brief Print a debug message (using logger).
669 @param string The message to print.
670 @param func_name The name of the calling function.
672 if isinstance(func_name, str)
is False or func_name
is None:
673 _func_name = inspect.currentframe()
674 if _func_name.f_back
is not None:
675 func_name = _func_name.f_back.f_code.co_name
677 func_name = _func_name.f_code.co_name
681 def disp_print_info(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
683 @brief Print an information message (using logger).
685 @param string The message to print.
686 @param func_name The name of the calling function.
688 if isinstance(func_name, str)
is False or func_name
is None:
689 _func_name = inspect.currentframe()
690 if _func_name.f_back
is not None:
691 func_name = _func_name.f_back.f_code.co_name
693 func_name = _func_name.f_code.co_name
694 self.
logger.info(
"(%s) %s", func_name, string)
698 @brief Print a warning message (using logger).
700 @param string The message to print.
701 @param func_name The name of the calling function.
703 if isinstance(func_name, str)
is False or func_name
is None:
704 _func_name = inspect.currentframe()
705 if _func_name.f_back
is not None:
706 func_name = _func_name.f_back.f_code.co_name
708 func_name = _func_name.f_code.co_name
709 self.
logger.warning(
"(%s) %s", func_name, string)
713 @brief Print an error message (using logger).
715 @param string The message to print.
716 @param func_name The name of the calling function.
718 if isinstance(func_name, str)
is False or func_name
is None:
719 _func_name = inspect.currentframe()
720 if _func_name.f_back
is not None:
721 func_name = _func_name.f_back.f_code.co_name
723 func_name = _func_name.f_code.co_name
728 @brief Print a critical message (using logger).
730 @param string The message to print.
731 @param func_name The name of the calling function.
733 if isinstance(func_name, str)
is False or func_name
is None:
734 _func_name = inspect.currentframe()
735 if _func_name.f_back
is not None:
736 func_name = _func_name.f_back.f_code.co_name
738 func_name = _func_name.f_code.co_name
739 self.
logger.critical(
"(%s) %s", func_name, string)
741 def log_custom_level(self, level: Union[int, str], string: str, func_name: Union[str,
None] =
None) ->
None:
743 @brief Log a message with a custom level.
745 @param level The custom level to use.
746 @param string The message to log.
747 @param func_name The name of the calling function.
749 if isinstance(func_name, str)
is False or func_name
is None:
750 _func_name = inspect.currentframe()
751 if _func_name.f_back
is not None:
752 func_name = _func_name.f_back.f_code.co_name
754 func_name = _func_name.f_code.co_name
757 def log_debug(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
759 @brief Log a debug message.
761 @param string The message to log.
762 @param func_name The name of the calling function.
764 if isinstance(func_name, str)
is False or func_name
is None:
765 _func_name = inspect.currentframe()
766 if _func_name.f_back
is not None:
767 func_name = _func_name.f_back.f_code.co_name
769 func_name = _func_name.f_code.co_name
772 def log_info(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
774 @brief Log an info message.
776 @param string The message to log.
777 @param func_name The name of the calling function.
779 if isinstance(func_name, str)
is False or func_name
is None:
780 _func_name = inspect.currentframe()
781 if _func_name.f_back
is not None:
782 func_name = _func_name.f_back.f_code.co_name
784 func_name = _func_name.f_code.co_name
787 def log_warning(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
789 @brief Log a warning message.
791 @param string The message to log.
792 @param func_name The name of the calling function.
794 if isinstance(func_name, str)
is False or func_name
is None:
795 _func_name = inspect.currentframe()
796 if _func_name.f_back
is not None:
797 func_name = _func_name.f_back.f_code.co_name
799 func_name = _func_name.f_code.co_name
802 def log_error(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
804 @brief Log an error message.
806 @param string The message to log.
807 @param func_name The name of the calling function.
809 if isinstance(func_name, str)
is False or func_name
is None:
810 _func_name = inspect.currentframe()
811 if _func_name.f_back
is not None:
812 func_name = _func_name.f_back.f_code.co_name
814 func_name = _func_name.f_code.co_name
817 def log_critical(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
819 @brief Log a critical message.
821 @param string The message to log.
822 @param func_name The name of the calling function.
824 if isinstance(func_name, str)
is False or func_name
is None:
825 _func_name = inspect.currentframe()
826 if _func_name.f_back
is not None:
827 func_name = _func_name.f_back.f_code.co_name
829 func_name = _func_name.f_code.co_name
834 @brief Close the log file if it was opened.
839 "The file was not opened, no need to close it",
840 inspect.currentframe().f_code.co_name
848 @brief Return the generated string.
850 @return The generated content.
858 @brief Generate the required amount of spaces for the padding of the shape.
860 @param string_length The length of the provided string.
861 @return The number of spaces required for the padding.
866 calculated_length = int(
869 if calculated_length % 2 == 1
and calculated_length != 0:
870 calculated_length += 1
879 @brief Open the file if required and add the current date and time.
893 @brief Check if an item is safe to write or not.
895 @param content The item to check.
896 @return True if the item is safe to write, False otherwise.
898 if isinstance(content, (str, int, float, tuple, complex, bytes, bytearray, memoryview))
is False:
904 @brief Create a string based on a character and a length.
906 @param length The length of the string.
907 @param character The character to use.
908 @return The created string.
910 line = [character
for i
in range(0, length)]
911 string =
"".join(line)
916 @brief Print the message letter by letter while applying a provided delay.
918 @param message The message to display.
919 @param delay The delay between each letter.
922 for letter
in message.split(
" "):
923 sys.stdout.write(letter)
925 sys.stdout.write(
" ")
928 for letter
in message:
929 sys.stdout.write(letter)
933 sys.stdout.write(message)
936 def animate_message(self, message: str =
"Hello World!", delay: float = 0.02) ->
None:
938 @brief Display or dump (to file) a message.
940 @param message The message to display or dump.
941 @param delay The delay between each letter.
944 message = f
"{message}"
954 @brief Display a message in a box.
956 @param msg The message to display.
957 @param char The character to use for the box.
959 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
960 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
961 @example #############################\n
962 @example # Sample text #\n
963 @example #############################
970 lines = msg.split(
"\n")
972 string_length = len(i)
974 title_content += char
975 title_content += white_spaces
977 if string_length % 2 == 1
and string_length != 0:
978 white_spaces = white_spaces[:-1]
979 title_content += white_spaces
980 title_content += char
981 title_content +=
'\n'
983 string_length = len(msg)
986 title_content += char
987 title_content += white_spaces
989 if string_length % 2 == 1
and string_length != 0:
990 white_spaces = white_spaces[:-1]
991 title_content += white_spaces
992 title_content += char
993 title_content +=
"\n"
995 generated_content = f
"{box_wall}\n"
996 generated_content += f
"{title_content}"
997 generated_content += f
"{box_wall}"
999 f
"{generated_content}",
1005 @brief Display a message in a rounded box.
1007 @param msg The message to display.
1009 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1010 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1011 @example ╔══════════════════════╗\n
1012 @example ║ Sample text ║\n
1013 @example ╚══════════════════════╝\n
1025 "The top left corner is not defined, using the default one",
1026 inspect.currentframe().f_code.co_name
1031 self.
nb_chr - offset_reset,
1037 "The horizontal line is not defined, using the default one",
1038 inspect.currentframe().f_code.co_name
1041 self.
nb_chr-offset_reset,
1049 "The top right corner is not defined, using the default one",
1050 inspect.currentframe().f_code.co_name
1061 "The bottom left corner is not defined, using the default one",
1062 inspect.currentframe().f_code.co_name
1068 self.
nb_chr-offset_reset,
1074 "The horizontal line is not defined, using the default one",
1075 inspect.currentframe().f_code.co_name
1078 self.
nb_chr-offset_reset,
1086 "The bottom right corner is not defined, using the default one",
1087 inspect.currentframe().f_code.co_name
1091 border_character =
""
1097 "The vertical line is not defined, using the default one",
1098 inspect.currentframe().f_code.co_name
1100 border_character =
"║"
1104 lines = msg.split(
"\n")
1106 string_length = len(i)
1108 center_content += border_character
1109 center_content += white_spaces
1111 if string_length % 2 == 1
and string_length != 0:
1112 white_spaces = white_spaces[:-1]
1113 center_content += white_spaces
1114 center_content += border_character
1115 center_content +=
'\n'
1117 string_length = len(msg)
1119 center_content += border_character
1120 center_content += white_spaces
1121 center_content += msg
1122 if string_length % 2 == 1
and string_length != 0:
1123 white_spaces = white_spaces[:-1]
1124 center_content += white_spaces
1125 center_content += border_character
1126 center_content +=
"\n"
1128 generated_content = f
"{top_wall}\n"
1129 generated_content += f
"{center_content}"
1130 generated_content += f
"{bottom_wall}"
1132 f
"{generated_content}",
1138 @brief Display a message in a box with different side and top characters.
1140 @param msg The message to display.
1142 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1143 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1144 @example _____________________________\n
1145 @example | Sample text |\n
1146 @example _____________________________
1154 "The ceiling boxes are not defined, using the default one",
1155 inspect.currentframe().f_code.co_name
1159 border_character =
""
1165 "The border character is not defined, using the default one",
1166 inspect.currentframe().f_code.co_name
1168 border_character =
"|"
1174 lines = msg.split(
"\n")
1176 string_length = len(i)
1178 title_content += border_character
1179 title_content += white_spaces
1181 if string_length % 2 == 1
and string_length != 0:
1182 white_spaces = white_spaces[:-1]
1183 title_content += white_spaces
1184 title_content += border_character
1185 title_content +=
'\n'
1187 string_length = len(msg)
1189 title_content += border_character
1190 title_content += white_spaces
1191 title_content += msg
1192 if string_length % 2 == 1
and string_length != 0:
1193 white_spaces = white_spaces[:-1]
1194 title_content += white_spaces
1195 title_content += border_character
1196 title_content +=
"\n"
1198 generated_content = f
"{box_wall}\n"
1199 generated_content += f
"{title_content}"
1200 generated_content += f
"{box_wall}"
1202 f
"{generated_content}",
1208 @brief Print a box format without internal vertical bars.
1210 @param message The message to display.
1211 @param character The character to use for the box.
1212 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1213 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1214 @example #############################\n
1215 @example Sample text \n
1216 @example #############################
1223 "The box character is not defined, using the default one",
1224 inspect.currentframe().f_code.co_name
1232 lines = message.split(
"\n")
1234 string_length = len(i)
1236 title_content += white_spaces
1238 if string_length % 2 == 1
and string_length != 0:
1239 white_spaces = white_spaces[:-1]
1240 title_content += white_spaces
1241 title_content +=
'\n'
1243 string_length = len(message)
1245 title_content += white_spaces
1246 title_content += message
1247 if string_length % 2 == 1
and string_length != 0:
1248 white_spaces = white_spaces[:-1]
1249 title_content += white_spaces
1250 title_content +=
"\n"
1252 generated_content = f
"{box_wall}\n"
1253 generated_content += f
"{title_content}"
1254 generated_content += f
"{box_wall}"
1256 f
"{generated_content}",
1262 @brief Display a message in a box with vertical bars.
1264 @param msg The message to display.
1265 @param character The character to use for the box.
1266 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1267 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1268 @example ###############\n
1272 @example # Sample text #\n
1276 @example ###############
1281 elif character ==
'':
1284 "The box character is not defined, using the default one",
1285 inspect.currentframe().f_code.co_name
1293 lines = msg.split(
"\n")
1295 string_length = len(i)
1297 title_content += character
1298 title_content += white_spaces
1300 if string_length % 2 == 1
and string_length != 0:
1301 white_spaces = white_spaces[:-1]
1302 title_content += white_spaces
1303 title_content += character
1304 title_content +=
"\n"
1307 string_length = len(msg)
1309 title_content += character
1310 title_content += white_spaces
1311 title_content += msg
1312 if string_length % 2 == 1
and string_length != 0:
1313 white_spaces = white_spaces[:-1]
1314 title_content += white_spaces
1315 title_content += character
1316 title_content +=
"\n"
1323 inner_line = f
"{character}{inner_line}{character}"
1325 generated_content = f
"{box_wall}\n"
1327 max_height = (inner_length / 4) - len(msg)
1333 while i < max_height:
1334 generated_content += f
"{inner_line}\n"
1336 generated_content += f
"{title_content}"
1338 while i < max_height:
1339 generated_content += f
"{inner_line}\n"
1342 generated_content += f
"{box_wall}"
1345 f
"{generated_content}",
1351 @brief Print a box format without internal horizontal bars.
1353 @param message The message to display.
1354 @param character The character to use for the box.
1355 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1356 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1362 @example # Sample text #\n
1371 elif character ==
'':
1374 "The box character is not defined, using the default one",
1375 inspect.currentframe().f_code.co_name
1381 lines = message.split(
"\n")
1383 string_length = len(i)
1385 title_content += character
1386 title_content += white_spaces
1388 if string_length % 2 == 1
and string_length != 0:
1389 white_spaces = white_spaces[:-1]
1390 title_content += white_spaces
1391 title_content += character
1392 title_content +=
"\n"
1395 string_length = len(message)
1397 title_content += character
1398 title_content += white_spaces
1399 title_content += message
1400 if string_length % 2 == 1
and string_length != 0:
1401 white_spaces = white_spaces[:-1]
1402 title_content += white_spaces
1403 title_content += character
1404 title_content +=
"\n"
1412 inner_line = f
"{character}{inner_line}{character}"
1414 generated_content =
""
1416 max_height = (inner_length / 4) - len(message)
1422 while i < max_height:
1423 generated_content += f
"{inner_line}\n"
1425 generated_content += f
"{title_content}"
1427 while i < max_height:
1428 if i+1 >= max_height:
1429 generated_content += f
"{inner_line}"
1431 generated_content += f
"{inner_line}\n"
1435 f
"{generated_content}",
1441 @brief Print a beautified title.
1443 @param title The title to display.
1449 @brief Print a beautified subtitle.
1451 @param sub_title The subtitle to display.
1457 @brief Print a beautified sub-subtitle.
1459 @param sub_sub_title The sub-subtitle to display.
1463 def message(self, message: Union[str, list]) ->
None:
1465 @brief Print a beautified message.
1467 @param message The message to display.
1468 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1469 @note Here is an example for the output (This is determined by the key repeated twice)\n
1470 @example @@ This is an example message @@
1472 if isinstance(message, list)
is True:
1474 m_msg = f
"{self.message_char}{self.message_char} {msg} "
1475 m_msg += f
"{self.message_char}{self.message_char}"
1481 msg = f
"{self.message_char}{self.message_char} "
1482 msg += f
" {message} {self.message_char}{self.message_char}"
1490 @brief Print a beautified error message.
1492 @param message The error message to display.
1493 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1494 @note Here is an example for the output (This is determined by the key repeated twice)\n
1495 @example ## This is an example message ##
1497 if isinstance(message, list)
is True:
1498 m_msg = f
"{self.message_error_char}{self.message_error_char} Error: "
1499 m_msg += f
"{self.message_error_char}{self.message_error_char}"
1501 m_msg = f
"{self.message_error_char}{self.message_error_char} {msg} "
1502 m_msg += f
"{self.message_error_char}{self.message_error_char}"
1508 msg = f
"{self.message_error_char}{self.message_error_char} Error:"
1509 msg += f
" {message} {self.message_error_char}{self.message_error_char}"
1517 @brief Print a beautified success message.
1519 @param message The success message to display.
1520 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1521 @note Here is an example for the output (This is determined by the key repeated twice)\n
1522 @example // This is an example message //
1524 if isinstance(message, list)
is True:
1525 m_msg = f
"{self.message_success_char}{self.message_success_char} Success: "
1526 m_msg += f
"{self.message_success_char}{self.message_success_char}"
1528 m_msg = f
"{self.message_success_char}{self.message_success_char} {msg} "
1529 m_msg += f
"{self.message_success_char}{self.message_success_char}"
1535 msg = f
"{self.message_success_char}{self.message_success_char} Success:"
1536 msg += f
" {message} {self.message_success_char}{self.message_success_char}"
1544 @brief Print a beautified warning message.
1546 @param message The warning message to display.
1547 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1548 @note Here is an example for the output (This is determined by the key repeated twice)\n
1549 @example !! This is an example message !!
1551 if isinstance(message, list)
is True:
1552 m_msg = f
"{self.message_warning_char}{self.message_warning_char} Warning: "
1553 m_msg += f
"{self.message_warning_char}{self.message_warning_char}"
1555 m_msg = f
"{self.message_warning_char}{self.message_warning_char} {msg} "
1556 m_msg += f
"{self.message_warning_char}{self.message_warning_char}"
1562 msg = f
"{self.message_warning_char}{self.message_warning_char} Warning:"
1563 msg += f
" {message} {self.message_warning_char}{self.message_warning_char}"
1571 @brief Print a beautified question message.
1573 @param message The question message to display.
1574 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1575 @note Here is an example for the output (This is determined by the key repeated twice)\n
1576 @example ?? This is an example message ??
1578 if isinstance(message, list)
is True:
1579 m_msg = f
"{self.message_question_char}{self.message_question_char} Question: "
1580 m_msg += f
"{self.message_question_char}{self.message_question_char}"
1582 m_msg = f
"{self.message_question_char}{self.message_question_char} {msg} "
1583 m_msg += f
"{self.message_question_char}{self.message_question_char}"
1589 msg = f
"{self.message_question_char}{self.message_question_char} Question:"
1590 msg += f
" {message} {self.message_question_char}{self.message_question_char}"
1598 @brief Print a beautified information message.
1600 @param message The information message to display.
1601 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1602 @note Here is an example for the output (This is determined by the key repeated twice)\n
1603 @example ii This is an example message ii
1605 if isinstance(message, list)
is True:
1607 m_msg = f
"{self.message_inform_char}{self.message_inform_char} {msg} "
1608 m_msg += f
"{self.message_inform_char}{self.message_inform_char}"
1614 msg = f
"{self.message_inform_char}{self.message_inform_char} {message} "
1615 msg += f
"{self.message_inform_char}{self.message_inform_char}"
1621 def _tree_node(self, line: str, offset: int, index: int, max_lenght: int) -> str:
1623 @brief Display a line of the tree.
1625 @param line The line to display.
1626 @param offset The offset for the line.
1627 @param index The index of the line.
1628 @param max_lenght The maximum length of the tree.
1629 @return The processed line.
1630 @note The characters displayed in this tree function is managed by the following keys:\n
1631 @note * TREE_NODE_CHAR\n
1632 @note * TREE_NODE_END_CHAR\n
1633 @note * TREE_LINE_SEPERATOR_CHAR\n
1634 @note * TREE_COLUMN_SEPERATOR_CHAR\n
1635 @example Here is an example generated by this function:\n
1636 @example ├─── data1\n
1639 processed_line = str()
1642 processed_line += f
"{self.tree_column_seperator_char} "
1644 if index
is max_lenght:
1645 processed_line += f
"{self.tree_node_end_char}{self.tree_line_seperator_char}"
1646 processed_line += f
"{self.tree_line_seperator_char}{self.tree_line_seperator_char}"
1648 processed_line += f
"{self.tree_node_char}{self.tree_line_seperator_char}"
1649 processed_line += f
"{self.tree_line_seperator_char}{self.tree_line_seperator_char}"
1652 processed_line +=
" "
1653 processed_line += line
1654 processed_line +=
'\n'
1655 return processed_line
1657 def tree(self, title: str, data: List[str], offset: int = 0) -> Union[str,
None]:
1659 @brief Print a list under the form of a beautified tree.
1661 @param title The title of the tree.
1662 @param data The data to display in the tree.
1663 @param offset The offset for the tree.
1664 @note The characters displayed in this tree function is managed by the following keys:\n
1665 @note * TREE_NODE_CHAR\n
1666 @note * TREE_NODE_END_CHAR\n
1667 @note * TREE_LINE_SEPERATOR_CHAR\n
1668 @note * TREE_COLUMN_SEPERATOR_CHAR\n
1669 @example Here is an example generated by this function:\n
1670 @example ├─── data1\n
1672 @return A stringified version of the tree if not set to be displayed.
1674 generated_content =
""
1676 generated_content += f
"{title}\n"
1677 length = len(data) - 1
1679 for line
in enumerate(data):
1680 if isinstance(data, list)
and isinstance(line[1], (list, dict)):
1687 generated_content += self.
tree(line[0], line[1], offset + 1)
1689 if isinstance(data, dict)
and isinstance(data[line[1]], (list, dict)):
1696 generated_content += self.
tree(
1702 if isinstance(data, dict)
and isinstance(data[line[1]], dict)
is False:
1704 f
"{line[1]}: {data[line[1]]}",
1718 f
"{generated_content}",
1722 return generated_content
1726 @brief Add the date and time at which the program was launched.
1727 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1728 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1729 @example ########################################\n
1730 @example # Run date: 07/06/2024 22:26:10 #\n
1731 @example ########################################
1733 self.
title(f
"Run date: {time.strftime('%d/%m/%Y %H:%M:%S')} ")
1737 @brief Test function to ensure all implemented methods work as expected.
1740 "test_data1":
"test_data1.1",
1741 "test_data2":
"test_data2.1",
1743 "test_data_list3.1",
1744 "test_data_list3.2",
1745 "test_data_list3.3",
1746 "test_data_list3.4",
1749 "test_data4":
"test_data4.1",
1751 "test_data5.1":
"test_data5.1.1",
1752 "test_data5.2":
"test_data5.2.1",
1753 "test_data5.3":
"test_data5.3.1",
1754 "test_data5.4":
"test_data5.4.1"
1758 "test_data6.1":
"test_data6.1.1",
1759 "test_data6.2":
"test_data6.2.1"
1762 "test_data_list6.3.1",
1763 "test_data_list6.3.1",
1764 "test_data_list6.3.1",
1765 "test_data_list6.3.1"
1770 "test_data7.1.1":
"test_data7.1.1.1",
1771 "test_data7.1.2":
"test_data7.1.2.1"
1789 self.
title(
"Test title")
1795 "Test Disp diff side and top message box"
1799 self.
tree(
"Test data", test_data)
1809 logging.WARNING,
"This is a test warning for custom level messages"
1811 custom_level_int = 2
1812 level_name =
"DARLING"
1820 f
"The custom level '{level_name}' could not be added, please check the configuration"
1825 f
"This is a test for custom level message \"{logging.getLevelName(custom_level_int)}\""
1827 custom_level_int = 196
1828 level_name =
"Ikuno"
1836 f
"The custom level '{level_name}' could not be added, please check the configuration"
1841 f
"This is a test for custom level message \"{logging.getLevelName(custom_level_int)}\""
1846if __name__ ==
"__main__":
1848 toml_content=TOML_CONF,
1850 file_name=
"test_run.tmp",
1851 file_descriptor=
None,
Union[int, str] _check_level_data(self, Union[str, int] level_name, logging.Logger logger_instance=None)
str create_string(self, length, character)
None disp_print_custom_level(self, Union[int, str] level, str string, Union[str, None] func_name=None)
None log_info(self, str string="", Union[str, None] func_name=None)
None disp_round_message_box(self, str msg="Sample text")
None question_message(self, Union[str, list] message)
None disp_print_error(self, str string="", Union[str, None] func_name=None)
int update_logging_colour_background(self, Union[str, int] colour, Union[str, int] level_name, logging.Logger logger_instance=None)
None append_run_date(self)
Union[str, None] tree(self, str title, List[str] data, int offset=0)
None box_vertical_no_horizontal(self, str message, str character="")
None log_error(self, str string="", Union[str, None] func_name=None)
str get_generated_content(self)
None log_warning(self, str string="", Union[str, None] func_name=None)
None sub_sub_title(self, str sub_sub_title)
None disp_print_warning(self, str string="", Union[str, None] func_name=None)
None _add_function_to_instance(self, object func_dest, str func_name, str func_code)
bool _is_safe(self, Any content)
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)
None disp_print_critical(self, str string="", Union[str, None] func_name=None)
None animate_message(self, str message="Hello World!", float delay=0.02)
None success_message(self, Union[str, list] message)
None disp_print_info(self, str string="", Union[str, None] func_name=None)
str _tree_node(self, str line, int offset, int index, int max_lenght)
_create_function(self, name, func_code)
logging.Logger _check_the_logging_instance(self, logging.Logger logger_instance=None)
None log_critical(self, str string="", Union[str, None] func_name=None)
None log_custom_level(self, Union[int, str] level, str string, Union[str, None] func_name=None)
Union[None, colorlog.ColoredFormatter] _get_colour_formatter(self, logging.Logger logger_instance=None)
None sub_title(self, str sub_title)
None disp_message_box(self, str msg, str char="#")
None update_disp_debug(self, bool debug)
None log_debug(self, str string="", Union[str, None] func_name=None)
Union[int, str] _check_colour_data(self, Union[str, int] colour, logging.Logger logger_instance=None)
tree_column_seperator_char
None disp_box_no_vertical(self, str message, str character="@")
str _calculate_required_spaces(self, int string_length)
None _setup_logger(self, Union[Logging, str, None] logger)
None display_animation(self, str message="Hello World!", float delay=0.02)
None title(self, str title)
None error_message(self, Union[str, list] message)
str background_colour_key
None message(self, Union[str, list] message)
int update_logging_colour_text(self, Union[str, int] colour, Union[str, int] level_name, logging.Logger logger_instance=None)
None warning_message(self, Union[str, list] message)
None disp_diff_side_and_top_message_box(self, str msg)
None update_logger_level(self, Union[int, LogLevelTracker.Levels] level=LogLevelTracker.Levels.NOTSET)
None disp_print_debug(self, str string="", Union[str, None] func_name=None)
None disp_vertical_message_box(self, str msg, str character='')
None inform_message(self, Union[str, List] message)
int add_custom_level(self, int level, str name, Union[int, str] colour_text="", Union[int, str] colour_bg="")
None test_the_class(self)