flutter-dev 0.1.0__py3-none-any.whl
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.
- common_utils.py +261 -0
- core/__init__.py +7 -0
- core/constants.py +57 -0
- core/state.py +25 -0
- create_page.py +288 -0
- fdev.py +258 -0
- flutter_dev-0.1.0.dist-info/METADATA +411 -0
- flutter_dev-0.1.0.dist-info/RECORD +30 -0
- flutter_dev-0.1.0.dist-info/WHEEL +5 -0
- flutter_dev-0.1.0.dist-info/entry_points.txt +5 -0
- flutter_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
- flutter_dev-0.1.0.dist-info/top_level.txt +9 -0
- gemini_api.py +395 -0
- git_diff_output_editor.py +34 -0
- install_legacy.py +467 -0
- managers/__init__.py +69 -0
- managers/ai.py +113 -0
- managers/app.py +541 -0
- managers/brew.py +477 -0
- managers/build.py +436 -0
- managers/datetime.py +49 -0
- managers/device.py +207 -0
- managers/doctor.py +286 -0
- managers/git.py +981 -0
- managers/git_account.py +542 -0
- managers/merge.py +165 -0
- managers/mirror.py +205 -0
- managers/project.py +138 -0
- managers/web_deploy.py +43 -0
- switch_ai.py +181 -0
managers/brew.py
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Brew Module - Interactive Homebrew package manager
|
|
4
|
+
Browse, inspect, and uninstall packages with full cleanup
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import shutil
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from common_utils import RED, GREEN, YELLOW, BLUE, MAGENTA, NC, CHECKMARK, CROSS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _run_brew(args, timeout=15):
|
|
15
|
+
"""Run a brew command and return stdout lines."""
|
|
16
|
+
try:
|
|
17
|
+
result = subprocess.run(
|
|
18
|
+
["brew"] + args,
|
|
19
|
+
capture_output=True,
|
|
20
|
+
text=True,
|
|
21
|
+
timeout=timeout,
|
|
22
|
+
encoding='utf-8',
|
|
23
|
+
errors='replace',
|
|
24
|
+
)
|
|
25
|
+
if result.returncode == 0:
|
|
26
|
+
return result.stdout.strip()
|
|
27
|
+
return ""
|
|
28
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
29
|
+
return ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get_leaves():
|
|
33
|
+
"""Get top-level installed packages (no dependents)."""
|
|
34
|
+
output = _run_brew(["leaves"])
|
|
35
|
+
if not output:
|
|
36
|
+
return []
|
|
37
|
+
return [line.strip() for line in output.splitlines() if line.strip()]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_all_installed():
|
|
41
|
+
"""Get all installed formulae."""
|
|
42
|
+
output = _run_brew(["list", "--formula"])
|
|
43
|
+
if not output:
|
|
44
|
+
return []
|
|
45
|
+
return [line.strip() for line in output.splitlines() if line.strip()]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get_casks():
|
|
49
|
+
"""Get all installed casks."""
|
|
50
|
+
output = _run_brew(["list", "--cask"])
|
|
51
|
+
if not output:
|
|
52
|
+
return []
|
|
53
|
+
return [line.strip() for line in output.splitlines() if line.strip()]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _is_cask(package):
|
|
57
|
+
"""Check if a package is a cask (GUI app) or formula."""
|
|
58
|
+
result = _run_brew(["info", "--cask", package])
|
|
59
|
+
return bool(result and "Not installed" not in result)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get_package_info(package):
|
|
63
|
+
"""Get detailed info about a package."""
|
|
64
|
+
info = {}
|
|
65
|
+
info['is_cask'] = _is_cask(package)
|
|
66
|
+
|
|
67
|
+
# Basic info
|
|
68
|
+
raw = _run_brew(["info", package])
|
|
69
|
+
if raw:
|
|
70
|
+
info['raw_info'] = raw
|
|
71
|
+
|
|
72
|
+
# Versions installed
|
|
73
|
+
versions_raw = _run_brew(["list", "--versions", package])
|
|
74
|
+
if versions_raw:
|
|
75
|
+
parts = versions_raw.split()
|
|
76
|
+
info['versions'] = parts[1:] if len(parts) > 1 else []
|
|
77
|
+
else:
|
|
78
|
+
info['versions'] = []
|
|
79
|
+
|
|
80
|
+
# Dependencies
|
|
81
|
+
deps_raw = _run_brew(["deps", package])
|
|
82
|
+
info['deps'] = [d.strip() for d in deps_raw.splitlines() if d.strip()] if deps_raw else []
|
|
83
|
+
|
|
84
|
+
# Who uses this package (reverse dependencies)
|
|
85
|
+
uses_raw = _run_brew(["uses", "--installed", package])
|
|
86
|
+
info['used_by'] = [u.strip() for u in uses_raw.splitlines() if u.strip()] if uses_raw else []
|
|
87
|
+
|
|
88
|
+
# Cache file
|
|
89
|
+
cache_raw = _run_brew(["--cache", package])
|
|
90
|
+
info['cache_path'] = cache_raw.strip() if cache_raw else None
|
|
91
|
+
|
|
92
|
+
# Install location
|
|
93
|
+
prefix_raw = _run_brew(["--prefix", package])
|
|
94
|
+
info['location'] = prefix_raw.strip() if prefix_raw else None
|
|
95
|
+
|
|
96
|
+
# Cellar location
|
|
97
|
+
cellar_raw = _run_brew(["--cellar", package])
|
|
98
|
+
info['cellar'] = cellar_raw.strip() if cellar_raw else None
|
|
99
|
+
|
|
100
|
+
return info
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _display_package_details(package, info):
|
|
104
|
+
"""Display detailed package information."""
|
|
105
|
+
pkg_type = f"{YELLOW}(cask){NC}" if info.get('is_cask') else f"{BLUE}(formula){NC}"
|
|
106
|
+
print(f"\n{BLUE}{'═' * 54}{NC}")
|
|
107
|
+
print(f"{BLUE} Package: {GREEN}{package}{NC} {pkg_type}")
|
|
108
|
+
print(f"{BLUE}{'═' * 54}{NC}")
|
|
109
|
+
|
|
110
|
+
# Versions
|
|
111
|
+
if info['versions']:
|
|
112
|
+
print(f"\n {YELLOW}Installed Version(s):{NC}")
|
|
113
|
+
for v in info['versions']:
|
|
114
|
+
print(f" {GREEN}{v}{NC}")
|
|
115
|
+
else:
|
|
116
|
+
print(f"\n {YELLOW}Version:{NC} {RED}unknown{NC}")
|
|
117
|
+
|
|
118
|
+
# Location
|
|
119
|
+
if info.get('location'):
|
|
120
|
+
print(f"\n {YELLOW}Install Location:{NC}")
|
|
121
|
+
print(f" {info['location']}")
|
|
122
|
+
|
|
123
|
+
if info.get('cellar'):
|
|
124
|
+
print(f" {YELLOW}Cellar:{NC}")
|
|
125
|
+
print(f" {info['cellar']}")
|
|
126
|
+
|
|
127
|
+
# Dependencies
|
|
128
|
+
if info['deps']:
|
|
129
|
+
print(f"\n {YELLOW}Dependencies ({len(info['deps'])}):{NC}")
|
|
130
|
+
for dep in info['deps']:
|
|
131
|
+
print(f" {BLUE}•{NC} {dep}")
|
|
132
|
+
else:
|
|
133
|
+
print(f"\n {YELLOW}Dependencies:{NC} none")
|
|
134
|
+
|
|
135
|
+
# Used by (reverse deps)
|
|
136
|
+
if info['used_by']:
|
|
137
|
+
print(f"\n {YELLOW}Used By ({len(info['used_by'])}):{NC}")
|
|
138
|
+
for user in info['used_by']:
|
|
139
|
+
print(f" {MAGENTA}•{NC} {user}")
|
|
140
|
+
else:
|
|
141
|
+
print(f"\n {YELLOW}Used By:{NC} {GREEN}nobody (safe to remove){NC}")
|
|
142
|
+
|
|
143
|
+
# Cache
|
|
144
|
+
if info.get('cache_path'):
|
|
145
|
+
import os
|
|
146
|
+
cache_exists = os.path.exists(info['cache_path'])
|
|
147
|
+
if cache_exists:
|
|
148
|
+
size = os.path.getsize(info['cache_path'])
|
|
149
|
+
size_str = _format_size(size)
|
|
150
|
+
print(f"\n {YELLOW}Cache:{NC} {size_str}")
|
|
151
|
+
print(f" {info['cache_path']}")
|
|
152
|
+
else:
|
|
153
|
+
print(f"\n {YELLOW}Cache:{NC} {GREEN}no cached files{NC}")
|
|
154
|
+
else:
|
|
155
|
+
print(f"\n {YELLOW}Cache:{NC} {GREEN}no cached files{NC}")
|
|
156
|
+
|
|
157
|
+
print(f"\n{BLUE}{'─' * 54}{NC}")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _format_size(size_bytes):
|
|
161
|
+
"""Format bytes into human-readable size."""
|
|
162
|
+
if size_bytes < 1024:
|
|
163
|
+
return f"{size_bytes} B"
|
|
164
|
+
elif size_bytes < 1024 * 1024:
|
|
165
|
+
return f"{size_bytes / 1024:.1f} KB"
|
|
166
|
+
elif size_bytes < 1024 * 1024 * 1024:
|
|
167
|
+
return f"{size_bytes / (1024 * 1024):.1f} MB"
|
|
168
|
+
else:
|
|
169
|
+
return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _uninstall_package(package, info):
|
|
173
|
+
"""Fully uninstall a package with dependencies and cache cleanup."""
|
|
174
|
+
# Warning if others depend on this
|
|
175
|
+
if info['used_by']:
|
|
176
|
+
print(f"\n {RED}Warning: ei package gulo depend kore '{package}' er upor:{NC}")
|
|
177
|
+
for user in info['used_by']:
|
|
178
|
+
print(f" {RED}• {user}{NC}")
|
|
179
|
+
print(f"\n {YELLOW}Uninstall korle oi package gulo break hote pare!{NC}")
|
|
180
|
+
|
|
181
|
+
confirm = input(f"\n {MAGENTA}'{package}' fully remove korte chao? (y/N): {NC}").strip().lower()
|
|
182
|
+
if confirm != 'y':
|
|
183
|
+
print(f" {YELLOW}Cancelled.{NC}")
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
print()
|
|
187
|
+
success = True
|
|
188
|
+
is_cask = info.get('is_cask', False)
|
|
189
|
+
|
|
190
|
+
# Step 1: Uninstall the package
|
|
191
|
+
pkg_type = "cask" if is_cask else "formula"
|
|
192
|
+
print(f" Uninstalling {package} ({pkg_type})...", end=' ', flush=True)
|
|
193
|
+
|
|
194
|
+
# Build uninstall commands in order of preference
|
|
195
|
+
if is_cask:
|
|
196
|
+
attempts = [
|
|
197
|
+
["brew", "uninstall", "--cask", "--zap", "--force", package],
|
|
198
|
+
["brew", "uninstall", "--cask", "--force", package],
|
|
199
|
+
["brew", "uninstall", "--cask", package],
|
|
200
|
+
]
|
|
201
|
+
else:
|
|
202
|
+
attempts = [
|
|
203
|
+
["brew", "uninstall", "--zap", package],
|
|
204
|
+
["brew", "uninstall", "--force", package],
|
|
205
|
+
["brew", "uninstall", package],
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
for cmd in attempts:
|
|
209
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
210
|
+
if result.returncode == 0:
|
|
211
|
+
print(f"{CHECKMARK}")
|
|
212
|
+
break
|
|
213
|
+
else:
|
|
214
|
+
print(f"{CROSS}")
|
|
215
|
+
err = result.stderr.strip() or result.stdout.strip()
|
|
216
|
+
print(f" {RED}{err}{NC}")
|
|
217
|
+
success = False
|
|
218
|
+
|
|
219
|
+
if not success:
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
# Step 2: Remove unused dependencies
|
|
223
|
+
print(f" Removing unused dependencies...", end=' ', flush=True)
|
|
224
|
+
result = subprocess.run(
|
|
225
|
+
["brew", "autoremove"],
|
|
226
|
+
capture_output=True, text=True
|
|
227
|
+
)
|
|
228
|
+
if result.returncode == 0:
|
|
229
|
+
removed = [l for l in result.stdout.splitlines() if 'Uninstalling' in l]
|
|
230
|
+
if removed:
|
|
231
|
+
print(f"{CHECKMARK} ({len(removed)} removed)")
|
|
232
|
+
for line in removed:
|
|
233
|
+
pkg = line.replace('Uninstalling', '').strip().split()[0] if line else ""
|
|
234
|
+
print(f" {BLUE}•{NC} {pkg}")
|
|
235
|
+
else:
|
|
236
|
+
print(f"{CHECKMARK} (none to remove)")
|
|
237
|
+
else:
|
|
238
|
+
print(f"{CROSS}")
|
|
239
|
+
|
|
240
|
+
# Step 3: Clean cache
|
|
241
|
+
print(f" Cleaning cache...", end=' ', flush=True)
|
|
242
|
+
result = subprocess.run(
|
|
243
|
+
["brew", "cleanup", "--prune=all"],
|
|
244
|
+
capture_output=True, text=True
|
|
245
|
+
)
|
|
246
|
+
if result.returncode == 0:
|
|
247
|
+
print(f"{CHECKMARK}")
|
|
248
|
+
else:
|
|
249
|
+
print(f"{CROSS}")
|
|
250
|
+
|
|
251
|
+
print(f"\n {GREEN}{CHECKMARK} '{package}' fully removed!{NC}")
|
|
252
|
+
return True
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _clear_package_cache(package, info):
|
|
256
|
+
"""Clear cached files for a specific package."""
|
|
257
|
+
import os
|
|
258
|
+
cache_path = info.get('cache_path')
|
|
259
|
+
|
|
260
|
+
if not cache_path or not os.path.exists(cache_path):
|
|
261
|
+
print(f"\n {GREEN}No cache found for '{package}'{NC}")
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
size = os.path.getsize(cache_path)
|
|
265
|
+
size_str = _format_size(size)
|
|
266
|
+
|
|
267
|
+
confirm = input(f"\n {MAGENTA}Cache ({size_str}) clear korte chao? (y/N): {NC}").strip().lower()
|
|
268
|
+
if confirm != 'y':
|
|
269
|
+
print(f" {YELLOW}Cancelled.{NC}")
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
os.remove(cache_path)
|
|
274
|
+
print(f" {CHECKMARK} Cache cleared! ({size_str} freed)")
|
|
275
|
+
except OSError as e:
|
|
276
|
+
print(f" {CROSS} {RED}Failed: {e}{NC}")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _show_package_menu(package, info):
|
|
280
|
+
"""Show action menu for a selected package."""
|
|
281
|
+
while True:
|
|
282
|
+
print(f"\n {YELLOW}Actions:{NC}")
|
|
283
|
+
print(f" {BLUE}1.{NC} Uninstall (full cleanup)")
|
|
284
|
+
print(f" {BLUE}2.{NC} Clear cache")
|
|
285
|
+
print(f" {BLUE}3.{NC} Show raw brew info")
|
|
286
|
+
print(f" {BLUE}0.{NC} Back to list")
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
choice = input(f"\n {MAGENTA}Select [0-3]: {NC}").strip()
|
|
290
|
+
|
|
291
|
+
if choice == '0' or choice == '':
|
|
292
|
+
return False # back to list
|
|
293
|
+
|
|
294
|
+
elif choice == '1':
|
|
295
|
+
removed = _uninstall_package(package, info)
|
|
296
|
+
if removed:
|
|
297
|
+
return True # refresh list
|
|
298
|
+
|
|
299
|
+
elif choice == '2':
|
|
300
|
+
_clear_package_cache(package, info)
|
|
301
|
+
|
|
302
|
+
elif choice == '3':
|
|
303
|
+
if info.get('raw_info'):
|
|
304
|
+
print(f"\n{info['raw_info']}")
|
|
305
|
+
else:
|
|
306
|
+
print(f"\n {RED}No info available{NC}")
|
|
307
|
+
|
|
308
|
+
else:
|
|
309
|
+
print(f" {RED}Invalid choice{NC}")
|
|
310
|
+
|
|
311
|
+
except KeyboardInterrupt:
|
|
312
|
+
print()
|
|
313
|
+
return False
|
|
314
|
+
except EOFError:
|
|
315
|
+
return False
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def brew_manager():
|
|
319
|
+
"""Interactive Homebrew package manager."""
|
|
320
|
+
# Check if brew is installed
|
|
321
|
+
if not shutil.which("brew"):
|
|
322
|
+
print(f"\n{RED}Homebrew is not installed!{NC}")
|
|
323
|
+
print(f"{YELLOW}Install: /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"{NC}")
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
while True:
|
|
327
|
+
print(f"\n{BLUE}╔══════════════════════════════════════════════════════╗{NC}")
|
|
328
|
+
print(f"{BLUE}║ fdev brew - Homebrew Package Manager ║{NC}")
|
|
329
|
+
print(f"{BLUE}╚══════════════════════════════════════════════════════╝{NC}")
|
|
330
|
+
print(f"\n {YELLOW}View:{NC}")
|
|
331
|
+
print(f" {BLUE}1.{NC} Top-level packages (brew leaves)")
|
|
332
|
+
print(f" {BLUE}2.{NC} All installed formulae")
|
|
333
|
+
print(f" {BLUE}3.{NC} All installed casks (GUI apps)")
|
|
334
|
+
print(f" {BLUE}4.{NC} Global cleanup (autoremove + cache)")
|
|
335
|
+
print(f" {BLUE}0.{NC} Exit")
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
mode = input(f"\n {MAGENTA}Select [0-4]: {NC}").strip()
|
|
339
|
+
except (KeyboardInterrupt, EOFError):
|
|
340
|
+
print()
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
if mode == '0' or mode == '':
|
|
344
|
+
return
|
|
345
|
+
elif mode == '4':
|
|
346
|
+
_global_cleanup()
|
|
347
|
+
continue
|
|
348
|
+
elif mode == '1':
|
|
349
|
+
packages = _get_leaves()
|
|
350
|
+
list_title = "Top-level Packages (brew leaves)"
|
|
351
|
+
elif mode == '2':
|
|
352
|
+
packages = _get_all_installed()
|
|
353
|
+
list_title = "All Installed Formulae"
|
|
354
|
+
elif mode == '3':
|
|
355
|
+
packages = _get_casks()
|
|
356
|
+
list_title = "All Installed Casks"
|
|
357
|
+
else:
|
|
358
|
+
print(f" {RED}Invalid choice{NC}")
|
|
359
|
+
continue
|
|
360
|
+
|
|
361
|
+
if not packages:
|
|
362
|
+
print(f"\n {YELLOW}No packages found.{NC}")
|
|
363
|
+
continue
|
|
364
|
+
|
|
365
|
+
# Package selection loop
|
|
366
|
+
_package_selection_loop(packages, list_title)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _package_selection_loop(packages, title):
|
|
370
|
+
"""Display numbered package list and handle selection."""
|
|
371
|
+
while True:
|
|
372
|
+
print(f"\n {YELLOW}{title} ({len(packages)}):{NC}\n")
|
|
373
|
+
|
|
374
|
+
# Display in columns for better readability
|
|
375
|
+
col_width = max(len(p) for p in packages) + 4
|
|
376
|
+
cols = max(1, 54 // col_width)
|
|
377
|
+
|
|
378
|
+
for i, pkg in enumerate(packages, 1):
|
|
379
|
+
num = f"{i}.".ljust(4)
|
|
380
|
+
entry = f" {BLUE}{num}{NC}{pkg.ljust(col_width)}"
|
|
381
|
+
if cols > 1 and i % cols != 0:
|
|
382
|
+
print(entry, end='')
|
|
383
|
+
else:
|
|
384
|
+
print(entry)
|
|
385
|
+
|
|
386
|
+
# Handle trailing newline for incomplete rows
|
|
387
|
+
if len(packages) % cols != 0 and cols > 1:
|
|
388
|
+
print()
|
|
389
|
+
|
|
390
|
+
print(f"\n {YELLOW}0.{NC} Back")
|
|
391
|
+
|
|
392
|
+
try:
|
|
393
|
+
choice = input(f"\n {MAGENTA}Select package [0-{len(packages)}]: {NC}").strip()
|
|
394
|
+
except (KeyboardInterrupt, EOFError):
|
|
395
|
+
print()
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
if choice == '0' or choice == '':
|
|
399
|
+
return
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
index = int(choice) - 1
|
|
403
|
+
if 0 <= index < len(packages):
|
|
404
|
+
selected = packages[index]
|
|
405
|
+
print(f"\n {GREEN}✓ Selected: {selected}{NC}")
|
|
406
|
+
print(f" Loading info...", end=' ', flush=True)
|
|
407
|
+
|
|
408
|
+
info = _get_package_info(selected)
|
|
409
|
+
print(f"{CHECKMARK}")
|
|
410
|
+
|
|
411
|
+
_display_package_details(selected, info)
|
|
412
|
+
refresh = _show_package_menu(selected, info)
|
|
413
|
+
|
|
414
|
+
if refresh:
|
|
415
|
+
# Package was removed, refresh list
|
|
416
|
+
return
|
|
417
|
+
else:
|
|
418
|
+
print(f" {RED}Invalid choice. Enter 1-{len(packages)}{NC}")
|
|
419
|
+
except ValueError:
|
|
420
|
+
print(f" {RED}Invalid input. Enter a number{NC}")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _global_cleanup():
|
|
424
|
+
"""Run global brew cleanup (autoremove + cache prune)."""
|
|
425
|
+
print(f"\n {YELLOW}Global Cleanup:{NC}\n")
|
|
426
|
+
|
|
427
|
+
confirm = input(f" {MAGENTA}Unused dependencies remove + cache clean korte chao? (y/N): {NC}").strip().lower()
|
|
428
|
+
if confirm != 'y':
|
|
429
|
+
print(f" {YELLOW}Cancelled.{NC}")
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
print()
|
|
433
|
+
|
|
434
|
+
# Autoremove
|
|
435
|
+
print(f" Removing unused dependencies...", end=' ', flush=True)
|
|
436
|
+
result = subprocess.run(
|
|
437
|
+
["brew", "autoremove"],
|
|
438
|
+
capture_output=True, text=True
|
|
439
|
+
)
|
|
440
|
+
if result.returncode == 0:
|
|
441
|
+
removed = [l for l in result.stdout.splitlines() if 'Uninstalling' in l]
|
|
442
|
+
if removed:
|
|
443
|
+
print(f"{CHECKMARK} ({len(removed)} removed)")
|
|
444
|
+
for line in removed:
|
|
445
|
+
parts = line.split()
|
|
446
|
+
pkg = parts[1] if len(parts) > 1 else line
|
|
447
|
+
print(f" {BLUE}•{NC} {pkg}")
|
|
448
|
+
else:
|
|
449
|
+
print(f"{CHECKMARK} (nothing to remove)")
|
|
450
|
+
else:
|
|
451
|
+
print(f"{CROSS}")
|
|
452
|
+
|
|
453
|
+
# Cache cleanup
|
|
454
|
+
print(f" Cleaning cache...", end=' ', flush=True)
|
|
455
|
+
result = subprocess.run(
|
|
456
|
+
["brew", "cleanup", "--prune=all", "-s"],
|
|
457
|
+
capture_output=True, text=True
|
|
458
|
+
)
|
|
459
|
+
if result.returncode == 0:
|
|
460
|
+
print(f"{CHECKMARK}")
|
|
461
|
+
else:
|
|
462
|
+
print(f"{CROSS}")
|
|
463
|
+
|
|
464
|
+
# Show disk usage
|
|
465
|
+
cache_raw = _run_brew(["--cache"])
|
|
466
|
+
if cache_raw:
|
|
467
|
+
import os
|
|
468
|
+
cache_dir = cache_raw.strip()
|
|
469
|
+
if os.path.isdir(cache_dir):
|
|
470
|
+
total = sum(
|
|
471
|
+
os.path.getsize(os.path.join(dp, f))
|
|
472
|
+
for dp, _, filenames in os.walk(cache_dir)
|
|
473
|
+
for f in filenames
|
|
474
|
+
)
|
|
475
|
+
print(f"\n {GREEN}Remaining cache size: {_format_size(total)}{NC}")
|
|
476
|
+
|
|
477
|
+
print(f"\n {GREEN}{CHECKMARK} Cleanup done!{NC}")
|