|
7 | 7 | from enum import Enum, auto |
8 | 8 | from typing import TYPE_CHECKING |
9 | 9 | from abc import ABC |
| 10 | +from functools import singledispatchmethod |
10 | 11 |
|
11 | 12 | from gitingest.utils.compat_func import readlink |
12 | 13 | from gitingest.utils.file_utils import _decodes, _get_preferred_encodings, _read_chunk |
@@ -36,81 +37,84 @@ class FileSystemStats: |
36 | 37 | total_files: int = 0 |
37 | 38 | total_size: int = 0 |
38 | 39 |
|
39 | | - |
40 | | -class Source: |
| 40 | +@dataclass |
| 41 | +class Source(ABC): |
41 | 42 | """Abstract base class for all sources (files, directories, etc).""" |
42 | | - summary: str = "" |
43 | | - tree: str = "" |
44 | 43 | @property |
45 | | - def content(self) -> str: |
46 | | - return self._content |
47 | | - @content.setter |
48 | | - def content(self, value: str) -> None: |
49 | | - self._content = value |
| 44 | + def tree(self) -> str: |
| 45 | + return self._tree() |
| 46 | + @property |
| 47 | + def summary(self) -> str: |
| 48 | + return getattr(self, "_summary", "") |
| 49 | + @summary.setter |
| 50 | + def summary(self, value: str) -> None: |
| 51 | + self._summary = value |
50 | 52 |
|
| 53 | +@dataclass |
51 | 54 | class FileSystemNode(Source): |
52 | | - """Base class for all file system nodes (file, directory, symlink).""" |
53 | | - def __init__(self, name: str, path_str: str, path: 'Path', depth: int = 0): |
54 | | - self.name = name |
55 | | - self.path_str = path_str |
56 | | - self.path = path |
57 | | - self.depth = depth |
58 | | - self.summary = "" |
59 | | - self.tree = "" |
60 | | - self.children: list[FileSystemNode] = [] |
61 | | - self.size: int = 0 |
| 55 | + name: str |
| 56 | + path_str: str |
| 57 | + path: Path |
| 58 | + depth: int = 0 |
| 59 | + size: int = 0 |
62 | 60 |
|
63 | 61 | @property |
64 | | - def content(self) -> str: |
65 | | - raise NotImplementedError("Content is not implemented for FileSystemNode") |
| 62 | + def tree(self): |
| 63 | + return self._tree() |
| 64 | + |
| 65 | + @singledispatchmethod |
| 66 | + def _tree(self): |
| 67 | + return self.name |
66 | 68 |
|
| 69 | +@dataclass |
67 | 70 | class FileSystemFile(FileSystemNode): |
68 | | - @property |
69 | | - def content(self) -> str: |
70 | | - with open(self.path, "r", encoding="utf-8") as f: |
71 | | - return f.read() |
| 71 | + pass # Nothing for now |
72 | 72 |
|
| 73 | +@FileSystemNode._tree.register |
| 74 | +def _(self: 'FileSystemFile'): |
| 75 | + return self.name |
| 76 | + |
| 77 | +@dataclass |
73 | 78 | class FileSystemDirectory(FileSystemNode): |
74 | 79 | children: list['FileSystemNode'] = field(default_factory=list) |
75 | 80 | file_count: int = 0 |
76 | 81 | dir_count: int = 0 |
77 | 82 | type: FileSystemNodeType = FileSystemNodeType.DIRECTORY |
78 | 83 |
|
79 | 84 | def sort_children(self) -> None: |
80 | | - """Sort the children nodes of a directory according to a specific order. |
81 | | -
|
82 | | - Order of sorting: |
83 | | - 2. Regular files (not starting with dot) |
84 | | - 3. Hidden files (starting with dot) |
85 | | - 4. Regular directories (not starting with dot) |
86 | | - 5. Hidden directories (starting with dot) |
87 | | -
|
88 | | - All groups are sorted alphanumerically within themselves. |
89 | | -
|
90 | | - Raises |
91 | | - ------ |
92 | | - ValueError |
93 | | - If the node is not a directory. |
94 | | - """ |
95 | | - if self.type != FileSystemNodeType.DIRECTORY: |
96 | | - msg = "Cannot sort children of a non-directory node" |
97 | | - raise ValueError(msg) |
98 | | - |
| 85 | + """Sort the children nodes of a directory according to a specific order.""" |
99 | 86 | def _sort_key(child: FileSystemNode) -> tuple[int, str]: |
100 | | - # returns the priority order for the sort function, 0 is first |
101 | | - # Groups: 0=README, 1=regular file, 2=hidden file, 3=regular dir, 4=hidden dir |
102 | 87 | name = child.name.lower() |
103 | | - if hasattr(child, 'type') and child.type == FileSystemNodeType.FILE: |
| 88 | + if hasattr(child, 'type') and getattr(child, 'type', None) == FileSystemNodeType.FILE: |
104 | 89 | if name == "readme" or name.startswith("readme."): |
105 | 90 | return (0, name) |
106 | 91 | return (1 if not name.startswith(".") else 2, name) |
107 | 92 | return (3 if not name.startswith(".") else 4, name) |
108 | | - |
109 | 93 | self.children.sort(key=_sort_key) |
110 | 94 |
|
| 95 | +@FileSystemNode._tree.register |
| 96 | +def _(self: 'FileSystemDirectory'): |
| 97 | + def render_tree(node, prefix="", is_last=True): |
| 98 | + lines = [] |
| 99 | + current_prefix = "└── " if is_last else "├── " |
| 100 | + display_name = node.name + "/" |
| 101 | + lines.append(f"{prefix}{current_prefix}{display_name}") |
| 102 | + if hasattr(node, 'children') and node.children: |
| 103 | + new_prefix = prefix + (" " if is_last else "│ ") |
| 104 | + for i, child in enumerate(node.children): |
| 105 | + is_last_child = i == len(node.children) - 1 |
| 106 | + lines.extend(child._tree()(child, prefix=new_prefix, is_last=is_last_child) if hasattr(child, '_tree') else [child.name]) |
| 107 | + return lines |
| 108 | + return "\n".join(render_tree(self)) |
| 109 | + |
| 110 | +@dataclass |
111 | 111 | class FileSystemSymlink(FileSystemNode): |
| 112 | + target: str = "" |
112 | 113 | # Add symlink-specific fields if needed |
113 | | - pass |
| 114 | + |
| 115 | +@FileSystemNode._tree.register |
| 116 | +def _(self: 'FileSystemSymlink'): |
| 117 | + return f"{self.name} -> {self.target}" if self.target else self.name |
114 | 118 |
|
115 | 119 |
|
116 | 120 | @dataclass |
|
0 commit comments