Display TTY  1
Customise your terminal's output
Loading...
Searching...
No Matches
my_disp.py
Go to the documentation of this file.
1"""
2# +==== BEGIN display_tty =================+
3# LOGO:
4# ..@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
5# .@...........................#@
6# @############################.@
7# @...........................@.@
8# @..#######################..@.@
9# @.#########################.@.@
10# @.##>_#####################.@.@
11# @.#########################.@.@
12# @.#########################.@.@
13# @.#########################.@.@
14# @.#########################.@.@
15# @..#######################..@.@
16# @...........................@.@
17# @..+----+______________.....@.@
18# @..+....+______________+....@.@
19# @..+----+...................@.@
20# @...........................@.#
21# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@#.
22# /STOP
23# PROJECT: display_tty
24# FILE: my_disp.py
25# CREATION DATE: 06-11-2025
26# LAST Modified: 12:22:40 06-11-2025
27# DESCRIPTION:
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.
29# @file my_disp.py
30# @brief The file in charge of managing the beautified output on the terminal.
31# /STOP
32# COPYRIGHT: (c) Henry Letellier
33# PURPOSE: File in charge of holding the actual disp class.
34# // AR
35# +==== END display_tty =================+
36"""
37
38import time
39import sys
40import inspect
41from typing import List, Dict, Union, Any
42import colorlog
43import logging
44
45
46# Check if the script is being run directly or imported
47try:
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
51except ImportError:
52 try:
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:
57 raise RuntimeError(
58 "Display TTY: Failed to import required dependencies"
59 ) from e
60
61
62class Logging:
63 """
64 @class Logging
65 @brief Represents a placeholder for the logging library. This is not a functioning class.
66 """
67
68 def __init__(self) -> None:
69 pass
70
71
72class Disp:
73 """
74 @class Disp
75 @brief The class in charge of displaying messages with various styles and animations.
76
77 @details This class provides methods to display messages in different formats, log messages, and manage output configurations.
78 """
79
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:
81 """
82 @brief Constructor for the Disp class.
83
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.
94 """
95 self.__version__ = "1.0.0"
96 self.toml_contenttoml_content = toml_content
97 self.background_colour_key = 'background_colour'
98 self.author = "(c) Created by Henry Letellier"
99 self.nb_chr = 40
100 self.debugdebug = debug
101 self.error = error
102 self.success = success
104 self.log_error_when_present = log_errors_when_present
105 self.log_warning_when_present = log_warning_when_present
108 self.title_wall_chr = self.toml_contenttoml_content["TITLE_WALL_CHARACTER"]
109 self.sub_title_wall_chr = self.toml_contenttoml_content["SUB_TITLE_WALL_CHARACTER"]
110 self.sub_sub_title_wall_chr = self.toml_contenttoml_content["SUB_SUB_TITLE_WALL_CHARACTER"]
111 self.message_char = self.toml_contenttoml_content["MESSAGE_CHARACTER"]
112 self.message_error_char = self.toml_contenttoml_content["MESSAGE_ERROR_CHARACTER"]
113 self.message_success_char = self.toml_contenttoml_content["MESSAGE_SUCCESS_CHARACTER"]
114 self.message_inform_char = self.toml_contenttoml_content["MESSAGE_INFORM_CHARACTER"]
115 self.message_warning_char = self.toml_contenttoml_content["MESSAGE_WARNING_CHARACTER"]
116 self.message_question_char = self.toml_contenttoml_content["MESSAGE_QUESTION_CHARACTER"]
117 if self.toml_contenttoml_content[KEY_PRETTIFY_OUTPUT_IN_BLOCKS] is True:
118 self.message_animation_delay = self.toml_contenttoml_content[KEY_ANIMATION_DELAY_BLOCKY]
119 else:
120 self.message_animation_delay = self.toml_contenttoml_content[KEY_ANIMATION_DELAY]
121 self.tree_node_char = self.toml_contenttoml_content["TREE_NODE_CHAR"]
122 self.tree_node_end_char = self.toml_contenttoml_content["TREE_NODE_END_CHAR"]
123 self.tree_line_seperator_char = self.toml_contenttoml_content["TREE_LINE_SEPERATOR_CHAR"]
124 self.tree_column_seperator_char = self.toml_contenttoml_content["TREE_COLUMN_SEPERATOR_CHAR"]
125 self.file_name = file_name
126 self.save_to_file = save_to_file
127 if self.toml_contenttoml_content[KEY_OUTPUT_MODE] == OUT_FILE:
128 self.save_to_file = True
129 self.file_descriptor = file_descriptor
131 if self.toml_contenttoml_content[KEY_OUTPUT_MODE] not in (OUT_FILE, OUT_STRING, OUT_TTY, OUT_DEFAULT):
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)
135 if self.toml_contenttoml_content[KEY_OUTPUT_MODE] == OUT_FILE:
136 self._open_file()
137 self._setup_logger(logger)
138
139 def _setup_logger(self, logger: Union[Logging, str, None]) -> None:
140 """
141 @brief Setup the logger for the class.
142
143 @param logger The logger to use. If None, a default logger will be used.
144 """
145 # ---- Logging data ----
146 if callable(logger) and hasattr(logger, "debug"):
147 self.logger = logger
148 else:
149 if isinstance(logger, str) is True:
150 self.logger = logging.getLogger(logger)
151 else:
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%('
156 format_string += self.background_colour_key
157 format_string += '_log_color)s%(levelname)s%(reset)s %(name)s: \'%(message)s\''
158 formatter = colorlog.ColoredFormatter(
159 fmt=format_string,
160 datefmt=None,
161 reset=True,
162 style='%',
163 log_colors={
164 'DEBUG': 'cyan',
165 'INFO': 'green',
166 'WARNING': 'yellow',
167 'ERROR': 'red',
168 'CRITICAL': 'bold_red'
169 },
170 secondary_log_colors={
172 'DEBUG': 'bg_black',
173 'INFO': 'bg_black',
174 'WARNING': 'bg_black',
175 'ERROR': 'bg_black',
176 'CRITICAL': 'bg_black',
177 }
178 },
179 )
180 handler.setFormatter(formatter)
181 self.logger.addHandler(handler)
182 # 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.
183 self.update_logger_level(1)
184 node = LogLevelTracker()
185 if node.check_presence() is False:
186 node.inject_class()
187
188 def _create_function(self, name, func_code):
189 # Create a namespace for the function
190 namespace = {}
191
192 # Execute the code that defines the function
193 exec(func_code, globals(), namespace)
194
195 # Extract the function from namespace
196 func = namespace[name]
197 return func
198
199 def _add_function_to_instance(self, func_dest: object, func_name: str, func_code: str) -> None:
200 """
201 @brief Add a dynamically created function to an instance.
202
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.
206 """
207 function_instance = self._create_function(func_name, func_code)
208 setattr(func_dest, func_name, function_instance)
209
210 def update_disp_debug(self, debug: bool) -> None:
211 """
212 @brief Update the debug mode.
213
214 @param debug Boolean indicating whether debug mode is enabled.
215 """
216 self.debugdebug = debug
217
218 def update_logger_level(self, level: Union[int, LogLevelTracker.Levels] = LogLevelTracker.Levels.NOTSET) -> None:
219 """
220 @brief Update the logger level.
221
222 @param level The log importance level. Defaults to NOTSET.
223
224 @return The status code of the operation.
225 """
226 _func_name = inspect.currentframe().f_code.co_name
227
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
234 if self.log_warning_when_present is True:
235 self.log_warning(
236 f"The level is not valid, defaulting to {level}",
237 _func_name
238 )
239 self.logger.setLevel(level)
240
241 def _check_the_logging_instance(self, logger_instance: logging.Logger = None) -> logging.Logger:
242 """
243 @brief Check if the logger instance is valid.
244
245 @param logger_instance The logger instance to validate.
246 @return A valid logger instance.
247 """
248 # Get the parent function name if present
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
252 else:
253 _func_name = _func_name.f_code.co_name
254
255 # Checking the logger instance
256 if logger_instance is None or not isinstance(logger_instance, logging.Logger):
257 if self.log_warning_when_present is True:
258 self.log_warning(
259 "No logger instance provided, using the default logger",
260 _func_name
261 )
262 logger_instance = self.logger
263 return logger_instance
264
265 def _check_colour_data(self, colour: Union[str, int], logger_instance: logging.Logger = None) -> Union[int, str]:
266 """
267 @brief Check if the provided colour data is valid.
268
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.
272 """
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
276 else:
277 _func_name = _func_name.f_code.co_name
278
279 # Checking if the colour exists
280 if isinstance(colour, int):
281 colour = LoggerColours.get_colour_string(LoggerColours, colour)
282 if LoggerColours.check_if_colour_present(LoggerColours, colour) is False:
283 if self.log_error_when_present is True:
284 self.log_error(
285 "The provided colour is not valid",
286 _func_name
287 )
288 return self.error
289 return colour
290
291 def _check_level_data(self, level_name: Union[str, int], logger_instance: logging.Logger = None) -> Union[int, str]:
292 """
293 @brief Check if the provided level data is valid.
294
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.
298 """
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
302 else:
303 _func_name = _func_name.f_code.co_name
304
305 # Check if there are any handlers in use.
306 if len(logger_instance.handlers) == 0:
307 if self.log_error_when_present is True:
308 self.log_error(
309 'No handlers are present in this logging instance',
310 _func_name
311 )
312 return self.error
313
314 # Checking for the presence of the logging level in the logger 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)
319 else:
320 if self.log_error_when_present is True:
321 self.log_error(
322 "The level name must be a string or an integer",
323 _func_name
324 )
325 return self.error
326 return name_string
327
328 def _get_colour_formatter(self, logger_instance: logging.Logger = None) -> Union[None, colorlog.ColoredFormatter]:
329 """
330 @brief Get the colour formatter from the logger instance.
331
332 @param logger_instance The logger instance to retrieve the formatter from.
333 @return The colour formatter or an error code.
334 """
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
338 else:
339 _func_name = _func_name.f_code.co_name
340
341 # iterate through the handlers to find the colour handler
342 colour_handler = None
343 for i in logger_instance.handlers:
344 if isinstance(i, colorlog.StreamHandler):
345 colour_handler = i
346 break
347 if not colour_handler:
348 if self.log_error_when_present is True:
349 self.log_error(
350 'No colour handler is present in this logging instance',
351 _func_name
352 )
353 return self.error
354
355 # Check if the colour is a string or an integer
356 if hasattr(colour_handler, "formatter") is False:
357 if self.log_error_when_present is True:
358 self.log_error(
359 'The colour handler has no formatter',
360 _func_name
361 )
362 return self.error
363 colour_formatter: colorlog.ColoredFormatter = colour_handler.formatter
364 if isinstance(colour_formatter, colorlog.ColoredFormatter) is False:
365 if self.log_error_when_present is True:
366 self.log_error(
367 'The formatter is not a ColoredFormatter',
368 _func_name
369 )
370 return self.error
371 if hasattr(colour_formatter, "log_colors") is False:
372 if self.log_error_when_present is True:
373 self.log_error(
374 'The formatter has no log_colors',
375 _func_name
376 )
377 return self.error
378 return colour_formatter
379
380 def update_logging_colour_text(self, colour: Union[str, int], level_name: Union[str, int], logger_instance: logging.Logger = None) -> int:
381 """
382 @brief Update or insert a logging colour for the text of the specified level.
383
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.
388 """
389 _func_name = inspect.currentframe().f_code.co_name
390 # Checking the logger instance
391 logger_instance = self._check_the_logging_instance(logger_instance)
392
393 name_string = self._check_level_data(
394 level_name,
395 logger_instance
396 )
397 if name_string == self.error:
398 if self.log_error_when_present is True:
399 self.log_error(
400 f"The level name {level_name} is not valid",
401 _func_name
402 )
403 return self.error
404
405 name_string = name_string.upper()
406
407 # Checking if the colour exists
408 colour_input = colour
409 colour = self._check_colour_data(
410 colour,
411 logger_instance
412 )
413 if colour == self.error:
414 if self.log_error_when_present is True:
415 self.log_error(
416 f"The colour {colour_input} is not valid",
417 _func_name
418 )
419 return self.error
420
421 internal_log_colors = self._get_colour_formatter(logger_instance)
422 if internal_log_colors == self.error or internal_log_colors is None:
423 if self.log_error_when_present is True:
424 self.log_error(
425 'The colour logging library is not valid',
426 _func_name
427 )
428 return self.error
429 lib_log_colors = internal_log_colors.log_colors
430 if isinstance(lib_log_colors, dict) is False:
431 if self.log_error_when_present is True:
432 self.log_error(
433 'The log_colors is not a dictionary',
434 _func_name
435 )
436 return self.error
437 for i in lib_log_colors:
438 if i.upper() == name_string:
439 lib_log_colors[i] = colour
440 return self.success
441 lib_log_colors[name_string.upper()] = colour
442 return self.success
443
444 def update_logging_colour_background(self, colour: Union[str, int], level_name: Union[str, int], logger_instance: logging.Logger = None) -> int:
445 """
446 @brief Update or insert a logging colour for the background of the specified level.
447
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.
452 """
453 _func_name = inspect.currentframe().f_code.co_name
454 # Checking the logger instance
455 logger_instance = self._check_the_logging_instance(logger_instance)
456
457 name_string = self._check_level_data(
458 level_name,
459 logger_instance
460 )
461 if name_string == self.error:
462 if self.log_error_when_present is True:
463 self.log_error(
464 f"The level name {level_name} is not valid",
465 _func_name
466 )
467 return self.error
468 name_string = name_string.upper()
469
470 # Checking if the colour exists
471 colour_input = colour
472 colour = self._check_colour_data(
473 colour,
474 logger_instance
475 )
476 if colour == self.error:
477 if self.log_error_when_present is True:
478 self.log_error(
479 f"The colour {colour_input} is not valid",
480 _func_name
481 )
482 return self.error
483
484 # Checking if the colour is a background colour
485 if colour.startswith("bg_") is False:
486 colour = "bg_" + colour
487
488 secondary_log_colors = self._get_colour_formatter(logger_instance)
489 if secondary_log_colors == self.error:
490 if self.log_error_when_present is True:
491 self.log_error(
492 'The secondary_log_colors is not valid',
493 _func_name
494 )
495 return self.error
496 secondary_log_colors = secondary_log_colors.secondary_log_colors
497 if isinstance(secondary_log_colors, dict) is False:
498 if self.log_error_when_present is True:
499 self.log_error(
500 'The secondary_log_colors is not a dictionary',
501 _func_name
502 )
503 return self.error
504
505 # Add the level and colour to the secondary log colours
506 if hasattr(secondary_log_colors, self.background_colour_key) is False:
507 secondary_log_colors[self.background_colour_key] = {
508 name_string.upper(): colour
509 }
510 return self.success
511 for i in secondary_log_colors[self.background_colour_key]:
512 if i.upper() == name_string:
513 secondary_log_colors[self.background_colour_key][i.upper(
514 )] = colour
515 return self.success
516 secondary_log_colors[
518 ][name_string.upper()] = colour
519 return self.success
520
521 def add_custom_level(self, level: int, name: str, colour_text: Union[int, str] = "", colour_bg: Union[int, str] = "") -> int:
522 """
523 @brief Add a custom level to the logger.
524
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.
530 """
531 _func_name = inspect.currentframe().f_code.co_name
532 logger = self._check_the_logging_instance(self.logger)
533 # Check if the level is already taken
534 if level in FORBIDDEN_NUMBER_LOG_LEVELS:
535 if self.log_error_when_present is True:
536 self.log_error(
537 f"The provided level is forbidden because already taken '{level}'",
538 _func_name
539 )
540 return self.error
541 if name in FORBIDDEN_NUMBER_LOG_LEVELS_CORRESPONDANCE:
542 if self.log_error_when_present is True:
543 self.log_error(
544 f"The provided name is forbidden because already taken '{name}'",
545 _func_name
546 )
547 return self.error
548 # Add the level to the logger
549 logging.addLevelName(level, name.upper())
550 if hasattr(logging.getLogger(), "log_level_tracker") is False:
551 if self.log_warning_when_present is True:
552 self.log_warning(
553 "The log level tracker is not present, adding",
554 _func_name
555 )
556 logging.getLogger().log_level_tracker = self.log_level_trackerlog_level_tracker
557 else:
558 self.log_level_trackerlog_level_tracker = logging.getLogger().log_level_tracker
559 if logging.getLogger().log_level_tracker.add_level(name, level) is False:
560 if self.log_error_when_present is True:
561 self.log_warning(
562 "The level could not be added to the log level tracker",
563 _func_name
564 )
565 return self.error
566 # Check the colours
567 if colour_text != "" or colour_text < 0:
568 colour_text_status = self.update_logging_colour_text(
569 colour_text,
570 level,
571 logger
572 )
573 if colour_text_status == self.error:
574 if self.log_error_when_present is True:
575 self.log_warning(
576 "The colour for the text could not be set",
577 _func_name
578 )
579 if colour_bg != "" or colour_bg < 0:
580 colour_bg_status = self.update_logging_colour_background(
581 colour_bg,
582 level,
583 logger
584 )
585 if colour_bg_status == self.error:
586 if self.log_error_when_present is True:
587 self.log_warning(
588 "The colour for the background could not be set",
589 _func_name
590 )
591
592 # generate the function name
593 func_name = name.lower()
594# # Generate the function for the logger
595# function_code = f"""
596# def {func_name}(self, message: str):
597# self.logger.log({level}, message)
598# """
599# self._add_function_to_instance(
600# logger,
601# func_name,
602# function_code
603# )
604 # Generate the function for the display class
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
612 else:
613 func_name = _func_name.f_code.co_name
614 self.log_custom_level({level}, string, func_name)
615"""
617 self,
618 func_disp_name,
619 function_disp_code
620 )
621 # Generate the shorthand name for the display class
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
629 else:
630 _func_name = _func_name.f_code.co_name
631 self.log_custom_level({level}, string, _func_name)
632"""
634 self,
635 func_log_name,
636 function_disp_short_code
637 )
638 return self.success
639
640 def disp_print_custom_level(self, level: Union[int, str], string: str, func_name: Union[str, None] = None) -> None:
641 """
642 @brief Print a message with a custom level.
643
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.
647 """
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
652 else:
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)
657 if level is None:
658 self.logger.error(
659 "The provided level is not valid"
660 )
661 return
662 if self.logger.isEnabledFor(level):
663 self.logger.log(level, "(%s) %s", func_name, string)
664
665 def disp_print_debug(self, string: str = "", func_name: Union[str, None] = None) -> None:
666 """
667 @brief Print a debug message (using logger).
668
669 @param string The message to print.
670 @param func_name The name of the calling function.
671 """
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
676 else:
677 func_name = _func_name.f_code.co_name
678 if self.debugdebug is True:
679 self.logger.debug("(%s) %s", func_name, string)
680
681 def disp_print_info(self, string: str = "", func_name: Union[str, None] = None) -> None:
682 """
683 @brief Print an information message (using logger).
684
685 @param string The message to print.
686 @param func_name The name of the calling function.
687 """
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
692 else:
693 func_name = _func_name.f_code.co_name
694 self.logger.info("(%s) %s", func_name, string)
695
696 def disp_print_warning(self, string: str = "", func_name: Union[str, None] = None) -> None:
697 """
698 @brief Print a warning message (using logger).
699
700 @param string The message to print.
701 @param func_name The name of the calling function.
702 """
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
707 else:
708 func_name = _func_name.f_code.co_name
709 self.logger.warning("(%s) %s", func_name, string)
710
711 def disp_print_error(self, string: str = "", func_name: Union[str, None] = None) -> None:
712 """
713 @brief Print an error message (using logger).
714
715 @param string The message to print.
716 @param func_name The name of the calling function.
717 """
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
722 else:
723 func_name = _func_name.f_code.co_name
724 self.logger.error("(%s) %s", func_name, string)
725
726 def disp_print_critical(self, string: str = "", func_name: Union[str, None] = None) -> None:
727 """
728 @brief Print a critical message (using logger).
729
730 @param string The message to print.
731 @param func_name The name of the calling function.
732 """
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
737 else:
738 func_name = _func_name.f_code.co_name
739 self.logger.critical("(%s) %s", func_name, string)
740
741 def log_custom_level(self, level: Union[int, str], string: str, func_name: Union[str, None] = None) -> None:
742 """
743 @brief Log a message with a custom level.
744
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.
748 """
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
753 else:
754 func_name = _func_name.f_code.co_name
755 self.disp_print_custom_level(level, string, func_name)
756
757 def log_debug(self, string: str = "", func_name: Union[str, None] = None) -> None:
758 """
759 @brief Log a debug message.
760
761 @param string The message to log.
762 @param func_name The name of the calling function.
763 """
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
768 else:
769 func_name = _func_name.f_code.co_name
770 self.disp_print_debug(string, func_name)
771
772 def log_info(self, string: str = "", func_name: Union[str, None] = None) -> None:
773 """
774 @brief Log an info message.
775
776 @param string The message to log.
777 @param func_name The name of the calling function.
778 """
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
783 else:
784 func_name = _func_name.f_code.co_name
785 self.disp_print_info(string, func_name)
786
787 def log_warning(self, string: str = "", func_name: Union[str, None] = None) -> None:
788 """
789 @brief Log a warning message.
790
791 @param string The message to log.
792 @param func_name The name of the calling function.
793 """
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
798 else:
799 func_name = _func_name.f_code.co_name
800 self.disp_print_warning(string, func_name)
801
802 def log_error(self, string: str = "", func_name: Union[str, None] = None) -> None:
803 """
804 @brief Log an error message.
805
806 @param string The message to log.
807 @param func_name The name of the calling function.
808 """
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
813 else:
814 func_name = _func_name.f_code.co_name
815 self.disp_print_error(string, func_name)
816
817 def log_critical(self, string: str = "", func_name: Union[str, None] = None) -> None:
818 """
819 @brief Log a critical message.
820
821 @param string The message to log.
822 @param func_name The name of the calling function.
823 """
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
828 else:
829 func_name = _func_name.f_code.co_name
830 self.disp_print_critical(string, func_name)
831
832 def close_file(self) -> None:
833 """
834 @brief Close the log file if it was opened.
835 """
836 if self.toml_contenttoml_content[KEY_OUTPUT_MODE] != OUT_FILE:
837 if self.log_warning_when_present is True:
838 self.log_warning(
839 "The file was not opened, no need to close it",
840 inspect.currentframe().f_code.co_name
841 )
842 return
843 if self.file_descriptor is not None:
844 self.file_descriptor.close()
845
846 def get_generated_content(self) -> str:
847 """
848 @brief Return the generated string.
849
850 @return The generated content.
851 """
852 data = self.generated_content
853 self.generated_content = ""
854 return data
855
856 def _calculate_required_spaces(self, string_length: int) -> str:
857 """
858 @brief Generate the required amount of spaces for the padding of the shape.
859
860 @param string_length The length of the provided string.
861 @return The number of spaces required for the padding.
862 """
863 if string_length >= self.max_whitespace:
864 white_spaces = " "
865 else:
866 calculated_length = int(
867 (self.max_whitespace - string_length)/2
868 )
869 if calculated_length % 2 == 1 and calculated_length != 0:
870 calculated_length += 1
871 white_spaces = self.create_string(
872 calculated_length,
873 " "
874 )
875 return white_spaces
876
877 def _open_file(self) -> None:
878 """
879 @brief Open the file if required and add the current date and time.
880 """
881 if self.save_to_file is True and self.file_descriptor is None:
882 self.file_descriptor = open(
883 self.file_name,
884 "a",
885 encoding="utf-8",
886 newline="\n"
887 )
888 if self.file_descriptor is not None:
889 self.append_run_date()
890
891 def _is_safe(self, content: Any) -> bool:
892 """
893 @brief Check if an item is safe to write or not.
894
895 @param content The item to check.
896 @return True if the item is safe to write, False otherwise.
897 """
898 if isinstance(content, (str, int, float, tuple, complex, bytes, bytearray, memoryview)) is False:
899 return False
900 return True
901
902 def create_string(self, length, character) -> str:
903 """
904 @brief Create a string based on a character and a length.
905
906 @param length The length of the string.
907 @param character The character to use.
908 @return The created string.
909 """
910 line = [character for i in range(0, length)]
911 string = "".join(line)
912 return string
913
914 def display_animation(self, message: str = "Hello World!", delay: float = 0.02) -> None:
915 """
916 @brief Print the message letter by letter while applying a provided delay.
917
918 @param message The message to display.
919 @param delay The delay between each letter.
920 """
921 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:
922 for letter in message.split(" "):
923 sys.stdout.write(letter)
924 sys.stdout.flush()
925 sys.stdout.write(" ")
926 time.sleep(delay)
927 elif self.toml_contenttoml_content[KEY_PRETTIFY_OUTPUT] is True:
928 for letter in message:
929 sys.stdout.write(letter)
930 sys.stdout.flush()
931 time.sleep(delay)
932 else:
933 sys.stdout.write(message)
934 print()
935
936 def animate_message(self, message: str = "Hello World!", delay: float = 0.02) -> None:
937 """
938 @brief Display or dump (to file) a message.
939
940 @param message The message to display or dump.
941 @param delay The delay between each letter.
942 """
943 if self._is_safe(message) is False:
944 message = f"{message}"
945 if self.save_to_file is True and self.file_descriptor is not None:
946 self.file_descriptor.write(f"{message}\n")
947 elif self.toml_contenttoml_content[KEY_OUTPUT_MODE] == OUT_STRING:
948 self.generated_content = f"{message}\n"
949 else:
950 self.display_animation(message, delay)
951
952 def disp_message_box(self, msg: str, char: str = "#") -> None:
953 """
954 @brief Display a message in a box.
955
956 @param msg The message to display.
957 @param char The character to use for the box.
958
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 #############################
964 """
965
966 box_wall = self.create_string(self.nb_chr, char)
967
968 title_content = ""
969 if "\n" in msg:
970 lines = msg.split("\n")
971 for i in lines:
972 string_length = len(i)
973 white_spaces = self._calculate_required_spaces(string_length)
974 title_content += char
975 title_content += white_spaces
976 title_content += i
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'
982 else:
983 string_length = len(msg)
984 white_spaces = self._calculate_required_spaces(string_length)
985 box_wall = self.create_string(self.nb_chr, char)
986 title_content += char
987 title_content += white_spaces
988 title_content += msg
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"
994
995 generated_content = f"{box_wall}\n"
996 generated_content += f"{title_content}"
997 generated_content += f"{box_wall}"
998 self.animate_message(
999 f"{generated_content}",
1001 )
1002
1003 def disp_round_message_box(self, msg: str = "Sample text") -> None:
1004 """
1005 @brief Display a message in a rounded box.
1006
1007 @param msg The message to display.
1008
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
1014 """
1015
1016 offset_reset = 2
1017
1018 # Generate the top line
1019 top_wall = ""
1020 if 'ROUND_BOX_CORNER_LEFT' in self.toml_contenttoml_content:
1021 top_wall += self.toml_contenttoml_content['ROUND_BOX_CORNER_LEFT']
1022 else:
1023 if self.log_warning_when_present is True:
1024 self.log_warning(
1025 "The top left corner is not defined, using the default one",
1026 inspect.currentframe().f_code.co_name
1027 )
1028 top_wall += "╔"
1029 if 'ROUND_BOX_HORIZONTAL' in self.toml_contenttoml_content:
1030 top_wall += self.create_string(
1031 self.nb_chr - offset_reset,
1032 self.toml_contenttoml_content['ROUND_BOX_HORIZONTAL']
1033 )
1034 else:
1035 if self.log_warning_when_present is True:
1036 self.log_warning(
1037 "The horizontal line is not defined, using the default one",
1038 inspect.currentframe().f_code.co_name
1039 )
1040 top_wall += self.create_string(
1041 self.nb_chr-offset_reset,
1042 "═"
1043 )
1044 if 'ROUND_BOX_CORNER_RIGHT' in self.toml_contenttoml_content:
1045 top_wall += self.toml_contenttoml_content['ROUND_BOX_CORNER_RIGHT']
1046 else:
1047 if self.log_warning_when_present is True:
1048 self.log_warning(
1049 "The top right corner is not defined, using the default one",
1050 inspect.currentframe().f_code.co_name
1051 )
1052 top_wall += "╗"
1053
1054 # Generate the bottom line
1055 bottom_wall = ""
1056 if 'ROUND_BOX_CORNER_BOTTOM_LEFT' in self.toml_contenttoml_content:
1057 bottom_wall += self.toml_contenttoml_content['ROUND_BOX_CORNER_BOTTOM_LEFT']
1058 else:
1059 if self.log_warning_when_present is True:
1060 self.log_warning(
1061 "The bottom left corner is not defined, using the default one",
1062 inspect.currentframe().f_code.co_name
1063 )
1064 bottom_wall += "╚"
1065
1066 if 'ROUND_BOX_HORIZONTAL' in self.toml_contenttoml_content:
1067 bottom_wall += self.create_string(
1068 self.nb_chr-offset_reset,
1069 self.toml_contenttoml_content['ROUND_BOX_HORIZONTAL']
1070 )
1071 else:
1072 if self.log_warning_when_present is True:
1073 self.log_warning(
1074 "The horizontal line is not defined, using the default one",
1075 inspect.currentframe().f_code.co_name
1076 )
1077 bottom_wall += self.create_string(
1078 self.nb_chr-offset_reset,
1079 "═"
1080 )
1081 if 'ROUND_BOX_CORNER_BOTTOM_RIGHT' in self.toml_contenttoml_content:
1082 bottom_wall += self.toml_contenttoml_content['ROUND_BOX_CORNER_BOTTOM_RIGHT']
1083 else:
1084 if self.log_warning_when_present is True:
1085 self.log_warning(
1086 "The bottom right corner is not defined, using the default one",
1087 inspect.currentframe().f_code.co_name
1088 )
1089 bottom_wall += "╝"
1090
1091 border_character = ""
1092 if 'ROUND_BOX_VERTICAL' in self.toml_contenttoml_content:
1093 border_character = self.toml_contenttoml_content['ROUND_BOX_VERTICAL']
1094 else:
1095 if self.log_warning_when_present is True:
1096 self.log_warning(
1097 "The vertical line is not defined, using the default one",
1098 inspect.currentframe().f_code.co_name
1099 )
1100 border_character = "║"
1101
1102 center_content = ""
1103 if "\n" in msg:
1104 lines = msg.split("\n")
1105 for i in lines:
1106 string_length = len(i)
1107 white_spaces = self._calculate_required_spaces(string_length)
1108 center_content += border_character
1109 center_content += white_spaces
1110 center_content += i
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'
1116 else:
1117 string_length = len(msg)
1118 white_spaces = self._calculate_required_spaces(string_length)
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"
1127
1128 generated_content = f"{top_wall}\n"
1129 generated_content += f"{center_content}"
1130 generated_content += f"{bottom_wall}"
1131 self.animate_message(
1132 f"{generated_content}",
1134 )
1135
1136 def disp_diff_side_and_top_message_box(self, msg: str) -> None:
1137 """
1138 @brief Display a message in a box with different side and top characters.
1139
1140 @param msg The message to display.
1141
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 _____________________________
1147 """
1148 ceiling_boxes = ""
1149 if 'DIFF_BORDER_LINE_CHARACTER_BOX' in self.toml_contenttoml_content:
1150 ceiling_boxes = self.toml_contenttoml_content['DIFF_BORDER_LINE_CHARACTER_BOX']
1151 else:
1152 if self.log_warning_when_present is True:
1153 self.log_warning(
1154 "The ceiling boxes are not defined, using the default one",
1155 inspect.currentframe().f_code.co_name
1156 )
1157 ceiling_boxes = "-"
1158
1159 border_character = ""
1160 if 'DIFF_SIDE_LINE_CHARACTER_BOX' in self.toml_contenttoml_content:
1161 border_character = self.toml_contenttoml_content['DIFF_SIDE_LINE_CHARACTER_BOX']
1162 else:
1163 if self.log_warning_when_present is True:
1164 self.log_warning(
1165 "The border character is not defined, using the default one",
1166 inspect.currentframe().f_code.co_name
1167 )
1168 border_character = "|"
1169
1170 box_wall = self.create_string(self.nb_chr, ceiling_boxes)
1171
1172 title_content = ""
1173 if "\n" in msg:
1174 lines = msg.split("\n")
1175 for i in lines:
1176 string_length = len(i)
1177 white_spaces = self._calculate_required_spaces(string_length)
1178 title_content += border_character
1179 title_content += white_spaces
1180 title_content += i
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'
1186 else:
1187 string_length = len(msg)
1188 white_spaces = self._calculate_required_spaces(string_length)
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"
1197
1198 generated_content = f"{box_wall}\n"
1199 generated_content += f"{title_content}"
1200 generated_content += f"{box_wall}"
1201 self.animate_message(
1202 f"{generated_content}",
1204 )
1205
1206 def disp_box_no_vertical(self, message: str, character: str = "@") -> None:
1207 """
1208 @brief Print a box format without internal vertical bars.
1209
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 #############################
1217 """
1218 if 'BOX_NO_VERTICAL' in self.toml_contenttoml_content:
1219 char = self.toml_contenttoml_content['BOX_NO_VERTICAL']
1220 else:
1221 if self.log_warning_when_present is True:
1222 self.log_warning(
1223 "The box character is not defined, using the default one",
1224 inspect.currentframe().f_code.co_name
1225 )
1226 char = character
1227
1228 box_wall = self.create_string(self.nb_chr, char)
1229
1230 title_content = ""
1231 if "\n" in message:
1232 lines = message.split("\n")
1233 for i in lines:
1234 string_length = len(i)
1235 white_spaces = self._calculate_required_spaces(string_length)
1236 title_content += white_spaces
1237 title_content += i
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'
1242 else:
1243 string_length = len(message)
1244 white_spaces = self._calculate_required_spaces(string_length)
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"
1251
1252 generated_content = f"{box_wall}\n"
1253 generated_content += f"{title_content}"
1254 generated_content += f"{box_wall}"
1255 self.animate_message(
1256 f"{generated_content}",
1258 )
1259
1260 def disp_vertical_message_box(self, msg: str, character: str = '') -> None:
1261 """
1262 @brief Display a message in a box with vertical bars.
1263
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
1269 @example # #\n
1270 @example # #\n
1271 @example # #\n
1272 @example # Sample text #\n
1273 @example # #\n
1274 @example # #\n
1275 @example # #\n
1276 @example ###############
1277 """
1278
1279 if 'BOX_NO_VERTICAL' in self.toml_contenttoml_content:
1280 character = self.toml_contenttoml_content['BOX_NO_VERTICAL']
1281 elif character == '':
1282 if self.log_warning_when_present is True:
1283 self.log_warning(
1284 "The box character is not defined, using the default one",
1285 inspect.currentframe().f_code.co_name
1286 )
1287 character = "#"
1288
1289 box_wall = self.create_string(self.nb_chr, character)
1290
1291 title_content = ""
1292 if "\n" in msg:
1293 lines = msg.split("\n")
1294 for i in lines:
1295 string_length = len(i)
1296 white_spaces = self._calculate_required_spaces(string_length)
1297 title_content += character
1298 title_content += white_spaces
1299 title_content += i
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"
1305 msg = lines
1306 else:
1307 string_length = len(msg)
1308 white_spaces = self._calculate_required_spaces(string_length)
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"
1317
1318 inner_length = int(self.max_whitespace)
1319 inner_line = self.create_string(
1320 inner_length,
1321 " "
1322 )
1323 inner_line = f"{character}{inner_line}{character}"
1324
1325 generated_content = f"{box_wall}\n"
1326 if "\n" in msg:
1327 max_height = (inner_length / 4) - len(msg)
1328 if max_height <= 2:
1329 max_height = 2
1330 else:
1331 max_height = 2
1332 i = 0
1333 while i < max_height:
1334 generated_content += f"{inner_line}\n"
1335 i += 1
1336 generated_content += f"{title_content}"
1337 i = 0
1338 while i < max_height:
1339 generated_content += f"{inner_line}\n"
1340 i += 1
1341
1342 generated_content += f"{box_wall}"
1343
1344 self.animate_message(
1345 f"{generated_content}",
1347 )
1348
1349 def box_vertical_no_horizontal(self, message: str, character: str = "") -> None:
1350 """
1351 @brief Print a box format without internal horizontal bars.
1352
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
1357 @example # #\n
1358 @example # #\n
1359 @example # #\n
1360 @example # #\n
1361 @example # #\n
1362 @example # Sample text #\n
1363 @example # #\n
1364 @example # #\n
1365 @example # #\n
1366 @example # #\n
1367 @example # #\n
1368 """
1369 if 'BOX_VERTICAL_NO_HORIZONTAL' in self.toml_contenttoml_content:
1370 character = self.toml_contenttoml_content['BOX_VERTICAL_NO_HORIZONTAL']
1371 elif character == '':
1372 if self.log_warning_when_present is True:
1373 self.log_warning(
1374 "The box character is not defined, using the default one",
1375 inspect.currentframe().f_code.co_name
1376 )
1377 character = "#"
1378
1379 title_content = ""
1380 if "\n" in message:
1381 lines = message.split("\n")
1382 for i in lines:
1383 string_length = len(i)
1384 white_spaces = self._calculate_required_spaces(string_length)
1385 title_content += character
1386 title_content += white_spaces
1387 title_content += i
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"
1393 message = lines
1394 else:
1395 string_length = len(message)
1396 white_spaces = self._calculate_required_spaces(string_length)
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"
1405 inner_length = int(self.max_whitespace)
1406 if len(message) > self.max_whitespace:
1407 inner_length = self.max_whitespace
1408 inner_line = self.create_string(
1409 inner_length,
1410 " "
1411 )
1412 inner_line = f"{character}{inner_line}{character}"
1413
1414 generated_content = ""
1415 if "\n" in message:
1416 max_height = (inner_length / 4) - len(message)
1417 if max_height <= 2:
1418 max_height = 2
1419 else:
1420 max_height = 2
1421 i = 0
1422 while i < max_height:
1423 generated_content += f"{inner_line}\n"
1424 i += 1
1425 generated_content += f"{title_content}"
1426 i = 0
1427 while i < max_height:
1428 if i+1 >= max_height:
1429 generated_content += f"{inner_line}"
1430 else:
1431 generated_content += f"{inner_line}\n"
1432 i += 1
1433
1434 self.animate_message(
1435 f"{generated_content}",
1437 )
1438
1439 def title(self, title: str) -> None:
1440 """
1441 @brief Print a beautified title.
1442
1443 @param title The title to display.
1444 """
1445 self.disp_message_box(title, self.title_wall_chr)
1446
1447 def sub_title(self, sub_title: str) -> None:
1448 """
1449 @brief Print a beautified subtitle.
1450
1451 @param sub_title The subtitle to display.
1452 """
1453 self.disp_message_box(sub_title, self.sub_title_wall_chr)
1454
1455 def sub_sub_title(self, sub_sub_title: str) -> None:
1456 """
1457 @brief Print a beautified sub-subtitle.
1458
1459 @param sub_sub_title The sub-subtitle to display.
1460 """
1461 self.disp_message_box(sub_sub_title, self.sub_sub_title_wall_chr)
1462
1463 def message(self, message: Union[str, list]) -> None:
1464 """
1465 @brief Print a beautified message.
1466
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 @@
1471 """
1472 if isinstance(message, list) is True:
1473 for msg in message:
1474 m_msg = f"{self.message_char}{self.message_char} {msg} "
1475 m_msg += f"{self.message_char}{self.message_char}"
1476 self.animate_message(
1477 m_msg,
1479 )
1480 else:
1481 msg = f"{self.message_char}{self.message_char} "
1482 msg += f" {message} {self.message_char}{self.message_char}"
1483 self.animate_message(
1484 msg,
1486 )
1487
1488 def error_message(self, message: Union[str, list]) -> None:
1489 """
1490 @brief Print a beautified error message.
1491
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 ##
1496 """
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}"
1500 for msg in message:
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}"
1503 self.animate_message(
1504 m_msg,
1506 )
1507 else:
1508 msg = f"{self.message_error_char}{self.message_error_char} Error:"
1509 msg += f" {message} {self.message_error_char}{self.message_error_char}"
1510 self.animate_message(
1511 msg,
1513 )
1514
1515 def success_message(self, message: Union[str, list]) -> None:
1516 """
1517 @brief Print a beautified success message.
1518
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 //
1523 """
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}"
1527 for msg in message:
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}"
1530 self.animate_message(
1531 m_msg,
1533 )
1534 else:
1535 msg = f"{self.message_success_char}{self.message_success_char} Success:"
1536 msg += f" {message} {self.message_success_char}{self.message_success_char}"
1537 self.animate_message(
1538 msg,
1540 )
1541
1542 def warning_message(self, message: Union[str, list]) -> None:
1543 """
1544 @brief Print a beautified warning message.
1545
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 !!
1550 """
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}"
1554 for msg in message:
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}"
1557 self.animate_message(
1558 m_msg,
1560 )
1561 else:
1562 msg = f"{self.message_warning_char}{self.message_warning_char} Warning:"
1563 msg += f" {message} {self.message_warning_char}{self.message_warning_char}"
1564 self.animate_message(
1565 msg,
1567 )
1568
1569 def question_message(self, message: Union[str, list]) -> None:
1570 """
1571 @brief Print a beautified question message.
1572
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 ??
1577 """
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}"
1581 for msg in message:
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}"
1584 self.animate_message(
1585 m_msg,
1587 )
1588 else:
1589 msg = f"{self.message_question_char}{self.message_question_char} Question:"
1590 msg += f" {message} {self.message_question_char}{self.message_question_char}"
1591 self.animate_message(
1592 msg,
1594 )
1595
1596 def inform_message(self, message: Union[str, List]) -> None:
1597 """
1598 @brief Print a beautified information message.
1599
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
1604 """
1605 if isinstance(message, list) is True:
1606 for msg in message:
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}"
1609 self.animate_message(
1610 m_msg,
1612 )
1613 else:
1614 msg = f"{self.message_inform_char}{self.message_inform_char} {message} "
1615 msg += f"{self.message_inform_char}{self.message_inform_char}"
1616 self.animate_message(
1617 msg,
1619 )
1620
1621 def _tree_node(self, line: str, offset: int, index: int, max_lenght: int) -> str:
1622 """
1623 @brief Display a line of the tree.
1624
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
1637 @example └─── data2
1638 """
1639 processed_line = str()
1640 i = 0
1641 while i < offset:
1642 processed_line += f"{self.tree_column_seperator_char} "
1643 i += 1
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}"
1647 else:
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}"
1650 if self._is_safe(line) is False:
1651 line = f"{line}"
1652 processed_line += " "
1653 processed_line += line
1654 processed_line += '\n'
1655 return processed_line
1656
1657 def tree(self, title: str, data: List[str], offset: int = 0) -> Union[str, None]:
1658 """
1659 @brief Print a list under the form of a beautified tree.
1660
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
1671 @example └─── data2
1672 @return A stringified version of the tree if not set to be displayed.
1673 """
1674 generated_content = ""
1675 if offset == 0:
1676 generated_content += f"{title}\n"
1677 length = len(data) - 1
1678
1679 for line in enumerate(data):
1680 if isinstance(data, list) and isinstance(line[1], (list, dict)):
1681 generated_content += self._tree_node(
1682 "<list instance>",
1683 offset,
1684 line[0],
1685 length
1686 )
1687 generated_content += self.tree(line[0], line[1], offset + 1)
1688 continue
1689 if isinstance(data, dict) and isinstance(data[line[1]], (list, dict)):
1690 generated_content += self._tree_node(
1691 line[1],
1692 offset,
1693 line[0],
1694 length
1695 )
1696 generated_content += self.tree(
1697 line[0],
1698 data[line[1]],
1699 offset + 1
1700 )
1701 continue
1702 if isinstance(data, dict) and isinstance(data[line[1]], dict) is False:
1703 generated_content += self._tree_node(
1704 f"{line[1]}: {data[line[1]]}",
1705 offset,
1706 line[0],
1707 length
1708 )
1709 else:
1710 generated_content += self._tree_node(
1711 line[1],
1712 offset,
1713 line[0],
1714 length
1715 )
1716 if offset == 0:
1717 self.animate_message(
1718 f"{generated_content}",
1720 )
1721 else:
1722 return generated_content
1723
1724 def append_run_date(self) -> None:
1725 """
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 ########################################
1732 """
1733 self.title(f"Run date: {time.strftime('%d/%m/%Y %H:%M:%S')} ")
1734
1735 def test_the_class(self) -> None:
1736 """
1737 @brief Test function to ensure all implemented methods work as expected.
1738 """
1739 test_data = {
1740 "test_data1": "test_data1.1",
1741 "test_data2": "test_data2.1",
1742 "test_data3": [
1743 "test_data_list3.1",
1744 "test_data_list3.2",
1745 "test_data_list3.3",
1746 "test_data_list3.4",
1747 "test_data_list3.5"
1748 ],
1749 "test_data4": "test_data4.1",
1750 "test_data5": {
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"
1755 },
1756 "test_data6": [
1757 {
1758 "test_data6.1": "test_data6.1.1",
1759 "test_data6.2": "test_data6.2.1"
1760 },
1761 [
1762 "test_data_list6.3.1",
1763 "test_data_list6.3.1",
1764 "test_data_list6.3.1",
1765 "test_data_list6.3.1"
1766 ]
1767 ],
1768 "test_data7": {
1769 "test_data7.1": {
1770 "test_data7.1.1": "test_data7.1.1.1",
1771 "test_data7.1.2": "test_data7.1.2.1"
1772 },
1773 "test_data7.2": [
1774 "test_data7.2.1",
1775 "test_data7.2.2",
1776 "test_data7.2.3",
1777 "test_data7.2.4",
1778 "test_data7.2.5"
1779 ]
1780 }
1781 }
1782 self.append_run_date()
1783 self.animate_message("Test Message !", 0.01)
1784 self.question_message("Test Question message !")
1785 self.error_message("Test Error !")
1786 self.inform_message("Test Inform !")
1787 self.success_message("Test Success !")
1788 self.warning_message("Test Warning !")
1789 self.title("Test title")
1790 self.sub_title("Test sub title")
1791 self.sub_sub_title("Test sub sub title")
1792 self.disp_box_no_vertical('Test Box no vertical')
1793 self.disp_round_message_box("Test Disp round message box")
1795 "Test Disp diff side and top message box"
1796 )
1797 self.disp_vertical_message_box("Test Disp vertical message box")
1798 self.box_vertical_no_horizontal("Test Box vertical no horizontal")
1799 self.tree("Test data", test_data)
1800 prev_debug = self.debugdebug
1801 self.debugdebug = True
1802 self.disp_print_debug("This is a test for debug messages")
1803 self.debugdebug = prev_debug
1804 self.disp_print_info("This is a test for info messages")
1805 self.disp_print_warning("This is a test for warning messages")
1806 self.disp_print_error("This is a test for error messages")
1807 self.disp_print_critical("This is a test for critical messages")
1808 self.log_custom_level(
1809 logging.WARNING, "This is a test warning for custom level messages"
1810 )
1811 custom_level_int = 2
1812 level_name = "DARLING"
1813 if self.add_custom_level(
1814 custom_level_int,
1815 level_name,
1816 "purple",
1817 LoggerColours.BLACK
1818 ) == self.error:
1819 self.log_error(
1820 f"The custom level '{level_name}' could not be added, please check the configuration"
1821 )
1822 else:
1823 self.log_custom_level(
1824 custom_level_int,
1825 f"This is a test for custom level message \"{logging.getLevelName(custom_level_int)}\""
1826 )
1827 custom_level_int = 196
1828 level_name = "Ikuno"
1829 if self.add_custom_level(
1830 custom_level_int,
1831 level_name,
1832 "cyan",
1833 LoggerColours.BLACK
1834 ) == self.error:
1835 self.log_error(
1836 f"The custom level '{level_name}' could not be added, please check the configuration"
1837 )
1838 else:
1839 self.log_custom_level(
1840 custom_level_int,
1841 f"This is a test for custom level message \"{logging.getLevelName(custom_level_int)}\""
1842 )
1843 self.close_file()
1844
1845
1846if __name__ == "__main__":
1847 DI = Disp(
1848 toml_content=TOML_CONF,
1849 save_to_file=False,
1850 file_name="test_run.tmp",
1851 file_descriptor=None,
1852 debug=False
1853 )
1854 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:291
str create_string(self, length, character)
Definition my_disp.py:902
None disp_print_custom_level(self, Union[int, str] level, str string, Union[str, None] func_name=None)
Definition my_disp.py:640
None log_info(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:772
None disp_round_message_box(self, str msg="Sample text")
Definition my_disp.py:1003
None question_message(self, Union[str, list] message)
Definition my_disp.py:1569
None disp_print_error(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:711
int update_logging_colour_background(self, Union[str, int] colour, Union[str, int] level_name, logging.Logger logger_instance=None)
Definition my_disp.py:444
Union[str, None] tree(self, str title, List[str] data, int offset=0)
Definition my_disp.py:1657
None box_vertical_no_horizontal(self, str message, str character="")
Definition my_disp.py:1349
None log_error(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:802
None log_warning(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:787
None sub_sub_title(self, str sub_sub_title)
Definition my_disp.py:1455
None disp_print_warning(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:696
None _add_function_to_instance(self, object func_dest, str func_name, str func_code)
Definition my_disp.py:199
bool _is_safe(self, Any content)
Definition my_disp.py:891
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:80
None disp_print_critical(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:726
None animate_message(self, str message="Hello World!", float delay=0.02)
Definition my_disp.py:936
None success_message(self, Union[str, list] message)
Definition my_disp.py:1515
None disp_print_info(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:681
str _tree_node(self, str line, int offset, int index, int max_lenght)
Definition my_disp.py:1621
_create_function(self, name, func_code)
Definition my_disp.py:188
logging.Logger _check_the_logging_instance(self, logging.Logger logger_instance=None)
Definition my_disp.py:241
None log_critical(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:817
None log_custom_level(self, Union[int, str] level, str string, Union[str, None] func_name=None)
Definition my_disp.py:741
Union[None, colorlog.ColoredFormatter] _get_colour_formatter(self, logging.Logger logger_instance=None)
Definition my_disp.py:328
None sub_title(self, str sub_title)
Definition my_disp.py:1447
None disp_message_box(self, str msg, str char="#")
Definition my_disp.py:952
None update_disp_debug(self, bool debug)
Definition my_disp.py:210
None log_debug(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:757
Union[int, str] _check_colour_data(self, Union[str, int] colour, logging.Logger logger_instance=None)
Definition my_disp.py:265
None disp_box_no_vertical(self, str message, str character="@")
Definition my_disp.py:1206
str _calculate_required_spaces(self, int string_length)
Definition my_disp.py:856
None _setup_logger(self, Union[Logging, str, None] logger)
Definition my_disp.py:139
None display_animation(self, str message="Hello World!", float delay=0.02)
Definition my_disp.py:914
None title(self, str title)
Definition my_disp.py:1439
None error_message(self, Union[str, list] message)
Definition my_disp.py:1488
None message(self, Union[str, list] message)
Definition my_disp.py:1463
int update_logging_colour_text(self, Union[str, int] colour, Union[str, int] level_name, logging.Logger logger_instance=None)
Definition my_disp.py:380
None warning_message(self, Union[str, list] message)
Definition my_disp.py:1542
None disp_diff_side_and_top_message_box(self, str msg)
Definition my_disp.py:1136
None update_logger_level(self, Union[int, LogLevelTracker.Levels] level=LogLevelTracker.Levels.NOTSET)
Definition my_disp.py:218
None disp_print_debug(self, str string="", Union[str, None] func_name=None)
Definition my_disp.py:665
None disp_vertical_message_box(self, str msg, str character='')
Definition my_disp.py:1260
None inform_message(self, Union[str, List] message)
Definition my_disp.py:1596
int add_custom_level(self, int level, str name, Union[int, str] colour_text="", Union[int, str] colour_bg="")
Definition my_disp.py:521