mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-25 08:04:57 +08:00
core/config: write LF strings as TOML multi-line literals
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
from typing import TYPE_CHECKING, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
import msgspec
|
import msgspec
|
||||||
import tomlkit
|
import tomlkit
|
||||||
@@ -370,6 +370,24 @@ class ConfigStore:
|
|||||||
self._document[key] = tomlkit.table()
|
self._document[key] = tomlkit.table()
|
||||||
return cast("MutableMapping[str, object]", self._document[key])
|
return cast("MutableMapping[str, object]", self._document[key])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _encode_toml_value(value: object) -> object:
|
||||||
|
"""Encode builtins for tomlkit; strings with LF become multi-line literals."""
|
||||||
|
if isinstance(value, str):
|
||||||
|
if "\n" in value:
|
||||||
|
return tomlkit.string(value, multiline=True)
|
||||||
|
return value
|
||||||
|
if isinstance(value, list):
|
||||||
|
arr: object = tomlkit.array()
|
||||||
|
append = cast("Any", arr).append
|
||||||
|
for item in cast("list[object]", value):
|
||||||
|
append(ConfigStore._encode_toml_value(item))
|
||||||
|
return arr
|
||||||
|
if isinstance(value, dict):
|
||||||
|
# Nested dicts at section level are rare; providers use dedicated helpers.
|
||||||
|
return ConfigStore._to_inline_table(cast("Mapping[str, object]", value))
|
||||||
|
return value
|
||||||
|
|
||||||
def _sync_section(self, key: str, data: object) -> None:
|
def _sync_section(self, key: str, data: object) -> None:
|
||||||
raw: dict[str, object] = msgspec.to_builtins(data)
|
raw: dict[str, object] = msgspec.to_builtins(data)
|
||||||
if not raw:
|
if not raw:
|
||||||
@@ -381,7 +399,7 @@ class ConfigStore:
|
|||||||
if k not in raw:
|
if k not in raw:
|
||||||
del section[k]
|
del section[k]
|
||||||
for k, v in raw.items():
|
for k, v in raw.items():
|
||||||
section[k] = v
|
section[k] = self._encode_toml_value(v)
|
||||||
|
|
||||||
def _sync_database_section(self) -> None:
|
def _sync_database_section(self) -> None:
|
||||||
"""Sync ``[database]`` to the document."""
|
"""Sync ``[database]`` to the document."""
|
||||||
@@ -403,7 +421,7 @@ class ConfigStore:
|
|||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
table[key] = ConfigStore._to_inline_table(cast("Mapping[str, object]", value))
|
table[key] = ConfigStore._to_inline_table(cast("Mapping[str, object]", value))
|
||||||
else:
|
else:
|
||||||
table[key] = value
|
table[key] = ConfigStore._encode_toml_value(value)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -427,7 +445,7 @@ class ConfigStore:
|
|||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
entry[key] = ConfigStore._to_inline_table(cast("Mapping[str, object]", value))
|
entry[key] = ConfigStore._to_inline_table(cast("Mapping[str, object]", value))
|
||||||
else:
|
else:
|
||||||
entry[key] = value
|
entry[key] = ConfigStore._encode_toml_value(value)
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
def _sync_providers_section(self) -> None:
|
def _sync_providers_section(self) -> None:
|
||||||
|
|||||||
@@ -282,6 +282,30 @@ def test_write_models_as_inline_tables(tmp_path: Path) -> None:
|
|||||||
assert again.providers["local"].models["gpt-test"].cost_factor == 2
|
assert again.providers["local"].models["gpt-test"].cost_factor == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_lf_strings_as_multiline(tmp_path: Path) -> None:
|
||||||
|
"""Strings containing LF are written as TOML multi-line strings (\"\"\"…\"\"\")."""
|
||||||
|
from plyngent.config import AgentConfig
|
||||||
|
|
||||||
|
path = tmp_path / "ml-string.toml"
|
||||||
|
config = ConfigStore(path=path, document=tomlkit.document())
|
||||||
|
# AgentConfig has no public setter; exercise the TOML encoder via write path.
|
||||||
|
config._agent = AgentConfig(
|
||||||
|
system_prompt="Line one.\nLine two.",
|
||||||
|
tool_directives="Use tools.\nBe careful.",
|
||||||
|
)
|
||||||
|
config.write()
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
assert 'system_prompt = """' in text
|
||||||
|
assert "Line one." in text and "Line two." in text
|
||||||
|
assert 'tool_directives = """' in text
|
||||||
|
# Escaped single-line form should not appear for these values.
|
||||||
|
assert 'system_prompt = "Line one.\\nLine two."' not in text
|
||||||
|
|
||||||
|
again = plyngent.config.load(path)
|
||||||
|
assert again.agent_config.system_prompt == "Line one.\nLine two."
|
||||||
|
assert again.agent_config.tool_directives == "Use tools.\nBe careful."
|
||||||
|
|
||||||
|
|
||||||
def test_update_config() -> None:
|
def test_update_config() -> None:
|
||||||
file = Path(__file__).parent / "plyngent-edit-2.toml"
|
file = Path(__file__).parent / "plyngent-edit-2.toml"
|
||||||
_ = shutil.copy(Path(__file__).parent / "plyngent-valid.toml", file)
|
_ = shutil.copy(Path(__file__).parent / "plyngent-valid.toml", file)
|
||||||
|
|||||||
Reference in New Issue
Block a user