Initial upload: config-converter-cli v1.0.0
This commit is contained in:
135
configconverter/validators.py
Normal file
135
configconverter/validators.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
"""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()
|
||||||
Reference in New Issue
Block a user