dsz 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.
- dsz-0.1.0/PKG-INFO +40 -0
- dsz-0.1.0/README.md +31 -0
- dsz-0.1.0/pyproject.toml +15 -0
- dsz-0.1.0/src/dsz/__init__.py +1 -0
- dsz-0.1.0/src/dsz/cli.py +36 -0
- dsz-0.1.0/src/dsz/core.py +111 -0
dsz-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dsz
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Show disk usage of a directory's immediate children.
|
|
5
|
+
Author: Kyle O'Malley
|
|
6
|
+
Author-email: Kyle O'Malley <j.kyle.omalley@gmail.com>
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# dsz
|
|
11
|
+
|
|
12
|
+
Show disk usage of a directory's immediate children, sorted by size.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
uv tool install dsz
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
dsz [PATH] [--min-percent N]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `PATH` — directory to scan (default: current directory)
|
|
27
|
+
- `--min-percent N` — collapse entries below N% of the total into a single `<other>` line (default: 1.0)
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
$ dsz ~
|
|
33
|
+
/home/user 24.2 GB
|
|
34
|
+
18.2 GB 75% Videos ###############
|
|
35
|
+
4.1 GB 17% Documents ###
|
|
36
|
+
1.6 GB 7% Downloads #
|
|
37
|
+
312.4 MB 1% <other 5>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Hidden entries (dot files and directories) and symlinks are excluded from all counts. Drill into a subdirectory by passing it as `PATH`.
|
dsz-0.1.0/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# dsz
|
|
2
|
+
|
|
3
|
+
Show disk usage of a directory's immediate children, sorted by size.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
uv tool install dsz
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
dsz [PATH] [--min-percent N]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- `PATH` — directory to scan (default: current directory)
|
|
18
|
+
- `--min-percent N` — collapse entries below N% of the total into a single `<other>` line (default: 1.0)
|
|
19
|
+
|
|
20
|
+
## Example
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
$ dsz ~
|
|
24
|
+
/home/user 24.2 GB
|
|
25
|
+
18.2 GB 75% Videos ###############
|
|
26
|
+
4.1 GB 17% Documents ###
|
|
27
|
+
1.6 GB 7% Downloads #
|
|
28
|
+
312.4 MB 1% <other 5>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Hidden entries (dot files and directories) and symlinks are excluded from all counts. Drill into a subdirectory by passing it as `PATH`.
|
dsz-0.1.0/pyproject.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dsz"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Show disk usage of a directory's immediate children."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Kyle O'Malley", email = "j.kyle.omalley@gmail.com" }]
|
|
7
|
+
requires-python = ">=3.14"
|
|
8
|
+
dependencies = []
|
|
9
|
+
|
|
10
|
+
[project.scripts]
|
|
11
|
+
dsz = "dsz.cli:main"
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["uv_build>=0.11.16,<0.12.0"]
|
|
15
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Show disk usage of a directory's immediate children."""
|
dsz-0.1.0/src/dsz/cli.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from dsz.core import generate_size_report
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
parser = argparse.ArgumentParser(description="Show disk usage of a directory's immediate children.")
|
|
11
|
+
parser.add_argument(
|
|
12
|
+
"PATH",
|
|
13
|
+
nargs="?",
|
|
14
|
+
default=".",
|
|
15
|
+
type=Path,
|
|
16
|
+
help="directory to scan (default: current directory)",
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--min-percent",
|
|
20
|
+
default=1.0,
|
|
21
|
+
metavar="N",
|
|
22
|
+
type=float,
|
|
23
|
+
help="collapse entries below N%% of total into one '<other>' line (default: 1.0)",
|
|
24
|
+
)
|
|
25
|
+
args = parser.parse_args()
|
|
26
|
+
|
|
27
|
+
directory: Path = args.PATH
|
|
28
|
+
min_percent: float = args.min_percent
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
results = generate_size_report(directory, min_percent)
|
|
32
|
+
except ValueError as e:
|
|
33
|
+
print(f"error: {e}", file=sys.stderr)
|
|
34
|
+
raise SystemExit(1)
|
|
35
|
+
|
|
36
|
+
print(results, end="")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""dsz scan module."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _fmt_size(n: int) -> str:
|
|
8
|
+
if n < 1024:
|
|
9
|
+
return f"{n} B"
|
|
10
|
+
|
|
11
|
+
size = float(n)
|
|
12
|
+
|
|
13
|
+
for unit in ("KB", "MB", "GB"):
|
|
14
|
+
size /= 1024
|
|
15
|
+
if size < 1024 or unit == "GB":
|
|
16
|
+
break
|
|
17
|
+
|
|
18
|
+
return f"{size:.1f} {unit}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _dir_size(path: str) -> int:
|
|
22
|
+
"""Return total byte size of all files under path, skipping symlinks and permission-denied entries."""
|
|
23
|
+
total_size = 0
|
|
24
|
+
stack = [path]
|
|
25
|
+
|
|
26
|
+
while stack:
|
|
27
|
+
current = stack.pop()
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
scandir_ctx = os.scandir(current)
|
|
31
|
+
except PermissionError:
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
with scandir_ctx as dir_iter:
|
|
35
|
+
for entry in dir_iter:
|
|
36
|
+
if entry.is_symlink():
|
|
37
|
+
continue
|
|
38
|
+
try:
|
|
39
|
+
entry_stat = entry.stat()
|
|
40
|
+
except OSError:
|
|
41
|
+
continue
|
|
42
|
+
if entry.is_file(follow_symlinks=False):
|
|
43
|
+
total_size += entry_stat.st_size
|
|
44
|
+
elif entry.is_dir(follow_symlinks=False):
|
|
45
|
+
stack.append(entry.path)
|
|
46
|
+
|
|
47
|
+
return total_size
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def generate_size_report(path: Path, min_percent: float) -> str:
|
|
51
|
+
"""Scan path's immediate children and return a formatted size report string.
|
|
52
|
+
|
|
53
|
+
Raises ValueError if path does not exist or is not a directory.
|
|
54
|
+
Hidden entries (leading dot) and symlinks are excluded from all counts.
|
|
55
|
+
Entries below min_percent of the total are collapsed into a single '<other N>' line.
|
|
56
|
+
"""
|
|
57
|
+
if not path.exists():
|
|
58
|
+
msg = f"path '{path}' does not exist"
|
|
59
|
+
raise ValueError(msg)
|
|
60
|
+
|
|
61
|
+
if not path.is_dir():
|
|
62
|
+
msg = f"path '{path}' is not a directory"
|
|
63
|
+
raise ValueError(msg)
|
|
64
|
+
|
|
65
|
+
total_size: int = 0
|
|
66
|
+
|
|
67
|
+
entries: dict[str, int] = {}
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
with os.scandir(path) as dir_iter:
|
|
71
|
+
for entry in dir_iter:
|
|
72
|
+
if entry.name.startswith("."):
|
|
73
|
+
continue
|
|
74
|
+
if entry.is_symlink():
|
|
75
|
+
continue
|
|
76
|
+
entry_stat = entry.stat()
|
|
77
|
+
if entry.is_file(follow_symlinks=False):
|
|
78
|
+
total_size += entry_stat.st_size
|
|
79
|
+
entries[entry.name] = entry_stat.st_size
|
|
80
|
+
elif entry.is_dir(follow_symlinks=False):
|
|
81
|
+
entry_size = _dir_size(entry.path)
|
|
82
|
+
total_size += entry_size
|
|
83
|
+
entries[entry.name] = entry_size
|
|
84
|
+
except PermissionError:
|
|
85
|
+
pass # unreadable directory: return header with whatever was counted
|
|
86
|
+
|
|
87
|
+
out = f"{path.absolute()} {_fmt_size(total_size)}\n"
|
|
88
|
+
|
|
89
|
+
if not entries or total_size == 0:
|
|
90
|
+
return out
|
|
91
|
+
|
|
92
|
+
sorted_entries = sorted(entries.items(), key=lambda x: -x[-1])
|
|
93
|
+
|
|
94
|
+
above, below = [], []
|
|
95
|
+
|
|
96
|
+
for name, size in sorted_entries:
|
|
97
|
+
percent = (size / total_size) * 100
|
|
98
|
+
(above if percent >= min_percent else below).append((name, size, percent))
|
|
99
|
+
|
|
100
|
+
for entry, size, percent in above:
|
|
101
|
+
bar = "#" * round(size / total_size * 20)
|
|
102
|
+
out += f"{_fmt_size(size):>10} {percent:>3.0f}% {entry:<20} {bar}\n"
|
|
103
|
+
|
|
104
|
+
if below:
|
|
105
|
+
other_size = sum(size for _, size, _ in below)
|
|
106
|
+
other_percent = (other_size / total_size) * 100
|
|
107
|
+
other_count = len(below)
|
|
108
|
+
bar = "#" * round(other_size / total_size * 20)
|
|
109
|
+
out += f"{_fmt_size(other_size):>10} {other_percent:>3.0f}% {f'<other {other_count}>':<20} {bar}\n"
|
|
110
|
+
|
|
111
|
+
return out
|