Skip to content

Commit 7c8cec8

Browse files
committed
feat: implement detailed streaming logs and linutil-style UI
1 parent 212c9ee commit 7c8cec8

File tree

7 files changed

+215
-110
lines changed

7 files changed

+215
-110
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,5 @@ cython_debug/
139139
.vscode/
140140
*.swp
141141
*.swo
142+
143+
linutil

src/bleach/core/cleaner.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from abc import ABC, abstractmethod
2+
from collections.abc import Generator
23
from dataclasses import dataclass
34

45

@@ -35,7 +36,8 @@ def scan(self) -> CleanupResult:
3536
"""Scan for cleanable resources without checking."""
3637
pass
3738

39+
3840
@abstractmethod
39-
def clean(self) -> CleanupResult:
40-
"""Perform the cleanup operation."""
41+
def clean(self) -> Generator[str, None, CleanupResult]:
42+
"""Perform the cleanup operation, yielding log messages."""
4143
pass

src/bleach/core/package_managers.py

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import shutil
22
import subprocess
3+
from collections.abc import Generator
34

45
from bleach.core.cleaner import Cleaner, CleanupResult
56

@@ -20,21 +21,30 @@ def scan(self) -> CleanupResult:
2021
# Checking /var/cache/apt/archives size could be a scan method
2122
return CleanupResult(success=True, message="APT detected.")
2223

23-
def clean(self) -> CleanupResult:
24+
def clean(self) -> Generator[str, None, CleanupResult]:
2425
try:
25-
# sudo apt-get clean && sudo apt-get autoremove -y
26+
# apt-get clean && apt-get autoremove -y
2627
steps = [
27-
["sudo", "apt-get", "clean"],
28-
["sudo", "apt-get", "autoremove", "-y"],
28+
(["apt-get", "clean"], "Cleaning APT cache..."),
29+
(["apt-get", "autoremove", "-y"], "Removing unused packages..."),
2930
]
30-
for cmd in steps:
31-
subprocess.run(
32-
cmd, check=True, capture_output=True
31+
32+
for cmd, desc in steps:
33+
yield f"[bold]{desc}[/]"
34+
process = subprocess.Popen(
35+
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
3336
)
37+
if process.stdout:
38+
for line in process.stdout:
39+
yield f" {line.strip()}"
40+
process.wait()
41+
42+
if process.returncode != 0:
43+
raise subprocess.CalledProcessError(process.returncode, cmd)
3444

3545
return CleanupResult(
3646
success=True,
37-
message="APT cache cleaned and unused packages removed."
47+
message="APT cleanup completed."
3848
)
3949
except subprocess.CalledProcessError as e:
4050
return CleanupResult(
@@ -56,13 +66,21 @@ def is_available(self) -> bool:
5666
def scan(self) -> CleanupResult:
5767
return CleanupResult(success=True, message="DNF detected.")
5868

59-
def clean(self) -> CleanupResult:
69+
def clean(self) -> Generator[str, None, CleanupResult]:
6070
try:
61-
subprocess.run(
62-
["sudo", "dnf", "clean", "all"],
63-
check=True,
64-
capture_output=True,
71+
cmd = ["dnf", "clean", "all"]
72+
yield f"[bold]Running {cmd}[/]"
73+
process = subprocess.Popen(
74+
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
6575
)
76+
if process.stdout:
77+
for line in process.stdout:
78+
yield f" {line.strip()}"
79+
process.wait()
80+
81+
if process.returncode != 0:
82+
raise subprocess.CalledProcessError(process.returncode, cmd)
83+
6684
return CleanupResult(success=True, message="DNF cache cleaned.")
6785
except subprocess.CalledProcessError as e:
6886
return CleanupResult(
@@ -84,15 +102,21 @@ def is_available(self) -> bool:
84102
def scan(self) -> CleanupResult:
85103
return CleanupResult(success=True, message="Pacman detected.")
86104

87-
def clean(self) -> CleanupResult:
105+
def clean(self) -> Generator[str, None, CleanupResult]:
88106
try:
89-
# pacman -Sc (Clean cache), usually requires interactive confirmation
90-
# or --noconfirm logic
91-
subprocess.run(
92-
["sudo", "pacman", "-Sc", "--noconfirm"],
93-
check=True,
94-
capture_output=True,
107+
cmd = ["pacman", "-Sc", "--noconfirm"]
108+
yield f"[bold]Running {cmd}[/]"
109+
process = subprocess.Popen(
110+
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
95111
)
112+
if process.stdout:
113+
for line in process.stdout:
114+
yield f" {line.strip()}"
115+
process.wait()
116+
117+
if process.returncode != 0:
118+
raise subprocess.CalledProcessError(process.returncode, cmd)
119+
96120
return CleanupResult(success=True, message="Pacman cache cleaned.")
97121
except subprocess.CalledProcessError as e:
98122
return CleanupResult(

src/bleach/core/system.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import shutil
22
import subprocess
3+
from collections.abc import Generator
34
from pathlib import Path
45

56
from bleach.core.cleaner import Cleaner, CleanupResult
@@ -35,16 +36,24 @@ def scan(self) -> CleanupResult:
3536
message=f"Journal logs detected: {size:.2f} MB"
3637
)
3738

38-
def clean(self) -> CleanupResult:
39+
def clean(self) -> Generator[str, None, CleanupResult]:
3940
try:
41+
yield "Vacuuming journal logs > 100M or older than 2 weeks..."
4042
# Vacuum logs older than 2 weeks or larger than 100M
41-
subprocess.run(
42-
["sudo", "journalctl", "--vacuum-size=100M", "--vacuum-time=2weeks"],
43-
check=True,
44-
capture_output=True
43+
process = subprocess.Popen(
44+
["journalctl", "--vacuum-size=100M", "--vacuum-time=2weeks"],
45+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
4546
)
47+
if process.stdout:
48+
for line in process.stdout:
49+
yield f" {line.strip()}"
50+
process.wait()
51+
52+
if process.returncode != 0:
53+
raise subprocess.CalledProcessError(process.returncode, process.args)
54+
4655
return CleanupResult(
47-
success=True, message="System logs vacuumed successfully."
56+
success=True, message="System logs vacuumed."
4857
)
4958
except subprocess.CalledProcessError as e:
5059
return CleanupResult(
@@ -68,10 +77,9 @@ def scan(self) -> CleanupResult:
6877
# Just a dummy scan for now or implement specific folder sizing
6978
return CleanupResult(success=True, message="Checking generic caches...")
7079

71-
def clean(self) -> CleanupResult:
80+
def clean(self) -> Generator[str, None, CleanupResult]:
7281
cleaned_mb = 0.0
73-
# Example: Clear standardized cache locations if safe
74-
# NOTE: Be very careful here. For now let's just do a safe subset.
82+
yield "Scanning for generic caches..."
7583

7684
# Safe targets: thumbnails
7785
targets = [
@@ -80,13 +88,12 @@ def clean(self) -> CleanupResult:
8088

8189
for target in targets:
8290
if target.exists():
83-
# Simple logic to remove content
84-
# In real imp, calculate size before delete
91+
yield f" Removing {target}..."
8592
try:
8693
shutil.rmtree(target)
8794
cleaned_mb += 1.0 # placeholder
88-
except Exception:
89-
pass
95+
except Exception as e:
96+
yield f" [red]Failed to remove {target}: {e}[/]"
9097

9198
return CleanupResult(
9299
success=True,

src/bleach/core/tools.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import shutil
22
import subprocess
3+
from collections.abc import Generator
34
from pathlib import Path
45

56
from bleach.core.cleaner import Cleaner, CleanupResult
@@ -20,14 +21,21 @@ def is_available(self) -> bool:
2021
def scan(self) -> CleanupResult:
2122
return CleanupResult(success=True, message="Docker detected.")
2223

23-
def clean(self) -> CleanupResult:
24+
def clean(self) -> Generator[str, None, CleanupResult]:
2425
try:
25-
# docker system prune -f
26-
subprocess.run(
27-
["docker", "system", "prune", "-f"],
28-
check=True,
29-
capture_output=True
26+
cmd = ["docker", "system", "prune", "-f"]
27+
yield "Pruning Docker resources..."
28+
process = subprocess.Popen(
29+
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
3030
)
31+
if process.stdout:
32+
for line in process.stdout:
33+
yield f" {line.strip()}"
34+
process.wait()
35+
36+
if process.returncode != 0:
37+
raise subprocess.CalledProcessError(process.returncode, cmd)
38+
3139
return CleanupResult(success=True, message="Docker resources pruned.")
3240
except subprocess.CalledProcessError as e:
3341
return CleanupResult(
@@ -49,13 +57,21 @@ def is_available(self) -> bool:
4957
def scan(self) -> CleanupResult:
5058
return CleanupResult(success=True, message="NPM detected.")
5159

52-
def clean(self) -> CleanupResult:
60+
def clean(self) -> Generator[str, None, CleanupResult]:
5361
try:
54-
subprocess.run(
55-
["npm", "cache", "clean", "--force"],
56-
check=True,
57-
capture_output=True
62+
cmd = ["npm", "cache", "clean", "--force"]
63+
yield "Cleaning NPM cache..."
64+
process = subprocess.Popen(
65+
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
5866
)
67+
if process.stdout:
68+
for line in process.stdout:
69+
yield f" {line.strip()}"
70+
process.wait()
71+
72+
if process.returncode != 0:
73+
raise subprocess.CalledProcessError(process.returncode, cmd)
74+
5975
return CleanupResult(success=True, message="NPM cache cleaned.")
6076
except subprocess.CalledProcessError as e:
6177
return CleanupResult(
@@ -88,9 +104,10 @@ def scan(self) -> CleanupResult:
88104
message=f"Pip cache: {size:.2f} MB"
89105
)
90106

91-
def clean(self) -> CleanupResult:
107+
def clean(self) -> Generator[str, None, CleanupResult]:
92108
path = Path.home() / ".cache/pip"
93109
if path.exists():
110+
yield f"Removing Pip cache at {path}..."
94111
try:
95112
shutil.rmtree(path)
96113
return CleanupResult(success=True, message="Pip cache removed.")

0 commit comments

Comments
 (0)