file_structure_representation.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. # mypy: allow-untyped-defs
  2. from typing import Dict, List
  3. from .glob_group import GlobGroup, GlobPattern
  4. __all__ = ["Directory"]
  5. class Directory:
  6. """A file structure representation. Organized as Directory nodes that have lists of
  7. their Directory children. Directories for a package are created by calling
  8. :meth:`PackageImporter.file_structure`."""
  9. def __init__(self, name: str, is_dir: bool):
  10. self.name = name
  11. self.is_dir = is_dir
  12. self.children: Dict[str, Directory] = {}
  13. def _get_dir(self, dirs: List[str]) -> "Directory":
  14. """Builds path of Directories if not yet built and returns last directory
  15. in list.
  16. Args:
  17. dirs (List[str]): List of directory names that are treated like a path.
  18. Returns:
  19. :class:`Directory`: The last Directory specified in the dirs list.
  20. """
  21. if len(dirs) == 0:
  22. return self
  23. dir_name = dirs[0]
  24. if dir_name not in self.children:
  25. self.children[dir_name] = Directory(dir_name, True)
  26. return self.children[dir_name]._get_dir(dirs[1:])
  27. def _add_file(self, file_path: str):
  28. """Adds a file to a Directory.
  29. Args:
  30. file_path (str): Path of file to add. Last element is added as a file while
  31. other paths items are added as directories.
  32. """
  33. *dirs, file = file_path.split("/")
  34. dir = self._get_dir(dirs)
  35. dir.children[file] = Directory(file, False)
  36. def has_file(self, filename: str) -> bool:
  37. """Checks if a file is present in a :class:`Directory`.
  38. Args:
  39. filename (str): Path of file to search for.
  40. Returns:
  41. bool: If a :class:`Directory` contains the specified file.
  42. """
  43. lineage = filename.split("/", maxsplit=1)
  44. child = lineage[0]
  45. grandchildren = lineage[1] if len(lineage) > 1 else None
  46. if child in self.children.keys():
  47. if grandchildren is None:
  48. return True
  49. else:
  50. return self.children[child].has_file(grandchildren)
  51. return False
  52. def __str__(self):
  53. str_list: List[str] = []
  54. self._stringify_tree(str_list)
  55. return "".join(str_list)
  56. def _stringify_tree(
  57. self,
  58. str_list: List[str],
  59. preamble: str = "",
  60. dir_ptr: str = "\u2500\u2500\u2500 ",
  61. ):
  62. """Recursive method to generate print-friendly version of a Directory."""
  63. space = " "
  64. branch = "\u2502 "
  65. tee = "\u251c\u2500\u2500 "
  66. last = "\u2514\u2500\u2500 "
  67. # add this directory's representation
  68. str_list.append(f"{preamble}{dir_ptr}{self.name}\n")
  69. # add directory's children representations
  70. if dir_ptr == tee:
  71. preamble = preamble + branch
  72. else:
  73. preamble = preamble + space
  74. file_keys: List[str] = []
  75. dir_keys: List[str] = []
  76. for key, val in self.children.items():
  77. if val.is_dir:
  78. dir_keys.append(key)
  79. else:
  80. file_keys.append(key)
  81. for index, key in enumerate(sorted(dir_keys)):
  82. if (index == len(dir_keys) - 1) and len(file_keys) == 0:
  83. self.children[key]._stringify_tree(str_list, preamble, last)
  84. else:
  85. self.children[key]._stringify_tree(str_list, preamble, tee)
  86. for index, file in enumerate(sorted(file_keys)):
  87. pointer = last if (index == len(file_keys) - 1) else tee
  88. str_list.append(f"{preamble}{pointer}{file}\n")
  89. def _create_directory_from_file_list(
  90. filename: str,
  91. file_list: List[str],
  92. include: "GlobPattern" = "**",
  93. exclude: "GlobPattern" = (),
  94. ) -> Directory:
  95. """Return a :class:`Directory` file structure representation created from a list of files.
  96. Args:
  97. filename (str): The name given to the top-level directory that will be the
  98. relative root for all file paths found in the file_list.
  99. file_list (List[str]): List of files to add to the top-level directory.
  100. include (Union[List[str], str]): An optional pattern that limits what is included from the file_list to
  101. files whose name matches the pattern.
  102. exclude (Union[List[str], str]): An optional pattern that excludes files whose name match the pattern.
  103. Returns:
  104. :class:`Directory`: a :class:`Directory` file structure representation created from a list of files.
  105. """
  106. glob_pattern = GlobGroup(include, exclude=exclude, separator="/")
  107. top_dir = Directory(filename, True)
  108. for file in file_list:
  109. if glob_pattern.matches(file):
  110. top_dir._add_file(file)
  111. return top_dir