directory-tree-printer 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,340 @@
1
+ Metadata-Version: 2.4
2
+ Name: directory-tree-printer
3
+ Version: 0.4.1
4
+ Summary: A Python CLI tool for generating clean directory tree structures.
5
+ Author: AmirmasoudCS
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/AmirmasoudCS/Tree-Printer
8
+ Project-URL: Repository, https://github.com/AmirmasoudCS/Tree-Printer
9
+ Project-URL: Issues, https://github.com/AmirmasoudCS/Tree-Printer/issues
10
+ Keywords: tree,cli,directory,filesystem,terminal
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Environment :: Console
16
+ Classifier: Operating System :: OS Independent
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pathspec
21
+ Requires-Dist: rich
22
+ Provides-Extra: dev
23
+ Requires-Dist: build; extra == "dev"
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: pytest-cov; extra == "dev"
26
+ Requires-Dist: twine; extra == "dev"
27
+ Requires-Dist: setuptools-scm[toml]>=8; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+
31
+ # ๐ŸŒฒ Tree Printer
32
+
33
+ [![GitHub Release](https://img.shields.io/github/v/release/AmirmasoudCS/Tree-Printer)](https://github.com/AmirmasoudCS/Tree-Printer/releases)
34
+ [![Tests](https://github.com/AmirmasoudCS/Tree-Printer/actions/workflows/tests.yml/badge.svg)](https://github.com/AmirmasoudCS/Tree-Printer/actions/workflows/tests.yml)
35
+ [![License](https://img.shields.io/github/license/AmirmasoudCS/Tree-Printer)](LICENSE)
36
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
37
+
38
+ A lightweight and customizable command-line utility for generating beautiful directory tree structures directly from your terminal.
39
+
40
+ Tree Printer makes it easy to visualize project layouts, document repositories, create examples for documentation, and inspect filesystem structures with optional icons, metadata, filtering, sorting, and color themes.
41
+
42
+ ---
43
+
44
+ ## โœจ Features
45
+
46
+ - ๐ŸŒณ Generate recursive directory trees
47
+ - ๐Ÿ“ Display directories only
48
+ - ๐Ÿ‘๏ธ Show or hide hidden files
49
+ - ๐Ÿšซ Exclude files, directories, or file extensions
50
+ - ๐Ÿ“ Limit recursion depth
51
+ - ๐Ÿ”ค Sort entries by:
52
+ - Name
53
+ - Size
54
+ - Last modified date
55
+ - ๐Ÿ“Š Display file size
56
+ - ๐Ÿ•’ Display modification timestamps
57
+ - ๐ŸŽจ Multiple color themes
58
+ - ๐Ÿ–ผ๏ธ Optional file and folder icons
59
+ - ๐Ÿ“ Export output to text files
60
+ - ๐Ÿšซ Respect `.gitignore`
61
+ - โšก Fast and lightweight
62
+ - ๐Ÿงช Fully tested with pytest
63
+ - ๐Ÿ”„ Continuous Integration using GitHub Actions
64
+
65
+ ---
66
+
67
+ ## ๐Ÿ“ฆ Installation
68
+
69
+ ### From PyPI (Recommended)
70
+
71
+ ```bash
72
+ pip install directory-tree-printer
73
+ ````
74
+
75
+ ### From source
76
+
77
+ ```bash
78
+ git clone https://github.com/AmirmasoudCS/Tree-Printer.git
79
+
80
+ cd Tree-Printer
81
+
82
+ pip install .
83
+ ```
84
+
85
+ ---
86
+
87
+ ## ๐Ÿš€ Quick Start
88
+
89
+ Print the current directory
90
+
91
+ ```bash
92
+ tp .
93
+ ```
94
+
95
+ Print another directory
96
+
97
+ ```bash
98
+ tp path/to/project
99
+ ```
100
+
101
+ Print only directories
102
+
103
+ ```bash
104
+ tp --dirs-only
105
+ ```
106
+
107
+ Show icons
108
+
109
+ ```bash
110
+ tp --icons
111
+ ```
112
+
113
+ Show file sizes
114
+
115
+ ```bash
116
+ tp --size
117
+ ```
118
+
119
+ Limit depth
120
+
121
+ ```bash
122
+ tp --max-depth 2
123
+ ```
124
+
125
+ Save output
126
+
127
+ ```bash
128
+ tp --output tree.txt
129
+ ```
130
+
131
+ ---
132
+
133
+ ## ๐Ÿ“– Examples
134
+
135
+ ### Basic
136
+
137
+ ```bash
138
+ tp .
139
+ ```
140
+
141
+ ```text
142
+ project
143
+ โ”œโ”€โ”€ src
144
+ โ”‚ โ”œโ”€โ”€ main.py
145
+ โ”‚ โ””โ”€โ”€ utils.py
146
+ โ”œโ”€โ”€ README.md
147
+ โ””โ”€โ”€ pyproject.toml
148
+ ```
149
+
150
+ ---
151
+
152
+ ### Icons
153
+
154
+ ```bash
155
+ tp --icons
156
+ ```
157
+
158
+ ```text
159
+ ๐Ÿ“ project
160
+ โ”œโ”€โ”€ ๐Ÿ“ src
161
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ main.py
162
+ โ”‚ โ””โ”€โ”€ ๐Ÿ utils.py
163
+ โ”œโ”€โ”€ ๐Ÿ“˜ README.md
164
+ โ””โ”€โ”€ โš™๏ธ pyproject.toml
165
+ ```
166
+
167
+ ---
168
+
169
+ ### File Sizes
170
+
171
+ ```bash
172
+ tp --size
173
+ ```
174
+
175
+ ```text
176
+ README.md (5.8 KB)
177
+ main.py (4.2 KB)
178
+ config.py (831 B)
179
+ ```
180
+
181
+ ---
182
+
183
+ ### Modified Dates
184
+
185
+ ```bash
186
+ tp --modified
187
+ ```
188
+
189
+ ```text
190
+ README.md (2026-05-12 18:42)
191
+ main.py (2026-05-11 14:30)
192
+ ```
193
+
194
+ ---
195
+
196
+ ### Themes
197
+
198
+ ```bash
199
+ tp --theme sunset
200
+ ```
201
+
202
+ ![Sunset Theme](images/themeImage1.png)
203
+
204
+ ---
205
+
206
+ ## โš™๏ธ CLI Options
207
+
208
+ | Option | Alias | Description |
209
+ | -------------------- | ----- | ----------------------------------- |
210
+ | `--max-depth` | `-md` | Limit recursion depth |
211
+ | `--show-hidden` | `-sh` | Include hidden files |
212
+ | `--dirs-only` | `-do` | Show only directories |
213
+ | `--exclude-dirs` | `-ed` | Exclude directories |
214
+ | `--exclude-files` | `-ef` | Exclude files |
215
+ | `--exclude-suffixes` | `-es` | Exclude file extensions |
216
+ | `--sort` | `-st` | Sort by name, size or modified |
217
+ | `--size` | `-s` | Display file sizes |
218
+ | `--modified` | `-m` | Display modified dates |
219
+ | `--icons` | `-i` | Display icons |
220
+ | `--theme` | `-th` | Select a color theme |
221
+ | `--no-color` | `-nc` | Disable colored output |
222
+ | `--gitignore` | `-gi` | Ignore files listed in `.gitignore` |
223
+ | `--output` | `-o` | Write output to a file |
224
+ | `--version` | `-v` | Show installed version |
225
+
226
+ ---
227
+
228
+ ## ๐Ÿงช Development
229
+
230
+ Clone the repository
231
+
232
+ ```bash
233
+ git clone https://github.com/AmirmasoudCS/Tree-Printer.git
234
+ ```
235
+
236
+ Install development dependencies
237
+
238
+ ```bash
239
+ pip install -e ".[dev]"
240
+ ```
241
+
242
+ Run the test suite
243
+
244
+ ```bash
245
+ pytest
246
+ ```
247
+
248
+ Run tests with coverage
249
+
250
+ ```bash
251
+ pytest --cov=tree_printer --cov-report=term-missing
252
+ ```
253
+
254
+ Build the package
255
+
256
+ ```bash
257
+ python -m build
258
+ ```
259
+
260
+ Verify the package
261
+
262
+ ```bash
263
+ python -m twine check dist/*
264
+ ```
265
+
266
+ ---
267
+
268
+ ## ๐Ÿ“‚ Project Structure
269
+
270
+ ```text
271
+
272
+ โ”œโ”€โ”€ ๐Ÿ“ images
273
+ โ”‚ โ””โ”€โ”€ ๐Ÿ–ผ๏ธ themeImage1.png
274
+ โ”œโ”€โ”€ ๐Ÿ“ tests
275
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ __init__.py
276
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ test_cli.py
277
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ test_formatter.py
278
+ โ”‚ โ””โ”€โ”€ ๐Ÿ test_printer.py
279
+ โ”œโ”€โ”€ ๐Ÿ“ tree_printer
280
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ __init__.py
281
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ cli.py
282
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ config.py
283
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ file_types.py
284
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ formatter.py
285
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ icons.py
286
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ models.py
287
+ โ”‚ โ”œโ”€โ”€ ๐Ÿ printer.py
288
+ โ”‚ โ””โ”€โ”€ ๐Ÿ themes.py
289
+ โ”œโ”€โ”€ ๐Ÿ“˜ CHANGELOG.md
290
+ โ”œโ”€โ”€ โš–๏ธ LICENSE
291
+ โ”œโ”€โ”€ ๐Ÿ main.py
292
+ โ”œโ”€โ”€ โš™๏ธ pyproject.toml
293
+ โ”œโ”€โ”€ ๐Ÿ“˜ README.md
294
+ โ””โ”€โ”€ ๐Ÿ“ requirements-dev.txt
295
+ ```
296
+
297
+ ---
298
+
299
+ ## ๐Ÿ›ฃ๏ธ Roadmap
300
+
301
+ Future improvements include:
302
+
303
+ * Additional export formats
304
+ * Custom icon packs
305
+ * More color themes
306
+ * Configuration file support
307
+ * Improved Windows terminal support
308
+ * Performance improvements for very large directories
309
+
310
+ Suggestions and pull requests are always welcome.
311
+
312
+ ---
313
+
314
+ ## ๐Ÿค Contributing
315
+
316
+ Contributions, feature requests, and bug reports are welcome.
317
+
318
+ If you'd like to contribute:
319
+
320
+ 1. Fork the repository
321
+ 2. Create a feature branch
322
+ 3. Make your changes
323
+ 4. Run the test suite
324
+ 5. Submit a Pull Request
325
+
326
+ ---
327
+
328
+ ## ๐Ÿ“œ Changelog
329
+
330
+ See [CHANGELOG.md](CHANGELOG.md) for release history.
331
+
332
+ ---
333
+
334
+ ## โš–๏ธ License
335
+
336
+ This project is licensed under the MIT License.
337
+
338
+ See [LICENSE](LICENSE) for details.
339
+
340
+ ---
@@ -0,0 +1,17 @@
1
+ directory_tree_printer-0.4.1.dist-info/licenses/LICENSE,sha256=lk9TUL2BhgEyBqk8cIk99ySGEo_m1qLWqnRXWDulBi4,1067
2
+ tree_printer/__init__.py,sha256=vpM83YWnxWMxcoMv_LYyBKscx2FBtGIsVd6ap8gbDWI,169
3
+ tree_printer/cli.py,sha256=rxd5HCgpIV2JB7vNtsECNEykdxI7p0xtoR_ElR9AD5A,4039
4
+ tree_printer/config.py,sha256=Cs2QXo4fQC2_rbw7_-t5X9Yr_16puxqsfvTzIZfS5uA,227
5
+ tree_printer/file_types.py,sha256=a-nNISqJKX02ZDBCYK-gZIifmnLlNcMUF0UDwglEylc,391
6
+ tree_printer/formatter.py,sha256=tq1_BcL3G97IoGjcjE1w695rHqDh6_XWX9gGmXfhlww,3137
7
+ tree_printer/icons.py,sha256=0ir8PjHpaZowNFzucbnkuJmmGZ7CvD2hKtBhVn54ODA,444
8
+ tree_printer/models.py,sha256=c7Ovq3XEy5O5TFs5H02QcZgpw8UWUeshob5gEgLdhSI,226
9
+ tree_printer/printer.py,sha256=mm4Ebgiin7MOk-rwwSmar_TkTxMQJzvErhBaRw6P2R0,4717
10
+ tree_printer/themes.py,sha256=OmF5bisNZBpCOyxnU433Jq8s0s1RbxWGkUamqBpJYVE,4079
11
+ directory_tree_printer-0.4.1.dist-info/METADATA,sha256=biB9T6UHOkmtuUCK1DA8R-UATHA9P4z4ZZDjBG3qOmQ,6987
12
+ directory_tree_printer-0.4.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
13
+ directory_tree_printer-0.4.1.dist-info/entry_points.txt,sha256=KXraYIy_PcOWs4odhwXv4thpd5WBX6kaRPAeEwN77p0,80
14
+ directory_tree_printer-0.4.1.dist-info/scm_file_list.json,sha256=w4cBoWsO4LNe0V6zbkbUqT0rCusn_gnPWnkM7tL2-TA,643
15
+ directory_tree_printer-0.4.1.dist-info/scm_version.json,sha256=BZRbRlGczaDc5grvnF8kaFHL6d5NDoL9-jgRNyX1Yc0,160
16
+ directory_tree_printer-0.4.1.dist-info/top_level.txt,sha256=wjkoS3ajkA1yI03t4-VJyME4ba-4qkhp-8Rcek86x3w,13
17
+ directory_tree_printer-0.4.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ tp = tree_printer.cli:run
3
+ tree-printer = tree_printer.cli:run
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amirmasoud
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,27 @@
1
+ {
2
+ "files": [
3
+ "README.md",
4
+ "LICENSE",
5
+ "pyproject.toml",
6
+ "CHANGELOG.md",
7
+ "main.py",
8
+ ".gitignore",
9
+ "requirements-dev.txt",
10
+ "images/themeImage1.png",
11
+ "tree_printer/icons.py",
12
+ "tree_printer/formatter.py",
13
+ "tree_printer/__init__.py",
14
+ "tree_printer/config.py",
15
+ "tree_printer/models.py",
16
+ "tree_printer/themes.py",
17
+ "tree_printer/cli.py",
18
+ "tree_printer/printer.py",
19
+ "tree_printer/file_types.py",
20
+ "tests/__init__.py",
21
+ "tests/test_printer.py",
22
+ "tests/test_formatter.py",
23
+ "tests/test_cli.py",
24
+ ".github/workflows/release.yml",
25
+ ".github/workflows/tests.yml"
26
+ ]
27
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.4.1",
3
+ "distance": 0,
4
+ "node": "gcd0f1e060c7446e931909c3cd16f582a994f09f8",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-04"
8
+ }
@@ -0,0 +1 @@
1
+ tree_printer
@@ -0,0 +1,8 @@
1
+ from .printer import TreePrinter
2
+ from .formatter import TreeFormatter
3
+ from .models import TreeNode
4
+ __all__ = [
5
+ "TreePrinter",
6
+ "TreeFormatter",
7
+ "TreeNode",
8
+ ]
tree_printer/cli.py ADDED
@@ -0,0 +1,145 @@
1
+ import argparse
2
+ #from rich_argparse import RichHelpFormatter
3
+ from importlib.metadata import version
4
+ from rich.console import Console
5
+ from .themes import THEMES
6
+ __version__ = version("directory-tree-printer")
7
+ from .printer import TreePrinter
8
+ from .formatter import TreeFormatter
9
+
10
+
11
+ def build_parser() -> argparse.ArgumentParser:
12
+ parser = argparse.ArgumentParser(
13
+ description="Generate and print directory tree structures"
14
+ )
15
+ parser.add_argument(
16
+ "path",
17
+ nargs="?",
18
+ default=".",
19
+ help="Root directory to generate the tree on"
20
+ )
21
+ parser.add_argument(
22
+ "--max-depth",
23
+ "-md",
24
+ type=int,
25
+ default=None,
26
+ help="Maximum directory depth that will be generated"
27
+ )
28
+ parser.add_argument(
29
+ "--output",
30
+ "-o",
31
+ help = "Write tree output to a file instead of printing"
32
+ )
33
+ parser.add_argument(
34
+ "--show-hidden",
35
+ "-sh",
36
+ action="store_true",
37
+ help="Include hidden files and directories"
38
+ )
39
+ parser.add_argument(
40
+ "--dirs-only",
41
+ "-do",
42
+ action="store_true",
43
+ help="Show directories only"
44
+ )
45
+ parser.add_argument(
46
+ "--exclude-dirs",
47
+ "-ed",
48
+ nargs="+",
49
+ default=None,
50
+ help="Exclude directories by name"
51
+ )
52
+ parser.add_argument(
53
+ "--exclude-files",
54
+ "-ef",
55
+ nargs="+",
56
+ default=None,
57
+ help="Exclude files by name"
58
+ )
59
+ parser.add_argument(
60
+ "--exclude-suffixes",
61
+ "-es",
62
+ nargs="+",
63
+ default=None,
64
+ help="Exclude files by suffixes"
65
+ )
66
+ parser.add_argument(
67
+ "--size",
68
+ "-s",
69
+ action="store_true",
70
+ help="Show file size"
71
+ )
72
+ parser.add_argument(
73
+ "--modified",
74
+ "-m",
75
+ action="store_true",
76
+ help="Show last modified time"
77
+ )
78
+ parser.add_argument(
79
+ "--icons",
80
+ "-i",
81
+ action="store_true",
82
+ help="Show icons next to files and directories"
83
+ )
84
+ parser.add_argument(
85
+ "--sort",
86
+ "-st",
87
+ choices=["name", "size", "modified"],
88
+ default = "name",
89
+ help = "Sort files by name, size or modified date"
90
+ )
91
+ parser.add_argument(
92
+ "--version",
93
+ "-v",
94
+ action="version",
95
+ version=f"tree-printer {__version__}"
96
+ )
97
+ parser.add_argument(
98
+ "--theme",
99
+ "-th",
100
+ default="default",
101
+ choices=THEMES.keys(),
102
+ help = "Choose the color theme"
103
+ )
104
+ parser.add_argument(
105
+ "--no-color",
106
+ "-nc",
107
+ action="store_true",
108
+ help="Disable colored output"
109
+ )
110
+ parser.add_argument(
111
+ "--gitignore",
112
+ "-gi",
113
+ action="store_true",
114
+ help="Respect .gitignore file patterns"
115
+ )
116
+ return parser
117
+
118
+
119
+ def run(argv: list[str] | None = None) -> None:
120
+ parser = build_parser()
121
+ args = parser.parse_args(argv)
122
+ printer = TreePrinter(
123
+ root_path=args.path,
124
+ show_hidden=args.show_hidden,
125
+ dirs_only=args.dirs_only,
126
+ exclude_dirs=args.exclude_dirs,
127
+ exclude_files=args.exclude_files,
128
+ exclude_suffixes=args.exclude_suffixes,
129
+ show_size=args.size,
130
+ show_modified=args.modified,
131
+ sort_by=args.sort,
132
+ use_gitignore=args.gitignore
133
+ )
134
+ formatter = TreeFormatter(show_icons=args.icons, theme_name=args.theme)
135
+ tree = printer.build_tree()
136
+ lines = formatter.format(tree, max_depth=args.max_depth)
137
+ if args.output:
138
+ text_lines = [line.plain for line in lines]
139
+ with open(args.output, "w", encoding="utf-8") as file:
140
+ file.write("\n".join(text_lines))
141
+ print(f"Tree written into {args.output}")
142
+ else:
143
+ console = Console(no_color=args.no_color)
144
+ for line in lines:
145
+ console.print(line)
tree_printer/config.py ADDED
@@ -0,0 +1,16 @@
1
+ DEFAULT_EXCLUDE_DIRS ={
2
+ ".git",
3
+ "__pycache__",
4
+ ".venv",
5
+ "venv",
6
+ "dist",
7
+ "build",
8
+ }
9
+ DEFAULT_EXCLUDE_FILES = {
10
+ ".DS_Store",
11
+ "Thumbs.db",
12
+ }
13
+ DEFAULT_EXCLUDE_SUFFIXES = {
14
+ ".pyc",
15
+ ".egg-info"
16
+ }
@@ -0,0 +1,19 @@
1
+ FILE_STYLES = {
2
+ ".py": "python",
3
+ ".md": "markdown",
4
+ ".json": "json",
5
+ ".png": "image",
6
+ ".jpg": "image",
7
+ ".jpeg": "image",
8
+ ".gif": "image",
9
+ ".csv": "data",
10
+ ".pdf": "document",
11
+ ".zip": "archive",
12
+ ".mp3": "audio",
13
+ ".mp4": "video",
14
+ ".html": "web",
15
+ ".css": "web",
16
+ ".js": "javascript",
17
+ ".toml": "config",
18
+ }
19
+ DEFAULT_FILE_STYLE = "file"
@@ -0,0 +1,86 @@
1
+ from datetime import datetime
2
+ from rich.text import Text
3
+ from .file_types import FILE_STYLES, DEFAULT_FILE_STYLE
4
+ from .themes import THEMES
5
+ from tree_printer.models import TreeNode
6
+ from pathlib import Path
7
+ from typing import List
8
+ from .icons import FILE_ICONS, DEFAULT_FILE_ICON
9
+
10
+ class TreeFormatter:
11
+ def __init__(
12
+ self,
13
+ show_icons : bool = False,
14
+ theme_name : str = "default"
15
+ ):
16
+ self.show_icons = show_icons
17
+ self.theme = THEMES.get(theme_name, THEMES["default"])
18
+ def format_metadata(self, node : TreeNode) -> Text:
19
+ style_key = self.get_style_key(node)
20
+ style = self.theme.get(style_key, "white")
21
+ label = f"{self.get_icon(node)}{node.name}"
22
+ text = Text(label, style=style)
23
+ metadata = []
24
+ if node.size is not None:
25
+ metadata.append(self.format_size(node.size))
26
+ if node.modified is not None:
27
+ formatted_date = datetime.fromtimestamp(node.modified).strftime("%Y-%m-%d %H:%M")
28
+ metadata.append(formatted_date)
29
+ if metadata:
30
+ text.append(f" ({' | '.join(metadata)})", style="dim")
31
+ return text
32
+ def format_size(self, size_in_bytes : int) -> str:
33
+ if size_in_bytes < 1024:
34
+ return f"{size_in_bytes} B"
35
+ units = ["KB", "MB", "GB", "TB", "PB"]
36
+ size = float(size_in_bytes)
37
+ for unit in units :
38
+ size/=1024
39
+ if size < 1024:
40
+ return f"{size:.1f} {unit}"
41
+ return f"{size:.1f} EB"
42
+ def format(
43
+ self,
44
+ node : TreeNode,
45
+ prefix : str = "",
46
+ depth : int = 0,
47
+ max_depth : int | None = None
48
+ ) -> list[Text]:
49
+ if max_depth is not None and depth > max_depth:
50
+ return []
51
+ lines = []
52
+ if depth == 0 :
53
+ lines.append(self.format_metadata(node))
54
+ for index, child in enumerate(node.children):
55
+ is_last = index == len(node.children) - 1
56
+ connector = "โ””โ”€โ”€ " if is_last else "โ”œโ”€โ”€ "
57
+ line = Text(prefix + connector)
58
+ line.append(self.format_metadata(child))
59
+ lines.append(line)
60
+ if child.is_dir:
61
+ extension = " " if is_last else "โ”‚ "
62
+ lines.extend(
63
+ self.format(
64
+ child,
65
+ prefix=prefix+extension,
66
+ depth=depth+1,
67
+ max_depth=max_depth
68
+ )
69
+ )
70
+ return lines
71
+ def get_icon(self, node : TreeNode) -> str :
72
+ if not self.show_icons:
73
+ return ""
74
+ if node.is_dir:
75
+ return "๐Ÿ“ "
76
+ name = node.name.lower()
77
+ if name == "license":
78
+ return "โš–๏ธ "
79
+ suffix = Path(node.name).suffix.lower()
80
+ return FILE_ICONS.get(suffix, DEFAULT_FILE_ICON)
81
+ def get_style_key(self, node : TreeNode) -> str:
82
+ if node.is_dir:
83
+ return "folder"
84
+ suffix = Path(node.name).suffix.lower()
85
+ return FILE_STYLES.get(suffix, DEFAULT_FILE_STYLE)
86
+
tree_printer/icons.py ADDED
@@ -0,0 +1,21 @@
1
+ FILE_ICONS = {
2
+ ".py" : "๐Ÿ ",
3
+ ".md" : "๐Ÿ“˜ ",
4
+ ".json": "๐Ÿงฉ ",
5
+ ".ipynb": "๐Ÿ“„ ",
6
+ ".txt" : "๐Ÿ“ ",
7
+ ".png": "๐Ÿ–ผ๏ธ ",
8
+ ".jpg": "๐Ÿ–ผ๏ธ ",
9
+ ".jpeg": "๐Ÿ–ผ๏ธ ",
10
+ ".gif": "๐ŸŽž๏ธ ",
11
+ ".csv": "๐Ÿ“Š ",
12
+ ".pdf": "๐Ÿ“• ",
13
+ ".zip": "๐Ÿ—œ๏ธ ",
14
+ ".mp3": "๐ŸŽต ",
15
+ ".mp4": "๐ŸŽฌ ",
16
+ ".html": "๐ŸŒ ",
17
+ ".css": "๐ŸŽจ ",
18
+ ".js": "โœจ ",
19
+ ".toml" : "โš™๏ธ "
20
+ }
21
+ DEFAULT_FILE_ICON = "๐Ÿ“„ "
tree_printer/models.py ADDED
@@ -0,0 +1,8 @@
1
+ from dataclasses import dataclass, field
2
+ @dataclass
3
+ class TreeNode:
4
+ name : str
5
+ is_dir : bool
6
+ size : int | None = None
7
+ modified : float | None = None
8
+ children : list["TreeNode"] = field(default_factory=list)
@@ -0,0 +1,129 @@
1
+ from pathlib import Path
2
+ import pathspec
3
+ from .config import DEFAULT_EXCLUDE_DIRS
4
+ from .config import DEFAULT_EXCLUDE_FILES
5
+ from .config import DEFAULT_EXCLUDE_SUFFIXES
6
+ from .models import TreeNode
7
+ from .formatter import TreeFormatter
8
+ from rich.console import Console
9
+ class TreePrinter:
10
+ def __init__(
11
+ self,
12
+ root_path : str | Path = ".",
13
+ exclude_dirs : list[str] | None = None,
14
+ exclude_files : list[str] | None = None,
15
+ exclude_suffixes : list[str] | None = None,
16
+ show_hidden : bool = False,
17
+ dirs_only : bool = False,
18
+ show_size : bool = False,
19
+ show_modified : bool = False,
20
+ sort_by : str = "name",
21
+ use_gitignore : bool = False,
22
+ console: Console | None = None,
23
+ ):
24
+ self.root = Path(root_path)
25
+ self.use_gitignore = use_gitignore
26
+ self.gitignore_spec = self.load_gitignore() if use_gitignore else None
27
+ self.show_hidden = show_hidden
28
+ self.dirs_only = dirs_only
29
+ self.show_size = show_size
30
+ self.show_modified = show_modified
31
+ self.sort_by = sort_by
32
+
33
+ self.exclude_dirs = (DEFAULT_EXCLUDE_DIRS if exclude_dirs is None else exclude_dirs)
34
+ self.exclude_files = (DEFAULT_EXCLUDE_FILES if exclude_files is None else exclude_files)
35
+ self.exclude_suffixes = (DEFAULT_EXCLUDE_SUFFIXES if exclude_suffixes is None else exclude_suffixes)
36
+ self.console = console or Console()
37
+ def should_exclude(self, path: Path) -> bool:
38
+ if path.is_dir() and path.name in self.exclude_dirs:
39
+ return True
40
+ if path.is_file() and path.name in self.exclude_files:
41
+ return True
42
+ if path.is_file() and path.suffix in self.exclude_suffixes:
43
+ return True
44
+
45
+ spec = getattr(self, "gitignore_spec", None)
46
+ if spec is not None:
47
+ try:
48
+ rel = path.relative_to(self.root)
49
+ except ValueError:
50
+ return False
51
+ rel_str = rel.as_posix()
52
+ if path.is_dir():
53
+ rel_str += "/"
54
+ if spec.match_file(rel_str):
55
+ return True
56
+ return False
57
+ def load_gitignore(self) -> pathspec.PathSpec | None:
58
+ gitignore_path = self.root/".gitignore"
59
+ if not gitignore_path.exists():
60
+ return None
61
+ try:
62
+ with open(gitignore_path, "r", encoding="utf-8") as f:
63
+ return pathspec.PathSpec.from_lines("gitignore", f.readlines()) # was "gitwildmatch"
64
+ except (OSError, UnicodeDecodeError):
65
+ return None
66
+
67
+ def render(
68
+ self,
69
+ show_icons: bool = False,
70
+ theme_name: str = "default",
71
+ max_depth: int | None = None,
72
+ no_color: bool = False,
73
+ ) -> None:
74
+ root = self.build_tree()
75
+ formatter = TreeFormatter(show_icons=show_icons, theme_name=theme_name)
76
+ lines = formatter.format(root, max_depth=max_depth)
77
+ for line in lines:
78
+ self.console.print(line)
79
+ def get_items(self, path: Path) -> list[Path]:
80
+ return sorted(
81
+ [
82
+ item
83
+ for item in path.iterdir()
84
+ if (
85
+ (self.show_hidden or not item.name.startswith("."))
86
+ and not self.should_exclude(item)
87
+ and (not self.dirs_only or item.is_dir())
88
+ )
89
+ ],
90
+ key=self.sort_key
91
+ )
92
+
93
+ def build_tree(self, path: Path | None = None) -> TreeNode:
94
+ path = path or self.root
95
+ size = None
96
+ modified = None
97
+ if self.show_size or self.show_modified:
98
+ stat = path.stat()
99
+ if self.show_size and path.is_file():
100
+ size = stat.st_size
101
+ if self.show_modified:
102
+ modified = stat.st_mtime
103
+ node = TreeNode(
104
+ name=path.name,
105
+ is_dir=path.is_dir(),
106
+ size=size,
107
+ modified=modified
108
+ )
109
+
110
+ if path.is_dir():
111
+ for item in self.get_items(path):
112
+ child_node = self.build_tree(item)
113
+ node.children.append(child_node)
114
+
115
+ return node
116
+ def sort_key(self, path : Path):
117
+ if self.sort_by == "size":
118
+ try:
119
+ size = path.stat().st_size
120
+ except OSError:
121
+ size = 0
122
+ return (not path.is_dir(), -size, path.name.lower())
123
+ elif self.sort_by == "modified":
124
+ try:
125
+ mtime = path.stat().st_mtime
126
+ except OSError:
127
+ mtime = 0
128
+ return (not path.is_dir(), -mtime, path.name.lower())
129
+ return (not path.is_dir(), path.name.lower())
tree_printer/themes.py ADDED
@@ -0,0 +1,154 @@
1
+ THEMES = {
2
+ "default": {
3
+ "folder": "blue",
4
+ "file": "white",
5
+ "python": "yellow",
6
+ "markdown": "cyan",
7
+ "json": "bright_yellow",
8
+ "image": "magenta",
9
+ "data": "green",
10
+ "document": "red",
11
+ "archive": "bright_black",
12
+ "audio": "bright_green",
13
+ "video": "bright_magenta",
14
+ "web": "bright_blue",
15
+ "javascript": "yellow",
16
+ "config": "bright_cyan",
17
+ },
18
+
19
+ "github": {
20
+ "folder": "bright_blue",
21
+ "file": "white",
22
+ "python": "bright_yellow",
23
+ "markdown": "bright_white",
24
+ "json": "yellow",
25
+ "image": "bright_magenta",
26
+ "data": "bright_green",
27
+ "document": "white",
28
+ "archive": "bright_black",
29
+ "audio": "green",
30
+ "video": "magenta",
31
+ "web": "cyan",
32
+ "javascript": "yellow",
33
+ "config": "bright_cyan",
34
+ },
35
+
36
+ "dracula": {
37
+ "folder": "bright_magenta",
38
+ "file": "white",
39
+ "python": "yellow",
40
+ "markdown": "cyan",
41
+ "json": "bright_yellow",
42
+ "image": "magenta",
43
+ "data": "green",
44
+ "document": "bright_red",
45
+ "archive": "bright_black",
46
+ "audio": "bright_green",
47
+ "video": "bright_magenta",
48
+ "web": "bright_cyan",
49
+ "javascript": "yellow",
50
+ "config": "bright_blue",
51
+ },
52
+
53
+ "monokai": {
54
+ "folder": "bright_blue",
55
+ "file": "white",
56
+ "python": "bright_green",
57
+ "markdown": "bright_cyan",
58
+ "json": "yellow",
59
+ "image": "magenta",
60
+ "data": "green",
61
+ "document": "bright_red",
62
+ "archive": "bright_black",
63
+ "audio": "cyan",
64
+ "video": "bright_magenta",
65
+ "web": "bright_blue",
66
+ "javascript": "bright_yellow",
67
+ "config": "bright_green",
68
+ },
69
+
70
+ "nord": {
71
+ "folder": "bright_cyan",
72
+ "file": "white",
73
+ "python": "yellow",
74
+ "markdown": "cyan",
75
+ "json": "bright_yellow",
76
+ "image": "magenta",
77
+ "data": "green",
78
+ "document": "bright_white",
79
+ "archive": "bright_black",
80
+ "audio": "bright_green",
81
+ "video": "bright_magenta",
82
+ "web": "blue",
83
+ "javascript": "yellow",
84
+ "config": "cyan",
85
+ },
86
+
87
+ "solarized": {
88
+ "folder": "blue",
89
+ "file": "bright_white",
90
+ "python": "yellow",
91
+ "markdown": "cyan",
92
+ "json": "bright_yellow",
93
+ "image": "magenta",
94
+ "data": "green",
95
+ "document": "red",
96
+ "archive": "bright_black",
97
+ "audio": "green",
98
+ "video": "bright_magenta",
99
+ "web": "bright_blue",
100
+ "javascript": "bright_yellow",
101
+ "config": "cyan",
102
+ },
103
+
104
+ "minimal": {
105
+ "folder": "white",
106
+ "file": "white",
107
+ "python": "white",
108
+ "markdown": "white",
109
+ "json": "white",
110
+ "image": "white",
111
+ "data": "white",
112
+ "document": "white",
113
+ "archive": "bright_black",
114
+ "audio": "white",
115
+ "video": "white",
116
+ "web": "white",
117
+ "javascript": "white",
118
+ "config": "white",
119
+ },
120
+
121
+ "neon": {
122
+ "folder": "bright_cyan",
123
+ "file": "bright_white",
124
+ "python": "bright_green",
125
+ "markdown": "bright_magenta",
126
+ "json": "bright_yellow",
127
+ "image": "bright_magenta",
128
+ "data": "bright_green",
129
+ "document": "bright_red",
130
+ "archive": "bright_black",
131
+ "audio": "bright_cyan",
132
+ "video": "bright_magenta",
133
+ "web": "bright_blue",
134
+ "javascript": "yellow",
135
+ "config": "bright_cyan",
136
+ },
137
+
138
+ "sunset": {
139
+ "folder": "bright_red",
140
+ "file": "bright_white",
141
+ "python": "yellow",
142
+ "markdown": "bright_magenta",
143
+ "json": "bright_yellow",
144
+ "image": "magenta",
145
+ "data": "green",
146
+ "document": "red",
147
+ "archive": "bright_black",
148
+ "audio": "bright_green",
149
+ "video": "bright_red",
150
+ "web": "bright_yellow",
151
+ "javascript": "yellow",
152
+ "config": "bright_magenta",
153
+ }
154
+ }