text: # -*- coding: utf-8 -*-
  """
  Code for creating and editing "fake directories" within the `seedir` package;
  i.e. Python folder tree objects.  This module can be used
  to make example folder tree diagrams, read folder tree strings, or convert
  abstract folder trees into real directories on a computer.
  """
  __pdoc__ = {}
  
  for key in ['get_random_int',
              'recursive_add_fakes',]:
      __pdoc__[key] = False
  
  import os
  import string
  import re
  
  import random
  
  from seedir.errors import FakedirError
  
  from seedir.folderstructure import FakeDirStructure
  
  from seedir.folderstructurehelpers import (sort_dir,
                                             filter_item_names,
                                             listdir_fullpath)
  
  import seedir.printing as printing
  
  from seedir.printing import words
  
  class FakeItem:
      '''Parent class for representing fake folders and files.'''
      def __init__(self, name, parent=None):
          '''
          Initialize the fake diretory or file object.
  
          Parameters
          ----------
          name : str
              Name for the folder or file.
          parent : seedir.fakedir.FakeDir or None, optional
              Parent of `self`. The default is `None`, meaning the object will
              be the "root" of the directory.
  
          Returns
          -------
          None.
  
          '''
          self.name = name
          self._parent = None
          self.parent = parent
          self.depth = 0
          self.set_depth()
  
      @property
      def parent(self):
          '''
          Getter for the `parent` attribute.
  
          Returns
          -------
          seedir.fakedir.FakeDir
              The parent folder of the object.
  
          '''
          return self._parent
  
      @parent.setter
      def parent(self, other):
          '''
          Setter for the `parent` attribute.
  
          When a new parent is assigned for an object, this method
          verifies that the other object is `seedir.fakedir.FakeDir`, and that the other
          objects children do not contain a fake item with the same name.
  
          If those conditions are met, `self` is removed from the children
          of its current parent, and its parent attribute is reassigned.
          Depths are also reset.
  
          Parameters
          ----------
          other : seedir.fakedir.FakeDir
              Fake directory to become the new `parent` for self.
  
          Raises
          ------
          FakedirError
              When other is not `seedir.fakedir.FakeDir` or when `self.name` is in
              the child names of other.
  
          Returns
          -------
          None.
  
          '''
          if other:
              if not isinstance(other, FakeDir):
                  raise FakedirError('other parameter must be instance of FakeDir')
              if self.name in other.get_child_names():
                  raise FakedirError('FakeDirs must have unique file/folder names')
              other._children.append(self)
          if self.parent is not None:
              self.parent._children.remove(self)
          self._parent = other
          self.set_depth()
          if isinstance(self, FakeDir):
              self.set_child_depths()
  
      def get_path(self):
          '''Return a "path" string of self, from the head `seedir.fakedir.FakeDir`
          (which has `parent == None`).
  
          >>> import seedir as sd
          >>> f = sd.randomdir(seed=5) # create a random FakeDir
          >>> f
          MyFakeDir/
          ├─senor.txt
          ├─verb.txt
          ├─takeoff.txt
          ├─monastic/
          │ ├─paddy.txt
          │ ├─ewe.txt
          │ ├─advantage.txt
          │ ├─hooves/
          │ └─registrar/
          └─zag/
            ├─thematic.txt
            ├─inelastic.txt
            ├─fierce.txt
            ├─gout/
            └─stein/
              ├─vector.txt
              ├─sora.txt
              └─proviso.txt
  
          >>> x = sd.FakeDir('newfolder', parent=f['zag/stein'])
          >>> x.get_path()
          'MyFakeDir/zag/stein/newfolder'
  
          '''
          parents = [self.name]
          on = self
          while on.parent is not None:
              on = on.parent
              parents.append(on.name)
          return '/'.join(parents[::-1])
  
      def set_depth(self):
          '''Set the depth attribute of `self`, based on the depth of parent.
          Automatically called when setting new parents.'''
          if self.parent is None:
              self.depth = 0
          else:
              self.depth = self.parent.depth + 1
  
      def isfile(self):
          """Returns `True` if instance is a `seedir.fakedir.FakeFile` object"""
          return isinstance(self, FakeFile)
  
      def isdir(self):
          """Returns True if instance is a `seedir.fakedir.FakeDir` object"""
          return isinstance(self, FakeDir)
  
  class FakeFile(FakeItem):
      '''Class to represent files in `seedir.fakedir.FakeDir` objects.
  
      Files in a `seedir.fakedir.FakeDir` have this type.
  
      >>> import seedir as sd
      >>> f = sd.randomdir(seed=5) # create a random FakeDir
      >>> f
      MyFakeDir/
      ├─senor.txt
      ├─verb.txt
      ├─takeoff.txt
      ├─monastic/
      │ ├─paddy.txt
      │ ├─ewe.txt
      │ ├─advantage.txt
      │ ├─hooves/
      │ └─registrar/
      └─zag/
        ├─thematic.txt
        ├─inelastic.txt
        ├─fierce.txt
        ├─gout/
        └─stein/
          ├─vector.txt
          ├─sora.txt
          └─proviso.txt
  
      >>> f['senor.txt']
      FakeFile(MyFakeDir/senor.txt)
  
      '''
      def __init__(self, name, parent=None):
          '''Same as `seedir.fakedir.FakeItem` initialization, but adds
          `filename` and `extension` attributes.
          '''
          super().__init__(name, parent)
          self.filename, self.extension = os.path.splitext(self.name)
  
      def __str__(self):
          return 'FakeFile({})'.format(self.get_path())
  
      def __repr__(self):
          return 'FakeFile({})'.format(self.get_path())
  
  class FakeDir(FakeItem):
      '''Class to represent fake folders.  Can be used to create
      custom folder tree diagrams.  See `seedir.fakedir.fakedir()` for converting
      a real directory into a FakeDir, `seedir.fakedir.fakedir_fromstring()` for
      converting a text folder diagram into a `FakeDir`, and `seedir.fakedir.randomdir()`
      for creating a random one.
  
      To make a `FakeDir` from scratch, use this class:
  
      >>> import seedir as sd
      >>> x = sd.FakeDir('myfakedir')
      >>> x.seedir()
      myfakedir/
  
      There are various ways to add to it:
  
      # using methods
      >>> x.create_file(['__init__.py', 'main.py', 'styles.txt'])
      >>> x.create_folder('docs')
  
      # initializing new objects and setting the parent
      >>> y = sd.FakeDir('resources', parent=x)
  
      # changing the parent of existing objects
      >>> z = sd.FakeDir('images')
      >>> z.parent = y
  
      >>> for n in ['a', 'b', 'c']:
      ...     z.create_file(n + '.png')
  
      >>> x.seedir(sort=True, first='folders')
      myfakedir/
      ├─docs/
      ├─resources/
      │ └─images/
      │   ├─a.png
      │   ├─b.png
      │   └─c.png
      ├─__init__.py
      ├─main.py
      └─styles.txt
  
      You can index with path-like strings:
  
      >>> x['resources/images/a.png']
      FakeFile(myfakedir/resources/images/a.png)
  
      '''
      def __init__(self, name, parent=None):
          '''Same as `seedir.fakedir.FakeItem` initialization, but adds
          the `_children` attribute for keeping track of items inside the fake dir.
          '''
          # alter children through FakeDir methods!
          self._children = []
          super().__init__(name, parent)
  
      def __str__(self):
          '''String conversion of `FakeDir`'''
          return 'FakeDir({})'.format(self.get_path())
  
      def __repr__(self):
          '''Representation of `FakeDir` (shown as a folder diagram).'''
          return self.seedir(printout=False)
  
      def __getitem__(self, path):
          """Use path-like strings to index `FakeDir` objects."""
          if not isinstance(path, str):
              raise FakedirError("Can only index FakeDir with int or str, "
                                 "not {}".format(type(path)))
          paths = path.split('/')
          current = self
          for p in paths:
              for f in current._children:
                  if p == f.name:
                      current = f
                      break
              else:
                  raise(FakedirError('Path "{}" not found through {}'.format(path, self)))
          return current
  
      def create_folder(self, name):
          """
          Create a new folder (`seedir.fakedir.FakeDir`) as a child.
  
          >>> import seedir as sd
          >>> x = sd.FakeDir('Test')
          >>> x.create_folder("new_folder")
          >>> x.seedir()
          Test/
          └─new_folder/
  
          Parameters
          ----------
          name : str
              Name of the new folder
  
          Returns
          -------
          None.
  
          """
          if isinstance(name, str):
              FakeDir(name, parent=self)
          else:
              for s in name:
                  FakeDir(s, parent=self)
  
      def create_file(self, name):
          """
          Create a new file (`seedir.fakedir.FakeFile`) as a child.
  
          >>> import seedir as sd
          >>> x = sd.FakeDir('Test')
          >>> x.create_file("new_file.txt")
          >>> x.seedir()
          Test/
          └─new_file.txt
  
          Parameters
          ----------
          name : str
              Name of the new file.
  
          Returns
          -------
          None.
  
          """
          if isinstance(name, str):
              FakeFile(name, parent=self)
          else:
              for s in name:
                  FakeFile(s, parent=self)
  
      def delete(self, child):
          '''
          Delete items from a `seedir.fakedir.FakeDir`.
  
          >>> import seedir as sd
          >>> r = sd.randomdir(seed=5)
          >>> r
          MyFakeDir/
          ├─senor.txt
          ├─verb.txt
          ├─takeoff.txt
          ├─monastic/
          │ ├─paddy.txt
          │ ├─ewe.txt
          │ ├─advantage.txt
          │ ├─hooves/
          │ └─registrar/
          └─zag/
            ├─thematic.txt
            ├─inelastic.txt
            ├─fierce.txt
            ├─gout/
            └─stein/
              ├─vector.txt
              ├─sora.txt
              └─proviso.txt
  
          >>> r['zag'].delete(['thematic.txt', 'inelastic.txt']) # delete with string names
          >>> r.delete(r['monastic']) # delete with objects
          >>> r
          MyFakeDir/
          ├─senor.txt
          ├─verb.txt
          ├─takeoff.txt
          └─zag/
            ├─fierce.txt
            ├─gout/
            └─stein/
              ├─vector.txt
              ├─sora.txt
              └─proviso.txt
  
          Parameters
          ----------
          child : str, FakeDir, FakeFile or list-like
              Child or children to remove.  Can be a string name, actual
              `seedir.fakedir.FakeDir` / `seedir.fakedir.FakeFile` object,
              or a collection of names or items.
  
          Raises
          ------
          FakedirError
              No item found to delete.
  
          Returns
          -------
          None.
  
          '''
          target = None
          if type(child) in [FakeDir, FakeFile]:
              target = child.name
          elif isinstance(child, str):
              target = child
          if target is not None:
              try:
                  to_del = next(f for f in self._children if f.name == target)
                  to_del.parent = None
              except StopIteration:
                  raise FakedirError('{} has no child with name "{}"'.format(self, target))
          else:
              child_copy = child.copy()
              for c in child_copy:
                  target = None
                  if type(c) in [FakeDir, FakeFile]:
                      target = c.name
                  elif isinstance(c, str):
                      target = c
                  if target is not None:
                      try:
                          to_del = next(f for f in self._children if
                                        f.name == target)
                          to_del.parent = None
                      except StopIteration:
                          raise FakedirError('{} has no child with name "{}"'.format(self, target))
  
      def get_child_names(self):
          '''Return a list of child names.
  
          >>> import seedir as sd
          >>> r = sd.randomdir(seed=5)
          >>> r
          MyFakeDir/
          ├─senor.txt
          ├─verb.txt
          ├─takeoff.txt
          ├─monastic/
          │ ├─paddy.txt
          │ ├─ewe.txt
          │ ├─advantage.txt
          │ ├─hooves/
          │ └─registrar/
          └─zag/
            ├─thematic.txt
            ├─inelastic.txt
            ├─fierce.txt
            ├─gout/
            └─stein/
              ├─vector.txt
              ├─sora.txt
              └─proviso.txt
  
          >>> r.get_child_names()
          ['senor.txt', 'verb.txt', 'takeoff.txt', 'monastic', 'zag']
  
          '''
          return [c.name for c in self._children]
  
      def listdir(self):
          '''Return the list of `seedir.fakedir.FakeFile` and `seedir.fakedir.FakeDir`
          objects that are children of `self` (like `os.listdir`).
  
          >>> import seedir as sd
          >>> r = sd.randomdir(seed=1)
          >>> r
          MyFakeDir/
          ├─churchmen.txt
          └─exposure/
  
          >>> print([str(i) for i in r.listdir()])
          ['FakeFile(MyFakeDir/churchmen.txt)', 'FakeDir(MyFakeDir/exposure)']
          '''
          return self._children
  
      def realize(self, path=None):
          '''
          Convert a fake file tree into a real one by creating a folder at a
          given path, and populating it with files and sub-directories.
  
          All files will be empty.
  
          ```
          import os
  
          import seedir as sd
  
          r = sd.randomdir(seed=1)
          # MyFakeDir/
          # ├─churchmen.txt
          # └─exposure/
  
          r.realize()
          os.path.isdir('MyFakeDir/scrooge')
          # True
          ```
  
          Parameters
          ----------
          path : str, optional
              System path where to create the folder. The default is `None`,
              in which the current working directory is used.
  
          Returns
          -------
          None.
  
          '''
          def create(f, root):
              fpath = f.get_path()
              joined = os.path.join(root, fpath)
              if isinstance(f, FakeDir):
                  os.mkdir(joined)
              elif isinstance(f, FakeFile):
                  with open(joined, 'w') as file:
                      pass
          if path is None:
              path = os.getcwd()
          os.mkdir(os.path.join(path, self.name))
          self.walk_apply(create, root=path)
  
      def seedir(self, style='lines', printout=True, indent=2, uniform=None,
                 anystart=None, depthlimit=None, itemlimit=None, beyond=None,
                 first=None, sort=False, sort_reverse=False, sort_key=None,
                 include_folders=None, exclude_folders=None, include_files=None,
                 exclude_files=None, regex=False, slash='/', mask=None,
                 **kwargs):
          '''
  
          Create a folder tree diagram for `self`.  `seedir.fakedir.FakeDir` version of
          `seedir.realdir.seedir()` (see its documentation for examples).
  
          Parameters
          ----------
          style : 'lines', 'dash', 'arrow', 'spaces', 'plus', or 'emoji', optional
              Style to use. The default is `'lines'`.  A style determines the set
              of characters ("tokens") used to represent the base structure of
              the directory (e.g. which items belong to which folders, when items
              are the last member of a folder, etc.).  The actual tokens being used
              by each style can be viewed with `seedir.printing.get_styleargs()`.
          printout : bool, optional
              Print the folder structure in the console. The default is `True`.  When
              `False`, the folder diagram is returned as a string.
          indent : int (>= 0), optional
              Number of spaces separating items from their parent folder.
              The default is `2`.
          uniform : str or None, optional
              Characters to use for all tokens when creating the tree diagram.
              The default is `None`.  When not `None`, the extend, space, split, and
              final tokens are replaced with `uniform` (the `'spaces'` style is
              essentially `uniform = '  '`).
          anystart : str or None, optional
              Characters to append before any item (i.e. folder or file).  The
              default is `None`.  Specific starts for folders and files can be
              specified (see `**kwargs`).
          depthlimit : int or None, optional
              Limit the depth of folders to traverse.  Folders at the `depthlimit` are
              included, but their contents are not shown (with the exception of the
              beyond parameter being specified).  The default is `None`, which can
              cause exceptionally long runtimes for deep or extensive directories.
          itemlimit : int or None, optional
              Limit the number of items in a directory to show.  Items beyond the
              `itemlimit` can be expressed using the `beyond` parameter.  The files and
              folders left out are determined by the sorting parameters
              (`sort`, `sort_reverse`, `sort_key`).  The default is `None`.
          beyond : str ('ellipsis', 'cotent' or a string starting with an underscore) or None, optional
              String to indicate directory contents beyond the `itemlimit` or the
              `depthlimit`.  The default is `None`.  Options are: `'ellipsis'` (`'...'`),
              `'content'` or `'contents'` (the number of files and folders beyond), or
              a string starting with `'_'` (everything after the leading underscore
              will be returned)
          first : 'files', 'folders', or None, optional
              Sort the directory so that either files or folders appear first.
              The default is `None`.
          sort : bool, optional
              Sort the directory. With no other specifications, the sort will be a
              simple alphabetical sort of the item names, but this can be altered
              with the `first`, `sort_reverse`, and `sort_key parameters`.
              The default is `False`.
          sort_reverse : bool, optional
              Reverse the sorting determined by `sort` or `sort_key`.
              The default is `False`.
          sort_key : function, optional
              Key to use for sorting file or folder names, akin to the `key` parameter
              of the builtin `sorted()` or `list.sort()`. The function should take a
              string as an argument. The default is `None`.
          include_folders, exclude_folders, include_files, exclude_files : str, list-like, or None, optional
              Folder / file names to include or exclude. The default is `None`.
          regex : bool, optional
              Interpret the strings of include/exclude file/folder arguments as
              regular expressions. The default is `False`.
          mask : function, optional
              Function for filtering items.  Each individual item object
              is passed to the mask function.  If `True` is returned, the
              item is kept.  The default is `None`.
          slash : str, option:
              Slash character to follow folders.  If `'sep'`, uses `os.se`p.  The
              default is `'/'`.
          **kwargs : str
              Specific tokens to use for creating the file tree diagram.  The tokens
              use by each builtin style can be seen with `seedir.printing.get_styleargs()`.
              Valid options are `extend` (characters to show the extension of a directory
              while its children are traversed), `space` (character to provide the
              correct indentation of an item when some of its parent / grandparent
              directories are completely traversed), `split` (characters to show a
              folder or file within a directory, with more items following),
              `final` (characters to show a folder or file within a directory,
              with no more items following), `folderstart` (characters to append
              before any folder), and `filestart` (characters to append beffore any
              file).  The following shows the default tokens for the `'lines'` style:
  
              >>> import seedir as sd
              >>> sd.get_styleargs('lines')
              {'split': '├─', 'extend': '│ ', 'space': '  ', 'final': '└─', 'folderstart': '', 'filestart': ''}
  
              All default style tokens are 2 character strings, except for
              `folderstart` and `filestart`.  Style tokens from `**kwargs` are not
              affected by the indent parameter.  The `uniform` and `anystart`
              parameters can be used to affect multiple style tokens.
  
          Raises
          ------
          SeedirError
              Improperly formatted arguments.
  
          Returns
          -------
          s (str) or None
              The tree diagram (as a string) or None if prinout = True, in which
              case the tree diagram is printed in the console.
  
          '''
  
          accept_kwargs = ['extend', 'split', 'space', 'final',
                           'folderstart', 'filestart']
  
          if any(i not in accept_kwargs for i in kwargs.keys()):
              raise FakedirError('kwargs must be any of {}'.format(accept_kwargs))
  
          if style:
              styleargs = printing.get_styleargs(style)
  
          styleargs = printing.format_indent(styleargs, indent=indent)
  
          if uniform is not None:
              for arg in ['extend', 'split', 'final', 'space']:
                  styleargs[arg] = uniform
  
          if anystart is not None:
              styleargs['folderstart'] = anystart
              styleargs['filestart'] = anystart
  
          for k in kwargs:
              if k in styleargs:
                  styleargs[k] = kwargs[k]
  
          if sort_key is not None or sort_reverse == True:
              sort = True
  
          if slash.lower() in ['sep', 'os.sep']:
              slash = os.sep
  
          s = FakeDirStructure(self,
                               depthlimit=depthlimit,
                               itemlimit=itemlimit,
                               beyond=beyond,
                               first=first,
                               sort=sort,
                               sort_reverse=sort_reverse,
                               sort_key=sort_key,
                               include_folders=include_folders,
                               exclude_folders=exclude_folders,
                               include_files=include_files,
                               exclude_files=exclude_files,
                               regex=regex,
                               slash=slash,
                               mask=mask,
                               **styleargs).strip()
  
          if printout:
              print(s)
          else:
              return s
  
      def set_child_depths(self):
          '''Recursively set depths of `self` and its children.
          Called automatically when a new `parent` is assigned.'''
          def apply_setdepth(FD):
              FD.set_depth()
          self.walk_apply(apply_setdepth)
  
      def trim(self, depthlimit):
          """
          Remove items beyond the `depthlimit`.
  
          >>> import seedir as sd
          >>> r = sd.randomdir(seed=456)
          >>> r
          MyFakeDir/
          ├─Vogel.txt
          ├─monkish.txt
          ├─jowly.txt
          ├─scrooge/
          │ ├─light.txt
          │ ├─reliquary.txt
          │ ├─sandal/
          │ ├─paycheck/
          │ │ ├─electrophoresis.txt
          │ │ └─Pyongyang/
          │ └─patrimonial/
          ├─Uganda/
          └─pedantic/
            └─cataclysmic.txt
  
          >>> r.trim(1)
          >>> r
          MyFakeDir/
          ├─Vogel.txt
          ├─monkish.txt
          ├─jowly.txt
          ├─scrooge/
          ├─Uganda/
          └─pedantic/
  
  
          Parameters
          ----------
          depthlimit : non-negative int
              Files beyond this depth will be cut. The root has depth `0`.
  
          Raises
          ------
          FakedirError
              `depthlimit` is not a non-negative int
  
          Returns
          -------
          None.
  
          """
          depthlimit = int(depthlimit)
          if depthlimit < 0:
              raise FakedirError('depthlimit must be non-negative int')
          depthlimit += self.depth
          def trim_apply(f, depthlimit):
              if depthlimit is not None and f.depth == depthlimit:
                  if isinstance(f, FakeDir):
                      f.delete(f.listdir())
          if depthlimit == self.depth:
              self.delete(self.listdir())
          else:
              self.walk_apply(trim_apply, depthlimit=depthlimit)
  
      def walk_apply(self, foo, *args, **kwargs):
          """
          Recursively apply a function the children of self (and so on)
  
          >>> import seedir as sd
          >>> r = sd.randomdir(seed=5)
          >>> r
          MyFakeDir/
          ├─senor.txt
          ├─verb.txt
          ├─takeoff.txt
          ├─monastic/
          │ ├─paddy.txt
          │ ├─ewe.txt
          │ ├─advantage.txt
          │ ├─hooves/
          │ └─registrar/
          └─zag/
            ├─thematic.txt
            ├─inelastic.txt
            ├─fierce.txt
            ├─gout/
            └─stein/
              ├─vector.txt
              ├─sora.txt
              └─proviso.txt
  
          >>> def replace_txt(f):
          ...    f.name = f.name.replace('txt', 'pdf')
  
          >>> r.walk_apply(replace_txt)
          >>> r
          MyFakeDir/
          ├─senor.pdf
          ├─verb.pdf
          ├─takeoff.pdf
          ├─monastic/
          │ ├─paddy.pdf
          │ ├─ewe.pdf
          │ ├─advantage.pdf
          │ ├─hooves/
          │ └─registrar/
          └─zag/
            ├─thematic.pdf
            ├─inelastic.pdf
            ├─fierce.pdf
            ├─gout/
            └─stein/
              ├─vector.pdf
              ├─sora.pdf
              └─proviso.pdf
  
          Parameters
          ----------
          foo : function
              Function to apply.
          *args :
              Additional positional arguments for `foo`.
          **kwargs :
              Additional keyword arguments for `foo`.
  
          Returns
          -------
          None.
  
          """
          for f in self._children:
              foo(f, *args, **kwargs)
              if isinstance(f, FakeDir):
                  f.walk_apply(foo, *args, **kwargs)
  
  def get_random_int(collection, seed=None):
      '''
      Helper function for selecting a random integer, used by seedir.populate().
  
      Parameters
      ----------
      collection : list-like
          Collection of integers to select from.
      seed : int or float, optional
          Random seed. The default is None.
  
      Raises
      ------
      TypeError
          Non-integer found.
  
      Returns
      -------
      r : int
          Randomly chosen int from collection.
  
      '''
      r = random.Random(seed).choice(collection)
      if not isinstance(r, int):
          raise TypeError('non int found')
      return r
  
  def populate(fakedir, depth=3, folders=2, files=2, stopchance=.5, seed=None,
               extensions=['txt']):
      '''
      Function for populating `seedir.fakedir.FakeDir` objects with random files and folders.
      Used by `seedir.fakedir.randomdir()`.  Random dictionary names are chosen
      for file and folder names.
  
      Parameters
      ----------
      fakedir : seedir.fakedir.FakeDir
          Fake directory to populate.
      depth : int, optional
          Maximum depth to create folders and files. The default is `3`.
      folders : int or collection of integers, optional
          Parameter for setting the number of folders per directory.
          The default is `2`.  If `int`, represents the number of folders
          per directory.  If collection of integers, a random value will be
          chosen from the collection each time a directory is popualted.
      files : int or collection of integers, optional, optional
          Same as the folders parameter, but for files.
      stopchance : float between 0 and 1, optional
          Chance that an added folder will not be populated. The default is `.5`.
      seed : int or float, optional
          Random seed. The default is `None`.
      extensions : list-likie, optional
          Collection of extensions to randomly select from for files.  The
          default is `['txt']`.  Leading period can be included or omitted.
  
      Raises
      ------
      FakedirError
          Issue selecting int from folders or files.
  
      Returns
      -------
      None, input is modified in place.
  
      '''
      random.seed(seed)
      if not isinstance(folders, int):
          try:
              fold_num = get_random_int(folders, seed=seed)
          except:
              raise FakedirError('folders must be an int or collection of int')
      else:
          fold_num = folders
      if not isinstance(files, int):
          try:
              file_num = get_random_int(files, seed=seed)
          except:
              raise FakedirError('files must be an int or collection of int')
      else:
          file_num = files
      for i in range(file_num):
          name = random.choice(words) + random.choice(extensions)
          while name in [f.name for f in fakedir._children]:
              name = random.choice(words) + random.choice(extensions)
          fakedir.create_file(name)
      for i in range(fold_num):
          name = random.choice(words)
          while name in [f.name for f in fakedir._children]:
              name = random.choice(words)
          fakedir.create_folder(name)
      for f in fakedir._children:
          if isinstance(f, FakeDir):
              if f.depth <= depth and random.uniform(0, 1) > stopchance:
                  if seed is not None:
                      seed += random.random()
                  populate(f, depth=depth, folders=folders, files=files,
                           seed=seed, stopchance=stopchance,
                           extensions=extensions)
  
  def randomdir(depth=2, files=range(1,4), folders=range(0,4),
                stopchance=.5, seed=None, name='MyFakeDir', extensions=['txt']):
      '''
      Create a randomized `seedir.fakedir.FakeDir`, initialized with random
      dictionary words.
  
      >>> import seedir as sd
      >>> sd.randomdir(seed=7)
      MyFakeDir/
      ├─Hardin.txt
      ├─Kathleen.txt
      ├─berserk/
      └─pineapple/
        ├─visceral.txt
        ├─gestural.txt
        ├─plenty.txt
        ├─discoid/
        │ ├─eclat.txt
        │ └─milord/
        ├─Offenbach/
        │ ├─Trianon.txt
        │ ├─Monday.txt
        │ ├─ditty.txt
        │ ├─peddle/
        │ ├─delta/
        │ └─irredentism/
        └─Perseus/
          ├─hothouse.txt
          ├─clock.txt
          ├─covetous/
          └─Ekstrom/
  
      Parameters
      ----------
      depth : int, optional
          Maximum depth to create folders and files. The default is `3`.
      folders : int or collection of integers, optional
          Parameter for setting the number of folders per directory.
          The default is `range(1,4)`.  If `int`, represents the number of folders
          per directory.  If collection of integers, a random value will be
          chosen from the collection each time a directory is popualted.
      files : int or collection of integers, optional, optional
          Same as the `folders` parameter, but for files.  The default
          is `range(0,4)`.
      stopchance : float between 0 and 1, optional
          Chance that an added folder will not be populated. The default is `.5`.
      seed : int or float, optional
          Random seed. The default is `None`.
      extensions : list-likie, optional
          Collection of extensions to randomly select from for files.  The
          default is `['txt']`.  Leading period can be included or omitted.
  
      Returns
      -------
      top : seedir.fakedir.FakeDir
          Fake directory.
  
      '''
      top = FakeDir(name)
      new_ex = []
      for x in extensions:
          if x[0] != '.':
              new_ex.append('.' + x)
          else:
              new_ex.append(x)
      populate(top, depth, folders, files, seed=seed, stopchance=stopchance,
               extensions=new_ex)
      return top
  
  def recursive_add_fakes(path, parent, depth=0, depthlimit=None,
                          itemlimit=None, first=None, sort=False,
                          sort_reverse=False, sort_key=None,
                          include_folders=None, exclude_folders=None,
                          include_files=None, exclude_files=None,
                          mask=None, regex=False):
      '''
      Recursive helper function for seedir.fakedir(), for creating a
      fake folder tree from a real one.
  
      Parameters
      ----------
      path : str
          System path of a folder.
      parent : seedir.fakedir.FakeDir
          Fake directory to add items to.
      depth : int, optional
          Tracker for depth of folders traversed. The default is 0.
      depthlimit : int, optional
          Limit on the depth of folders to traverse. The default is None.
      itemlimit : int, optional
          Limit on the number of items to include per directory.
          The default is None.
      first : 'folders' or 'files', optional
          Sort to have folders or files appear first. The default is None.
      sort : bool, optional
          Apply a sort to items in each directory. The default is False.
      sort_reverse : bool, optional
          Reverse the sort. The default is False.
      sort_key : function, optional
          Key function for sorting item names. The default is None.
      include_folders, exclude_folders, include_files, exclude_files : str, list-like, or None, optional
              Folder / file names to include or exclude. The default is None.
      mask : function, optional
          Function for filtering items.  Absolute paths of each individual item
          are passed to the mask function.  If True is returned, the
          item is kept.  The default is None.
      regex : bool, optional
          Interpret include/exclude folder/file arguments as regular
          expressions. The default is False.
  
      Returns
      -------
      None.
  
      '''
      if depthlimit is not None and depth >= depthlimit:
          return
      depth +=1
      listdir = listdir_fullpath(path)
      if sort or first is not None:
          listdir = sort_dir(listdir, first=first,
                             sort_reverse=sort_reverse, sort_key=sort_key)
      if any(arg is not None for arg in [
              include_folders,
              exclude_folders,
              include_files,
              exclude_files,
              mask]):
          listdir = filter_item_names(listdir,
                                      include_folders=include_folders,
                                      exclude_folders=exclude_folders,
                                      include_files=include_files,
                                      exclude_files=exclude_files,
                                      regex=regex,
                                      mask=mask)
      for i, f in enumerate(listdir):
          name = os.path.basename(f)
          if i == itemlimit:
              break
          if os.path.isdir(f):
              new = FakeDir(name=name, parent=parent)
              recursive_add_fakes(path=f, parent=new, depth=depth,
                                  depthlimit=depthlimit,
                                  itemlimit=itemlimit,
                                  include_folders=include_folders,
                                  exclude_folders=exclude_folders,
                                  include_files=include_files,
                                  exclude_files=exclude_files,
                                  mask=mask, regex=regex)
          else:
              new = FakeFile(name=name, parent=parent)
  
  def fakedir(path, depthlimit=None, itemlimit=None, first=None,
              sort=False, sort_reverse=False, sort_key=None,
              include_folders=None, exclude_folders=None, include_files=None,
              exclude_files=None, mask=None, regex=True):
      '''
      Function for creating a `seedir.fakedir.FakeDir` (representation of a directory)
      from a real system directory.  Rather than immediately representing
      a directory as a string (`seedir.realdir.seedir()`), this function can be used
      to create an editable representation of the directory, or to join one or
      more directories.
  
  
      >>> import seedir as sd
      >>> f = sd.fakedir('.', depthlimit=0)
      >>> type(f)
      <class 'seedir.fakedir.FakeDir'>
  
      Parameters
      ----------
      path : str
          System path of a directory.
      depthlimit : int, optional
          Limit on the depth of directories to traverse. Folders at the depth
          limit will be included, but their contents will not be.
          The default is `None`.
      itemlimit : int, optional
          Limit on the number of items to include per directory.
          The default is `None`, meaning all items will be added.  The priority
          of items is determined by `os.listdir()`, unless sorting arguments
          are passed.
      first : 'folders' or 'files', optional
          Sort to show folders or files first. The default is `None`.
      sort : bool, optional
          Apply a (name) sort on each directory. The default is `False`.
      sort_reverse : bool, optional
          Reverse the sort. The default is `False`.
      sort_key : function, optional
          Key function for sorting the file names. The default is `None`.
      include_folders, exclude_folders, include_files, exclude_files : str, list-like, or None, optional
              Folder / file names to include or exclude. The default is `None`.
      mask : function, optional
          Function for filtering items.  Absolute paths of each individual item
          are passed to the mask function.  If `True` is returned, the
          item is kept.  The default is `Non`e.
      regex : bool, optional
          Interpret include/exclude folder/file arguments as regular
          expressions. The default is `False`.
  
      Raises
      ------
      FakedirError
          path does not point to a directory.
  
      Returns
      -------
      output : seedir.fakedir.FakeDir
          Fake directory matching the path.
  
      '''
      if not os.path.isdir(path):
          raise FakedirError('path must be a directory')
      output = FakeDir(os.path.basename(path))
      recursive_add_fakes(path, parent=output, depthlimit=depthlimit,
                          itemlimit=itemlimit,
                          first=first,
                          sort=sort,
                          sort_reverse=sort_reverse,
                          sort_key=sort_key,
                          include_folders=include_folders,
                          exclude_folders=exclude_folders,
                          include_files=include_files,
                          exclude_files=exclude_files,
                          mask=mask, regex=regex)
      return output
  
  def fakedir_fromstring(s, start_chars=None, name_chars=None,
                         header_regex=None, name_regex=None,
                         supername='FakeDir', parse_comments=True):
      '''
      Convert a string folder tree diagram into a `seedir.fakedir.FakeDir`.
      This can be used to read in external representations of folder structures,
      edit them, and recreate them in a new location.
  
      This function has mostly been tested with examples from Stack Overflow
      and other Python learning sites (as well as output from `seedir.realdir.seedir()`).
      There are surely cases which will causes errors or unexpected results.
  
      >>> import seedir as sd
      >>> s = """doc/
      ... ├─_static/
      ... │ ├─embedded/
      ... │ │ ├─deep_file
      ... │ │ └─very/
      ... │ │   └─deep/
      ... │ │     └─folder/
      ... │ │       └─very_deep_file
      ... │ └─less_deep_file
      ... ├─about.rst
      ... ├─conf.py
      ... └─index.rst"""
  
      >>> f = sd.fakedir_fromstring(s) # now can be edited or restyled
      >>> f.seedir(style='spaces')
      doc/
        _static/
          embedded/
            deep_file
            very/
              deep/
                folder/
                  very_deep_file
          less_deep_file
        about.rst
        conf.py
        index.rst
  
  
      Parameters
      ----------
      s : str
          String representation a folder tree.
      start_chars : str, optional
          A string of characters which will be searched for as the start
          of a folder or file name. The default is `None`, in which case the
          characters are all letters, numbers, and punctuation marks except
          for `/:?"*<>|` or `+=-`.
      name_chars : str, optional
          A string of characters which will be searched for as being part
          of a file or folder name. The default is `None`, in which case the
          characters are all letters, numbers, punctuation marks except
          for `/:?"*<>|,` and spaces.
      header_regex : str, optional
          Regular expression to match the "header" of each line, i.e. the
          structural characters of the folder diagram.  The default is `None`.
          If passed, the `start_chars` argument will be ignored.  This and
          `name_regex` are intended to provide functionality for parsing
          specific or unusual cases.
      name_regex : str, optional
          Regular expression to match all the folder or file names.
          The default is `None`.
          If passed, the `start_chars` argument will be ignored.
      supername : str, optional
          Name to give the head directory if one cannot be found.
          The default is `'FakeDir'`.  This can happen when there is no clear
          head directory in the string.
      parse_comments : bool, optional
          Try to parse and remove Python comments (following `#`).
          The default is `True`.
  
      Returns
      -------
      seedir.fakedir.FakeDir
          Fake directory corresponding to the input string.
  
      '''
      slashes = ['/', '\\', os.sep]
      joinedslashes = ''.join(slashes)
  
      byline = s.split('\n')
      keyboard_chars = (string.ascii_letters + string.digits
                        + string.punctuation)
      filtered = "".join([c for c in keyboard_chars if c not in '/:?"*<>|'])
      if start_chars is None:
          start_chars = "".join([c for c in filtered if c not in '+=-'])
      if name_chars is None:
          name_chars = filtered + '\s' + '-'
  
      names = []
      headers = []
      depths = []
  
      for line in byline:
          if not line:
              continue
          if header_regex is None:
              header = re.match('.*?(?=[{}])'.format(start_chars), line)
          else:
              header = re.match(header_regex, line)
          if header is None:
              continue
          else:
              header = header.group()
          depth = len(header)
          if name_regex is None:
              name = re.match('[{}]*[/\\\\]*'.format(name_chars), line[depth:])
          else:
              name = re.match(name_regex, line)
          if name is None:
              continue
          else:
              name = name.group()
          if '#' in name and parse_comments:
              name = re.match('.*?(?=#)', name).group().strip()
          if not name:
              continue
  
          headers.append(header)
          names.append(name)
          depths.append(depth)
  
      fakeitems = []
      superparent = None
      min_depth = min(depths)
      if len([d for d in depths if d == min_depth]) > 1:
          superparent = FakeDir(supername)
      min_depth_index1 = depths.index(min_depth)
      if any(i > min_depth for i in depths[:min_depth_index1]):
          superparent = FakeDir(supername)
  
      for i, name in enumerate(names):
          is_folder = False
          if name.strip()[-1] in slashes:
              is_folder = True
          if i < len(names) - 1:
              if depths[i + 1] > depths[i]:
                  is_folder = True
  
          fmt_name = name.rstrip(joinedslashes)
  
          if depths[i] == min_depth:
              if is_folder:
                  fakeitems.append(FakeDir(fmt_name, parent=superparent))
              else:
                  fakeitems.append(FakeFile(fmt_name, parent=superparent))
          else:
              shallower = [d for d in depths[:i] if d < depths[i]]
              if shallower:
                  max_shallower = max([d for d in depths[:i] if d < depths[i]])
                  parent_index = max(idx for idx, val in enumerate(depths[:i])
                                     if val == max_shallower)
                  parent = fakeitems[parent_index]
              else:
                  parent = superparent
              if is_folder:
                  fakeitems.append(FakeDir(fmt_name, parent=parent))
              else:
                  fakeitems.append(FakeFile(fmt_name, parent=parent))
  
      if superparent is not None:
          return superparent
      else:
          idx = depths.index(min_depth)
          return fakeitems[idx]
  
  