From b66b96be8adb943f01b89c7ea90fdceb1a6aee10 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 04:11:15 +0000 Subject: [PATCH] Add utility modules: file operations and logging --- dev_env_sync/utils/logging.py | 155 ++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 dev_env_sync/utils/logging.py diff --git a/dev_env_sync/utils/logging.py b/dev_env_sync/utils/logging.py new file mode 100644 index 0000000..dc55f7c --- /dev/null +++ b/dev_env_sync/utils/logging.py @@ -0,0 +1,155 @@ +"""Logging utilities for dev-env-sync.""" + +import logging +import os +from datetime import datetime +from pathlib import Path +from typing import Optional + + +LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +DATE_FORMAT = "%Y-%m-%d %H:%M:%S" + + +class ColoredFormatter(logging.Formatter): + """Custom formatter with colored output for console.""" + + COLORS = { + "DEBUG": "\033[36m", + "INFO": "\033[32m", + "WARNING": "\033[33m", + "ERROR": "\033[31m", + "CRITICAL": "\033[35m", + } + RESET = "\033[0m" + + def format(self, record: logging.LogRecord) -> str: + if record.levelname in self.COLORS: + color = self.COLORS[record.levelname] + message = super().format(record) + return f"{color}{message}{self.RESET}" + return super().format(record) + + +def setup_logging( + verbose: bool = False, + quiet: bool = False, + log_file: Optional[str] = None, + log_level: Optional[int] = None, +) -> logging.Logger: + """Set up logging configuration.""" + logger = logging.getLogger("dev_env_sync") + + if log_level is not None: + logger.setLevel(log_level) + elif verbose: + logger.setLevel(logging.DEBUG) + elif quiet: + logger.setLevel(logging.WARNING) + else: + logger.setLevel(logging.INFO) + + logger.handlers.clear() + + console_handler = logging.StreamHandler() + console_formatter = ColoredFormatter(LOG_FORMAT, datefmt=DATE_FORMAT) + console_handler.setFormatter(console_formatter) + logger.addHandler(console_handler) + + if log_file: + log_path = Path(log_file).expanduser() + log_path.parent.mkdir(parents=True, exist_ok=True) + + file_handler = logging.FileHandler(log_path, encoding="utf-8") + file_formatter = logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT) + file_handler.setFormatter(file_formatter) + logger.addHandler(file_handler) + + return logger + + +def get_logger(name: str = "dev_env_sync") -> logging.Logger: + """Get a logger instance.""" + return logging.getLogger(name) + + +class OperationLogger: + """Logger for tracking operations in dry-run mode.""" + + def __init__(self, base_logger: logging.Logger, dry_run: bool = False): + self.logger = base_logger + self.dry_run = dry_run + self.operations = [] + + def log_operation(self, operation_type: str, details: dict): + """Log an operation.""" + operation = { + "type": operation_type, + "timestamp": datetime.now().isoformat(), + "details": details, + } + self.operations.append(operation) + + if self.dry_run: + self.logger.info(f"[DRY-RUN] {operation_type}: {details}") + else: + self.logger.debug(f"Executing {operation_type}: {details}") + + def log_symlink_create(self, source: Path, target: Path): + """Log symlink creation.""" + self.log_operation("create_symlink", { + "source": str(source), + "target": str(target), + }) + + def log_symlink_remove(self, target: Path): + """Log symlink removal.""" + self.log_operation("remove_symlink", { + "target": str(target), + }) + + def log_file_copy(self, source: Path, destination: Path): + """Log file copy.""" + self.log_operation("copy_file", { + "source": str(source), + "destination": str(destination), + }) + + def log_file_backup(self, source: Path, backup_path: Path): + """Log file backup.""" + self.log_operation("backup_file", { + "source": str(source), + "backup_path": str(backup_path), + }) + + def log_file_remove(self, path: Path): + """Log file removal.""" + self.log_operation("remove_file", { + "path": str(path), + }) + + def log_package_install(self, package_manager: str, package: str): + """Log package installation.""" + self.log_operation("install_package", { + "package_manager": package_manager, + "package": package, + }) + + def log_editor_extension(self, editor: str, extension: str, action: str = "install"): + """Log editor extension installation.""" + self.log_operation("editor_extension", { + "editor": editor, + "extension": extension, + "action": action, + }) + + def get_operations_summary(self) -> str: + """Get a summary of all logged operations.""" + if not self.operations: + return "No operations logged." + + summary = [] + for op in self.operations: + summary.append(f"- {op['type']}: {op['details']}") + + return "\n".join(summary)