Coverage for src / ezqt_app / utils / printer.py: 89.69%

75 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-06 13:12 +0000

1# /////////////////////////////////////////////////////////////// 

2# UTILS.PRINTER - Console output utility 

3# Project: ezqt_app 

4# /////////////////////////////////////////////////////////////// 

5 

6"""Centralized console printer with consistent formatting and color support.""" 

7 

8from __future__ import annotations 

9 

10# /////////////////////////////////////////////////////////////// 

11# IMPORTS 

12# /////////////////////////////////////////////////////////////// 

13# Third-party imports 

14from colorama import Fore, Style 

15 

16 

17# /////////////////////////////////////////////////////////////// 

18# CLASSES 

19# /////////////////////////////////////////////////////////////// 

20class Printer: 

21 """Centralized printer for console output with consistent formatting. 

22 

23 Provides methods for different message types: 

24 - Info messages (light gray) 

25 - Success messages (green) 

26 - Warning messages (yellow) 

27 - Error messages (red) 

28 - Verbose messages (light black, gated by ``verbose`` flag) 

29 """ 

30 

31 def __init__(self, verbose: bool = False, debug: bool = False): 

32 self.verbose = verbose 

33 self.debug = debug 

34 

35 def info(self, message: str, prefix: str = "~") -> None: 

36 """Print an info message.""" 

37 print(f"{Fore.WHITE}{prefix} {message}{Style.RESET_ALL}") 

38 

39 def success(self, message: str, prefix: str = "✓") -> None: 

40 """Print a success message.""" 

41 print(f"{Fore.GREEN}{prefix} {message}{Style.RESET_ALL}") 

42 

43 def warning(self, message: str, prefix: str = "!") -> None: 

44 """Print a warning message.""" 

45 print(f"{Fore.YELLOW}{prefix} {message}{Style.RESET_ALL}") 

46 

47 def error(self, message: str, prefix: str = "✗") -> None: 

48 """Print an error message.""" 

49 print(f"{Fore.RED}{prefix} {message}{Style.RESET_ALL}") 

50 

51 def verbose_msg(self, message: str, prefix: str = "~") -> None: 

52 """Print a verbose message (only when ``verbose`` is enabled).""" 

53 if self.verbose: 

54 print(f"{Fore.LIGHTBLACK_EX}{prefix} {message}{Style.RESET_ALL}") 

55 

56 def debug_msg(self, message: str, prefix: str = "~") -> None: 

57 """Print a debug message (only when ``debug`` is enabled).""" 

58 if self.debug: 

59 print(f"{Fore.LIGHTBLACK_EX}{prefix} {message}{Style.RESET_ALL}") 

60 

61 def action(self, message: str, prefix: str = "+") -> None: 

62 """Print an action message (blue).""" 

63 print(f"{Fore.BLUE}{prefix} {message}{Style.RESET_ALL}") 

64 

65 def init(self, message: str, prefix: str = "🚀") -> None: 

66 """Print an initialization message (magenta).""" 

67 print(f"{Fore.MAGENTA}{prefix} {message}{Style.RESET_ALL}") 

68 

69 def section(self, title: str, prefix: str = "=") -> None: 

70 """Print a section header with separator.""" 

71 separator = prefix * (len(title) + 4) 

72 print(f"{Fore.CYAN}{separator}{Style.RESET_ALL}") 

73 print(f"{Fore.CYAN}{prefix} {title} {prefix}{Style.RESET_ALL}") 

74 print(f"{Fore.CYAN}{separator}{Style.RESET_ALL}") 

75 

76 def config_display(self, config_data: dict, _title: str = "Configuration") -> None: 

77 """Display configuration data in a formatted ASCII box.""" 

78 self.action("[System] Loaded application settings.") 

79 print(f"{Fore.LIGHTBLACK_EX}...{Style.RESET_ALL}") 

80 print( 

81 f"{Fore.LIGHTBLACK_EX} ┌───────────────────────────────────────────────┐{Style.RESET_ALL}" 

82 ) 

83 for key, val in config_data.items(): 

84 print( 

85 f"{Fore.LIGHTBLACK_EX} |- {key}: {Fore.LIGHTWHITE_EX}{val}{Style.RESET_ALL}" 

86 ) 

87 print( 

88 f"{Fore.LIGHTBLACK_EX} └───────────────────────────────────────────────┘{Style.RESET_ALL}" 

89 ) 

90 print(f"{Fore.LIGHTBLACK_EX}...{Style.RESET_ALL}") 

91 

92 def list_items( 

93 self, items: list[str], title: str | None = None, max_items: int = 3 

94 ) -> None: 

95 """Print a (possibly truncated) list of items.""" 

96 if title: 96 ↛ 97line 96 didn't jump to line 97 because the condition on line 96 was never true

97 self.info(title) 

98 if items: 98 ↛ 105line 98 didn't jump to line 105 because the condition on line 98 was always true

99 display_items = items[:max_items] 

100 items_str = ", ".join(display_items) 

101 if len(items) > max_items: 101 ↛ 103line 101 didn't jump to line 103 because the condition on line 101 was always true

102 items_str += "..." 

103 self.verbose_msg(f" {items_str}") 

104 else: 

105 self.verbose_msg(" (no items)") 

106 

107 def file_operation( 

108 self, operation: str, file_path: str, status: str = "completed" 

109 ) -> None: 

110 """Print a file operation message.""" 

111 if status == "completed": 

112 self.info(f"[{operation}] {file_path}") 

113 elif status == "error": 

114 self.error(f"[{operation}] {file_path}") 

115 elif status == "warning": 115 ↛ exitline 115 didn't return from function 'file_operation' because the condition on line 115 was always true

116 self.warning(f"[{operation}] {file_path}") 

117 

118 def custom_print( 

119 self, message: str, color: str = "WHITE", prefix: str = "" 

120 ) -> None: 

121 """Print a message with an arbitrary colorama color name.""" 

122 color_attr = getattr(Fore, color.upper(), Fore.WHITE) 

123 prefix_part = f"{prefix} " if prefix else "" 

124 print(f"{color_attr}{prefix_part}{message}{Style.RESET_ALL}") 

125 

126 def raw_print(self, message: str) -> None: 

127 """Print a raw message without any formatting.""" 

128 print(message) 

129 

130 def qrc_compilation_result( 

131 self, success: bool, error_message: str | None = None 

132 ) -> None: 

133 """Print QRC compilation result.""" 

134 if success: 134 ↛ 135line 134 didn't jump to line 135 because the condition on line 134 was never true

135 self.action("[Initializer] Generated binaries definitions from QRC file.") 

136 else: 

137 self.warning("[Initializer] QRC compilation skipped") 

138 if error_message: 138 ↛ exitline 138 didn't return from function 'qrc_compilation_result' because the condition on line 138 was always true

139 self.debug_msg(f"[Initializer] Error details: {error_message}") 

140 

141 

142# /////////////////////////////////////////////////////////////// 

143# SINGLETONS 

144# /////////////////////////////////////////////////////////////// 

145_default_printer = Printer() 

146 

147 

148# /////////////////////////////////////////////////////////////// 

149# FUNCTIONS 

150# /////////////////////////////////////////////////////////////// 

151def get_printer(verbose: bool | None = None, debug: bool | None = None) -> Printer: 

152 """Return a printer configured from global defaults and optional overrides.""" 

153 resolved_verbose = _default_printer.verbose if verbose is None else bool(verbose) 

154 resolved_debug = _default_printer.debug if debug is None else bool(debug) 

155 if ( 

156 resolved_verbose == _default_printer.verbose 

157 and resolved_debug == _default_printer.debug 

158 ): 

159 return _default_printer 

160 return Printer(verbose=resolved_verbose, debug=resolved_debug) 

161 

162 

163def set_global_verbose(verbose: bool) -> None: 

164 """Set the global verbose mode on the default printer instance.""" 

165 global _default_printer 

166 _default_printer = Printer(verbose=verbose, debug=_default_printer.debug) 

167 

168 

169def set_global_debug(debug: bool) -> None: 

170 """Set the global debug mode on the default printer instance.""" 

171 global _default_printer 

172 _default_printer = Printer(verbose=_default_printer.verbose, debug=debug)