file_structure_representation.py 4.6 KB

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