You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
97 lines
2.6 KiB
Python
97 lines
2.6 KiB
Python
"""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'<Path {self.path}>'
|
|
|
|
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)
|