Skip to content

Commit 417f05d

Browse files
committed
feat: find relative path to allow unix folder expression for leading sep scenario
1 parent ce55085 commit 417f05d

File tree

3 files changed

+52
-13
lines changed

3 files changed

+52
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Changed:
9+
- Tree Search: Search relative path allows unix folder expression for leading sep symbol.
810

911
## [0.23.0] - 2024-12-26
1012
### Changed:

bigtree/tree/search.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -243,44 +243,51 @@ def find_relative_paths(
243243
"""
244244
sep = tree.sep
245245
if path_name.startswith(sep):
246-
resolved_node = find_full_path(tree, path_name)
247-
return (resolved_node,)
246+
path_list = path_name.rstrip(sep).lstrip(sep).split(sep)
247+
if path_list[0] not in (tree.root.node_name, "..", ".", "*"):
248+
raise ValueError(
249+
f"Path {path_name} does not match the root node name {tree.root.node_name}"
250+
)
251+
if path_list[0] == tree.root.node_name:
252+
path_list[0] = "."
253+
result = find_relative_paths(tree.root, sep.join(path_list))
254+
return result
248255
path_name = path_name.rstrip(sep).lstrip(sep)
249256
path_list = path_name.split(sep)
250257
wildcard_indicator = "*" in path_name
251258
resolved_nodes: List[NodeT] = []
252259

253-
def resolve(node: NodeT, path_idx: int) -> None:
260+
def resolve(_node: NodeT, path_idx: int) -> None:
254261
"""Resolve node based on path name
255262
256263
Args:
257-
node (Node): current node
264+
_node (Node): current node
258265
path_idx (int): current index in path_list
259266
"""
260267
if path_idx == len(path_list):
261-
resolved_nodes.append(node)
268+
resolved_nodes.append(_node)
262269
else:
263270
path_component = path_list[path_idx]
264271
if path_component == ".":
265-
resolve(node, path_idx + 1)
272+
resolve(_node, path_idx + 1)
266273
elif path_component == "..":
267-
if node.is_root:
274+
if _node.is_root:
268275
raise exceptions.SearchError(
269276
"Invalid path name. Path goes beyond root node."
270277
)
271-
resolve(node.parent, path_idx + 1)
278+
resolve(_node.parent, path_idx + 1)
272279
elif path_component == "*":
273-
for child in node.children:
280+
for child in _node.children:
274281
resolve(child, path_idx + 1)
275282
else:
276-
node = find_child_by_name(node, path_component)
277-
if not node:
283+
child_node = find_child_by_name(_node, path_component)
284+
if not child_node:
278285
if not wildcard_indicator:
279286
raise exceptions.SearchError(
280287
f"Invalid path name. Node {path_component} cannot be found."
281288
)
282289
else:
283-
resolve(node, path_idx + 1)
290+
resolve(child_node, path_idx + 1)
284291

285292
resolve(tree, 0)
286293
result = tuple(resolved_nodes)

tests/tree/test_search.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,20 @@ def test_find_relative_paths_sep_leading(self):
255255
expected,
256256
), f"Expected find_relative_paths to return {expected}, received {actual}"
257257

258+
def test_find_relative_paths_sep_leading_wildcard(self):
259+
inputs = ["/*", "/a/b/*", "/a/c/*", "/a/b/e/*"]
260+
expected_ans = [
261+
(self.b, self.c),
262+
(self.d, self.e),
263+
(self.f,),
264+
(self.g, self.h),
265+
]
266+
for input_, expected in zip(inputs, expected_ans):
267+
actual = search.find_relative_paths(self.b, input_)
268+
assert (
269+
actual == expected
270+
), f"Expected find_relative_paths to return {expected}, received {actual}"
271+
258272
def test_find_relative_paths_sep_trailing(self):
259273
inputs = [self.b, self.c, self.d, self.e, self.f, self.g, self.h]
260274
expected_ans = [self.a, self.a, self.b, self.b, self.c, self.e, self.e]
@@ -264,6 +278,17 @@ def test_find_relative_paths_sep_trailing(self):
264278
expected,
265279
), f"Expected find_relative_paths to return {expected}, received {actual}"
266280

281+
def test_find_relative_paths_wrong_root_error(self):
282+
inputs = ["/b/a", "/c"]
283+
for input_ in inputs:
284+
with pytest.raises(ValueError) as exc_info:
285+
search.find_relative_paths(self.a, input_)
286+
assert str(
287+
exc_info.value
288+
) == Constants.ERROR_SEARCH_FULL_PATH_INVALID_ROOT.format(
289+
path_name=input_, root_name="a"
290+
)
291+
267292
def test_find_relative_paths_wrong_node_error(self):
268293
inputs = [
269294
("a/e", "a"),
@@ -280,7 +305,12 @@ def test_find_relative_paths_wrong_node_error(self):
280305
)
281306

282307
def test_find_relative_paths_wrong_path_error(self):
283-
inputs_list = [(self.a, ["../"]), (self.b, ["../../"])]
308+
inputs_list = [
309+
(self.a, ["../"]),
310+
(self.b, ["../../"]),
311+
(self.a, ["/../"]),
312+
(self.b, ["/.."]),
313+
]
284314
for inputs in inputs_list:
285315
for input_ in inputs[1]:
286316
with pytest.raises(exceptions.SearchError) as exc_info:

0 commit comments

Comments
 (0)