"""Validation module for config-converter-cli.""" from typing import Optional, Tuple from rich.console import Console from rich.syntax import Syntax from rich.text import Text from configconverter.converters import Converter from configconverter.exceptions import ParseError, ValidationError class Validator: """Validates configuration files for syntax errors.""" def __init__(self): self.converter = Converter() self.console = Console() def validate( self, content: str, format: Optional[str] = None ) -> Tuple[bool, Optional[ParseError]]: """Validate content for syntax errors. Args: content: The content to validate format: Optional format hint (json, yaml, toml) Returns: Tuple of (is_valid, error) """ try: if format: detected_format = format else: detected_format = self.converter.detect_format(content) data = self.converter._parse(content, detected_format) return True, None except ParseError as e: return False, e except Exception as e: return False, ParseError(f"Validation failed: {e}") def validate_file( self, file_path: str, format: Optional[str] = None ) -> Tuple[bool, Optional[ParseError]]: """Validate a file for syntax errors. Args: file_path: Path to the file format: Optional format hint (json, yaml, toml) Returns: Tuple of (is_valid, error) """ try: with open(file_path, "r", encoding="utf-8") as f: content = f.read() return self.validate(content, format) except FileNotFoundError: return False, ParseError(f"File not found: {file_path}") except Exception as e: return False, ParseError(f"Failed to read file: {e}") def format_error_message( self, error: ParseError, content: str ) -> str: """Format a parse error with syntax highlighting. Args: error: The parse error content: The original content Returns: Formatted error message """ lines = content.split("\n") if error.line_number is not None: line_idx = error.line_number - 1 if 0 <= line_idx < len(lines): context_lines = 2 start = max(0, line_idx - context_lines) end = min(len(lines), line_idx + context_lines + 1) snippet_lines = [] for i in range(start, end): prefix = ">>> " if i == line_idx else " " snippet_lines.append(f"{prefix}{i + 1:4d} | {lines[i]}") snippet = "\n".join(snippet_lines) return f"Parse Error at line {error.line_number}:\n{snippet}\n\n{error.message}" return f"Parse Error: {error.message}" def print_error( self, error: ParseError, content: str, file_path: Optional[str] = None ) -> None: """Print a validation error with colored output. Args: error: The parse error content: The original content file_path: Optional file path for header """ header = f"[bold red]Error[/bold red]" if file_path: header += f" in [cyan]{file_path}[/cyan]" self.console.print(header) if error.line_number is not None: lines = content.split("\n") line_idx = error.line_number - 1 if 0 <= line_idx < len(lines): self.console.print( f"Line {error.line_number}: [red]{error.message}[/red]" ) self.console.print() syntax = Syntax( lines[line_idx], "python", line_numbers=True, start_line=error.line_number, highlight_lines={error.line_number}, ) self.console.print(syntax) else: self.console.print(f"[red]{error.message}[/red]") validator = Validator()