| # from jaraco.path 3.5 |
| |
| import functools |
| import pathlib |
| from typing import Dict, Union |
| |
| try: |
| from typing import Protocol, runtime_checkable |
| except ImportError: # pragma: no cover |
| # Python 3.7 |
| from typing_extensions import Protocol, runtime_checkable # type: ignore |
| |
| |
| FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore |
| |
| |
| @runtime_checkable |
| class TreeMaker(Protocol): |
| def __truediv__(self, *args, **kwargs): |
| ... # pragma: no cover |
| |
| def mkdir(self, **kwargs): |
| ... # pragma: no cover |
| |
| def write_text(self, content, **kwargs): |
| ... # pragma: no cover |
| |
| def write_bytes(self, content): |
| ... # pragma: no cover |
| |
| |
| def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker: |
| return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore |
| |
| |
| def build( |
| spec: FilesSpec, |
| prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore |
| ): |
| """ |
| Build a set of files/directories, as described by the spec. |
| |
| Each key represents a pathname, and the value represents |
| the content. Content may be a nested directory. |
| |
| >>> spec = { |
| ... 'README.txt': "A README file", |
| ... "foo": { |
| ... "__init__.py": "", |
| ... "bar": { |
| ... "__init__.py": "", |
| ... }, |
| ... "baz.py": "# Some code", |
| ... } |
| ... } |
| >>> target = getfixture('tmp_path') |
| >>> build(spec, target) |
| >>> target.joinpath('foo/baz.py').read_text(encoding='utf-8') |
| '# Some code' |
| """ |
| for name, contents in spec.items(): |
| create(contents, _ensure_tree_maker(prefix) / name) |
| |
| |
| @functools.singledispatch |
| def create(content: Union[str, bytes, FilesSpec], path): |
| path.mkdir(exist_ok=True) |
| build(content, prefix=path) # type: ignore |
| |
| |
| @create.register |
| def _(content: bytes, path): |
| path.write_bytes(content) |
| |
| |
| @create.register |
| def _(content: str, path): |
| path.write_text(content, encoding='utf-8') |
| |
| |
| @create.register |
| def _(content: str, path): |
| path.write_text(content, encoding='utf-8') |
| |
| |
| class Recording: |
| """ |
| A TreeMaker object that records everything that would be written. |
| |
| >>> r = Recording() |
| >>> build({'foo': {'foo1.txt': 'yes'}, 'bar.txt': 'abc'}, r) |
| >>> r.record |
| ['foo/foo1.txt', 'bar.txt'] |
| """ |
| |
| def __init__(self, loc=pathlib.PurePosixPath(), record=None): |
| self.loc = loc |
| self.record = record if record is not None else [] |
| |
| def __truediv__(self, other): |
| return Recording(self.loc / other, self.record) |
| |
| def write_text(self, content, **kwargs): |
| self.record.append(str(self.loc)) |
| |
| write_bytes = write_text |
| |
| def mkdir(self, **kwargs): |
| return |