From 69e373abdf6d14c8374c35678536bcf55e25afe2 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Wed, 4 Feb 2026 07:05:27 +0000 Subject: [PATCH] Initial upload: ConfigConvert CLI with full test suite and CI/CD --- config_convert/validators/syntax.py | 133 ++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 config_convert/validators/syntax.py diff --git a/config_convert/validators/syntax.py b/config_convert/validators/syntax.py new file mode 100644 index 0000000..fa4a3eb --- /dev/null +++ b/config_convert/validators/syntax.py @@ -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}")