Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [0.23.0] - 2024-12-26
### Changed:
Expand Down
31 changes: 19 additions & 12 deletions bigtree/tree/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,44 +243,51 @@ def find_relative_paths(
"""
sep = tree.sep
if path_name.startswith(sep):
resolved_node = find_full_path(tree, path_name)
return (resolved_node,)
path_list = path_name.rstrip(sep).lstrip(sep).split(sep)
if path_list[0] not in (tree.root.node_name, "..", ".", "*"):
raise ValueError(
f"Path {path_name} does not match the root node name {tree.root.node_name}"
)
if path_list[0] == tree.root.node_name:
path_list[0] = "."
result = find_relative_paths(tree.root, sep.join(path_list))
return result
path_name = path_name.rstrip(sep).lstrip(sep)
path_list = path_name.split(sep)
wildcard_indicator = "*" in path_name
resolved_nodes: List[NodeT] = []

def resolve(node: NodeT, path_idx: int) -> None:
def resolve(_node: NodeT, path_idx: int) -> None:
"""Resolve node based on path name

Args:
node (Node): current node
_node (Node): current node
path_idx (int): current index in path_list
"""
if path_idx == len(path_list):
resolved_nodes.append(node)
resolved_nodes.append(_node)
else:
path_component = path_list[path_idx]
if path_component == ".":
resolve(node, path_idx + 1)
resolve(_node, path_idx + 1)
elif path_component == "..":
if node.is_root:
if _node.is_root:
raise exceptions.SearchError(
"Invalid path name. Path goes beyond root node."
)
resolve(node.parent, path_idx + 1)
resolve(_node.parent, path_idx + 1)
elif path_component == "*":
for child in node.children:
for child in _node.children:
resolve(child, path_idx + 1)
else:
node = find_child_by_name(node, path_component)
if not node:
child_node = find_child_by_name(_node, path_component)
if not child_node:
if not wildcard_indicator:
raise exceptions.SearchError(
f"Invalid path name. Node {path_component} cannot be found."
)
else:
resolve(node, path_idx + 1)
resolve(child_node, path_idx + 1)

resolve(tree, 0)
result = tuple(resolved_nodes)
Expand Down
32 changes: 31 additions & 1 deletion tests/tree/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,20 @@ def test_find_relative_paths_sep_leading(self):
expected,
), f"Expected find_relative_paths to return {expected}, received {actual}"

def test_find_relative_paths_sep_leading_wildcard(self):
inputs = ["/*", "/a/b/*", "/a/c/*", "/a/b/e/*"]
expected_ans = [
(self.b, self.c),
(self.d, self.e),
(self.f,),
(self.g, self.h),
]
for input_, expected in zip(inputs, expected_ans):
actual = search.find_relative_paths(self.b, input_)
assert (
actual == expected
), f"Expected find_relative_paths to return {expected}, received {actual}"

def test_find_relative_paths_sep_trailing(self):
inputs = [self.b, self.c, self.d, self.e, self.f, self.g, self.h]
expected_ans = [self.a, self.a, self.b, self.b, self.c, self.e, self.e]
Expand All @@ -264,6 +278,17 @@ def test_find_relative_paths_sep_trailing(self):
expected,
), f"Expected find_relative_paths to return {expected}, received {actual}"

def test_find_relative_paths_wrong_root_error(self):
inputs = ["/b/a", "/c"]
for input_ in inputs:
with pytest.raises(ValueError) as exc_info:
search.find_relative_paths(self.a, input_)
assert str(
exc_info.value
) == Constants.ERROR_SEARCH_FULL_PATH_INVALID_ROOT.format(
path_name=input_, root_name="a"
)

def test_find_relative_paths_wrong_node_error(self):
inputs = [
("a/e", "a"),
Expand All @@ -280,7 +305,12 @@ def test_find_relative_paths_wrong_node_error(self):
)

def test_find_relative_paths_wrong_path_error(self):
inputs_list = [(self.a, ["../"]), (self.b, ["../../"])]
inputs_list = [
(self.a, ["../"]),
(self.b, ["../../"]),
(self.a, ["/../"]),
(self.b, ["/.."]),
]
for inputs in inputs_list:
for input_ in inputs[1]:
with pytest.raises(exceptions.SearchError) as exc_info:
Expand Down
Loading