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.
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}")