mac_cleaner 1.0.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/README.md +52 -0
- data/exe/mac_cleaner +5 -0
- data/lib/common.sh +1751 -0
- data/lib/mac_cleaner/analyzer.rb +156 -0
- data/lib/mac_cleaner/cleaner.rb +497 -0
- data/lib/mac_cleaner/cli.rb +22 -0
- data/lib/mac_cleaner/version.rb +3 -0
- data/lib/mac_cleaner.rb +8 -0
- data/lib/paginated_menu.sh +688 -0
- data/lib/simple_menu.sh +292 -0
- data/lib/whitelist_manager.sh +289 -0
- metadata +72 -4
data/lib/simple_menu.sh
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Paginated menu with arrow key navigation
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
# Terminal control functions
|
|
7
|
+
enter_alt_screen() { tput smcup 2> /dev/null || true; }
|
|
8
|
+
leave_alt_screen() { tput rmcup 2> /dev/null || true; }
|
|
9
|
+
|
|
10
|
+
# Main paginated multi-select menu function
|
|
11
|
+
paginated_multi_select() {
|
|
12
|
+
local title="$1"
|
|
13
|
+
shift
|
|
14
|
+
local -a items=("$@")
|
|
15
|
+
local external_alt_screen=false
|
|
16
|
+
if [[ "${MOLE_MANAGED_ALT_SCREEN:-}" == "1" || "${MOLE_MANAGED_ALT_SCREEN:-}" == "true" ]]; then
|
|
17
|
+
external_alt_screen=true
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Validation
|
|
21
|
+
if [[ ${#items[@]} -eq 0 ]]; then
|
|
22
|
+
echo "No items provided" >&2
|
|
23
|
+
return 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
local total_items=${#items[@]}
|
|
27
|
+
local items_per_page=15
|
|
28
|
+
local cursor_pos=0
|
|
29
|
+
local top_index=0
|
|
30
|
+
local -a selected=()
|
|
31
|
+
|
|
32
|
+
# Initialize selection array
|
|
33
|
+
for ((i = 0; i < total_items; i++)); do
|
|
34
|
+
selected[i]=false
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
if [[ -n "${MOLE_PRESELECTED_INDICES:-}" ]]; then
|
|
38
|
+
local cleaned_preselect="${MOLE_PRESELECTED_INDICES//[[:space:]]/}"
|
|
39
|
+
local -a initial_indices=()
|
|
40
|
+
IFS=',' read -ra initial_indices <<< "$cleaned_preselect"
|
|
41
|
+
for idx in "${initial_indices[@]}"; do
|
|
42
|
+
if [[ "$idx" =~ ^[0-9]+$ && $idx -ge 0 && $idx -lt $total_items ]]; then
|
|
43
|
+
selected[idx]=true
|
|
44
|
+
fi
|
|
45
|
+
done
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Preserve original TTY settings so we can restore them reliably
|
|
49
|
+
local original_stty=""
|
|
50
|
+
if [[ -t 0 ]] && command -v stty > /dev/null 2>&1; then
|
|
51
|
+
original_stty=$(stty -g 2> /dev/null || echo "")
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
restore_terminal() {
|
|
55
|
+
show_cursor
|
|
56
|
+
if [[ -n "${original_stty-}" ]]; then
|
|
57
|
+
stty "${original_stty}" 2> /dev/null || stty sane 2> /dev/null || stty echo icanon 2> /dev/null || true
|
|
58
|
+
else
|
|
59
|
+
stty sane 2> /dev/null || stty echo icanon 2> /dev/null || true
|
|
60
|
+
fi
|
|
61
|
+
if [[ "${external_alt_screen:-false}" == false ]]; then
|
|
62
|
+
leave_alt_screen
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Cleanup function
|
|
67
|
+
cleanup() {
|
|
68
|
+
trap - EXIT INT TERM
|
|
69
|
+
restore_terminal
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Interrupt handler
|
|
73
|
+
handle_interrupt() {
|
|
74
|
+
cleanup
|
|
75
|
+
exit 130 # Standard exit code for Ctrl+C
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
trap cleanup EXIT
|
|
79
|
+
trap handle_interrupt INT TERM
|
|
80
|
+
|
|
81
|
+
# Setup terminal - preserve interrupt character
|
|
82
|
+
stty -echo -icanon intr ^C 2> /dev/null || true
|
|
83
|
+
if [[ $external_alt_screen == false ]]; then
|
|
84
|
+
enter_alt_screen
|
|
85
|
+
# Clear screen once on entry to alt screen
|
|
86
|
+
printf "\033[2J\033[H" >&2
|
|
87
|
+
else
|
|
88
|
+
printf "\033[H" >&2
|
|
89
|
+
fi
|
|
90
|
+
hide_cursor
|
|
91
|
+
|
|
92
|
+
# Helper functions
|
|
93
|
+
print_line() { printf "\r\033[2K%s\n" "$1" >&2; }
|
|
94
|
+
|
|
95
|
+
render_item() {
|
|
96
|
+
local idx=$1 is_current=$2
|
|
97
|
+
local checkbox="$ICON_EMPTY"
|
|
98
|
+
[[ ${selected[idx]} == true ]] && checkbox="$ICON_SOLID"
|
|
99
|
+
|
|
100
|
+
if [[ $is_current == true ]]; then
|
|
101
|
+
printf "\r\033[2K${BLUE}${ICON_ARROW} %s %s${NC}\n" "$checkbox" "${items[idx]}" >&2
|
|
102
|
+
else
|
|
103
|
+
printf "\r\033[2K %s %s\n" "$checkbox" "${items[idx]}" >&2
|
|
104
|
+
fi
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Draw the complete menu
|
|
108
|
+
draw_menu() {
|
|
109
|
+
# Move to home position without clearing (reduces flicker)
|
|
110
|
+
printf "\033[H" >&2
|
|
111
|
+
|
|
112
|
+
# Clear each line as we go instead of clearing entire screen
|
|
113
|
+
local clear_line="\r\033[2K"
|
|
114
|
+
|
|
115
|
+
# Count selections for header display
|
|
116
|
+
local selected_count=0
|
|
117
|
+
for ((i = 0; i < total_items; i++)); do
|
|
118
|
+
[[ ${selected[i]} == true ]] && ((selected_count++))
|
|
119
|
+
done
|
|
120
|
+
|
|
121
|
+
# Header
|
|
122
|
+
printf "${clear_line}${PURPLE}%s${NC} ${GRAY}%d/%d selected${NC}\n" "${title}" "$selected_count" "$total_items" >&2
|
|
123
|
+
|
|
124
|
+
if [[ $total_items -eq 0 ]]; then
|
|
125
|
+
printf "${clear_line}${GRAY}No items available${NC}\n" >&2
|
|
126
|
+
printf "${clear_line}\n" >&2
|
|
127
|
+
printf "${clear_line}${GRAY}Q/ESC${NC} Quit\n" >&2
|
|
128
|
+
printf "${clear_line}" >&2
|
|
129
|
+
return
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
if [[ $top_index -gt $((total_items - 1)) ]]; then
|
|
133
|
+
if [[ $total_items -gt $items_per_page ]]; then
|
|
134
|
+
top_index=$((total_items - items_per_page))
|
|
135
|
+
else
|
|
136
|
+
top_index=0
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
local visible_count=$((total_items - top_index))
|
|
141
|
+
[[ $visible_count -gt $items_per_page ]] && visible_count=$items_per_page
|
|
142
|
+
[[ $visible_count -le 0 ]] && visible_count=1
|
|
143
|
+
if [[ $cursor_pos -ge $visible_count ]]; then
|
|
144
|
+
cursor_pos=$((visible_count - 1))
|
|
145
|
+
[[ $cursor_pos -lt 0 ]] && cursor_pos=0
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
printf "${clear_line}\n" >&2
|
|
149
|
+
|
|
150
|
+
# Items for current window
|
|
151
|
+
local start_idx=$top_index
|
|
152
|
+
local end_idx=$((top_index + items_per_page - 1))
|
|
153
|
+
[[ $end_idx -ge $total_items ]] && end_idx=$((total_items - 1))
|
|
154
|
+
|
|
155
|
+
for ((i = start_idx; i <= end_idx; i++)); do
|
|
156
|
+
[[ $i -lt 0 ]] && continue
|
|
157
|
+
local is_current=false
|
|
158
|
+
[[ $((i - start_idx)) -eq $cursor_pos ]] && is_current=true
|
|
159
|
+
render_item $i $is_current
|
|
160
|
+
done
|
|
161
|
+
|
|
162
|
+
# Fill empty slots to clear previous content
|
|
163
|
+
local items_shown=$((end_idx - start_idx + 1))
|
|
164
|
+
[[ $items_shown -lt 0 ]] && items_shown=0
|
|
165
|
+
for ((i = items_shown; i < items_per_page; i++)); do
|
|
166
|
+
printf "${clear_line}\n" >&2
|
|
167
|
+
done
|
|
168
|
+
|
|
169
|
+
# Clear any remaining lines at bottom
|
|
170
|
+
printf "${clear_line}\n" >&2
|
|
171
|
+
printf "${clear_line}${GRAY}${ICON_NAV_UP}/${ICON_NAV_DOWN}${NC} Navigate ${GRAY}|${NC} ${GRAY}Space${NC} Select ${GRAY}|${NC} ${GRAY}Enter${NC} Confirm ${GRAY}|${NC} ${GRAY}Q/ESC${NC} Quit\n" >&2
|
|
172
|
+
|
|
173
|
+
# Clear one more line to ensure no artifacts
|
|
174
|
+
printf "${clear_line}" >&2
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Show help screen
|
|
178
|
+
show_help() {
|
|
179
|
+
printf "\033[H\033[J" >&2
|
|
180
|
+
cat >&2 << EOF
|
|
181
|
+
Help - Navigation Controls
|
|
182
|
+
==========================
|
|
183
|
+
|
|
184
|
+
${ICON_NAV_UP} / ${ICON_NAV_DOWN} Navigate up/down
|
|
185
|
+
Space Select/deselect item
|
|
186
|
+
Enter Confirm selection
|
|
187
|
+
Q / ESC Exit
|
|
188
|
+
|
|
189
|
+
Press any key to continue...
|
|
190
|
+
EOF
|
|
191
|
+
read -n 1 -s >&2
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Main interaction loop
|
|
195
|
+
while true; do
|
|
196
|
+
draw_menu
|
|
197
|
+
local key=$(read_key)
|
|
198
|
+
|
|
199
|
+
case "$key" in
|
|
200
|
+
"QUIT")
|
|
201
|
+
cleanup
|
|
202
|
+
return 1
|
|
203
|
+
;;
|
|
204
|
+
"UP")
|
|
205
|
+
if [[ $total_items -eq 0 ]]; then
|
|
206
|
+
:
|
|
207
|
+
elif [[ $cursor_pos -gt 0 ]]; then
|
|
208
|
+
((cursor_pos--))
|
|
209
|
+
elif [[ $top_index -gt 0 ]]; then
|
|
210
|
+
((top_index--))
|
|
211
|
+
fi
|
|
212
|
+
;;
|
|
213
|
+
"DOWN")
|
|
214
|
+
if [[ $total_items -eq 0 ]]; then
|
|
215
|
+
:
|
|
216
|
+
else
|
|
217
|
+
local absolute_index=$((top_index + cursor_pos))
|
|
218
|
+
if [[ $absolute_index -lt $((total_items - 1)) ]]; then
|
|
219
|
+
local visible_count=$((total_items - top_index))
|
|
220
|
+
[[ $visible_count -gt $items_per_page ]] && visible_count=$items_per_page
|
|
221
|
+
|
|
222
|
+
if [[ $cursor_pos -lt $((visible_count - 1)) ]]; then
|
|
223
|
+
((cursor_pos++))
|
|
224
|
+
elif [[ $((top_index + visible_count)) -lt $total_items ]]; then
|
|
225
|
+
((top_index++))
|
|
226
|
+
visible_count=$((total_items - top_index))
|
|
227
|
+
[[ $visible_count -gt $items_per_page ]] && visible_count=$items_per_page
|
|
228
|
+
if [[ $cursor_pos -ge $visible_count ]]; then
|
|
229
|
+
cursor_pos=$((visible_count - 1))
|
|
230
|
+
fi
|
|
231
|
+
fi
|
|
232
|
+
fi
|
|
233
|
+
fi
|
|
234
|
+
;;
|
|
235
|
+
"SPACE")
|
|
236
|
+
local idx=$((top_index + cursor_pos))
|
|
237
|
+
if [[ $idx -lt $total_items ]]; then
|
|
238
|
+
if [[ ${selected[idx]} == true ]]; then
|
|
239
|
+
selected[idx]=false
|
|
240
|
+
else
|
|
241
|
+
selected[idx]=true
|
|
242
|
+
fi
|
|
243
|
+
fi
|
|
244
|
+
;;
|
|
245
|
+
"ALL")
|
|
246
|
+
for ((i = 0; i < total_items; i++)); do
|
|
247
|
+
selected[i]=true
|
|
248
|
+
done
|
|
249
|
+
;;
|
|
250
|
+
"NONE")
|
|
251
|
+
for ((i = 0; i < total_items; i++)); do
|
|
252
|
+
selected[i]=false
|
|
253
|
+
done
|
|
254
|
+
;;
|
|
255
|
+
"HELP") show_help ;;
|
|
256
|
+
"ENTER")
|
|
257
|
+
# Store result in global variable instead of returning via stdout
|
|
258
|
+
local -a selected_indices=()
|
|
259
|
+
for ((i = 0; i < total_items; i++)); do
|
|
260
|
+
if [[ ${selected[i]} == true ]]; then
|
|
261
|
+
selected_indices+=("$i")
|
|
262
|
+
fi
|
|
263
|
+
done
|
|
264
|
+
|
|
265
|
+
# Allow empty selection - don't auto-select cursor position
|
|
266
|
+
# This fixes the bug where unselecting all items would still select the last cursor position
|
|
267
|
+
local final_result=""
|
|
268
|
+
if [[ ${#selected_indices[@]} -gt 0 ]]; then
|
|
269
|
+
local IFS=','
|
|
270
|
+
final_result="${selected_indices[*]}"
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
# Remove the trap to avoid cleanup on normal exit
|
|
274
|
+
trap - EXIT INT TERM
|
|
275
|
+
|
|
276
|
+
# Store result in global variable
|
|
277
|
+
MOLE_SELECTION_RESULT="$final_result"
|
|
278
|
+
|
|
279
|
+
# Manually cleanup terminal before returning
|
|
280
|
+
restore_terminal
|
|
281
|
+
|
|
282
|
+
return 0
|
|
283
|
+
;;
|
|
284
|
+
esac
|
|
285
|
+
done
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# Export function for external use
|
|
289
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
290
|
+
echo "This is a library file. Source it from other scripts." >&2
|
|
291
|
+
exit 1
|
|
292
|
+
fi
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Whitelist management functionality
|
|
3
|
+
# Shows actual files that would be deleted by dry-run
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# Get script directory and source dependencies
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/common.sh"
|
|
10
|
+
source "$SCRIPT_DIR/simple_menu.sh"
|
|
11
|
+
|
|
12
|
+
# Config file path
|
|
13
|
+
WHITELIST_CONFIG="$HOME/.config/mole/whitelist"
|
|
14
|
+
|
|
15
|
+
# Default whitelist patterns (preselected on first run)
|
|
16
|
+
declare -a DEFAULT_WHITELIST_PATTERNS=(
|
|
17
|
+
"$HOME/Library/Caches/ms-playwright*"
|
|
18
|
+
"$HOME/.cache/huggingface*"
|
|
19
|
+
"$HOME/.m2/repository/*"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Save whitelist patterns to config
|
|
23
|
+
save_whitelist_patterns() {
|
|
24
|
+
local -a patterns
|
|
25
|
+
patterns=("$@")
|
|
26
|
+
mkdir -p "$(dirname "$WHITELIST_CONFIG")"
|
|
27
|
+
|
|
28
|
+
cat > "$WHITELIST_CONFIG" << 'EOF'
|
|
29
|
+
# Mole Whitelist - Protected paths won't be deleted
|
|
30
|
+
# Default protections: Playwright browsers, HuggingFace models, Maven local repo (can be disabled)
|
|
31
|
+
# Add one pattern per line to keep items safe.
|
|
32
|
+
EOF
|
|
33
|
+
|
|
34
|
+
if [[ ${#patterns[@]} -gt 0 ]]; then
|
|
35
|
+
local -a unique_patterns=()
|
|
36
|
+
for pattern in "${patterns[@]}"; do
|
|
37
|
+
local duplicate="false"
|
|
38
|
+
if [[ ${#unique_patterns[@]} -gt 0 ]]; then
|
|
39
|
+
for existing in "${unique_patterns[@]}"; do
|
|
40
|
+
if patterns_equivalent "$pattern" "$existing"; then
|
|
41
|
+
duplicate="true"
|
|
42
|
+
break
|
|
43
|
+
fi
|
|
44
|
+
done
|
|
45
|
+
fi
|
|
46
|
+
[[ "$duplicate" == "true" ]] && continue
|
|
47
|
+
unique_patterns+=("$pattern")
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
if [[ ${#unique_patterns[@]} -gt 0 ]]; then
|
|
51
|
+
printf '\n' >> "$WHITELIST_CONFIG"
|
|
52
|
+
for pattern in "${unique_patterns[@]}"; do
|
|
53
|
+
echo "$pattern" >> "$WHITELIST_CONFIG"
|
|
54
|
+
done
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Get all cache items with their patterns
|
|
60
|
+
get_all_cache_items() {
|
|
61
|
+
# Format: "display_name|pattern|category"
|
|
62
|
+
cat << 'EOF'
|
|
63
|
+
Apple Mail cache|$HOME/Library/Caches/com.apple.mail/*|system_cache
|
|
64
|
+
Gradle build cache (Android Studio, Gradle projects)|$HOME/.gradle/caches/*|ide_cache
|
|
65
|
+
Gradle daemon processes cache|$HOME/.gradle/daemon/*|ide_cache
|
|
66
|
+
Xcode DerivedData (build outputs, indexes)|$HOME/Library/Developer/Xcode/DerivedData/*|ide_cache
|
|
67
|
+
Xcode internal cache files|$HOME/Library/Caches/com.apple.dt.Xcode/*|ide_cache
|
|
68
|
+
Xcode iOS device support symbols|$HOME/Library/Developer/Xcode/iOS DeviceSupport/*/Symbols/System/Library/Caches/*|ide_cache
|
|
69
|
+
Maven local repository (Java dependencies)|$HOME/.m2/repository/*|ide_cache
|
|
70
|
+
JetBrains IDEs cache (IntelliJ, PyCharm, WebStorm)|$HOME/Library/Caches/JetBrains/*|ide_cache
|
|
71
|
+
Android Studio cache and indexes|$HOME/Library/Caches/Google/AndroidStudio*/*|ide_cache
|
|
72
|
+
VS Code runtime cache|$HOME/Library/Application Support/Code/Cache/*|ide_cache
|
|
73
|
+
VS Code extension and update cache|$HOME/Library/Application Support/Code/CachedData/*|ide_cache
|
|
74
|
+
VS Code system cache (Cursor, VSCodium)|$HOME/Library/Caches/com.microsoft.VSCode/*|ide_cache
|
|
75
|
+
Cursor editor cache|$HOME/Library/Caches/com.todesktop.230313mzl4w4u92/*|ide_cache
|
|
76
|
+
Bazel build cache|$HOME/.cache/bazel/*|compiler_cache
|
|
77
|
+
Go build cache and module cache|$HOME/Library/Caches/go-build/*|compiler_cache
|
|
78
|
+
Rust Cargo registry cache|$HOME/.cargo/registry/cache/*|compiler_cache
|
|
79
|
+
Rustup toolchain downloads|$HOME/.rustup/downloads/*|compiler_cache
|
|
80
|
+
ccache compiler cache|$HOME/.ccache/*|compiler_cache
|
|
81
|
+
sccache distributed compiler cache|$HOME/.cache/sccache/*|compiler_cache
|
|
82
|
+
CocoaPods cache (iOS dependencies)|$HOME/Library/Caches/CocoaPods/*|package_manager
|
|
83
|
+
npm package cache|$HOME/.npm/_cacache/*|package_manager
|
|
84
|
+
pip Python package cache|$HOME/.cache/pip/*|package_manager
|
|
85
|
+
Homebrew downloaded packages|$HOME/Library/Caches/Homebrew/*|package_manager
|
|
86
|
+
Yarn package manager cache|$HOME/.cache/yarn/*|package_manager
|
|
87
|
+
pnpm package store|$HOME/.pnpm-store/*|package_manager
|
|
88
|
+
Composer PHP dependencies cache|$HOME/.composer/cache/*|package_manager
|
|
89
|
+
RubyGems cache|$HOME/.gem/cache/*|package_manager
|
|
90
|
+
Go module cache|$HOME/go/pkg/mod/cache/*|package_manager
|
|
91
|
+
PyTorch model cache|$HOME/.cache/torch/*|ai_ml_cache
|
|
92
|
+
TensorFlow model and dataset cache|$HOME/.cache/tensorflow/*|ai_ml_cache
|
|
93
|
+
HuggingFace models and datasets|$HOME/.cache/huggingface/*|ai_ml_cache
|
|
94
|
+
Playwright browser binaries|$HOME/Library/Caches/ms-playwright*|ai_ml_cache
|
|
95
|
+
Selenium WebDriver binaries|$HOME/.cache/selenium/*|ai_ml_cache
|
|
96
|
+
Ollama local AI models|$HOME/.ollama/models/*|ai_ml_cache
|
|
97
|
+
Safari web browser cache|$HOME/Library/Caches/com.apple.Safari/*|browser_cache
|
|
98
|
+
Chrome browser cache|$HOME/Library/Caches/Google/Chrome/*|browser_cache
|
|
99
|
+
Firefox browser cache|$HOME/Library/Caches/Firefox/*|browser_cache
|
|
100
|
+
Brave browser cache|$HOME/Library/Caches/BraveSoftware/Brave-Browser/*|browser_cache
|
|
101
|
+
Docker Desktop image cache|$HOME/Library/Containers/com.docker.docker/Data/*|container_cache
|
|
102
|
+
Podman container cache|$HOME/.local/share/containers/cache/*|container_cache
|
|
103
|
+
Font cache|$HOME/Library/Caches/com.apple.FontRegistry/*|system_cache
|
|
104
|
+
Spotlight metadata cache|$HOME/Library/Caches/com.apple.spotlight/*|system_cache
|
|
105
|
+
CloudKit cache|$HOME/Library/Caches/CloudKit/*|system_cache
|
|
106
|
+
EOF
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
patterns_equivalent() {
|
|
110
|
+
local first="${1/#~/$HOME}"
|
|
111
|
+
local second="${2/#~/$HOME}"
|
|
112
|
+
|
|
113
|
+
# Only exact string match, no glob expansion
|
|
114
|
+
[[ "$first" == "$second" ]] && return 0
|
|
115
|
+
return 1
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
load_whitelist() {
|
|
119
|
+
local -a patterns=()
|
|
120
|
+
|
|
121
|
+
if [[ -f "$WHITELIST_CONFIG" ]]; then
|
|
122
|
+
while IFS= read -r line; do
|
|
123
|
+
line="${line#${line%%[![:space:]]*}}"
|
|
124
|
+
line="${line%${line##*[![:space:]]}}"
|
|
125
|
+
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
|
126
|
+
patterns+=("$line")
|
|
127
|
+
done < "$WHITELIST_CONFIG"
|
|
128
|
+
else
|
|
129
|
+
patterns=("${DEFAULT_WHITELIST_PATTERNS[@]}")
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
if [[ ${#patterns[@]} -gt 0 ]]; then
|
|
133
|
+
local -a unique_patterns=()
|
|
134
|
+
for pattern in "${patterns[@]}"; do
|
|
135
|
+
local duplicate="false"
|
|
136
|
+
if [[ ${#unique_patterns[@]} -gt 0 ]]; then
|
|
137
|
+
for existing in "${unique_patterns[@]}"; do
|
|
138
|
+
if patterns_equivalent "$pattern" "$existing"; then
|
|
139
|
+
duplicate="true"
|
|
140
|
+
break
|
|
141
|
+
fi
|
|
142
|
+
done
|
|
143
|
+
fi
|
|
144
|
+
[[ "$duplicate" == "true" ]] && continue
|
|
145
|
+
unique_patterns+=("$pattern")
|
|
146
|
+
done
|
|
147
|
+
CURRENT_WHITELIST_PATTERNS=("${unique_patterns[@]}")
|
|
148
|
+
else
|
|
149
|
+
CURRENT_WHITELIST_PATTERNS=()
|
|
150
|
+
fi
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
is_whitelisted() {
|
|
154
|
+
local pattern="$1"
|
|
155
|
+
local check_pattern="${pattern/#\~/$HOME}"
|
|
156
|
+
|
|
157
|
+
if [[ ${#CURRENT_WHITELIST_PATTERNS[@]} -eq 0 ]]; then
|
|
158
|
+
return 1
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
for existing in "${CURRENT_WHITELIST_PATTERNS[@]}"; do
|
|
162
|
+
local existing_expanded="${existing/#\~/$HOME}"
|
|
163
|
+
# Support both exact match and glob pattern match
|
|
164
|
+
# shellcheck disable=SC2053
|
|
165
|
+
if [[ "$check_pattern" == "$existing_expanded" ]] || [[ $check_pattern == $existing_expanded ]]; then
|
|
166
|
+
return 0
|
|
167
|
+
fi
|
|
168
|
+
done
|
|
169
|
+
return 1
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
manage_whitelist() {
|
|
173
|
+
manage_whitelist_categories
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
manage_whitelist_categories() {
|
|
177
|
+
clear
|
|
178
|
+
echo ""
|
|
179
|
+
echo -e "${PURPLE}Whitelist Manager${NC}"
|
|
180
|
+
echo ""
|
|
181
|
+
echo ""
|
|
182
|
+
|
|
183
|
+
# Load currently enabled patterns from both sources
|
|
184
|
+
load_whitelist
|
|
185
|
+
|
|
186
|
+
# Build cache items list
|
|
187
|
+
local -a cache_items=()
|
|
188
|
+
local -a cache_patterns=()
|
|
189
|
+
local -a menu_options=()
|
|
190
|
+
local index=0
|
|
191
|
+
|
|
192
|
+
while IFS='|' read -r display_name pattern _; do
|
|
193
|
+
# Expand $HOME in pattern
|
|
194
|
+
pattern="${pattern/\$HOME/$HOME}"
|
|
195
|
+
|
|
196
|
+
cache_items+=("$display_name")
|
|
197
|
+
cache_patterns+=("$pattern")
|
|
198
|
+
menu_options+=("$display_name")
|
|
199
|
+
|
|
200
|
+
((index++))
|
|
201
|
+
done < <(get_all_cache_items)
|
|
202
|
+
|
|
203
|
+
# Prioritize already-selected items to appear first
|
|
204
|
+
local -a selected_cache_items=()
|
|
205
|
+
local -a selected_cache_patterns=()
|
|
206
|
+
local -a selected_menu_options=()
|
|
207
|
+
local -a remaining_cache_items=()
|
|
208
|
+
local -a remaining_cache_patterns=()
|
|
209
|
+
local -a remaining_menu_options=()
|
|
210
|
+
|
|
211
|
+
for ((i = 0; i < ${#cache_patterns[@]}; i++)); do
|
|
212
|
+
if is_whitelisted "${cache_patterns[i]}"; then
|
|
213
|
+
selected_cache_items+=("${cache_items[i]}")
|
|
214
|
+
selected_cache_patterns+=("${cache_patterns[i]}")
|
|
215
|
+
selected_menu_options+=("${menu_options[i]}")
|
|
216
|
+
else
|
|
217
|
+
remaining_cache_items+=("${cache_items[i]}")
|
|
218
|
+
remaining_cache_patterns+=("${cache_patterns[i]}")
|
|
219
|
+
remaining_menu_options+=("${menu_options[i]}")
|
|
220
|
+
fi
|
|
221
|
+
done
|
|
222
|
+
|
|
223
|
+
cache_items=()
|
|
224
|
+
cache_patterns=()
|
|
225
|
+
menu_options=()
|
|
226
|
+
if [[ ${#selected_cache_items[@]} -gt 0 ]]; then
|
|
227
|
+
cache_items=("${selected_cache_items[@]}")
|
|
228
|
+
cache_patterns=("${selected_cache_patterns[@]}")
|
|
229
|
+
menu_options=("${selected_menu_options[@]}")
|
|
230
|
+
fi
|
|
231
|
+
if [[ ${#remaining_cache_items[@]} -gt 0 ]]; then
|
|
232
|
+
cache_items+=("${remaining_cache_items[@]}")
|
|
233
|
+
cache_patterns+=("${remaining_cache_patterns[@]}")
|
|
234
|
+
menu_options+=("${remaining_menu_options[@]}")
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
if [[ ${#selected_cache_patterns[@]} -gt 0 ]]; then
|
|
238
|
+
local -a preselected_indices=()
|
|
239
|
+
for ((i = 0; i < ${#selected_cache_patterns[@]}; i++)); do
|
|
240
|
+
preselected_indices+=("$i")
|
|
241
|
+
done
|
|
242
|
+
local IFS=','
|
|
243
|
+
export MOLE_PRESELECTED_INDICES="${preselected_indices[*]}"
|
|
244
|
+
else
|
|
245
|
+
unset MOLE_PRESELECTED_INDICES
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
MOLE_SELECTION_RESULT=""
|
|
249
|
+
paginated_multi_select "Whitelist Manager – Select caches to protect" "${menu_options[@]}"
|
|
250
|
+
unset MOLE_PRESELECTED_INDICES
|
|
251
|
+
local exit_code=$?
|
|
252
|
+
|
|
253
|
+
if [[ $exit_code -ne 0 ]]; then
|
|
254
|
+
echo ""
|
|
255
|
+
echo -e "${YELLOW}Cancelled${NC}"
|
|
256
|
+
return 1
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
# Convert selected indices to patterns
|
|
260
|
+
local -a selected_patterns=()
|
|
261
|
+
if [[ -n "$MOLE_SELECTION_RESULT" ]]; then
|
|
262
|
+
local -a selected_indices
|
|
263
|
+
IFS=',' read -ra selected_indices <<< "$MOLE_SELECTION_RESULT"
|
|
264
|
+
for idx in "${selected_indices[@]}"; do
|
|
265
|
+
if [[ $idx -ge 0 && $idx -lt ${#cache_patterns[@]} ]]; then
|
|
266
|
+
local pattern="${cache_patterns[$idx]}"
|
|
267
|
+
# Convert back to portable format with ~
|
|
268
|
+
pattern="${pattern/#$HOME/~}"
|
|
269
|
+
selected_patterns+=("$pattern")
|
|
270
|
+
fi
|
|
271
|
+
done
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
# Save to whitelist config (bash 3.2 + set -u safe)
|
|
275
|
+
if [[ ${#selected_patterns[@]} -gt 0 ]]; then
|
|
276
|
+
save_whitelist_patterns "${selected_patterns[@]}"
|
|
277
|
+
else
|
|
278
|
+
save_whitelist_patterns
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
print_summary_block "success" \
|
|
282
|
+
"Protected ${#selected_patterns[@]} cache(s)" \
|
|
283
|
+
"Saved to ${WHITELIST_CONFIG}"
|
|
284
|
+
printf '\n'
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
288
|
+
manage_whitelist
|
|
289
|
+
fi
|
metadata
CHANGED
|
@@ -1,21 +1,89 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mac_cleaner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ojisanchamchi
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: thor
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: bundler
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.7'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.7'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.2'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.2'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rspec
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.13'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.13'
|
|
12
68
|
description: A script to clean your Mac and analyze disk space.
|
|
13
69
|
email:
|
|
14
70
|
- ojisanchamchi@gmail.com
|
|
15
|
-
executables:
|
|
71
|
+
executables:
|
|
72
|
+
- mac_cleaner
|
|
16
73
|
extensions: []
|
|
17
74
|
extra_rdoc_files: []
|
|
18
|
-
files:
|
|
75
|
+
files:
|
|
76
|
+
- README.md
|
|
77
|
+
- exe/mac_cleaner
|
|
78
|
+
- lib/common.sh
|
|
79
|
+
- lib/mac_cleaner.rb
|
|
80
|
+
- lib/mac_cleaner/analyzer.rb
|
|
81
|
+
- lib/mac_cleaner/cleaner.rb
|
|
82
|
+
- lib/mac_cleaner/cli.rb
|
|
83
|
+
- lib/mac_cleaner/version.rb
|
|
84
|
+
- lib/paginated_menu.sh
|
|
85
|
+
- lib/simple_menu.sh
|
|
86
|
+
- lib/whitelist_manager.sh
|
|
19
87
|
homepage: https://github.com/ojisanchamchi/mac_cleaner
|
|
20
88
|
licenses:
|
|
21
89
|
- MIT
|