diff --git a/chatmastermind/tags.py b/chatmastermind/tags.py index 28583a2..bfe5fd5 100644 --- a/chatmastermind/tags.py +++ b/chatmastermind/tags.py @@ -30,6 +30,67 @@ class Tag(str): return instance +def delete_tags(tags: set[Tag], tags_delete: set[Tag]) -> set[Tag]: + """ + Deletes the given tags and returns a new set. + """ + return tags.difference(tags_delete) + + +def add_tags(tags: set[Tag], tags_add: set[Tag]) -> set[Tag]: + """ + Adds the given tags and returns a new set. + """ + return set(sorted(tags | tags_add)) + + +def merge_tags(tags: set[Tag], tags_merge: list[set[Tag]]) -> set[Tag]: + """ + Merges the tags in 'tags_merge' into the current one and returns a new set. + """ + for ts in tags_merge: + tags |= ts + return tags + + +def rename_tags(tags: set[Tag], tags_rename: set[tuple[Tag, Tag]]) -> set[Tag]: + """ + Renames the given tags and returns a new set. The first tuple element + is the old name, the second one is the new name. + """ + for t in tags_rename: + if t[0] in tags: + tags.remove(t[0]) + tags.add(t[1]) + return set(sorted(tags)) + + +def match_tags(tags: set[Tag], tags_or: Optional[set[Tag]], tags_and: Optional[set[Tag]], + tags_not: Optional[set[Tag]]) -> bool: + """ + Checks if the given set 'tags' matches the given tag requirements: + - 'tags_or' : matches if this TagLine contains ANY of those tags + - 'tags_and': matches if this TagLine contains ALL of those tags + - 'tags_not': matches if this TagLine contains NONE of those tags + + Note that it's sufficient if 'tags' matches one of 'tags_or' or 'tags_and', + i. e. you can select a TagLine if it either contains one of the tags in 'tags_or' + or all of the tags in 'tags_and' but it must never contain any of the tags in + 'tags_not'. If 'tags_or' and 'tags_and' are 'None', they match all tags (tag + exclusion is still done if 'tags_not' is not 'None'). + """ + required_tags_present = False + excluded_tags_missing = False + if ((tags_or is None and tags_and is None) + or (tags_or and any(tag in tags for tag in tags_or)) # noqa: W503 + or (tags_and and all(tag in tags for tag in tags_and))): # noqa: W503 + required_tags_present = True + if ((tags_not is None) + or (not any(tag in tags for tag in tags_not))): # noqa: W503 + excluded_tags_missing = True + return required_tags_present and excluded_tags_missing + + class TagLine(str): """ A line of tags. It starts with a prefix ('TAGS:'), followed by a list of tags, @@ -71,37 +132,29 @@ class TagLine(str): def merge(self, taglines: set['TagLine']) -> 'TagLine': """ - Merges the tags of all given taglines into the current one - and returns a new TagLine. + Merges the tags of all given taglines into the current one and returns a new TagLine. """ - merged_tags = self.tags() - for tl in taglines: - merged_tags |= tl.tags() - return self.from_set(set(sorted(merged_tags))) + tags_merge = [tl.tags() for tl in taglines] + return self.from_set(merge_tags(self.tags(), tags_merge)) - def delete_tags(self, tags: set[Tag]) -> 'TagLine': + def delete_tags(self, tags_delete: set[Tag]) -> 'TagLine': """ Deletes the given tags and returns a new TagLine. """ - return self.from_set(self.tags().difference(tags)) + return self.from_set(delete_tags(self.tags(), tags_delete)) - def add_tags(self, tags: set[Tag]) -> 'TagLine': + def add_tags(self, tags_add: set[Tag]) -> 'TagLine': """ Adds the given tags and returns a new TagLine. """ - return self.from_set(set(sorted(self.tags() | tags))) + return self.from_set(add_tags(self.tags(), tags_add)) - def rename_tags(self, tags: set[tuple[Tag, Tag]]) -> 'TagLine': + def rename_tags(self, tags_rename: set[tuple[Tag, Tag]]) -> 'TagLine': """ Renames the given tags and returns a new TagLine. The first tuple element is the old name, the second one is the new name. """ - new_tags = self.tags() - for t in tags: - if t[0] in new_tags: - new_tags.remove(t[0]) - new_tags.add(t[1]) - return self.from_set(set(sorted(new_tags))) + return self.from_set(rename_tags(self.tags(), tags_rename)) def match_tags(self, tags_or: Optional[set[Tag]], tags_and: Optional[set[Tag]], tags_not: Optional[set[Tag]]) -> bool: @@ -117,14 +170,4 @@ class TagLine(str): 'tags_not'. If 'tags_or' and 'tags_and' are 'None', they match all tags (tag exclusion is still done if 'tags_not' is not 'None'). """ - tag_set = self.tags() - required_tags_present = False - excluded_tags_missing = False - if ((tags_or is None and tags_and is None) - or (tags_or and any(tag in tag_set for tag in tags_or)) # noqa: W503 - or (tags_and and all(tag in tag_set for tag in tags_and))): # noqa: W503 - required_tags_present = True - if ((tags_not is None) - or (not any(tag in tag_set for tag in tags_not))): # noqa: W503 - excluded_tags_missing = True - return required_tags_present and excluded_tags_missing + return match_tags(self.tags(), tags_or, tags_and, tags_not)