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