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
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-30 09:38 -0600
1from typing import Optional
3from phml.nodes import AST, All_Nodes, Element, Root
4from phml.utils.travel import path, walk
5from phml.utils.validate import Test, test
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]
19def ancestor(*nodes: All_Nodes) -> Optional[All_Nodes]:
20 """Get the common ancestor between two nodes.
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.
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
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)
39 return total_path[-1] if len(total_path) > 0 else None
42def find(node: Root | Element | AST, condition: Test) -> Optional[All_Nodes]:
43 """Walk the nodes children and return the desired node.
45 Returns the first node that matches the condition.
47 Args:
48 node (Root | Element): Starting node.
49 condition (Test): Condition to check against each node.
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
57 for n in walk(node):
58 if test(n, condition):
59 return n
61 return None
64def find_all(node: Root | Element | AST, condition: Test) -> list[All_Nodes]:
65 """Find all nodes that match the condition.
67 Args:
68 node (Root | Element): Starting node.
69 condition (Test): Condition to apply to each node.
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
77 results = []
78 for n in walk(node):
79 if test(n, condition):
80 results.append(n)
81 return results
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.
91 Args:
92 node (All_Nodes): Node to get sibling from.
93 condition (Test): Condition to check against each node.
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
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
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.
119 Args:
120 node (All_Nodes): Node to get siblings from.
121 condition (Test): Condition to check against each node.
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 = []
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)
138 return matches
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.
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.
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
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
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.
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.
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 = []
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
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.
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.
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
220 if _range is not None:
221 start = _range.start
222 end = _range.stop
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