dube 2.0.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.
- dube-2.0.0/LICENSE +21 -0
- dube-2.0.0/PKG-INFO +115 -0
- dube-2.0.0/README.md +96 -0
- dube-2.0.0/duBE.py +252 -0
- dube-2.0.0/dube.egg-info/PKG-INFO +115 -0
- dube-2.0.0/dube.egg-info/SOURCES.txt +10 -0
- dube-2.0.0/dube.egg-info/dependency_links.txt +1 -0
- dube-2.0.0/dube.egg-info/entry_points.txt +2 -0
- dube-2.0.0/dube.egg-info/requires.txt +1 -0
- dube-2.0.0/dube.egg-info/top_level.txt +1 -0
- dube-2.0.0/pyproject.toml +30 -0
- dube-2.0.0/setup.cfg +4 -0
dube-2.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Serber1990
|
|
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.
|
dube-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dube
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: duBE — du But Easier: enhanced disk usage analyser with tree view, sort, filters and clean output
|
|
5
|
+
Author-email: Serber1990 <serber1990@pm.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/serber1990/duBE
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/serber1990/duBE/issues
|
|
9
|
+
Project-URL: Source Code, https://github.com/serber1990/duBE
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Topic :: System :: Filesystems
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: shellcolorize
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# duBE — du But Easier
|
|
21
|
+
|
|
22
|
+
[](https://badge.fury.io/py/dube)
|
|
23
|
+
[](https://opensource.org/licenses/MIT)
|
|
24
|
+
[](https://github.com/serber1990/duBE/stargazers)
|
|
25
|
+
|
|
26
|
+
Enhanced disk usage analyser — a cleaner, more powerful `du` replacement with tree view, sorting, filters and readable output.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## ✨ Features
|
|
31
|
+
|
|
32
|
+
- 🌲 **Tree view** (`--tree`) — visual directory hierarchy with cumulative sizes per branch
|
|
33
|
+
- 📊 **Sort by size** — `--sort asc` / `--sort desc`
|
|
34
|
+
- 🕐 **Modification time** — `--time` shows last-modified date alongside each entry
|
|
35
|
+
- 🚫 **Smart exclusions** — skip directories, zero-size entries, or entries below a threshold
|
|
36
|
+
- 📏 **Apparent size** — actual file content vs. disk blocks
|
|
37
|
+
- 🌈 **Clean, color-coded output** — readable at a glance without visual noise
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 📥 Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install dube
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or clone locally:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/serber1990/duBE.git
|
|
51
|
+
cd duBE
|
|
52
|
+
pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🛠 Usage
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
dube [directory] [options]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Option | Description |
|
|
64
|
+
|--------|-------------|
|
|
65
|
+
| `directory` | Directory to analyse (default: `.`) |
|
|
66
|
+
| `--tree` | Show results as a directory tree with cumulative sizes |
|
|
67
|
+
| `--max-depth N` | Limit recursion depth |
|
|
68
|
+
| `-a`, `--all` | Include individual files |
|
|
69
|
+
| `--sort asc\|desc` | Sort by size |
|
|
70
|
+
| `--time` | Show last modification time per entry |
|
|
71
|
+
| `--time-style FMT` | Date format: `iso`, `long-iso`, `full-iso`, or strftime string |
|
|
72
|
+
| `--apparent-size` | Show apparent size instead of disk blocks |
|
|
73
|
+
| `--follow-symlinks` | Follow symbolic links |
|
|
74
|
+
| `--same-filesystem` | Restrict to one filesystem |
|
|
75
|
+
| `--exclude DIR` | Exclude a directory (repeatable) |
|
|
76
|
+
| `-z`, `--exclude-zero` | Hide zero-size entries |
|
|
77
|
+
| `--threshold N` | Show entries ≥ N blocks (negative = ≤ \|N\|) |
|
|
78
|
+
| `-v`, `--version` | Show version |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 🎨 Examples
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Analyse current directory
|
|
86
|
+
dube .
|
|
87
|
+
|
|
88
|
+
# Tree view, 2 levels deep
|
|
89
|
+
dube /home/user --tree --max-depth 2
|
|
90
|
+
|
|
91
|
+
# Largest directories first, show modification dates
|
|
92
|
+
dube /var/log --sort desc --time
|
|
93
|
+
|
|
94
|
+
# Include individual files, skip empty entries
|
|
95
|
+
dube /etc -a -z
|
|
96
|
+
|
|
97
|
+
# Only show entries bigger than 100 blocks
|
|
98
|
+
dube /home --threshold 100
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 📝 License
|
|
104
|
+
|
|
105
|
+
MIT — see [LICENSE](LICENSE).
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 💬 Feedback
|
|
110
|
+
|
|
111
|
+
Open an issue or reach out via GitHub.
|
|
112
|
+
|
|
113
|
+
## 🌐 Connect
|
|
114
|
+
|
|
115
|
+
[](https://github.com/serber1990)
|
dube-2.0.0/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# duBE — du But Easier
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/dube)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/serber1990/duBE/stargazers)
|
|
6
|
+
|
|
7
|
+
Enhanced disk usage analyser — a cleaner, more powerful `du` replacement with tree view, sorting, filters and readable output.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- 🌲 **Tree view** (`--tree`) — visual directory hierarchy with cumulative sizes per branch
|
|
14
|
+
- 📊 **Sort by size** — `--sort asc` / `--sort desc`
|
|
15
|
+
- 🕐 **Modification time** — `--time` shows last-modified date alongside each entry
|
|
16
|
+
- 🚫 **Smart exclusions** — skip directories, zero-size entries, or entries below a threshold
|
|
17
|
+
- 📏 **Apparent size** — actual file content vs. disk blocks
|
|
18
|
+
- 🌈 **Clean, color-coded output** — readable at a glance without visual noise
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📥 Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install dube
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or clone locally:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/serber1990/duBE.git
|
|
32
|
+
cd duBE
|
|
33
|
+
pip install -e .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🛠 Usage
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
dube [directory] [options]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Option | Description |
|
|
45
|
+
|--------|-------------|
|
|
46
|
+
| `directory` | Directory to analyse (default: `.`) |
|
|
47
|
+
| `--tree` | Show results as a directory tree with cumulative sizes |
|
|
48
|
+
| `--max-depth N` | Limit recursion depth |
|
|
49
|
+
| `-a`, `--all` | Include individual files |
|
|
50
|
+
| `--sort asc\|desc` | Sort by size |
|
|
51
|
+
| `--time` | Show last modification time per entry |
|
|
52
|
+
| `--time-style FMT` | Date format: `iso`, `long-iso`, `full-iso`, or strftime string |
|
|
53
|
+
| `--apparent-size` | Show apparent size instead of disk blocks |
|
|
54
|
+
| `--follow-symlinks` | Follow symbolic links |
|
|
55
|
+
| `--same-filesystem` | Restrict to one filesystem |
|
|
56
|
+
| `--exclude DIR` | Exclude a directory (repeatable) |
|
|
57
|
+
| `-z`, `--exclude-zero` | Hide zero-size entries |
|
|
58
|
+
| `--threshold N` | Show entries ≥ N blocks (negative = ≤ \|N\|) |
|
|
59
|
+
| `-v`, `--version` | Show version |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🎨 Examples
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Analyse current directory
|
|
67
|
+
dube .
|
|
68
|
+
|
|
69
|
+
# Tree view, 2 levels deep
|
|
70
|
+
dube /home/user --tree --max-depth 2
|
|
71
|
+
|
|
72
|
+
# Largest directories first, show modification dates
|
|
73
|
+
dube /var/log --sort desc --time
|
|
74
|
+
|
|
75
|
+
# Include individual files, skip empty entries
|
|
76
|
+
dube /etc -a -z
|
|
77
|
+
|
|
78
|
+
# Only show entries bigger than 100 blocks
|
|
79
|
+
dube /home --threshold 100
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 📝 License
|
|
85
|
+
|
|
86
|
+
MIT — see [LICENSE](LICENSE).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 💬 Feedback
|
|
91
|
+
|
|
92
|
+
Open an issue or reach out via GitHub.
|
|
93
|
+
|
|
94
|
+
## 🌐 Connect
|
|
95
|
+
|
|
96
|
+
[](https://github.com/serber1990)
|
dube-2.0.0/duBE.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import signal
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from shellcolorize import Color
|
|
9
|
+
|
|
10
|
+
VERSION = "2.0.0"
|
|
11
|
+
|
|
12
|
+
EXCLUDE_DIRS = {'/proc', '/sys', '/dev', '/run', '/mnt', '/media', '/lost+found'}
|
|
13
|
+
|
|
14
|
+
# ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
_ANSI = re.compile(r'\033\[[0-9;]*m')
|
|
17
|
+
|
|
18
|
+
def _vlen(s: str) -> int:
|
|
19
|
+
return len(_ANSI.sub('', s))
|
|
20
|
+
|
|
21
|
+
def _rpad(s: str, width: int) -> str:
|
|
22
|
+
return ' ' * max(0, width - _vlen(s)) + s
|
|
23
|
+
|
|
24
|
+
# ── Display primitives ────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
def _header(*parts: str) -> None:
|
|
27
|
+
inner = ' ' + ' · '.join(p for p in parts if p)
|
|
28
|
+
w = max(46, len(inner) + 4)
|
|
29
|
+
border = '═' * w
|
|
30
|
+
print()
|
|
31
|
+
print(f" {Color.CYAN}╔{border}╗{Color.RESET}")
|
|
32
|
+
print(f" {Color.CYAN}║{Color.RESET}{Color.BOLD}{inner}{Color.RESET}"
|
|
33
|
+
+ ' ' * max(0, w - len(inner))
|
|
34
|
+
+ f" {Color.CYAN}║{Color.RESET}")
|
|
35
|
+
print(f" {Color.CYAN}╚{border}╝{Color.RESET}")
|
|
36
|
+
print()
|
|
37
|
+
|
|
38
|
+
def _separator(width: int = 52) -> None:
|
|
39
|
+
print(f" {Color.DIM}{'─' * width}{Color.RESET}")
|
|
40
|
+
|
|
41
|
+
# ── Size formatting ───────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
def format_size(size: int, block_size: int = 1024) -> str:
|
|
44
|
+
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
45
|
+
value = size * block_size
|
|
46
|
+
for unit in units[:-1]:
|
|
47
|
+
if value < 1024:
|
|
48
|
+
return f"{value:7.2f} {unit}"
|
|
49
|
+
value /= 1024
|
|
50
|
+
return f"{value:7.2f} {units[-1]}"
|
|
51
|
+
|
|
52
|
+
def _fmt_time(path: str, time_style: str = 'iso') -> str:
|
|
53
|
+
t = datetime.fromtimestamp(os.path.getmtime(path))
|
|
54
|
+
if time_style.startswith('+'):
|
|
55
|
+
return t.strftime(time_style[1:])
|
|
56
|
+
fmts = {'full-iso': '%Y-%m-%d %H:%M:%S', 'long-iso': '%Y-%m-%d %H:%M', 'iso': '%Y-%m-%d'}
|
|
57
|
+
return t.strftime(fmts.get(time_style, time_style))
|
|
58
|
+
|
|
59
|
+
# ── Core scanner ──────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
def scan(start: str, max_depth=None, follow_symlinks=False, same_filesystem=False,
|
|
62
|
+
block_size=1024, threshold=None, show_all=False, apparent_size=False,
|
|
63
|
+
extra_excludes=None) -> tuple:
|
|
64
|
+
"""
|
|
65
|
+
Returns (total_size, sizes_dict) where sizes_dict maps each path
|
|
66
|
+
to the sum of its DIRECT files only (used for flat view and tree rollup).
|
|
67
|
+
"""
|
|
68
|
+
excludes = EXCLUDE_DIRS | set(extra_excludes or [])
|
|
69
|
+
base_dev = os.stat(start).st_dev if same_filesystem else None
|
|
70
|
+
depth0 = start.rstrip(os.sep).count(os.sep)
|
|
71
|
+
sizes = {}
|
|
72
|
+
total = 0
|
|
73
|
+
|
|
74
|
+
for dirpath, dirnames, filenames in os.walk(start, followlinks=follow_symlinks):
|
|
75
|
+
cur_depth = dirpath.count(os.sep) - depth0
|
|
76
|
+
|
|
77
|
+
if max_depth is not None and cur_depth >= max_depth:
|
|
78
|
+
dirnames[:] = []
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
dirnames[:] = [
|
|
82
|
+
d for d in dirnames
|
|
83
|
+
if os.path.join(dirpath, d) not in excludes
|
|
84
|
+
and (base_dev is None or os.stat(os.path.join(dirpath, d)).st_dev == base_dev)
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
dir_size = 0
|
|
88
|
+
for fname in filenames:
|
|
89
|
+
fp = os.path.join(dirpath, fname)
|
|
90
|
+
try:
|
|
91
|
+
if os.path.islink(fp) and not follow_symlinks:
|
|
92
|
+
continue
|
|
93
|
+
fsize = (os.path.getsize(fp) if apparent_size
|
|
94
|
+
else (os.stat(fp).st_blocks * 512)) // block_size
|
|
95
|
+
if threshold is None or (threshold > 0 and fsize >= threshold) or \
|
|
96
|
+
(threshold < 0 and fsize <= abs(threshold)):
|
|
97
|
+
dir_size += fsize
|
|
98
|
+
if show_all:
|
|
99
|
+
sizes[fp] = fsize
|
|
100
|
+
except OSError:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
sizes[dirpath] = dir_size
|
|
104
|
+
total += dir_size
|
|
105
|
+
|
|
106
|
+
return total, sizes
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _cumulative(sizes: dict) -> dict:
|
|
110
|
+
"""Roll up sizes so every directory contains total size of all descendants."""
|
|
111
|
+
cum = dict(sizes)
|
|
112
|
+
for path in sorted(cum.keys(), key=lambda p: -p.count(os.sep)):
|
|
113
|
+
parent = os.path.dirname(path)
|
|
114
|
+
if parent != path and parent in cum:
|
|
115
|
+
cum[parent] += cum[path]
|
|
116
|
+
return cum
|
|
117
|
+
|
|
118
|
+
# ── Output: flat list ─────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
def print_flat(directory: str, total: int, sizes: dict, block_size: int,
|
|
121
|
+
show_time: bool, time_style: str, sort_order, exclude_zero: bool) -> None:
|
|
122
|
+
_header('duBE', os.path.abspath(directory))
|
|
123
|
+
|
|
124
|
+
entries = {p: s for p, s in sizes.items() if not (exclude_zero and s == 0)}
|
|
125
|
+
reverse = sort_order == 'desc'
|
|
126
|
+
sorted_entries = sorted(entries.items(), key=lambda x: x[1], reverse=reverse)
|
|
127
|
+
|
|
128
|
+
# Right-align size column
|
|
129
|
+
size_w = max((_vlen(format_size(s, block_size)) for _, s in sorted_entries), default=9)
|
|
130
|
+
|
|
131
|
+
for path, size in sorted_entries:
|
|
132
|
+
sz = _rpad(f"{Color.YELLOW}{format_size(size, block_size)}{Color.RESET}", size_w + 9)
|
|
133
|
+
line = f" {sz} {path}"
|
|
134
|
+
if show_time:
|
|
135
|
+
try:
|
|
136
|
+
ts = _fmt_time(path, time_style)
|
|
137
|
+
line += f" {Color.DIM}{ts}{Color.RESET}"
|
|
138
|
+
except OSError:
|
|
139
|
+
pass
|
|
140
|
+
print(line)
|
|
141
|
+
|
|
142
|
+
_separator(size_w + 52)
|
|
143
|
+
total_str = f"{Color.YELLOW}{Color.BOLD}{format_size(total, block_size)}{Color.RESET}"
|
|
144
|
+
print(f" {_rpad(total_str, size_w + 9)} {Color.BOLD}Total{Color.RESET}")
|
|
145
|
+
print()
|
|
146
|
+
|
|
147
|
+
# ── Output: tree ──────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
def print_tree(directory: str, sizes: dict, block_size: int) -> None:
|
|
150
|
+
_header('duBE', os.path.abspath(directory), 'tree')
|
|
151
|
+
|
|
152
|
+
cum = _cumulative(sizes)
|
|
153
|
+
dirs_only = {p: s for p, s in cum.items() if os.path.isdir(p)}
|
|
154
|
+
|
|
155
|
+
def _render(path: str, prefix: str = '', is_last: bool = True, root: bool = False) -> None:
|
|
156
|
+
size = dirs_only.get(path, 0)
|
|
157
|
+
name = os.path.basename(path) or path
|
|
158
|
+
sz_str = f"{Color.YELLOW}{format_size(size, block_size)}{Color.RESET}"
|
|
159
|
+
|
|
160
|
+
if root:
|
|
161
|
+
print(f" {Color.CYAN}{Color.BOLD}{path}{Color.RESET} {sz_str}")
|
|
162
|
+
child_prefix = ''
|
|
163
|
+
else:
|
|
164
|
+
conn = f"{Color.DIM}{'└── ' if is_last else '├── '}{Color.RESET}"
|
|
165
|
+
print(f" {Color.DIM}{prefix}{Color.RESET}{conn}{sz_str} {name}")
|
|
166
|
+
child_prefix = prefix + (' ' if is_last else '│ ')
|
|
167
|
+
|
|
168
|
+
children = sorted(
|
|
169
|
+
[p for p in dirs_only if os.path.dirname(p) == path and p != path]
|
|
170
|
+
)
|
|
171
|
+
for i, child in enumerate(children):
|
|
172
|
+
_render(child, child_prefix, i == len(children) - 1, root=False)
|
|
173
|
+
|
|
174
|
+
_render(directory, root=True)
|
|
175
|
+
print()
|
|
176
|
+
|
|
177
|
+
# ── CLI ───────────────────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
def _handle_sigint(signum, frame):
|
|
180
|
+
print(f"\n {Color.RED}✖ Interrupted{Color.RESET}\n")
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def main() -> None:
|
|
185
|
+
signal.signal(signal.SIGINT, _handle_sigint)
|
|
186
|
+
|
|
187
|
+
parser = argparse.ArgumentParser(
|
|
188
|
+
prog='dube',
|
|
189
|
+
description='duBE — du But Easier: enhanced disk usage analyser',
|
|
190
|
+
)
|
|
191
|
+
parser.add_argument('directory', nargs='?', default='.',
|
|
192
|
+
help='Directory to analyse (default: current directory)')
|
|
193
|
+
parser.add_argument('--max-depth', type=int,
|
|
194
|
+
help='Maximum directory depth to recurse')
|
|
195
|
+
parser.add_argument('-a', '--all', action='store_true',
|
|
196
|
+
help='Include individual files in output')
|
|
197
|
+
parser.add_argument('--tree', action='store_true',
|
|
198
|
+
help='Display results as a directory tree')
|
|
199
|
+
parser.add_argument('--block-size', type=int, default=1024,
|
|
200
|
+
help='Block size in bytes (default: 1024)')
|
|
201
|
+
parser.add_argument('--apparent-size', action='store_true',
|
|
202
|
+
help='Show apparent size instead of disk usage')
|
|
203
|
+
parser.add_argument('--follow-symlinks',action='store_true',
|
|
204
|
+
help='Follow symbolic links')
|
|
205
|
+
parser.add_argument('--same-filesystem',action='store_true',
|
|
206
|
+
help='Restrict to a single filesystem')
|
|
207
|
+
parser.add_argument('--exclude', action='append', metavar='DIR',
|
|
208
|
+
help='Exclude a directory (repeatable)')
|
|
209
|
+
parser.add_argument('-z', '--exclude-zero', action='store_true',
|
|
210
|
+
help='Hide entries with zero size')
|
|
211
|
+
parser.add_argument('--threshold', type=int, metavar='N',
|
|
212
|
+
help='Only show entries ≥ N blocks (negative = ≤ |N|)')
|
|
213
|
+
parser.add_argument('--time', action='store_true',
|
|
214
|
+
help='Show last modification time alongside each entry')
|
|
215
|
+
parser.add_argument('--time-style', default='iso', metavar='FMT',
|
|
216
|
+
help='Date format for --time: iso, long-iso, full-iso, or strftime string')
|
|
217
|
+
parser.add_argument('--sort', choices=['asc', 'desc'],
|
|
218
|
+
help='Sort output by size')
|
|
219
|
+
parser.add_argument('-v', '--version', action='version', version=f'dube {VERSION}')
|
|
220
|
+
args = parser.parse_args()
|
|
221
|
+
|
|
222
|
+
directory = args.directory
|
|
223
|
+
if not os.path.isdir(directory):
|
|
224
|
+
print(f"\n {Color.RED}✖ Not a directory: {directory}{Color.RESET}\n")
|
|
225
|
+
sys.exit(1)
|
|
226
|
+
|
|
227
|
+
total, sizes = scan(
|
|
228
|
+
directory,
|
|
229
|
+
max_depth=args.max_depth,
|
|
230
|
+
follow_symlinks=args.follow_symlinks,
|
|
231
|
+
same_filesystem=args.same_filesystem,
|
|
232
|
+
block_size=args.block_size,
|
|
233
|
+
threshold=args.threshold,
|
|
234
|
+
show_all=args.all,
|
|
235
|
+
apparent_size=args.apparent_size,
|
|
236
|
+
extra_excludes=args.exclude,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if args.tree:
|
|
240
|
+
print_tree(directory, sizes, args.block_size)
|
|
241
|
+
else:
|
|
242
|
+
print_flat(
|
|
243
|
+
directory, total, sizes, args.block_size,
|
|
244
|
+
show_time=args.time,
|
|
245
|
+
time_style=args.time_style,
|
|
246
|
+
sort_order=args.sort,
|
|
247
|
+
exclude_zero=args.exclude_zero,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == '__main__':
|
|
252
|
+
main()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dube
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: duBE — du But Easier: enhanced disk usage analyser with tree view, sort, filters and clean output
|
|
5
|
+
Author-email: Serber1990 <serber1990@pm.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/serber1990/duBE
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/serber1990/duBE/issues
|
|
9
|
+
Project-URL: Source Code, https://github.com/serber1990/duBE
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Topic :: System :: Filesystems
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: shellcolorize
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# duBE — du But Easier
|
|
21
|
+
|
|
22
|
+
[](https://badge.fury.io/py/dube)
|
|
23
|
+
[](https://opensource.org/licenses/MIT)
|
|
24
|
+
[](https://github.com/serber1990/duBE/stargazers)
|
|
25
|
+
|
|
26
|
+
Enhanced disk usage analyser — a cleaner, more powerful `du` replacement with tree view, sorting, filters and readable output.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## ✨ Features
|
|
31
|
+
|
|
32
|
+
- 🌲 **Tree view** (`--tree`) — visual directory hierarchy with cumulative sizes per branch
|
|
33
|
+
- 📊 **Sort by size** — `--sort asc` / `--sort desc`
|
|
34
|
+
- 🕐 **Modification time** — `--time` shows last-modified date alongside each entry
|
|
35
|
+
- 🚫 **Smart exclusions** — skip directories, zero-size entries, or entries below a threshold
|
|
36
|
+
- 📏 **Apparent size** — actual file content vs. disk blocks
|
|
37
|
+
- 🌈 **Clean, color-coded output** — readable at a glance without visual noise
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 📥 Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install dube
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or clone locally:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/serber1990/duBE.git
|
|
51
|
+
cd duBE
|
|
52
|
+
pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🛠 Usage
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
dube [directory] [options]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Option | Description |
|
|
64
|
+
|--------|-------------|
|
|
65
|
+
| `directory` | Directory to analyse (default: `.`) |
|
|
66
|
+
| `--tree` | Show results as a directory tree with cumulative sizes |
|
|
67
|
+
| `--max-depth N` | Limit recursion depth |
|
|
68
|
+
| `-a`, `--all` | Include individual files |
|
|
69
|
+
| `--sort asc\|desc` | Sort by size |
|
|
70
|
+
| `--time` | Show last modification time per entry |
|
|
71
|
+
| `--time-style FMT` | Date format: `iso`, `long-iso`, `full-iso`, or strftime string |
|
|
72
|
+
| `--apparent-size` | Show apparent size instead of disk blocks |
|
|
73
|
+
| `--follow-symlinks` | Follow symbolic links |
|
|
74
|
+
| `--same-filesystem` | Restrict to one filesystem |
|
|
75
|
+
| `--exclude DIR` | Exclude a directory (repeatable) |
|
|
76
|
+
| `-z`, `--exclude-zero` | Hide zero-size entries |
|
|
77
|
+
| `--threshold N` | Show entries ≥ N blocks (negative = ≤ \|N\|) |
|
|
78
|
+
| `-v`, `--version` | Show version |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 🎨 Examples
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Analyse current directory
|
|
86
|
+
dube .
|
|
87
|
+
|
|
88
|
+
# Tree view, 2 levels deep
|
|
89
|
+
dube /home/user --tree --max-depth 2
|
|
90
|
+
|
|
91
|
+
# Largest directories first, show modification dates
|
|
92
|
+
dube /var/log --sort desc --time
|
|
93
|
+
|
|
94
|
+
# Include individual files, skip empty entries
|
|
95
|
+
dube /etc -a -z
|
|
96
|
+
|
|
97
|
+
# Only show entries bigger than 100 blocks
|
|
98
|
+
dube /home --threshold 100
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 📝 License
|
|
104
|
+
|
|
105
|
+
MIT — see [LICENSE](LICENSE).
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 💬 Feedback
|
|
110
|
+
|
|
111
|
+
Open an issue or reach out via GitHub.
|
|
112
|
+
|
|
113
|
+
## 🌐 Connect
|
|
114
|
+
|
|
115
|
+
[](https://github.com/serber1990)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
shellcolorize
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
duBE
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dube"
|
|
7
|
+
version = "2.0.0"
|
|
8
|
+
description = "duBE — du But Easier: enhanced disk usage analyser with tree view, sort, filters and clean output"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Serber1990", email = "serber1990@pm.me" }]
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
dependencies = ["shellcolorize"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: POSIX :: Linux",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Topic :: System :: Filesystems",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/serber1990/duBE"
|
|
23
|
+
"Bug Tracker" = "https://github.com/serber1990/duBE/issues"
|
|
24
|
+
"Source Code" = "https://github.com/serber1990/duBE"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
dube = "duBE:main"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools]
|
|
30
|
+
py-modules = ["duBE"]
|
dube-2.0.0/setup.cfg
ADDED