Files

136 lines
4.2 KiB
Python

"""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()