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

1from typing import Callable, Optional 

2 

3from phml.nodes import AST, Element, Root 

4from phml.utils.validate.test 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.utils import has_property, test, walk 

30 

31 self.indexed_tree = {} 

32 self.key = key 

33 

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) 

59 

60 def add(self, node: Element): 

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

62 

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 

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

76 

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) 

80 

81 def map(self, modifier: Callable) -> list: 

82 """Applies the passed modifier to each index. 

83 

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