Coverage for phml\locate\find.py: 100%

83 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-12-08 13:16 -0600

1"""phml.utils.locate.find 

2 

3Collection of utility methods to find one or many of a specific node. 

4""" 

5 

6from typing import Optional 

7 

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

9from phml.travel.travel import path, walk 

10from phml.validate import Test, check 

11 

12__all__ = [ 

13 "ancestor", 

14 "find", 

15 "find_all", 

16 "find_after", 

17 "find_all_after", 

18 "find_all_before", 

19 "find_before", 

20 "find_all_between", 

21] 

22 

23 

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

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

26 

27 Args: 

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

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

30 return the root. 

31 

32 Returns: 

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

34 ancestor or None if not found. 

35 """ 

36 total_path: list = None 

37 

38 def filter_func(node, total_path) -> bool: 

39 return node in total_path 

40 

41 for node in nodes: 

42 if total_path is not None: 

43 total_path = list(filter(lambda n: filter_func(n, total_path), path(node))) 

44 else: 

45 total_path = path(node) 

46 

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

48 

49 

50def find(start: Root | Element | AST, condition: Test, strict: bool = True) -> Optional[All_Nodes]: 

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

52 

53 Returns the first node that matches the condition. 

54 

55 Args: 

56 start (Root | Element): Starting node. 

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

58 

59 Returns: 

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

61 """ 

62 if isinstance(start, AST): 

63 start = start.tree 

64 

65 for node in walk(start): 

66 if check(node, condition, strict=strict): 

67 return node 

68 

69 return None 

70 

71 

72def find_all(start: Root | Element | AST, condition: Test, strict: bool = True) -> list[All_Nodes]: 

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

74 

75 Args: 

76 start (Root | Element): Starting node. 

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

78 

79 Returns: 

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

81 """ 

82 if isinstance(start, AST): 

83 start = start.tree 

84 

85 results = [] 

86 for node in walk(start): 

87 if check(node, condition, strict=strict): 

88 results.append(node) 

89 return results 

90 

91 

92def find_after( 

93 start: All_Nodes, 

94 condition: Optional[Test] = None, 

95 strict: bool = True, 

96) -> Optional[All_Nodes]: 

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

98 the condition. 

99 

100 Args: 

101 start (All_Nodes): Node to get sibling from. 

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

103 

104 Returns: 

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

106 are no siblings. 

107 """ 

108 

109 if not isinstance(start, (AST, Root)): 

110 idx = start.parent.children.index(start) 

111 if len(start.parent.children) - 1 > idx: 

112 for node in start.parent.children[idx + 1 :]: 

113 if condition is not None: 

114 if check(node, condition, strict=strict): 

115 return node 

116 else: 

117 return node 

118 return None 

119 

120 

121def find_all_after( 

122 start: Element, 

123 condition: Optional[Test] = None, 

124 strict: bool = True, 

125) -> list[All_Nodes]: 

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

127 

128 Args: 

129 start (All_Nodes): Node to get siblings from. 

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

131 

132 Returns: 

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

134 condition or an empty list if none were found. 

135 """ 

136 idx = start.parent.children.index(start) 

137 matches = [] 

138 

139 if len(start.parent.children) - 1 > idx: 

140 for node in start.parent.children[idx + 1 :]: 

141 if condition is not None: 

142 if check(node, condition, strict=strict): 

143 matches.append(node) 

144 else: 

145 matches.append(node) 

146 

147 return matches 

148 

149 

150def find_before( 

151 start: All_Nodes, 

152 condition: Optional[Test] = None, 

153 strict: bool = True, 

154) -> Optional[All_Nodes]: 

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

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

157 

158 Args: 

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

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

161 

162 Returns: 

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

164 or None if no prior siblings. 

165 """ 

166 

167 if not isinstance(start, (AST, Root)): 

168 idx = start.parent.children.index(start) 

169 if idx > 0: 

170 for node in start.parent.children[idx - 1 :: -1]: 

171 if condition is not None: 

172 if check(node, condition, strict=strict): 

173 return node 

174 else: 

175 return node 

176 return None 

177 

178 

179def find_all_before( 

180 start: Element, 

181 condition: Optional[Test] = None, 

182 strict: bool = True, 

183) -> list[All_Nodes]: 

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

185 

186 Args: 

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

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

189 

190 Returns: 

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

192 Empty list if no nodes were found. 

193 """ 

194 idx = start.parent.children.index(start) 

195 matches = [] 

196 

197 if idx > 0: 

198 for node in start.parent.children[:idx]: 

199 if condition is not None: 

200 if check(node, condition, strict=strict): 

201 matches.append(node) 

202 else: 

203 matches.append(node) 

204 return matches 

205 

206 

207def find_all_between( 

208 parent: Root | Element | AST, 

209 start: Optional[int] = 0, 

210 end: Optional[int] = 0, 

211 condition: Optional[Test] = None, 

212 _range: Optional[slice] = None, 

213 strict: bool = True, 

214) -> list[All_Nodes]: 

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

216 to end index. 

217 

218 Args: 

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

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

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

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

223 _range (slice, optional): Slice to apply to the parent nodes children instead of start and 

224 end indecies. Defaults to None. 

225 

226 Returns: 

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

228 """ 

229 if isinstance(parent, AST): 

230 parent = parent.tree 

231 

232 if _range is not None: 

233 start = _range.start 

234 end = _range.stop 

235 

236 results = [] 

237 if start in range(0, end) and end in range(start, len(parent.children) + 1): 

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

239 if condition is not None: 

240 if check(node, condition, strict=strict): 

241 results.append(node) 

242 else: 

243 results.append(node) 

244 return results