diff --git a/pasch/file/json.py b/pasch/file/json.py index 2dd32d6..b09b4a5 100644 --- a/pasch/file/json.py +++ b/pasch/file/json.py @@ -1,12 +1,28 @@ -from typing import Self import json from dataclasses import dataclass -from typing import Any +from typing import Any, Self from .file import TAG, File from .text import TextFile +def _merge_values(a: Any, b: Any) -> Any: + if not isinstance(a, dict) or not isinstance(b, dict): + return b + + result = {} + for k, v_a in a.items(): + result[k] = v_a + for k, v_b in b.items(): + v_a = a.get(k) + if v_a is None: + result[k] = v_b + else: + result[k] = _merge_values(v_a, v_b) + + return result + + @dataclass class JsonFileProxy: file: "JsonFile" @@ -15,18 +31,14 @@ class JsonFileProxy: def at(self, *path: str) -> Self: return JsonFileProxy(self.file, self.path + path) - def set(self, value: Any) -> None: - if not self.path: - self.file.set(value) + def set(self, path: str | tuple[str, ...], value: Any) -> None: + if isinstance(path, str): + path = (path,) - data = self.file.data - *parts, last = self.path - for part in parts: - data = data.setdefault(part, {}) - data[last] = value + self.file.set(self.path + path, value) - def tag_here(self, tag: str = TAG) -> None: - self.set(tag) + def tag(self, path: str | tuple[str, ...] = "_tag") -> None: + self.set(path, TAG) class JsonFile(File): @@ -36,13 +48,32 @@ class JsonFile(File): def at(self, *path: str) -> JsonFileProxy: return JsonFileProxy(self, path) - def set(self, value: Any) -> None: - self.data = value + def get(self, path: str | tuple[str, ...]) -> Any: + data = self.data + for part in path: + data = data[part] + return data - def tag(self, tag: str = TAG, key: str | list[str] = "_tag") -> None: - if isinstance(key, str): - self.at(key).tag_here(tag=tag) - self.at(*key).tag_here(tag=tag) + def set(self, path: str | tuple[str, ...], value: Any) -> None: + if isinstance(path, str): + path = (path,) + + if not path: + self.data = value + return + + *parts, last = path + + data = self.data + for part in parts: + data = data.setdefault(part, {}) + data[last] = value + + def merge(self, path: str | tuple[str, ...], value: Any) -> None: + self.set(path, _merge_values(self.get(path), value)) + + def tag(self, path: str | tuple[str, ...] = "_tag") -> None: + self.set(path, TAG) def to_text(self) -> TextFile: return TextFile(json.dumps(self.data)) diff --git a/pasch/file/toml.py b/pasch/file/toml.py index 688ffed..6005901 100644 --- a/pasch/file/toml.py +++ b/pasch/file/toml.py @@ -4,6 +4,7 @@ from typing import Any, Self import toml from .file import File +from .json import JsonFile from .text import TextFile @@ -15,31 +16,33 @@ class TomlFileProxy: def at(self, *path: str) -> Self: return TomlFileProxy(self.file, self.path + path) - def set(self, value: Any) -> None: - if not self.path: - self.file.set(value) + def set(self, path: str | tuple[str, ...], value: Any) -> None: + if isinstance(path, str): + path = (path,) - data = self.file.data - *parts, last = self.path - for part in parts: - data = data.setdefault(part, {}) - data[last] = value + self.file.set(self.path + path, value) class TomlFile(File): def __init__(self, data: Any = {}) -> None: - self.data = data + self.json = JsonFile(data) def at(self, *path: str) -> TomlFileProxy: return TomlFileProxy(self, path) - def set(self, value: Any) -> None: - self.data = value + def get(self, path: str | tuple[str, ...]) -> Any: + return self.json.get(path) + + def set(self, path: str | tuple[str, ...], value: Any) -> None: + self.json.set(path, value) + + def merge(self, path: str | tuple[str, ...], value: Any) -> None: + self.json.merge(path, value) def to_text(self) -> TextFile: file = TextFile() file.tag(comment="#") - file.append(toml.dumps(self.data), newline=False) + file.append(toml.dumps(self.json.data), newline=False) return file def to_bytes(self) -> bytes: