Source code for hyperdiary.simplepath
from typing import Iterable, List
[docs]class InvalidPathError(Exception):
pass
[docs]class NotARelativePathError(InvalidPathError):
pass
[docs]class NotAnAbsolutePathError(InvalidPathError):
pass
_valid_path_chars = 'abcdefghijklmnopqrstuvwxyz0123456789-_.'
def _validate_and_shorten(is_absolute: bool, elements: Iterable[str]) \
-> List[str]:
'''
>>> _validate_and_shorten(True, ['a', 'b', 'c', '..', '..'])
['a']
>>> _validate_and_shorten(False, ['..', '..'])
['..', '..']
>>> _validate_and_shorten(True, ['..', '..'])
Traceback (most recent call last):
...
hyperdiary.simplepath.InvalidPathError: traversing over root
>>> _validate_and_shorten(True, ['x', '..'])
[]
>>> _validate_and_shorten(False, ['x', '..'])
[]
>>> _validate_and_shorten(True, [''])
[]
>>> _validate_and_shorten(True, [])
[]
>>> _validate_and_shorten(False, ['.'])
[]
>>> _validate_and_shorten(False, [''])
[]
>>> _validate_and_shorten(False, [])
[]
>>> _validate_and_shorten(False, ['.'])
[]
'''
new_elements = [] # type: List[str]
for el in elements:
if el == '.' or not el:
continue
elif el == '..':
if not new_elements:
if is_absolute:
raise InvalidPathError('traversing over root')
else:
new_elements.append('..')
else:
if new_elements[-1] == '..':
new_elements.append('..')
else:
new_elements.pop()
else:
if not all(c in _valid_path_chars for c in el):
raise InvalidPathError('invalid character in path element "{}"'
.format(el))
new_elements.append(el)
return new_elements
[docs]class RelativePath:
def __init__(self, path: str) -> None:
'''
>>> RelativePath('a/b/c')
RelativePath('./a/b/c')
>>> RelativePath('..')
RelativePath('..')
>>> RelativePath('.')
RelativePath('.')
>>> RelativePath('')
RelativePath('.')
'''
if path.startswith('/'):
raise NotARelativePathError(path)
self.elements = _validate_and_shorten(False, path.split('/'))
def __str__(self) -> str:
'''
>>> str(RelativePath('a/b/c'))
'./a/b/c'
'''
prefix = '.' + ('/' if self.elements else '')
if self.elements and self.elements[0] == '..':
prefix = ''
return prefix + '/'.join(self.elements)
def __repr__(self) -> str:
'''
>>> RelativePath('d/e/f')
RelativePath('./d/e/f')
>>> RelativePath('./../f')
RelativePath('../f')
'''
return 'RelativePath(\'{}\')'.format(self)
[docs]class AbsolutePath:
def __init__(self, path: str) -> None:
'''
>>> AbsolutePath('/a/b/c')
AbsolutePath('/a/b/c')
>>> AbsolutePath('/')
AbsolutePath('/')
>>> AbsolutePath('/folder/index.html')
AbsolutePath('/folder/index.html')
>>> AbsolutePath('a/b/c')
Traceback (most recent call last):
...
hyperdiary.simplepath.NotAnAbsolutePathError: a/b/c
>>> AbsolutePath('')
Traceback (most recent call last):
...
hyperdiary.simplepath.NotAnAbsolutePathError
'''
if not path.startswith('/'):
raise NotAnAbsolutePathError(path)
self.elements = _validate_and_shorten(True, path.split('/'))
def __eq__(self, other: object) -> bool:
'''
>>> AbsolutePath('/a/b/c') == AbsolutePath('/a/b/c')
True
>>> AbsolutePath('/a/b/c') == AbsolutePath('/a/./b/c/../c')
True
>>> AbsolutePath('/a/b/c') == AbsolutePath('/a')
False
>>> AbsolutePath('/a/b/c') != AbsolutePath('/a')
True
'''
if not isinstance(other, AbsolutePath):
return False
if not len(self.elements) == len(other.elements):
return False
return all(el1 == el2 for el1, el2 in zip(self.elements,
other.elements))
def __add__(self, other: RelativePath) -> 'AbsolutePath':
'''
>>> AbsolutePath('/a/b/c') + RelativePath('d/e/f')
AbsolutePath('/a/b/c/d/e/f')
>>> AbsolutePath('/a/b/c') + RelativePath('../..')
AbsolutePath('/a')
>>> AbsolutePath('/a/b/c') + RelativePath('../../..')
AbsolutePath('/')
>>> AbsolutePath('/a/b/c') + RelativePath('../../../..')
Traceback (most recent call last):
...
hyperdiary.simplepath.InvalidPathError: traversing over root
>>> AbsolutePath('/a/b/c/././') + RelativePath('../../../x/y') \
== AbsolutePath('/x/y')
True
'''
return AbsolutePath('{}/{}'.format(self, other))
def __sub__(self, other: 'AbsolutePath') -> RelativePath:
'''
>>> AbsolutePath('/a/b/c') - AbsolutePath('/a/b/c')
RelativePath('.')
>>> AbsolutePath('/a/b/c') - AbsolutePath('/a/b/x')
RelativePath('../c')
>>> AbsolutePath('/a/b/c') - AbsolutePath('/d/e/f')
RelativePath('../../../a/b/c')
>>> a = AbsolutePath('/a/b/c'); b = AbsolutePath('/a/x/y')
>>> b + (a - b) == a
True
>>> a + (b - a) == b
True
'''
e1 = self.elements.copy()
e2 = other.elements.copy()
while e1 and e2 and e1[0] == e2[0]:
e1.pop(0)
e2.pop(0)
return RelativePath('../' * len(e2) + '/'.join(e1))
def __str__(self) -> str:
'''
>>> str(AbsolutePath('/a/b/c'))
'/a/b/c'
'''
return '/' + '/'.join(self.elements)
def __repr__(self) -> str:
'''
>>> AbsolutePath('/a/b/c')
AbsolutePath('/a/b/c')
'''
return 'AbsolutePath(\'{}\')'.format(self)
def __hash__(self) -> int:
return hash(repr(self))