-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |