treex-cli 0.1.0__tar.gz

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Corey Goldberg
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,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: treex-cli
3
+ Version: 0.1.0
4
+ Summary: Print a directory tree with file metadata
5
+ Author: Corey Goldberg
6
+ Maintainer: Corey Goldberg
7
+ License-Expression: MIT
8
+ Project-URL: homepage, https://github.com/cgoldberg/treex
9
+ Project-URL: source, https://github.com/cgoldberg/treex
10
+ Project-URL: download, https://pypi.org/project/treex-cli
11
+ Keywords: tree,directory,files,cli,metadata
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Dynamic: license-file
25
+
26
+ # treex
27
+
28
+ ## Print a directory tree with file metadata
29
+
30
+ - Copyright (c) 2026 [Corey Goldberg][github-profile]
31
+ - Development: [GitHub][github-repo]
32
+ - Releases: [PyPI][pypi-home]
33
+ - License: [MIT][mit-license]
34
+
35
+ ----
36
+
37
+ ## About
38
+
39
+ `treex` is a command-line utility (similar to [tree][tree-home]) that recursively
40
+ scans a directory and prints its contents as a tree using Unicode box-drawing
41
+ characters. File names include their human-readable size and, optionally,
42
+ modification time. Text files also include their line count, while binary and
43
+ unreadable files are marked with their type. When Git is installed, it respects
44
+ `.gitignore` rules.
45
+
46
+ Requirements:
47
+
48
+ - Python 3.10+
49
+ - Git 2.0+ (optional)
50
+ - UTF-8-compatible terminal
51
+
52
+ ----
53
+
54
+ ## Installation
55
+
56
+ Install `treex` from [PyPI][pypi-home] using either `pip` or `pipx`:
57
+
58
+ - `pip install treex-cli`
59
+ - `pipx install treex-cli`
60
+
61
+ ----
62
+
63
+ ## Usage
64
+
65
+ ```
66
+ $ treex --help
67
+ usage: treex [-h] [-a] [-w N] [-m] [directory]
68
+
69
+ print a directory tree with file metadata.
70
+
71
+ positional arguments:
72
+ directory directory to scan (default: current directory)
73
+
74
+ options:
75
+ -h, --help show this help message and exit
76
+ -a, --all show all files, including git-ignored
77
+ -w, --width N starting column for file metadata (default: 50)
78
+ -m, --modified show file modification times
79
+ ```
80
+
81
+ [github-profile]: https://github.com/cgoldberg
82
+ [github-repo]: https://github.com/cgoldberg/treex
83
+ [pypi-home]: https://pypi.org/project/treex-cli
84
+ [mit-license]: https://raw.githubusercontent.com/cgoldberg/treex/refs/heads/main/LICENSE
85
+ [tree-home]: https://oldmanprogrammer.net/source.php?dir=projects/tree
@@ -0,0 +1,60 @@
1
+ # treex
2
+
3
+ ## Print a directory tree with file metadata
4
+
5
+ - Copyright (c) 2026 [Corey Goldberg][github-profile]
6
+ - Development: [GitHub][github-repo]
7
+ - Releases: [PyPI][pypi-home]
8
+ - License: [MIT][mit-license]
9
+
10
+ ----
11
+
12
+ ## About
13
+
14
+ `treex` is a command-line utility (similar to [tree][tree-home]) that recursively
15
+ scans a directory and prints its contents as a tree using Unicode box-drawing
16
+ characters. File names include their human-readable size and, optionally,
17
+ modification time. Text files also include their line count, while binary and
18
+ unreadable files are marked with their type. When Git is installed, it respects
19
+ `.gitignore` rules.
20
+
21
+ Requirements:
22
+
23
+ - Python 3.10+
24
+ - Git 2.0+ (optional)
25
+ - UTF-8-compatible terminal
26
+
27
+ ----
28
+
29
+ ## Installation
30
+
31
+ Install `treex` from [PyPI][pypi-home] using either `pip` or `pipx`:
32
+
33
+ - `pip install treex-cli`
34
+ - `pipx install treex-cli`
35
+
36
+ ----
37
+
38
+ ## Usage
39
+
40
+ ```
41
+ $ treex --help
42
+ usage: treex [-h] [-a] [-w N] [-m] [directory]
43
+
44
+ print a directory tree with file metadata.
45
+
46
+ positional arguments:
47
+ directory directory to scan (default: current directory)
48
+
49
+ options:
50
+ -h, --help show this help message and exit
51
+ -a, --all show all files, including git-ignored
52
+ -w, --width N starting column for file metadata (default: 50)
53
+ -m, --modified show file modification times
54
+ ```
55
+
56
+ [github-profile]: https://github.com/cgoldberg
57
+ [github-repo]: https://github.com/cgoldberg/treex
58
+ [pypi-home]: https://pypi.org/project/treex-cli
59
+ [mit-license]: https://raw.githubusercontent.com/cgoldberg/treex/refs/heads/main/LICENSE
60
+ [tree-home]: https://oldmanprogrammer.net/source.php?dir=projects/tree
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "treex-cli"
7
+ version = "0.1.0"
8
+ description = "Print a directory tree with file metadata"
9
+ license = "MIT"
10
+ license-files = ["LICENSE"]
11
+ authors = [{name = "Corey Goldberg"}]
12
+ maintainers = [{name = "Corey Goldberg"}]
13
+ readme = "README.md"
14
+ requires-python = ">= 3.10"
15
+ keywords = ["tree", "directory", "files", "cli", "metadata"]
16
+ classifiers = [
17
+ "Environment :: Console",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ ]
27
+
28
+ [project.scripts]
29
+ treex = "treex:main"
30
+
31
+ [project.urls]
32
+ homepage = "https://github.com/cgoldberg/treex"
33
+ source = "https://github.com/cgoldberg/treex"
34
+ download = "https://pypi.org/project/treex-cli"
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "tox",
39
+ ]
40
+ lint = [
41
+ "ruff",
42
+ ]
43
+ test = [
44
+ "pytest",
45
+ ]
46
+ validate = [
47
+ "validate-pyproject[all]",
48
+ ]
49
+
50
+ [tool.setuptools]
51
+ py-modules = ["treex"]
52
+
53
+ [tool.pytest]
54
+ addopts = ["-v"]
55
+ filterwarnings = ["default"]
56
+
57
+ [tool.ruff]
58
+ line-length = 88
59
+ respect-gitignore = true
60
+ target-version = "py310"
61
+
62
+ [tool.ruff.lint]
63
+ select = ["ALL"]
64
+ ignore = [
65
+ "ANN", "COM812", "D", "DTZ001", "DTZ006",
66
+ "FBT", "PLR", "S603", "S607", "T201"
67
+ ]
68
+ fixable = ["ALL"]
69
+
70
+ [tool.ruff.lint.per-file-ignores]
71
+ "**/test_*.py" = ["S101", "S108"]
72
+
73
+ [tool.ruff.format]
74
+ docstring-code-format = true
75
+ docstring-code-line-length = 88
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,258 @@
1
+ # Copyright (c) 2026 Corey Goldberg
2
+ # SPDX-License-Identifier: MIT
3
+
4
+
5
+ """Print a directory tree with file metadata.
6
+
7
+ The directory tree is rendered using Unicode box-drawing characters. File names are
8
+ displayed with their human-readable size and, optionally, modification time. Text
9
+ files also include their line count. Binary and unreadable files are marked with
10
+ their type.
11
+
12
+ When run inside a Git repository, files and directories ignored by Git
13
+ are automatically excluded using Git's own ignore rules. If Git is not
14
+ installed, or the directory is not part of a Git repository, the
15
+ filesystem is scanned normally without Git filtering.
16
+
17
+ Use --all to disable Git ignore filtering and show all files.
18
+ Use --width to control the column at which file metadata starts.
19
+ Use --modified to show file modification times.
20
+ """
21
+
22
+ import argparse
23
+ import subprocess
24
+ from contextlib import suppress
25
+ from datetime import datetime
26
+ from pathlib import Path
27
+
28
+ DEFAULT_METADATA_COLUMN = 50
29
+
30
+
31
+ class GitIgnore:
32
+ """Use Git's own ignore machinery when available."""
33
+
34
+ def __init__(self, directory):
35
+ self.directory = Path(directory).resolve()
36
+ self.enabled = False
37
+ self.repo_root = None
38
+ # Git isn't installed or can't be executed.
39
+ with suppress(OSError):
40
+ result = subprocess.run(
41
+ ["git", "rev-parse", "--show-toplevel"],
42
+ cwd=self.directory,
43
+ stdout=subprocess.PIPE,
44
+ stderr=subprocess.DEVNULL,
45
+ text=True,
46
+ check=False,
47
+ )
48
+ if result.returncode == 0:
49
+ self.repo_root = Path(result.stdout.strip()).resolve()
50
+ self.enabled = True
51
+
52
+ def ignored(self, path):
53
+ """Return True if Git considers the path ignored."""
54
+ if not self.enabled:
55
+ return False
56
+ try:
57
+ relative_path = path.resolve().relative_to(self.repo_root)
58
+ result = subprocess.run(
59
+ ["git", "check-ignore", "--quiet", "--", str(relative_path)],
60
+ cwd=self.repo_root,
61
+ stdout=subprocess.DEVNULL,
62
+ stderr=subprocess.DEVNULL,
63
+ check=False,
64
+ )
65
+ except (ValueError, OSError):
66
+ return False
67
+ else:
68
+ return result.returncode == 0
69
+
70
+
71
+ def format_size(size): # noqa: RET503
72
+ """Format bytes as a human-readable size."""
73
+ units = ["B", "KB", "MB", "GB", "TB"]
74
+ for unit in units:
75
+ if size < 1024 or unit == units[-1]:
76
+ if unit == "B":
77
+ return f"{size} B"
78
+ return f"{size:.1f} {unit}"
79
+ size /= 1024
80
+
81
+
82
+ def format_modified(timestamp):
83
+ """Format a modification timestamp for display."""
84
+ return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M")
85
+
86
+
87
+ def is_binary_file(path, chunk_size=8192):
88
+ """Return True if the file appears to be binary."""
89
+ try:
90
+ with path.open("rb") as f:
91
+ chunk = f.read(chunk_size)
92
+ if not chunk:
93
+ return False
94
+ # Check for NUL bytes.
95
+ if b"\x00" in chunk:
96
+ return True
97
+ # Treat files that aren't valid UTF-8 as binary.
98
+ try:
99
+ chunk.decode("utf-8")
100
+ except UnicodeDecodeError:
101
+ return True
102
+ else:
103
+ return False
104
+ except OSError:
105
+ return True
106
+
107
+
108
+ def count_lines(path):
109
+ """Count lines without loading the entire file into memory."""
110
+ count = 0
111
+ try:
112
+ with path.open("rb") as f:
113
+ for _ in f:
114
+ count += 1
115
+ except OSError:
116
+ return None
117
+ return count
118
+
119
+
120
+ def file_info(path):
121
+ """Return size, description, raw size, and modification time."""
122
+ try:
123
+ stat = path.stat()
124
+ size = stat.st_size
125
+ modified = format_modified(stat.st_mtime)
126
+ except OSError:
127
+ return "?", "[unreadable]", 0, None
128
+ size_text = format_size(size)
129
+ if is_binary_file(path):
130
+ return size_text, "[binary]", size, modified
131
+ lines = count_lines(path)
132
+ if lines is None:
133
+ return size_text, "[unreadable]", size, modified
134
+ return size_text, f"{lines:,} lines", size, modified
135
+
136
+
137
+ def print_tree(
138
+ directory,
139
+ prefix="",
140
+ stats=None,
141
+ gitignore=None,
142
+ width=DEFAULT_METADATA_COLUMN,
143
+ show_modified=False,
144
+ ):
145
+ """Recursively print the directory tree."""
146
+ if stats is None:
147
+ stats = {
148
+ "directories": 0,
149
+ "files": 0,
150
+ "total_size": 0,
151
+ }
152
+ path = Path(directory)
153
+ try:
154
+ entries = list(path.iterdir())
155
+ except PermissionError:
156
+ print(prefix + "└── [permission denied]")
157
+ return stats
158
+ # Filter ignored entries before determining which entry is last.
159
+ visible_entries = []
160
+ for entry in entries:
161
+ # Git's internal repository data is never useful in the tree output.
162
+ if entry.name == ".git":
163
+ continue
164
+ if gitignore and gitignore.ignored(entry):
165
+ continue
166
+ visible_entries.append(entry)
167
+ visible_entries.sort(key=lambda p: (p.is_file(), p.name.lower()))
168
+ for index, entry in enumerate(visible_entries):
169
+ is_last = index == len(visible_entries) - 1
170
+ connector = "└── " if is_last else "├── "
171
+ if entry.is_dir():
172
+ stats["directories"] += 1
173
+ print(prefix + connector + entry.name)
174
+ extension = " " if is_last else "│ "
175
+ print_tree(
176
+ entry,
177
+ prefix + extension,
178
+ stats,
179
+ gitignore,
180
+ width,
181
+ show_modified,
182
+ )
183
+ elif entry.is_file():
184
+ stats["files"] += 1
185
+ size, info, raw_size, modified = file_info(entry)
186
+ stats["total_size"] += raw_size
187
+ # The complete tree/name portion is padded to the
188
+ # requested width so metadata lines up vertically.
189
+ tree_name = prefix + connector + entry.name
190
+ output = f"{tree_name:<{width}}{size:>10} {info}"
191
+ if show_modified and modified is not None:
192
+ output += f" {modified}"
193
+ print(output)
194
+ return stats
195
+
196
+
197
+ def parse_args(argv=None):
198
+ """Parse command-line arguments."""
199
+ parser = argparse.ArgumentParser(
200
+ description="print a directory tree with file metadata.",
201
+ )
202
+ parser.add_argument(
203
+ "directory",
204
+ nargs="?",
205
+ default=Path(),
206
+ type=Path,
207
+ help="directory to scan (default: current directory)",
208
+ )
209
+ parser.add_argument(
210
+ "-a",
211
+ "--all",
212
+ action="store_true",
213
+ help="show all files, including git-ignored",
214
+ )
215
+ parser.add_argument(
216
+ "-w",
217
+ "--width",
218
+ type=int,
219
+ default=DEFAULT_METADATA_COLUMN,
220
+ metavar="N",
221
+ help=f"starting column for file metadata (default: {DEFAULT_METADATA_COLUMN})",
222
+ )
223
+ parser.add_argument(
224
+ "-m",
225
+ "--modified",
226
+ action="store_true",
227
+ help="show file modification times",
228
+ )
229
+ return parser.parse_args(argv)
230
+
231
+
232
+ def main():
233
+ args = parse_args()
234
+ path = args.directory
235
+ if not path.is_dir():
236
+ print(f"Not a directory: {path}")
237
+ return 1
238
+ if args.width < 1:
239
+ print("Width must be greater than zero.")
240
+ return 1
241
+ # Only initialize Git integration when it will actually be used.
242
+ gitignore = None if args.all else GitIgnore(path)
243
+ print(path.resolve())
244
+ stats = print_tree(
245
+ path, gitignore=gitignore, width=args.width, show_modified=args.modified
246
+ )
247
+ print(
248
+ f""
249
+ f""
250
+ f"{stats['directories']} directories, "
251
+ f"{stats['files']} files, "
252
+ f"{format_size(stats['total_size'])}"
253
+ )
254
+ return 0
255
+
256
+
257
+ if __name__ == "__main__":
258
+ raise SystemExit(main())
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: treex-cli
3
+ Version: 0.1.0
4
+ Summary: Print a directory tree with file metadata
5
+ Author: Corey Goldberg
6
+ Maintainer: Corey Goldberg
7
+ License-Expression: MIT
8
+ Project-URL: homepage, https://github.com/cgoldberg/treex
9
+ Project-URL: source, https://github.com/cgoldberg/treex
10
+ Project-URL: download, https://pypi.org/project/treex-cli
11
+ Keywords: tree,directory,files,cli,metadata
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Dynamic: license-file
25
+
26
+ # treex
27
+
28
+ ## Print a directory tree with file metadata
29
+
30
+ - Copyright (c) 2026 [Corey Goldberg][github-profile]
31
+ - Development: [GitHub][github-repo]
32
+ - Releases: [PyPI][pypi-home]
33
+ - License: [MIT][mit-license]
34
+
35
+ ----
36
+
37
+ ## About
38
+
39
+ `treex` is a command-line utility (similar to [tree][tree-home]) that recursively
40
+ scans a directory and prints its contents as a tree using Unicode box-drawing
41
+ characters. File names include their human-readable size and, optionally,
42
+ modification time. Text files also include their line count, while binary and
43
+ unreadable files are marked with their type. When Git is installed, it respects
44
+ `.gitignore` rules.
45
+
46
+ Requirements:
47
+
48
+ - Python 3.10+
49
+ - Git 2.0+ (optional)
50
+ - UTF-8-compatible terminal
51
+
52
+ ----
53
+
54
+ ## Installation
55
+
56
+ Install `treex` from [PyPI][pypi-home] using either `pip` or `pipx`:
57
+
58
+ - `pip install treex-cli`
59
+ - `pipx install treex-cli`
60
+
61
+ ----
62
+
63
+ ## Usage
64
+
65
+ ```
66
+ $ treex --help
67
+ usage: treex [-h] [-a] [-w N] [-m] [directory]
68
+
69
+ print a directory tree with file metadata.
70
+
71
+ positional arguments:
72
+ directory directory to scan (default: current directory)
73
+
74
+ options:
75
+ -h, --help show this help message and exit
76
+ -a, --all show all files, including git-ignored
77
+ -w, --width N starting column for file metadata (default: 50)
78
+ -m, --modified show file modification times
79
+ ```
80
+
81
+ [github-profile]: https://github.com/cgoldberg
82
+ [github-repo]: https://github.com/cgoldberg/treex
83
+ [pypi-home]: https://pypi.org/project/treex-cli
84
+ [mit-license]: https://raw.githubusercontent.com/cgoldberg/treex/refs/heads/main/LICENSE
85
+ [tree-home]: https://oldmanprogrammer.net/source.php?dir=projects/tree
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ treex.py
5
+ treex_cli.egg-info/PKG-INFO
6
+ treex_cli.egg-info/SOURCES.txt
7
+ treex_cli.egg-info/dependency_links.txt
8
+ treex_cli.egg-info/entry_points.txt
9
+ treex_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ treex = treex:main
@@ -0,0 +1 @@
1
+ treex