diff --git a/pkgs/clan-cli/clan_cli/inventory/__init__.py b/pkgs/clan-cli/clan_cli/inventory/__init__.py index a2cb05af1..18f743e8f 100644 --- a/pkgs/clan-cli/clan_cli/inventory/__init__.py +++ b/pkgs/clan-cli/clan_cli/inventory/__init__.py @@ -119,6 +119,37 @@ def load_inventory_json( return inventory +def patch(d: dict[str, Any], path: str, content: Any) -> None: + """ + Update the value at a specific dot-separated path in a nested dictionary. + + :param d: The dictionary to update. + :param path: The dot-separated path to the key (e.g., 'foo.bar'). + :param content: The new value to set. + """ + keys = path.split(".") + current = d + for key in keys[:-1]: + current = current.setdefault(key, {}) + current[keys[-1]] = content + + +@API.register +def patch_inventory_with(base_dir: Path, section: str, content: dict[str, Any]) -> None: + inventory_file = get_path(base_dir) + + curr_inventory = {} + with inventory_file.open("r") as f: + curr_inventory = json.load(f) + + patch(curr_inventory, section, content) + + with inventory_file.open("w") as f: + json.dump(curr_inventory, f, indent=2) + + commit_file(inventory_file, base_dir, commit_message=f"inventory.{section}: Update") + + @API.register def set_inventory( inventory: Inventory | dict[str, Any], flake_dir: str | Path, message: str diff --git a/pkgs/clan-cli/tests/test_patch_inventory.py b/pkgs/clan-cli/tests/test_patch_inventory.py new file mode 100644 index 000000000..c788288bb --- /dev/null +++ b/pkgs/clan-cli/tests/test_patch_inventory.py @@ -0,0 +1,36 @@ +# Functions to test +from clan_cli.inventory import patch + + +def test_patch_nested() -> None: + orig = {"a": 1, "b": {"a": 2.1, "b": 2.2}, "c": 3} + + patch(orig, "b.b", "foo") + + # Should only update the nested value + assert orig == {"a": 1, "b": {"a": 2.1, "b": "foo"}, "c": 3} + + +def test_patch_nested_dict() -> None: + orig = {"a": 1, "b": {"a": 2.1, "b": 2.2}, "c": 3} + + # This should update the whole "b" dict + # Which also removes all other keys + patch(orig, "b", {"b": "foo"}) + + # Should only update the nested value + assert orig == {"a": 1, "b": {"b": "foo"}, "c": 3} + + +def test_create_missing_paths() -> None: + orig = {"a": 1} + + patch(orig, "b.c", "foo") + + # Should only update the nested value + assert orig == {"a": 1, "b": {"c": "foo"}} + + orig = {} + patch(orig, "a.b.c", "foo") + + assert orig == {"a": {"b": {"c": "foo"}}}