From 9cba2be54e0494bca40fd03970a35b0c4d46bbd8 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Mon, 30 Mar 2026 09:57:31 -0600 Subject: [PATCH] feat: add disk usage summary Uses du to show per-directory disk usage and highlights the top 5 largest directories. Co-Authored-By: Claude Opus 4.6 (1M context) --- luminos_lib/disk.py | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 luminos_lib/disk.py diff --git a/luminos_lib/disk.py b/luminos_lib/disk.py new file mode 100644 index 0000000..da069cc --- /dev/null +++ b/luminos_lib/disk.py @@ -0,0 +1,63 @@ +"""Disk usage summary using du.""" + +import subprocess + + +def get_disk_usage(target, show_hidden=False): + """Get per-directory disk usage via du. + + Returns a list of dicts: {path, size_bytes, size_human}. + """ + cmd = ["du", "-b", "--max-depth=2", target] + + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=30, + ) + except (subprocess.TimeoutExpired, FileNotFoundError): + return [] + + entries = [] + for line in result.stdout.strip().split("\n"): + if not line.strip(): + continue + parts = line.split("\t", 1) + if len(parts) != 2: + continue + try: + size = int(parts[0]) + except ValueError: + continue + path = parts[1] + + # Skip hidden directories if not requested + if not show_hidden: + segments = path.replace(target, "").split("/") + if any(s.startswith(".") and s != "." for s in segments): + continue + + entries.append({ + "path": path, + "size_bytes": size, + "size_human": _human_size(size), + }) + + return entries + + +def _human_size(nbytes): + """Convert bytes to human-readable string.""" + for unit in ("B", "KB", "MB", "GB", "TB"): + if nbytes < 1024: + if unit == "B": + return f"{nbytes} {unit}" + return f"{nbytes:.1f} {unit}" + nbytes /= 1024 + return f"{nbytes:.1f} PB" + + +def top_directories(usage, n=5): + """Return the top n largest directories from usage data.""" + # Exclude the root entry (last line of du is usually the total) + sorted_entries = sorted(usage, key=lambda x: x["size_bytes"], reverse=True) + return sorted_entries[:n]