"""Path handling""" from .exc import ValidationError from .identity import Identity from .types import ALPHA_LOWER, ALPHA_UPPER, DIGIT PATH_PUNCTUATION = "/'()-._~!$&+,:=@%" PATH_CHARACTER = ALPHA_LOWER + ALPHA_UPPER + DIGIT + PATH_PUNCTUATION class Path: """A document path""" _SEGMENT_PATTERN = f'/[{PATH_CHARACTER}]+'.replace('$', '\\$') PATTERN = f'^({_SEGMENT_PATTERN})+$' def __init__(self, path: str) -> None: self.validate(path, allow_ephemeral=True) self.path = path @staticmethod def validate(path: str, allow_ephemeral: bool = False) -> None: """Validate a path""" if not 2 <= len(path) <= 512: raise ValidationError('Path length must be between 2 and 512') if not path.startswith('/'): raise ValidationError('Paths must start with a /') if path.endswith('/'): raise ValidationError('Paths must not end with a /') if path.startswith('/@'): raise ValidationError('Paths must not start with /@') if '//' in path: raise ValidationError('Paths must not contain //') if path.count('!') > 1: raise ValidationError('Only one ! is allowed in paths') if '!' in path and not allow_ephemeral: raise ValidationError('Only ephemeral paths may contain !') @property def is_shared(self) -> bool: """Check if the path is shared""" return '~' not in self.path @property def is_ephemeral(self) -> bool: """Check if the path is ephemeral""" return '!' in self.path def can_write(self, author: Identity) -> bool: """Check if a specific author has write access over a document""" if self.is_shared: return True segments = self.path.split('/') for segment in segments: for allowed_author in segment.split('~'): if Identity.valid_address(allowed_author) and str(author) == allowed_author: return True return False def __str__(self) -> str: return self.path def __repr__(self) -> str: return f'' def __eq__(self, other: object) -> bool: return str(self) == str(other) def __lt__(self, other: object) -> bool: return str(self) < str(other) def __gt__(self, other: object) -> bool: return str(self) > str(other) def startswith(self, sub: str) -> bool: """Check if path starts with sub""" return self.path.startswith(sub) def endswith(self, sub: str) -> bool: """Check if path ends with sub""" return self.path.endswith(sub)