Coverage for phml\utils\locate\find.py: 86%

83 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-30 09:38 -0600

1from typing import Optional 

2 

3from phml.nodes import AST, All_Nodes, Element, Root 

4from phml.utils.travel import path, walk 

5from phml.utils.validate import Test, test 

6 

7__all__ = [ 

8 "ancestor", 

9 "find", 

10 "find_all", 

11 "find_after", 

12 "find_all_after", 

13 "find_all_before", 

14 "find_before", 

15 "find_all_between", 

16] 

17 

18 

19def ancestor(*nodes: All_Nodes) -> Optional[All_Nodes]: 

20 """Get the common ancestor between two nodes. 

21 

22 Args: 

23 *nodes (All_Nodes): A list of any number of nodes 

24 to find the common ancestor form. Worst case it will 

25 return the root. 

26 

27 Returns: 

28 Optional[All_Nodes]: The node that is the common 

29 ancestor or None if not found. 

30 """ 

31 total_path: list = None 

32 

33 for node in nodes: 

34 if total_path is not None: 

35 total_path = list(filter(lambda n: n in total_path, path(node))) 

36 else: 

37 total_path = path(node) 

38 

39 return total_path[-1] if len(total_path) > 0 else None 

40 

41 

42def find(node: Root | Element | AST, condition: Test) -> Optional[All_Nodes]: 

43 """Walk the nodes children and return the desired node. 

44 

45 Returns the first node that matches the condition. 

46 

47 Args: 

48 node (Root | Element): Starting node. 

49 condition (Test): Condition to check against each node. 

50 

51 Returns: 

52 Optional[All_Nodes]: Returns the found node or None if not found. 

53 """ 

54 if isinstance(node, AST): 

55 node = node.tree 

56 

57 for n in walk(node): 

58 if test(n, condition): 

59 return n 

60 

61 return None 

62 

63 

64def find_all(node: Root | Element | AST, condition: Test) -> list[All_Nodes]: 

65 """Find all nodes that match the condition. 

66 

67 Args: 

68 node (Root | Element): Starting node. 

69 condition (Test): Condition to apply to each node. 

70 

71 Returns: 

72 list[All_Nodes]: List of found nodes. Empty if no nodes are found. 

73 """ 

74 if isinstance(node, AST): 

75 node = node.tree 

76 

77 results = [] 

78 for n in walk(node): 

79 if test(n, condition): 

80 results.append(n) 

81 return results 

82 

83 

84def find_after( 

85 node: Root | Element | AST, 

86 condition: Optional[Test] = None, 

87) -> Optional[All_Nodes]: 

88 """Get the first sibling node following the provided node that matches 

89 the condition. 

90 

91 Args: 

92 node (All_Nodes): Node to get sibling from. 

93 condition (Test): Condition to check against each node. 

94 

95 Returns: 

96 Optional[All_Nodes]: Returns the first sibling or None if there 

97 are no siblings. 

98 """ 

99 if isinstance(node, AST): 

100 node = node.tree 

101 

102 idx = node.parent.children.index(node) 

103 if len(node.parent.children) - 1 > idx: 

104 for el in node.parent.children[idx + 1 :]: 

105 if condition is not None: 

106 if test(el, condition): 

107 return el 

108 else: 

109 return el 

110 return None 

111 

112 

113def find_all_after( 

114 node: Element, 

115 condition: Optional[Test] = None, 

116) -> list[All_Nodes]: 

117 """Get all sibling nodes that match the condition. 

118 

119 Args: 

120 node (All_Nodes): Node to get siblings from. 

121 condition (Test): Condition to check against each node. 

122 

123 Returns: 

124 list[All_Nodes]: Returns the all siblings that match the 

125 condition or an empty list if none were found. 

126 """ 

127 idx = node.parent.children.index(node) 

128 matches = [] 

129 

130 if len(node.parent.children) - 1 > idx: 

131 for el in node.parent.children[idx + 1 :]: 

132 if condition is not None: 

133 if test(el, condition): 

134 matches.append(el) 

135 else: 

136 matches.append(el) 

137 

138 return matches 

139 

140 

141def find_before( 

142 node: Element, 

143 condition: Optional[Test] = None, 

144) -> Optional[All_Nodes]: 

145 """Find the first sibling node before the given node. If a condition is applied 

146 then it will be the first sibling node that passes that condition. 

147 

148 Args: 

149 node (All_Nodes): The node to find the previous sibling from. 

150 condition (Optional[Test]): The test that is applied to each node. 

151 

152 Returns: 

153 Optional[All_Nodes]: The first node before the given node 

154 or None if no prior siblings. 

155 """ 

156 if isinstance(node, AST): 

157 node = node.tree 

158 

159 idx = node.parent.children.index(node) 

160 if idx > 0: 

161 for el in node.parent.children[idx - 1 :: -1]: 

162 if condition is not None: 

163 if test(el, condition): 

164 return el 

165 else: 

166 return el 

167 return None 

168 

169 

170def find_all_before( 

171 node: Element, 

172 condition: Optional[Test] = None, 

173) -> list[All_Nodes]: 

174 """Find all nodes that come before the given node. 

175 

176 Args: 

177 node (All_Nodes): The node to find all previous siblings from. 

178 condition (Optional[Test]): The condition to apply to each node. 

179 

180 Returns: 

181 list[All_Nodes]: A list of nodes that come before the given node. 

182 Empty list if no nodes were found. 

183 """ 

184 idx = node.parent.children.index(node) 

185 matches = [] 

186 

187 if idx > 0: 

188 for el in node.parent.children[:idx]: 

189 if condition is not None: 

190 if test(el, condition): 

191 matches.append(el) 

192 else: 

193 matches.append(el) 

194 return matches 

195 

196 

197def find_all_between( 

198 parent: Root | Element | AST, 

199 start: Optional[int] = 0, 

200 end: Optional[int] = 0, 

201 condition: Optional[Test] = None, 

202 _range: Optional[slice] = None, 

203) -> list[All_Nodes]: 

204 """Find all sibling nodes in parent that meet the provided condition from start index 

205 to end index. 

206 

207 Args: 

208 parent (Root | Element): The parent element to get nodes from. 

209 start (int, optional): The starting index, inclusive. Defaults to 0. 

210 end (int, optional): The ending index, exclusive. Defaults to 0. 

211 condition (Test, optional): Condition to apply to each node. Defaults to None. 

212 _range (slice, optional): Slice to apply to the parent nodes children instead of start and end indecies. Defaults to None. 

213 

214 Returns: 

215 list[All_Nodes]: List of all matching nodes or an empty list if none were found. 

216 """ 

217 if isinstance(parent, AST): 

218 parent = parent.tree 

219 

220 if _range is not None: 

221 start = _range.start 

222 end = _range.stop 

223 

224 results = [] 

225 if start >= 0 and end <= len(parent.children) and start < end: 

226 for node in parent.children[start:end]: 

227 if condition is not None: 

228 if test(node, condition): 

229 results.append(node) 

230 else: 

231 results.append(node) 

232 return results