diff --git a/pyproject.toml b/pyproject.toml index b5c00f4..f993de0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "rich>=14.2.0", "structlog>=25.5.0", "sulguk>=0.11.1", + "tomli-w>=1.2.0", "typer>=0.21.0", "watchfiles>=0.21.0", ] diff --git a/src/takopi/config.py b/src/takopi/config.py index e008435..d3c4d9d 100644 --- a/src/takopi/config.py +++ b/src/takopi/config.py @@ -5,6 +5,8 @@ from dataclasses import dataclass, field from pathlib import Path from typing import Any +import tomli_w + HOME_CONFIG_PATH = Path.home() / ".takopi" / "takopi.toml" @@ -92,68 +94,12 @@ class ProjectsConfig: return tuple(self.chat_map.keys()) -def _toml_escape(value: str) -> str: - return value.replace("\\", "\\\\").replace('"', '\\"') - - -def _format_toml_value(value: Any) -> str: - if isinstance(value, bool): - return "true" if value else "false" - if isinstance(value, int): - return str(value) - if isinstance(value, float): - return repr(value) - if isinstance(value, Path): - return f'"{_toml_escape(str(value))}"' - if isinstance(value, str): - return f'"{_toml_escape(value)}"' - if isinstance(value, (list, tuple)): - inner = ", ".join(_format_toml_value(item) for item in value) - return f"[{inner}]" - raise ConfigError(f"Unsupported config value {value!r}") - - -def _table_has_scalars(table: dict[str, Any]) -> bool: - return any(not isinstance(value, dict) for value in table.values()) - - def dump_toml(config: dict[str, Any]) -> str: - lines: list[str] = [] - - def write_kv(key: str, value: Any) -> None: - lines.append(f"{key} = {_format_toml_value(value)}") - - def write_table(name: str, table: dict[str, Any]) -> None: - if lines and lines[-1] != "": - lines.append("") - lines.append(f"[{name}]") - for key, value in table.items(): - if isinstance(value, dict): - continue - write_kv(key, value) - for key, value in table.items(): - if isinstance(value, dict): - write_table(f"{name}.{key}", value) - - for key, value in config.items(): - if isinstance(value, dict): - continue - write_kv(key, value) - - for key, value in config.items(): - if not isinstance(value, dict): - continue - if _table_has_scalars(value): - write_table(key, value) - continue - for subkey, subvalue in value.items(): - if isinstance(subvalue, dict): - write_table(f"{key}.{subkey}", subvalue) - else: - write_table(key, value) - break - - return "\n".join(lines) + "\n" + try: + dumped = tomli_w.dumps(config) + except (TypeError, ValueError) as e: + raise ConfigError(f"Unsupported config value: {e}") from None + return dumped def write_config(config: dict[str, Any], path: Path) -> None: diff --git a/uv.lock b/uv.lock index 9853e1e..0b99350 100644 --- a/uv.lock +++ b/uv.lock @@ -581,6 +581,7 @@ dependencies = [ { name = "rich" }, { name = "structlog" }, { name = "sulguk" }, + { name = "tomli-w" }, { name = "typer" }, { name = "watchfiles" }, ] @@ -607,6 +608,7 @@ requires-dist = [ { name = "rich", specifier = ">=14.2.0" }, { name = "structlog", specifier = ">=25.5.0" }, { name = "sulguk", specifier = ">=0.11.1" }, + { name = "tomli-w", specifier = ">=1.2.0" }, { name = "typer", specifier = ">=0.21.0" }, { name = "watchfiles", specifier = ">=0.21.0" }, ] @@ -620,6 +622,15 @@ dev = [ { name = "ty", specifier = ">=0.0.8" }, ] +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + [[package]] name = "tqdm" version = "4.67.1"