Initial upload: ConfigConvert CLI with full test suite and CI/CD
This commit is contained in:
133
config_convert/validators/syntax.py
Normal file
133
config_convert/validators/syntax.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""Syntax validation for config formats."""
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
ENV_LINE_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*\s*=(.*)$")
|
||||
|
||||
|
||||
class ValidationError:
|
||||
"""Represents a validation error with location info."""
|
||||
|
||||
def __init__(self, message: str, line: Optional[int] = None, column: Optional[int] = None):
|
||||
self.message = message
|
||||
self.line = line
|
||||
self.column = column
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.line is not None:
|
||||
if self.column is not None:
|
||||
return f"Line {self.line}, Column {self.column}: {self.message}"
|
||||
return f"Line {self.line}: {self.message}"
|
||||
return self.message
|
||||
|
||||
|
||||
def validate_json(data: str) -> Tuple[bool, Optional[ValidationError]]:
|
||||
"""Validate JSON syntax.
|
||||
|
||||
Args:
|
||||
data: The JSON string to validate
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error)
|
||||
"""
|
||||
try:
|
||||
json.loads(data)
|
||||
return True, None
|
||||
except json.JSONDecodeError as e:
|
||||
return False, ValidationError(str(e), e.lineno, e.colno)
|
||||
|
||||
|
||||
def validate_yaml(data: str) -> Tuple[bool, Optional[ValidationError]]:
|
||||
"""Validate YAML syntax.
|
||||
|
||||
Args:
|
||||
data: The YAML string to validate
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error)
|
||||
"""
|
||||
try:
|
||||
yaml.safe_load(data)
|
||||
return True, None
|
||||
except yaml.YAMLError as e:
|
||||
lineno = getattr(e, "lineno", None)
|
||||
colno = getattr(e, "column", None)
|
||||
problem = getattr(e, "problem", str(e))
|
||||
if lineno is not None:
|
||||
return False, ValidationError(problem, lineno, colno)
|
||||
return False, ValidationError(problem)
|
||||
|
||||
|
||||
def validate_toml(data: str) -> Tuple[bool, Optional[ValidationError]]:
|
||||
"""Validate TOML syntax.
|
||||
|
||||
Args:
|
||||
data: The TOML string to validate
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error)
|
||||
"""
|
||||
import sys
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
|
||||
try:
|
||||
tomllib.loads(data)
|
||||
return True, None
|
||||
except Exception as e:
|
||||
error_str = str(e)
|
||||
line_match = re.search(r"line (\d+)", error_str)
|
||||
line = int(line_match.group(1)) if line_match else None
|
||||
return False, ValidationError(error_str, line)
|
||||
|
||||
|
||||
def validate_env(data: str) -> Tuple[bool, Optional[ValidationError]]:
|
||||
"""Validate ENV format syntax.
|
||||
|
||||
Args:
|
||||
data: The ENV string to validate
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error)
|
||||
"""
|
||||
lines = data.splitlines()
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith("#"):
|
||||
continue
|
||||
if not line.strip() == line:
|
||||
return False, ValidationError(f"Invalid KEY=value format", line_num)
|
||||
if not ENV_LINE_PATTERN.match(line):
|
||||
return False, ValidationError(f"Invalid KEY=value format", line_num)
|
||||
return True, None
|
||||
|
||||
|
||||
def validate(data: str, format: str) -> Tuple[bool, Optional[ValidationError]]:
|
||||
"""Validate syntax for any supported format.
|
||||
|
||||
Args:
|
||||
data: The string to validate
|
||||
format: The format to validate (json, yaml, toml, env)
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error)
|
||||
"""
|
||||
format_lower = format.lower()
|
||||
if format_lower == "json":
|
||||
return validate_json(data)
|
||||
elif format_lower == "yaml":
|
||||
return validate_yaml(data)
|
||||
elif format_lower == "toml":
|
||||
return validate_toml(data)
|
||||
elif format_lower == "env":
|
||||
return validate_env(data)
|
||||
else:
|
||||
return False, ValidationError(f"Unsupported format: {format}")
|
||||
Reference in New Issue
Block a user