Coverage for phml\locate\index.py: 100%
43 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 16:33 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 16:33 -0600
1from typing import Callable, Optional
3from phml.nodes import AST, Element, Root
4from phml.validate.check 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 import check, walk # pylint: disable=import-outside-toplevel
31 if isinstance(start, AST):
32 start = start.tree
34 self.indexed_tree = {}
35 self.key = key
37 for node in walk(start):
38 if isinstance(node, Element):
39 if condition is not None:
40 if check(node, condition):
41 self.add(node)
42 else:
43 self.add(node)
45 def __iter__(self):
46 return iter(self.indexed_tree)
48 def items(self) -> tuple[str, list]:
49 """Get the key value pairs of all indexes."""
50 return self.indexed_tree.items()
52 def values(self) -> list[list]:
53 """Get all the values in the collection."""
54 return self.indexed_tree.values()
56 def keys(self) -> list[str]:
57 """Get all the keys in the collection."""
58 return self.indexed_tree.keys()
60 def add(self, node: Element):
61 """Adds element to indexed collection if not already there."""
63 key = node[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[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)
76 if len(self.indexed_tree[key]) == 0:
77 self.indexed_tree.pop(key, None)
79 def get(self, _key: str) -> Optional[list[Element]]:
80 """Get a specific index from the indexed tree."""
81 return self.indexed_tree.get(_key)
83 # Built in key functions
85 @classmethod
86 def key_by_tag(cls, node: Element) -> str:
87 """Builds the key from an elements tag. If the node is not an element
88 then the node's type is returned."""
90 return node.tag