This commit is contained in:
135
config_converter/converters/ini_converter.py
Normal file
135
config_converter/converters/ini_converter.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""INI format converter."""
|
||||
|
||||
import re
|
||||
from configparser import ConfigParser, RawConfigParser
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from config_converter.converters.base import BaseConverter, ConversionError
|
||||
|
||||
|
||||
def flatten_dict(data: Dict[str, Any], parent_key: str = "", sep: str = ".") -> Dict[str, Any]:
|
||||
"""Flatten nested dictionary for INI output."""
|
||||
items: Dict[str, Any] = {}
|
||||
for key, value in data.items():
|
||||
new_key = f"{parent_key}{sep}{key}" if parent_key else key
|
||||
if isinstance(value, dict):
|
||||
items.update(flatten_dict(value, new_key, sep))
|
||||
else:
|
||||
items[new_key] = value
|
||||
return items
|
||||
|
||||
|
||||
def unflatten_dict(data: Dict[str, Any], sep: str = ".") -> Dict[str, Any]:
|
||||
"""Unflatten flat dictionary for INI input."""
|
||||
result: Dict[str, Any] = {}
|
||||
for key, value in data.items():
|
||||
parts = key.split(sep)
|
||||
target = result
|
||||
for part in parts[:-1]:
|
||||
if part not in target:
|
||||
target[part] = {}
|
||||
target = target[part]
|
||||
target[parts[-1]] = value
|
||||
return result
|
||||
|
||||
|
||||
class IniConverter(BaseConverter):
|
||||
"""Converter for INI configuration files."""
|
||||
|
||||
FORMAT_NAME = "ini"
|
||||
FILE_EXTENSIONS = ["ini", "cfg"]
|
||||
|
||||
def read(self, source: str | Path) -> Dict[str, Any]:
|
||||
"""Read and parse an INI configuration file."""
|
||||
try:
|
||||
config = RawConfigParser(allow_no_value=True)
|
||||
config.read(source, encoding="utf-8")
|
||||
result: Dict[str, Any] = {}
|
||||
for section in config.sections():
|
||||
section_dict: Dict[str, Any] = {}
|
||||
for key, value in config.items(section):
|
||||
if value:
|
||||
try:
|
||||
section_dict[key] = int(value)
|
||||
except ValueError:
|
||||
try:
|
||||
section_dict[key] = float(value)
|
||||
except ValueError:
|
||||
if value.lower() in ("true", "yes", "on"):
|
||||
section_dict[key] = True
|
||||
elif value.lower() in ("false", "no", "off"):
|
||||
section_dict[key] = False
|
||||
else:
|
||||
section_dict[key] = value
|
||||
else:
|
||||
section_dict[key] = None
|
||||
result[section] = section_dict
|
||||
return result
|
||||
except Exception as e:
|
||||
raise ConversionError(f"Invalid INI in {source}: {e}") from e
|
||||
|
||||
def write(self, data: Dict[str, Any], target: str | Path) -> None:
|
||||
"""Write configuration data to an INI file."""
|
||||
try:
|
||||
config = ConfigParser()
|
||||
for section, section_data in data.items():
|
||||
if isinstance(section_data, dict):
|
||||
config[section] = {}
|
||||
for key, value in section_data.items():
|
||||
config[section][key] = str(value) if value is not None else ""
|
||||
else:
|
||||
config["DEFAULT"] = {}
|
||||
config["DEFAULT"]["settings"] = str(section_data)
|
||||
with open(target, "w", encoding="utf-8") as f:
|
||||
config.write(f)
|
||||
except (OSError, TypeError) as e:
|
||||
raise ConversionError(f"Failed to write INI to {target}: {e}") from e
|
||||
|
||||
def parse(self, content: str) -> Dict[str, Any]:
|
||||
"""Parse INI content from a string."""
|
||||
try:
|
||||
from io import StringIO
|
||||
|
||||
config = RawConfigParser(allow_no_value=True)
|
||||
config.read_string(content)
|
||||
result: Dict[str, Any] = {}
|
||||
for section in config.sections():
|
||||
section_dict: Dict[str, Any] = {}
|
||||
for key, value in config.items(section):
|
||||
if value:
|
||||
try:
|
||||
section_dict[key] = int(value)
|
||||
except ValueError:
|
||||
try:
|
||||
section_dict[key] = float(value)
|
||||
except ValueError:
|
||||
if value.lower() in ("true", "yes", "on"):
|
||||
section_dict[key] = True
|
||||
elif value.lower() in ("false", "no", "off"):
|
||||
section_dict[key] = False
|
||||
else:
|
||||
section_dict[key] = value
|
||||
else:
|
||||
section_dict[key] = None
|
||||
result[section] = section_dict
|
||||
return result
|
||||
except Exception as e:
|
||||
raise ConversionError(f"Invalid INI content: {e}") from e
|
||||
|
||||
def format(self, data: Dict[str, Any]) -> str:
|
||||
"""Format configuration data to an INI string."""
|
||||
try:
|
||||
config = ConfigParser()
|
||||
for section, section_data in data.items():
|
||||
if isinstance(section_data, dict):
|
||||
config[section] = {}
|
||||
for key, value in section_data.items():
|
||||
config[section][key] = str(value) if value is not None else ""
|
||||
from io import StringIO
|
||||
|
||||
output = StringIO()
|
||||
config.write(output)
|
||||
return output.getvalue()
|
||||
except (TypeError, ValueError) as e:
|
||||
raise ConversionError(f"Failed to format data as INI: {e}") from e
|
||||
Reference in New Issue
Block a user