From 377194086b48891c77541688e388e155c4be9c1d Mon Sep 17 00:00:00 2001 From: Tw93 Date: Sun, 28 Sep 2025 17:01:25 +0800 Subject: [PATCH] :art: Make the code more maintainable --- .gitignore | 3 +- README.md | 51 ++++----- bin/clean.sh | 18 ++-- lib/common.sh | 292 +++++++++++++++++++++++++++++++++++++++++++++++--- lib/menu.sh | 6 +- mole | 32 +++--- 6 files changed, 333 insertions(+), 69 deletions(-) mode change 100644 => 100755 lib/common.sh diff --git a/.gitignore b/.gitignore index 6aafc4a..0c59c53 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ temp/ *.lock # Claude Code -.claude/ \ No newline at end of file +.claude/ +CLAUDE.md diff --git a/README.md b/README.md index 39d96b6..9d12abb 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,24 @@
- Mole Logo + Mole Logo

Mole

-

🧹 Like a mole, dig deep to clean your mac.

+

🦑 Dig deep like a mole to clean your Mac.

+

A Bash toolkit that tunnels through caches, leftovers, and forgotten libraries so your macOS stays fast without risking the essentials.

-## Features +## Highlights -- 🐦 Deep Clean: System/user caches, logs, temp and more -- πŸ›‘οΈ Safe by default: Skips critical system and input method settings -- πŸ‘€ App Uninstall: Remove app bundle and related data comprehensively -- πŸ‘» Smooth TUI: Fast arrow-key menus with pagination for large lists +- 🦑 Deep-clean hidden caches, logs, and temp files in one sweep +- πŸ›‘ Guardrails built in: skip vital macOS and input method data +- πŸ“¦ Smart uninstall removes apps together with every leftover directory +- ⚑️ Fast arrow-key TUI with pagination for big app lists -## Installation +## Install & Update ```bash curl -fsSL https://raw.githubusercontent.com/tw93/mole/main/install.sh | bash ``` -## Usage +## Daily Commands ```bash mole # Interactive main menu @@ -26,33 +27,23 @@ mole uninstall # Interactive app uninstaller mole --help # Show help ``` -### Example Output +### Quick Peek ```bash -πŸ•³οΈ Mole - Deeper system cleanup -======================== -🍎 Detected: Apple Silicon | πŸ’Ύ Free space: 45.2GB -πŸš€ Mode: User-level cleanup (no password required) +$ mole clean +🦑 MOLE β€” Dig deep like a mole to clean your Mac. -β–Ά System essentials - βœ“ User app cache - βœ“ User app logs - βœ“ Trash +Collecting inventory ... -β–Ά Browser cleanup - βœ“ Safari cache - βœ“ Chrome cache +β–Ά System essentials freed 3.1GB (caches, logs, trash) +β–Ά Browser cleanup freed 820MB (Safari, Chrome, Arc) +β–Ά Developer tools freed 4.6GB (npm, Docker, Homebrew) -β–Ά Developer tools - βœ“ npm cache - βœ“ Docker resources - βœ“ Homebrew cache - -πŸŽ‰ Cleanup complete | πŸ’Ύ Freed space: 8.45GB -πŸ“Š Items processed: 342 | πŸ’Ύ Free space now: 53.7GB +πŸŽ‰ Done! 8.5GB reclaimed across 342 items. +πŸ’‘ Tip: run `mole --help` to discover more commands. ``` -## What Gets Cleaned +## What Mole Cleans | Category | Items Cleaned | Safety | |---|---|---| @@ -63,7 +54,7 @@ mole --help # Show help | πŸ“± Apps | Common app caches (e.g., Slack, Discord, Teams, Notion, 1Password) | Safe | | 🍎 Apple Silicon | Rosetta 2, media services, user activity caches | Safe | -## Uninstaller +## Smart Uninstall - Fast scan of `/Applications` with system-app filtering (e.g., `com.apple.*`) - Ranks apps by last used time and shows size hints diff --git a/bin/clean.sh b/bin/clean.sh index ea3de07..91f3105 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -298,12 +298,7 @@ start_cleanup() { echo "πŸ•³οΈ Mole - Deeper system cleanup" echo "==================================================" echo "" - echo "This will clean:" - echo " β€’ App caches and logs" - echo " β€’ Browser data" - echo " β€’ Developer tool caches" - echo " β€’ Temporary files" - echo " β€’ And much more..." + echo "This will clean: App caches & logs, Browser data, Developer tools, Temporary files & more..." echo "" # Check if we're in an interactive terminal @@ -697,7 +692,7 @@ perform_cleanup() { end_section # ===== 5. Orphaned leftovers ===== - log_header "Checking for orphaned app files" + start_section "Orphaned app files" # Build a list of installed application bundle identifiers echo -e " ${BLUE}πŸ”${NC} Building app list..." @@ -801,6 +796,7 @@ perform_cleanup() { if [ "$found_orphaned" = false ]; then echo -e " ${GREEN}βœ“${NC} No orphaned files found" fi + end_section # Common temp and test data safe_clean ~/Library/Application\ Support/TestApp* "Test app data" @@ -823,12 +819,13 @@ perform_cleanup() { # System cleanup was moved to the beginning (right after password verification) # ===== 7. iOS device backups ===== - log_header "Checking iOS device backups..." + start_section "iOS device backups" backup_dir="$HOME/Library/Application Support/MobileSync/Backup" if [[ -d "$backup_dir" ]] && find "$backup_dir" -mindepth 1 -maxdepth 1 | read -r _; then backup_kb=$(du -sk "$backup_dir" 2>/dev/null | awk '{print $1}') if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then # >100MB backup_human=$(du -shm "$backup_dir" 2>/dev/null | awk '{print $1"M"}') + note_activity echo -e " πŸ‘‰ Found ${GREEN}${backup_human}${NC}, you can delete it manually" echo -e " πŸ‘‰ ${backup_dir}" else @@ -837,9 +834,11 @@ perform_cleanup() { else echo -e " ${BLUE}✨${NC} Nothing to tidy" fi + end_section # ===== 8. Summary ===== - log_header "Cleanup summary" + start_section "Cleanup summary" + note_activity space_after=$(df / | tail -1 | awk '{print $4}') current_space_after=$(get_free_space) @@ -864,6 +863,7 @@ perform_cleanup() { fi echo "===================================================================" + end_section } main() { diff --git a/lib/common.sh b/lib/common.sh old mode 100644 new mode 100755 index b426da0..28c5415 --- a/lib/common.sh +++ b/lib/common.sh @@ -2,20 +2,63 @@ # Mole - Common Functions Library # Shared utilities and functions for all modules -# Color definitions -GREEN='\033[0;32m' -BLUE='\033[0;34m' -YELLOW='\033[1;33m' -PURPLE='\033[0;35m' -RED='\033[0;31m' -NC='\033[0m' +set -euo pipefail -# Logging functions -log_info() { echo -e "${BLUE}$1${NC}"; } -log_success() { echo -e "${GREEN}βœ… $1${NC}"; } -log_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; } -log_error() { echo -e "${RED}❌ $1${NC}"; } -log_header() { echo -e "\n${PURPLE}β–Ά $1${NC}"; } +# Color definitions (readonly for safety) +readonly ESC=$'\033' +readonly GREEN="${ESC}[0;32m" +readonly BLUE="${ESC}[0;34m" +readonly YELLOW="${ESC}[1;33m" +readonly PURPLE="${ESC}[0;35m" +readonly RED="${ESC}[0;31m" +readonly NC="${ESC}[0m" + +# Logging configuration +readonly LOG_FILE="${HOME}/.config/mole/mole.log" +readonly LOG_MAX_SIZE_DEFAULT=1048576 # 1MB + +# Ensure log directory exists +mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true + +# Enhanced logging functions with file logging support +log_info() { + rotate_log + echo -e "${BLUE}$1${NC}" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1" >> "$LOG_FILE" 2>/dev/null || true +} + +log_success() { + rotate_log + echo -e "${GREEN}βœ… $1${NC}" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$LOG_FILE" 2>/dev/null || true +} + +log_warning() { + rotate_log + echo -e "${YELLOW}⚠️ $1${NC}" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1" >> "$LOG_FILE" 2>/dev/null || true +} + +log_error() { + rotate_log + echo -e "${RED}❌ $1${NC}" >&2 + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$LOG_FILE" 2>/dev/null || true +} + +log_header() { + rotate_log + echo -e "\n${PURPLE}β–Ά $1${NC}" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] SECTION: $1" >> "$LOG_FILE" 2>/dev/null || true +} + +# Log file maintenance +rotate_log() { + local max_size="${MOLE_MAX_LOG_SIZE:-$LOG_MAX_SIZE_DEFAULT}" + if [[ -f "$LOG_FILE" ]] && [[ $(stat -f%z "$LOG_FILE" 2>/dev/null || echo 0) -gt "$max_size" ]]; then + mv "$LOG_FILE" "${LOG_FILE}.old" 2>/dev/null || true + touch "$LOG_FILE" 2>/dev/null || true + fi +} # System detection detect_architecture() { @@ -96,9 +139,69 @@ handle_error() { # File size utilities get_human_size() { local path="$1" + if [[ ! -e "$path" ]]; then + echo "N/A" + return 1 + fi du -sh "$path" 2>/dev/null | cut -f1 || echo "N/A" } +# Convert bytes to human readable format +bytes_to_human() { + local bytes="$1" + if [[ ! "$bytes" =~ ^[0-9]+$ ]]; then + echo "0B" + return 1 + fi + + if ((bytes >= 1073741824)); then # >= 1GB + echo "$bytes" | awk '{printf "%.2fGB", $1/1073741824}' + elif ((bytes >= 1048576)); then # >= 1MB + echo "$bytes" | awk '{printf "%.1fMB", $1/1048576}' + elif ((bytes >= 1024)); then # >= 1KB + echo "$bytes" | awk '{printf "%.0fKB", $1/1024}' + else + echo "${bytes}B" + fi +} + +# Calculate directory size in bytes +get_directory_size_bytes() { + local path="$1" + if [[ ! -d "$path" ]]; then + echo "0" + return 1 + fi + du -sk "$path" 2>/dev/null | cut -f1 | awk '{print $1 * 1024}' || echo "0" +} + +# Safe file operation with backup +safe_remove() { + local path="$1" + local backup_dir="${2:-/tmp/mole_backup_$(date +%s)}" + local backup_enabled="${MOLE_BACKUP_ENABLED:-true}" + + if [[ ! -e "$path" ]]; then + return 0 + fi + + if [[ "$backup_enabled" == "true" ]]; then + # Create backup directory if it doesn't exist + mkdir -p "$backup_dir" 2>/dev/null || return 1 + + local basename_path + basename_path=$(basename "$path") + + if ! cp -R "$path" "$backup_dir/$basename_path" 2>/dev/null; then + log_warning "Backup failed for $path, skipping removal" + return 1 + fi + log_info "Backup created at $backup_dir/$basename_path" + fi + + rm -rf "$path" 2>/dev/null || true +} + # Permission checks check_sudo() { if ! sudo -n true 2>/dev/null; then @@ -119,3 +222,166 @@ request_sudo() { return 1 fi } + +# Configuration management +readonly CONFIG_FILE="${HOME}/.config/mole/config" + +# Load configuration with defaults +load_config() { + # Default configuration + MOLE_LOG_LEVEL="${MOLE_LOG_LEVEL:-INFO}" + MOLE_AUTO_CONFIRM="${MOLE_AUTO_CONFIRM:-false}" + MOLE_BACKUP_ENABLED="${MOLE_BACKUP_ENABLED:-true}" + MOLE_MAX_LOG_SIZE="${MOLE_MAX_LOG_SIZE:-1048576}" + MOLE_PARALLEL_JOBS="${MOLE_PARALLEL_JOBS:-}" # Empty means auto-detect + + # Load user configuration if exists + if [[ -f "$CONFIG_FILE" ]]; then + source "$CONFIG_FILE" 2>/dev/null || true + fi +} + +# Save configuration +save_config() { + mkdir -p "$(dirname "$CONFIG_FILE")" 2>/dev/null || return 1 + cat > "$CONFIG_FILE" << EOF +# Mole Configuration File +# Generated on $(date) + +# Log level: DEBUG, INFO, WARNING, ERROR +MOLE_LOG_LEVEL="$MOLE_LOG_LEVEL" + +# Auto confirm operations (true/false) +MOLE_AUTO_CONFIRM="$MOLE_AUTO_CONFIRM" + +# Enable backup before deletion (true/false) +MOLE_BACKUP_ENABLED="$MOLE_BACKUP_ENABLED" + +# Maximum log file size in bytes +MOLE_MAX_LOG_SIZE="$MOLE_MAX_LOG_SIZE" + +# Number of parallel jobs for operations (empty = auto-detect) +MOLE_PARALLEL_JOBS="$MOLE_PARALLEL_JOBS" +EOF +} + +# Progress tracking +# Use parameter expansion for portable global initialization (macOS bash lacks declare -g). +: "${PROGRESS_CURRENT:=0}" +: "${PROGRESS_TOTAL:=0}" +: "${PROGRESS_MESSAGE:=}" + +# Initialize progress tracking +init_progress() { + PROGRESS_CURRENT=0 + PROGRESS_TOTAL="$1" + PROGRESS_MESSAGE="${2:-Processing}" +} + +# Update progress +update_progress() { + PROGRESS_CURRENT="$1" + local message="${2:-$PROGRESS_MESSAGE}" + local percentage=$((PROGRESS_CURRENT * 100 / PROGRESS_TOTAL)) + + # Create progress bar + local bar_length=20 + local filled_length=$((percentage * bar_length / 100)) + local bar="" + + for ((i=0; i/dev/null || true + wait "$SPINNER_PID" 2>/dev/null || true + SPINNER_PID="" + printf "\r\033[K" # Clear the line + fi +} + +# Calculate optimal parallel jobs based on system resources +get_optimal_parallel_jobs() { + local operation_type="${1:-default}" + local optimal_parallel=4 + + # Try to detect optimal parallel jobs based on CPU cores + if command -v nproc >/dev/null 2>&1; then + optimal_parallel=$(nproc) + elif command -v sysctl >/dev/null 2>&1; then + optimal_parallel=$(sysctl -n hw.ncpu 2>/dev/null || echo 4) + fi + + # Apply operation-specific limits + case "$operation_type" in + "scan") + # For scanning: min 2, max 8 + if [[ $optimal_parallel -lt 2 ]]; then + optimal_parallel=2 + elif [[ $optimal_parallel -gt 8 ]]; then + optimal_parallel=8 + fi + ;; + "clean") + # For file operations: min 2, max 6 (more conservative) + if [[ $optimal_parallel -lt 2 ]]; then + optimal_parallel=2 + elif [[ $optimal_parallel -gt 6 ]]; then + optimal_parallel=6 + fi + ;; + *) + # Default: min 2, max 4 (safest) + if [[ $optimal_parallel -lt 2 ]]; then + optimal_parallel=2 + elif [[ $optimal_parallel -gt 4 ]]; then + optimal_parallel=4 + fi + ;; + esac + + # Use configured value if available, otherwise use calculated optimal + if [[ -n "${MOLE_PARALLEL_JOBS:-}" ]]; then + echo "$MOLE_PARALLEL_JOBS" + else + echo "$optimal_parallel" + fi +} + +# Initialize configuration on sourcing +load_config diff --git a/lib/menu.sh b/lib/menu.sh index 6bee66c..b3d3a9d 100755 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -7,8 +7,10 @@ declare -a menu_options=() declare -i selected=0 declare -i menu_size=0 -# ANSI escape sequences -readonly ESC=$'\033' +# ANSI escape sequences (allow reuse when sourced after lib/common.sh) +if [[ -z "${ESC+x}" ]]; then + readonly ESC=$'\033' +fi readonly UP="${ESC}[A" readonly DOWN="${ESC}[B" readonly ENTER=$'\n' diff --git a/mole b/mole index 1589836..bf027a2 100755 --- a/mole +++ b/mole @@ -21,23 +21,27 @@ source "$SCRIPT_DIR/lib/common.sh" # Version info VERSION="1.0.0" +MOLE_TAGLINE="Dig deep like a mole to clean your Mac." + +show_brand_banner() { + printf '%b🦑 %bMOLE%b β€” %b%s%b\n' \ + "$PURPLE" "$BLUE" "$NC" "$GREEN" "$MOLE_TAGLINE" "$NC" +} show_help() { - cat <<'EOF' -Mole can dig deep to clean your mac. -===================================== + show_brand_banner + echo + printf "%s%s%s\n" "$BLUE" "USAGE" "$NC" + printf " %s%s%s [command]\n\n" "$GREEN" "mole" "$NC" -USAGE: - mole [command] + printf "%s%s%s\n" "$BLUE" "COMMANDS" "$NC" + printf " %s%-16s%s %s\n" "$GREEN" "mole" "$NC" "Interactive main menu" + printf " %s%-16s%s %s\n" "$GREEN" "mole clean" "$NC" "Deeper system cleanup" + printf " %s%-16s%s %s\n" "$GREEN" "mole uninstall" "$NC" "Remove applications completely" + printf " %s%-16s%s %s\n" "$GREEN" "mole --help" "$NC" "Show this help message" -COMMANDS: - mole # Interactive main menu - mole clean # Deeper system cleanup - mole uninstall # Remove applications completely - mole --help # Show this help message - -For more information, visit: https://github.com/tw93/mole -EOF + printf "\n%s%s%s\n" "$BLUE" "MORE" "$NC" + printf " https://github.com/tw93/mole\n" } show_main_menu() { @@ -46,7 +50,7 @@ show_main_menu() { if [[ "$redraw_full" == "true" ]]; then echo "" - echo "Mole can dig deep to clean your mac." + show_brand_banner echo "" fi