Files
config-convert-cli/config_convert/validators/syntax.py
7000pctAUTO 69e373abdf
Some checks failed
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
Initial upload: ConfigConvert CLI with full test suite and CI/CD
2026-02-04 07:05:27 +00:00

134 lines
3.6 KiB
Python

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