1  # from jaraco.path 3.5
       2  
       3  import functools
       4  import pathlib
       5  from typing import Dict, Union
       6  
       7  try:
       8      from typing import Protocol, runtime_checkable
       9  except ImportError:  # pragma: no cover
      10      # Python 3.7
      11      from typing_extensions import Protocol, runtime_checkable  # type: ignore
      12  
      13  
      14  FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']]  # type: ignore
      15  
      16  
      17  @runtime_checkable
      18  class ESC[4;38;5;81mTreeMaker(ESC[4;38;5;149mProtocol):
      19      def __truediv__(self, *args, **kwargs):
      20          ...  # pragma: no cover
      21  
      22      def mkdir(self, **kwargs):
      23          ...  # pragma: no cover
      24  
      25      def write_text(self, content, **kwargs):
      26          ...  # pragma: no cover
      27  
      28      def write_bytes(self, content):
      29          ...  # pragma: no cover
      30  
      31  
      32  def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker:
      33      return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj)  # type: ignore
      34  
      35  
      36  def build(
      37      spec: FilesSpec,
      38      prefix: Union[str, TreeMaker] = pathlib.Path(),  # type: ignore
      39  ):
      40      """
      41      Build a set of files/directories, as described by the spec.
      42  
      43      Each key represents a pathname, and the value represents
      44      the content. Content may be a nested directory.
      45  
      46      >>> spec = {
      47      ...     'README.txt': "A README file",
      48      ...     "foo": {
      49      ...         "__init__.py": "",
      50      ...         "bar": {
      51      ...             "__init__.py": "",
      52      ...         },
      53      ...         "baz.py": "# Some code",
      54      ...     }
      55      ... }
      56      >>> target = getfixture('tmp_path')
      57      >>> build(spec, target)
      58      >>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
      59      '# Some code'
      60      """
      61      for name, contents in spec.items():
      62          create(contents, _ensure_tree_maker(prefix) / name)
      63  
      64  
      65  @functools.singledispatch
      66  def create(content: Union[str, bytes, FilesSpec], path):
      67      path.mkdir(exist_ok=True)
      68      build(content, prefix=path)  # type: ignore
      69  
      70  
      71  @create.register
      72  def _(content: bytes, path):
      73      path.write_bytes(content)
      74  
      75  
      76  @create.register
      77  def _(content: str, path):
      78      path.write_text(content, encoding='utf-8')
      79  
      80  
      81  @create.register
      82  def _(content: str, path):
      83      path.write_text(content, encoding='utf-8')
      84  
      85  
      86  class ESC[4;38;5;81mRecording:
      87      """
      88      A TreeMaker object that records everything that would be written.
      89  
      90      >>> r = Recording()
      91      >>> build({'foo': {'foo1.txt': 'yes'}, 'bar.txt': 'abc'}, r)
      92      >>> r.record
      93      ['foo/foo1.txt', 'bar.txt']
      94      """
      95  
      96      def __init__(self, loc=pathlib.PurePosixPath(), record=None):
      97          self.loc = loc
      98          self.record = record if record is not None else []
      99  
     100      def __truediv__(self, other):
     101          return Recording(self.loc / other, self.record)
     102  
     103      def write_text(self, content, **kwargs):
     104          self.record.append(str(self.loc))
     105  
     106      write_bytes = write_text
     107  
     108      def mkdir(self, **kwargs):
     109          return