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