1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 15:04:42 +00:00

🎨 Make the code more maintainable

This commit is contained in:
Tw93
2025-09-28 17:01:25 +08:00
parent df716abc53
commit 377194086b
6 changed files with 333 additions and 69 deletions

3
.gitignore vendored
View File

@@ -37,4 +37,5 @@ temp/
*.lock
# Claude Code
.claude/
.claude/
CLAUDE.md

View File

@@ -1,23 +1,24 @@
<div align="center">
<img src="https://cdn.tw93.fun/pic/cole.jpg" alt="Mole Logo" width="120" height="120" style="border-radius:50%" />
<img src="https://cdn.tw93.fun/pic/cole.png" alt="Mole Logo" width="120" height="120" style="border-radius:50%" />
<h1 style="margin: 12px 0 6px;">Mole</h1>
<p><em>🧹 Like a mole, dig deep to clean your mac.</em></p>
<p><em>🦡 Dig deep like a mole to clean your Mac.</em></p>
<p style="max-width:480px;">A Bash toolkit that tunnels through caches, leftovers, and forgotten libraries so your macOS stays fast without risking the essentials.</p>
</div>
## 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

View File

@@ -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() {

292
lib/common.sh Normal file → Executable file
View File

@@ -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<filled_length; i++)); do
bar="${bar}"
done
for ((i=filled_length; i<bar_length; i++)); do
bar="${bar}"
done
printf "\r${BLUE}[%s] %3d%% %s (%d/%d)${NC}" "$bar" "$percentage" "$message" "$PROGRESS_CURRENT" "$PROGRESS_TOTAL"
if [[ $PROGRESS_CURRENT -eq $PROGRESS_TOTAL ]]; then
echo
fi
}
# Spinner for indeterminate progress
: "${SPINNER_PID:=}"
start_spinner() {
local message="${1:-Working}"
stop_spinner # Stop any existing spinner
(
local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
local i=0
while true; do
printf "\r${BLUE}%s %s${NC}" "${spin:$i:1}" "$message"
((i++))
if [[ $i -eq ${#spin} ]]; then
i=0
fi
sleep 0.1
done
) &
SPINNER_PID=$!
}
stop_spinner() {
if [[ -n "$SPINNER_PID" ]]; then
kill "$SPINNER_PID" 2>/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

View File

@@ -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'

32
mole
View File

@@ -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