Module treegenerator.treegenerator

Expand source code
from pathlib import Path

from printbuddies import ProgBar, clear, print_in_place


class TreeGenerator:
    """Generates a nested dictionary of arbitrary depth
    representing the subdirectories of a given directory."""

    def __init__(self, starting_directory: str | Path):
        self.starting_dir = Path(starting_directory)
        self.paths = [self.starting_dir]
        self._pipe = "|"
        self._tee = "|-"
        self._elbow = "|_"
        self.generate()

    def generate(self):
        self.paths.extend(self._crawl(self.starting_dir))
        clear()
        self._drop_parents()
        self._generate_tree()
        self._generate_tree_string()

    def _drop_parents(self):
        self.paths = [
            Path(str(path)[str(path).find(self.starting_dir.stem) :])
            for path in self.paths
        ]

    def __str__(self):
        return f"{self.tree_string}"

    def _crawl(self, start_dir: Path) -> list[Path]:
        """Generate recursive list of paths by crawling
        down every branch from the starting_dir."""
        paths = []
        print_in_place(f"Crawling {start_dir}...")
        for item in start_dir.iterdir():
            if item.is_file():
                paths.append(item)
            elif item.is_dir():
                paths.extend(self._crawl(item))
                print_in_place(f"Crawling {start_dir}...")
        return paths

    def _generate_tree(self):
        """Generate nested dictionary of subdirectories."""
        self.tree = {}
        prog_bar = ProgBar(len(self.paths))
        prog_bar.display()
        for path in sorted(self.paths):
            if path.parts[0] not in self.tree:
                self.tree[path.parts[0]] = {}
            current_layer = self.tree[path.parts[0]]
            branches = path.parts[1:]
            for branch in branches:
                if branch not in current_layer:
                    current_layer[branch] = {}
                current_layer = current_layer[branch]
            prog_bar.display()

    def _format_branch_name(self, branch_name: str, index: int) -> str:
        if index == 0:
            return f"{branch_name}\n"
        elif index == 1:
            return f'{self._pipe}{"  "*index}{self._tee}{branch_name}\n'
        else:
            return (
                f'{self._pipe}{("  "+self._pipe)*(index-1)}  {self._tee}{branch_name}\n'
            )

    def _convert_branch_to_string(
        self, branch: dict, branch_name: str, index: int
    ) -> str:
        """Iterates through a nested dictionary and returns a string representation."""
        output = self._format_branch_name(branch_name, index)
        for i, leaf in enumerate(branch.keys()):
            if branch[leaf] == {}:
                output += f'{self._pipe}{("  "+self._pipe)*index}  '
                if i == len(branch.keys()) - 1:
                    output += f"{self._elbow}{leaf}\n"
                    output += f'{self._pipe}{("  "+self._pipe)*index}\n'
                else:
                    output += f"{self._tee}{leaf}\n"
            else:
                output += self._convert_branch_to_string(branch[leaf], leaf, index + 1)
        return output

    def _trim_tree(self):
        tree = self.tree_string.splitlines()
        self.tree_string = ""
        for i, line in enumerate(tree[:-1]):
            if (
                all(ch in f"{self._pipe} " for ch in line)
                and i < len(tree) - 2
                and self._tee in tree[i + 1]
            ):
                self.tree_string += f"{line[:tree[i+1].find(self._tee)+1]}\n"
            else:
                self.tree_string += f"{line}\n"

    def _generate_tree_string(self):
        """Generates formatted string representation of
        the directory tree."""
        self.tree_string = ""
        for root in self.tree.keys():
            self.tree_string += self._convert_branch_to_string(self.tree[root], root, 0)
        self._trim_tree()


class UrlTreeGenerator(TreeGenerator):
    """Generates a tree representation of a given list of urls."""

    def __init__(self, urls: list[str]):
        self.paths = [Path(url) for url in urls]
        self._pipe = "|"
        self._tee = "|-"
        self._elbow = "|_"

    def generate(self):
        self._generate_tree()
        self._generate_tree_string()

Classes

class TreeGenerator (starting_directory: str | pathlib.Path)

Generates a nested dictionary of arbitrary depth representing the subdirectories of a given directory.

Expand source code
class TreeGenerator:
    """Generates a nested dictionary of arbitrary depth
    representing the subdirectories of a given directory."""

    def __init__(self, starting_directory: str | Path):
        self.starting_dir = Path(starting_directory)
        self.paths = [self.starting_dir]
        self._pipe = "|"
        self._tee = "|-"
        self._elbow = "|_"
        self.generate()

    def generate(self):
        self.paths.extend(self._crawl(self.starting_dir))
        clear()
        self._drop_parents()
        self._generate_tree()
        self._generate_tree_string()

    def _drop_parents(self):
        self.paths = [
            Path(str(path)[str(path).find(self.starting_dir.stem) :])
            for path in self.paths
        ]

    def __str__(self):
        return f"{self.tree_string}"

    def _crawl(self, start_dir: Path) -> list[Path]:
        """Generate recursive list of paths by crawling
        down every branch from the starting_dir."""
        paths = []
        print_in_place(f"Crawling {start_dir}...")
        for item in start_dir.iterdir():
            if item.is_file():
                paths.append(item)
            elif item.is_dir():
                paths.extend(self._crawl(item))
                print_in_place(f"Crawling {start_dir}...")
        return paths

    def _generate_tree(self):
        """Generate nested dictionary of subdirectories."""
        self.tree = {}
        prog_bar = ProgBar(len(self.paths))
        prog_bar.display()
        for path in sorted(self.paths):
            if path.parts[0] not in self.tree:
                self.tree[path.parts[0]] = {}
            current_layer = self.tree[path.parts[0]]
            branches = path.parts[1:]
            for branch in branches:
                if branch not in current_layer:
                    current_layer[branch] = {}
                current_layer = current_layer[branch]
            prog_bar.display()

    def _format_branch_name(self, branch_name: str, index: int) -> str:
        if index == 0:
            return f"{branch_name}\n"
        elif index == 1:
            return f'{self._pipe}{"  "*index}{self._tee}{branch_name}\n'
        else:
            return (
                f'{self._pipe}{("  "+self._pipe)*(index-1)}  {self._tee}{branch_name}\n'
            )

    def _convert_branch_to_string(
        self, branch: dict, branch_name: str, index: int
    ) -> str:
        """Iterates through a nested dictionary and returns a string representation."""
        output = self._format_branch_name(branch_name, index)
        for i, leaf in enumerate(branch.keys()):
            if branch[leaf] == {}:
                output += f'{self._pipe}{("  "+self._pipe)*index}  '
                if i == len(branch.keys()) - 1:
                    output += f"{self._elbow}{leaf}\n"
                    output += f'{self._pipe}{("  "+self._pipe)*index}\n'
                else:
                    output += f"{self._tee}{leaf}\n"
            else:
                output += self._convert_branch_to_string(branch[leaf], leaf, index + 1)
        return output

    def _trim_tree(self):
        tree = self.tree_string.splitlines()
        self.tree_string = ""
        for i, line in enumerate(tree[:-1]):
            if (
                all(ch in f"{self._pipe} " for ch in line)
                and i < len(tree) - 2
                and self._tee in tree[i + 1]
            ):
                self.tree_string += f"{line[:tree[i+1].find(self._tee)+1]}\n"
            else:
                self.tree_string += f"{line}\n"

    def _generate_tree_string(self):
        """Generates formatted string representation of
        the directory tree."""
        self.tree_string = ""
        for root in self.tree.keys():
            self.tree_string += self._convert_branch_to_string(self.tree[root], root, 0)
        self._trim_tree()

Subclasses

Methods

def generate(self)
Expand source code
def generate(self):
    self.paths.extend(self._crawl(self.starting_dir))
    clear()
    self._drop_parents()
    self._generate_tree()
    self._generate_tree_string()
class UrlTreeGenerator (urls: list[str])

Generates a tree representation of a given list of urls.

Expand source code
class UrlTreeGenerator(TreeGenerator):
    """Generates a tree representation of a given list of urls."""

    def __init__(self, urls: list[str]):
        self.paths = [Path(url) for url in urls]
        self._pipe = "|"
        self._tee = "|-"
        self._elbow = "|_"

    def generate(self):
        self._generate_tree()
        self._generate_tree_string()

Ancestors

Methods

def generate(self)
Expand source code
def generate(self):
    self._generate_tree()
    self._generate_tree_string()