Skip to content

Commit

Permalink
rework
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed Oct 25, 2024
1 parent b59624a commit e49c3b6
Showing 1 changed file with 35 additions and 32 deletions.
67 changes: 35 additions & 32 deletions yarl/_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,49 +43,48 @@ def normalize_path(path: str) -> str:
return prefix + "/".join(normalize_path_segments(segments))


class SimplePath:
__slots__ = ("_tail", "_root", "path")
class SimplePath(tuple):

def __init__(self, path: str) -> None:
"""Initialize a SimplePath object."""
self._tail = [x for x in path.split("/") if x and x != "."]
self._root = "/" if path[0] == "/" else ""
self.path = self._root + ("/".join(self._tail) if self._tail else ".")
def __str__(self) -> str:
"""Return the string representation of the path."""
root, tail = self
return root + ("/".join(tail) if tail else ".")

def parent(self) -> "SimplePath":
"""Return the parent path of a given path."""
parent = object.__new__(SimplePath)
parent._tail = self._tail[:-1]
parent._root = self._root
parent.path = self._root + ("/".join(parent._tail) if parent._tail else ".")
return parent
root, tail = self
return SimplePath((root, tail[:-1]))

@property
def name(self) -> str:
"""Return the last part of the path."""
return (self._tail[-1] if self._tail else "") or "."
_, tail = self
return (tail[-1] if tail else "") or "."

@property
def parts_count(self) -> int:
"""Return the number of parts in the path."""
return len(self._tail) + bool(self._root)
root, tail = self
return len(tail) + bool(root)

@property
def parts(self):
"""An object providing sequence-like access to the
components in the filesystem path."""
if self._root:
return (self._root,) + tuple(self._tail)
return tuple(self._tail)
root, tail = self
return (root,) + tail if root else tail

def parents(self) -> Generator["SimplePath", None, None]:
"""Return a list of parent paths for a given path."""
for i in range(len(self._tail) - 1, -1, -1):
parent = object.__new__(SimplePath)
parent._tail = self._tail[:i]
parent._root = self._root
parent.path = self._root + ("/".join(parent._tail) if parent._tail else ".")
yield parent
root, tail = self
for i in range(len(tail) - 1, -1, -1):
yield SimplePath((root, tail[:i]))


def make_simple_path_from_str(path: str) -> SimplePath:
tail = tuple(x for x in path.split("/") if x and x != ".")
root = "/" if path[0] == "/" else ""
return SimplePath((root, tail))


def calculate_relative_path(target: str, base: str) -> str:
Expand All @@ -97,38 +96,42 @@ def calculate_relative_path(target: str, base: str) -> str:
target = target or "/"
base = base or "/"

target_path = SimplePath(target)
base_path = SimplePath(base)
target_path = make_simple_path_from_str(target)
base_path = make_simple_path_from_str(base)
if base_path and base[-1] != "/":
base_path = base_path.parent()

target_path_parent_strs: Union[set[str], None] = None
target_path_str = str(target_path)
base_path_str = str(base_path)
for step, path in enumerate(chain((base_path,), base_path.parents())):
if path.path == target_path.path:
path_str = str(path)
if path_str == target_path_str:
break
# If the target_path_parent_strs is already built use the quick path
if target_path_parent_strs is not None:
if path.path in target_path_parent_strs:
if path_str in target_path_parent_strs:
break
elif path.name == "..":
raise ValueError(f"'..' segment in {base_path.path!r} cannot be walked")
raise ValueError(f"'..' segment in {base_path_str!r} cannot be walked")
continue
target_path_parent_strs = set()
# We check one at a time because enumerating parents
# builds the value on demand, and we want to stop
# as soon as we find the common parent
for parent in target_path.parents():
if parent.path == base_path.path:
parent_str = str(parent)
if parent_str == base_path_str:
break
target_path_parent_strs.add(parent.path)
target_path_parent_strs.add(parent_str)
else:
# If we didn't break, it means we didn't find a common parent
if path.name == "..":
raise ValueError(f"'..' segment in {base_path.path!r} cannot be walked")
raise ValueError(f"'..' segment in {base_path_str!r} cannot be walked")
continue
break
else:
msg = f"{target_path.path!r} and {base_path.path!r} have different anchors"
msg = f"{target_path_str!r} and {base_path_str!r} have different anchors"
raise ValueError(msg)

return "/".join((*("..",) * step, *target_path.parts[path.parts_count :])) or "."

0 comments on commit e49c3b6

Please sign in to comment.