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

1# pylint: disable=missing-module-docstring 

2from typing import Callable, Optional 

3 

4from phml.nodes import AST, Element, Root 

5from phml.utils.validate.check import Test 

6 

7 

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. 

11 

12 Nodes that don't match the condition or don't have a valid key are not indexed. 

13 """ 

14 

15 indexed_tree: dict[str, list[Element]] 

16 """The indexed collection of elements""" 

17 

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 

31 

32 if isinstance(start, AST): 

33 start = start.tree 

34 

35 self.indexed_tree = {} 

36 self.key = key 

37 

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) 

45 

46 def __iter__(self): 

47 return iter(self.indexed_tree) 

48 

49 def items(self) -> tuple[str, list]: 

50 """Get the key value pairs of all indexes.""" 

51 return self.indexed_tree.items() 

52 

53 def values(self) -> list[list]: 

54 """Get all the values in the collection.""" 

55 return self.indexed_tree.values() 

56 

57 def keys(self) -> list[str]: 

58 """Get all the keys in the collection.""" 

59 return self.indexed_tree.keys() 

60 

61 def add(self, node: Element): 

62 """Adds element to indexed collection if not already there.""" 

63 

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] 

67 

68 if node not in self.indexed_tree[key]: 

69 self.indexed_tree[key].append(node) 

70 

71 def remove(self, node: Element): 

72 """Removes element from indexed collection if there.""" 

73 

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) 

79 

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) 

83 

84 # Built in key functions 

85 

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.""" 

90 

91 return node.tag