diff options
Diffstat (limited to 'bsfs/namespace/namespace.py')
-rw-r--r-- | bsfs/namespace/namespace.py | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/bsfs/namespace/namespace.py b/bsfs/namespace/namespace.py new file mode 100644 index 0000000..f652dcd --- /dev/null +++ b/bsfs/namespace/namespace.py @@ -0,0 +1,104 @@ +""" + +Part of the BlackStar filesystem (bsfs) module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import typing + +# bsfs imports +from bsfs.utils import URI, typename + +# exports +__all__: typing.Sequence[str] = ( + 'ClosedNamespace', + 'Namespace', + ) + + +## code ## + +class Namespace(): + """A namespace consists of a common prefix that is used in a set of URIs. + + Note that the prefix must include the separator between + path and fragment (typically a '#' or a '/'). + """ + + # namespace prefix. + prefix: URI + + # fragment separator. + fsep: str + + # path separator. + psep: str + + def __init__(self, prefix: URI, fsep: str = '#', psep: str = '/'): + # ensure prefix type + prefix = URI(prefix) + # truncate fragment separator + while prefix.endswith(fsep): + prefix = URI(prefix[:-1]) + # truncate path separator + while prefix.endswith(psep): + prefix = URI(prefix[:-1]) + # store members + self.prefix = prefix + self.fsep = fsep + self.psep = psep + + def __eq__(self, other: typing.Any) -> bool: + return isinstance(other, type(self)) \ + and self.prefix == other.prefix \ + and self.fsep == other.fsep \ + and self.psep == other.psep + + def __hash__(self) -> int: + return hash((type(self), self.prefix, self.fsep, self.psep)) + + def __str__(self) -> str: + return f'{typename(self)}({self.prefix})' + + def __repr__(self) -> str: + return f'{typename(self)}({self.prefix}, {self.fsep}, {self.psep})' + + def __getattr__(self, fragment: str) -> URI: + """Return prefix + fragment.""" + return URI(self.prefix + self.fsep + fragment) + + def __getitem__(self, fragment: str) -> URI: + """Alias for getattr(self, fragment).""" + return self.__getattr__(fragment) + + def __add__(self, value: typing.Any) -> 'Namespace': + """Concatenate another namespace to this one.""" + if not isinstance(value, str): + return NotImplemented + return Namespace(self.prefix + self.psep + value, self.fsep, self.psep) + + +class ClosedNamespace(Namespace): + """Namespace that covers a restricted set of URIs.""" + + # set of permissible fragments. + fragments: typing.Set[str] + + def __init__(self, prefix: URI, *args: str, fsep: str = '#', psep: str = '/'): + super().__init__(prefix, fsep, psep) + self.fragments = set(args) + + def __eq__(self, other: typing.Any) -> bool: + return super().__eq__(other) and self.fragments == other.fragments + + def __hash__(self) -> int: + return hash((type(self), self.prefix, tuple(sorted(self.fragments)))) + + def __getattr__(self, fragment: str) -> URI: + """Return prefix + fragment or raise a KeyError if the fragment is not part of this namespace.""" + if fragment not in self.fragments: + raise KeyError(f'{fragment} is not a valid fragment of namespace {self.prefix}') + return super().__getattr__(fragment) + +## EOF ## |