# -*- coding: utf-8 -*- """ Debug-Ausgabefunktionen für Trixy. Stellt farbcodierte Ausgabefunktionen bereit, die zwischen Debug-Modus (stdout) und Logging umschalten können. """ import sys import logging from datetime import datetime from typing import Any # ANSI-Farbcodes für Terminal-Ausgabe class Colors: """ANSI-Escape-Codes für farbige Terminal-Ausgabe.""" RESET = "\033[0m" BOLD = "\033[1m" # Vordergrundfarben BLACK = "\033[30m" RED = "\033[31m" GREEN = "\033[32m" YELLOW = "\033[33m" BLUE = "\033[34m" MAGENTA = "\033[35m" CYAN = "\033[36m" WHITE = "\033[37m" # Helle Varianten BRIGHT_RED = "\033[91m" BRIGHT_GREEN = "\033[92m" BRIGHT_YELLOW = "\033[93m" BRIGHT_BLUE = "\033[94m" BRIGHT_MAGENTA = "\033[95m" BRIGHT_CYAN = "\033[96m" BRIGHT_WHITE = "\033[97m" # Globaler Debug-Modus-Flag _debug_mode: bool = False # Logger für nicht-Debug-Modus _logger = logging.getLogger("trixy") # File-Router für automatisches File-Logging (optional) _file_router = None def set_debug_mode(enabled: bool) -> None: """ Aktiviert oder deaktiviert den Debug-Modus. Im Debug-Modus werden alle Ausgaben direkt auf stdout geschrieben. Andernfalls wird das Logging-System verwendet. Args: enabled: True für Debug-Modus (stdout), False für Logging """ global _debug_mode _debug_mode = enabled def is_debug_mode() -> bool: """Gibt zurück, ob der Debug-Modus aktiv ist.""" return _debug_mode def _setup_file_routing(router) -> None: """ Setzt den File-Router fuer automatisches File-Logging. Args: router: FileLogRouter-Instanz oder None zum Deaktivieren """ global _file_router _file_router = router def _format_message(level: str, color: str, message: str, *args: Any) -> str: """ Formatiert eine Debug-Nachricht mit Zeitstempel und Farbe. Args: level: Log-Level-String (INFO, DEBUG, ERROR, WARN) color: ANSI-Farbcode message: Nachrichtenformat-String *args: Argumente für Format-String Returns: Formatierte Nachricht mit Farbe und Zeitstempel """ timestamp = datetime.now().strftime("%H:%M:%S") if args: try: formatted_msg = message % args except TypeError: formatted_msg = message + " " + " ".join(str(a) for a in args) else: formatted_msg = message return f"{Colors.BRIGHT_WHITE}[{timestamp}]{Colors.RESET} {color}{Colors.BOLD}[{level}]{Colors.RESET} {formatted_msg}" def _output(level: str, color: str, log_level: int, message: str, *args: Any) -> None: """ Gibt eine Nachricht aus (Debug-Modus) oder loggt sie. Args: level: Level-String für Anzeige color: ANSI-Farbcode log_level: logging-Level für Logger message: Nachricht oder Format-String *args: Argumente für Format-String """ if _debug_mode: formatted = _format_message(level, color, message, *args) print(formatted, file=sys.stdout if log_level < logging.ERROR else sys.stderr) else: if args: try: _logger.log(log_level, message, *args) except TypeError: _logger.log(log_level, message + " " + " ".join(str(a) for a in args)) else: _logger.log(log_level, message) # File-Logging (in beiden Modi, gesteuert durch file_log_level) if _file_router is not None: if args: try: file_msg = message % args except TypeError: file_msg = message + " " + " ".join(str(a) for a in args) else: file_msg = message _file_router.log(log_level, file_msg) def pinfo(message: str, *args: Any) -> None: """ Gibt eine Info-Nachricht aus. Args: message: Nachricht oder Format-String *args: Argumente für Format-String """ _output("INFO", Colors.BRIGHT_GREEN, logging.INFO, message, *args) def pdebug(message: str, *args: Any) -> None: """ Gibt eine Debug-Nachricht aus. Args: message: Nachricht oder Format-String *args: Argumente für Format-String """ _output("DEBUG", Colors.BRIGHT_CYAN, logging.DEBUG, message, *args) def perror(message: str, *args: Any) -> None: """ Gibt eine Fehlermeldung aus. Args: message: Nachricht oder Format-String *args: Argumente für Format-String """ _output("ERROR", Colors.BRIGHT_RED, logging.ERROR, message, *args) def pwarn(message: str, *args: Any) -> None: """ Gibt eine Warnmeldung aus. Args: message: Nachricht oder Format-String *args: Argumente für Format-String """ _output("WARN", Colors.BRIGHT_YELLOW, logging.WARNING, message, *args) def pprogress(label: str, current: int, total: int, suffix: str = "") -> None: """ Zeigt eine sich in-place aktualisierende Progressbar an. Im Debug-Modus: Terminal-Progressbar mit \\r Sonst: Nur alle 5% eine Log-Zeile Args: label: Beschriftung der Progressbar current: Aktueller Fortschritt in Bytes total: Gesamtgroesse in Bytes suffix: Zusaetzliche Info (Geschwindigkeit, ETA) """ if total <= 0: return percent = (current / total) * 100 if _debug_mode: # Terminal-Breite ermitteln try: import shutil term_width = shutil.get_terminal_size().columns except Exception: term_width = 80 # Label auf max. 25 Zeichen kuerzen short_label = label if len(label) <= 25 else "..." + label[-22:] # Progressbar-Laenge dynamisch (Minimum 15) # Aufbau: [HH:MM:SS] [PROGRESS] label: [bar] xx.x% suffix # ANSI-Codes zaehlen nicht zur sichtbaren Breite fixed_len = 10 + 1 + 10 + 1 + len(short_label) + 2 + 2 + 7 + 1 + len(suffix) bar_length = max(15, term_width - fixed_len) if bar_length > 30: bar_length = 30 filled = int(bar_length * current / total) bar = "█" * filled + "░" * (bar_length - filled) timestamp = datetime.now().strftime("%H:%M:%S") line = ( f"\r{Colors.BRIGHT_WHITE}[{timestamp}]{Colors.RESET} " f"{Colors.BRIGHT_BLUE}{Colors.BOLD}[PROGRESS]{Colors.RESET} " f"{short_label}: [{bar}] {percent:5.1f}% {suffix}" ) # Zeile mit Leerzeichen auf Terminal-Breite auffuellen (alte Zeichen loeschen) visible_len = 10 + 1 + 10 + 1 + len(short_label) + 2 + bar_length + 2 + 7 + len(suffix) padding = max(0, term_width - visible_len) line += " " * padding print(line, end="", flush=True) # Bei 100%: Newline ausgeben if current >= total: print() else: # Logging-Modus: Nur alle 5% current_step = int(percent / 5) prev_percent = ((current - 8192) / total) * 100 if current > 8192 else -1 prev_step = int(prev_percent / 5) if current >= total or current_step > prev_step: _logger.info(f"{label}: {percent:.0f}% {suffix}")