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

1from typing import Callable, Optional 

2 

3from phml.nodes import AST, Element, Root 

4from phml.validate.check import Test 

5 

6 

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. 

10 

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

12 """ 

13 

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

15 """The indexed collection of elements""" 

16 

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 

30 

31 if isinstance(start, AST): 

32 start = start.tree 

33 

34 self.indexed_tree = {} 

35 self.key = key 

36 

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) 

44 

45 def __iter__(self): 

46 return iter(self.indexed_tree) 

47 

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

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

50 return self.indexed_tree.items() 

51 

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

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

54 return self.indexed_tree.values() 

55 

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

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

58 return self.indexed_tree.keys() 

59 

60 def add(self, node: Element): 

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

62 

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] 

66 

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

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

69 

70 def remove(self, node: Element): 

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

72 

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) 

78 

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) 

82 

83 # Built in key functions 

84 

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

89 

90 return node.tag