1#!/usr/bin/env python3
2"""Deep-merge a nix-managed toml into a mutable one, leaving unmanaged keys alone."""
3
4import os
5import sys
6import tempfile
7
8import tomlkit
9from tomlkit.items import AbstractTable
10
11
12def merge(target, managed):
13 for key, value in managed.items():
14 existing = target.get(key)
15 if isinstance(value, AbstractTable) and isinstance(existing, AbstractTable):
16 merge(existing, value)
17 else:
18 target[key] = value
19
20
21def load(path, missing_ok=False):
22 try:
23 with open(path, encoding="utf-8") as f:
24 return tomlkit.parse(f.read())
25 except FileNotFoundError:
26 if missing_ok:
27 return tomlkit.document()
28 sys.exit(f"codex-config-upsert: {path}: no such file")
29 except (OSError, tomlkit.exceptions.TOMLKitError) as e:
30 sys.exit(f"codex-config-upsert: {path}: {e}")
31
32
33def main():
34 if len(sys.argv) != 3:
35 sys.exit("usage: codex-config-upsert.py <managed.toml> <target.toml>")
36 managed_path, target_path = sys.argv[1], sys.argv[2]
37
38 target = load(target_path, missing_ok=True)
39 merge(target, load(managed_path))
40
41 merged = tomlkit.dumps(target).encode("utf-8")
42 try:
43 with open(target_path, "rb") as f:
44 if f.read() == merged:
45 return
46 mode = os.stat(target_path).st_mode & 0o777
47 except FileNotFoundError:
48 mode = 0o644
49
50 directory = os.path.dirname(os.path.abspath(target_path))
51 fd, tmp = tempfile.mkstemp(dir=directory)
52 try:
53 with os.fdopen(fd, "wb") as f:
54 f.write(merged)
55 os.chmod(tmp, mode)
56 os.replace(tmp, target_path)
57 except BaseException:
58 os.unlink(tmp)
59 raise
60
61
62if __name__ == "__main__":
63 main()