Nymbo commited on
Commit
7017cfc
·
verified ·
1 Parent(s): 579b170

Delete Modules/_tree_utils.py

Browse files
Files changed (1) hide show
  1. Modules/_tree_utils.py +0 -265
Modules/_tree_utils.py DELETED
@@ -1,265 +0,0 @@
1
- """
2
- Shared tree rendering utilities for filesystem-like output.
3
-
4
- Provides functions to build and render tree structures with line connectors
5
- (├──, └──, │) for visual hierarchy display.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import os
11
- from datetime import datetime
12
- from typing import Callable, Optional
13
-
14
-
15
- def build_tree(entries: list[tuple[str, dict]]) -> dict:
16
- """
17
- Build a nested tree structure from flat path entries.
18
-
19
- Args:
20
- entries: List of (path, metadata) tuples where path uses forward slashes.
21
- Paths ending with '/' are treated as directories.
22
-
23
- Returns:
24
- Nested dict with "__files__" key for files at each level.
25
-
26
- Example:
27
- entries = [
28
- ("scripts/utils.py", {"size": 1234}),
29
- ("docs/readme.md", {"size": 500}),
30
- ]
31
- tree = build_tree(entries)
32
- """
33
- root: dict = {"__files__": []}
34
-
35
- for path, metadata in entries:
36
- parts = path.rstrip("/").split("/")
37
- is_dir = path.endswith("/")
38
-
39
- node = root
40
- for i, part in enumerate(parts[:-1]):
41
- if part not in node:
42
- node[part] = {"__files__": []}
43
- node = node[part]
44
-
45
- final = parts[-1]
46
- if is_dir:
47
- # Ensure directory exists
48
- if final not in node:
49
- node[final] = {"__files__": []}
50
- # Store directory metadata if provided
51
- if metadata:
52
- node[final]["__meta__"] = metadata
53
- else:
54
- # Add file
55
- node["__files__"].append((final, metadata))
56
-
57
- return root
58
-
59
-
60
- def render_tree(
61
- node: dict,
62
- prefix: str = "",
63
- format_entry: Optional[Callable[[str, dict, bool], str]] = None,
64
- ) -> list[str]:
65
- """
66
- Render a tree with line connectors.
67
-
68
- Args:
69
- node: Nested dict from build_tree()
70
- prefix: Current line prefix for indentation
71
- format_entry: Optional callback to format each entry.
72
- Signature: (name, metadata, is_dir) -> str
73
- If None, uses default formatting.
74
-
75
- Returns:
76
- List of formatted lines.
77
- """
78
- result = []
79
-
80
- # Default formatter
81
- def default_format(name: str, meta: dict, is_dir: bool) -> str:
82
- if is_dir:
83
- return f"{name}/"
84
- size = meta.get("size")
85
- if size is not None:
86
- return f"{name} ({_fmt_size(size)})"
87
- return name
88
-
89
- fmt = format_entry or default_format
90
-
91
- # Collect entries: subdirs first, then files
92
- entries = []
93
- subdirs = sorted(k for k in node.keys() if k not in ("__files__", "__meta__"))
94
- files_here = sorted(node.get("__files__", []), key=lambda x: x[0])
95
-
96
- for dirname in subdirs:
97
- dir_meta = node[dirname].get("__meta__", {})
98
- entries.append(("dir", dirname, node[dirname], dir_meta))
99
- for fname, fmeta in files_here:
100
- entries.append(("file", fname, None, fmeta))
101
-
102
- for i, entry in enumerate(entries):
103
- is_last = (i == len(entries) - 1)
104
- connector = "└── " if is_last else "├── "
105
- child_prefix = prefix + (" " if is_last else "│ ")
106
-
107
- etype, name, subtree, meta = entry
108
-
109
- if etype == "dir":
110
- result.append(f"{prefix}{connector}{fmt(name, meta, True)}")
111
- result.extend(render_tree(subtree, child_prefix, format_entry))
112
- else:
113
- result.append(f"{prefix}{connector}{fmt(name, meta, False)}")
114
-
115
- return result
116
-
117
-
118
- def _fmt_size(num_bytes: int) -> str:
119
- """Format byte size as human-readable string."""
120
- units = ["B", "KB", "MB", "GB"]
121
- size = float(num_bytes)
122
- for unit in units:
123
- if size < 1024.0:
124
- return f"{size:.1f} {unit}"
125
- size /= 1024.0
126
- return f"{size:.1f} TB"
127
-
128
-
129
- def walk_and_build_tree(
130
- abs_path: str,
131
- *,
132
- show_hidden: bool = False,
133
- recursive: bool = False,
134
- max_entries: int = 100,
135
- ) -> tuple[dict, int, bool]:
136
- """
137
- Walk a directory and build a tree structure.
138
-
139
- Args:
140
- abs_path: Absolute path to directory
141
- show_hidden: Include hidden files/dirs (starting with '.')
142
- recursive: Recurse into subdirectories
143
- max_entries: Maximum entries before truncation
144
-
145
- Returns:
146
- (tree, total_entries, truncated)
147
- """
148
- entries: list[tuple[str, dict]] = []
149
- total = 0
150
- truncated = False
151
-
152
- for root, dirs, files in os.walk(abs_path):
153
- # Filter hidden
154
- if not show_hidden:
155
- dirs[:] = [d for d in dirs if not d.startswith('.')]
156
- files = [f for f in files if not f.startswith('.')]
157
-
158
- dirs.sort()
159
- files.sort()
160
-
161
- # Compute relative path from the listing root
162
- try:
163
- rel_root = os.path.relpath(root, abs_path)
164
- except Exception:
165
- rel_root = ""
166
- prefix = "" if rel_root == "." else rel_root.replace("\\", "/") + "/"
167
-
168
- # Add directories (with trailing slash to indicate dir)
169
- for d in dirs:
170
- p = os.path.join(root, d)
171
- try:
172
- mtime = datetime.fromtimestamp(os.path.getmtime(p)).strftime("%Y-%m-%d %H:%M")
173
- except Exception:
174
- mtime = "?"
175
- entries.append((f"{prefix}{d}/", {"mtime": mtime}))
176
- total += 1
177
- if total >= max_entries:
178
- truncated = True
179
- break
180
-
181
- if truncated:
182
- break
183
-
184
- # Add files
185
- for f in files:
186
- p = os.path.join(root, f)
187
- try:
188
- size = os.path.getsize(p)
189
- mtime = datetime.fromtimestamp(os.path.getmtime(p)).strftime("%Y-%m-%d %H:%M")
190
- except Exception:
191
- size, mtime = 0, "?"
192
- entries.append((f"{prefix}{f}", {"size": size, "mtime": mtime}))
193
- total += 1
194
- if total >= max_entries:
195
- truncated = True
196
- break
197
-
198
- if truncated:
199
- break
200
-
201
- if not recursive:
202
- break
203
-
204
- return build_tree(entries), total, truncated
205
-
206
-
207
- def format_dir_listing(
208
- abs_path: str,
209
- display_path: str,
210
- *,
211
- show_hidden: bool = False,
212
- recursive: bool = False,
213
- max_entries: int = 100,
214
- fmt_size_fn: Optional[Callable[[int], str]] = None,
215
- ) -> str:
216
- """
217
- Format a directory listing as a visual tree.
218
-
219
- Args:
220
- abs_path: Absolute path to directory
221
- display_path: User-friendly path to show in header
222
- show_hidden: Include hidden files/dirs
223
- recursive: Recurse into subdirectories
224
- max_entries: Maximum entries before truncation
225
- fmt_size_fn: Optional custom size formatter (defaults to _fmt_size)
226
-
227
- Returns:
228
- Formatted string with tree output.
229
- """
230
- fmt_size = fmt_size_fn or _fmt_size
231
-
232
- tree, total, truncated = walk_and_build_tree(
233
- abs_path,
234
- show_hidden=show_hidden,
235
- recursive=recursive,
236
- max_entries=max_entries,
237
- )
238
-
239
- # Formatter with size + date
240
- def format_entry(name: str, meta: dict, is_dir: bool) -> str:
241
- mtime = meta.get("mtime", "")
242
- if is_dir:
243
- return f"{name}/ ({mtime})"
244
- size = meta.get("size", 0)
245
- return f"{name} ({fmt_size(size)}, {mtime})"
246
-
247
- tree_lines = render_tree(tree, " ", format_entry)
248
-
249
- header = f"Listing of {display_path}\nRoot: /\nEntries: {total}"
250
- if truncated:
251
- header += f"\n… Truncated at {max_entries} entries."
252
-
253
- lines = [header, "", "└── /"]
254
- lines.extend(tree_lines)
255
-
256
- return "\n".join(lines).strip()
257
-
258
-
259
- __all__ = [
260
- "build_tree",
261
- "render_tree",
262
- "_fmt_size",
263
- "walk_and_build_tree",
264
- "format_dir_listing",
265
- ]