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: 0:54:12 05-02-2026
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 =================+
42from typing
import List, Dict, Union, Any
49 from colours
import LoggerColours
50 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
51 from log_level_tracker
import LogLevelTracker
54 from .colours
import LoggerColours
55 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
56 from .log_level_tracker
import LogLevelTracker
57 except ImportError
as e:
59 "Display TTY: Failed to import required dependencies"
66 @brief Represents a placeholder for the logging library. This is not a functioning class.
76 @brief The class in charge of displaying messages with various styles and animations.
78 @details This class provides methods to display messages in different formats, log messages, and manage output configurations.
81 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:
83 @brief Constructor for the Disp class.
85 @param toml_content Dictionary containing configuration values.
86 @param save_to_file Boolean indicating whether to save output to a file.
87 @param file_name Name of the file to save output to.
88 @param file_descriptor File descriptor for the output file.
89 @param debug Boolean indicating whether debug mode is enabled.
90 @param logger Logger instance or name to use for logging.
91 @param success Integer representing the success status code.
92 @param error Integer representing the error status code.
93 @param log_warning_when_present Boolean indicating whether to log warnings when they arise in one of the function calls.
94 @param log_errors_when_present Boolean indicating whether to log errors when they arise in one of the function calls.
99 self.
author =
"(c) Created by Henry Letellier"
133 msg = f
"Invalid output mode. Must be one of '{OUT_FILE}', "
134 msg += f
"'{OUT_STRING}', '{OUT_TTY}', '{OUT_DEFAULT}'"
135 raise ValueError(msg)
142 @brief Setup the logger for the class.
144 @param logger The logger to use. If None, a default logger will be used.
147 if callable(logger)
and hasattr(logger,
"debug"):
150 if isinstance(logger, str)
is True:
151 self.
logger = logging.getLogger(logger)
153 self.
logger = logging.getLogger(self.__class__.__name__)
154 if not self.
logger.hasHandlers():
155 handler = colorlog.StreamHandler()
156 format_string =
'[%(asctime)s] %(log_color)s%('
158 format_string +=
'_log_color)s%(levelname)s%(reset)s %(name)s: \'%(message)s\''
159 formatter = colorlog.ColoredFormatter(
169 'CRITICAL':
'bold_red'
171 secondary_log_colors={
175 'WARNING':
'bg_black',
177 'CRITICAL':
'bg_black',
181 handler.setFormatter(formatter)
182 self.
logger.addHandler(handler)
186 if node.check_presence()
is False:
194 exec(func_code, globals(), namespace)
197 func = namespace[name]
202 @brief Add a dynamically created function to an instance.
204 @param func_dest The destination object to add the function to.
205 @param func_name Name of the function.
206 @param func_code Code of the function.
209 setattr(func_dest, func_name, function_instance)
213 @brief Update the debug mode.
215 @param debug Boolean indicating whether debug mode is enabled.
221 @brief Update the logger level.
223 @param level The log importance level. Defaults to NOTSET.
225 @return The status code of the operation.
227 _func_name = inspect.currentframe().f_code.co_name
229 if isinstance(level, str):
230 level = level.upper()
231 if hasattr(logging,
"LogLevelTracker"):
232 level = logging.LogLevelTracker.get_level(level)
233 if isinstance(level, int)
is False or level
not in LogLevelTracker.Levels.__all__:
234 level = LogLevelTracker.Levels.NOTSET
237 f
"The level is not valid, defaulting to {level}",
240 self.
logger.setLevel(level)
244 @brief Check if the logger instance is valid.
246 @param logger_instance The logger instance to validate.
247 @return A valid logger instance.
250 _func_name = inspect.currentframe()
251 if _func_name.f_back
is not None:
252 _func_name = _func_name.f_back.f_code.co_name
254 _func_name = _func_name.f_code.co_name
257 if logger_instance
is None or not isinstance(logger_instance, logging.Logger):
260 "No logger instance provided, using the default logger",
263 logger_instance = self.
logger
264 return logger_instance
266 def _check_colour_data(self, colour: Union[str, int], logger_instance: logging.Logger =
None) -> Union[int, str]:
268 @brief Check if the provided colour data is valid.
270 @param colour The colour to validate.
271 @param logger_instance The logger instance to use for logging errors.
272 @return The validated colour or an error code.
274 _func_name = inspect.currentframe()
275 if _func_name.f_back
is not None:
276 _func_name = _func_name.f_back.f_code.co_name
278 _func_name = _func_name.f_code.co_name
281 if isinstance(colour, int):
282 colour = LoggerColours.get_colour_string(LoggerColours, colour)
283 if LoggerColours.check_if_colour_present(LoggerColours, colour)
is False:
286 "The provided colour is not valid",
292 def _check_level_data(self, level_name: Union[str, int], logger_instance: logging.Logger =
None) -> Union[int, str]:
294 @brief Check if the provided level data is valid.
296 @param level_name The level name or number to validate.
297 @param logger_instance The logger instance to use for logging errors.
298 @return The validated level name or an error code.
300 _func_name = inspect.currentframe()
301 if _func_name.f_back
is not None:
302 _func_name = _func_name.f_back.f_code.co_name
304 _func_name = _func_name.f_code.co_name
307 if len(logger_instance.handlers) == 0:
310 'No handlers are present in this logging instance',
316 if isinstance(level_name, str):
317 name_string = level_name.upper()
318 elif isinstance(level_name, int):
319 name_string = logging.getLevelName(level_name)
323 "The level name must be a string or an integer",
331 @brief Get the colour formatter from the logger instance.
333 @param logger_instance The logger instance to retrieve the formatter from.
334 @return The colour formatter or an error code.
336 _func_name = inspect.currentframe()
337 if _func_name.f_back
is not None:
338 _func_name = _func_name.f_back.f_code.co_name
340 _func_name = _func_name.f_code.co_name
343 colour_handler =
None
344 for i
in logger_instance.handlers:
345 if isinstance(i, colorlog.StreamHandler):
348 if not colour_handler:
351 'No colour handler is present in this logging instance',
357 if hasattr(colour_handler,
"formatter")
is False:
360 'The colour handler has no formatter',
364 colour_formatter: colorlog.ColoredFormatter = colour_handler.formatter
365 if isinstance(colour_formatter, colorlog.ColoredFormatter)
is False:
368 'The formatter is not a ColoredFormatter',
372 if hasattr(colour_formatter,
"log_colors")
is False:
375 'The formatter has no log_colors',
379 return colour_formatter
383 @brief Update or insert a logging colour for the text of the specified level.
385 @param colour The colour to use (string or number).
386 @param level_name The level name or number.
387 @param logger_instance The logger instance to update.
388 @return The status code of the operation.
390 _func_name = inspect.currentframe().f_code.co_name
398 if name_string == self.
error:
401 f
"The level name {level_name} is not valid",
406 name_string = name_string.upper()
409 colour_input = colour
414 if colour == self.
error:
417 f
"The colour {colour_input} is not valid",
423 if internal_log_colors == self.
error or internal_log_colors
is None:
426 'The colour logging library is not valid',
430 lib_log_colors = internal_log_colors.log_colors
431 if isinstance(lib_log_colors, dict)
is False:
434 'The log_colors is not a dictionary',
438 for i
in lib_log_colors:
439 if i.upper() == name_string:
440 lib_log_colors[i] = colour
442 lib_log_colors[name_string.upper()] = colour
447 @brief Update or insert a logging colour for the background of the specified level.
449 @param colour The colour to use (string or number).
450 @param level_name The level name or number.
451 @param logger_instance The logger instance to update.
452 @return The status code of the operation.
454 _func_name = inspect.currentframe().f_code.co_name
462 if name_string == self.
error:
465 f
"The level name {level_name} is not valid",
469 name_string = name_string.upper()
472 colour_input = colour
477 if colour == self.
error:
480 f
"The colour {colour_input} is not valid",
486 if colour.startswith(
"bg_")
is False:
487 colour =
"bg_" + colour
490 if secondary_log_colors == self.
error:
493 'The secondary_log_colors is not valid',
497 secondary_log_colors = secondary_log_colors.secondary_log_colors
498 if isinstance(secondary_log_colors, dict)
is False:
501 'The secondary_log_colors is not a dictionary',
509 name_string.upper(): colour
513 if i.upper() == name_string:
517 secondary_log_colors[
519 ][name_string.upper()] = colour
522 def add_custom_level(self, level: int, name: str, colour_text: Union[int, str] =
"", colour_bg: Union[int, str] =
"") -> int:
524 @brief Add a custom level to the logger.
526 @param level The integer value of the custom level.
527 @param name The name of the custom level.
528 @param colour_text The text colour for the custom level.
529 @param colour_bg The background colour for the custom level.
530 @return The status code of the operation.
532 _func_name = inspect.currentframe().f_code.co_name
535 if level
in FORBIDDEN_NUMBER_LOG_LEVELS:
538 f
"The provided level is forbidden because already taken '{level}'",
542 if name
in FORBIDDEN_NUMBER_LOG_LEVELS_CORRESPONDANCE:
545 f
"The provided name is forbidden because already taken '{name}'",
550 logging.addLevelName(level, name.upper())
551 if hasattr(logging.getLogger(),
"log_level_tracker")
is False:
554 "The log level tracker is not present, adding",
560 if logging.getLogger().log_level_tracker.add_level(name, level)
is False:
563 "The level could not be added to the log level tracker",
568 if colour_text !=
"" or colour_text < 0:
574 if colour_text_status == self.
error:
577 "The colour for the text could not be set",
580 if colour_bg !=
"" or colour_bg < 0:
586 if colour_bg_status == self.
error:
589 "The colour for the background could not be set",
594 func_name = name.lower()
606 func_disp_name = f
"disp_print_{func_name}"
607 function_disp_code = f
"""
608def {func_disp_name}(self, string: str = "", func_name: Union[str, None] = None) -> None:
609 if isinstance(func_name, str) is False or func_name is None:
610 _func_name = inspect.currentframe()
611 if _func_name.f_back is not None:
612 func_name = _func_name.f_back.f_code.co_name
614 func_name = _func_name.f_code.co_name
615 self.log_custom_level({level}, string, func_name)
623 func_log_name = f
"log_{func_name}"
624 function_disp_short_code = f
"""
625def {func_log_name}(self, string: str = "", func_name: Union[str, None] = None) -> None:
626 if isinstance(func_name, str) is False or func_name is None:
627 _func_name = inspect.currentframe()
628 if _func_name.f_back is not None:
629 _func_name = _func_name.f_back.f_code.co_name
631 _func_name = _func_name.f_code.co_name
632 self.log_custom_level({level}, string, _func_name)
637 function_disp_short_code
643 @brief Print a message with a custom level.
645 @param level The custom level to use.
646 @param string The message to print.
647 @param func_name The name of the calling function.
649 if isinstance(func_name, str)
is False or func_name
is None:
650 _func_name = inspect.currentframe()
651 if _func_name.f_back
is not None:
652 func_name = _func_name.f_back.f_code.co_name
654 func_name = _func_name.f_code.co_name
655 if isinstance(level, str):
656 log_level_tracker: LogLevelTracker = logging.getLogger().log_level_tracker
657 level = log_level_tracker.get_level(level)
660 "The provided level is not valid"
663 if self.
logger.isEnabledFor(level):
664 self.
logger.log(level,
"(%s) %s", func_name, string)
668 @brief Print a debug message (using logger).
670 @param string The message to print.
671 @param func_name The name of the calling function.
675 if isinstance(func_name, str)
is False or func_name
is None:
676 _func_name = inspect.currentframe()
677 if _func_name.f_back
is not None:
678 func_name = _func_name.f_back.f_code.co_name
680 func_name = _func_name.f_code.co_name
683 def disp_print_info(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
685 @brief Print an information message (using logger).
687 @param string The message to print.
688 @param func_name The name of the calling function.
690 if isinstance(func_name, str)
is False or func_name
is None:
691 _func_name = inspect.currentframe()
692 if _func_name.f_back
is not None:
693 func_name = _func_name.f_back.f_code.co_name
695 func_name = _func_name.f_code.co_name
696 self.
logger.info(
"(%s) %s", func_name, string)
700 @brief Print a warning message (using logger).
702 @param string The message to print.
703 @param func_name The name of the calling function.
705 if isinstance(func_name, str)
is False or func_name
is None:
706 _func_name = inspect.currentframe()
707 if _func_name.f_back
is not None:
708 func_name = _func_name.f_back.f_code.co_name
710 func_name = _func_name.f_code.co_name
711 self.
logger.warning(
"(%s) %s", func_name, string)
715 @brief Print an error message (using logger).
717 @param string The message to print.
718 @param func_name The name of the calling function.
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
725 func_name = _func_name.f_code.co_name
730 @brief Print a critical message (using logger).
732 @param string The message to print.
733 @param func_name The name of the calling function.
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
740 func_name = _func_name.f_code.co_name
741 self.
logger.critical(
"(%s) %s", func_name, string)
743 def log_custom_level(self, level: Union[int, str], string: str, func_name: Union[str,
None] =
None) ->
None:
745 @brief Log a message with a custom level.
747 @param level The custom level to use.
748 @param string The message to log.
749 @param func_name The name of the calling function.
751 if isinstance(func_name, str)
is False or func_name
is None:
752 _func_name = inspect.currentframe()
753 if _func_name.f_back
is not None:
754 func_name = _func_name.f_back.f_code.co_name
756 func_name = _func_name.f_code.co_name
759 def log_debug(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
761 @brief Log a debug message.
763 @param string The message to log.
764 @param func_name The name of the calling function.
768 if isinstance(func_name, str)
is False or func_name
is None:
769 _func_name = inspect.currentframe()
770 if _func_name.f_back
is not None:
771 func_name = _func_name.f_back.f_code.co_name
773 func_name = _func_name.f_code.co_name
776 def log_info(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
778 @brief Log an info message.
780 @param string The message to log.
781 @param func_name The name of the calling function.
783 if isinstance(func_name, str)
is False or func_name
is None:
784 _func_name = inspect.currentframe()
785 if _func_name.f_back
is not None:
786 func_name = _func_name.f_back.f_code.co_name
788 func_name = _func_name.f_code.co_name
791 def log_warning(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
793 @brief Log a warning message.
795 @param string The message to log.
796 @param func_name The name of the calling function.
798 if isinstance(func_name, str)
is False or func_name
is None:
799 _func_name = inspect.currentframe()
800 if _func_name.f_back
is not None:
801 func_name = _func_name.f_back.f_code.co_name
803 func_name = _func_name.f_code.co_name
806 def log_error(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
808 @brief Log an error message.
810 @param string The message to log.
811 @param func_name The name of the calling function.
813 if isinstance(func_name, str)
is False or func_name
is None:
814 _func_name = inspect.currentframe()
815 if _func_name.f_back
is not None:
816 func_name = _func_name.f_back.f_code.co_name
818 func_name = _func_name.f_code.co_name
821 def log_critical(self, string: str =
"", func_name: Union[str,
None] =
None) ->
None:
823 @brief Log a critical message.
825 @param string The message to log.
826 @param func_name The name of the calling function.
828 if isinstance(func_name, str)
is False or func_name
is None:
829 _func_name = inspect.currentframe()
830 if _func_name.f_back
is not None:
831 func_name = _func_name.f_back.f_code.co_name
833 func_name = _func_name.f_code.co_name
838 @brief Close the log file if it was opened.
843 "The file was not opened, no need to close it",
844 inspect.currentframe().f_code.co_name
852 @brief Return the generated string.
854 @return The generated content.
862 @brief Generate the required amount of spaces for the padding of the shape.
864 @param string_length The length of the provided string.
865 @return The number of spaces required for the padding.
870 calculated_length = int(
873 if calculated_length % 2 == 1
and calculated_length != 0:
874 calculated_length += 1
883 @brief Open the file if required and add the current date and time.
897 @brief Check if an item is safe to write or not.
899 @param content The item to check.
900 @return True if the item is safe to write, False otherwise.
902 if isinstance(content, (str, int, float, tuple, complex, bytes, bytearray, memoryview))
is False:
908 @brief Create a string based on a character and a length.
910 @param length The length of the string.
911 @param character The character to use.
912 @return The created string.
914 line = [character
for i
in range(0, length)]
915 string =
"".join(line)
920 @brief Print the message letter by letter while applying a provided delay.
922 @param message The message to display.
923 @param delay The delay between each letter.
926 for letter
in message.split(
" "):
927 sys.stdout.write(letter)
929 sys.stdout.write(
" ")
932 for letter
in message:
933 sys.stdout.write(letter)
937 sys.stdout.write(message)
940 def animate_message(self, message: str =
"Hello World!", delay: float = 0.02) ->
None:
942 @brief Display or dump (to file) a message.
944 @param message The message to display or dump.
945 @param delay The delay between each letter.
948 message = f
"{message}"
958 @brief Display a message in a box.
960 @param msg The message to display.
961 @param char The character to use for the box.
963 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
964 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
965 @example #############################\n
966 @example # Sample text #\n
967 @example #############################
974 lines = msg.split(
"\n")
976 string_length = len(i)
978 title_content += char
979 title_content += white_spaces
981 if string_length % 2 == 1
and string_length != 0:
982 white_spaces = white_spaces[:-1]
983 title_content += white_spaces
984 title_content += char
985 title_content +=
'\n'
987 string_length = len(msg)
990 title_content += char
991 title_content += white_spaces
993 if string_length % 2 == 1
and string_length != 0:
994 white_spaces = white_spaces[:-1]
995 title_content += white_spaces
996 title_content += char
997 title_content +=
"\n"
999 generated_content = f
"{box_wall}\n"
1000 generated_content += f
"{title_content}"
1001 generated_content += f
"{box_wall}"
1003 f
"{generated_content}",
1009 @brief Display a message in a rounded box.
1011 @param msg The message to display.
1013 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1014 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1015 @example ╔══════════════════════╗\n
1016 @example ║ Sample text ║\n
1017 @example ╚══════════════════════╝\n
1029 "The top left corner is not defined, using the default one",
1030 inspect.currentframe().f_code.co_name
1035 self.
nb_chr - offset_reset,
1041 "The horizontal line is not defined, using the default one",
1042 inspect.currentframe().f_code.co_name
1045 self.
nb_chr-offset_reset,
1053 "The top right corner is not defined, using the default one",
1054 inspect.currentframe().f_code.co_name
1065 "The bottom left corner is not defined, using the default one",
1066 inspect.currentframe().f_code.co_name
1072 self.
nb_chr-offset_reset,
1078 "The horizontal line is not defined, using the default one",
1079 inspect.currentframe().f_code.co_name
1082 self.
nb_chr-offset_reset,
1090 "The bottom right corner is not defined, using the default one",
1091 inspect.currentframe().f_code.co_name
1095 border_character =
""
1101 "The vertical line is not defined, using the default one",
1102 inspect.currentframe().f_code.co_name
1104 border_character =
"║"
1108 lines = msg.split(
"\n")
1110 string_length = len(i)
1112 center_content += border_character
1113 center_content += white_spaces
1115 if string_length % 2 == 1
and string_length != 0:
1116 white_spaces = white_spaces[:-1]
1117 center_content += white_spaces
1118 center_content += border_character
1119 center_content +=
'\n'
1121 string_length = len(msg)
1123 center_content += border_character
1124 center_content += white_spaces
1125 center_content += msg
1126 if string_length % 2 == 1
and string_length != 0:
1127 white_spaces = white_spaces[:-1]
1128 center_content += white_spaces
1129 center_content += border_character
1130 center_content +=
"\n"
1132 generated_content = f
"{top_wall}\n"
1133 generated_content += f
"{center_content}"
1134 generated_content += f
"{bottom_wall}"
1136 f
"{generated_content}",
1142 @brief Display a message in a box with different side and top characters.
1144 @param msg The message to display.
1146 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1147 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1148 @example _____________________________\n
1149 @example | Sample text |\n
1150 @example _____________________________
1158 "The ceiling boxes are not defined, using the default one",
1159 inspect.currentframe().f_code.co_name
1163 border_character =
""
1169 "The border character is not defined, using the default one",
1170 inspect.currentframe().f_code.co_name
1172 border_character =
"|"
1178 lines = msg.split(
"\n")
1180 string_length = len(i)
1182 title_content += border_character
1183 title_content += white_spaces
1185 if string_length % 2 == 1
and string_length != 0:
1186 white_spaces = white_spaces[:-1]
1187 title_content += white_spaces
1188 title_content += border_character
1189 title_content +=
'\n'
1191 string_length = len(msg)
1193 title_content += border_character
1194 title_content += white_spaces
1195 title_content += msg
1196 if string_length % 2 == 1
and string_length != 0:
1197 white_spaces = white_spaces[:-1]
1198 title_content += white_spaces
1199 title_content += border_character
1200 title_content +=
"\n"
1202 generated_content = f
"{box_wall}\n"
1203 generated_content += f
"{title_content}"
1204 generated_content += f
"{box_wall}"
1206 f
"{generated_content}",
1212 @brief Print a box format without internal vertical bars.
1214 @param message The message to display.
1215 @param character The character to use for the box.
1216 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1217 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1218 @example #############################\n
1219 @example Sample text \n
1220 @example #############################
1227 "The box character is not defined, using the default one",
1228 inspect.currentframe().f_code.co_name
1236 lines = message.split(
"\n")
1238 string_length = len(i)
1240 title_content += white_spaces
1242 if string_length % 2 == 1
and string_length != 0:
1243 white_spaces = white_spaces[:-1]
1244 title_content += white_spaces
1245 title_content +=
'\n'
1247 string_length = len(message)
1249 title_content += white_spaces
1250 title_content += message
1251 if string_length % 2 == 1
and string_length != 0:
1252 white_spaces = white_spaces[:-1]
1253 title_content += white_spaces
1254 title_content +=
"\n"
1256 generated_content = f
"{box_wall}\n"
1257 generated_content += f
"{title_content}"
1258 generated_content += f
"{box_wall}"
1260 f
"{generated_content}",
1266 @brief Display a message in a box with vertical bars.
1268 @param msg The message to display.
1269 @param character The character to use for the box.
1270 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1271 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1272 @example ###############\n
1276 @example # Sample text #\n
1280 @example ###############
1285 elif character ==
'':
1288 "The box character is not defined, using the default one",
1289 inspect.currentframe().f_code.co_name
1297 lines = msg.split(
"\n")
1299 string_length = len(i)
1301 title_content += character
1302 title_content += white_spaces
1304 if string_length % 2 == 1
and string_length != 0:
1305 white_spaces = white_spaces[:-1]
1306 title_content += white_spaces
1307 title_content += character
1308 title_content +=
"\n"
1311 string_length = len(msg)
1313 title_content += character
1314 title_content += white_spaces
1315 title_content += msg
1316 if string_length % 2 == 1
and string_length != 0:
1317 white_spaces = white_spaces[:-1]
1318 title_content += white_spaces
1319 title_content += character
1320 title_content +=
"\n"
1327 inner_line = f
"{character}{inner_line}{character}"
1329 generated_content = f
"{box_wall}\n"
1331 max_height = (inner_length / 4) - len(msg)
1337 while i < max_height:
1338 generated_content += f
"{inner_line}\n"
1340 generated_content += f
"{title_content}"
1342 while i < max_height:
1343 generated_content += f
"{inner_line}\n"
1346 generated_content += f
"{box_wall}"
1349 f
"{generated_content}",
1355 @brief Print a box format without internal horizontal bars.
1357 @param message The message to display.
1358 @param character The character to use for the box.
1359 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1360 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1366 @example # Sample text #\n
1375 elif character ==
'':
1378 "The box character is not defined, using the default one",
1379 inspect.currentframe().f_code.co_name
1385 lines = message.split(
"\n")
1387 string_length = len(i)
1389 title_content += character
1390 title_content += white_spaces
1392 if string_length % 2 == 1
and string_length != 0:
1393 white_spaces = white_spaces[:-1]
1394 title_content += white_spaces
1395 title_content += character
1396 title_content +=
"\n"
1399 string_length = len(message)
1401 title_content += character
1402 title_content += white_spaces
1403 title_content += message
1404 if string_length % 2 == 1
and string_length != 0:
1405 white_spaces = white_spaces[:-1]
1406 title_content += white_spaces
1407 title_content += character
1408 title_content +=
"\n"
1416 inner_line = f
"{character}{inner_line}{character}"
1418 generated_content =
""
1420 max_height = (inner_length / 4) - len(message)
1426 while i < max_height:
1427 generated_content += f
"{inner_line}\n"
1429 generated_content += f
"{title_content}"
1431 while i < max_height:
1432 if i+1 >= max_height:
1433 generated_content += f
"{inner_line}"
1435 generated_content += f
"{inner_line}\n"
1439 f
"{generated_content}",
1445 @brief Print a beautified title.
1447 @param title The title to display.
1453 @brief Print a beautified subtitle.
1455 @param sub_title The subtitle to display.
1461 @brief Print a beautified sub-subtitle.
1463 @param sub_sub_title The sub-subtitle to display.
1467 def message(self, message: Union[str, list]) ->
None:
1469 @brief Print a beautified message.
1471 @param message The message to display.
1472 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1473 @note Here is an example for the output (This is determined by the key repeated twice)\n
1474 @example @@ This is an example message @@
1476 if isinstance(message, list)
is True:
1478 m_msg = f
"{self.message_char}{self.message_char} {msg} "
1479 m_msg += f
"{self.message_char}{self.message_char}"
1485 msg = f
"{self.message_char}{self.message_char} "
1486 msg += f
" {message} {self.message_char}{self.message_char}"
1494 @brief Print a beautified error message.
1496 @param message The error message to display.
1497 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1498 @note Here is an example for the output (This is determined by the key repeated twice)\n
1499 @example ## This is an example message ##
1501 if isinstance(message, list)
is True:
1502 m_msg = f
"{self.message_error_char}{self.message_error_char} Error: "
1503 m_msg += f
"{self.message_error_char}{self.message_error_char}"
1505 m_msg = f
"{self.message_error_char}{self.message_error_char} {msg} "
1506 m_msg += f
"{self.message_error_char}{self.message_error_char}"
1512 msg = f
"{self.message_error_char}{self.message_error_char} Error:"
1513 msg += f
" {message} {self.message_error_char}{self.message_error_char}"
1521 @brief Print a beautified success message.
1523 @param message The success message to display.
1524 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1525 @note Here is an example for the output (This is determined by the key repeated twice)\n
1526 @example // This is an example message //
1528 if isinstance(message, list)
is True:
1529 m_msg = f
"{self.message_success_char}{self.message_success_char} Success: "
1530 m_msg += f
"{self.message_success_char}{self.message_success_char}"
1532 m_msg = f
"{self.message_success_char}{self.message_success_char} {msg} "
1533 m_msg += f
"{self.message_success_char}{self.message_success_char}"
1539 msg = f
"{self.message_success_char}{self.message_success_char} Success:"
1540 msg += f
" {message} {self.message_success_char}{self.message_success_char}"
1548 @brief Print a beautified warning message.
1550 @param message The warning message to display.
1551 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1552 @note Here is an example for the output (This is determined by the key repeated twice)\n
1553 @example !! This is an example message !!
1555 if isinstance(message, list)
is True:
1556 m_msg = f
"{self.message_warning_char}{self.message_warning_char} Warning: "
1557 m_msg += f
"{self.message_warning_char}{self.message_warning_char}"
1559 m_msg = f
"{self.message_warning_char}{self.message_warning_char} {msg} "
1560 m_msg += f
"{self.message_warning_char}{self.message_warning_char}"
1566 msg = f
"{self.message_warning_char}{self.message_warning_char} Warning:"
1567 msg += f
" {message} {self.message_warning_char}{self.message_warning_char}"
1575 @brief Print a beautified question message.
1577 @param message The question message to display.
1578 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1579 @note Here is an example for the output (This is determined by the key repeated twice)\n
1580 @example ?? This is an example message ??
1582 if isinstance(message, list)
is True:
1583 m_msg = f
"{self.message_question_char}{self.message_question_char} Question: "
1584 m_msg += f
"{self.message_question_char}{self.message_question_char}"
1586 m_msg = f
"{self.message_question_char}{self.message_question_char} {msg} "
1587 m_msg += f
"{self.message_question_char}{self.message_question_char}"
1593 msg = f
"{self.message_question_char}{self.message_question_char} Question:"
1594 msg += f
" {message} {self.message_question_char}{self.message_question_char}"
1602 @brief Print a beautified information message.
1604 @param message The information message to display.
1605 @note This function displays the provided message using the 'MESSAGE_CHARACTER' key in the toml configuration\n
1606 @note Here is an example for the output (This is determined by the key repeated twice)\n
1607 @example ii This is an example message ii
1609 if isinstance(message, list)
is True:
1611 m_msg = f
"{self.message_inform_char}{self.message_inform_char} {msg} "
1612 m_msg += f
"{self.message_inform_char}{self.message_inform_char}"
1618 msg = f
"{self.message_inform_char}{self.message_inform_char} {message} "
1619 msg += f
"{self.message_inform_char}{self.message_inform_char}"
1625 def _tree_node(self, line: str, offset: int, index: int, max_lenght: int) -> str:
1627 @brief Display a line of the tree.
1629 @param line The line to display.
1630 @param offset The offset for the line.
1631 @param index The index of the line.
1632 @param max_lenght The maximum length of the tree.
1633 @return The processed line.
1634 @note The characters displayed in this tree function is managed by the following keys:\n
1635 @note * TREE_NODE_CHAR\n
1636 @note * TREE_NODE_END_CHAR\n
1637 @note * TREE_LINE_SEPERATOR_CHAR\n
1638 @note * TREE_COLUMN_SEPERATOR_CHAR\n
1639 @example Here is an example generated by this function:\n
1640 @example ├─── data1\n
1643 processed_line = str()
1646 processed_line += f
"{self.tree_column_seperator_char} "
1648 if index
is max_lenght:
1649 processed_line += f
"{self.tree_node_end_char}{self.tree_line_seperator_char}"
1650 processed_line += f
"{self.tree_line_seperator_char}{self.tree_line_seperator_char}"
1652 processed_line += f
"{self.tree_node_char}{self.tree_line_seperator_char}"
1653 processed_line += f
"{self.tree_line_seperator_char}{self.tree_line_seperator_char}"
1656 processed_line +=
" "
1657 processed_line += line
1658 processed_line +=
'\n'
1659 return processed_line
1661 def tree(self, title: str, data: List[str], offset: int = 0) -> Union[str,
None]:
1663 @brief Print a list under the form of a beautified tree.
1665 @param title The title of the tree.
1666 @param data The data to display in the tree.
1667 @param offset The offset for the tree.
1668 @note The characters displayed in this tree function is managed by the following keys:\n
1669 @note * TREE_NODE_CHAR\n
1670 @note * TREE_NODE_END_CHAR\n
1671 @note * TREE_LINE_SEPERATOR_CHAR\n
1672 @note * TREE_COLUMN_SEPERATOR_CHAR\n
1673 @example Here is an example generated by this function:\n
1674 @example ├─── data1\n
1676 @return A stringified version of the tree if not set to be displayed.
1678 generated_content =
""
1680 generated_content += f
"{title}\n"
1681 length = len(data) - 1
1683 for line
in enumerate(data):
1684 if isinstance(data, list)
and isinstance(line[1], (list, dict)):
1691 generated_content += self.
tree(line[0], line[1], offset + 1)
1693 if isinstance(data, dict)
and isinstance(data[line[1]], (list, dict)):
1700 generated_content += self.
tree(
1706 if isinstance(data, dict)
and isinstance(data[line[1]], dict)
is False:
1708 f
"{line[1]}: {data[line[1]]}",
1722 f
"{generated_content}",
1726 return generated_content
1730 @brief Add the date and time at which the program was launched.
1731 @note The text is displayed in the center of the box, it is just difficult to show that in a function comment.\n
1732 @note This is a sample box (characters and dimensions depend on the provided configuration):\n
1733 @example ########################################\n
1734 @example # Run date: 07/06/2024 22:26:10 #\n
1735 @example ########################################
1737 self.
title(f
"Run date: {time.strftime('%d/%m/%Y %H:%M:%S')} ")
1741 @brief Test function to ensure all implemented methods work as expected.
1744 "test_data1":
"test_data1.1",
1745 "test_data2":
"test_data2.1",
1747 "test_data_list3.1",
1748 "test_data_list3.2",
1749 "test_data_list3.3",
1750 "test_data_list3.4",
1753 "test_data4":
"test_data4.1",
1755 "test_data5.1":
"test_data5.1.1",
1756 "test_data5.2":
"test_data5.2.1",
1757 "test_data5.3":
"test_data5.3.1",
1758 "test_data5.4":
"test_data5.4.1"
1762 "test_data6.1":
"test_data6.1.1",
1763 "test_data6.2":
"test_data6.2.1"
1766 "test_data_list6.3.1",
1767 "test_data_list6.3.1",
1768 "test_data_list6.3.1",
1769 "test_data_list6.3.1"
1774 "test_data7.1.1":
"test_data7.1.1.1",
1775 "test_data7.1.2":
"test_data7.1.2.1"
1793 self.
title(
"Test title")
1799 "Test Disp diff side and top message box"
1803 self.
tree(
"Test data", test_data)
1813 logging.WARNING,
"This is a test warning for custom level messages"
1815 custom_level_int = 2
1816 level_name =
"DARLING"
1824 f
"The custom level '{level_name}' could not be added, please check the configuration"
1829 f
"This is a test for custom level message \"{logging.getLevelName(custom_level_int)}\""
1831 custom_level_int = 196
1832 level_name =
"Ikuno"
1840 f
"The custom level '{level_name}' could not be added, please check the configuration"
1845 f
"This is a test for custom level message \"{logging.getLevelName(custom_level_int)}\""
1850if __name__ ==
"__main__":
1852 toml_content=TOML_CONF,
1854 file_name=
"test_run.tmp",
1855 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)