From 6470e7fe5ca1d0417f4c7b8a2bd3ac43abc7c496 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Thu, 29 Jan 2026 10:50:33 +0000 Subject: [PATCH] Add formatters, utils, schemas and version --- configforge/utils/file_parser.py | 147 +++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 configforge/utils/file_parser.py diff --git a/configforge/utils/file_parser.py b/configforge/utils/file_parser.py new file mode 100644 index 0000000..40a625a --- /dev/null +++ b/configforge/utils/file_parser.py @@ -0,0 +1,147 @@ +"""File parsing utilities for ConfigForge.""" + +import os +from pathlib import Path +from typing import Any, Optional + +from configforge.exceptions import ( + ConversionError, + FileOperationError, + InvalidConfigFormatError, +) + + +SUPPORTED_FORMATS = ["json", "yaml", "yml", "toml", "env", "ini"] + + +def detect_format(filepath: str) -> str: + """Detect the format of a configuration file based on its extension.""" + ext = Path(filepath).suffix.lower() + + format_mapping = { + ".json": "json", + ".yaml": "yaml", + ".yml": "yaml", + ".toml": "toml", + ".env": "env", + ".ini": "ini", + } + + if ext not in format_mapping: + raise InvalidConfigFormatError( + f"Unsupported file format: {ext}. Supported formats: {', '.join(SUPPORTED_FORMATS)}" + ) + + return format_mapping[ext] + + +class FileParser: + """Parser for configuration files in various formats.""" + + @staticmethod + def parse(filepath: str) -> dict: + """Parse a configuration file and return its contents as a dictionary.""" + try: + format_type = detect_format(filepath) + parser_method = getattr(FileParser, f"_parse_{format_type}", None) + + if parser_method is None: + raise InvalidConfigFormatError(f"No parser available for format: {format_type}") + + with open(filepath, "r", encoding="utf-8") as f: + content = f.read() + + return parser_method(content, filepath) + + except FileNotFoundError: + raise FileOperationError(f"File not found: {filepath}", filepath=filepath) + except PermissionError: + raise FileOperationError(f"Permission denied: {filepath}", filepath=filepath) + except Exception as e: + if isinstance(e, (InvalidConfigFormatError, FileOperationError, ConversionError)): + raise + raise ConversionError(f"Failed to parse {filepath}: {str(e)}") from e + + @staticmethod + def _parse_json(content: str, filepath: str) -> dict: + """Parse JSON configuration file.""" + import json + + try: + return json.loads(content) + except json.JSONDecodeError as e: + raise ConversionError(f"Invalid JSON in {filepath}: {str(e)}") from e + + @staticmethod + def _parse_yaml(content: str, filepath: str) -> dict: + """Parse YAML configuration file.""" + import yaml + + try: + return yaml.safe_load(content) or {} + except yaml.YAMLError as e: + raise ConversionError(f"Invalid YAML in {filepath}: {str(e)}") from e + + @staticmethod + def _parse_toml(content: str, filepath: str) -> dict: + """Parse TOML configuration file.""" + import tomlkit + + try: + return tomlkit.loads(content) + except tomlkit.exceptions.ParseError as e: + raise ConversionError(f"Invalid TOML in {filepath}: {str(e)}") from e + + @staticmethod + def _parse_env(content: str, filepath: str) -> dict: + """Parse ENV configuration file.""" + result = {} + for line_num, line in enumerate(content.splitlines(), 1): + line = line.strip() + if not line or line.startswith("#"): + continue + + if "=" in line: + key, _, value = line.partition("=") + key = key.strip() + value = value.strip() + + if not key: + continue + + value = value.strip('"').strip("'") + result[key] = value + + return result + + @staticmethod + def _parse_ini(content: str, filepath: str) -> dict: + """Parse INI configuration file.""" + result = {} + current_section = "default" + + for line_num, line in enumerate(content.splitlines(), 1): + line = line.strip() + if not line or line.startswith("#") or line.startswith(";"): + continue + + if line.startswith("[") and line.endswith("]"): + current_section = line[1:-1].strip() + if current_section not in result: + result[current_section] = {} + elif "=" in line: + key, _, value = line.partition("=") + key = key.strip() + value = value.strip() + + if current_section == "default": + result[key] = value + else: + result[current_section][key] = value + + return result + + +def parse_file(filepath: str) -> dict: + """Parse a configuration file and return its contents as a dictionary.""" + return FileParser.parse(filepath)