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
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 13:16 -0600
1"""phml.utils.locate.find
3Collection of utility methods to find one or many of a specific node.
4"""
6from typing import Optional
8from phml.nodes import AST, All_Nodes, Element, Root
9from phml.travel.travel import path, walk
10from phml.validate import Test, check
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]
24def ancestor(*nodes: All_Nodes) -> Optional[All_Nodes]:
25 """Get the common ancestor between two nodes.
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.
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
38 def filter_func(node, total_path) -> bool:
39 return node in total_path
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)
47 return total_path[-1] if len(total_path) > 0 else None
50def find(start: Root | Element | AST, condition: Test, strict: bool = True) -> Optional[All_Nodes]:
51 """Walk the nodes children and return the desired node.
53 Returns the first node that matches the condition.
55 Args:
56 start (Root | Element): Starting node.
57 condition (Test): Condition to check against each node.
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
65 for node in walk(start):
66 if check(node, condition, strict=strict):
67 return node
69 return None
72def find_all(start: Root | Element | AST, condition: Test, strict: bool = True) -> list[All_Nodes]:
73 """Find all nodes that match the condition.
75 Args:
76 start (Root | Element): Starting node.
77 condition (Test): Condition to apply to each node.
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
85 results = []
86 for node in walk(start):
87 if check(node, condition, strict=strict):
88 results.append(node)
89 return results
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.
100 Args:
101 start (All_Nodes): Node to get sibling from.
102 condition (Test): Condition to check against each node.
104 Returns:
105 Optional[All_Nodes]: Returns the first sibling or None if there
106 are no siblings.
107 """
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
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.
128 Args:
129 start (All_Nodes): Node to get siblings from.
130 condition (Test): Condition to check against each node.
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 = []
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)
147 return matches
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.
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.
162 Returns:
163 Optional[All_Nodes]: The first node before the given node
164 or None if no prior siblings.
165 """
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
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.
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.
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 = []
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
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.
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.
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
232 if _range is not None:
233 start = _range.start
234 end = _range.stop
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