Coverage for phml\utils\locate\index.py: 14%
51 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-30 09:38 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-30 09:38 -0600
1from typing import Callable, Optional
3from phml.nodes import AST, Element, Root
4from phml.utils.validate.test import Test
7class Index:
8 """Uses the given key or key generator and creates a mutable dict of key value pairs
9 that can be easily indexed.
11 Nodes that don't match the condition or don't have a valid key are not indexed.
12 """
14 indexed_tree: dict[str, list[Element]]
15 """The indexed collection of elements"""
17 def __init__(
18 self, key: str | Callable, start: AST | Root | Element, condition: Optional[Test] = None
19 ):
20 """
21 Args:
22 `key` (str | Callable): Str represents the property to use as an index. Callable
23 represents a function to call on each element to generate a key. The returned key
24 must be able to be converted to a string. If none then element is skipped.
25 `start` (AST | Root | Element): The root or node to start at while indexing
26 `test` (Test): The test to apply to each node. Only valid/passing nodes
27 will be indexed
28 """
29 from phml.utils import has_property, test, walk
31 self.indexed_tree = {}
32 self.key = key
34 for node in walk(start):
35 if isinstance(node, Element):
36 if condition is not None:
37 if test(node, condition):
38 if isinstance(key, str) and has_property(node, key):
39 if node.properties[key] not in self.indexed_tree:
40 self.indexed_tree[node.properties[key]] = []
41 self.indexed_tree[node.properties[key]].append(node)
42 elif isinstance(key, Callable):
43 k = key(node)
44 if k is not None:
45 if k not in self.indexed_tree:
46 self.indexed_tree[k] = []
47 self.indexed_tree[str(k)].append(node)
48 else:
49 if isinstance(key, str) and has_property(node, key):
50 if node.properties[key] not in self.indexed_tree:
51 self.indexed_tree[node.properties[key]] = []
52 self.indexed_tree[node.properties[key]].append(node)
53 elif isinstance(key, Callable):
54 k = key(node)
55 if k is not None:
56 if k not in self.indexed_tree:
57 self.indexed_tree[k] = []
58 self.indexed_tree[str(k)].append(node)
60 def add(self, node: Element):
61 """Adds element to indexed collection if not already there."""
63 key = node.properties[self.key] if isinstance(self.key, str) else self.key(node)
64 if key not in self.indexed_tree:
65 self.indexed_tree[key] = node
67 if node not in self.indexed_tree[key]:
68 self.indexed_tree[key].append(node)
70 def remove(self, node: Element):
71 """Removes element from indexed collection if there."""
73 key = node.properties[self.key] if isinstance(self.key, str) else self.key(node)
74 if key in self.indexed_tree and node in self.indexed_tree[key]:
75 self.indexed_tree[key].remove(node)
77 def get(self, _key: str) -> Optional[list[Element]]:
78 """Get a specific index from the indexed tree."""
79 return self.indexed_tree.get(_key)
81 def map(self, modifier: Callable) -> list:
82 """Applies the passed modifier to each index.
84 Returns:
85 list of results generated by the modifier applied
86 to each index.
87 """
88 result = []
89 for value in self.indexed_tree.values():
90 result.extend([modifier(v) for v in value])
91 return result