Add source code files
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-04 21:54:22 +00:00
parent adedcd4332
commit 8ff6169518

View 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