From 2e5d51b0af83b2ff94bb4c1ee2b8560073a6f119 Mon Sep 17 00:00:00 2001 From: jan Date: Sun, 27 Feb 2022 21:18:27 -0800 Subject: [PATCH] resolve name conflicts rather than just giving up this lets us merge libraries without having to mutate `other`, which brings a bunch of problems around cache invalidation --- masque/library/device_library.py | 27 ++++++++++++++---- masque/library/library.py | 49 +++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/masque/library/device_library.py b/masque/library/device_library.py index f1a3b6f..b00ef8c 100644 --- a/masque/library/device_library.py +++ b/masque/library/device_library.py @@ -88,7 +88,12 @@ class DeviceLibrary: """ self.generators[const.pattern.name] = lambda: const - def add(self: L, other: L) -> L: + def add( + self: D, + other: D, + use_ours: Callable[[str], bool] = lambda name: False, + use_theirs: Callable[[str], bool] = lambda name: False, + ) -> D: """ Add keys from another library into this one. @@ -96,15 +101,25 @@ class DeviceLibrary: Args: other: The library to insert keys from + use_ours: Decision function for name conflicts. Will be called with duplicate cell names. + Should return `True` if the value from `self` should be used. + use_theirs: Decision function for name conflicts. Same format as `use_ours`. + Should return `True` if the value from `other` should be used. + `use_ours` takes priority over `use_theirs`. Returns: self """ - conflicts = [key for key in other.generators - if key in self.generators] + duplicates = set(self.keys()) & set(other.keys()) + keep_ours = set(name for name in duplicates if use_ours(name)) + keep_theirs = set(name for name in duplicates - keep_ours if use_theirs(name)) + conflicts = duplicates - keep_ours - keep_theirs if conflicts: - raise LibraryError('Duplicate keys encountered in library merge: ' + pformat(conflicts)) + raise DeviceLibraryError('Duplicate keys encountered in DeviceLibrary merge: ' + + pformat(conflicts)) - self.generators.update(other.generators) - self.cache.update(other.cache) + for name in set(other.generators.keys()) - keep_ours: + self.generators[name] = other.generators[name] + if name in other.cache: + self.cache[name] = other.cache[name] return self diff --git a/masque/library/library.py b/masque/library/library.py index 38e942b..927eeff 100644 --- a/masque/library/library.py +++ b/masque/library/library.py @@ -205,31 +205,52 @@ class Library: _ = self.get_secondary(*key2) return self - def add(self: L, other: L) -> L: + def add( + self: L, + other: L, + use_ours: Callable[[Union[str, Tuple[str, str]]], bool] = lambda name: False, + use_theirs: Callable[[Union[str, Tuple[str, str]]], bool] = lambda name: False, + ) -> L: """ Add keys from another library into this one. - There must be no conflicting keys. - Args: other: The library to insert keys from - + use_ours: Decision function for name conflicts. + May be called with cell names and (name, tag) tuples for primary or + secondary cells, respectively. + Should return `True` if the value from `self` should be used. + use_theirs: Decision function for name conflicts. Same format as `use_ours`. + Should return `True` if the value from `other` should be used. + `use_ours` takes priority over `use_theirs`. Returns: self """ - conflicts = [key for key in other.primary - if key in self.primary] - if conflicts: - raise LibraryError('Duplicate keys encountered in library merge: ' + pformat(conflicts)) + duplicates1 = set(self.primary.keys()) & set(other.primary.keys()) + duplicates2 = set(self.secondary.keys()) & set(other.secondary.keys()) + keep_ours1 = set(name for name in duplicates1 if use_ours(name)) + keep_ours2 = set(name for name in duplicates2 if use_ours(name)) + keep_theirs1 = set(name for name in duplicates1 - keep_ours1 if use_theirs(name)) + keep_theirs2 = set(name for name in duplicates2 - keep_ours2 if use_theirs(name)) + conflicts1 = duplicates1 - keep_ours1 - keep_theirs1 + conflicts2 = duplicates2 - keep_ours2 - keep_theirs2 + + if conflicts1: + raise LibraryError('Unresolved duplicate keys encountered in library merge: ' + pformat(conflicts1)) - conflicts2 = [key2 for key2 in other.secondary - if key2 in self.secondary] if conflicts2: - raise LibraryError('Duplicate secondary keys encountered in library merge: ' + pformat(conflicts2)) + raise LibraryError('Unresolved duplicate secondary keys encountered in library merge: ' + pformat(conflicts2)) + + for key1 in set(other.primary.keys()) - keep_ours1: + self[key1] = other.primary[key1] + if key1 in other.cache: + self.cache[key1] = other.cache[key1] + + for key2 in set(other.secondary.keys()) - keep_ours2: + self.set_secondary(*key2, other.secondary[key2]) + if key2 in other.cache: + self.cache[key2] = other.cache[key2] - self.primary.update(other.primary) - self.secondary.update(other.secondary) - self.cache.update(other.cache) return self def demote(self, key: str) -> None: