Skip to content

filetree

Class info

Classes

Name Children Inherits
DirectoryTree
jinjarope.filetree
A class to generate and print directory tree structure.
    SortCriteria
    jinjarope.filetree
    Enumeration for different sorting criteria.
      TreeOptions
      jinjarope.filetree
      Configuration options for directory tree printing.

        🛈 DocStrings

        DirectoryTree

        A class to generate and print directory tree structure.

        Source code in src/jinjarope/filetree.py
         80
         81
         82
         83
         84
         85
         86
         87
         88
         89
         90
         91
         92
         93
         94
         95
         96
         97
         98
         99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134
        135
        136
        137
        138
        139
        140
        141
        142
        143
        144
        145
        146
        147
        148
        149
        150
        151
        152
        153
        154
        155
        156
        157
        158
        159
        160
        161
        162
        163
        164
        165
        166
        167
        168
        169
        170
        171
        172
        173
        174
        175
        176
        177
        178
        179
        180
        181
        182
        183
        184
        185
        186
        187
        188
        189
        190
        191
        192
        193
        194
        195
        196
        197
        198
        199
        200
        201
        202
        203
        204
        205
        206
        207
        208
        209
        210
        211
        212
        213
        214
        215
        216
        217
        218
        219
        220
        221
        222
        223
        224
        225
        226
        227
        228
        229
        230
        231
        232
        233
        234
        235
        236
        237
        238
        239
        240
        241
        242
        243
        244
        245
        246
        247
        248
        249
        250
        251
        252
        253
        254
        255
        256
        257
        258
        259
        260
        261
        262
        263
        264
        265
        266
        267
        268
        269
        270
        271
        272
        273
        274
        275
        276
        277
        278
        279
        280
        281
        282
        283
        284
        285
        286
        287
        288
        289
        290
        291
        292
        293
        294
        295
        296
        297
        298
        299
        300
        301
        class DirectoryTree:
            """A class to generate and print directory tree structure."""
        
            PIPE = "┃   "
            ELBOW = "┗━━ "
            TEE = "┣━━ "
            DIRECTORY = "📂"
        
            def __init__(
                self,
                root_path: str | os.PathLike[str],
                options: TreeOptions | None = None,
            ) -> None:
                """A class to generate and print directory tree structure.
        
                Attributes:
                    root_path: Root path of the directory tree.
                    options: Options for directory tree printing.
                """
                self.root_path = upath.UPath(root_path)
                self.options = options or TreeOptions()
        
            def _get_sort_key(self, path: pathlib.Path) -> tuple[bool, Any]:
                """Generate sort key based on current sort criteria.
        
                Args:
                    path: Path to get sort key for.
        
                Returns:
                    Tuple containing boolean indicating if path is a directory and
                    the sort key based on the selected criteria.
                """
                info = _get_path_info(path)
                criteria_keys = {
                    SortCriteria.NAME: lambda: (info["name"].lower(),),
                    SortCriteria.SIZE: lambda: (info["size"],),
                    SortCriteria.DATE: lambda: (info["mtime"],),
                    SortCriteria.EXTENSION: lambda: (info["extension"], info["name"].lower()),
                }
                # Always sort directories first within each category
                return not path.is_dir(), criteria_keys[self.options.sort_criteria]()
        
            def _should_include(self, path: pathlib.Path) -> bool:
                """Check if path should be included based on filters.
        
                Args:
                    path: Path to check.
        
                Returns:
                    True if the path should be included, False otherwise.
                """
                name = path.name
        
                if not self.options.show_hidden and name.startswith("."):
                    return False
        
                if self.options.include_pattern and not self.options.include_pattern.match(name):
                    return False
        
                if self.options.exclude_pattern and self.options.exclude_pattern.match(name):
                    return False
        
                return not (
                    self.options.allowed_extensions
                    and (
                        path.is_file()
                        and path.suffix.lower() not in self.options.allowed_extensions
                    )
                )
        
            def _is_directory_empty_after_filters(
                self,
                directory: pathlib.Path,
                depth: int = 0,
            ) -> bool:
                """Recursively check if directory is empty after applying all filters.
        
                Args:
                    directory: Directory to check.
                    depth: Current depth of recursion.
        
                Returns:
                    True if directory has no visible contents after filtering,
                    False otherwise.
                """
                if self.options.max_depth is not None and depth > self.options.max_depth:
                    return True
        
                try:
                    # Get all paths and apply filters
                    paths = [p for p in directory.iterdir() if self._should_include(p)]
        
                    # If no paths remain after filtering, directory is considered empty
                    if not paths:
                        return True
        
                    # For directories, recursively check if they're empty
                    for path in paths:
                        if path.is_dir():
                            # If a directory is not empty, this directory is not empty
                            if not self._is_directory_empty_after_filters(path, depth + 1):
                                return False
                        else:
                            # If we find any visible file, directory is not empty
                            return False
                    else:
                        # If we only found empty directories, this directory is empty
                        return True
        
                except (PermissionError, OSError):
                    # Treat inaccessible directories as empty
                    return True
        
            def _get_tree_entries(
                self, directory: pathlib.Path, prefix: str = "", depth: int = 0
            ) -> list[tuple[str, pathlib.Path, bool]]:
                """Generate tree entries with proper formatting.
        
                Args:
                    directory: Directory to generate entries for.
                    prefix: Prefix string for the entry.
                    depth: Current depth of recursion.
        
                Returns:
                    List of tuples containing prefix, path and boolean indicating if it's
                    the last entry.
                """
                entries: list[tuple[str, pathlib.Path, bool]] = []
        
                if self.options.max_depth is not None and depth > self.options.max_depth:
                    return entries
        
                try:
                    # Get all paths and apply sorting
                    paths = sorted(
                        directory.iterdir(),
                        key=self._get_sort_key,
                        reverse=self.options.reverse_sort,
                    )
                except (PermissionError, OSError) as e:
                    print(f"Error accessing {directory}: {e}")
                    return entries
        
                # Filter paths and check if they're empty (if directories)
                visible_paths: list[pathlib.Path] = []
                for path in paths:
                    if not self._should_include(path):
                        continue
        
                    if (
                        path.is_dir()
                        and self.options.hide_empty
                        and self._is_directory_empty_after_filters(path, depth + 1)
                    ):
                        continue
                    visible_paths.append(path)
        
                # Process visible paths
                for i, path in enumerate(visible_paths):
                    is_last = i == len(visible_paths) - 1
                    connector = self.ELBOW if is_last else self.TEE
        
                    entries.append((f"{prefix}{connector}", path, is_last))
        
                    if path.is_dir():
                        new_prefix = f"{prefix}{self.PIPE}" if not is_last else f"{prefix}    "
                        entries.extend(self._get_tree_entries(path, new_prefix, depth + 1))
        
                return entries
        
            def get_tree_text(self) -> str:
                """Generate and return the directory tree as a string."""
                return "\n".join(self.iter_tree_lines())
        
            def iter_tree_lines(self) -> Iterator[str]:
                """Iterate the directory and yield the formatted lines.
        
                Included items as well as design is based on the configured options.
                """
                if not self.root_path.exists():
                    msg = f"Path does not exist: {self.root_path}"
                    raise FileNotFoundError(msg)
        
                # Check if root directory is empty after filtering
                if self.options.hide_empty and self._is_directory_empty_after_filters(
                    self.root_path
                ):
                    icon = self.DIRECTORY if self.options.show_icons else ""
                    yield f"{icon} {self.root_path.name} (empty)"
                    return
        
                root_icon = self.DIRECTORY if self.options.show_icons else ""
                yield f"{root_icon} {self.root_path.name}"
        
                for prefix, path, _is_last in self._get_tree_entries(self.root_path):
                    info = _get_path_info(path)
        
                    # Prepare icon
                    icon = ""
                    if self.options.show_icons:
                        icon = (
                            self.DIRECTORY
                            if info["is_dir"]
                            else iconfilters.get_path_ascii_icon(path)
                        )
        
                    # Prepare additional information
                    details: list[str] = []
        
                    if self.options.show_size and not info["is_dir"]:
                        details.append(f"{filters.do_filesizeformat(info['size'])}")
        
                    if self.options.show_date:
                        s = textfilters.format_timestamp(info["mtime"], self.options.date_format)
                        details.append(s)
                    if self.options.show_permissions:
                        permissions = stat.filemode(info["mode"])
                        details.append(permissions)
        
                    details_str = f" ({', '.join(details)})" if details else ""
        
                    yield f"{prefix}{icon} {path.name}{details_str}"
        

        __init__

        __init__(root_path: str | PathLike[str], options: TreeOptions | None = None) -> None
        

        A class to generate and print directory tree structure.

        Attributes:

        Name Type Description
        root_path

        Root path of the directory tree.

        options

        Options for directory tree printing.

        Source code in src/jinjarope/filetree.py
         88
         89
         90
         91
         92
         93
         94
         95
         96
         97
         98
         99
        100
        def __init__(
            self,
            root_path: str | os.PathLike[str],
            options: TreeOptions | None = None,
        ) -> None:
            """A class to generate and print directory tree structure.
        
            Attributes:
                root_path: Root path of the directory tree.
                options: Options for directory tree printing.
            """
            self.root_path = upath.UPath(root_path)
            self.options = options or TreeOptions()
        

        get_tree_text

        get_tree_text() -> str
        

        Generate and return the directory tree as a string.

        Source code in src/jinjarope/filetree.py
        250
        251
        252
        def get_tree_text(self) -> str:
            """Generate and return the directory tree as a string."""
            return "\n".join(self.iter_tree_lines())
        

        iter_tree_lines

        iter_tree_lines() -> Iterator[str]
        

        Iterate the directory and yield the formatted lines.

        Included items as well as design is based on the configured options.

        Source code in src/jinjarope/filetree.py
        254
        255
        256
        257
        258
        259
        260
        261
        262
        263
        264
        265
        266
        267
        268
        269
        270
        271
        272
        273
        274
        275
        276
        277
        278
        279
        280
        281
        282
        283
        284
        285
        286
        287
        288
        289
        290
        291
        292
        293
        294
        295
        296
        297
        298
        299
        300
        301
        def iter_tree_lines(self) -> Iterator[str]:
            """Iterate the directory and yield the formatted lines.
        
            Included items as well as design is based on the configured options.
            """
            if not self.root_path.exists():
                msg = f"Path does not exist: {self.root_path}"
                raise FileNotFoundError(msg)
        
            # Check if root directory is empty after filtering
            if self.options.hide_empty and self._is_directory_empty_after_filters(
                self.root_path
            ):
                icon = self.DIRECTORY if self.options.show_icons else ""
                yield f"{icon} {self.root_path.name} (empty)"
                return
        
            root_icon = self.DIRECTORY if self.options.show_icons else ""
            yield f"{root_icon} {self.root_path.name}"
        
            for prefix, path, _is_last in self._get_tree_entries(self.root_path):
                info = _get_path_info(path)
        
                # Prepare icon
                icon = ""
                if self.options.show_icons:
                    icon = (
                        self.DIRECTORY
                        if info["is_dir"]
                        else iconfilters.get_path_ascii_icon(path)
                    )
        
                # Prepare additional information
                details: list[str] = []
        
                if self.options.show_size and not info["is_dir"]:
                    details.append(f"{filters.do_filesizeformat(info['size'])}")
        
                if self.options.show_date:
                    s = textfilters.format_timestamp(info["mtime"], self.options.date_format)
                    details.append(s)
                if self.options.show_permissions:
                    permissions = stat.filemode(info["mode"])
                    details.append(permissions)
        
                details_str = f" ({', '.join(details)})" if details else ""
        
                yield f"{prefix}{icon} {path.name}{details_str}"
        

        SortCriteria

        Bases: Enum

        Enumeration for different sorting criteria.

        Source code in src/jinjarope/filetree.py
        22
        23
        24
        25
        26
        27
        28
        class SortCriteria(Enum):
            """Enumeration for different sorting criteria."""
        
            NAME = auto()
            SIZE = auto()
            DATE = auto()
            EXTENSION = auto()
        

        TreeOptions dataclass

        Configuration options for directory tree printing.

        Source code in src/jinjarope/filetree.py
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        @dataclass
        class TreeOptions:
            """Configuration options for directory tree printing."""
        
            show_hidden: bool = False
            show_size: bool = True
            show_date: bool = False
            show_permissions: bool = False
            show_icons: bool = True
            max_depth: int | None = None
            include_pattern: Pattern[str] | None = None
            exclude_pattern: Pattern[str] | None = None
            allowed_extensions: set[str] | None = None
            hide_empty: bool = True
            sort_criteria: SortCriteria = SortCriteria.NAME
            reverse_sort: bool = False
            date_format: str = "%Y-%m-%d %H:%M:%S"
        

        get_directory_tree

        get_directory_tree(
            root_path: str | PathLike[str],
            *,
            show_hidden: bool = False,
            show_size: bool = True,
            show_date: bool = False,
            show_permissions: bool = False,
            show_icons: bool = True,
            max_depth: int | None = None,
            include_pattern: Pattern[str] | None = None,
            exclude_pattern: Pattern[str] | None = None,
            allowed_extensions: set[str] | None = None,
            hide_empty: bool = True,
            sort_criteria: SortCriteria = NAME,
            reverse_sort: bool = False,
            date_format: str = "%Y-%m-%d %H:%M:%S"
        ) -> str
        

        Create a DirectoryTree instance with the specified options.

        Parameters:

        Name Type Description Default
        root_path str | PathLike[str]

        Root path of the directory tree

        required
        show_hidden bool

        Whether to show hidden files/directories

        False
        show_size bool

        Whether to show file sizes

        True
        show_date bool

        Whether to show modification dates

        False
        show_permissions bool

        Whether to show file permissions

        False
        show_icons bool

        Whether to show icons for files/directories

        True
        max_depth int | None

        Maximum depth to traverse (None for unlimited)

        None
        include_pattern Pattern[str] | None

        Regex pattern for files/directories to include

        None
        exclude_pattern Pattern[str] | None

        Regex pattern for files/directories to exclude

        None
        allowed_extensions set[str] | None

        Set of allowed file extensions

        None
        hide_empty bool

        Whether to hide empty directories

        True
        sort_criteria SortCriteria

        Criteria for sorting entries

        NAME
        reverse_sort bool

        Whether to reverse the sort order

        False
        date_format str

        Format string for dates

        '%Y-%m-%d %H:%M:%S'

        Returns:

        Name Type Description
        DirectoryTree str

        Configured DirectoryTree instance

        Example
        tree = create_directory_tree(
            ".",
            show_hidden=True,
            max_depth=3,
            allowed_extensions={".py", ".txt"},
            exclude_pattern=re.compile(r"__pycache__")
        )
        tree.print_tree()
        
        Source code in src/jinjarope/filetree.py
        304
        305
        306
        307
        308
        309
        310
        311
        312
        313
        314
        315
        316
        317
        318
        319
        320
        321
        322
        323
        324
        325
        326
        327
        328
        329
        330
        331
        332
        333
        334
        335
        336
        337
        338
        339
        340
        341
        342
        343
        344
        345
        346
        347
        348
        349
        350
        351
        352
        353
        354
        355
        356
        357
        358
        359
        360
        361
        362
        363
        364
        365
        366
        367
        368
        369
        370
        def get_directory_tree(
            root_path: str | os.PathLike[str],
            *,
            show_hidden: bool = False,
            show_size: bool = True,
            show_date: bool = False,
            show_permissions: bool = False,
            show_icons: bool = True,
            max_depth: int | None = None,
            include_pattern: Pattern[str] | None = None,
            exclude_pattern: Pattern[str] | None = None,
            allowed_extensions: set[str] | None = None,
            hide_empty: bool = True,
            sort_criteria: SortCriteria = SortCriteria.NAME,
            reverse_sort: bool = False,
            date_format: str = "%Y-%m-%d %H:%M:%S",
        ) -> str:
            """Create a DirectoryTree instance with the specified options.
        
            Args:
                root_path: Root path of the directory tree
                show_hidden: Whether to show hidden files/directories
                show_size: Whether to show file sizes
                show_date: Whether to show modification dates
                show_permissions: Whether to show file permissions
                show_icons: Whether to show icons for files/directories
                max_depth: Maximum depth to traverse (None for unlimited)
                include_pattern: Regex pattern for files/directories to include
                exclude_pattern: Regex pattern for files/directories to exclude
                allowed_extensions: Set of allowed file extensions
                hide_empty: Whether to hide empty directories
                sort_criteria: Criteria for sorting entries
                reverse_sort: Whether to reverse the sort order
                date_format: Format string for dates
        
            Returns:
                DirectoryTree: Configured DirectoryTree instance
        
            Example:
                ```python
                tree = create_directory_tree(
                    ".",
                    show_hidden=True,
                    max_depth=3,
                    allowed_extensions={".py", ".txt"},
                    exclude_pattern=re.compile(r"__pycache__")
                )
                tree.print_tree()
                ```
            """
            options = TreeOptions(
                show_hidden=show_hidden,
                show_size=show_size,
                show_date=show_date,
                show_permissions=show_permissions,
                show_icons=show_icons,
                max_depth=max_depth,
                include_pattern=include_pattern,
                exclude_pattern=exclude_pattern,
                allowed_extensions=allowed_extensions,
                hide_empty=hide_empty,
                sort_criteria=sort_criteria,
                reverse_sort=reverse_sort,
                date_format=date_format,
            )
        
            return DirectoryTree(root_path, options).get_tree_text()