133 lines
5.4 KiB
Python
133 lines
5.4 KiB
Python
"""INI format converter."""
|
|
|
|
from configparser import ConfigParser, RawConfigParser
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Union
|
|
|
|
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: Union[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: Union[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:
|
|
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
|