Source code for masterpiece.url

"""
Path for identifying objects in a hierarchy.

Author: Juha Meskanen
Date: 2024-10-26
"""

from __future__ import annotations
from typing import List


[docs] class URL: """Hierarchical path for identifying objects in a hierarchy."""
[docs] def __init__(self, path: str = "") -> None: """Initializes the URL with an optional starting path. Args: path (str): The initial path as a string (e.g., "/foo/bar"). """ self.segments: List[str] = [ segment for segment in path.strip("/").split("/") if segment ] self.is_rooted: bool = path.startswith("/")
def __eq__(self, other: object) -> bool: if isinstance(other, URL): return self.segments == other.segments return False def __str__(self) -> str: return f"/{'/'.join(self.segments)}"
[docs] def push_tail(self, name: str) -> None: """Adds a segment to the end of the URL path (child). Args: name (str): The name of the segment to add. """ self.segments.append(name)
[docs] def push_head(self, name: str) -> None: """Adds a segment to the beginning of the URL path (parent). Args: name (str): The name of the segment to add. """ self.segments.insert(0, name)
[docs] def pop_tail(self) -> str: """Removes and returns the last segment of the URL path. Returns: str: The last segment of the path. Raises: IndexError: If the path is empty. """ if self.segments: return self.segments.pop() raise IndexError("Cannot pop from an empty URL")
[docs] def pop_head(self) -> str: """Removes and returns the first segment of the URL path. Returns: str: The first segment of the path. Raises: IndexError: If the path is empty. """ if self.segments: return self.segments.pop(0) raise IndexError("Cannot pop from an empty URL")
[docs] def get(self) -> str: """Gets the full URL path as a string. Returns: str: The full path, starting with a forward slash if absolute. """ prefix: str = "/" if self.is_rooted else "" return prefix + "/".join(self.segments)
[docs] def normalize(self) -> URL: """Simplifies the URL path by resolving "." and "..". Returns: URL: The normalized path. """ stack: List[str] = [] for segment in self.segments: if segment == "..": if stack: stack.pop() # Go up one level elif not self.is_rooted: stack.append("..") # Preserve leading ".." for relative paths elif segment != ".": stack.append(segment) self.segments = stack return self
[docs] def is_absolute(self) -> bool: """Checks if the URL path is absolute. Returns: bool: True if the path starts with "/", False otherwise. """ return self.is_rooted
[docs] def make_absolute(self, base: URL) -> URL: """Makes the path absolute using a given base path. Args: base (URL): The base path to combine with. Returns: URL: The absolute path. """ if self.is_absolute(): return self new_path: URL = base.copy() new_path.segments.extend(self.segments) return new_path.normalize()
[docs] def starts_with(self, segment: str) -> bool: """Checks if the path starts with the given segment. Args: segment (str): The segment to check. Returns: bool: True if the path starts with the segment, False otherwise. """ return bool(self.segments and self.segments[0] == segment)
[docs] def prepend_base(self, base: str) -> None: """Prepends a base path if the current path is not absolute. Args: base (str): The base path to prepend. """ if not self.is_absolute(): base_segments: List[str] = [ segment for segment in base.strip("/").split("/") if segment ] self.segments = base_segments + self.segments self.is_rooted = base.startswith("/")
[docs] def is_empty(self) -> bool: """Checks if the URL path has no segments. Returns: bool: True if the path is empty, False otherwise. """ return len(self.segments) == 0
[docs] def copy(self) -> URL: """Creates a deep copy of the URL. Returns: URL: A new instance with the same segments. """ return URL(self.get())
def __repr__(self) -> str: """Returns the formal string representation of the URL. Returns: str: A string representation suitable for debugging. """ return f"URL({self.get()!r})"