Files
buttonplus-esphome-generator/src/buttonplus_generator/merger.py
T

91 lines
3.0 KiB
Python

from typing import Any, Dict, List
def deep_merge(a: Dict[str, Any], b: Dict[str, Any]) -> Dict[str, Any]:
"""Recursively merge b into a and return a new dict.
Lists are concatenated; scalar values in b override a.
"""
result = dict(a)
for k, v in b.items():
# Key from b also in a
if k in result:
# a[k] and b[k] are both a dictionary: recursive deep_merge
if isinstance(result[k], dict) and isinstance(v, dict):
result[k] = deep_merge(result[k], v)
# a[k] and b[k] are both a list: list_merge
elif isinstance(result[k], list) and isinstance(v, list):
result[k] = list_merge(result[k], v)
else:
result[k] = v
else:
result[k] = v
return result
def list_merge(a: List[Any], b: List[Any]) -> List[Any]:
"""
Merge two list together based on id's. This is similar to !extend in YAML.
Steps:
1. Check if both lists contain Dicts with an `id` key in them
1a. Otherwise just return a + b (extend)
2. For every entry in a:
3. Store id in a list for later
4. Find the same id in b, if not found add to result without merge
5. Perform a deep_merge on the dictionary, add to result list
6. Ensure there are no id's missing from b, by checking every entry agains the list from step 3
7. Return result
"""
# If either list is empty just concatenate
if not a or not b:
return list(a) + list(b)
# Detect whether both lists contain dicts with an 'id' key
a_has_id = any(isinstance(x, dict) and "id" in x for x in a)
b_has_id = any(isinstance(x, dict) and "id" in x for x in b)
# If there are no id-based dicts in either list, just extend
if not (a_has_id and b_has_id):
return list(a) + list(b)
result: List[Any] = []
seen_ids = set()
# Merge entries from `a`, matching by `id` in `b` when present
for item in a:
if isinstance(item, dict) and "id" in item:
id_val = item.get("id")
# find matching entry in b
match = None
for bi in b:
if isinstance(bi, dict) and bi.get("id") == id_val:
match = bi
break
if match is not None:
# deep_merge returns a new dict
merged = deep_merge(item, match)
result.append(merged)
else:
result.append(item)
if id_val is not None:
seen_ids.add(id_val)
else:
# Not an id'd dict, keep as-is
result.append(item)
# Add remaining entries from b that were not merged or that don't have ids
for item in b:
if isinstance(item, dict) and "id" in item:
id_val = item.get("id")
if id_val is None:
result.append(item)
elif id_val not in seen_ids:
result.append(item)
else:
result.append(item)
return result