Skip to content

Commit

Permalink
TypedPath.py (#24 closes #23)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg authored Mar 4, 2024
2 parents 8c2ed54 + f86de53 commit 5e98465
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 0 deletions.
73 changes: 73 additions & 0 deletions python/selfie-lib/selfie_lib/TypedPath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from functools import total_ordering


@total_ordering
class TypedPath:
def __init__(self, absolute_path: str):
self.absolute_path = absolute_path

@property
def name(self) -> str:
if self.absolute_path.endswith("/"):
path = self.absolute_path[:-1]
else:
path = self.absolute_path
last_slash = path.rfind("/")
return path[last_slash + 1 :]

@property
def is_folder(self) -> bool:
return self.absolute_path.endswith("/")

def assert_folder(self) -> None:
if not self.is_folder:
raise AssertionError(
f"Expected {self} to be a folder but it doesn't end with `/`"
)

def parent_folder(self) -> "TypedPath":
if self.absolute_path == "/":
raise ValueError("Path does not have a parent folder")
trimmed_path = self.absolute_path.rstrip("/")
last_idx = trimmed_path.rfind("/")
return TypedPath.of_folder(trimmed_path[: last_idx + 1])

def resolve_file(self, child: str) -> "TypedPath":
self.assert_folder()
if child.startswith("/") or child.endswith("/"):
raise ValueError("Child path is not valid for file resolution")
return self.of_file(f"{self.absolute_path}{child}")

def resolve_folder(self, child: str) -> "TypedPath":
self.assert_folder()
if child.startswith("/"):
raise ValueError("Child path starts with a slash")
return self.of_folder(f"{self.absolute_path}{child}/")

def relativize(self, child: "TypedPath") -> str:
self.assert_folder()
if not child.absolute_path.startswith(self.absolute_path):
raise ValueError(f"Expected {child} to start with {self.absolute_path}")
return child.absolute_path[len(self.absolute_path) :]

def __eq__(self, other: object) -> bool:
if not isinstance(other, TypedPath):
return NotImplemented
return self.absolute_path == other.absolute_path

def __lt__(self, other: "TypedPath") -> bool:
return self.absolute_path < other.absolute_path

@classmethod
def of_folder(cls, path: str) -> "TypedPath":
unix_path = path.replace("\\", "/")
if not unix_path.endswith("/"):
unix_path += "/"
return cls(unix_path)

@classmethod
def of_file(cls, path: str) -> "TypedPath":
unix_path = path.replace("\\", "/")
if unix_path.endswith("/"):
raise ValueError("Expected path to not end with a slash for a file")
return cls(unix_path)
89 changes: 89 additions & 0 deletions python/selfie-lib/tests/TypedPath_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import pytest
from selfie_lib.TypedPath import TypedPath


def test_initialization():
path = TypedPath("/home/user/")
assert path.absolute_path == "/home/user/"
assert path.is_folder
assert path.name == "user"


def test_parent_folder():
path = TypedPath("/home/user/documents/")
parent = path.parent_folder()
assert isinstance(parent, TypedPath)
assert parent.absolute_path == "/home/user/"


def test_resolve_file():
folder = TypedPath("/home/user/")
file = folder.resolve_file("document.txt")
assert file.absolute_path == "/home/user/document.txt"
assert not file.is_folder
assert file.name == "document.txt"


def test_resolve_folder():
folder = TypedPath("/home/user/")
subfolder = folder.resolve_folder("documents")
assert subfolder.absolute_path == "/home/user/documents/"
assert subfolder.is_folder
assert subfolder.name == "documents"


def test_relativize():
folder = TypedPath("/home/user/")
file = TypedPath("/home/user/document.txt")
relative_path = folder.relativize(file)
assert relative_path == "document.txt"


def test_of_folder_class_method():
folder = TypedPath.of_folder("/home/user/documents")
assert folder.absolute_path == "/home/user/documents/"
assert folder.is_folder


def test_of_file_class_method():
file = TypedPath.of_file("/home/user/document.txt")
assert file.absolute_path == "/home/user/document.txt"
assert not file.is_folder


def test_assert_folder_failure():
with pytest.raises(AssertionError):
file = TypedPath("/home/user/document.txt")
file.assert_folder()


def test_parent_folder_failure():
with pytest.raises(ValueError):
path = TypedPath("/")
path.parent_folder()


def test_equality():
path1 = TypedPath("/home/user/")
path2 = TypedPath("/home/user/")
assert path1 == path2


def test_inequality():
path1 = TypedPath("/home/user/")
path2 = TypedPath("/home/another_user/")
assert path1 != path2


def test_ordering():
path1 = TypedPath("/home/a/")
path2 = TypedPath("/home/b/")
assert path1 < path2
assert path2 > path1


def test_relativize_error():
parent = TypedPath("/home/user/")
child = TypedPath("/home/another_user/document.txt")
with pytest.raises(ValueError):
parent.relativize(child)

0 comments on commit 5e98465

Please sign in to comment.