|
1 | 1 | import collections
|
| 2 | +import contextlib |
2 | 3 | import itertools
|
3 | 4 | import pathlib
|
4 | 5 | import operator
|
| 6 | +import re |
5 | 7 |
|
6 | 8 | from . import abc
|
7 | 9 |
|
@@ -130,7 +132,33 @@ class NamespaceReader(abc.TraversableResources):
|
130 | 132 | def __init__(self, namespace_path):
|
131 | 133 | if 'NamespacePath' not in str(namespace_path):
|
132 | 134 | raise ValueError('Invalid path')
|
133 |
| - self.path = MultiplexedPath(*list(namespace_path)) |
| 135 | + self.path = MultiplexedPath(*map(self._resolve, namespace_path)) |
| 136 | + |
| 137 | + @classmethod |
| 138 | + def _resolve(cls, path_str) -> abc.Traversable: |
| 139 | + r""" |
| 140 | + Given an item from a namespace path, resolve it to a Traversable. |
| 141 | +
|
| 142 | + path_str might be a directory on the filesystem or a path to a |
| 143 | + zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or |
| 144 | + ``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``. |
| 145 | + """ |
| 146 | + (dir,) = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir()) |
| 147 | + return dir |
| 148 | + |
| 149 | + @classmethod |
| 150 | + def _candidate_paths(cls, path_str): |
| 151 | + yield pathlib.Path(path_str) |
| 152 | + yield from cls._resolve_zip_path(path_str) |
| 153 | + |
| 154 | + @staticmethod |
| 155 | + def _resolve_zip_path(path_str): |
| 156 | + for match in reversed(list(re.finditer(r'[\\/]', path_str))): |
| 157 | + with contextlib.suppress( |
| 158 | + FileNotFoundError, IsADirectoryError, PermissionError |
| 159 | + ): |
| 160 | + inner = path_str[match.end() :].replace('\\', '/') + '/' |
| 161 | + yield ZipPath(path_str[: match.start()], inner.lstrip('/')) |
134 | 162 |
|
135 | 163 | def resource_path(self, resource):
|
136 | 164 | """
|
|
0 commit comments