Coverage for phml\utils\locate\index.py: 100%
43 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 11:28 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 11:28 -0600
1# pylint: disable=missing-module-docstring
2from typing import Callable, Optional
4from phml.nodes import AST, Element, Root
5from phml.utils.validate.check import Test
8class Index:
9 """Uses the given key or key generator and creates a mutable dict of key value pairs
10 that can be easily indexed.
12 Nodes that don't match the condition or don't have a valid key are not indexed.
13 """
15 indexed_tree: dict[str, list[Element]]
16 """The indexed collection of elements"""
18 def __init__(
19 self, key: str | Callable, start: AST | Root | Element, condition: Optional[Test] = None
20 ):
21 """
22 Args:
23 `key` (str | Callable): Str represents the property to use as an index. Callable
24 represents a function to call on each element to generate a key. The returned key
25 must be able to be converted to a string. If none then element is skipped.
26 `start` (AST | Root | Element): The root or node to start at while indexing
27 `test` (Test): The test to apply to each node. Only valid/passing nodes
28 will be indexed
29 """
30 from phml.utils import check, walk # pylint: disable=import-outside-toplevel
32 if isinstance(start, AST):
33 start = start.tree
35 self.indexed_tree = {}
36 self.key = key
38 for node in walk(start):
39 if isinstance(node, Element):
40 if condition is not None:
41 if check(node, condition):
42 self.add(node)
43 else:
44 self.add(node)
46 def __iter__(self):
47 return iter(self.indexed_tree)
49 def items(self) -> tuple[str, list]:
50 """Get the key value pairs of all indexes."""
51 return self.indexed_tree.items()
53 def values(self) -> list[list]:
54 """Get all the values in the collection."""
55 return self.indexed_tree.values()
57 def keys(self) -> list[str]:
58 """Get all the keys in the collection."""
59 return self.indexed_tree.keys()
61 def add(self, node: Element):
62 """Adds element to indexed collection if not already there."""
64 key = node[self.key] if isinstance(self.key, str) else self.key(node)
65 if key not in self.indexed_tree:
66 self.indexed_tree[key] = [node]
68 if node not in self.indexed_tree[key]:
69 self.indexed_tree[key].append(node)
71 def remove(self, node: Element):
72 """Removes element from indexed collection if there."""
74 key = node[self.key] if isinstance(self.key, str) else self.key(node)
75 if key in self.indexed_tree and node in self.indexed_tree[key]:
76 self.indexed_tree[key].remove(node)
77 if len(self.indexed_tree[key]) == 0:
78 self.indexed_tree.pop(key, None)
80 def get(self, _key: str) -> Optional[list[Element]]:
81 """Get a specific index from the indexed tree."""
82 return self.indexed_tree.get(_key)
84 # Built in key functions
86 @classmethod
87 def key_by_tag(cls, node: Element) -> str:
88 """Builds the key from an elements tag. If the node is not an element
89 then the node's type is returned."""
91 return node.tag