treex-cli 0.1.0__tar.gz → 0.1.1__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: treex-cli
3
- Version: 0.1.0
4
- Summary: Print a directory tree with file metadata
3
+ Version: 0.1.1
4
+ Summary: List directory contents in a tree format with file metadata.
5
5
  Author: Corey Goldberg
6
6
  Maintainer: Corey Goldberg
7
7
  License-Expression: MIT
@@ -25,7 +25,7 @@ Dynamic: license-file
25
25
 
26
26
  # treex
27
27
 
28
- ## Print a directory tree with file metadata
28
+ ## List directory contents in a tree format with file metadata.
29
29
 
30
30
  - Copyright (c) 2026 [Corey Goldberg][github-profile]
31
31
  - Development: [GitHub][github-repo]
@@ -51,6 +51,37 @@ Requirements:
51
51
 
52
52
  ----
53
53
 
54
+ ## Status
55
+
56
+ <table>
57
+ <tr>
58
+ <td>Latest Version</td>
59
+ <td>
60
+ <a href="https://pypi.org/project/treex-cli">
61
+ <img src="https://img.shields.io/pypi/v/treex-cli.svg">
62
+ </a>
63
+ </td>
64
+ </tr>
65
+ <tr>
66
+ <td>Tests (CI)</td>
67
+ <td>
68
+ <a href="https://github.com/cgoldberg/treex/actions/workflows/test.yml">
69
+ <img src="https://github.com/cgoldberg/treex/actions/workflows/test.yml/badge.svg">
70
+ </a>
71
+ </td>
72
+ </tr>
73
+ <tr>
74
+ <td>Supported Python Versions</td>
75
+ <td>
76
+ <a href="https://pypi.org/project/treex-cli">
77
+ <img src="https://img.shields.io/pypi/pyversions/treex-cli">
78
+ </a>
79
+ </td>
80
+ </tr>
81
+ </table>
82
+
83
+ ----
84
+
54
85
  ## Installation
55
86
 
56
87
  Install `treex` from [PyPI][pypi-home] using either `pip` or `pipx`:
@@ -64,20 +95,27 @@ Install `treex` from [PyPI][pypi-home] using either `pip` or `pipx`:
64
95
 
65
96
  ```
66
97
  $ treex --help
67
- usage: treex [-h] [-a] [-w N] [-m] [directory]
98
+ usage: treex [-h] [-a] [-m] [-s] [-w N] [directory]
68
99
 
69
- print a directory tree with file metadata.
100
+ List directory contents in a tree format with file metadata.
70
101
 
71
102
  positional arguments:
72
103
  directory directory to scan (default: current directory)
73
104
 
74
105
  options:
75
106
  -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)
107
+ -a, --all show all files (including those ignored by Git)
78
108
  -m, --modified show file modification times
109
+ -s, --summary show summary only
110
+ -w, --width N starting column for file metadata (default: 50)
79
111
  ```
80
112
 
113
+ ----
114
+
115
+ ## Screenshot
116
+
117
+ ![Screenshot](https://raw.githubusercontent.com/cgoldberg/treex/refs/heads/main/screenshot.png)
118
+
81
119
  [github-profile]: https://github.com/cgoldberg
82
120
  [github-repo]: https://github.com/cgoldberg/treex
83
121
  [pypi-home]: https://pypi.org/project/treex-cli
@@ -1,6 +1,6 @@
1
1
  # treex
2
2
 
3
- ## Print a directory tree with file metadata
3
+ ## List directory contents in a tree format with file metadata.
4
4
 
5
5
  - Copyright (c) 2026 [Corey Goldberg][github-profile]
6
6
  - Development: [GitHub][github-repo]
@@ -26,6 +26,37 @@ Requirements:
26
26
 
27
27
  ----
28
28
 
29
+ ## Status
30
+
31
+ <table>
32
+ <tr>
33
+ <td>Latest Version</td>
34
+ <td>
35
+ <a href="https://pypi.org/project/treex-cli">
36
+ <img src="https://img.shields.io/pypi/v/treex-cli.svg">
37
+ </a>
38
+ </td>
39
+ </tr>
40
+ <tr>
41
+ <td>Tests (CI)</td>
42
+ <td>
43
+ <a href="https://github.com/cgoldberg/treex/actions/workflows/test.yml">
44
+ <img src="https://github.com/cgoldberg/treex/actions/workflows/test.yml/badge.svg">
45
+ </a>
46
+ </td>
47
+ </tr>
48
+ <tr>
49
+ <td>Supported Python Versions</td>
50
+ <td>
51
+ <a href="https://pypi.org/project/treex-cli">
52
+ <img src="https://img.shields.io/pypi/pyversions/treex-cli">
53
+ </a>
54
+ </td>
55
+ </tr>
56
+ </table>
57
+
58
+ ----
59
+
29
60
  ## Installation
30
61
 
31
62
  Install `treex` from [PyPI][pypi-home] using either `pip` or `pipx`:
@@ -39,20 +70,27 @@ Install `treex` from [PyPI][pypi-home] using either `pip` or `pipx`:
39
70
 
40
71
  ```
41
72
  $ treex --help
42
- usage: treex [-h] [-a] [-w N] [-m] [directory]
73
+ usage: treex [-h] [-a] [-m] [-s] [-w N] [directory]
43
74
 
44
- print a directory tree with file metadata.
75
+ List directory contents in a tree format with file metadata.
45
76
 
46
77
  positional arguments:
47
78
  directory directory to scan (default: current directory)
48
79
 
49
80
  options:
50
81
  -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)
82
+ -a, --all show all files (including those ignored by Git)
53
83
  -m, --modified show file modification times
84
+ -s, --summary show summary only
85
+ -w, --width N starting column for file metadata (default: 50)
54
86
  ```
55
87
 
88
+ ----
89
+
90
+ ## Screenshot
91
+
92
+ ![Screenshot](https://raw.githubusercontent.com/cgoldberg/treex/refs/heads/main/screenshot.png)
93
+
56
94
  [github-profile]: https://github.com/cgoldberg
57
95
  [github-repo]: https://github.com/cgoldberg/treex
58
96
  [pypi-home]: https://pypi.org/project/treex-cli
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "treex-cli"
7
- version = "0.1.0"
8
- description = "Print a directory tree with file metadata"
7
+ version = "0.1.1"
8
+ description="List directory contents in a tree format with file metadata."
9
9
  license = "MIT"
10
10
  license-files = ["LICENSE"]
11
11
  authors = [{name = "Corey Goldberg"}]
@@ -2,7 +2,7 @@
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
4
 
5
- """Print a directory tree with file metadata.
5
+ """List directory contents in a tree-like format with file metadata.
6
6
 
7
7
  The directory tree is rendered using Unicode box-drawing characters. File names are
8
8
  displayed with their human-readable size and, optionally, modification time. Text
@@ -29,13 +29,13 @@ DEFAULT_METADATA_COLUMN = 50
29
29
 
30
30
 
31
31
  class GitIgnore:
32
- """Use Git's own ignore machinery when available."""
32
+ """Use Git's ignore rules when available."""
33
33
 
34
34
  def __init__(self, directory):
35
35
  self.directory = Path(directory).resolve()
36
36
  self.enabled = False
37
37
  self.repo_root = None
38
- # Git isn't installed or can't be executed.
38
+ # Check Git availability and find the repository root
39
39
  with suppress(OSError):
40
40
  result = subprocess.run(
41
41
  ["git", "rev-parse", "--show-toplevel"],
@@ -45,12 +45,13 @@ class GitIgnore:
45
45
  text=True,
46
46
  check=False,
47
47
  )
48
+ # Enable Git support if this is a repository
48
49
  if result.returncode == 0:
49
50
  self.repo_root = Path(result.stdout.strip()).resolve()
50
51
  self.enabled = True
51
52
 
52
53
  def ignored(self, path):
53
- """Return True if Git considers the path ignored."""
54
+ """Check if Git considers the path ignored."""
54
55
  if not self.enabled:
55
56
  return False
56
57
  try:
@@ -80,12 +81,12 @@ def format_size(size): # noqa: RET503
80
81
 
81
82
 
82
83
  def format_modified(timestamp):
83
- """Format a modification timestamp for display."""
84
+ """Format a modification timestamp."""
84
85
  return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M")
85
86
 
86
87
 
87
88
  def is_binary_file(path, chunk_size=8192):
88
- """Return True if the file appears to be binary."""
89
+ """Check if the file appears to be binary."""
89
90
  try:
90
91
  with path.open("rb") as f:
91
92
  chunk = f.read(chunk_size)
@@ -107,18 +108,15 @@ def is_binary_file(path, chunk_size=8192):
107
108
 
108
109
  def count_lines(path):
109
110
  """Count lines without loading the entire file into memory."""
110
- count = 0
111
111
  try:
112
112
  with path.open("rb") as f:
113
- for _ in f:
114
- count += 1
113
+ return sum(1 for _ in f)
115
114
  except OSError:
116
115
  return None
117
- return count
118
116
 
119
117
 
120
118
  def file_info(path):
121
- """Return size, description, raw size, and modification time."""
119
+ """Get file info."""
122
120
  try:
123
121
  stat = path.stat()
124
122
  size = stat.st_size
@@ -141,8 +139,14 @@ def print_tree(
141
139
  gitignore=None,
142
140
  width=DEFAULT_METADATA_COLUMN,
143
141
  show_modified=False,
142
+ show_tree=True,
144
143
  ):
145
144
  """Recursively print the directory tree."""
145
+
146
+ def display(message):
147
+ if show_tree:
148
+ print(message)
149
+
146
150
  if stats is None:
147
151
  stats = {
148
152
  "directories": 0,
@@ -153,24 +157,23 @@ def print_tree(
153
157
  try:
154
158
  entries = list(path.iterdir())
155
159
  except PermissionError:
156
- print(prefix + "└── [permission denied]")
160
+ display(prefix + "└── [permission denied]")
157
161
  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()))
162
+ # Filter ignored entries and Git's internal directory
163
+ visible_entries = [
164
+ entry
165
+ for entry in entries
166
+ if not gitignore or (entry.name != ".git" and not gitignore.ignored(entry))
167
+ ]
168
+ # Sort directories before files, group symlinks
169
+ # by target type, and sort names case-insensitive.
170
+ visible_entries.sort(key=lambda p: (p.is_file(), p.name.casefold()))
168
171
  for index, entry in enumerate(visible_entries):
169
172
  is_last = index == len(visible_entries) - 1
170
173
  connector = "└── " if is_last else "├── "
171
174
  if entry.is_dir():
172
175
  stats["directories"] += 1
173
- print(prefix + connector + entry.name)
176
+ display(f"{prefix}{connector}{entry.name}")
174
177
  extension = " " if is_last else "│ "
175
178
  print_tree(
176
179
  entry,
@@ -179,25 +182,26 @@ def print_tree(
179
182
  gitignore,
180
183
  width,
181
184
  show_modified,
185
+ show_tree,
182
186
  )
183
187
  elif entry.is_file():
184
188
  stats["files"] += 1
185
- size, info, raw_size, modified = file_info(entry)
186
- stats["total_size"] += raw_size
189
+ size_text, info, size, modified = file_info(entry)
190
+ stats["total_size"] += size
187
191
  # The complete tree/name portion is padded to the
188
- # requested width so metadata lines up vertically.
192
+ # specified width so metadata lines up vertically.
189
193
  tree_name = prefix + connector + entry.name
190
- output = f"{tree_name:<{width}}{size:>10} {info}"
194
+ output = f"{tree_name:<{width}}{size_text:>10} {info}"
191
195
  if show_modified and modified is not None:
192
196
  output += f" {modified}"
193
- print(output)
197
+ display(output)
194
198
  return stats
195
199
 
196
200
 
197
201
  def parse_args(argv=None):
198
202
  """Parse command-line arguments."""
199
203
  parser = argparse.ArgumentParser(
200
- description="print a directory tree with file metadata.",
204
+ description="List directory contents in a tree format with file metadata.",
201
205
  )
202
206
  parser.add_argument(
203
207
  "directory",
@@ -210,7 +214,19 @@ def parse_args(argv=None):
210
214
  "-a",
211
215
  "--all",
212
216
  action="store_true",
213
- help="show all files, including git-ignored",
217
+ help="show all files (including those ignored by Git)",
218
+ )
219
+ parser.add_argument(
220
+ "-m",
221
+ "--modified",
222
+ action="store_true",
223
+ help="show file modification times",
224
+ )
225
+ parser.add_argument(
226
+ "-s",
227
+ "--summary",
228
+ action="store_true",
229
+ help="show summary only",
214
230
  )
215
231
  parser.add_argument(
216
232
  "-w",
@@ -220,12 +236,6 @@ def parse_args(argv=None):
220
236
  metavar="N",
221
237
  help=f"starting column for file metadata (default: {DEFAULT_METADATA_COLUMN})",
222
238
  )
223
- parser.add_argument(
224
- "-m",
225
- "--modified",
226
- action="store_true",
227
- help="show file modification times",
228
- )
229
239
  return parser.parse_args(argv)
230
240
 
231
241
 
@@ -238,17 +248,23 @@ def main():
238
248
  if args.width < 1:
239
249
  print("Width must be greater than zero.")
240
250
  return 1
241
- # Only initialize Git integration when it will actually be used.
251
+ # Only initialize Git support if it will be used
242
252
  gitignore = None if args.all else GitIgnore(path)
243
- print(path.resolve())
253
+ show_tree = not args.summary
254
+ if show_tree:
255
+ print(path)
244
256
  stats = print_tree(
245
- path, gitignore=gitignore, width=args.width, show_modified=args.modified
257
+ path,
258
+ gitignore=gitignore,
259
+ width=args.width,
260
+ show_modified=args.modified,
261
+ show_tree=show_tree,
246
262
  )
263
+ if show_tree:
264
+ print()
247
265
  print(
248
- f""
249
- f""
250
- f"{stats['directories']} directories, "
251
- f"{stats['files']} files, "
266
+ f"{stats['directories']} directories • "
267
+ f"{stats['files']} files • "
252
268
  f"{format_size(stats['total_size'])}"
253
269
  )
254
270
  return 0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: treex-cli
3
- Version: 0.1.0
4
- Summary: Print a directory tree with file metadata
3
+ Version: 0.1.1
4
+ Summary: List directory contents in a tree format with file metadata.
5
5
  Author: Corey Goldberg
6
6
  Maintainer: Corey Goldberg
7
7
  License-Expression: MIT
@@ -25,7 +25,7 @@ Dynamic: license-file
25
25
 
26
26
  # treex
27
27
 
28
- ## Print a directory tree with file metadata
28
+ ## List directory contents in a tree format with file metadata.
29
29
 
30
30
  - Copyright (c) 2026 [Corey Goldberg][github-profile]
31
31
  - Development: [GitHub][github-repo]
@@ -51,6 +51,37 @@ Requirements:
51
51
 
52
52
  ----
53
53
 
54
+ ## Status
55
+
56
+ <table>
57
+ <tr>
58
+ <td>Latest Version</td>
59
+ <td>
60
+ <a href="https://pypi.org/project/treex-cli">
61
+ <img src="https://img.shields.io/pypi/v/treex-cli.svg">
62
+ </a>
63
+ </td>
64
+ </tr>
65
+ <tr>
66
+ <td>Tests (CI)</td>
67
+ <td>
68
+ <a href="https://github.com/cgoldberg/treex/actions/workflows/test.yml">
69
+ <img src="https://github.com/cgoldberg/treex/actions/workflows/test.yml/badge.svg">
70
+ </a>
71
+ </td>
72
+ </tr>
73
+ <tr>
74
+ <td>Supported Python Versions</td>
75
+ <td>
76
+ <a href="https://pypi.org/project/treex-cli">
77
+ <img src="https://img.shields.io/pypi/pyversions/treex-cli">
78
+ </a>
79
+ </td>
80
+ </tr>
81
+ </table>
82
+
83
+ ----
84
+
54
85
  ## Installation
55
86
 
56
87
  Install `treex` from [PyPI][pypi-home] using either `pip` or `pipx`:
@@ -64,20 +95,27 @@ Install `treex` from [PyPI][pypi-home] using either `pip` or `pipx`:
64
95
 
65
96
  ```
66
97
  $ treex --help
67
- usage: treex [-h] [-a] [-w N] [-m] [directory]
98
+ usage: treex [-h] [-a] [-m] [-s] [-w N] [directory]
68
99
 
69
- print a directory tree with file metadata.
100
+ List directory contents in a tree format with file metadata.
70
101
 
71
102
  positional arguments:
72
103
  directory directory to scan (default: current directory)
73
104
 
74
105
  options:
75
106
  -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)
107
+ -a, --all show all files (including those ignored by Git)
78
108
  -m, --modified show file modification times
109
+ -s, --summary show summary only
110
+ -w, --width N starting column for file metadata (default: 50)
79
111
  ```
80
112
 
113
+ ----
114
+
115
+ ## Screenshot
116
+
117
+ ![Screenshot](https://raw.githubusercontent.com/cgoldberg/treex/refs/heads/main/screenshot.png)
118
+
81
119
  [github-profile]: https://github.com/cgoldberg
82
120
  [github-repo]: https://github.com/cgoldberg/treex
83
121
  [pypi-home]: https://pypi.org/project/treex-cli
File without changes
File without changes